Skip to content

Commit

Permalink
Successfully using value classes to tag integers and build a finite f…
Browse files Browse the repository at this point in the history
…ield stub around them.

Following the approach here: Kotlin/KEEP#259 (comment)

Next, we should adapt this to conform with the approaches in kmath, where this is largely already used.
  • Loading branch information
Mikael Vejdemo-Johansson committed Feb 16, 2024
1 parent 2aff8c8 commit 33353b5
Showing 1 changed file with 136 additions and 23 deletions.
159 changes: 136 additions & 23 deletions src/commonTest/kotlin/org/appliedtopology/tda4j/FieldArithmeticSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,133 @@ import io.kotest.assertions.withClue
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.property.checkAll
import space.kscience.kmath.operations.BigIntField
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.Field
import space.kscience.kmath.operations.algebra
import kotlin.jvm.JvmInline
import kotlin.math.roundToInt

fun FiniteField(p: Int) = BigIntField // placeholder stub waiting for an implementation to test
@JvmInline value class IntModP(val value: Int)

interface FiniteFieldContext

inline operator fun <C : FiniteFieldContext, R> C.invoke(block: (C) -> R): R = block(this)

interface FieldOps<X> {
val one: X
val zero: X

fun plus(
left: X,
right: X,
): X

fun minus(
left: X,
right: X,
): X

fun times(
left: X,
right: X,
): X

fun div(
left: X,
right: X,
): X

fun compare(
left: X,
right: X,
): Int

fun number(x: Int): X

fun number(x: Double): X

fun exportToInt(x: X): Int
}

class FieldC<X>(val delegate: FieldOps<X>) : FiniteFieldContext {
val one: X = delegate.one
val zero: X = delegate.zero

inline operator fun X.plus(other: X) = delegate.plus(this@plus, other)

inline operator fun X.minus(other: X) = delegate.minus(this@minus, other)

inline operator fun X.times(other: X) = delegate.times(this@times, other)

inline operator fun X.div(other: X) = delegate.div(this@div, other)

inline fun X.compareTo(other: X): Int = delegate.compare(this@compareTo, other)

inline fun number(x: Double): X = delegate.number(x)

inline fun number(x: Int): X = delegate.number(x)

inline fun X.toInt(): Int = delegate.exportToInt(this)
}

class FiniteField(val p: Int) : FieldOps<IntModP> {
override val one = IntModP(1)
override val zero = IntModP(0)

fun canonical(x: Int): Int {
var value = x % p
if (value < 0) {
value += p
}
if (value > (p - 1) / 2) {
return (-p) + value % p
} else {
return value % p
}
}

override fun exportToInt(self: IntModP): Int = canonical(self.value)

override fun plus(
left: IntModP,
right: IntModP,
): IntModP = IntModP((left.value + right.value) % p)

override fun minus(
left: IntModP,
right: IntModP,
): IntModP = IntModP((left.value - right.value) % p)

override fun times(
left: IntModP,
right: IntModP,
): IntModP = IntModP((left.value * right.value) % p)

override fun div(
left: IntModP,
right: IntModP,
): IntModP = IntModP((left.value / right.value) % p) // this is wrong

override fun compare(
left: IntModP,
right: IntModP,
): Int = exportToInt(left).compareTo(exportToInt(right))

override fun number(x: Int): IntModP = IntModP(canonical(x))

override fun number(x: Double): IntModP = number(x.roundToInt())
}

val FiniteField.algebra
get() = FieldC(this)

// I'm not at all sure this is how to write a function for this
fun <T, U : Field<T>> dosomething(
fun <T, U : FieldC<T>> U.dosomething(
a: T,
b: T,
field: U,
): T =
with(field) {
a - b
}
): T = a - b

class FieldArithmeticSpec : StringSpec({
"Computing with doubles" {
with(DoubleField) {
with(Double.algebra) {
withClue("Should be commutative") {
checkAll<Pair<Double, Double>> {
val a = number(it.first)
Expand All @@ -33,16 +141,16 @@ class FieldArithmeticSpec : StringSpec({
}

withClue("5+13") {
(5 * one + 13 * one).shouldBeEqual(18 * one)
(number(5) + number(13)).shouldBeEqual(number(18))
}

withClue("5-13") {
(5 * one - 13 * one).shouldBeEqual(-8 * one)
(number(5) - number(13)).shouldBeEqual(number(-8))
}

withClue("calling a function") {
dosomething(5 * one, 13 * one, this).shouldBeEqual(-8 * one)
}
// withClue("calling a function") {
// dosomething(number(5), number(13)).shouldBeEqual(number(-8))
// }

withClue("Converting to a string should give the normalized range") {
(3 * one).toString().shouldBeEqual("3.0")
Expand All @@ -53,7 +161,8 @@ class FieldArithmeticSpec : StringSpec({
}

"Computing with finite fields" {
with(FiniteField(17)) {
val ff17 = FiniteField(17)
with(ff17.algebra) {
withClue("Should be commutative") {
checkAll<Pair<Int, Int>> {
val a = number(it.first)
Expand All @@ -64,21 +173,25 @@ class FieldArithmeticSpec : StringSpec({
}

withClue("5+13") {
(5 * one + 17 * one).shouldBeEqual(1 * one)
val a = number(5)
val b = number(13)
val x = a + b
x + x
(number(5) + number(13)).shouldBeEqual(one)
}

withClue("5-13") {
(5 * one - 17 * one).shouldBeEqual(9 * one)
(number(5) - number(13)).toInt().shouldBeEqual(number(9).toInt())
}

withClue("calling a function") {
dosomething(5 * one, 13 * one, this).shouldBeEqual(9 * one)
dosomething(number(5), number(13)).toInt().shouldBeEqual(number(9).toInt())
}

withClue("Converting to a string should give the normalized range") {
(3 * one).toString().shouldBeEqual("3u")
(-3 * one).toString().shouldBeEqual("-3u")
(14 * one).toString().shouldBeEqual("-3u")
number(3).toString().shouldBeEqual("IntModP(value=3)")
number(-3).toString().shouldBeEqual("IntModP(value=-3)")
number(14).toString().shouldBeEqual("IntModP(value=-3)")
}
}
}
Expand Down

0 comments on commit 33353b5

Please sign in to comment.