From 2483c56f1c95180e168bd564be6131122a6c8cfe Mon Sep 17 00:00:00 2001 From: Gleb Minaev <43728100+lounres@users.noreply.github.com> Date: Thu, 3 Mar 2022 20:45:35 +0300 Subject: [PATCH 1/4] Restructured Polynomial --- .../kmath/functions/AbstractPolynomial.kt | 354 +++++++++++++++ .../kscience/kmath/functions/Piecewise.kt | 8 +- .../kscience/kmath/functions/Polynomial.kt | 425 ++++++++++++++---- .../kmath/functions/polynomialUtil.kt | 139 ++++++ .../kmath/integration/SplineIntegrator.kt | 3 +- .../kmath/interpolation/Interpolator.kt | 4 +- .../kmath/functions/PolynomialTest.kt | 11 +- kotlin-js-store/yarn.lock | 56 +-- 8 files changed, 846 insertions(+), 154 deletions(-) create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt new file mode 100644 index 000000000..f6a617656 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt @@ -0,0 +1,354 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.functions + +import space.kscience.kmath.operations.* +import kotlin.js.JsName +import kotlin.jvm.JvmName + + +/** + * Abstraction of polynomials. + */ +public interface AbstractPolynomial + +/** + * Abstraction of ring of polynomials of type [P] over ring of constants of type [C]. + */ +@Suppress("INAPPLICABLE_JVM_NAME", "PARAMETER_NAME_CHANGED_ON_OVERRIDE") +public interface AbstractPolynomialSpace> : Ring

