From 0d1825a0444d9b5d57ef044cfaee77c97bd75e96 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 18 Jul 2018 12:52:58 +0300 Subject: [PATCH] Optional math operations. --- .../scientifik/kmath/operations/Algebra.kt | 42 +++++++++------- .../scientifik/kmath/operations/Fields.kt | 39 ++++++++++----- .../kmath/operations/OptionalOperations.kt | 48 +++++++++++++++++++ .../kmath/operations/RealFieldTest.kt | 14 ++++++ 4 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt create mode 100644 common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt b/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt index 71147d56e..6f06f5980 100644 --- a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt @@ -1,5 +1,16 @@ package scientifik.kmath.operations + +/** + * The generic mathematics elements which is able to store its context + */ +interface MathElement{ + /** + * The context this element belongs to + */ + val context: S +} + /** * A general interface representing linear context of some kind. * The context defines sum operation for its elements and multiplication by real value. @@ -37,23 +48,20 @@ interface Space { /** * The element of linear context - * @param S self type of the element. Needed for static type checking + * @param T self type of the element. Needed for static type checking + * @param S the type of space */ -interface SpaceElement> { - /** - * The context this element belongs to - */ - val context: Space +interface SpaceElement>: MathElement { /** * Self value. Needed for static type checking. Needed to avoid type erasure on JVM. */ - val self: S + val self: T - operator fun plus(b: S): S = context.add(self, b) - operator fun minus(b: S): S = context.add(self, context.multiply(b, -1.0)) - operator fun times(k: Number): S = context.multiply(self, k.toDouble()) - operator fun div(k: Number): S = context.multiply(self, 1.0 / k.toDouble()) + operator fun plus(b: T): T = context.add(self, b) + operator fun minus(b: T): T = context.add(self, context.multiply(b, -1.0)) + operator fun times(k: Number): T = context.multiply(self, k.toDouble()) + operator fun div(k: Number): T = context.multiply(self, 1.0 / k.toDouble()) } /** @@ -77,10 +85,10 @@ interface Ring : Space { /** * Ring element */ -interface RingElement> : SpaceElement { - override val context: Ring +interface RingElement> : SpaceElement { + override val context: S - operator fun times(b: S): S = context.multiply(self, b) + operator fun times(b: T): T = context.multiply(self, b) } /** @@ -96,8 +104,8 @@ interface Field : Ring { /** * Field element */ -interface FieldElement> : RingElement { - override val context: Field +interface FieldElement> : RingElement { + override val context: S - operator fun div(b: S): S = context.divide(self, b) + operator fun div(b: T): T = context.divide(self, b) } \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt b/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt index 7c9e2ddb7..2f3eb99ff 100644 --- a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt +++ b/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt @@ -1,23 +1,39 @@ package scientifik.kmath.operations +import kotlin.math.pow import kotlin.math.sqrt /** * Field for real values */ -object RealField : Field { +object RealField : Field, TrigonometricOperations, PowerOperations, ExponentialOperations { override val zero: Real = Real(0.0) override fun add(a: Real, b: Real): Real = Real(a.value + b.value) override val one: Real = Real(1.0) override fun multiply(a: Real, b: Real): Real = Real(a.value * b.value) override fun multiply(a: Real, k: Double): Real = Real(a.value * k) override fun divide(a: Real, b: Real): Real = Real(a.value / b.value) + + override fun sin(arg: Real): Real = Real(kotlin.math.sin(arg.value)) + override fun cos(arg: Real): Real = Real(kotlin.math.cos(arg.value)) + + override fun power(arg: Real, pow: Double): Real = Real(arg.value.pow(pow)) + + override fun exp(arg: Real): Real = Real(kotlin.math.exp(arg.value)) + + override fun ln(arg: Real): Real = Real(kotlin.math.ln(arg.value)) } /** - * Real field element wrapping double + * Real field element wrapping double. + * + * TODO could be replaced by inline class in kotlin 1.3 if it would allow to avoid boxing */ -class Real(val value: Double) : FieldElement, Number() { +class Real(val value: Double) : Number(), FieldElement { + /* + * The class uses composition instead of inheritance since Double is final + */ + override fun toByte(): Byte = value.toByte() override fun toChar(): Char = value.toChar() override fun toDouble(): Double = value @@ -29,8 +45,10 @@ class Real(val value: Double) : FieldElement, Number() { //values are dynamically calculated to save memory override val self get() = this + override val context get() = RealField + } /** @@ -54,10 +72,9 @@ object ComplexField : Field { /** * Complex number class */ -data class Complex(val re: Double, val im: Double) : FieldElement { - override val self: Complex - get() = this - override val context: Field +data class Complex(val re: Double, val im: Double) : FieldElement { + override val self: Complex get() = this + override val context: ComplexField get() = ComplexField /** @@ -72,15 +89,15 @@ data class Complex(val re: Double, val im: Double) : FieldElement { val module: Double get() = sqrt(square) - - //TODO is it convenient? - operator fun not() = conjugate } +/** + * A field for double without boxing. Does not produce appropriate field element + */ object DoubleField : Field { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b - override fun multiply(a: Double, b: Double): Double = a * b + override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b override val one: Double = 1.0 override fun divide(a: Double, b: Double): Double = a / b } \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt new file mode 100644 index 000000000..18ef89407 --- /dev/null +++ b/common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -0,0 +1,48 @@ +package scientifik.kmath.operations + + +/* Trigonometric operations */ + +/** + * A container for trigonometric operations for specific type. Trigonometric operations are limited to fields. + * + * The operations are not exposed to class directly to avoid method bloat but instead are declared in the field. + * It also allows to override behavior for optional operations + * + */ +interface TrigonometricOperations: Field { + fun sin(arg: T): T + fun cos(arg: T): T + + fun tg(arg: T): T = sin(arg) / cos(arg) + + fun ctg(arg: T): T = cos(arg) / sin(arg) +} + +fun >> sin(arg: T): T = arg.context.sin(arg) +fun >> cos(arg: T): T = arg.context.cos(arg) +fun >> tg(arg: T): T = arg.context.tg(arg) +fun >> ctg(arg: T): T = arg.context.ctg(arg) + +/* Power and roots */ + +/** + * A context extension to include power operations like square roots, etc + */ +interface PowerOperations { + fun power(arg: T, pow: Double): T +} + +infix fun >> T.pow(power: Double): T = context.power(this, power) +fun >> sqrt(arg: T): T = arg pow 0.5 +fun >> sqr(arg: T): T = arg pow 2.0 + +/* Exponential */ + +interface ExponentialOperations{ + fun exp(arg: T): T + fun ln(arg: T): T +} + +fun >> exp(arg:T): T = arg.context.exp(arg) +fun >> ln(arg:T): T = arg.context.ln(arg) \ No newline at end of file diff --git a/common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt new file mode 100644 index 000000000..b07158cc3 --- /dev/null +++ b/common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -0,0 +1,14 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertEquals + +class RealFieldTest { + @Test + fun testSqrt() { + val sqrt = with(RealField) { + sqrt(25 * one) + } + assertEquals(5.0, sqrt.toDouble()) + } +} \ No newline at end of file