diff --git a/README.md b/README.md index 0df279889..34761e838 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/scientifik/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) - -Bintray-dev: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) - +[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![DOI](https://zenodo.org/badge/129486382.svg)](https://zenodo.org/badge/latestdoi/129486382) +![Gradle build](https://github.com/mipt-npm/kmath/workflows/Gradle%20build/badge.svg) + +Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/scientifik/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) + +Bintray-dev: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) + # KMath Could be pronounced as `key-math`. The Kotlin MATHematics library is intended as a Kotlin-based analog to Python's `numpy` library. In contrast to `numpy` and `scipy` it is modular and has a lightweight core. diff --git a/build.gradle.kts b/build.gradle.kts index a63966dfe..f3a3c13d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("scientifik.publish") version "0.2.6" apply false + id("scientifik.publish") version "0.3.1" apply false } val kmathVersion by extra("0.1.4-dev-1") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 0ed769db8..485185526 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -6,14 +6,14 @@ annotation class KMathContext /** * Marker interface for any algebra */ -interface Algebra +interface Algebra -inline operator fun T.invoke(block: T.() -> R): R = run(block) +inline operator fun , R> T.invoke(block: T.() -> R): R = run(block) /** * Space-like operations without neutral element */ -interface SpaceOperations : Algebra { +interface SpaceOperations : Algebra { /** * Addition operation for two context elements */ diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt index 4e8dbf36d..1e0453f08 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt @@ -1,4 +1,13 @@ package scientifik.kmath.operations -fun Space.sum(data : Iterable): T = data.fold(zero) { left, right -> add(left,right) } -fun Space.sum(data : Sequence): T = data.fold(zero) { left, right -> add(left, right) } \ No newline at end of file +fun Space.sum(data: Iterable): T = data.fold(zero) { left, right -> add(left, right) } +fun Space.sum(data: Sequence): T = data.fold(zero) { left, right -> add(left, right) } + +//TODO optimized power operation +fun RingOperations.power(arg: T, power: Int): T { + var res = arg + repeat(power - 1) { + res *= arg + } + return res +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt index 2a77e36ef..bd83932e7 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -29,7 +29,7 @@ fun >> ctg(arg: T): T = arg.conte /** * A context extension to include power operations like square roots, etc */ -interface PowerOperations { +interface PowerOperations : Algebra { fun power(arg: T, pow: Number): T fun sqrt(arg: T) = power(arg, 0.5) @@ -42,7 +42,7 @@ fun >> sqr(arg: T): T = arg pow 2.0 /* Exponential */ -interface ExponentialOperations { +interface ExponentialOperations: Algebra { fun exp(arg: T): T fun ln(arg: T): T } diff --git a/kmath-functions/build.gradle.kts b/kmath-functions/build.gradle.kts index 373d9b8ac..4c158a32e 100644 --- a/kmath-functions/build.gradle.kts +++ b/kmath-functions/build.gradle.kts @@ -1,23 +1,11 @@ plugins { id("scientifik.mpp") - //id("scientifik.atomic") } kotlin.sourceSets { commonMain { dependencies { api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Scientifik.coroutinesVersion}") - } - } - jvmMain { - dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Scientifik.coroutinesVersion}") - } - } - jsMain { - dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Scientifik.coroutinesVersion}") } } } diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt deleted file mode 100644 index d1263e58b..000000000 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt +++ /dev/null @@ -1,2 +0,0 @@ -package scientifik.kmath.functions - diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt index 855ed26cd..a05462863 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt @@ -1,4 +1,94 @@ package scientifik.kmath.functions -interface Polynomial { -} \ No newline at end of file +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space +import kotlin.jvm.JvmName +import kotlin.math.max +import kotlin.math.pow + +/** + * Polynomial coefficients without fixation on specific context they are applied to + * @param coefficients constant is the leftmost coefficient + */ +inline class Polynomial(val coefficients: List) { + constructor(vararg coefficients: T) : this(coefficients.toList()) +} + +fun Polynomial.value() = + coefficients.reduceIndexed { index: Int, acc: Double, d: Double -> acc + d.pow(index) } + + +fun > Polynomial.value(ring: C, arg: T): T = ring.run { + if( coefficients.isEmpty()) return@run zero + var res = coefficients.first() + var powerArg = arg + for( index in 1 until coefficients.size){ + res += coefficients[index]*powerArg + //recalculating power on each step to avoid power costs on long polynomials + powerArg *= arg + } + return@run res +} + +/** + * Represent a polynomial as a context-dependent function + */ +fun > Polynomial.asMathFunction(): MathFunction = object : MathFunction { + override fun C.invoke(arg: T): T = value(this, arg) +} + +/** + * Represent the polynomial as a regular context-less function + */ +fun > Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } + +@JvmName("asRealUFunction") +fun Polynomial.asFunction(): (Double) -> Double = asFunction(RealField) + +/** + * An algebra for polynomials + */ +class PolynomialSpace>(val ring: C) : Space> { + + override fun add(a: Polynomial, b: Polynomial): Polynomial { + val dim = max(a.coefficients.size, b.coefficients.size) + ring.run { + return Polynomial(List(dim) { index -> + a.coefficients.getOrElse(index) { zero } + b.coefficients.getOrElse(index) { zero } + }) + } + } + + override fun multiply(a: Polynomial, k: Number): Polynomial { + ring.run { + return Polynomial(List(a.coefficients.size) { index -> a.coefficients[index] * k }) + } + } + + override val zero: Polynomial = Polynomial(emptyList()) + + operator fun Polynomial.invoke(arg: T): T = value(ring, arg) +} + +fun , R> C.polynomial(block: PolynomialSpace.() -> R): R { + return PolynomialSpace(this).run(block) +} + +class PiecewisePolynomial> internal constructor( + val lowerBoundary: T, + val pieces: List>> +) + +private fun > PiecewisePolynomial.findPiece(arg: T): Polynomial? { + if (arg < lowerBoundary || arg > pieces.last().first) return null + return pieces.first { arg < it.first }.second +} + +/** + * Return a value of polynomial function with given [ring] an given [arg] or null if argument is outside of piecewise definition. + */ +fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = + findPiece(arg)?.value(ring, arg) + +fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt index c26443926..2b822b3ba 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt @@ -1,54 +1,33 @@ -package scientifik.kmath.misc +package scientifik.kmath.functions +import scientifik.kmath.operations.Algebra import scientifik.kmath.operations.RealField -import scientifik.kmath.operations.SpaceOperations -import kotlin.jvm.JvmName /** * A regular function that could be called only inside specific algebra context + * @param T source type + * @param C source algebra constraint + * @param R result type */ -interface UFunction> { - operator fun C.invoke(arg: T): T +interface MathFunction, R> { + operator fun C.invoke(arg: T): R } +fun MathFunction.invoke(arg: Double): R = RealField.invoke(arg) + /** - * A suspendable univariate function defined in algebraic context + * A suspendable function defined in algebraic context */ -interface USFunction> { - suspend operator fun C.invoke(arg: T): T +interface SuspendableMathFunction, R> { + suspend operator fun C.invoke(arg: T): R } -suspend fun USFunction.invoke(arg: Double) = RealField.invoke(arg) +suspend fun SuspendableMathFunction.invoke(arg: Double) = RealField.invoke(arg) -interface MFunction> { - /** - * The input dimension of the function - */ - val dimension: UInt - - operator fun C.invoke(vararg args: T): T -} - /** - * A suspendable multivariate (N->1) function defined on algebraic context + * A parametric function with parameter */ -interface MSFunction> { - /** - * The input dimension of the function - */ - val dimension: UInt - - suspend operator fun C.invoke(vararg args: T): T -} - -suspend fun MSFunction.invoke(args: DoubleArray) = RealField.invoke(*args.toTypedArray()) -@JvmName("varargInvoke") -suspend fun MSFunction.invoke(vararg args: Double) = RealField.invoke(*args.toTypedArray()) - -/** - * A suspendable parametric function with parameter - */ -interface PSFunction> { - suspend operator fun C.invoke(arg: T, parameter: P): T +interface ParametricFunction> { + operator fun C.invoke(arg: T, parameter: P): T } diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt index 0b1ca7795..1df9df5d7 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt @@ -1,7 +1,21 @@ package scientifik.kmath.interpolation -import scientifik.kmath.functions.MathFunction +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.value +import scientifik.kmath.operations.Ring interface Interpolator { - fun interpolate(points: Collection>): MathFunction + fun interpolate(points: Collection>): (X) -> Y +} + +interface PolynomialInterpolator> : Interpolator { + val algebra: Ring + + fun getDefaultValue(): T = error("Out of bounds") + + fun interpolatePolynomials(points: Collection>): PiecewisePolynomial + + override fun interpolate(points: Collection>): (T) -> T = { x -> + interpolatePolynomials(points).value(algebra, x) ?: getDefaultValue() + } } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt index 4db9da7b6..2fc8aaccc 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt @@ -1,4 +1,24 @@ package scientifik.kmath.interpolation -class LinearInterpolator { +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.Polynomial +import scientifik.kmath.operations.Field + +/** + * Reference JVM implementation: https://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/analysis/interpolation/LinearInterpolator.java + */ +class LinearInterpolator>(override val algebra: Field) : PolynomialInterpolator { + + override fun interpolatePolynomials(points: Collection>): PiecewisePolynomial = algebra.run { + //sorting points + val sorted = points.sortedBy { it.first } + + val pairs: List>> = (0 until points.size - 1).map { i -> + val slope = (sorted[i + 1].second - sorted[i].second) / (sorted[i + 1].first - sorted[i].first) + val const = sorted[i].second - slope * sorted[i].first + sorted[i + 1].first to Polynomial(const, slope) + } + + return PiecewisePolynomial(sorted.first().first, pairs) + } } \ No newline at end of file diff --git a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt index 34202b388..71303107e 100644 --- a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt +++ b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt @@ -1,5 +1,26 @@ package scientifik.kmath.interpolation -import org.junit.Assert.* +import scientifik.kmath.functions.asFunction +import scientifik.kmath.operations.RealField +import kotlin.test.Test +import kotlin.test.assertEquals -class LinearInterpolatorTest \ No newline at end of file + +class LinearInterpolatorTest { + @Test + fun testInterpolation() { + val data = listOf( + 0.0 to 0.0, + 1.0 to 1.0, + 2.0 to 3.0, + 3.0 to 4.0 + ) + val polynomial = LinearInterpolator(RealField).interpolatePolynomials(data) + val function = polynomial.asFunction(RealField) + +// assertEquals(null, function(-1.0)) +// assertEquals(0.5, function(0.5)) + assertEquals(2.0, function(1.5)) + assertEquals(3.0, function(2.0)) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e85b32fd2..fdb140541 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,10 +1,10 @@ pluginManagement { plugins { - id("scientifik.mpp") version "0.2.5" - id("scientifik.jvm") version "0.2.5" - id("scientifik.atomic") version "0.2.5" - id("scientifik.publish") version "0.2.5" + id("scientifik.mpp") version "0.3.1" + id("scientifik.jvm") version "0.3.1" + id("scientifik.atomic") version "0.3.1" + id("scientifik.publish") version "0.3.1" } repositories { @@ -19,7 +19,7 @@ pluginManagement { resolutionStrategy { eachPlugin { when (requested.id.id) { - "scientifik.mpp", "scientifik.publish" -> useModule("scientifik:gradle-tools:${requested.version}") + "scientifik.mpp", "scientifik.jvm", "scientifik.publish" -> useModule("scientifik:gradle-tools:${requested.version}") } } } @@ -29,6 +29,7 @@ rootProject.name = "kmath" include( ":kmath-memory", ":kmath-core", + ":kmath-functions", // ":kmath-io", ":kmath-coroutines", ":kmath-histograms",