{ + // region Constant-integer relation + /** + * Returns sum of the constant and the integer represented as constant (member of underlying ring). + * + * The operation is equivalent to adding [other] copies of unit of underlying ring to [this]. + */ + @JvmName("constantIntPlus") + public operator fun C.plus(other: Int): C + /** + * Returns difference between the constant and the integer represented as constant (member of underlying ring). + * + * The operation is equivalent to subtraction [other] copies of unit of underlying ring from [this]. + */ + @JvmName("constantIntMinus") + public operator fun C.minus(other: Int): C + /** + * Returns product of the constant and the integer represented as constant (member of underlying ring). + * + * The operation is equivalent to sum of [other] copies of [this]. + */ + @JvmName("constantIntTimes") + public operator fun C.times(other: Int): C + // endregion + + // region Integer-constant relation + /** + * Returns sum of the integer represented as constant (member of underlying ring) and the constant. + * + * The operation is equivalent to adding [this] copies of unit of underlying ring to [other]. + */ + @JvmName("intConstantPlus") + public operator fun Int.plus(other: C): C + /** + * Returns difference between the integer represented as constant (member of underlying ring) and the constant. + * + * The operation is equivalent to subtraction [this] copies of unit of underlying ring from [other]. + */ + @JvmName("intConstantMinus") + public operator fun Int.minus(other: C): C + /** + * Returns product of the integer represented as constant (member of underlying ring) and the constant. + * + * The operation is equivalent to sum of [this] copies of [other]. + */ + @JvmName("intConstantTimes") + public operator fun Int.times(other: C): C + // endregion + + // region Polynomial-integer relation + /** + * Returns sum of the constant and the integer represented as polynomial. + * + * The operation is equivalent to adding [other] copies of unit polynomial to [this]. + */ + public operator fun P.plus(other: Int): P = optimizedAddMultiplied(this, one, other) + /** + * Returns difference between the constant and the integer represented as polynomial. + * + * The operation is equivalent to subtraction [other] copies of unit polynomial from [this]. + */ + public operator fun P.minus(other: Int): P = optimizedAddMultiplied(this, one, -other) + /** + * Returns product of the constant and the integer represented as polynomial. + * + * The operation is equivalent to sum of [other] copies of [this]. + */ + public operator fun P.times(other: Int): P = optimizedMultiply(this, other) + // endregion + + // region Integer-polynomial relation + /** + * Returns sum of the integer represented as polynomial and the constant. + * + * The operation is equivalent to adding [this] copies of unit polynomial to [other]. + */ + public operator fun Int.plus(other: P): P = optimizedAddMultiplied(other, one, this) + /** + * Returns difference between the integer represented as polynomial and the constant. + * + * The operation is equivalent to subtraction [this] copies of unit polynomial from [other]. + */ + public operator fun Int.minus(other: P): P = optimizedAddMultiplied(-other, one, this) + /** + * Returns product of the integer represented as polynomial and the constant. + * + * The operation is equivalent to sum of [this] copies of [other]. + */ + public operator fun Int.times(other: P): P = optimizedMultiply(other, this) + // endregion + + // region Constant-constant relation + /** + * Returns the same constant. + */ + @JvmName("constantUnaryPlus") + @JsName("constantUnaryPlus") + public operator fun C.unaryPlus(): C = this + /** + * Returns negation of the constant. + */ + @JvmName("constantUnaryMinus") + @JsName("constantUnaryMinus") + public operator fun C.unaryMinus(): C + /** + * Returns sum of the constants. + */ + @JvmName("constantPlus") + @JsName("constantPlus") + public operator fun C.plus(other: C): C + /** + * Returns difference of the constants. + */ + @JvmName("constantMinus") + @JsName("constantMinus") + public operator fun C.minus(other: C): C + /** + * Returns product of the constants. + */ + @JvmName("constantTimes") + @JsName("constantTimes") + public operator fun C.times(other: C): C + + /** + * Check if the instant is zero constant. + */ + @JvmName("constantIsZero") + public fun C.isZero(): Boolean + /** + * Check if the instant is NOT zero constant. + */ + @JvmName("constantIsNotZero") + public fun C.isNotZero(): Boolean + /** + * Check if the instant is unit constant. + */ + @JvmName("constantIsOne") + public fun C.isOne(): Boolean + /** + * Check if the instant is NOT unit constant. + */ + @JvmName("constantIsNotOne") + public fun C.isNotOne(): Boolean + /** + * Check if the instant is minus unit constant. + */ + @JvmName("constantIsMinusOne") + public fun C.isMinusOne(): Boolean + /** + * Check if the instant is NOT minus unit constant. + */ + @JvmName("constantIsNotMinusOne") + public fun C.isNotMinusOne(): Boolean + // endregion + + // region Constant-polynomial relation + /** + * Returns sum of the constant represented as polynomial and the polynomial. + */ + public operator fun C.plus(other: P): P + /** + * Returns difference between the constant represented as polynomial and the polynomial. + */ + public operator fun C.minus(other: P): P + /** + * Returns product of the constant represented as polynomial and the polynomial. + */ + public operator fun C.times(other: P): P + // endregion + + // region Polynomial-constant relation + /** + * Returns sum of the constant represented as polynomial and the polynomial. + */ + public operator fun P.plus(other: C): P + /** + * Returns difference between the constant represented as polynomial and the polynomial. + */ + public operator fun P.minus(other: C): P + /** + * Returns product of the constant represented as polynomial and the polynomial. + */ + public operator fun P.times(other: C): P + // endregion + + // region Polynomial-polynomial relation + /** + * Returns the same polynomial. + */ + public override operator fun P.unaryPlus(): P = this + /** + * Returns negation of the polynomial. + */ + public override operator fun P.unaryMinus(): P + /** + * Returns sum of the polynomials. + */ + public override operator fun P.plus(other: P): P + /** + * Returns difference of the polynomials. + */ + public override operator fun P.minus(other: P): P + /** + * Returns product of the polynomials. + */ + public override operator fun P.times(other: P): P + + /** + * Check if the instant is zero polynomial. + */ + public fun P.isZero(): Boolean = this == zero + /** + * Check if the instant is NOT zero polynomial. + */ + public fun P.isNotZero(): Boolean = this != zero + /** + * Check if the instant is unit polynomial. + */ + public fun P.isOne(): Boolean = this == one + /** + * Check if the instant is NOT unit polynomial. + */ + public fun P.isNotOne(): Boolean = this != one + /** + * Check if the instant is minus unit polynomial. + */ + public fun P.isMinusOne(): Boolean = this == -one + /** + * Check if the instant is NOT minus unit polynomial. + */ + public fun P.isNotMinusOne(): Boolean = this != -one + + /** + * Instance of zero polynomial (zero of the polynomial ring). + */ + override val zero: P + /** + * Instance of unit polynomial (unit of the polynomial ring). + */ + override val one: P + + /** + * Checks equality of the polynomials. + */ + @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "CovariantEquals") + public fun P.equals(other: P): Boolean + // endregion + + // Not sure is it necessary... + // region Polynomial properties + /** + * Degree of the polynomial, [see also](https://en.wikipedia.org/wiki/Degree_of_a_polynomial). If the polynomial is + * zero, degree is -1. + */ + public val P.degree: Int + + /** + * Checks if the instant is constant polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isConstant(): Boolean = degree <= 0 + /** + * Checks if the instant is **not** constant polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isNotConstant(): Boolean = !isConstant() + /** + * Checks if the instant is constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isNonZeroConstant(): Boolean = degree == 0 + /** + * Checks if the instant is **not** constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isNotNonZeroConstant(): Boolean = !isNonZeroConstant() + /** + * If polynomial is a constant polynomial represents and returns it as constant. + * Otherwise, (when the polynomial is not constant polynomial) returns `null`. + */ + public fun P.asConstantOrNull(): C? + /** + * If polynomial is a constant polynomial represents and returns it as constant. + * Otherwise, (when the polynomial is not constant polynomial) raises corresponding exception. + */ + public fun P.asConstant(): C = asConstantOrNull() ?: error("Can not represent non-constant polynomial as a constant") + // endregion + + // region Legacy of Ring interface + override fun add(left: P, right: P): P = left + right + override fun multiply(left: P, right: P): P = left * right + // endregion + + public companion object { + // TODO: All of this should be moved to algebraic structures' place for utilities + // TODO: Move receiver to context receiver + /** + * Multiplication of element and integer. + * + * @receiver the multiplicand. + * @param other the multiplier. + * @return the difference. + */ + internal tailrec fun Group.optimizedMultiply(arg: C, other: Int): C = + when { + other == 0 -> zero + other == 1 -> arg + other == -1 -> -arg + other % 2 == 0 -> optimizedMultiply(arg + arg, other / 2) + other % 2 == 1 -> optimizedAddMultiplied(arg, arg + arg, other / 2) + other % 2 == -1 -> optimizedAddMultiplied(-arg, arg + arg, other / 2) + else -> error("Error in multiplication group instant by integer: got reminder by division by 2 different from 0, 1 and -1") + } + + // TODO: Move receiver to context receiver + /** + * Adds product of [arg] and [multiplier] to [base]. + * + * @receiver the algebra to provide multiplication. + * @param base the augend. + * @param arg the multiplicand. + * @param multiplier the multiplier. + * @return sum of the augend [base] and product of the multiplicand [arg] and the multiplier [multiplier]. + * @author Gleb Minaev + */ + internal tailrec fun Group.optimizedAddMultiplied(base: C, arg: C, multiplier: Int): C = + when { + multiplier == 0 -> base + multiplier == 1 -> base + arg + multiplier == -1 -> base - arg + multiplier % 2 == 0 -> optimizedAddMultiplied(base, arg + arg, multiplier / 2) + multiplier % 2 == 1 -> optimizedAddMultiplied(base + arg, arg + arg, multiplier / 2) + multiplier % 2 == -1 -> optimizedAddMultiplied(base + arg, arg + arg, multiplier / 2) + else -> error("Error in multiplication group instant by integer: got reminder by division by 2 different from 0, 1 and -1") + } + } +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt index 16af7f555..cfd21d552 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt @@ -117,16 +117,16 @@ public fun > PiecewisePolynomial( * Return a value of polynomial function with given [ring] a given [arg] or null if argument is outside piecewise * definition. */ -public fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = - findPiece(arg)?.value(ring, arg) +public fun , C : Ring> PiecewisePolynomial.substitute(ring: C, arg: T): T? = + findPiece(arg)?.substitute(ring, arg) /** * Convert this polynomial to a function returning nullable value (null if argument is outside piecewise range). */ -public fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } +public fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { substitute(ring, it) } /** * Convert this polynomial to a function using [defaultValue] for arguments outside the piecewise range. */ public fun , C : Ring> PiecewisePolynomial.asFunction(ring: C, defaultValue: T): (T) -> T = - { value(ring, it) ?: defaultValue } + { substitute(ring, it) ?: defaultValue } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt index a36d36f52..30280a396 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt @@ -5,128 +5,371 @@ package space.kscience.kmath.functions -import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedAddMultiplied +import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedMultiply import space.kscience.kmath.operations.* -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract +import kotlin.jvm.JvmName import kotlin.math.max -import kotlin.math.pow +import kotlin.math.min /** * Polynomial coefficients model without fixation on specific context they are applied to. * * @param coefficients constant is the leftmost coefficient. */ -public class Polynomial(public val coefficients: List) { +public class Polynomial(public val coefficients: List) : AbstractPolynomial { override fun toString(): String = "Polynomial$coefficients" + +// public companion object { +// /** +// * Default name of variables used in string representations. +// * +// * @see Polynomial.toString +// */ +// public var defaultVariableName: String = "x" +// +// /** +// * Represents result of division with remainder. +// */ +// public data class DividingResult( +// val quotient: Polynomial, +// val reminder: Polynomial +// ) +// } } /** - * Returns a [Polynomial] instance with given [coefficients]. + * Represents internal [Polynomial] errors. + */ +internal class PolynomialError(message: String): Error(message) + +// region Constructors and converters + +/** + * Returns a [Polynomial] instance with given [coefficients]. The collection of coefficients will be reversed if + * [reverse] parameter is true. */ @Suppress("FunctionName") -public fun Polynomial(vararg coefficients: T): Polynomial = Polynomial(coefficients.toList()) +public fun Polynomial(coefficients: List, reverse: Boolean = false): Polynomial = + Polynomial(with(coefficients) { if (reverse) reversed() else this }) /** - * Evaluates the value of the given double polynomial for given double argument. + * Returns a [Polynomial] instance with given [coefficients]. The collection of coefficients will be reversed if + * [reverse] parameter is true. */ -public fun Polynomial.value(arg: Double): Double = coefficients.reduceIndexed { index, acc, c -> - acc + c * arg.pow(index) -} +@Suppress("FunctionName") +public fun Polynomial(vararg coefficients: T, reverse: Boolean = false): Polynomial = + Polynomial(with(coefficients) { if (reverse) reversed() else toList() }) + +public fun T.asPolynomial() : Polynomial = Polynomial(listOf(this)) + +// endregion /** - * Evaluates the value of the given polynomial for given argument. - * https://en.wikipedia.org/wiki/Horner%27s_method - */ -public fun > Polynomial.value(ring: C, arg: T): T = ring { - if (coefficients.isEmpty()) return@ring zero - var result: T = coefficients.last() - for (j in coefficients.size - 2 downTo 0) { - result = (arg * result) + coefficients[j] - } - return result -} - -/** - * Represent the polynomial as a regular context-less function. - */ -public fun > Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } - -/** - * Create a polynomial witch represents differentiated version of this polynomial - */ -@UnstableKMathAPI -public fun Polynomial.differentiate( - algebra: A, -): Polynomial where A : Ring, A : NumericAlgebra = algebra { - Polynomial(coefficients.drop(1).mapIndexed { index, t -> number(index) * t }) -} - -/** - * Create a polynomial witch represents indefinite integral version of this polynomial - */ -@UnstableKMathAPI -public fun Polynomial.integrate( - algebra: A, -): Polynomial where A : Field, A : NumericAlgebra = algebra { - val integratedCoefficients = buildList(coefficients.size + 1) { - add(zero) - coefficients.forEachIndexed{ index, t -> add(t / (number(index) + one)) } - } - Polynomial(integratedCoefficients) -} - -/** - * Compute a definite integral of a given polynomial in a [range] - */ -@UnstableKMathAPI -public fun > Polynomial.integrate( - algebra: Field, - range: ClosedRange, -): T = algebra { - val integral = integrate(algebra) - integral.value(algebra, range.endInclusive) - integral.value(algebra, range.start) -} - -/** - * Space of polynomials. + * Space of polynomials constructed over ring. * - * @param T the type of operated polynomials. - * @param C the intersection of [Ring] of [T] and [ScaleOperations] of [T]. - * @param ring the [C] instance. + * @param C the type of constants. Polynomials have them as a coefficients in their terms. + * @param A type of underlying ring of constants. It's [Ring] of [C]. + * @param ring underlying ring of constants of type [A]. */ -public class PolynomialSpace( - private val ring: C, -) : Group>, ScaleOperations> where C : Ring, C : ScaleOperations { - override val zero: Polynomial = Polynomial(emptyList()) +@Suppress("INAPPLICABLE_JVM_NAME") // KT-31420 +public open class PolynomialSpace>( + public val ring: A, +) : AbstractPolynomialSpace>{ + // region Constant-integer relation + @JvmName("constantIntPlus") + public override operator fun C.plus(other: Int): C = ring { optimizedAddMultiplied(this@plus, one, other) } + @JvmName("constantIntMinus") + public override operator fun C.minus(other: Int): C = ring { optimizedAddMultiplied(this@minus, one, -other) } + @JvmName("constantIntTimes") + public override operator fun C.times(other: Int): C = ring { optimizedMultiply(this@times, other) } + // endregion - override fun Polynomial.unaryMinus(): Polynomial = ring { + // region Integer-constant relation + @JvmName("intConstantPlus") + public override operator fun Int.plus(other: C): C = ring { optimizedAddMultiplied(other, one, this@plus) } + @JvmName("intConstantMinus") + public override operator fun Int.minus(other: C): C = ring { optimizedAddMultiplied(-other, one, this@minus) } + @JvmName("intConstantTimes") + public override operator fun Int.times(other: C): C = ring { optimizedMultiply(other, this@times) } + // endregion + + // region Polynomial-integer relation + public override operator fun Polynomial.plus(other: Int): Polynomial = + if (other == 0) this + else + Polynomial( + coefficients + .toMutableList() + .apply { + if (isEmpty()) this[0] = ring.zero + other + else this[0] = this[0]!! + other + } + ) + public override operator fun Polynomial.minus(other: Int): Polynomial = + if (other == 0) this + else + Polynomial( + coefficients + .toMutableList() + .apply { + if (isEmpty()) this[0] = ring.zero - other + else this[0] = this[0]!! - other + } + ) + public override operator fun Polynomial.times(other: Int): Polynomial = + if (other == 0) zero + else Polynomial( + coefficients + .subList(0, degree + 1) + .map { it * other } + ) + // endregion + + // region Integer-polynomial relation + public override operator fun Int.plus(other: Polynomial): Polynomial = + if (this == 0) other + else + Polynomial( + other.coefficients + .toMutableList() + .apply { + if (isEmpty()) this[0] = ring.zero + this@plus + else this[0] = this[0]!! + this@plus + } + ) + public override operator fun Int.minus(other: Polynomial): Polynomial = + if (this == 0) other + else + Polynomial( + other.coefficients + .toMutableList() + .apply { + if (isEmpty()) this[0] = ring.zero - this@minus + else this[0] = this[0]!! - this@minus + } + ) + public override operator fun Int.times(other: Polynomial): Polynomial = + if (this == 0) zero + else Polynomial( + other.coefficients + .subList(0, other.degree + 1) + .map { it * this } + ) + // endregion + + // region Constant-constant relation + @JvmName("constantUnaryMinus") + public override operator fun C.unaryMinus(): C = ring { -this@unaryMinus } + @JvmName("constantPlus") + public override operator fun C.plus(other: C): C = ring { this@plus + other } + @JvmName("constantMinus") + public override operator fun C.minus(other: C): C = ring { this@minus - other } + @JvmName("constantTimes") + public override operator fun C.times(other: C): C = ring { this@times * other } + @JvmName("constantIsZero") + public override fun C.isZero(): Boolean = ring { this == zero } + @JvmName("constantIsNotZero") + public override fun C.isNotZero(): Boolean = ring { this != zero } + @JvmName("constantIsOne") + public override fun C.isOne(): Boolean = ring { this == one } + @JvmName("constantIsNotOne") + public override fun C.isNotOne(): Boolean = ring { this != one } + @JvmName("constantIsMinusOne") + public override fun C.isMinusOne(): Boolean = ring { this == -one } + @JvmName("constantIsNotMinusOne") + public override fun C.isNotMinusOne(): Boolean = ring { this != -one } + // endregion + + // region Constant-polynomial relation + public override operator fun C.plus(other: Polynomial): Polynomial = + with(other.coefficients) { + if (isEmpty()) Polynomial(listOf(this@plus)) + else Polynomial( + toMutableList() + .apply { + this[0] += this@plus + } + ) + } +// if (degree == -1) UnivariatePolynomial(other) else UnivariatePolynomial( +// listOf(coefficients[0] + other) + coefficients.subList(1, degree + 1) +// ) + public override operator fun C.minus(other: Polynomial): Polynomial = + with(other.coefficients) { + if (isEmpty()) Polynomial(listOf(-this@minus)) + else Polynomial( + toMutableList() + .apply { + this[0] -= this@minus + } + ) + } +// if (degree == -1) UnivariatePolynomial(other) else UnivariatePolynomial( +// listOf(coefficients[0] + other) + coefficients.subList(1, degree + 1) +// ) + public override operator fun C.times(other: Polynomial): Polynomial = + Polynomial( + other.coefficients +// .subList(0, other.degree + 1) + .map { it * this } + ) + // endregion + + // region Polynomial-constant relation + public override operator fun Polynomial.plus(other: C): Polynomial = + if (other.isZero()) this + else with(coefficients) { + if (isEmpty()) Polynomial(listOf(other)) + else Polynomial( + toMutableList() + .apply { + this[0] += other + } + ) + } +// if (degree == -1) UnivariatePolynomial(other) else UnivariatePolynomial( +// listOf(coefficients[0] + other) + coefficients.subList(1, degree + 1) +// ) + public override operator fun Polynomial.minus(other: C): Polynomial = + with(coefficients) { + if (isEmpty()) Polynomial(listOf(other)) + else Polynomial( + toMutableList() + .apply { + this[0] -= other + } + ) + } +// if (degree == -1) UnivariatePolynomial(-other) else UnivariatePolynomial( +// listOf(coefficients[0] - other) + coefficients.subList(1, degree + 1) +// ) + public override operator fun Polynomial.times(other: C): Polynomial = + Polynomial( + coefficients +// .subList(0, degree + 1) + .map { it * other } + ) + // endregion + + // region Polynomial-polynomial relation + public override operator fun Polynomial.unaryMinus(): Polynomial = Polynomial(coefficients.map { -it }) - } - - override fun add(left: Polynomial, right: Polynomial): Polynomial { - val dim = max(left.coefficients.size, right.coefficients.size) - - return ring { - Polynomial(List(dim) { index -> - left.coefficients.getOrElse(index) { zero } + right.coefficients.getOrElse(index) { zero } - }) + public override operator fun Polynomial.plus(other: Polynomial): Polynomial = + Polynomial( + (0..max(degree, other.degree)) + .map { + when { + it > degree -> other.coefficients[it] + it > other.degree -> coefficients[it] + else -> coefficients[it] + other.coefficients[it] + } + } + .ifEmpty { listOf(ring.zero) } + ) + public override operator fun Polynomial.minus(other: Polynomial): Polynomial = + Polynomial( + (0..max(degree, other.degree)) + .map { + when { + it > degree -> -other.coefficients[it] + it > other.degree -> coefficients[it] + else -> coefficients[it] - other.coefficients[it] + } + } + .ifEmpty { listOf(ring.zero) } + ) + public override operator fun Polynomial.times(other: Polynomial): Polynomial { + val thisDegree = degree + val otherDegree = other.degree + return when { + thisDegree == -1 -> this + otherDegree == -1 -> other + else -> + Polynomial( + (0..(thisDegree + otherDegree)) + .map { d -> + (max(0, d - otherDegree)..(min(thisDegree, d))) + .map { coefficients[it] * other.coefficients[d - it] } + .reduce { acc, rational -> acc + rational } + } + ) } } - override fun scale(a: Polynomial, value: Double): Polynomial = - ring { Polynomial(List(a.coefficients.size) { index -> a.coefficients[index] * value }) } + public override fun Polynomial.isZero(): Boolean = coefficients.all { it.isZero() } + public override fun Polynomial.isNotZero(): Boolean = coefficients.any { it.isNotZero() } + public override fun Polynomial.isOne(): Boolean = + with(coefficients) { isNotEmpty() && asSequence().withIndex().any { (index, c) -> if (index == 0) c.isOne() else c.isZero() } } // TODO: It's better to write new methods like `anyIndexed`. But what's better way to do it? + public override fun Polynomial.isNotOne(): Boolean = !isOne() + public override fun Polynomial.isMinusOne(): Boolean = + with(coefficients) { isNotEmpty() && asSequence().withIndex().any { (index, c) -> if (index == 0) c.isMinusOne() else c.isZero() } } // TODO: It's better to write new methods like `anyIndexed`. But what's better way to do it? + public override fun Polynomial.isNotMinusOne(): Boolean = !isMinusOne() + + override val zero: Polynomial = Polynomial(emptyList()) + override val one: Polynomial = Polynomial(listOf(ring.one)) + + @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "CovariantEquals") + public override fun Polynomial.equals(other: Polynomial): Boolean = + when { + this === other -> true + else -> { + if (this.degree == other.degree) + (0..degree).all { coefficients[it] == other.coefficients[it] } + else false + } + } + // endregion + + // Not sure is it necessary... + // region Polynomial properties + public override val Polynomial.degree: Int get() = coefficients.indexOfLast { it != ring.zero } + + public override fun Polynomial.asConstantOrNull(): C? = + with(coefficients) { + when { + isEmpty() -> ring.zero + degree > 0 -> null + else -> first() + } + } + public override fun Polynomial.asConstant(): C = asConstantOrNull() ?: error("Can not represent non-constant polynomial as a constant") + + @Suppress("NOTHING_TO_INLINE") + public inline fun Polynomial.substitute(argument: C): C = this.substitute(ring, argument) + @Suppress("NOTHING_TO_INLINE") + public inline fun Polynomial.substitute(argument: Polynomial): Polynomial = this.substitute(ring, argument) + + @Suppress("NOTHING_TO_INLINE") + public inline fun Polynomial.asFunction(): (C) -> C = { this.substitute(ring, it) } + @Suppress("NOTHING_TO_INLINE") + public inline fun Polynomial.asFunctionOnConstants(): (C) -> C = { this.substitute(ring, it) } + @Suppress("NOTHING_TO_INLINE") + public inline fun Polynomial.asFunctionOnPolynomials(): (Polynomial) -> Polynomial = { this.substitute(ring, it) } /** * Evaluates the polynomial for the given value [arg]. */ - public operator fun Polynomial.invoke(arg: T): T = value(ring, arg) - - public fun Polynomial.asFunction(): (T) -> T = asFunction(ring) + @Suppress("NOTHING_TO_INLINE") + public inline operator fun Polynomial.invoke(argument: C): C = this.substitute(ring, argument) + @Suppress("NOTHING_TO_INLINE") + public inline operator fun Polynomial.invoke(argument: Polynomial): Polynomial = this.substitute(ring, argument) + // endregion } -public inline fun C.polynomial(block: PolynomialSpace.() -> R): R where C : Ring, C : ScaleOperations { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return PolynomialSpace(this).block() +/** + * Space of polynomials constructed over ring. + * + * @param C the type of constants. Polynomials have them as a coefficients in their terms. + * @param A type of underlying ring of constants. It's [Ring] of [C]. + * @param ring underlying ring of constants of type [A]. + */ +public class ScalablePolynomialSpace( + ring: A, +) : PolynomialSpace(ring), ScaleOperations> where A : Ring, A : ScaleOperations { + + override fun scale(a: Polynomial, value: Double): Polynomial = + ring { Polynomial(List(a.coefficients.size) { index -> a.coefficients[index] * value }) } + } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt new file mode 100644 index 000000000..1a3eb7874 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.functions + +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.* +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName +import kotlin.math.pow + + +// region Utilities + +/** + * Removes zeros on the end of the coefficient list of polynomial. + */ +//context(PolynomialSpace) +//fun > Polynomial.removeZeros() : Polynomial = +// if (degree > -1) Polynomial(coefficients.subList(0, degree + 1)) else zero + +/** + * Crates a [PolynomialSpace] over received ring. + */ +public fun > A.polynomial(): PolynomialSpace = + PolynomialSpace(this) + +/** + * Crates a [PolynomialSpace]'s scope over received ring. + */ +public inline fun , R> A.polynomial(block: PolynomialSpace.() -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return PolynomialSpace(this).block() +} + +/** + * Crates a [ScalablePolynomialSpace] over received scalable ring. + */ +public fun A.scalablePolynomial(): ScalablePolynomialSpace where A : Ring, A : ScaleOperations = + ScalablePolynomialSpace(this) + +/** + * Crates a [ScalablePolynomialSpace]'s scope over received scalable ring. + */ +public inline fun A.scalablePolynomial(block: ScalablePolynomialSpace.() -> R): R where A : Ring, A : ScaleOperations { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ScalablePolynomialSpace(this).block() +} + +// endregion + +// region Polynomial substitution and functional representation + +// TODO: May be apply Horner's method too? +/** + * Evaluates the value of the given double polynomial for given double argument. + */ +public fun Polynomial.substitute(arg: Double): Double = + coefficients.reduceIndexedOrNull { index, acc, c -> + acc + c * arg.pow(index) + } ?: .0 + +/** + * Evaluates the value of the given polynomial for given argument. + * + * It is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method). + */ +public fun Polynomial.substitute(ring: Ring, arg: C): C = ring { + if (coefficients.isEmpty()) return@ring zero + var result: C = coefficients.last() + for (j in coefficients.size - 2 downTo 0) { + result = (arg * result) + coefficients[j] + } + return result +} + +// TODO: (Waiting for hero) Replace with optimisation: the [result] may be unboxed, and all operations may be performed +// as soon as possible on it +public fun Polynomial.substitute(ring: Ring, arg: Polynomial) : Polynomial = ring.polynomial { + if (coefficients.isEmpty()) return zero + var result: Polynomial = coefficients.last().asPolynomial() + for (j in coefficients.size - 2 downTo 0) { + result = (arg * result) + coefficients[j] + } + return result +} + +/** + * Represent the polynomial as a regular context-less function. + */ +public fun > Polynomial.asFunction(ring: A): (C) -> C = { substitute(ring, it) } + +/** + * Represent the polynomial as a regular context-less function. + */ +public fun > Polynomial.asPolynomialFunctionOver(ring: A): (Polynomial) -> Polynomial = { substitute(ring, it) } + +// endregion + +// region Algebraic derivative and antiderivative + +/** + * Returns algebraic derivative of received polynomial. + */ +@UnstableKMathAPI +public fun Polynomial.derivative( + algebra: A, +): Polynomial where A : Ring, A : NumericAlgebra = algebra { + Polynomial(coefficients.drop(1).mapIndexed { index, t -> number(index) * t }) +} + +/** + * Create a polynomial witch represents indefinite integral version of this polynomial + */ +@UnstableKMathAPI +public fun Polynomial.antiderivative( + algebra: A, +): Polynomial where A : Field, A : NumericAlgebra = algebra { + val integratedCoefficients = buildList(coefficients.size + 1) { + add(zero) + coefficients.forEachIndexed{ index, t -> add(t / (number(index) + one)) } + } + Polynomial(integratedCoefficients) +} + +/** + * Compute a definite integral of a given polynomial in a [range] + */ +@UnstableKMathAPI +public fun > Polynomial.integrate( + algebra: Field, + range: ClosedRange, +): C = algebra { + val integral = antiderivative(algebra) + integral.substitute(algebra, range.endInclusive) - integral.substitute(algebra, range.start) +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt index 15d548641..2e6873ece 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt @@ -7,6 +7,7 @@ package space.kscience.kmath.integration import space.kscience.kmath.functions.PiecewisePolynomial import space.kscience.kmath.functions.integrate +import space.kscience.kmath.functions.antiderivative import space.kscience.kmath.interpolation.PolynomialInterpolator import space.kscience.kmath.interpolation.SplineInterpolator import space.kscience.kmath.interpolation.interpolatePolynomials @@ -23,7 +24,7 @@ import space.kscience.kmath.structures.MutableBufferFactory @OptIn(PerformancePitfall::class) @UnstableKMathAPI public fun > PiecewisePolynomial.integrate(algebra: Field): PiecewisePolynomial = - PiecewisePolynomial(pieces.map { it.first to it.second.integrate(algebra) }) + PiecewisePolynomial(pieces.map { it.first to it.second.antiderivative(algebra) }) /** * Compute definite integral of given [PiecewisePolynomial] piece by piece in a given [range] diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt index b13adefa5..bbd76c87e 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt @@ -9,7 +9,7 @@ package space.kscience.kmath.interpolation import space.kscience.kmath.data.XYColumnarData import space.kscience.kmath.functions.PiecewisePolynomial -import space.kscience.kmath.functions.value +import space.kscience.kmath.functions.substitute import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Ring import space.kscience.kmath.structures.Buffer @@ -33,7 +33,7 @@ public interface PolynomialInterpolator> : Interpolator): PiecewisePolynomial override fun interpolate(points: XYColumnarData): (T) -> T = { x -> - interpolatePolynomials(points).value(algebra, x) ?: getDefaultValue() + interpolatePolynomials(points).substitute(algebra, x) ?: getDefaultValue() } } diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/functions/PolynomialTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/functions/PolynomialTest.kt index 05c16d17e..e0f0e32a4 100644 --- a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/functions/PolynomialTest.kt +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/functions/PolynomialTest.kt @@ -5,13 +5,22 @@ package space.kscience.kmath.functions +import space.kscience.kmath.operations.algebra import kotlin.test.Test import kotlin.test.assertEquals class PolynomialTest { + @Test + fun simple_polynomial_test() { + Double.algebra.scalablePolynomial { + val x = Polynomial(listOf(0.0, 1.0)) + val polynomial = x * x - 2 * x + 1 + assertEquals(0.0, polynomial.substitute(1.0), 0.001) + } + } @Test fun testIntegration() { val polynomial = Polynomial(1.0, -2.0, 1.0) - assertEquals(0.0, polynomial.value(1.0), 0.001) + assertEquals(0.0, polynomial.substitute(1.0), 0.001) } } \ No newline at end of file diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index e21abe604..9fc75720e 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -274,11 +274,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -astring@1.7.5: - version "1.7.5" - resolved "https://registry.yarnpkg.com/astring/-/astring-1.7.5.tgz#a7d47fceaf32b052d33a3d07c511efeec67447ca" - integrity sha512-lobf6RWXb8c4uZ7Mdq0U12efYmpD1UFnyOWVJPTa3ukqZrMopav+2hdNu0hgBF0JIBFK9QgrBDfwYvh3DFJDAA== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -294,24 +289,11 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== -benchmark@*: - version "2.1.4" - resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" - integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik= - dependencies: - lodash "^4.17.4" - platform "^1.3.3" - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -binaryen@101.0.0: - version "101.0.0" - resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-101.0.0.tgz#42a9e4cc7a22e2c1d75a31d28005a9b518b2c555" - integrity sha512-FRmVxvrR8jtcf0qcukNAPZDM3dZ2sc9GmA/hKxBI7k3fFzREKh1cAs+ruQi+ITTKz7u/AuFMuVnbJwTh0ef/HQ== - body-parser@^1.19.0: version "1.19.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" @@ -599,14 +581,6 @@ dom-serialize@^2.2.1: extend "^3.0.0" void-elements "^2.0.0" -dukat@0.5.8-rc.4: - version "0.5.8-rc.4" - resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21" - integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA== - dependencies: - google-protobuf "3.12.2" - typescript "3.9.5" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -881,11 +855,6 @@ glob@^7.1.3, glob@^7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" -google-protobuf@3.12.2: - version "3.12.2" - resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.2.tgz#50ce9f9b6281235724eb243d6a83e969a2176e53" - integrity sha512-4CZhpuRr1d6HjlyrxoXoocoGFnRYgKULgMtikMddA9ztRyYR59Aondv2FioyxWVamRo0rF2XpYawkTCBEQOSkA== - graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.9" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" @@ -1065,11 +1034,6 @@ jest-worker@^27.4.1: merge-stream "^2.0.0" supports-color "^8.0.0" -js-base64@3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.6.1.tgz#555aae398b74694b4037af1f8a5a6209d170efbe" - integrity sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ== - js-yaml@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -1179,7 +1143,7 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -1437,11 +1401,6 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -platform@^1.3.3: - version "1.3.6" - resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" - integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== - postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" @@ -1696,14 +1655,6 @@ source-map-loader@3.0.0: iconv-lite "^0.6.2" source-map-js "^0.6.2" -source-map-support@0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -1838,11 +1789,6 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@3.9.5: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== - ua-parser-js@^0.7.28: version "0.7.31" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" From ab9dcd83b90a468a325948a1c32257ae0a5d5875 Mon Sep 17 00:00:00 2001 From: Gleb Minaev <43728100+lounres@users.noreply.github.com> Date: Thu, 10 Mar 2022 01:44:14 +0300 Subject: [PATCH 2/4] Added abstract rational functions --- .../kmath/functions/AbstractPolynomial.kt | 16 +- .../functions/AbstractRationalFunction.kt | 507 ++++++++++++++++++ 2 files changed, 515 insertions(+), 8 deletions(-) create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt index f6a617656..237a72bcc 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt @@ -70,19 +70,19 @@ public interface AbstractPolynomialSpace> : Ring

// region Polynomial-integer relation /** - * Returns sum of the constant and the integer represented as polynomial. + * Returns sum of the polynomial and the integer represented as polynomial. * * The operation is equivalent to adding [other] copies of unit polynomial to [this]. */ public operator fun P.plus(other: Int): P = optimizedAddMultiplied(this, one, other) /** - * Returns difference between the constant and the integer represented as polynomial. + * Returns difference between the polynomial and the integer represented as polynomial. * * The operation is equivalent to subtraction [other] copies of unit polynomial from [this]. */ public operator fun P.minus(other: Int): P = optimizedAddMultiplied(this, one, -other) /** - * Returns product of the constant and the integer represented as polynomial. + * Returns product of the polynomial and the integer represented as polynomial. * * The operation is equivalent to sum of [other] copies of [this]. */ @@ -91,19 +91,19 @@ public interface AbstractPolynomialSpace> : Ring

// region Integer-polynomial relation /** - * Returns sum of the integer represented as polynomial and the constant. + * Returns sum of the integer represented as polynomial and the polynomial. * * The operation is equivalent to adding [this] copies of unit polynomial to [other]. */ public operator fun Int.plus(other: P): P = optimizedAddMultiplied(other, one, this) /** - * Returns difference between the integer represented as polynomial and the constant. + * Returns difference between the integer represented as polynomial and the polynomial. * * The operation is equivalent to subtraction [this] copies of unit polynomial from [other]. */ public operator fun Int.minus(other: P): P = optimizedAddMultiplied(-other, one, this) /** - * Returns product of the integer represented as polynomial and the constant. + * Returns product of the integer represented as polynomial and the polynomial. * * The operation is equivalent to sum of [this] copies of [other]. */ @@ -254,11 +254,11 @@ public interface AbstractPolynomialSpace> : Ring

/** * Instance of zero polynomial (zero of the polynomial ring). */ - override val zero: P + public override val zero: P /** * Instance of unit polynomial (unit of the polynomial ring). */ - override val one: P + public override val one: P /** * Checks equality of the polynomials. diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt new file mode 100644 index 000000000..5cb570c9f --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt @@ -0,0 +1,507 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.functions + +import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedAddMultiplied +import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedMultiply +import space.kscience.kmath.operations.* +import kotlin.js.JsName +import kotlin.jvm.JvmName + + +/** + * Abstraction of rational function. + */ +public interface AbstractRationalFunction> + +@Suppress("INAPPLICABLE_JVM_NAME", "PARAMETER_NAME_CHANGED_ON_OVERRIDE") +public interface AbstractRationalFunctionalSpace, R: AbstractRationalFunction> : Ring { + // region Constant-integer relation + /** + * Returns sum of the constant and the integer represented as constant (member of underlying ring). + * + * The operation is equivalent to adding [other] copies of unit of underlying ring to [this]. + */ + @JvmName("constantIntPlus") + public operator fun C.plus(other: Int): C + /** + * Returns difference between the constant and the integer represented as constant (member of underlying ring). + * + * The operation is equivalent to subtraction [other] copies of unit of underlying ring from [this]. + */ + @JvmName("constantIntMinus") + public operator fun C.minus(other: Int): C + /** + * Returns product of the constant and the integer represented as constant (member of underlying ring). + * + * The operation is equivalent to sum of [other] copies of [this]. + */ + @JvmName("constantIntTimes") + public operator fun C.times(other: Int): C + // endregion + + // region Integer-constant relation + /** + * Returns sum of the integer represented as constant (member of underlying ring) and the constant. + * + * The operation is equivalent to adding [this] copies of unit of underlying ring to [other]. + */ + @JvmName("intConstantPlus") + public operator fun Int.plus(other: C): C + /** + * Returns difference between the integer represented as constant (member of underlying ring) and the constant. + * + * The operation is equivalent to subtraction [this] copies of unit of underlying ring from [other]. + */ + @JvmName("intConstantMinus") + public operator fun Int.minus(other: C): C + /** + * Returns product of the integer represented as constant (member of underlying ring) and the constant. + * + * The operation is equivalent to sum of [this] copies of [other]. + */ + @JvmName("intConstantTimes") + public operator fun Int.times(other: C): C + // endregion + + // region Polynomial-integer relation + /** + * Returns sum of the constant and the integer represented as polynomial. + * + * The operation is equivalent to adding [other] copies of unit polynomial to [this]. + */ + public operator fun P.plus(other: Int): P + /** + * Returns difference between the constant and the integer represented as polynomial. + * + * The operation is equivalent to subtraction [other] copies of unit polynomial from [this]. + */ + public operator fun P.minus(other: Int): P + /** + * Returns product of the constant and the integer represented as polynomial. + * + * The operation is equivalent to sum of [other] copies of [this]. + */ + public operator fun P.times(other: Int): P + // endregion + + // region Integer-polynomial relation + /** + * Returns sum of the integer represented as polynomial and the constant. + * + * The operation is equivalent to adding [this] copies of unit polynomial to [other]. + */ + public operator fun Int.plus(other: P): P + /** + * Returns difference between the integer represented as polynomial and the constant. + * + * The operation is equivalent to subtraction [this] copies of unit polynomial from [other]. + */ + public operator fun Int.minus(other: P): P + /** + * Returns product of the integer represented as polynomial and the constant. + * + * The operation is equivalent to sum of [this] copies of [other]. + */ + public operator fun Int.times(other: P): P + // endregion + + // region Rational-integer relation + /** + * Returns sum of the rational function and the integer represented as rational function. + * + * The operation is equivalent to adding [other] copies of unit polynomial to [this]. + */ + public operator fun R.plus(other: Int): R = optimizedAddMultiplied(this, one, other) + /** + * Returns difference between the rational function and the integer represented as rational function. + * + * The operation is equivalent to subtraction [other] copies of unit polynomial from [this]. + */ + public operator fun R.minus(other: Int): R = optimizedAddMultiplied(this, one, -other) + /** + * Returns product of the rational function and the integer represented as rational function. + * + * The operation is equivalent to sum of [other] copies of [this]. + */ + public operator fun R.times(other: Int): R = optimizedMultiply(this, other) + // endregion + + // region Integer-Rational relation + /** + * Returns sum of the integer represented as rational function and the rational function. + * + * The operation is equivalent to adding [this] copies of unit polynomial to [other]. + */ + public operator fun Int.plus(other: R): R = optimizedAddMultiplied(other, one, this) + /** + * Returns difference between the integer represented as rational function and the rational function. + * + * The operation is equivalent to subtraction [this] copies of unit polynomial from [other]. + */ + public operator fun Int.minus(other: R): R = optimizedAddMultiplied(-other, one, this) + /** + * Returns product of the integer represented as rational function and the rational function. + * + * The operation is equivalent to sum of [this] copies of [other]. + */ + public operator fun Int.times(other: R): R = optimizedMultiply(other, this) + // endregion + + // region Constant-constant relation + /** + * Returns the same constant. + */ + @JvmName("constantUnaryPlus") + @JsName("constantUnaryPlus") + public operator fun C.unaryPlus(): C = this + /** + * Returns negation of the constant. + */ + @JvmName("constantUnaryMinus") + @JsName("constantUnaryMinus") + public operator fun C.unaryMinus(): C + /** + * Returns sum of the constants. + */ + @JvmName("constantPlus") + @JsName("constantPlus") + public operator fun C.plus(other: C): C + /** + * Returns difference of the constants. + */ + @JvmName("constantMinus") + @JsName("constantMinus") + public operator fun C.minus(other: C): C + /** + * Returns product of the constants. + */ + @JvmName("constantTimes") + @JsName("constantTimes") + public operator fun C.times(other: C): C + + /** + * Check if the instant is zero constant. + */ + @JvmName("constantIsZero") + public fun C.isZero(): Boolean + /** + * Check if the instant is NOT zero constant. + */ + @JvmName("constantIsNotZero") + public fun C.isNotZero(): Boolean + /** + * Check if the instant is unit constant. + */ + @JvmName("constantIsOne") + public fun C.isOne(): Boolean + /** + * Check if the instant is NOT unit constant. + */ + @JvmName("constantIsNotOne") + public fun C.isNotOne(): Boolean + /** + * Check if the instant is minus unit constant. + */ + @JvmName("constantIsMinusOne") + public fun C.isMinusOne(): Boolean + /** + * Check if the instant is NOT minus unit constant. + */ + @JvmName("constantIsNotMinusOne") + public fun C.isNotMinusOne(): Boolean + // endregion + + // region Constant-polynomial relation + /** + * Returns sum of the constant represented as polynomial and the polynomial. + */ + public operator fun C.plus(other: P): P + /** + * Returns difference between the constant represented as polynomial and the polynomial. + */ + public operator fun C.minus(other: P): P + /** + * Returns product of the constant represented as polynomial and the polynomial. + */ + public operator fun C.times(other: P): P + // endregion + + // region Polynomial-constant relation + /** + * Returns sum of the constant represented as polynomial and the polynomial. + */ + public operator fun P.plus(other: C): P + /** + * Returns difference between the constant represented as polynomial and the polynomial. + */ + public operator fun P.minus(other: C): P + /** + * Returns product of the constant represented as polynomial and the polynomial. + */ + public operator fun P.times(other: C): P + // endregion + + // region Polynomial-polynomial relation + /** + * Returns the same polynomial. + */ + public operator fun P.unaryPlus(): P = this + /** + * Returns negation of the polynomial. + */ + public operator fun P.unaryMinus(): P + /** + * Returns sum of the polynomials. + */ + public operator fun P.plus(other: P): P + /** + * Returns difference of the polynomials. + */ + public operator fun P.minus(other: P): P + /** + * Returns product of the polynomials. + */ + public operator fun P.times(other: P): P + + /** + * Check if the instant is zero polynomial. + */ + public fun P.isZero(): Boolean = this == zeroPolynomial + /** + * Check if the instant is NOT zero polynomial. + */ + public fun P.isNotZero(): Boolean = this != zeroPolynomial + /** + * Check if the instant is unit polynomial. + */ + public fun P.isOne(): Boolean = this == onePolynomial + /** + * Check if the instant is NOT unit polynomial. + */ + public fun P.isNotOne(): Boolean = this != onePolynomial + /** + * Check if the instant is minus unit polynomial. + */ + public fun P.isMinusOne(): Boolean = this == -onePolynomial + /** + * Check if the instant is NOT minus unit polynomial. + */ + public fun P.isNotMinusOne(): Boolean = this != -onePolynomial + + /** + * Instance of zero polynomial (zero of the polynomial ring). + */ + public val zeroPolynomial: P + /** + * Instance of unit polynomial (unit of the polynomial ring). + */ + public val onePolynomial: P + + /** + * Checks equality of the polynomials. + */ + @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "CovariantEquals") + public fun P.equals(other: P): Boolean + // endregion + + // region Constant-rational relation + /** + * Returns sum of the constant represented as rational function and the rational function. + */ + public operator fun C.plus(other: R): R + /** + * Returns difference between the constant represented as polynomial and the rational function. + */ + public operator fun C.minus(other: R): R + /** + * Returns product of the constant represented as polynomial and the rational function. + */ + public operator fun C.times(other: R): R + // endregion + + // region Rational-constant relation + /** + * Returns sum of the constant represented as rational function and the rational function. + */ + public operator fun R.plus(other: C): R + /** + * Returns difference between the constant represented as rational function and the rational function. + */ + public operator fun R.minus(other: C): R + /** + * Returns product of the constant represented as rational function and the rational function. + */ + public operator fun R.times(other: C): R + // endregion + + // region Polynomial-rational relation + /** + * Returns sum of the polynomial represented as rational function and the rational function. + */ + public operator fun P.plus(other: R): R + /** + * Returns difference between the polynomial represented as polynomial and the rational function. + */ + public operator fun P.minus(other: R): R + /** + * Returns product of the polynomial represented as polynomial and the rational function. + */ + public operator fun P.times(other: R): R + // endregion + + // region Rational-polynomial relation + /** + * Returns sum of the polynomial represented as rational function and the rational function. + */ + public operator fun R.plus(other: P): R + /** + * Returns difference between the polynomial represented as rational function and the rational function. + */ + public operator fun R.minus(other: P): R + /** + * Returns product of the polynomial represented as rational function and the rational function. + */ + public operator fun R.times(other: P): R + // endregion + + // region Rational-rational relation + /** + * Returns the same rational function. + */ + public override operator fun R.unaryPlus(): R = this + /** + * Returns negation of the rational function. + */ + public override operator fun R.unaryMinus(): R + /** + * Returns sum of the rational functions. + */ + public override operator fun R.plus(other: R): R + /** + * Returns difference of the rational functions. + */ + public override operator fun R.minus(other: R): R + /** + * Returns product of the rational functions. + */ + public override operator fun R.times(other: R): R + + /** + * Check if the instant is zero rational function. + */ + public fun R.isZero(): Boolean = this == zero + /** + * Check if the instant is NOT zero rational function. + */ + public fun R.isNotZero(): Boolean = this != zero + /** + * Check if the instant is unit rational function. + */ + public fun R.isOne(): Boolean = this == one + /** + * Check if the instant is NOT unit rational function. + */ + public fun R.isNotOne(): Boolean = this != one + /** + * Check if the instant is minus unit rational function. + */ + public fun R.isMinusOne(): Boolean = this == -one + /** + * Check if the instant is NOT minus unit rational function. + */ + public fun R.isNotMinusOne(): Boolean = this != -one + + /** + * Instance of zero rational function (zero of the rational functions ring). + */ + public override val zero: R + /** + * Instance of unit polynomial (unit of the rational functions ring). + */ + public override val one: R + + /** + * Checks equality of the rational functions. + */ + @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "CovariantEquals") + public fun R.equals(other: R): Boolean + // endregion + + // Not sure is it necessary... + // region Polynomial properties + /** + * Degree of the polynomial, [see also](https://en.wikipedia.org/wiki/Degree_of_a_polynomial). If the polynomial is + * zero, degree is -1. + */ + public val P.degree: Int + + /** + * Checks if the instant is constant polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isConstant(): Boolean = degree <= 0 + /** + * Checks if the instant is **not** constant polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isNotConstant(): Boolean = !isConstant() + /** + * Checks if the instant is constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isNonZeroConstant(): Boolean = degree == 0 + /** + * Checks if the instant is **not** constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + public fun P.isNotNonZeroConstant(): Boolean = !isNonZeroConstant() + + public fun P.asConstantOrNull(): C? + + public fun P.asConstant(): C = asConstantOrNull() ?: error("Can not represent non-constant polynomial as a constant") + + // endregion + + // Not sure is it necessary... + // region Polynomial properties + /** + * Checks if the instant is constant polynomial (of degree no more than 0) over considered ring. + */ + public fun R.isConstant(): Boolean + /** + * Checks if the instant is **not** constant polynomial (of degree no more than 0) over considered ring. + */ + public fun R.isNotConstant(): Boolean = !isConstant() + /** + * Checks if the instant is constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + public fun R.isNonZeroConstant(): Boolean + /** + * Checks if the instant is **not** constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + public fun R.isNotNonZeroConstant(): Boolean = !isNonZeroConstant() + + public fun R.asConstantOrNull(): C? + + public fun R.asConstant(): C = asConstantOrNull() ?: error("Can not represent non-constant polynomial as a constant") + + // TODO: Перенести в реализацию +// fun R.substitute(argument: C): C +// fun R.substitute(argument: P): R +// fun R.substitute(argument: R): R +// +// fun R.asFunction(): (C) -> C = /*this::substitute*/ { this.substitute(it) } +// fun R.asFunctionOnConstants(): (C) -> C = /*this::substitute*/ { this.substitute(it) } +// fun P.asFunctionOnPolynomials(): (P) -> R = /*this::substitute*/ { this.substitute(it) } +// fun R.asFunctionOnRationalFunctions(): (R) -> R = /*this::substitute*/ { this.substitute(it) } +// +// operator fun R.invoke(argument: C): C = this.substitute(argument) +// operator fun R.invoke(argument: P): R = this.substitute(argument) +// operator fun R.invoke(argument: R): R = this.substitute(argument) + // endregion + + // region Legacy + override fun add(left: R, right: R): R = left + right + override fun multiply(left: R, right: R): R = left * right + // endregion +} \ No newline at end of file From ffea8cc2234d869745fb0b99a8baedbf9cb51118 Mon Sep 17 00:00:00 2001 From: Gleb Minaev <43728100+lounres@users.noreply.github.com> Date: Sun, 13 Mar 2022 03:25:25 +0300 Subject: [PATCH 3/4] Regenerated READMEs --- README.md | 4 ++-- kmath-ast/README.md | 47 +++++++++++++++++++++++++++++--------- kmath-complex/README.md | 6 ++--- kmath-core/README.md | 6 ++--- kmath-ejml/README.md | 6 ++--- kmath-for-real/README.md | 6 ++--- kmath-functions/README.md | 6 ++--- kmath-jafama/README.md | 6 ++--- kmath-kotlingrad/README.md | 6 ++--- kmath-nd4j/README.md | 6 ++--- kmath-tensors/README.md | 6 ++--- 11 files changed, 65 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 99dd6d00f..76b8ce2f7 100644 --- a/README.md +++ b/README.md @@ -308,8 +308,8 @@ repositories { } dependencies { - api("space.kscience:kmath-core:0.3.0-dev-17") - // api("space.kscience:kmath-core-jvm:0.3.0-dev-17") for jvm-specific version + api("space.kscience:kmath-core:0.3.0-dev-19") + // api("space.kscience:kmath-core-jvm:0.3.0-dev-19") for jvm-specific version } ``` diff --git a/kmath-ast/README.md b/kmath-ast/README.md index bedf17486..9411befe3 100644 --- a/kmath-ast/README.md +++ b/kmath-ast/README.md @@ -10,7 +10,7 @@ Extensions to MST API: transformations, dynamic compilation and visualization. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-19`. **Gradle:** ```gradle @@ -20,7 +20,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-ast:0.3.0-dev-17' + implementation 'space.kscience:kmath-ast:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation("space.kscience:kmath-ast:0.3.0-dev-17") + implementation("space.kscience:kmath-ast:0.3.0-dev-19") } ``` @@ -66,20 +66,19 @@ For example, the following code: ```kotlin import space.kscience.kmath.asm.compileToExpression -import space.kscience.kmath.complex.ComplexField +import space.kscience.kmath.operations.DoubleField -"x+2".parseMath().compileToExpression(ComplexField) +"x^3-x+3".parseMath().compileToExpression(DoubleField) ``` … leads to generation of bytecode, which can be decompiled to the following Java class: ```java -import java.util.Map; -import kotlin.jvm.functions.Function2; -import space.kscience.kmath.asm.internal.MapIntrinsics; -import space.kscience.kmath.complex.Complex; -import space.kscience.kmath.expressions.Expression; -import space.kscience.kmath.expressions.Symbol; +import java.util.*; +import kotlin.jvm.functions.*; +import space.kscience.kmath.asm.internal.*; +import space.kscience.kmath.complex.*; +import space.kscience.kmath.expressions.*; public final class CompiledExpression_45045_0 implements Expression { private final Object[] constants; @@ -91,6 +90,32 @@ public final class CompiledExpression_45045_0 implements Expression { } ``` +For `LongRing`, `IntRing`, and `DoubleField` specialization is supported for better performance: + +```java +import java.util.*; +import space.kscience.kmath.asm.internal.*; +import space.kscience.kmath.expressions.*; + +public final class CompiledExpression_-386104628_0 implements DoubleExpression { + private final SymbolIndexer indexer; + + public SymbolIndexer getIndexer() { + return this.indexer; + } + + public double invoke(double[] arguments) { + double var2 = arguments[0]; + return Math.pow(var2, 3.0D) - var2 + 3.0D; + } + + public final Double invoke(Map arguments) { + double var2 = ((Double)MapIntrinsics.getOrFail(arguments, "x")).doubleValue(); + return Math.pow(var2, 3.0D) - var2 + 3.0D; + } +} +``` + Setting JVM system property `space.kscience.kmath.ast.dump.generated.classes` to `1` makes the translator dump class files to program's working directory, so they can be reviewed manually. #### Limitations diff --git a/kmath-complex/README.md b/kmath-complex/README.md index 92f2435ba..cfaf43aa1 100644 --- a/kmath-complex/README.md +++ b/kmath-complex/README.md @@ -8,7 +8,7 @@ Complex and hypercomplex number systems in KMath. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-19`. **Gradle:** ```gradle @@ -18,7 +18,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-complex:0.3.0-dev-17' + implementation 'space.kscience:kmath-complex:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -29,6 +29,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-complex:0.3.0-dev-17") + implementation("space.kscience:kmath-complex:0.3.0-dev-19") } ``` diff --git a/kmath-core/README.md b/kmath-core/README.md index e765ad50c..4e980baf5 100644 --- a/kmath-core/README.md +++ b/kmath-core/README.md @@ -15,7 +15,7 @@ performance calculations to code generation. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-19`. **Gradle:** ```gradle @@ -25,7 +25,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-core:0.3.0-dev-17' + implementation 'space.kscience:kmath-core:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -36,6 +36,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-core:0.3.0-dev-17") + implementation("space.kscience:kmath-core:0.3.0-dev-19") } ``` diff --git a/kmath-ejml/README.md b/kmath-ejml/README.md index fcd092bf1..24b36aa0d 100644 --- a/kmath-ejml/README.md +++ b/kmath-ejml/README.md @@ -9,7 +9,7 @@ EJML based linear algebra implementation. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-19`. **Gradle:** ```gradle @@ -19,7 +19,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-ejml:0.3.0-dev-17' + implementation 'space.kscience:kmath-ejml:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -30,6 +30,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-ejml:0.3.0-dev-17") + implementation("space.kscience:kmath-ejml:0.3.0-dev-19") } ``` diff --git a/kmath-for-real/README.md b/kmath-for-real/README.md index 938327612..f6b02e6ad 100644 --- a/kmath-for-real/README.md +++ b/kmath-for-real/README.md @@ -9,7 +9,7 @@ Specialization of KMath APIs for Double numbers. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-19`. **Gradle:** ```gradle @@ -19,7 +19,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-for-real:0.3.0-dev-17' + implementation 'space.kscience:kmath-for-real:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -30,6 +30,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-for-real:0.3.0-dev-17") + implementation("space.kscience:kmath-for-real:0.3.0-dev-19") } ``` diff --git a/kmath-functions/README.md b/kmath-functions/README.md index 3d4beee47..a7f4f9b6f 100644 --- a/kmath-functions/README.md +++ b/kmath-functions/README.md @@ -11,7 +11,7 @@ Functions and interpolations. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-19`. **Gradle:** ```gradle @@ -21,7 +21,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-functions:0.3.0-dev-17' + implementation 'space.kscience:kmath-functions:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -32,6 +32,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-functions:0.3.0-dev-17") + implementation("space.kscience:kmath-functions:0.3.0-dev-19") } ``` diff --git a/kmath-jafama/README.md b/kmath-jafama/README.md index 760244751..3e0d9c418 100644 --- a/kmath-jafama/README.md +++ b/kmath-jafama/README.md @@ -7,7 +7,7 @@ Integration with [Jafama](https://github.com/jeffhain/jafama). ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-jafama:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-jafama:0.3.0-dev-19`. **Gradle:** ```gradle @@ -17,7 +17,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-jafama:0.3.0-dev-17' + implementation 'space.kscience:kmath-jafama:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -28,7 +28,7 @@ repositories { } dependencies { - implementation("space.kscience:kmath-jafama:0.3.0-dev-17") + implementation("space.kscience:kmath-jafama:0.3.0-dev-19") } ``` diff --git a/kmath-kotlingrad/README.md b/kmath-kotlingrad/README.md index 588ccb9b4..422ce4fb0 100644 --- a/kmath-kotlingrad/README.md +++ b/kmath-kotlingrad/README.md @@ -8,7 +8,7 @@ ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-kotlingrad:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-kotlingrad:0.3.0-dev-19`. **Gradle:** ```gradle @@ -18,7 +18,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-kotlingrad:0.3.0-dev-17' + implementation 'space.kscience:kmath-kotlingrad:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -29,6 +29,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-kotlingrad:0.3.0-dev-17") + implementation("space.kscience:kmath-kotlingrad:0.3.0-dev-19") } ``` diff --git a/kmath-nd4j/README.md b/kmath-nd4j/README.md index 7ca9cd4fd..23d529e72 100644 --- a/kmath-nd4j/README.md +++ b/kmath-nd4j/README.md @@ -9,7 +9,7 @@ ND4J based implementations of KMath abstractions. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-19`. **Gradle:** ```gradle @@ -19,7 +19,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-nd4j:0.3.0-dev-17' + implementation 'space.kscience:kmath-nd4j:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -30,7 +30,7 @@ repositories { } dependencies { - implementation("space.kscience:kmath-nd4j:0.3.0-dev-17") + implementation("space.kscience:kmath-nd4j:0.3.0-dev-19") } ``` diff --git a/kmath-tensors/README.md b/kmath-tensors/README.md index 42ce91336..93f78e895 100644 --- a/kmath-tensors/README.md +++ b/kmath-tensors/README.md @@ -9,7 +9,7 @@ Common linear algebra operations on tensors. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-tensors:0.3.0-dev-17`. +The Maven coordinates of this project are `space.kscience:kmath-tensors:0.3.0-dev-19`. **Gradle:** ```gradle @@ -19,7 +19,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-tensors:0.3.0-dev-17' + implementation 'space.kscience:kmath-tensors:0.3.0-dev-19' } ``` **Gradle Kotlin DSL:** @@ -30,6 +30,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-tensors:0.3.0-dev-17") + implementation("space.kscience:kmath-tensors:0.3.0-dev-19") } ``` From 843d63c76a46a55c683f4a8276663f5fecc6d6f5 Mon Sep 17 00:00:00 2001 From: Gleb Minaev <43728100+lounres@users.noreply.github.com> Date: Sun, 13 Mar 2022 03:27:00 +0300 Subject: [PATCH 4/4] Added support for all polynomials. But standard utilities still are not fully implemented. --- .../kmath/functions/AbstractPolynomial.kt | 47 +- .../functions/AbstractRationalFunction.kt | 2 - .../kmath/functions/LabeledPolynomial.kt | 927 ++++++++++++++++++ .../kmath/functions/NumberedPolynomial.kt | 689 +++++++++++++ .../kscience/kmath/functions/Polynomial.kt | 151 ++- .../kscience/kmath/functions/Variable.kt | 38 + .../kscience/kmath/functions/algebraicStub.kt | 51 + .../kmath/functions/labeledPolynomialUtil.kt | 490 +++++++++ .../kmath/functions/numberedPolynomialUtil.kt | 605 ++++++++++++ .../kmath/functions/polynomialUtil.kt | 6 +- 10 files changed, 2911 insertions(+), 95 deletions(-) create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/LabeledPolynomial.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Variable.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/algebraicStub.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/labeledPolynomialUtil.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedPolynomialUtil.kt diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt index 237a72bcc..b7b7116f0 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractPolynomial.kt @@ -17,6 +17,9 @@ public interface AbstractPolynomial /** * Abstraction of ring of polynomials of type [P] over ring of constants of type [C]. + * + * @param C the type of constants. Polynomials have them as a coefficients in their terms. + * @param P the type of polynomials. */ @Suppress("INAPPLICABLE_JVM_NAME", "PARAMETER_NAME_CHANGED_ON_OVERRIDE") public interface AbstractPolynomialSpace> : Ring

{ @@ -307,48 +310,4 @@ public interface AbstractPolynomialSpace> : Ring

override fun add(left: P, right: P): P = left + right override fun multiply(left: P, right: P): P = left * right // endregion - - public companion object { - // TODO: All of this should be moved to algebraic structures' place for utilities - // TODO: Move receiver to context receiver - /** - * Multiplication of element and integer. - * - * @receiver the multiplicand. - * @param other the multiplier. - * @return the difference. - */ - internal tailrec fun Group.optimizedMultiply(arg: C, other: Int): C = - when { - other == 0 -> zero - other == 1 -> arg - other == -1 -> -arg - other % 2 == 0 -> optimizedMultiply(arg + arg, other / 2) - other % 2 == 1 -> optimizedAddMultiplied(arg, arg + arg, other / 2) - other % 2 == -1 -> optimizedAddMultiplied(-arg, arg + arg, other / 2) - else -> error("Error in multiplication group instant by integer: got reminder by division by 2 different from 0, 1 and -1") - } - - // TODO: Move receiver to context receiver - /** - * Adds product of [arg] and [multiplier] to [base]. - * - * @receiver the algebra to provide multiplication. - * @param base the augend. - * @param arg the multiplicand. - * @param multiplier the multiplier. - * @return sum of the augend [base] and product of the multiplicand [arg] and the multiplier [multiplier]. - * @author Gleb Minaev - */ - internal tailrec fun Group.optimizedAddMultiplied(base: C, arg: C, multiplier: Int): C = - when { - multiplier == 0 -> base - multiplier == 1 -> base + arg - multiplier == -1 -> base - arg - multiplier % 2 == 0 -> optimizedAddMultiplied(base, arg + arg, multiplier / 2) - multiplier % 2 == 1 -> optimizedAddMultiplied(base + arg, arg + arg, multiplier / 2) - multiplier % 2 == -1 -> optimizedAddMultiplied(base + arg, arg + arg, multiplier / 2) - else -> error("Error in multiplication group instant by integer: got reminder by division by 2 different from 0, 1 and -1") - } - } } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt index 5cb570c9f..34050aa0f 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/AbstractRationalFunction.kt @@ -5,8 +5,6 @@ package space.kscience.kmath.functions -import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedAddMultiplied -import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedMultiply import space.kscience.kmath.operations.* import kotlin.js.JsName import kotlin.jvm.JvmName diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/LabeledPolynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/LabeledPolynomial.kt new file mode 100644 index 000000000..48f6f57fa --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/LabeledPolynomial.kt @@ -0,0 +1,927 @@ +package space.kscience.kmath.functions + +import space.kscience.kmath.operations.* +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.experimental.ExperimentalTypeInference +import kotlin.jvm.JvmName +import kotlin.math.max + + +/** + * Represents multivariate polynomials with labeled variables. + * + * @param C Ring in which the polynomial is considered. + */ +public class LabeledPolynomial +internal constructor( + /** + * Map that collects coefficients of the polynomial. Every non-zero monomial + * `a x_1^{d_1} ... x_n^{d_n}` is represented as pair "key-value" in the map, where value is coefficients `a` and + * key is map that associates variables in the monomial with multiplicity of them occurring in the monomial. + * For example polynomial + * ``` + * 5 a^2 c^3 - 6 b + 0 b c + * ``` + * has coefficients represented as + * ``` + * mapOf( + * mapOf( + * a to 2, + * c to 3 + * ) to 5, + * mapOf( + * b to 1 + * ) to (-6) + * ) + * ``` + * where `a`, `b` and `c` are corresponding [Variable] objects. + */ + public val coefficients: Map, C> +) : AbstractPolynomial { + override fun toString(): String = "LabeledPolynomial$coefficients" +} + +// region Internal utilities + +/** + * Represents internal [LabeledPolynomial] errors. + */ +internal class LabeledPolynomialError: Error { + constructor(): super() + constructor(message: String): super(message) + constructor(message: String?, cause: Throwable?): super(message, cause) + constructor(cause: Throwable?): super(cause) +} + +/** + * Throws an [LabeledPolynomialError] with the given [message]. + */ +internal fun labeledPolynomialError(message: Any): Nothing = throw LabeledPolynomialError(message.toString()) + +/** + * Returns the same degrees description of the monomial, but without zero degrees. + */ +internal fun Map.cleanUp() = filterValues { it > 0U } + +// endregion + +// region Constructors and converters + +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//internal fun > LabeledPolynomial(coefs: Map, C>, toCheckInput: Boolean = true): LabeledPolynomial { +// if (!toCheckInput) return LabeledPolynomial(coefs) +// +// // Map for cleaned coefficients. +// val fixedCoefs = mutableMapOf, C>() +// +// // Cleaning the degrees, summing monomials of the same degrees. +// for (entry in coefs) { +// val key = entry.key.cleanUp() +// val value = entry.value +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// // Removing zero monomials. +// return LabeledPolynomial( +// fixedCoefs +// .filter { it.value.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//internal fun > LabeledPolynomial(pairs: Collection, C>>, toCheckInput: Boolean): LabeledPolynomial { +// if (!toCheckInput) return LabeledPolynomial(pairs.toMap()) +// +// // Map for cleaned coefficients. +// val fixedCoefs = mutableMapOf, C>() +// +// // Cleaning the degrees, summing monomials of the same degrees. +// for (entry in pairs) { +// val key = entry.first.cleanUp() +// val value = entry.second +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// // Removing zero monomials. +// return LabeledPolynomial( +// fixedCoefs.filterValues { it.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represents monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//internal fun > LabeledPolynomial(vararg pairs: Pair, C>, toCheckInput: Boolean): LabeledPolynomial { +// if (!toCheckInput) return LabeledPolynomial(pairs.toMap()) +// +// // Map for cleaned coefficients. +// val fixedCoefs = mutableMapOf, C>() +// +// // Cleaning the degrees, summing monomials of the same degrees. +// for (entry in pairs) { +// val key = entry.first.cleanUp() +// val value = entry.second +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// // Removing zero monomials. +// return LabeledPolynomial( +// fixedCoefs.filterValues { it.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param coefs Coefficients of the instants. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//fun > LabeledPolynomial(coefs: Map, C>): LabeledPolynomial = LabeledPolynomial(coefs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represents monomials. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//fun > LabeledPolynomial(pairs: Collection, C>>): LabeledPolynomial = LabeledPolynomial(pairs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represents monomials. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//fun > LabeledPolynomial(vararg pairs: Pair, C>): LabeledPolynomial = LabeledPolynomial(*pairs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(LabeledPolynomialSpace) +//internal fun > LabeledPolynomial(coefs: Map, C>, toCheckInput: Boolean = true): LabeledPolynomial { +// if (!toCheckInput) return LabeledPolynomial(coefs) +// +// // Map for cleaned coefficients. +// val fixedCoefs = mutableMapOf, C>() +// +// // Cleaning the degrees, summing monomials of the same degrees. +// for (entry in coefs) { +// val key = entry.key.cleanUp() +// val value = entry.value +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// // Removing zero monomials. +// return LabeledPolynomial( +// fixedCoefs +// .filter { it.value.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(LabeledPolynomialSpace) +//internal fun > LabeledPolynomial(pairs: Collection, C>>, toCheckInput: Boolean): LabeledPolynomial { +// if (!toCheckInput) return LabeledPolynomial(pairs.toMap()) +// +// // Map for cleaned coefficients. +// val fixedCoefs = mutableMapOf, C>() +// +// // Cleaning the degrees, summing monomials of the same degrees. +// for (entry in pairs) { +// val key = entry.first.cleanUp() +// val value = entry.second +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// // Removing zero monomials. +// return LabeledPolynomial( +// fixedCoefs.filterValues { it.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represents monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(LabeledPolynomialSpace) +//internal fun > LabeledPolynomial(vararg pairs: Pair, C>, toCheckInput: Boolean): LabeledPolynomial { +// if (!toCheckInput) return LabeledPolynomial(pairs.toMap()) +// +// // Map for cleaned coefficients. +// val fixedCoefs = mutableMapOf, C>() +// +// // Cleaning the degrees, summing monomials of the same degrees. +// for (entry in pairs) { +// val key = entry.first.cleanUp() +// val value = entry.second +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// // Removing zero monomials. +// return LabeledPolynomial( +// fixedCoefs.filterValues { it.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param coefs Coefficients of the instants. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial(coefs: Map, C>): LabeledPolynomial = LabeledPolynomial(coefs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represents monomials. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial(pairs: Collection, C>>): LabeledPolynomial = LabeledPolynomial(pairs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from keys of received +// * map, sums up proportional monomials, removes aero monomials, and if result is zero map adds only element in it. +// * +// * @param pairs Collection of pairs that represents monomials. +// * +// * @throws LabeledPolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial(vararg pairs: Pair, C>): LabeledPolynomial = LabeledPolynomial(*pairs, toCheckInput = true) +// +//fun C.asLabeledPolynomial() : LabeledPolynomial = LabeledPolynomial(mapOf(emptyMap() to this)) +// +//context(A) +//fun > Variable.asLabeledPolynomial() : LabeledPolynomial = LabeledPolynomial(mapOf(mapOf(this to 1U) to one)) +// +//context(LabeledPolynomialSpace) +//fun > Variable.asLabeledPolynomial() : LabeledPolynomial = LabeledPolynomial(mapOf(mapOf(this to 1U) to ring.one)) +// +//context(A) +//fun > Variable.asLabeledPolynomial(c: C) : LabeledPolynomial = +// if(c.isZero()) LabeledPolynomial(emptyMap()) +// else LabeledPolynomial(mapOf(mapOf(this to 1U) to c)) +// +//context(LabeledPolynomialSpace) +//fun > Variable.asLabeledPolynomial(c: C) : LabeledPolynomial = +// if(c.isZero()) zero +// else LabeledPolynomial(mapOf(mapOf(this to 1U) to c)) + +// endregion + +/** + * Space of polynomials. + * + * @param C the type of operated polynomials. + * @param A the intersection of [Ring] of [C] and [ScaleOperations] of [C]. + * @param ring the [A] instance. + */ +@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE", "INAPPLICABLE_JVM_NAME") +public class LabeledPolynomialSpace>( + public val ring: A, +) : AbstractPolynomialSpace> { + // region Constant-integer relation + @JvmName("constantIntPlus") + public override operator fun C.plus(other: Int): C = ring { optimizedAddMultiplied(this@plus, one, other) } + @JvmName("constantIntMinus") + public override operator fun C.minus(other: Int): C = ring { optimizedAddMultiplied(this@minus, one, -other) } + @JvmName("constantIntTimes") + public override operator fun C.times(other: Int): C = ring { optimizedMultiply(this@times, other) } + // endregion + + // region Integer-constant relation + @JvmName("intConstantPlus") + public override operator fun Int.plus(other: C): C = ring { optimizedAddMultiplied(other, one, this@plus) } + @JvmName("intConstantMinus") + public override operator fun Int.minus(other: C): C = ring { optimizedAddMultiplied(-other, one, this@minus) } + @JvmName("intConstantTimes") + public override operator fun Int.times(other: C): C = ring { optimizedMultiply(other, this@times) } + // endregion + + // region Variable-integer relation + public operator fun Variable.plus(other: Int): LabeledPolynomial = + if (other == 0) LabeledPolynomial(mapOf( + mapOf(this@plus to 1U) to ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(this@plus to 1U) to ring.one, + emptyMap() to ring.one * other, + )) + public operator fun Variable.minus(other: Int): LabeledPolynomial = + if (other == 0) LabeledPolynomial(mapOf( + mapOf(this@minus to 1U) to -ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(this@minus to 1U) to -ring.one, + emptyMap() to ring.one * other, + )) + public operator fun Variable.times(other: Int): LabeledPolynomial = + if (other == 0) zero + else LabeledPolynomial(mapOf( + mapOf(this to 1U) to ring.one * other, + )) + // endregion + + // region Integer-variable relation + public operator fun Int.plus(other: Variable): LabeledPolynomial = + if (this == 0) LabeledPolynomial(mapOf( + mapOf(other to 1U) to ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(other to 1U) to ring.one, + emptyMap() to ring.one * this@plus, + )) + public operator fun Int.minus(other: Variable): LabeledPolynomial = + if (this == 0) LabeledPolynomial(mapOf( + mapOf(other to 1U) to -ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(other to 1U) to -ring.one, + emptyMap() to ring.one * this@minus, + )) + public operator fun Int.times(other: Variable): LabeledPolynomial = + if (this == 0) zero + else LabeledPolynomial(mapOf( + mapOf(other to 1U) to ring.one * this@times, + )) + // endregion + + // region Polynomial-integer relation + public override operator fun LabeledPolynomial.plus(other: Int): LabeledPolynomial = + if (other == 0) this + else + LabeledPolynomial( + coefficients + .toMutableMap() + .apply { + val degs = emptyMap() + + val result = getOrElse(degs) { ring.zero } + other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun LabeledPolynomial.minus(other: Int): LabeledPolynomial = + if (other == 0) this + else + LabeledPolynomial( + coefficients + .toMutableMap() + .apply { + val degs = emptyMap() + + val result = getOrElse(degs) { ring.zero } - other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun LabeledPolynomial.times(other: Int): LabeledPolynomial = + if (other == 0) zero + else LabeledPolynomial( + coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> c * other } + } + ) + // endregion + + // region Integer-polynomial relation + public override operator fun Int.plus(other: LabeledPolynomial): LabeledPolynomial = + if (this == 0) other + else + LabeledPolynomial( + other.coefficients + .toMutableMap() + .apply { + val degs = emptyMap() + + val result = this@plus + getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun Int.minus(other: LabeledPolynomial): LabeledPolynomial = + if (this == 0) other + else + LabeledPolynomial( + other.coefficients + .toMutableMap() + .apply { + val degs = emptyMap() + + val result = this@minus - getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun Int.times(other: LabeledPolynomial): LabeledPolynomial = + if (this == 0) zero + else LabeledPolynomial( + other.coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> this@times * c } + } + ) + // endregion + + // region Constant-constant relation + @JvmName("constantUnaryMinus") + override operator fun C.unaryMinus(): C = ring { -this@unaryMinus } + @JvmName("constantPlus") + override operator fun C.plus(other: C): C = ring { this@plus + other } + @JvmName("constantMinus") + override operator fun C.minus(other: C): C = ring { this@minus - other } + @JvmName("constantTimes") + override operator fun C.times(other: C): C = ring { this@times * other } + @JvmName("constantIsZero") + public override fun C.isZero(): Boolean = ring { this == zero } + @JvmName("constantIsNotZero") + public override fun C.isNotZero(): Boolean = ring { this != zero } + @JvmName("constantIsOne") + public override fun C.isOne(): Boolean = ring { this == one } + @JvmName("constantIsNotOne") + public override fun C.isNotOne(): Boolean = ring { this != one } + @JvmName("constantIsMinusOne") + public override fun C.isMinusOne(): Boolean = ring { this == -one } + @JvmName("constantIsNotMinusOne") + public override fun C.isNotMinusOne(): Boolean = ring { this != -one } + // endregion + + // region Constant-variable relation + public operator fun C.plus(other: Variable): LabeledPolynomial = + if (isZero()) LabeledPolynomial(mapOf( + mapOf(other to 1U) to ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(other to 1U) to ring.one, + emptyMap() to this@plus, + )) + public operator fun C.minus(other: Variable): LabeledPolynomial = + if (isZero()) LabeledPolynomial(mapOf( + mapOf(other to 1U) to -ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(other to 1U) to -ring.one, + emptyMap() to this@minus, + )) + public operator fun C.times(other: Variable): LabeledPolynomial = + if (isZero()) zero + else LabeledPolynomial(mapOf( + mapOf(other to 1U) to this@times, + )) + // endregion + + // region Variable-constant relation + public operator fun Variable.plus(other: C): LabeledPolynomial = + if (other.isZero()) LabeledPolynomial(mapOf( + mapOf(this@plus to 1U) to ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(this@plus to 1U) to ring.one, + emptyMap() to other, + )) + public operator fun Variable.minus(other: C): LabeledPolynomial = + if (other.isZero()) LabeledPolynomial(mapOf( + mapOf(this@minus to 1U) to -ring.one, + )) + else LabeledPolynomial(mapOf( + mapOf(this@minus to 1U) to -ring.one, + emptyMap() to other, + )) + public operator fun Variable.times(other: C): LabeledPolynomial = + if (other.isZero()) zero + else LabeledPolynomial(mapOf( + mapOf(this@times to 1U) to other, + )) + // endregion + + // region Constant-polynomial relation + override operator fun C.plus(other: LabeledPolynomial): LabeledPolynomial = + if (this.isZero()) other + else with(other.coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(emptyMap() to this@plus)) + else LabeledPolynomial( + toMutableMap() + .apply { + val degs = emptyMap() + + val result = this@plus + getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + override operator fun C.minus(other: LabeledPolynomial): LabeledPolynomial = + if (this.isZero()) other + else with(other.coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(emptyMap() to this@minus)) + else LabeledPolynomial( + toMutableMap() + .apply { + forEach { (degs, c) -> if(degs.isNotEmpty()) this[degs] = -c } + + val degs = emptyMap() + + val result = this@minus - getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + override operator fun C.times(other: LabeledPolynomial): LabeledPolynomial = + if (this.isZero()) zero + else LabeledPolynomial( + other.coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> this@times * c } + } + ) + // endregion + + // region Polynomial-constant relation + /** + * Returns sum of the polynomials. [other] is interpreted as [UnivariatePolynomial]. + */ + override operator fun LabeledPolynomial.plus(other: C): LabeledPolynomial = + if (other.isZero()) this + else with(coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(emptyMap() to other)) + else LabeledPolynomial( + toMutableMap() + .apply { + val degs = emptyMap() + + val result = getOrElse(degs) { ring.zero } + other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + /** + * Returns difference of the polynomials. [other] is interpreted as [UnivariatePolynomial]. + */ + override operator fun LabeledPolynomial.minus(other: C): LabeledPolynomial = + if (other.isZero()) this + else with(coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(emptyMap() to other)) + else LabeledPolynomial( + toMutableMap() + .apply { + forEach { (degs, c) -> if(degs.isNotEmpty()) this[degs] = -c } + + val degs = emptyMap() + + val result = getOrElse(degs) { ring.zero } - other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + /** + * Returns product of the polynomials. [other] is interpreted as [UnivariatePolynomial]. + */ + override operator fun LabeledPolynomial.times(other: C): LabeledPolynomial = + if (other.isZero()) zero + else LabeledPolynomial( + coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> c * other } + } + ) + // endregion + + // region Variable-variable relation + public operator fun Variable.plus(other: Variable): LabeledPolynomial = + if (this == other) LabeledPolynomial(mapOf( + mapOf(this to 1U) to ring.one * 2 + )) + else LabeledPolynomial(mapOf( + mapOf(this to 1U) to ring.one, + mapOf(other to 1U) to ring.one, + )) + public operator fun Variable.minus(other: Variable): LabeledPolynomial = + if (this == other) zero + else LabeledPolynomial(mapOf( + mapOf(this to 1U) to ring.one, + mapOf(other to 1U) to -ring.one, + )) + public operator fun Variable.times(other: Variable): LabeledPolynomial = + if (this == other) LabeledPolynomial(mapOf( + mapOf(this to 2U) to ring.one + )) + else LabeledPolynomial(mapOf( + mapOf(this to 1U, other to 1U) to ring.one, + )) + // endregion + + // region Variable-polynomial relation + public operator fun Variable.plus(other: LabeledPolynomial): LabeledPolynomial = + with(other.coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(mapOf(this@plus to 1u) to ring.one)) + else LabeledPolynomial( + toMutableMap() + .apply { + val degs = mapOf(this@plus to 1U) + + val result = ring.one + getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + public operator fun Variable.minus(other: LabeledPolynomial): LabeledPolynomial = + with(other.coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(mapOf(this@minus to 1u) to ring.one)) + else LabeledPolynomial( + toMutableMap() + .apply { + forEach { (degs, c) -> if(degs.isNotEmpty()) this[degs] = -c } + + val degs = mapOf(this@minus to 1U) + + val result = ring.one - getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + public operator fun Variable.times(other: LabeledPolynomial): LabeledPolynomial = + LabeledPolynomial( + other.coefficients + .mapKeys { (degs, _) -> degs.toMutableMap().also{ it[this] = if (this in it) it[this]!! + 1U else 1U } } + ) + // endregion + + // region Polynomial-variable relation + public operator fun LabeledPolynomial.plus(other: Variable): LabeledPolynomial = + with(coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(mapOf(other to 1u) to ring.one)) + else LabeledPolynomial( + toMutableMap() + .apply { + val degs = mapOf(other to 1U) + + val result = ring.one + getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + public operator fun LabeledPolynomial.minus(other: Variable): LabeledPolynomial = + with(coefficients) { + if (isEmpty()) LabeledPolynomial(mapOf(mapOf(other to 1u) to ring.one)) + else LabeledPolynomial( + toMutableMap() + .apply { + val degs = mapOf(other to 1U) + + val result = ring.one - getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + public operator fun LabeledPolynomial.times(other: Variable): LabeledPolynomial = + LabeledPolynomial( + coefficients + .mapKeys { (degs, _) -> degs.toMutableMap().also{ it[other] = if (other in it) it[other]!! + 1U else 1U } } + ) + // endregion + + // region Polynomial-polynomial relation + /** + * Returns negation of the polynomial. + */ + override fun LabeledPolynomial.unaryMinus(): LabeledPolynomial = + LabeledPolynomial( + coefficients.mapValues { -it.value } + ) + /** + * Returns sum of the polynomials. + */ + override operator fun LabeledPolynomial.plus(other: LabeledPolynomial): LabeledPolynomial = + LabeledPolynomial( + coefficients + .applyAndRemoveZeros { + other.coefficients + .mapValuesTo(this) { (key, value) -> if (key in this) this[key]!! + value else value } + } + ) + /** + * Returns difference of the polynomials. + */ + override operator fun LabeledPolynomial.minus(other: LabeledPolynomial): LabeledPolynomial = + LabeledPolynomial( + coefficients + .applyAndRemoveZeros { + other.coefficients + .mapValuesTo(this) { (key, value) -> if (key in this) this[key]!! - value else -value } + } + ) + /** + * Returns product of the polynomials. + */ + override operator fun LabeledPolynomial.times(other: LabeledPolynomial): LabeledPolynomial = + when { + isZero() -> zero + other.isZero() -> zero + else -> LabeledPolynomial( + buildCoefficients { + for ((degs1, c1) in coefficients) for ((degs2, c2) in other.coefficients) { + val degs = degs1.toMutableMap() + degs2.mapValuesTo(degs) { (variable, deg) -> degs.getOrElse(variable) { 0u } + deg } + val c = c1 * c2 + this[degs] = if (degs in this) this[degs]!! + c else c + } + } + ) + } + + override val zero: LabeledPolynomial = LabeledPolynomial(mapOf(emptyMap() to ring.zero)) + override val one: LabeledPolynomial = LabeledPolynomial(mapOf(emptyMap() to ring.one)) + + // TODO: Docs + @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "CovariantEquals") + override fun LabeledPolynomial.equals(other: LabeledPolynomial): Boolean = + when { + this === other -> true + else -> coefficients.size == other.coefficients.size && + coefficients.all { (key, value) -> with(other.coefficients) { key in this && this[key] == value } } + } + // endregion + + // Not sure is it necessary... + // region Polynomial properties + /** + * Degree of the polynomial, [see also](https://en.wikipedia.org/wiki/Degree_of_a_polynomial). If the polynomial is + * zero, degree is -1. + */ + override val LabeledPolynomial.degree: Int + get() = coefficients.entries.maxOfOrNull { (degs, c) -> if (c.isZero()) -1 else degs.values.sum().toInt() } ?: -1 + /** + * Map that associates variables (that appear in the polynomial in positive exponents) with their most exponents + * in which they are appeared in the polynomial. + * + * As consequence all values in the map are positive integers. Also, if the polynomial is constant, the map is empty. + * And keys of the map is the same as in [variables]. + */ + public val LabeledPolynomial.degrees: Map + get() = + buildMap { + coefficients.entries.forEach { (degs, c) -> + if (c.isNotZero()) degs.mapValuesTo(this) { (variable, deg) -> + max(getOrElse(variable) { 0u }, deg) + } + } + } + /** + * Set of all variables that appear in the polynomial in positive exponents. + */ + public val LabeledPolynomial.variables: Set + get() = + buildSet { + coefficients.entries.forEach { (degs, c) -> if (c.isNotZero()) addAll(degs.keys) } + } + /** + * Count of all variables that appear in the polynomial in positive exponents. + */ + public val LabeledPolynomial.countOfVariables: Int get() = variables.size + + /** + * Checks if the instant is constant polynomial (of degree no more than 0) over considered ring. + */ + override fun LabeledPolynomial.isConstant(): Boolean = + coefficients.all { (degs, c) -> degs.isEmpty() || c.isZero() } + /** + * Checks if the instant is constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + override fun LabeledPolynomial.isNonZeroConstant(): Boolean = + with(coefficients) { + var foundAbsoluteTermAndItIsNotZero = false + for ((degs, c) in this) { + if (degs.isNotEmpty()) if (c.isNotZero()) return@with false + else { + if (c.isZero()) return@with false + else foundAbsoluteTermAndItIsNotZero = true + } + } + foundAbsoluteTermAndItIsNotZero + } + + override fun LabeledPolynomial.asConstantOrNull(): C? = + with(coefficients) { + if(isConstant()) getOrElse(emptyMap()) { ring.zero } + else null + } + +// @Suppress("NOTHING_TO_INLINE") +// public inline fun LabeledPolynomial.substitute(argument: Map): LabeledPolynomial = this.substitute(ring, argument) +// @Suppress("NOTHING_TO_INLINE") +// @JvmName("substitutePolynomial") +// public inline fun LabeledPolynomial.substitute(argument: Map>): LabeledPolynomial = this.substitute(ring, argument) +// +// @Suppress("NOTHING_TO_INLINE") +// public inline fun LabeledPolynomial.asFunction(): (Map) -> LabeledPolynomial = { this.substitute(ring, it) } +// @Suppress("NOTHING_TO_INLINE") +// public inline fun LabeledPolynomial.asFunctionOnConstants(): (Map) -> LabeledPolynomial = { this.substitute(ring, it) } +// @Suppress("NOTHING_TO_INLINE") +// public inline fun LabeledPolynomial.asFunctionOnPolynomials(): (Map>) -> LabeledPolynomial = { this.substitute(ring, it) } +// +// @Suppress("NOTHING_TO_INLINE") +// public inline operator fun LabeledPolynomial.invoke(argument: Map): LabeledPolynomial = this.substitute(ring, argument) +// @Suppress("NOTHING_TO_INLINE") +// @JvmName("invokePolynomial") +// public inline operator fun LabeledPolynomial.invoke(argument: Map>): LabeledPolynomial = this.substitute(ring, argument) + // endregion + + // region Legacy + @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") + override inline fun add(left: LabeledPolynomial, right: LabeledPolynomial): LabeledPolynomial = left + right + @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") + override inline fun multiply(left: LabeledPolynomial, right: LabeledPolynomial): LabeledPolynomial = left * right + // endregion + + // region Utilities + // TODO: Move to region internal utilities with context receiver + internal fun MutableMap, C>.applyAndRemoveZeros(block: MutableMap, C>.() -> Unit) : MutableMap, C> { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + block() + for ((degs, c) in this) if (c.isZero()) this.remove(degs) + return this + } + internal fun Map, C>.applyAndRemoveZeros(block: MutableMap, C>.() -> Unit) : Map, C> = + toMutableMap().applyAndRemoveZeros(block) + @OptIn(ExperimentalTypeInference::class) + internal fun buildCoefficients(@BuilderInference builderAction: MutableMap, C>.() -> Unit): Map, C> { + contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } + return buildMap { + builderAction() + for ((degs, c) in this) if (c.isZero()) this.remove(degs) + } + } + // endregion +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt new file mode 100644 index 000000000..f1ad9a74f --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt @@ -0,0 +1,689 @@ +package space.kscience.kmath.functions + +import space.kscience.kmath.operations.* +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.experimental.ExperimentalTypeInference +import kotlin.jvm.JvmName +import kotlin.math.max + + +/** + * Polynomial model without fixation on specific context they are applied to. + * + * @param C the type of constants. + */ +public class NumberedPolynomial +internal constructor( + /** + * Map that collects coefficients of the polynomial. Every monomial `a x_1^{d_1} ... x_n^{d_n}` is represented as + * pair "key-value" in the map, where value is coefficients `a` and + * key is list that associates index of every variable in the monomial with multiplicity of the variable occurring + * in the monomial. For example coefficients of polynomial `5 x_1^2 x_3^3 - 6 x_2` can be represented as + * ``` + * mapOf( + * listOf(2, 0, 3) to 5, + * listOf(0, 1) to (-6), + * ) + * ``` + * and also as + * ``` + * mapOf( + * listOf(2, 0, 3) to 5, + * listOf(0, 1) to (-6), + * listOf(0, 1, 1) to 0, + * ) + * ``` + * It is recommended not to put zero monomials into the map, but is not prohibited. Lists of degrees always do not + * contain any zeros on end, but can contain zeros on start or anywhere in middle. + */ + public val coefficients: Map, C> +) : AbstractPolynomial { + override fun toString(): String = "NumberedPolynomial$coefficients" +} + +// region Internal utilities + +/** + * Represents internal [Polynomial] errors. + */ +internal class NumberedPolynomialError : Error { + constructor(): super() + constructor(message: String): super(message) + constructor(message: String?, cause: Throwable?): super(message, cause) + constructor(cause: Throwable?): super(cause) +} + +/** + * Throws an [PolynomialError] with the given [message]. + */ +internal fun numberedPolynomialError(message: Any): Nothing = throw PolynomialError(message.toString()) + +/** + * Returns the same degrees description of the monomial, but without extra zero degrees on the end. + */ +internal fun List.cleanUp() = subList(0, indexOfLast { it != 0U } + 1) + +// endregion + +// region Constructors and converters +// Waiting for context receivers :( TODO: Replace with context receivers when they will be available + +//context(A) +//@Suppress("FunctionName") +//internal fun > NumberedPolynomial(coefs: Map, C>, toCheckInput: Boolean): NumberedPolynomial { +// if (!toCheckInput) return NumberedPolynomial(coefs) +// +// val fixedCoefs = mutableMapOf, C>() +// +// for (entry in coefs) { +// val key = entry.key.cleanUp() +// val value = entry.value +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// return NumberedPolynomial( +// fixedCoefs +// .filter { it.value.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//@Suppress("FunctionName") +//internal fun > NumberedPolynomial(pairs: Collection, C>>, toCheckInput: Boolean): NumberedPolynomial { +// if (!toCheckInput) return NumberedPolynomial(pairs.toMap()) +// +// val fixedCoefs = mutableMapOf, C>() +// +// for (entry in pairs) { +// val key = entry.first.cleanUp() +// val value = entry.second +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// return NumberedPolynomial( +// fixedCoefs +// .filter { it.value.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//@Suppress("FunctionName") +//internal fun > NumberedPolynomial(vararg pairs: Pair, C>, toCheckInput: Boolean): NumberedPolynomial = +// NumberedPolynomial(pairs.toMap(), toCheckInput) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param coefs Coefficients of the instants. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//public fun > NumberedPolynomial(coefs: Map, C>) = NumberedPolynomial(coefs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//public fun > NumberedPolynomial(pairs: Collection, C>>) = NumberedPolynomial(pairs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(A) +//public fun > NumberedPolynomial(vararg pairs: Pair, C>) = NumberedPolynomial(*pairs, toCheckInput = true) +// +//context(NumberedPolynomialSpace) +//@Suppress("FunctionName") +//internal fun > NumberedPolynomial(coefs: Map, C>, toCheckInput: Boolean): NumberedPolynomial { +// if (!toCheckInput) return NumberedPolynomial(coefs) +// +// val fixedCoefs = mutableMapOf, C>() +// +// for (entry in coefs) { +// val key = entry.key.cleanUp() +// val value = entry.value +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// return NumberedPolynomial( +// fixedCoefs +// .filter { it.value.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(NumberedPolynomialSpace) +//@Suppress("FunctionName") +//internal fun > NumberedPolynomial(pairs: Collection, C>>, toCheckInput: Boolean): NumberedPolynomial { +// if (!toCheckInput) return NumberedPolynomial(pairs.toMap()) +// +// val fixedCoefs = mutableMapOf, C>() +// +// for (entry in pairs) { +// val key = entry.first.cleanUp() +// val value = entry.second +// fixedCoefs[key] = if (key in fixedCoefs) fixedCoefs[key]!! + value else value +// } +// +// return NumberedPolynomial( +// fixedCoefs +// .filter { it.value.isNotZero() } +// ) +//} +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * @param toCheckInput If it's `true` cleaning of [coefficients] is executed otherwise it is not. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(NumberedPolynomialSpace) +//@Suppress("FunctionName") +//internal fun > NumberedPolynomial(vararg pairs: Pair, C>, toCheckInput: Boolean): NumberedPolynomial = +// NumberedPolynomial(pairs.toList(), toCheckInput) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param coefs Coefficients of the instants. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial(coefs: Map, C>) = NumberedPolynomial(coefs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial(pairs: Collection, C>>) = NumberedPolynomial(pairs, toCheckInput = true) +///** +// * Gets the coefficients in format of [coefficients] field and cleans it: removes zero degrees from end of received +// * lists, sums up proportional monomials, removes zero monomials, and if result is empty map adds only element in it. +// * +// * @param pairs Collection of pairs that represent monomials. +// * +// * @throws PolynomialError If no coefficient received or if any of degrees in any monomial is negative. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial(vararg pairs: Pair, C>) = NumberedPolynomial(*pairs, toCheckInput = true) + +public fun > C.asNumberedPolynomial() : NumberedPolynomial = NumberedPolynomial(mapOf(emptyList() to this)) + +// endregion + +/** + * Space of polynomials. + * + * @param C the type of operated polynomials. + * @param A the intersection of [Ring] of [C] and [ScaleOperations] of [C]. + * @param ring the [A] instance. + */ +@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE", "INAPPLICABLE_JVM_NAME") +public class NumberedPolynomialSpace>( + public val ring: A, +) : AbstractPolynomialSpace> { + // region Constant-integer relation + @JvmName("constantIntPlus") + public override operator fun C.plus(other: Int): C = ring { optimizedAddMultiplied(this@plus, one, other) } + @JvmName("constantIntMinus") + public override operator fun C.minus(other: Int): C = ring { optimizedAddMultiplied(this@minus, one, -other) } + @JvmName("constantIntTimes") + public override operator fun C.times(other: Int): C = ring { optimizedMultiply(this@times, other) } + // endregion + + // region Integer-constant relation + @JvmName("intConstantPlus") + public override operator fun Int.plus(other: C): C = ring { optimizedAddMultiplied(other, one, this@plus) } + @JvmName("intConstantMinus") + public override operator fun Int.minus(other: C): C = ring { optimizedAddMultiplied(-other, one, this@minus) } + @JvmName("intConstantTimes") + public override operator fun Int.times(other: C): C = ring { optimizedMultiply(other, this@times) } + // endregion + + // region Polynomial-integer relation + public override operator fun NumberedPolynomial.plus(other: Int): NumberedPolynomial = + if (other == 0) this + else + NumberedPolynomial( + coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + val result = getOrElse(degs) { ring.zero } + other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun NumberedPolynomial.minus(other: Int): NumberedPolynomial = + if (other == 0) this + else + NumberedPolynomial( + coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + val result = getOrElse(degs) { ring.zero } - other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun NumberedPolynomial.times(other: Int): NumberedPolynomial = + if (other == 0) zero + else NumberedPolynomial( + coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> c * other } + } + ) + // endregion + + // region Integer-polynomial relation + public override operator fun Int.plus(other: NumberedPolynomial): NumberedPolynomial = + if (this == 0) other + else + NumberedPolynomial( + other.coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + val result = this@plus + getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun Int.minus(other: NumberedPolynomial): NumberedPolynomial = + if (this == 0) other + else + NumberedPolynomial( + other.coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + val result = this@minus - getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + public override operator fun Int.times(other: NumberedPolynomial): NumberedPolynomial = + if (this == 0) zero + else NumberedPolynomial( + other.coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> this@times * c } + } + ) + // endregion + + // region Constant-constant relation + @JvmName("constantUnaryMinus") + override operator fun C.unaryMinus(): C = ring { -this@unaryMinus } + @JvmName("constantPlus") + override operator fun C.plus(other: C): C = ring { this@plus + other } + @JvmName("constantMinus") + override operator fun C.minus(other: C): C = ring { this@minus - other } + @JvmName("constantTimes") + override operator fun C.times(other: C): C = ring { this@times * other } + @JvmName("constantIsZero") + public override fun C.isZero(): Boolean = ring { this == zero } + @JvmName("constantIsNotZero") + public override fun C.isNotZero(): Boolean = ring { this != zero } + @JvmName("constantIsOne") + public override fun C.isOne(): Boolean = ring { this == one } + @JvmName("constantIsNotOne") + public override fun C.isNotOne(): Boolean = ring { this != one } + @JvmName("constantIsMinusOne") + public override fun C.isMinusOne(): Boolean = ring { this == -one } + @JvmName("constantIsNotMinusOne") + public override fun C.isNotMinusOne(): Boolean = ring { this != -one } + // endregion + + // region Constant-polynomial relation + override operator fun C.plus(other: NumberedPolynomial): NumberedPolynomial = + if (this.isZero()) other + else with(other.coefficients) { + if (isEmpty()) NumberedPolynomial(mapOf(emptyList() to this@plus)) + else NumberedPolynomial( + toMutableMap() + .apply { + val degs = emptyList() + + val result = this@plus + getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + override operator fun C.minus(other: NumberedPolynomial): NumberedPolynomial = + if (this.isZero()) -other + else with(other.coefficients) { + if (isEmpty()) NumberedPolynomial(mapOf(listOf() to this@minus)) + else NumberedPolynomial( + toMutableMap() + .apply { + forEach { (degs, c) -> if(degs.isNotEmpty()) this[degs] = -c } + + val degs = emptyList() + + val result = this@minus - getOrElse(degs) { ring.zero } + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + override operator fun C.times(other: NumberedPolynomial): NumberedPolynomial = + if (this.isZero()) zero + else NumberedPolynomial( + other.coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> this@times * c } + } + ) + // endregion + + // region Polynomial-constant relation + /** + * Returns sum of the polynomials. [other] is interpreted as [NumberedPolynomial]. + */ + override operator fun NumberedPolynomial.plus(other: C): NumberedPolynomial = + if (other.isZero()) this + else with(coefficients) { + if (isEmpty()) NumberedPolynomial(mapOf(listOf() to other)) + else NumberedPolynomial( + toMutableMap() + .apply { + val degs = emptyList() + + val result = getOrElse(degs) { ring.zero } + other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + /** + * Returns difference of the polynomials. [other] is interpreted as [NumberedPolynomial]. + */ + override operator fun NumberedPolynomial.minus(other: C): NumberedPolynomial = + if (other.isZero()) this + else with(coefficients) { + if (isEmpty()) NumberedPolynomial(mapOf(listOf() to other)) + else NumberedPolynomial( + toMutableMap() + .apply { + val degs = emptyList() + + val result = getOrElse(degs) { ring.zero } - other + + if (result.isZero()) remove(degs) + else this[degs] = result + } + ) + } + /** + * Returns product of the polynomials. [other] is interpreted as [NumberedPolynomial]. + */ + override operator fun NumberedPolynomial.times(other: C): NumberedPolynomial = + if (other.isZero()) zero + else NumberedPolynomial( + coefficients + .applyAndRemoveZeros { + mapValues { (_, c) -> c * other } + } + ) + // endregion + + // region Polynomial-polynomial relation + /** + * Returns negation of the polynomial. + */ + override fun NumberedPolynomial.unaryMinus(): NumberedPolynomial = + NumberedPolynomial( + coefficients.mapValues { -it.value } + ) + /** + * Returns sum of the polynomials. + */ + override operator fun NumberedPolynomial.plus(other: NumberedPolynomial): NumberedPolynomial = + NumberedPolynomial( + coefficients + .applyAndRemoveZeros { + other.coefficients + .mapValuesTo(this) { (key, value) -> if (key in this) this[key]!! + value else value } + } + ) + /** + * Returns difference of the polynomials. + */ + override operator fun NumberedPolynomial.minus(other: NumberedPolynomial): NumberedPolynomial = + NumberedPolynomial( + coefficients + .applyAndRemoveZeros { + other.coefficients + .mapValuesTo(this) { (key, value) -> if (key in this) this[key]!! - value else -value } + } + ) + /** + * Returns product of the polynomials. + */ + override operator fun NumberedPolynomial.times(other: NumberedPolynomial): NumberedPolynomial = + when { + isZero() -> zero + other.isZero() -> zero + else -> + NumberedPolynomial( + buildCoefficients { + for ((degs1, c1) in coefficients) for ((degs2, c2) in other.coefficients) { + val degs = + (0..max(degs1.lastIndex, degs2.lastIndex)) + .map { degs1.getOrElse(it) { 0U } + degs2.getOrElse(it) { 0U } } + val c = c1 * c2 + this[degs] = if (degs in this) this[degs]!! + c else c + } + } + ) + } + + public override fun NumberedPolynomial.isZero(): Boolean = coefficients.values.all { it.isZero() } + public override fun NumberedPolynomial.isNotZero(): Boolean = coefficients.values.any { it.isNotZero() } + public override fun NumberedPolynomial.isOne(): Boolean = + with(coefficients) { + var foundAbsoluteTermAndItIsOne = false + for ((degs, c) in this) { + if (degs.isNotEmpty()) if (c.isNotZero()) return@with false + else { + if (c.isNotOne()) return@with false + else foundAbsoluteTermAndItIsOne = true + } + } + foundAbsoluteTermAndItIsOne + } + public override fun NumberedPolynomial.isNotOne(): Boolean = !isOne() + public override fun NumberedPolynomial.isMinusOne(): Boolean = + with(coefficients) { + var foundAbsoluteTermAndItIsMinusOne = false + for ((degs, c) in this) { + if (degs.isNotEmpty()) if (c.isNotZero()) return@with false + else { + if (c.isNotMinusOne()) return@with false + else foundAbsoluteTermAndItIsMinusOne = true + } + } + foundAbsoluteTermAndItIsMinusOne + } + public override fun NumberedPolynomial.isNotMinusOne(): Boolean = !isMinusOne() + + override val zero: NumberedPolynomial = NumberedPolynomial(emptyMap()) + override val one: NumberedPolynomial = + NumberedPolynomial( + mapOf( + listOf() to ring.one // 1 * x_1^0 * x_2^0 * ... + ) + ) + + // TODO: Docs + @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "CovariantEquals") + override fun NumberedPolynomial.equals(other: NumberedPolynomial): Boolean = + when { + this === other -> true + else -> coefficients.size == other.coefficients.size && + coefficients.all { (key, value) -> with(other.coefficients) { key in this && this[key] == value } } + } + // endregion + + // Not sure is it necessary... + // region Polynomial properties + /** + * Count of all variables that appear in the polynomial in positive exponents. + */ + public val NumberedPolynomial.countOfVariables: Int + get() = coefficients.entries.maxOfOrNull { (degs, c) -> if (c.isZero()) 0 else degs.size } ?: 0 + /** + * Degree of the polynomial, [see also](https://en.wikipedia.org/wiki/Degree_of_a_polynomial). If the polynomial is + * zero, degree is -1. + */ + override val NumberedPolynomial.degree: Int + get() = coefficients.entries.maxOfOrNull { (degs, c) -> if (c.isZero()) -1 else degs.sum().toInt() } ?: -1 + /** + * List that associates indices of variables (that appear in the polynomial in positive exponents) with their most + * exponents in which the variables are appeared in the polynomial. + * + * As consequence all values in the list are non-negative integers. Also, if the polynomial is constant, the list is empty. + * And size of the list is [countOfVariables]. + */ + public val NumberedPolynomial.degrees: List + get() = + buildList(countOfVariables) { + repeat(countOfVariables) { add(0U) } + coefficients.entries.forEach { (degs, c) -> + if (c.isNotZero()) degs.forEachIndexed { index, deg -> + this[index] = max(this[index], deg) + } + } + } + + /** + * Checks if the instant is constant polynomial (of degree no more than 0) over considered ring. + */ + override fun NumberedPolynomial.isConstant(): Boolean = + coefficients.all { (degs, c) -> degs.isEmpty() || c.isZero() } + /** + * Checks if the instant is constant non-zero polynomial (of degree no more than 0) over considered ring. + */ + override fun NumberedPolynomial.isNonZeroConstant(): Boolean = + with(coefficients) { + var foundAbsoluteTermAndItIsNotZero = false + for ((degs, c) in this) { + if (degs.isNotEmpty()) if (c.isNotZero()) return@with false + else { + if (c.isZero()) return@with false + else foundAbsoluteTermAndItIsNotZero = true + } + } + foundAbsoluteTermAndItIsNotZero + } + + override fun NumberedPolynomial.asConstantOrNull(): C? = + with(coefficients) { + if(isConstant()) getOrElse(emptyList()) { ring.zero } + else null + } + + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(argument: Map): NumberedPolynomial = this.substitute(ring, argument) + @Suppress("NOTHING_TO_INLINE") + @JvmName("substitutePolynomial") + public inline fun NumberedPolynomial.substitute(argument: Map>): NumberedPolynomial = this.substitute(ring, argument) + + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunction(): (Map) -> NumberedPolynomial = { this.substitute(ring, it) } + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunctionOnConstants(): (Map) -> NumberedPolynomial = { this.substitute(ring, it) } + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunctionOnPolynomials(): (Map>) -> NumberedPolynomial = { this.substitute(ring, it) } + + @Suppress("NOTHING_TO_INLINE") + public inline operator fun NumberedPolynomial.invoke(argument: Map): NumberedPolynomial = this.substitute(ring, argument) + @Suppress("NOTHING_TO_INLINE") + @JvmName("invokePolynomial") + public inline operator fun NumberedPolynomial.invoke(argument: Map>): NumberedPolynomial = this.substitute(ring, argument) + // endregion + + // region Legacy + @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") + override inline fun add(left: NumberedPolynomial, right: NumberedPolynomial): NumberedPolynomial = left + right + @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") + override inline fun multiply(left: NumberedPolynomial, right: NumberedPolynomial): NumberedPolynomial = left * right + // endregion + + // region Utilities + // TODO: Move to region internal utilities with context receiver + internal fun MutableMap, C>.applyAndRemoveZeros(block: MutableMap, C>.() -> Unit) : MutableMap, C> { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + block() + for ((degs, c) in this) if (c.isZero()) this.remove(degs) + return this + } + internal fun Map, C>.applyAndRemoveZeros(block: MutableMap, C>.() -> Unit) : Map, C> = + toMutableMap().applyAndRemoveZeros(block) + @OptIn(ExperimentalTypeInference::class) + internal fun buildCoefficients(@BuilderInference builderAction: MutableMap, C>.() -> Unit): Map, C> { + contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } + return buildMap { + builderAction() + for ((degs, c) in this) if (c.isZero()) this.remove(degs) + } + } + // endregion +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt index 30280a396..99d6b0659 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt @@ -5,43 +5,38 @@ package space.kscience.kmath.functions -import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedAddMultiplied -import space.kscience.kmath.functions.AbstractPolynomialSpace.Companion.optimizedMultiply import space.kscience.kmath.operations.* import kotlin.jvm.JvmName import kotlin.math.max import kotlin.math.min /** - * Polynomial coefficients model without fixation on specific context they are applied to. + * Polynomial model without fixation on specific context they are applied to. * * @param coefficients constant is the leftmost coefficient. */ public class Polynomial(public val coefficients: List) : AbstractPolynomial { override fun toString(): String = "Polynomial$coefficients" - -// public companion object { -// /** -// * Default name of variables used in string representations. -// * -// * @see Polynomial.toString -// */ -// public var defaultVariableName: String = "x" -// -// /** -// * Represents result of division with remainder. -// */ -// public data class DividingResult( -// val quotient: Polynomial, -// val reminder: Polynomial -// ) -// } } +// region Internal utilities + /** * Represents internal [Polynomial] errors. */ -internal class PolynomialError(message: String): Error(message) +internal class PolynomialError : Error { + constructor(): super() + constructor(message: String): super(message) + constructor(message: String?, cause: Throwable?): super(message, cause) + constructor(cause: Throwable?): super(cause) +} + +/** + * Throws an [PolynomialError] with the given [message]. + */ +internal fun polynomialError(message: Any): Nothing = throw PolynomialError(message.toString()) + +// endregion // region Constructors and converters @@ -66,16 +61,16 @@ public fun T.asPolynomial() : Polynomial = Polynomial(listOf(this)) // endregion /** - * Space of polynomials constructed over ring. + * Space of univariate polynomials constructed over ring. * * @param C the type of constants. Polynomials have them as a coefficients in their terms. * @param A type of underlying ring of constants. It's [Ring] of [C]. * @param ring underlying ring of constants of type [A]. */ -@Suppress("INAPPLICABLE_JVM_NAME") // KT-31420 +@Suppress("INAPPLICABLE_JVM_NAME") // TODO: KT-31420 public open class PolynomialSpace>( public val ring: A, -) : AbstractPolynomialSpace>{ +) : AbstractPolynomialSpace> { // region Constant-integer relation @JvmName("constantIntPlus") public override operator fun C.plus(other: Int): C = ring { optimizedAddMultiplied(this@plus, one, other) } @@ -102,8 +97,14 @@ public open class PolynomialSpace>( coefficients .toMutableList() .apply { - if (isEmpty()) this[0] = ring.zero + other - else this[0] = this[0]!! + other + val result = getOrElse(0) { ring.zero } + other + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) public override operator fun Polynomial.minus(other: Int): Polynomial = @@ -113,8 +114,14 @@ public open class PolynomialSpace>( coefficients .toMutableList() .apply { - if (isEmpty()) this[0] = ring.zero - other - else this[0] = this[0]!! - other + val result = getOrElse(0) { ring.zero } - other + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) public override operator fun Polynomial.times(other: Int): Polynomial = @@ -134,8 +141,14 @@ public open class PolynomialSpace>( other.coefficients .toMutableList() .apply { - if (isEmpty()) this[0] = ring.zero + this@plus - else this[0] = this[0]!! + this@plus + val result = this@plus + getOrElse(0) { ring.zero } + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) public override operator fun Int.minus(other: Polynomial): Polynomial = @@ -145,8 +158,16 @@ public open class PolynomialSpace>( other.coefficients .toMutableList() .apply { - if (isEmpty()) this[0] = ring.zero - this@minus - else this[0] = this[0]!! - this@minus + forEachIndexed { index, c -> if (index != 0) this[index] = -c } + + val result = this@minus - getOrElse(0) { ring.zero } + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) public override operator fun Int.times(other: Polynomial): Polynomial = @@ -183,12 +204,20 @@ public open class PolynomialSpace>( // region Constant-polynomial relation public override operator fun C.plus(other: Polynomial): Polynomial = - with(other.coefficients) { + if (this.isZero()) other + else with(other.coefficients) { if (isEmpty()) Polynomial(listOf(this@plus)) else Polynomial( toMutableList() .apply { - this[0] += this@plus + val result = if (size == 0) this@plus else this@plus + get(0) + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) } @@ -196,12 +225,22 @@ public open class PolynomialSpace>( // listOf(coefficients[0] + other) + coefficients.subList(1, degree + 1) // ) public override operator fun C.minus(other: Polynomial): Polynomial = - with(other.coefficients) { + if (this.isZero()) other + else with(other.coefficients) { if (isEmpty()) Polynomial(listOf(-this@minus)) else Polynomial( toMutableList() .apply { - this[0] -= this@minus + forEachIndexed { index, c -> if (index != 0) this[index] = -c } + + val result = if (size == 0) this@minus else this@minus - get(0) + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) } @@ -209,9 +248,10 @@ public open class PolynomialSpace>( // listOf(coefficients[0] + other) + coefficients.subList(1, degree + 1) // ) public override operator fun C.times(other: Polynomial): Polynomial = - Polynomial( + if (this.isZero()) other + else Polynomial( other.coefficients -// .subList(0, other.degree + 1) + .subList(0, other.degree + 1) .map { it * this } ) // endregion @@ -224,7 +264,14 @@ public open class PolynomialSpace>( else Polynomial( toMutableList() .apply { - this[0] += other + val result = if (size == 0) other else get(0) + other + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) } @@ -232,12 +279,20 @@ public open class PolynomialSpace>( // listOf(coefficients[0] + other) + coefficients.subList(1, degree + 1) // ) public override operator fun Polynomial.minus(other: C): Polynomial = - with(coefficients) { + if (other.isZero()) this + else with(coefficients) { if (isEmpty()) Polynomial(listOf(other)) else Polynomial( toMutableList() .apply { - this[0] -= other + val result = if (size == 0) other else get(0) - other + val isResultZero = result.isZero() + + when { + size == 0 && !isResultZero -> add(result) + size > 1 || !isResultZero -> this[0] = result + else -> clear() + } } ) } @@ -245,9 +300,10 @@ public open class PolynomialSpace>( // listOf(coefficients[0] - other) + coefficients.subList(1, degree + 1) // ) public override operator fun Polynomial.times(other: C): Polynomial = - Polynomial( + if (other.isZero()) this + else Polynomial( coefficients -// .subList(0, degree + 1) + .subList(0, degree + 1) .map { it * other } ) // endregion @@ -283,8 +339,8 @@ public open class PolynomialSpace>( val thisDegree = degree val otherDegree = other.degree return when { - thisDegree == -1 -> this - otherDegree == -1 -> other + thisDegree == -1 -> zero + otherDegree == -1 -> zero else -> Polynomial( (0..(thisDegree + otherDegree)) @@ -293,6 +349,7 @@ public open class PolynomialSpace>( .map { coefficients[it] * other.coefficients[d - it] } .reduce { acc, rational -> acc + rational } } + .run { subList(0, indexOfLast { it.isNotZero() } + 1) } ) } } @@ -321,8 +378,8 @@ public open class PolynomialSpace>( } // endregion - // Not sure is it necessary... // region Polynomial properties + public override val Polynomial.degree: Int get() = coefficients.indexOfLast { it != ring.zero } public override fun Polynomial.asConstantOrNull(): C? = @@ -354,8 +411,8 @@ public open class PolynomialSpace>( public inline operator fun Polynomial.invoke(argument: C): C = this.substitute(ring, argument) @Suppress("NOTHING_TO_INLINE") public inline operator fun Polynomial.invoke(argument: Polynomial): Polynomial = this.substitute(ring, argument) - // endregion + // endregion } /** diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Variable.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Variable.kt new file mode 100644 index 000000000..410604fd3 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Variable.kt @@ -0,0 +1,38 @@ +package space.kscience.kmath.functions + +import kotlin.reflect.KProperty + + +/** + * Represents class of labeled variables like usual + * `x`, `y`, `z`, `a`, `b`, `n`, `m`, etc. + * + * Variables does not contain any information about field (or ring, ets.) they are considered in + * and therefore about coefficient. + * + * @property name Is the label or name of variable. For `x` it is `"x"`, for `n` – `"n"`, etc. + */ +public data class Variable (val name: String) : Comparable { + /** + * Represents the variable as a string. + * + * @return Only name of the variable. + */ + override fun toString(): String = name + /** + * Compares two variables. + * Comparison is realised by comparison of variables' names. + * + * Used in [LabeledPolynomial] and [LabeledRationalFunction] to sort monomials in + * [LabeledPolynomial.toString] and [LabeledRationalFunction.toString] in lexicographic order. + * + * @see Comparable.compareTo + * @sample LabeledPolynomial.monomialComparator + * @return Only name of the variable. + */ + override fun compareTo(other: Variable): Int = name.compareTo(other.name) + + public companion object { + public operator fun getValue(thisRef: Any?, property: KProperty<*>) : Variable = Variable(property.name) + } +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/algebraicStub.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/algebraicStub.kt new file mode 100644 index 000000000..9e5043b8c --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/algebraicStub.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.functions + +import space.kscience.kmath.operations.Group + + +// TODO: All of this should be moved to algebraic structures' place for utilities +// TODO: Move receiver to context receiver +/** + * Multiplication of element and integer. + * + * @receiver the multiplicand. + * @param other the multiplier. + * @return the difference. + */ +internal tailrec fun Group.optimizedMultiply(arg: C, other: Int): C = + when { + other == 0 -> zero + other == 1 -> arg + other == -1 -> -arg + other % 2 == 0 -> optimizedMultiply(arg + arg, other / 2) + other % 2 == 1 -> optimizedAddMultiplied(arg, arg + arg, other / 2) + other % 2 == -1 -> optimizedAddMultiplied(-arg, arg + arg, other / 2) + else -> error("Error in multiplication group instant by integer: got reminder by division by 2 different from 0, 1 and -1") + } + +// TODO: Move receiver to context receiver +/** + * Adds product of [arg] and [multiplier] to [base]. + * + * @receiver the algebra to provide multiplication. + * @param base the augend. + * @param arg the multiplicand. + * @param multiplier the multiplier. + * @return sum of the augend [base] and product of the multiplicand [arg] and the multiplier [multiplier]. + * @author Gleb Minaev + */ +internal tailrec fun Group.optimizedAddMultiplied(base: C, arg: C, multiplier: Int): C = + when { + multiplier == 0 -> base + multiplier == 1 -> base + arg + multiplier == -1 -> base - arg + multiplier % 2 == 0 -> optimizedAddMultiplied(base, arg + arg, multiplier / 2) + multiplier % 2 == 1 -> optimizedAddMultiplied(base + arg, arg + arg, multiplier / 2) + multiplier % 2 == -1 -> optimizedAddMultiplied(base + arg, arg + arg, multiplier / 2) + else -> error("Error in multiplication group instant by integer: got reminder by division by 2 different from 0, 1 and -1") + } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/labeledPolynomialUtil.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/labeledPolynomialUtil.kt new file mode 100644 index 000000000..62ac31b64 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/labeledPolynomialUtil.kt @@ -0,0 +1,490 @@ +package space.kscience.kmath.functions + +import space.kscience.kmath.operations.* +import kotlin.contracts.* + + +// TODO: Docs + +// region Sort of legacy + +//// region Constants +// +//// TODO: Reuse underlying ring extensions +// +//context(LabeledPolynomialSpace) +//@Suppress("NOTHING_TO_INLINE") +//fun > numberConstant(value: Int): C = ring { number(value) } +// +//context(LabeledPolynomialSpace) +//fun > power(arg: C, pow: UInt): C = ring { power(arg, pow) } +// +//context(LabeledPolynomialSpace) +//fun > multiplyWithPower(base: C, arg: C, pow: UInt): C = ring { multiplyWithPower(base, arg, pow) } +// +//// endregion + +//// region Variables +// +//context(LabeledPolynomialSpace) +//fun > power(arg: Variable, pow: UInt): LabeledPolynomial = +// if (pow == 0U) one +// else LabeledPolynomial(mapOf( +// mapOf(arg to pow) to ring.one +// )) +// +//// endregion + +//// region Polynomials +// +//context(LabeledPolynomialSpace) +//fun > number(value: Int): LabeledPolynomial = ring { LabeledPolynomial(mapOf(emptyMap() to number(value))) } +// +//context(LabeledPolynomialSpace) +//fun > multiplyWithPower(base: LabeledPolynomial, arg: LabeledPolynomial, pow: UInt): LabeledPolynomial = +// when { +// arg.isZero() && pow > 0U -> base +// arg.isOne() -> base +// arg.isMinusOne() -> if (pow % 2U == 0U) base else -base +// else -> multiplyWithPowerInternalLogic(base, arg, pow) +// } +// +//// Trivial but slow as duck +//context(LabeledPolynomialSpace) +//internal tailrec fun > multiplyWithPowerInternalLogic(base: LabeledPolynomial, arg: LabeledPolynomial, exponent: UInt): LabeledPolynomial = +// when { +// exponent == 0U -> base +// exponent == 1U -> base * arg +// exponent % 2U == 0U -> multiplyWithPowerInternalLogic(base, arg * arg, exponent / 2U) +// exponent % 2U == 1U -> multiplyWithPowerInternalLogic(base * arg, arg * arg, exponent / 2U) +// else -> error("Error in raising ring instant by unsigned integer: got reminder by division by 2 different from 0 and 1") +// } +// +//// endregion + +// endregion + +// region Utilities + +// TODO: Docs +@OptIn(ExperimentalContracts::class) +public inline fun , R> A.labeledPolynomial(block: LabeledPolynomialSpace.() -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return LabeledPolynomialSpace(this).block() +} + +// endregion + +//// region String representations +// +///** +// * Represents the polynomial as a [String] with names of variables substituted with names from [names]. +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.represent(names: Map = emptyMap()): String = +// coefficients.entries +// .sortedWith { o1, o2 -> LabeledPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .toSortedMap() +// .filter { it.value > 0U } +// .map { (variable, deg) -> +// val variableName = names.getOrDefault(variable, variable.toString()) +// when (deg) { +// 1U -> variableName +// else -> "$variableName^$deg" +// } +// } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer]. +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.represent(namer: (Variable) -> String): String = +// coefficients.entries +// .sortedWith { o1, o2 -> LabeledPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .toSortedMap() +// .filter { it.value > 0U } +// .map { (variable, deg) -> +// when (deg) { +// 1U -> namer(variable) +// else -> "${namer(variable)}^$deg" +// } +// } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] with names of variables substituted with names from [names] and with +// * brackets around the string if needed (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.representWithBrackets(names: Map = emptyMap()): String = +// with(represent(names)) { if (coefficients.count() == 1) this else "($this)" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer] and with brackets around the string if needed +// * (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.representWithBrackets(namer: (Variable) -> String): String = +// with(represent(namer)) { if (coefficients.count() == 1) this else "($this)" } +// +///** +// * Represents the polynomial as a [String] with names of variables substituted with names from [names]. +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.representReversed(names: Map = emptyMap()): String = +// coefficients.entries +// .sortedWith { o1, o2 -> -LabeledPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .toSortedMap() +// .filter { it.value > 0U } +// .map { (variable, deg) -> +// val variableName = names.getOrDefault(variable, variable.toString()) +// when (deg) { +// 1U -> variableName +// else -> "$variableName^$deg" +// } +// } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer]. +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.representReversed(namer: (Variable) -> String): String = +// coefficients.entries +// .sortedWith { o1, o2 -> -LabeledPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .toSortedMap() +// .filter { it.value > 0U } +// .map { (variable, deg) -> +// when (deg) { +// 1U -> namer(variable) +// else -> "${namer(variable)}^$deg" +// } +// } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] with names of variables substituted with names from [names] and with +// * brackets around the string if needed (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.representReversedWithBrackets(names: Map = emptyMap()): String = +// with(representReversed(names)) { if (coefficients.count() == 1) this else "($this)" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer] and with brackets around the string if needed +// * (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(LabeledPolynomialSpace) +//fun > LabeledPolynomial.representReversedWithBrackets(namer: (Variable) -> String): String = +// with(representReversed(namer)) { if (coefficients.count() == 1) this else "($this)" } +// +//// endregion + +// region Operator extensions + +//// region Field case +// +//operator fun > Polynomial.div(other: Polynomial): Polynomial { +// if (other.isZero()) throw ArithmeticException("/ by zero") +// if (isZero()) return this +// +// fun Map, T>.leadingTerm() = +// this +// .asSequence() +// .map { Pair(it.key, it.value) } +// .reduce { (accDegs, accC), (listDegs, listC) -> +// for (i in 0..accDegs.lastIndex) { +// if (accDegs[i] > listDegs.getOrElse(i) { 0 }) return@reduce accDegs to accC +// if (accDegs[i] < listDegs.getOrElse(i) { 0 }) return@reduce listDegs to listC +// } +// if (accDegs.size < listDegs.size) listDegs to listC else accDegs to accC +// } +// +// var thisCoefficients = coefficients.toMutableMap() +// val otherCoefficients = other.coefficients +// val quotientCoefficients = HashMap, T>() +// +// var (thisLeadingTermDegs, thisLeadingTermC) = thisCoefficients.leadingTerm() +// val (otherLeadingTermDegs, otherLeadingTermC) = otherCoefficients.leadingTerm() +// +// while ( +// thisLeadingTermDegs.size >= otherLeadingTermDegs.size && +// (0..otherLeadingTermDegs.lastIndex).all { thisLeadingTermDegs[it] >= otherLeadingTermDegs[it] } +// ) { +// val multiplierDegs = +// thisLeadingTermDegs +// .mapIndexed { index, deg -> deg - otherLeadingTermDegs.getOrElse(index) { 0 } } +// .cleanUp() +// val multiplierC = thisLeadingTermC / otherLeadingTermC +// +// quotientCoefficients[multiplierDegs] = multiplierC +// +// for ((degs, t) in otherCoefficients) { +// val productDegs = +// (0..max(degs.lastIndex, multiplierDegs.lastIndex)) +// .map { degs.getOrElse(it) { 0 } + multiplierDegs.getOrElse(it) { 0 } } +// .cleanUp() +// val productC = t * multiplierC +// thisCoefficients[productDegs] = +// if (productDegs in thisCoefficients) thisCoefficients[productDegs]!! - productC else -productC +// } +// +// thisCoefficients = thisCoefficients.filterValues { it.isNotZero() }.toMutableMap() +// +// if (thisCoefficients.isEmpty()) +// return Polynomial(quotientCoefficients, toCheckInput = false) +// +// val t = thisCoefficients.leadingTerm() +// thisLeadingTermDegs = t.first +// thisLeadingTermC = t.second +// } +// +// return Polynomial(quotientCoefficients, toCheckInput = false) +//} +// +//operator fun > Polynomial.div(other: T): Polynomial = +// if (other.isZero()) throw ArithmeticException("/ by zero") +// else +// Polynomial( +// coefficients +// .mapValues { it.value / other }, +// toCheckInput = false +// ) +// +//operator fun > Polynomial.rem(other: Polynomial): Polynomial { +// if (other.isZero()) throw ArithmeticException("/ by zero") +// if (isZero()) return this +// +// fun Map, T>.leadingTerm() = +// this +// .asSequence() +// .map { Pair(it.key, it.value) } +// .reduce { (accDegs, accC), (listDegs, listC) -> +// for (i in 0..accDegs.lastIndex) { +// if (accDegs[i] > listDegs.getOrElse(i) { 0 }) return@reduce accDegs to accC +// if (accDegs[i] < listDegs.getOrElse(i) { 0 }) return@reduce listDegs to listC +// } +// if (accDegs.size < listDegs.size) listDegs to listC else accDegs to accC +// } +// +// var thisCoefficients = coefficients.toMutableMap() +// val otherCoefficients = other.coefficients +// +// var (thisLeadingTermDegs, thisLeadingTermC) = thisCoefficients.leadingTerm() +// val (otherLeadingTermDegs, otherLeadingTermC) = otherCoefficients.leadingTerm() +// +// while ( +// thisLeadingTermDegs.size >= otherLeadingTermDegs.size && +// (0..otherLeadingTermDegs.lastIndex).all { thisLeadingTermDegs[it] >= otherLeadingTermDegs[it] } +// ) { +// val multiplierDegs = +// thisLeadingTermDegs +// .mapIndexed { index, deg -> deg - otherLeadingTermDegs.getOrElse(index) { 0 } } +// .cleanUp() +// val multiplierC = thisLeadingTermC / otherLeadingTermC +// +// for ((degs, t) in otherCoefficients) { +// val productDegs = +// (0..max(degs.lastIndex, multiplierDegs.lastIndex)) +// .map { degs.getOrElse(it) { 0 } + multiplierDegs.getOrElse(it) { 0 } } +// .cleanUp() +// val productC = t * multiplierC +// thisCoefficients[productDegs] = +// if (productDegs in thisCoefficients) thisCoefficients[productDegs]!! - productC else -productC +// } +// +// thisCoefficients = thisCoefficients.filterValues { it.isNotZero() }.toMutableMap() +// +// if (thisCoefficients.isEmpty()) +// return Polynomial(thisCoefficients, toCheckInput = false) +// +// val t = thisCoefficients.leadingTerm() +// thisLeadingTermDegs = t.first +// thisLeadingTermC = t.second +// } +// +// return Polynomial(thisCoefficients, toCheckInput = false) +//} +// +//infix fun > Polynomial.divrem(other: Polynomial): Polynomial.Companion.DividingResult { +// if (other.isZero()) throw ArithmeticException("/ by zero") +// if (isZero()) return Polynomial.Companion.DividingResult(this, this) +// +// fun Map, T>.leadingTerm() = +// this +// .asSequence() +// .map { Pair(it.key, it.value) } +// .reduce { (accDegs, accC), (listDegs, listC) -> +// for (i in 0..accDegs.lastIndex) { +// if (accDegs[i] > listDegs.getOrElse(i) { 0 }) return@reduce accDegs to accC +// if (accDegs[i] < listDegs.getOrElse(i) { 0 }) return@reduce listDegs to listC +// } +// if (accDegs.size < listDegs.size) listDegs to listC else accDegs to accC +// } +// +// var thisCoefficients = coefficients.toMutableMap() +// val otherCoefficients = other.coefficients +// val quotientCoefficients = HashMap, T>() +// +// var (thisLeadingTermDegs, thisLeadingTermC) = thisCoefficients.leadingTerm() +// val (otherLeadingTermDegs, otherLeadingTermC) = otherCoefficients.leadingTerm() +// +// while ( +// thisLeadingTermDegs.size >= otherLeadingTermDegs.size && +// (0..otherLeadingTermDegs.lastIndex).all { thisLeadingTermDegs[it] >= otherLeadingTermDegs[it] } +// ) { +// val multiplierDegs = +// thisLeadingTermDegs +// .mapIndexed { index, deg -> deg - otherLeadingTermDegs.getOrElse(index) { 0 } } +// .cleanUp() +// val multiplierC = thisLeadingTermC / otherLeadingTermC +// +// quotientCoefficients[multiplierDegs] = multiplierC +// +// for ((degs, t) in otherCoefficients) { +// val productDegs = +// (0..max(degs.lastIndex, multiplierDegs.lastIndex)) +// .map { degs.getOrElse(it) { 0 } + multiplierDegs.getOrElse(it) { 0 } } +// .cleanUp() +// val productC = t * multiplierC +// thisCoefficients[productDegs] = +// if (productDegs in thisCoefficients) thisCoefficients[productDegs]!! - productC else -productC +// } +// +// thisCoefficients = thisCoefficients.filterValues { it.isNotZero() }.toMutableMap() +// +// if (thisCoefficients.isEmpty()) +// return Polynomial.Companion.DividingResult( +// Polynomial(quotientCoefficients, toCheckInput = false), +// Polynomial(thisCoefficients, toCheckInput = false) +// ) +// +// val t = thisCoefficients.leadingTerm() +// thisLeadingTermDegs = t.first +// thisLeadingTermC = t.second +// } +// +// return Polynomial.Companion.DividingResult( +// Polynomial(quotientCoefficients, toCheckInput = false), +// Polynomial(thisCoefficients, toCheckInput = false) +// ) +//} +// +//// endregion + +// endregion + +//// region Polynomial substitution and functional representation +// +//public fun LabeledPolynomial.substitute(ring: Ring, args: Map): LabeledPolynomial = ring { +// if (coefficients.isEmpty()) return this@substitute +// LabeledPolynomial( +// buildMap { +// coefficients.forEach { (degs, c) -> +// val newDegs = degs.filterKeys { it !in args } +// val newC = degs.entries.asSequence().filter { it.key in args }.fold(c) { acc, (variable, deg) -> +// multiplyWithPower(acc, args[variable]!!, deg) +// } +// this[newDegs] = if (newDegs in this) this[newDegs]!! + newC else newC +// } +// } +// ) +//} +// +//// TODO: Replace with optimisation: the [result] may be unboxed, and all operations may be performed as soon as +//// possible on it +//@JvmName("substitutePolynomial") +//fun LabeledPolynomial.substitute(ring: Ring, arg: Map>) : LabeledPolynomial = +// ring.labeledPolynomial { +// if (coefficients.isEmpty()) return zero +// coefficients +// .asSequence() +// .map { (degs, c) -> +// degs.entries +// .asSequence() +// .filter { it.key in arg } +// .fold(LabeledPolynomial(mapOf(degs.filterKeys { it !in arg } to c))) { acc, (index, deg) -> +// multiplyWithPower(acc, arg[index]!!, deg) +// } +// } +// .reduce { acc, polynomial -> acc + polynomial } // TODO: Rewrite. Might be slow. +// } +// +//// TODO: Substitute rational function +// +//fun > LabeledPolynomial.asFunctionOver(ring: A): (Map) -> LabeledPolynomial = +// { substitute(ring, it) } +// +//fun > LabeledPolynomial.asPolynomialFunctionOver(ring: A): (Map>) -> LabeledPolynomial = +// { substitute(ring, it) } +// +//// endregion + +//// region Algebraic derivative and antiderivative +//// TODO +//// endregion \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedPolynomialUtil.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedPolynomialUtil.kt new file mode 100644 index 000000000..d4053442d --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedPolynomialUtil.kt @@ -0,0 +1,605 @@ +package space.kscience.kmath.functions + +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.* +import kotlin.contracts.* +import kotlin.jvm.JvmName + + +// TODO: Docs + +// region Sort of legacy + +//// region Constants +// +//// TODO: Reuse underlying ring extensions +// +//context(NumberedPolynomialSpace) +//@Suppress("NOTHING_TO_INLINE") +//public fun > numberConstant(value: Int): C = ring { number(value) } +// +//context(NumberedPolynomialSpace) +//public fun > multiplyWithPower(base: C, arg: C, pow: UInt): C = ring { multiplyWithPower(base, arg, pow) } +// +//// endregion + +//// region Polynomials +// +//context(NumberedPolynomialSpace) +//public fun > number(value: Int): NumberedPolynomial = ring { NumberedPolynomial(mapOf(emptyList() to number(value))) } +// +//context(NumberedPolynomialSpace) +//public fun > multiplyWithPower(base: NumberedPolynomial, arg: NumberedPolynomial, pow: UInt): NumberedPolynomial = +// when { +// arg.isZero() && pow > 0U -> base +// arg.isOne() -> base +// arg.isMinusOne() -> if (pow % 2U == 0U) base else -base +// else -> multiplyWithPowerInternalLogic(base, arg, pow) +// } +// +//// Trivial but slow as duck +//context(NumberedPolynomialSpace) +//internal tailrec fun > multiplyWithPowerInternalLogic(base: NumberedPolynomial, arg: NumberedPolynomial, exponent: UInt): NumberedPolynomial = +// when { +// exponent == 0U -> base +// exponent == 1U -> base * arg +// exponent % 2U == 0U -> multiplyWithPowerInternalLogic(base, arg * arg, exponent / 2U) +// exponent % 2U == 1U -> multiplyWithPowerInternalLogic(base * arg, arg * arg, exponent / 2U) +// else -> error("Error in raising ring instant by unsigned integer: got reminder by division by 2 different from 0 and 1") +// } +// +//// endregion + +// endregion + +// region Utilities + +// TODO: Docs +@OptIn(ExperimentalContracts::class) +public inline fun , R> A.numberedPolynomial(block: NumberedPolynomialSpace.() -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return NumberedPolynomialSpace(this).block() +} + +// endregion + +//// region String representations +// +///** +// * Represents the polynomial as a [String] where name of variable with index `i` is [withVariableName] + `"_${i+1}"`. +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.represent(withVariableName: String = NumberedPolynomial.defaultVariableName): String = +// coefficients.entries +// .sortedWith { o1, o2 -> NumberedPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .mapIndexed { index, deg -> +// when (deg) { +// 0U -> "" +// 1U -> "${withVariableName}_${index+1}" +// else -> "${withVariableName}_${index+1}^$deg" +// } +// } +// .filter { it.isNotEmpty() } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer]. +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.represent(namer: (Int) -> String): String = +// coefficients.entries +// .sortedWith { o1, o2 -> NumberedPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .mapIndexed { index, deg -> +// when (deg) { +// 0U -> "" +// 1U -> namer(index) +// else -> "${namer(index)}^$deg" +// } +// } +// .filter { it.isNotEmpty() } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] where name of variable with index `i` is [withVariableName] + `"_${i+1}"` +// * and with brackets around the string if needed (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.representWithBrackets(withVariableName: String = NumberedPolynomial.defaultVariableName): String = +// with(represent(withVariableName)) { if (coefficients.count() == 1) this else "($this)" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer] and with brackets around the string if needed +// * (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.representWithBrackets(namer: (Int) -> String): String = +// with(represent(namer)) { if (coefficients.count() == 1) this else "($this)" } +// +///** +// * Represents the polynomial as a [String] where name of variable with index `i` is [withVariableName] + `"_${i+1}"`. +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.representReversed(withVariableName: String = NumberedPolynomial.defaultVariableName): String = +// coefficients.entries +// .sortedWith { o1, o2 -> -NumberedPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .mapIndexed { index, deg -> +// when (deg) { +// 0U -> "" +// 1U -> "${withVariableName}_${index+1}" +// else -> "${withVariableName}_${index+1}^$deg" +// } +// } +// .filter { it.isNotEmpty() } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer]. +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.representReversed(namer: (Int) -> String): String = +// coefficients.entries +// .sortedWith { o1, o2 -> -NumberedPolynomial.monomialComparator.compare(o1.key, o2.key) } +// .asSequence() +// .map { (degs, t) -> +// if (degs.isEmpty()) "$t" +// else { +// when { +// t.isOne() -> "" +// t.isMinusOne() -> "-" +// else -> "$t " +// } + +// degs +// .mapIndexed { index, deg -> +// when (deg) { +// 0U -> "" +// 1U -> namer(index) +// else -> "${namer(index)}^$deg" +// } +// } +// .filter { it.isNotEmpty() } +// .joinToString(separator = " ") { it } +// } +// } +// .joinToString(separator = " + ") { it } +// .ifEmpty { "0" } +// +///** +// * Represents the polynomial as a [String] where name of variable with index `i` is [withVariableName] + `"_${i+1}"` +// * and with brackets around the string if needed (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.representReversedWithBrackets(withVariableName: String = NumberedPolynomial.defaultVariableName): String = +// with(representReversed(withVariableName)) { if (coefficients.count() == 1) this else "($this)" } +// +///** +// * Represents the polynomial as a [String] naming variables by [namer] and with brackets around the string if needed +// * (i.e. when there are at least two addends in the representation). +// * Consider that monomials are sorted in **reversed** lexicographic order. +// */ +//context(NumberedPolynomialSpace) +//public fun > NumberedPolynomial.representReversedWithBrackets(namer: (Int) -> String): String = +// with(representReversed(namer)) { if (coefficients.count() == 1) this else "($this)" } +// +//// endregion + +//// region Polynomial substitution and functional representation +// +//public fun NumberedPolynomial.substitute(ring: Ring, args: Map): NumberedPolynomial = ring { +// if (coefficients.isEmpty()) return this@substitute +// NumberedPolynomial( +// buildMap { +// coefficients.forEach { (degs, c) -> +// val newDegs = degs.mapIndexed { index, deg -> if (index in args) 0U else deg }.cleanUp() +// val newC = degs.foldIndexed(c) { index, acc, deg -> +// if (index in args) multiplyWithPower(acc, args[index]!!, deg) +// else acc +// } +// this[newDegs] = if (newDegs in this) this[newDegs]!! + newC else newC +// } +// } +// ) +//} +// +//// TODO: Replace with optimisation: the [result] may be unboxed, and all operations may be performed as soon as +//// possible on it +//@JvmName("substitutePolynomial") +//public fun NumberedPolynomial.substitute(ring: Ring, arg: Map>) : NumberedPolynomial = +// ring.numberedPolynomialSpace { +// if (coefficients.isEmpty()) return zero +// coefficients +// .asSequence() +// .map { (degs, c) -> +// degs.foldIndexed( +// NumberedPolynomial( +// degs.mapIndexed { index, deg -> if (index in arg) 0U else deg } to c +// ) +// ) { index, acc, deg -> if (index in arg) multiplyWithPower(acc, arg[index]!!, deg) else acc } +// } +// .reduce { acc, polynomial -> acc + polynomial } // TODO: Rewrite. Might be slow. +// } +// +//// TODO: Substitute rational function +// +//public fun > NumberedPolynomial.asFunctionOver(ring: A): (Map) -> NumberedPolynomial = +// { substitute(ring, it) } +// +//public fun > NumberedPolynomial.asPolynomialFunctionOver(ring: A): (Map>) -> NumberedPolynomial = +// { substitute(ring, it) } +// +//// endregion + +// region Operator extensions + +//// region Field case +// +//operator fun > Polynomial.div(other: Polynomial): Polynomial { +// if (other.isZero()) throw ArithmeticException("/ by zero") +// if (isZero()) return this +// +// fun Map, T>.leadingTerm() = +// this +// .asSequence() +// .map { Pair(it.key, it.value) } +// .reduce { (accDegs, accC), (listDegs, listC) -> +// for (i in 0..accDegs.lastIndex) { +// if (accDegs[i] > listDegs.getOrElse(i) { 0 }) return@reduce accDegs to accC +// if (accDegs[i] < listDegs.getOrElse(i) { 0 }) return@reduce listDegs to listC +// } +// if (accDegs.size < listDegs.size) listDegs to listC else accDegs to accC +// } +// +// var thisCoefficients = coefficients.toMutableMap() +// val otherCoefficients = other.coefficients +// val quotientCoefficients = HashMap, T>() +// +// var (thisLeadingTermDegs, thisLeadingTermC) = thisCoefficients.leadingTerm() +// val (otherLeadingTermDegs, otherLeadingTermC) = otherCoefficients.leadingTerm() +// +// while ( +// thisLeadingTermDegs.size >= otherLeadingTermDegs.size && +// (0..otherLeadingTermDegs.lastIndex).all { thisLeadingTermDegs[it] >= otherLeadingTermDegs[it] } +// ) { +// val multiplierDegs = +// thisLeadingTermDegs +// .mapIndexed { index, deg -> deg - otherLeadingTermDegs.getOrElse(index) { 0 } } +// .cleanUp() +// val multiplierC = thisLeadingTermC / otherLeadingTermC +// +// quotientCoefficients[multiplierDegs] = multiplierC +// +// for ((degs, t) in otherCoefficients) { +// val productDegs = +// (0..max(degs.lastIndex, multiplierDegs.lastIndex)) +// .map { degs.getOrElse(it) { 0 } + multiplierDegs.getOrElse(it) { 0 } } +// .cleanUp() +// val productC = t * multiplierC +// thisCoefficients[productDegs] = +// if (productDegs in thisCoefficients) thisCoefficients[productDegs]!! - productC else -productC +// } +// +// thisCoefficients = thisCoefficients.filterValues { it.isNotZero() }.toMutableMap() +// +// if (thisCoefficients.isEmpty()) +// return Polynomial(quotientCoefficients, toCheckInput = false) +// +// val t = thisCoefficients.leadingTerm() +// thisLeadingTermDegs = t.first +// thisLeadingTermC = t.second +// } +// +// return Polynomial(quotientCoefficients, toCheckInput = false) +//} +// +//operator fun > Polynomial.div(other: T): Polynomial = +// if (other.isZero()) throw ArithmeticException("/ by zero") +// else +// Polynomial( +// coefficients +// .mapValues { it.value / other }, +// toCheckInput = false +// ) +// +//operator fun > Polynomial.rem(other: Polynomial): Polynomial { +// if (other.isZero()) throw ArithmeticException("/ by zero") +// if (isZero()) return this +// +// fun Map, T>.leadingTerm() = +// this +// .asSequence() +// .map { Pair(it.key, it.value) } +// .reduce { (accDegs, accC), (listDegs, listC) -> +// for (i in 0..accDegs.lastIndex) { +// if (accDegs[i] > listDegs.getOrElse(i) { 0 }) return@reduce accDegs to accC +// if (accDegs[i] < listDegs.getOrElse(i) { 0 }) return@reduce listDegs to listC +// } +// if (accDegs.size < listDegs.size) listDegs to listC else accDegs to accC +// } +// +// var thisCoefficients = coefficients.toMutableMap() +// val otherCoefficients = other.coefficients +// +// var (thisLeadingTermDegs, thisLeadingTermC) = thisCoefficients.leadingTerm() +// val (otherLeadingTermDegs, otherLeadingTermC) = otherCoefficients.leadingTerm() +// +// while ( +// thisLeadingTermDegs.size >= otherLeadingTermDegs.size && +// (0..otherLeadingTermDegs.lastIndex).all { thisLeadingTermDegs[it] >= otherLeadingTermDegs[it] } +// ) { +// val multiplierDegs = +// thisLeadingTermDegs +// .mapIndexed { index, deg -> deg - otherLeadingTermDegs.getOrElse(index) { 0 } } +// .cleanUp() +// val multiplierC = thisLeadingTermC / otherLeadingTermC +// +// for ((degs, t) in otherCoefficients) { +// val productDegs = +// (0..max(degs.lastIndex, multiplierDegs.lastIndex)) +// .map { degs.getOrElse(it) { 0 } + multiplierDegs.getOrElse(it) { 0 } } +// .cleanUp() +// val productC = t * multiplierC +// thisCoefficients[productDegs] = +// if (productDegs in thisCoefficients) thisCoefficients[productDegs]!! - productC else -productC +// } +// +// thisCoefficients = thisCoefficients.filterValues { it.isNotZero() }.toMutableMap() +// +// if (thisCoefficients.isEmpty()) +// return Polynomial(thisCoefficients, toCheckInput = false) +// +// val t = thisCoefficients.leadingTerm() +// thisLeadingTermDegs = t.first +// thisLeadingTermC = t.second +// } +// +// return Polynomial(thisCoefficients, toCheckInput = false) +//} +// +//infix fun > Polynomial.divrem(other: Polynomial): Polynomial.Companion.DividingResult { +// if (other.isZero()) throw ArithmeticException("/ by zero") +// if (isZero()) return Polynomial.Companion.DividingResult(this, this) +// +// fun Map, T>.leadingTerm() = +// this +// .asSequence() +// .map { Pair(it.key, it.value) } +// .reduce { (accDegs, accC), (listDegs, listC) -> +// for (i in 0..accDegs.lastIndex) { +// if (accDegs[i] > listDegs.getOrElse(i) { 0 }) return@reduce accDegs to accC +// if (accDegs[i] < listDegs.getOrElse(i) { 0 }) return@reduce listDegs to listC +// } +// if (accDegs.size < listDegs.size) listDegs to listC else accDegs to accC +// } +// +// var thisCoefficients = coefficients.toMutableMap() +// val otherCoefficients = other.coefficients +// val quotientCoefficients = HashMap, T>() +// +// var (thisLeadingTermDegs, thisLeadingTermC) = thisCoefficients.leadingTerm() +// val (otherLeadingTermDegs, otherLeadingTermC) = otherCoefficients.leadingTerm() +// +// while ( +// thisLeadingTermDegs.size >= otherLeadingTermDegs.size && +// (0..otherLeadingTermDegs.lastIndex).all { thisLeadingTermDegs[it] >= otherLeadingTermDegs[it] } +// ) { +// val multiplierDegs = +// thisLeadingTermDegs +// .mapIndexed { index, deg -> deg - otherLeadingTermDegs.getOrElse(index) { 0 } } +// .cleanUp() +// val multiplierC = thisLeadingTermC / otherLeadingTermC +// +// quotientCoefficients[multiplierDegs] = multiplierC +// +// for ((degs, t) in otherCoefficients) { +// val productDegs = +// (0..max(degs.lastIndex, multiplierDegs.lastIndex)) +// .map { degs.getOrElse(it) { 0 } + multiplierDegs.getOrElse(it) { 0 } } +// .cleanUp() +// val productC = t * multiplierC +// thisCoefficients[productDegs] = +// if (productDegs in thisCoefficients) thisCoefficients[productDegs]!! - productC else -productC +// } +// +// thisCoefficients = thisCoefficients.filterValues { it.isNotZero() }.toMutableMap() +// +// if (thisCoefficients.isEmpty()) +// return Polynomial.Companion.DividingResult( +// Polynomial(quotientCoefficients, toCheckInput = false), +// Polynomial(thisCoefficients, toCheckInput = false) +// ) +// +// val t = thisCoefficients.leadingTerm() +// thisLeadingTermDegs = t.first +// thisLeadingTermC = t.second +// } +// +// return Polynomial.Companion.DividingResult( +// Polynomial(quotientCoefficients, toCheckInput = false), +// Polynomial(thisCoefficients, toCheckInput = false) +// ) +//} +// +//// endregion + +// endregion + +// region Polynomial substitution and functional representation + +// TODO: May be apply Horner's method too? +/** + * Evaluates the value of the given double polynomial for given double argument. + */ +public fun NumberedPolynomial.substitute(args: Map): NumberedPolynomial = Double.algebra { + val acc = LinkedHashMap, Double>(coefficients.size) + for ((degs, c) in coefficients) { + val newDegs = degs.mapIndexed { index, deg -> if (index !in args) deg else 0u }.cleanUp() + val newC = args.entries.fold(c) { product, (variable, substitutor) -> + val deg = degs.getOrElse(variable) { 0u } + if (deg == 0u) product else product * substitutor.pow(deg.toInt()) + } + if (newDegs !in acc) acc[newDegs] = c + else acc[newDegs] = acc[newDegs]!! + c + } + return NumberedPolynomial(acc) +} + +/** + * Evaluates the value of the given polynomial for given argument. + * + * It is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method). + */ +public fun NumberedPolynomial.substitute(ring: Ring, args: Map): NumberedPolynomial = ring { + val acc = LinkedHashMap, C>(coefficients.size) + for ((degs, c) in coefficients) { + val newDegs = degs.mapIndexed { index, deg -> if (index !in args) deg else 0u }.cleanUp() + val newC = args.entries.fold(c) { product, (variable, substitutor) -> + val deg = degs.getOrElse(variable) { 0u } + if (deg == 0u) product else product * power(substitutor, deg) + } + if (newDegs !in acc) acc[newDegs] = c + else acc[newDegs] = acc[newDegs]!! + c + } + return NumberedPolynomial(acc) +} + +// TODO: (Waiting for hero) Replace with optimisation: the [result] may be unboxed, and all operations may be performed +// as soon as possible on it +@JvmName("substitutePolynomial") +public fun NumberedPolynomial.substitute(ring: Ring, args: Map>) : NumberedPolynomial = TODO() /*ring.numberedPolynomial { + val acc = LinkedHashMap, NumberedPolynomial>(coefficients.size) + for ((degs, c) in coefficients) { + val newDegs = degs.mapIndexed { index, deg -> if (index !in args) deg else 0u }.cleanUp() + val newC = args.entries.fold(c.asNumberedPolynomial()) { product, (variable, substitutor) -> + val deg = degs.getOrElse(variable) { 0u } + if (deg == 0u) product else product * power(substitutor, deg) + } + if (newDegs !in acc) acc[newDegs] = c.asNumberedPolynomial() + else acc[newDegs] = acc[newDegs]!! + c + } +}*/ + +/** + * Represent the polynomial as a regular context-less function. + */ +public fun > NumberedPolynomial.asFunction(ring: A): (Map) -> NumberedPolynomial = { substitute(ring, it) } + +/** + * Represent the polynomial as a regular context-less function. + */ +public fun > NumberedPolynomial.asPolynomialFunctionOver(ring: A): (Map>) -> NumberedPolynomial = { substitute(ring, it) } + +// endregion + +// region Algebraic derivative and antiderivative + +/** + * Returns algebraic derivative of received polynomial. + */ +@UnstableKMathAPI +public fun NumberedPolynomial.derivativeBy( + algebra: A, + variable: Int, +): Polynomial where A : Ring, A : NumericAlgebra = algebra { + TODO() +} + +/** + * Returns algebraic derivative of received polynomial. + */ +@UnstableKMathAPI +public fun NumberedPolynomial.derivativeBy( + algebra: A, + variables: IntArray, +): Polynomial where A : Ring, A : NumericAlgebra = algebra { + TODO() +} + +/** + * Returns algebraic derivative of received polynomial. + */ +@UnstableKMathAPI +public fun NumberedPolynomial.derivativeBy( + algebra: A, + variables: Collection, +): Polynomial where A : Ring, A : NumericAlgebra = derivativeBy(algebra, variables.toIntArray()) + +/** + * Create a polynomial witch represents indefinite integral version of this polynomial + */ +@UnstableKMathAPI +public fun NumberedPolynomial.antiderivativeBy( + algebra: A, + variable: Int, +): Polynomial where A : Field, A : NumericAlgebra = algebra { + TODO() +} + +/** + * Create a polynomial witch represents indefinite integral version of this polynomial + */ +@UnstableKMathAPI +public fun NumberedPolynomial.antiderivativeBy( + algebra: A, + variables: IntArray, +): Polynomial where A : Field, A : NumericAlgebra = algebra { + TODO() +} + +/** + * Create a polynomial witch represents indefinite integral version of this polynomial + */ +@UnstableKMathAPI +public fun NumberedPolynomial.antiderivativeBy( + algebra: A, + variables: Collection, +): Polynomial where A : Field, A : NumericAlgebra = antiderivativeBy(algebra, variables.toIntArray()) + +// endregion \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt index 1a3eb7874..4d99b3a45 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialUtil.kt @@ -121,7 +121,7 @@ public fun Polynomial.antiderivative( ): Polynomial where A : Field, A : NumericAlgebra = algebra { val integratedCoefficients = buildList(coefficients.size + 1) { add(zero) - coefficients.forEachIndexed{ index, t -> add(t / (number(index) + one)) } + coefficients.forEachIndexed{ index, t -> add(t / number(index + 1)) } } Polynomial(integratedCoefficients) } @@ -136,4 +136,6 @@ public fun > Polynomial.integrate( ): C = algebra { val integral = antiderivative(algebra) integral.substitute(algebra, range.endInclusive) - integral.substitute(algebra, range.start) -} \ No newline at end of file +} + +// endregion \ No newline at end of file