diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/bufferOperation.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/bufferOperation.kt index 31b0c2841..762f08be1 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/bufferOperation.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/bufferOperation.kt @@ -91,6 +91,15 @@ public inline fun Buffer.fold(initial: R, operation: (acc: R, T) return accumulator } +/** + * Fold given buffer according to indexed [operation] + */ +public inline fun Buffer.foldIndexed(initial: R, operation: (index: Int, acc: R, T) -> R): R { + var accumulator = initial + for (index in this.indices) accumulator = operation(index, accumulator, get(index)) + return accumulator +} + /** * Zip two buffers using given [transform]. */ diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListPolynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListPolynomial.kt index fce179fc8..42e3f7301 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListPolynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListPolynomial.kt @@ -48,8 +48,8 @@ public data class ListPolynomial( } /** - * Arithmetic context for univariate polynomials with coefficients stored as a [List] constructed with the given [ring] - * of constants. + * Arithmetic context for univariate polynomials with coefficients stored as a [List] constructed with the provided + * [ring] of constants. * * @param C the type of constants. Polynomials have them a coefficients in their terms. * @param A type of provided underlying ring of constants. It's [Ring] of [C]. @@ -313,6 +313,10 @@ public open class ListPolynomialSpace>( } ) } + /** + * Raises [arg] to the integer power [exponent]. + */ // TODO: To optimize boxing + override fun power(arg: ListPolynomial, exponent: UInt): ListPolynomial = super.power(arg, exponent) /** * Instance of zero polynomial (zero of the polynomial ring). @@ -332,42 +336,42 @@ public open class ListPolynomialSpace>( // TODO: When context receivers will be ready move all of this substitutions and invocations to utilities with // [ListPolynomialSpace] as a context receiver /** - * Evaluates value of [this] polynomial on provided argument. + * Evaluates value of [this] polynomial on provided [argument]. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.substitute(argument: C): C = this.substitute(ring, argument) + public inline fun ListPolynomial.substitute(argument: C): C = substitute(ring, argument) /** * Substitutes provided polynomial [argument] into [this] polynomial. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.substitute(argument: ListPolynomial): ListPolynomial = this.substitute(ring, argument) + public inline fun ListPolynomial.substitute(argument: ListPolynomial): ListPolynomial = substitute(ring, argument) /** * Represent [this] polynomial as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.asFunction(): (C) -> C = { this.substitute(ring, it) } + public inline fun ListPolynomial.asFunction(): (C) -> C = asFunctionOver(ring) /** * Represent [this] polynomial as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.asFunctionOfConstant(): (C) -> C = { this.substitute(ring, it) } + public inline fun ListPolynomial.asFunctionOfConstant(): (C) -> C = asFunctionOfConstantOver(ring) /** * Represent [this] polynomial as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.asFunctionOfPolynomial(): (ListPolynomial) -> ListPolynomial = { this.substitute(ring, it) } + public inline fun ListPolynomial.asFunctionOfPolynomial(): (ListPolynomial) -> ListPolynomial = asFunctionOfPolynomialOver(ring) /** * Evaluates value of [this] polynomial on provided [argument]. */ @Suppress("NOTHING_TO_INLINE") - public inline operator fun ListPolynomial.invoke(argument: C): C = this.substitute(ring, argument) + public inline operator fun ListPolynomial.invoke(argument: C): C = substitute(ring, argument) /** * Evaluates value of [this] polynomial on provided [argument]. */ @Suppress("NOTHING_TO_INLINE") - public inline operator fun ListPolynomial.invoke(argument: ListPolynomial): ListPolynomial = this.substitute(ring, argument) + public inline operator fun ListPolynomial.invoke(argument: ListPolynomial): ListPolynomial = substitute(ring, argument) } /** diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListRationalFunction.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListRationalFunction.kt index f3e352bcd..e23baa548 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListRationalFunction.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/ListRationalFunction.kt @@ -9,7 +9,7 @@ import space.kscience.kmath.operations.Ring /** - * Represents rational function that stores its numerator and denominator as [ListPolynomial]s. + * Represents univariate rational function that stores its numerator and denominator as [ListPolynomial]s. */ public data class ListRationalFunction( public override val numerator: ListPolynomial, @@ -56,82 +56,82 @@ public class ListRationalFunctionSpace> ( * Evaluates value of [this] polynomial on provided argument. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.substitute(argument: C): C = this.substitute(ring, argument) + public inline fun ListPolynomial.substitute(argument: C): C = substitute(ring, argument) /** * Substitutes provided polynomial [argument] into [this] polynomial. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.substitute(argument: ListPolynomial): ListPolynomial = this.substitute(ring, argument) + public inline fun ListPolynomial.substitute(argument: ListPolynomial): ListPolynomial = substitute(ring, argument) /** * Substitutes provided rational function [argument] into [this] polynomial. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.substitute(argument: ListRationalFunction): ListRationalFunction = this.substitute(ring, argument) + public inline fun ListPolynomial.substitute(argument: ListRationalFunction): ListRationalFunction = substitute(ring, argument) /** * Substitutes provided polynomial [argument] into [this] rational function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListRationalFunction.substitute(argument: ListPolynomial): ListRationalFunction = this.substitute(ring, argument) + public inline fun ListRationalFunction.substitute(argument: ListPolynomial): ListRationalFunction = substitute(ring, argument) /** * Substitutes provided rational function [argument] into [this] rational function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListRationalFunction.substitute(argument: ListRationalFunction): ListRationalFunction = this.substitute(ring, argument) + public inline fun ListRationalFunction.substitute(argument: ListRationalFunction): ListRationalFunction = substitute(ring, argument) /** * Represent [this] polynomial as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.asFunction(): (C) -> C = { this.substitute(ring, it) } + public inline fun ListPolynomial.asFunction(): (C) -> C = { substitute(ring, it) } /** * Represent [this] polynomial as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.asFunctionOfConstant(): (C) -> C = { this.substitute(ring, it) } + public inline fun ListPolynomial.asFunctionOfConstant(): (C) -> C = { substitute(ring, it) } /** * Represent [this] polynomial as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.asFunctionOfPolynomial(): (ListPolynomial) -> ListPolynomial = { this.substitute(ring, it) } + public inline fun ListPolynomial.asFunctionOfPolynomial(): (ListPolynomial) -> ListPolynomial = { substitute(ring, it) } /** * Represent [this] polynomial as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListPolynomial.asFunctionOfRationalFunction(): (ListRationalFunction) -> ListRationalFunction = { this.substitute(ring, it) } + public inline fun ListPolynomial.asFunctionOfRationalFunction(): (ListRationalFunction) -> ListRationalFunction = { substitute(ring, it) } /** * Represent [this] rational function as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListRationalFunction.asFunctionOfPolynomial(): (ListPolynomial) -> ListRationalFunction = { this.substitute(ring, it) } + public inline fun ListRationalFunction.asFunctionOfPolynomial(): (ListPolynomial) -> ListRationalFunction = { substitute(ring, it) } /** * Represent [this] rational function as a regular context-less function. */ @Suppress("NOTHING_TO_INLINE") - public inline fun ListRationalFunction.asFunctionOfRationalFunction(): (ListRationalFunction) -> ListRationalFunction = { this.substitute(ring, it) } + public inline fun ListRationalFunction.asFunctionOfRationalFunction(): (ListRationalFunction) -> ListRationalFunction = { substitute(ring, it) } /** * Evaluates value of [this] polynomial on provided argument. */ @Suppress("NOTHING_TO_INLINE") - public inline operator fun ListPolynomial.invoke(argument: C): C = this.substitute(ring, argument) + public inline operator fun ListPolynomial.invoke(argument: C): C = substitute(ring, argument) /** * Evaluates value of [this] polynomial on provided argument. */ @Suppress("NOTHING_TO_INLINE") - public inline operator fun ListPolynomial.invoke(argument: ListPolynomial): ListPolynomial = this.substitute(ring, argument) + public inline operator fun ListPolynomial.invoke(argument: ListPolynomial): ListPolynomial = substitute(ring, argument) /** * Evaluates value of [this] polynomial on provided argument. */ @Suppress("NOTHING_TO_INLINE") - public inline operator fun ListPolynomial.invoke(argument: ListRationalFunction): ListRationalFunction = this.substitute(ring, argument) + public inline operator fun ListPolynomial.invoke(argument: ListRationalFunction): ListRationalFunction = substitute(ring, argument) /** * Evaluates value of [this] rational function on provided argument. */ @Suppress("NOTHING_TO_INLINE") - public inline operator fun ListRationalFunction.invoke(argument: ListPolynomial): ListRationalFunction = this.substitute(ring, argument) + public inline operator fun ListRationalFunction.invoke(argument: ListPolynomial): ListRationalFunction = substitute(ring, argument) /** * Evaluates value of [this] rational function on provided argument. */ @Suppress("NOTHING_TO_INLINE") - public inline operator fun ListRationalFunction.invoke(argument: ListRationalFunction): ListRationalFunction = this.substitute(ring, argument) + public inline operator fun ListRationalFunction.invoke(argument: ListRationalFunction): ListRationalFunction = substitute(ring, argument) } \ 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..9304e66da --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt @@ -0,0 +1,429 @@ +/* + * 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.Ring +import space.kscience.kmath.structures.Buffer +import kotlin.jvm.JvmName +import kotlin.math.max + + +/** + * Represents univariate polynomial that stores its coefficients in a [Map] and terms' signatures in a [List]. + * + * @param C the type of constants. + */ +public data class NumberedPolynomial +@PublishedApi +internal constructor( + /** + * Map that contains coefficients of the polynomial. Every monomial `a x_1^{d_1} ... x_n^{d_n}` is stored as pair + * "key-value" in the map, where the value is the coefficients `a` and the key is a list that associates index of + * every variable in the monomial with multiplicity of the variable occurring in the monomial. For example + * coefficients of a polynomial `5 x_1^2 x_3^3 - 6 x_2` can be represented as + * ``` + * mapOf( + * listOf(2, 0, 3) to 5, // 5 x_1^2 x_3^3 + + * listOf(0, 1) to (-6), // (-6) x_2^1 + * ) + * ``` + * and also as + * ``` + * mapOf( + * listOf(2, 0, 3) to 5, // 5 x_1^2 x_3^3 + + * listOf(0, 1) to (-6), // (-6) x_2^1 + * listOf(0, 1, 1) to 0, // 0 x_2^1 x_3^1 + * ) + * ``` + * It is not prohibited to put extra zero monomials into the map (as for `0 x_2 x_3` in the example). But the + * bigger the coefficients map the worse performance of arithmetical operations performed on it. Thus, it is + * recommended not to put (or even to remove) extra (or useless) monomials in the coefficients map. + */ + public val coefficients: Map, C> +) : Polynomial { + override fun toString(): String = "NumberedPolynomial$coefficients" +} + +/** + * Arithmetic context for multivariate polynomials with coefficients stored as a [Map] and terms' signatures stored as a + * [List] constructed with the provided [ring] of constants. + * + * @param C the type of constants. Polynomials have them a coefficients in their terms. + * @param A type of provided underlying ring of constants. It's [Ring] of [C]. + * @param ring underlying ring of constants of type [A]. + */ +public class NumberedPolynomialSpace>( + public override val ring: A, +) : PolynomialSpaceOverRing, A> { + /** + * Returns sum of the polynomial and the integer represented as a polynomial. + * + * The operation is equivalent to adding [other] copies of unit polynomial to [this]. + */ + public override operator fun NumberedPolynomial.plus(other: Int): NumberedPolynomial = + if (other == 0) this + else + NumberedPolynomialAsIs( + coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + this[degs] = getOrElse(degs) { constantZero } + other + } + ) + /** + * Returns difference between the polynomial and the integer represented as a polynomial. + * + * The operation is equivalent to subtraction [other] copies of unit polynomial from [this]. + */ + public override operator fun NumberedPolynomial.minus(other: Int): NumberedPolynomial = + if (other == 0) this + else + NumberedPolynomialAsIs( + coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + this[degs] = getOrElse(degs) { constantZero } - other + } + ) + /** + * Returns product of the polynomial and the integer represented as a polynomial. + * + * The operation is equivalent to sum of [other] copies of [this]. + */ + public override operator fun NumberedPolynomial.times(other: Int): NumberedPolynomial = + if (other == 0) zero + else NumberedPolynomialAsIs( + coefficients + .toMutableMap() + .apply { + for (degs in keys) this[degs] = this[degs]!! * other + } + ) + + /** + * Returns sum of the integer represented as a polynomial and the polynomial. + * + * The operation is equivalent to adding [this] copies of unit polynomial to [other]. + */ + public override operator fun Int.plus(other: NumberedPolynomial): NumberedPolynomial = + if (this == 0) other + else + NumberedPolynomialAsIs( + other.coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + this[degs] = this@plus + getOrElse(degs) { constantZero } + } + ) + /** + * Returns difference between the integer represented as a polynomial and the polynomial. + * + * The operation is equivalent to subtraction [this] copies of unit polynomial from [other]. + */ + public override operator fun Int.minus(other: NumberedPolynomial): NumberedPolynomial = + if (this == 0) other + else + NumberedPolynomialAsIs( + other.coefficients + .toMutableMap() + .apply { + val degs = emptyList() + + this[degs] = this@minus - getOrElse(degs) { constantZero } + } + ) + /** + * Returns product of the integer represented as a polynomial and the polynomial. + * + * The operation is equivalent to sum of [this] copies of [other]. + */ + public override operator fun Int.times(other: NumberedPolynomial): NumberedPolynomial = + if (this == 0) zero + else NumberedPolynomialAsIs( + other.coefficients + .toMutableMap() + .apply { + for (degs in keys) this[degs] = this@times * this[degs]!! + } + ) + + /** + * Converts the integer [value] to polynomial. + */ + public override fun number(value: Int): NumberedPolynomial = number(constantNumber(value)) + + /** + * Returns sum of the constant represented as a polynomial and the polynomial. + */ + override operator fun C.plus(other: NumberedPolynomial): NumberedPolynomial = + with(other.coefficients) { + if (isEmpty()) NumberedPolynomialAsIs(mapOf(emptyList() to this@plus)) + else NumberedPolynomialAsIs( + toMutableMap() + .apply { + val degs = emptyList() + + this[degs] = this@plus + getOrElse(degs) { constantZero } + } + ) + } + /** + * Returns difference between the constant represented as a polynomial and the polynomial. + */ + override operator fun C.minus(other: NumberedPolynomial): NumberedPolynomial = + with(other.coefficients) { + if (isEmpty()) NumberedPolynomialAsIs(mapOf(emptyList() to this@minus)) + else NumberedPolynomialAsIs( + toMutableMap() + .apply { + forEach { (degs, c) -> if(degs.isNotEmpty()) this[degs] = -c } + + val degs = emptyList() + + this[degs] = this@minus - getOrElse(degs) { constantZero } + } + ) + } + /** + * Returns product of the constant represented as a polynomial and the polynomial. + */ + override operator fun C.times(other: NumberedPolynomial): NumberedPolynomial = + NumberedPolynomialAsIs( + other.coefficients + .toMutableMap() + .apply { + for (degs in keys) this[degs] = this@times * this[degs]!! + } + ) + + /** + * Returns sum of the constant represented as a polynomial and the polynomial. + */ + override operator fun NumberedPolynomial.plus(other: C): NumberedPolynomial = + with(coefficients) { + if (isEmpty()) NumberedPolynomialAsIs(mapOf(emptyList() to other)) + else NumberedPolynomialAsIs( + toMutableMap() + .apply { + val degs = emptyList() + + this[degs] = getOrElse(degs) { constantZero } + other + } + ) + } + /** + * Returns difference between the constant represented as a polynomial and the polynomial. + */ + override operator fun NumberedPolynomial.minus(other: C): NumberedPolynomial = + with(coefficients) { + if (isEmpty()) NumberedPolynomialAsIs(mapOf(emptyList() to other)) + else NumberedPolynomialAsIs( + toMutableMap() + .apply { + val degs = emptyList() + + this[degs] = getOrElse(degs) { constantZero } - other + } + ) + } + /** + * Returns product of the constant represented as a polynomial and the polynomial. + */ + override operator fun NumberedPolynomial.times(other: C): NumberedPolynomial = + NumberedPolynomialAsIs( + coefficients + .toMutableMap() + .apply { + for (degs in keys) this[degs] = this[degs]!! * other + } + ) + + /** + * Converts the constant [value] to polynomial. + */ + public override fun number(value: C): NumberedPolynomial = + NumberedPolynomialAsIs(mapOf(emptyList() to value)) + + /** + * Returns negation of the polynomial. + */ + override fun NumberedPolynomial.unaryMinus(): NumberedPolynomial = + NumberedPolynomialAsIs( + coefficients.mapValues { -it.value } + ) + /** + * Returns sum of the polynomials. + */ + override operator fun NumberedPolynomial.plus(other: NumberedPolynomial): NumberedPolynomial = + NumberedPolynomialAsIs( + buildMap(coefficients.size + other.coefficients.size) { + other.coefficients.mapValuesTo(this) { it.value } + 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 = + NumberedPolynomialAsIs( + buildMap(coefficients.size + other.coefficients.size) { + other.coefficients.mapValuesTo(this) { it.value } + 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 = + NumberedPolynomialAsIs( + buildMap(coefficients.size * other.coefficients.size) { + 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 + } + } + ) + /** + * Raises [arg] to the integer power [exponent]. + */ // TODO: To optimize boxing + override fun power(arg: NumberedPolynomial, exponent: UInt): NumberedPolynomial = super.power(arg, exponent) + + /** + * Instance of zero polynomial (zero of the polynomial ring). + */ + override val zero: NumberedPolynomial = NumberedPolynomialAsIs(emptyMap()) + /** + * Instance of unit polynomial (unit of the polynomial ring). + */ + override val one: NumberedPolynomial by lazy { + NumberedPolynomialAsIs( + mapOf( + emptyList() to constantOne // 1 * x_1^0 * x_2^0 * ... + ) + ) + } + + /** + * Maximal index (ID) of variable occurring in the polynomial with positive power. If there is no such variable, + * the result is `-1`. + */ + public val NumberedPolynomial.lastVariable: Int + get() = coefficients.entries.maxOfOrNull { (degs, _) -> degs.lastIndex } ?: -1 + /** + * 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, _) -> 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 last index of the list is [lastVariable]. + */ + public val NumberedPolynomial.degrees: List + get() = + MutableList(lastVariable + 1) { 0u }.apply { + coefficients.entries.forEach { (degs, _) -> + degs.forEachIndexed { index, deg -> + this[index] = max(this[index], deg) + } + } + } + /** + * Counts degree of the polynomial by the specified [variable]. + */ + public fun NumberedPolynomial.degreeBy(variable: Int): UInt = + coefficients.entries.maxOfOrNull { (degs, _) -> degs.getOrElse(variable) { 0u } } ?: 0u + /** + * Counts degree of the polynomial by the specified [variables]. + */ + public fun NumberedPolynomial.degreeBy(variables: Collection): UInt = + coefficients.entries.maxOfOrNull { (degs, _) -> + degs.withIndex().filter { (index, _) -> index in variables }.sumOf { it.value } + } ?: 0u + /** + * Count of variables occurring in the polynomial with positive power. If there is no such variable, + * the result is `0`. + */ + public val NumberedPolynomial.countOfVariables: Int + get() = + MutableList(lastVariable + 1) { false }.apply { + coefficients.entries.forEach { (degs, _) -> + degs.forEachIndexed { index, deg -> + if (deg != 0u) this[index] = true + } + } + }.count { it } + + // TODO: When context receivers will be ready move all of this substitutions and invocations to utilities with + // [ListPolynomialSpace] as a context receiver + /** + * Substitutes provided arguments [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(arguments: Map): NumberedPolynomial = substitute(ring, arguments) + /** + * Substitutes provided arguments [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + @JvmName("substitutePolynomial") + public inline fun NumberedPolynomial.substitute(arguments: Map>) : NumberedPolynomial = substitute(ring, arguments) + /** + * Substitutes provided arguments [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(arguments: Buffer): NumberedPolynomial = substitute(ring, arguments) + /** + * Substitutes provided arguments [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + @JvmName("substitutePolynomial") + public inline fun NumberedPolynomial.substitute(arguments: Buffer>) : NumberedPolynomial = substitute(ring, arguments) + /** + * Substitutes provided arguments [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substituteFully(arguments: Buffer): C = this.substituteFully(ring, arguments) + + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunction(): (Buffer) -> C = asFunctionOver(ring) + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunctionOfConstant(): (Buffer) -> C = asFunctionOfConstantOver(ring) + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunctionOfPolynomial(): (Buffer>) -> NumberedPolynomial = asFunctionOfPolynomialOver(ring) + + /** + * Evaluates value of [this] polynomial on provided [arguments]. + */ + @Suppress("NOTHING_TO_INLINE") + public inline operator fun NumberedPolynomial.invoke(arguments: Buffer): C = substituteFully(ring, arguments) + /** + * Substitutes provided [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + @JvmName("invokePolynomial") + public inline operator fun NumberedPolynomial.invoke(arguments: Buffer>): NumberedPolynomial = substitute(ring, arguments) +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedRationalFunction.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedRationalFunction.kt new file mode 100644 index 000000000..a2986879d --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedRationalFunction.kt @@ -0,0 +1,239 @@ +/* + * 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.Ring +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.Buffer +import kotlin.jvm.JvmName +import kotlin.math.max + + +/** + * Represents multivariate rational function that stores its numerator and denominator as [NumberedPolynomial]s. + */ +public class NumberedRationalFunction internal constructor( + public override val numerator: NumberedPolynomial, + public override val denominator: NumberedPolynomial +) : RationalFunction> { + override fun toString(): String = "NumberedRationalFunction${numerator.coefficients}/${denominator.coefficients}" +} + +/** + * Arithmetic context for univariate rational functions with numerator and denominator represented as [NumberedPolynomial]s. + * + * @param C the type of constants. Polynomials have them a coefficients in their terms. + * @param A type of provided underlying ring of constants. It's [Ring] of [C]. + * @param ring underlying ring of constants of type [A]. + */ +public class NumberedRationalFunctionSpace> ( + public val ring: A, +) : + RationalFunctionalSpaceOverPolynomialSpace< + C, + NumberedPolynomial, + NumberedRationalFunction, + NumberedPolynomialSpace, + >, + PolynomialSpaceOfFractions< + C, + NumberedPolynomial, + NumberedRationalFunction, + >() { + + /** + * Underlying polynomial ring. Its polynomial operations are inherited by local polynomial operations. + */ + public override val polynomialRing : NumberedPolynomialSpace = NumberedPolynomialSpace(ring) + /** + * Constructor of rational functions (of type [NumberedRationalFunction]) from numerator and denominator (of type [NumberedPolynomial]). + */ + protected override fun constructRationalFunction( + numerator: NumberedPolynomial, + denominator: NumberedPolynomial + ): NumberedRationalFunction = + NumberedRationalFunction(numerator, denominator) + + /** + * Maximal index (ID) of variable occurring in the polynomial with positive power. If there is no such variable, + * the result is `-1`. + */ + public val NumberedPolynomial.lastVariable: Int get() = polynomialRing { lastVariable } + /** + * 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 last index of the list is [lastVariable]. + */ + public val NumberedPolynomial.degrees: List get() = polynomialRing { degrees } + /** + * Counts degree of the polynomial by the specified [variable]. + */ + public fun NumberedPolynomial.degreeBy(variable: Int): UInt = polynomialRing { degreeBy(variable) } + /** + * Counts degree of the polynomial by the specified [variables]. + */ + public fun NumberedPolynomial.degreeBy(variables: Collection): UInt = polynomialRing { degreeBy(variables) } + /** + * Count of variables occurring in the polynomial with positive power. If there is no such variable, + * the result is `0`. + */ + public val NumberedPolynomial.countOfVariables: Int get() = polynomialRing { countOfVariables } + + /** + * Count of all variables that appear in the polynomial in positive exponents. + */ + public val NumberedRationalFunction.lastVariable: Int + get() = polynomialRing { max(numerator.lastVariable, denominator.lastVariable) } + /** + * Count of variables occurring in the rational function with positive power. If there is no such variable, + * the result is `0`. + */ + public val NumberedRationalFunction.countOfVariables: Int + get() = + MutableList(lastVariable + 1) { false }.apply { + numerator.coefficients.entries.forEach { (degs, _) -> + degs.forEachIndexed { index, deg -> + if (deg != 0u) this[index] = true + } + } + denominator.coefficients.entries.forEach { (degs, _) -> + degs.forEachIndexed { index, deg -> + if (deg != 0u) this[index] = true + } + } + }.count { it } + + // TODO: When context receivers will be ready move all of this substitutions and invocations to utilities with + // [ListPolynomialSpace] as a context receiver + /** + * Substitutes provided constant [argument] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(argument: Map): NumberedPolynomial = substitute(ring, argument) + /** + * Substitutes provided polynomial [argument] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(argument: Map>): NumberedPolynomial = substitute(ring, argument) + /** + * Substitutes provided rational function [argument] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(argument: Map>): NumberedRationalFunction = substitute(ring, argument) + /** + * Substitutes provided constant [argument] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.substitute(argument: Map): NumberedRationalFunction = substitute(ring, argument) + /** + * Substitutes provided polynomial [argument] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.substitute(argument: Map>): NumberedRationalFunction = substitute(ring, argument) + /** + * Substitutes provided rational function [argument] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.substitute(argument: Map>): NumberedRationalFunction = substitute(ring, argument) + /** + * Substitutes provided constant [argument] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(argument: Buffer): NumberedPolynomial = substitute(ring, argument) + /** + * Substitutes provided polynomial [argument] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(argument: Buffer>): NumberedPolynomial = substitute(ring, argument) + /** + * Substitutes provided rational function [argument] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substitute(argument: Buffer>): NumberedRationalFunction = substitute(ring, argument) + /** + * Substitutes provided constant [argument] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.substitute(argument: Buffer): NumberedRationalFunction = substitute(ring, argument) + /** + * Substitutes provided polynomial [arguments] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.substitute(arguments: Buffer>): NumberedRationalFunction = substitute(ring, arguments) + /** + * Substitutes provided rational function [arguments] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.substitute(arguments: Buffer>): NumberedRationalFunction = substitute(ring, arguments) + /** + * Substitutes provided constant [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.substituteFully(arguments: Buffer): C = substituteFully(ring, arguments) + + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunction(): (Buffer) -> C = asFunctionOver(ring) + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunctionOfConstant(): (Buffer) -> C = asFunctionOfConstantOver(ring) + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunctionOfPolynomial(): (Buffer>) -> NumberedPolynomial = asFunctionOfPolynomialOver(ring) + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedPolynomial.asFunctionOfRationalFunction(): (Buffer>) -> NumberedRationalFunction = asFunctionOfRationalFunctionOver(ring) + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.asFunctionOfPolynomial(): (Buffer>) -> NumberedRationalFunction = asFunctionOfPolynomialOver(ring) + /** + * Represent [this] polynomial as a regular context-less function. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun NumberedRationalFunction.asFunctionOfRationalFunction(): (Buffer>) -> NumberedRationalFunction = asFunctionOfRationalFunctionOver(ring) + + /** + * Evaluates value of [this] polynomial on provided [arguments]. + */ + @Suppress("NOTHING_TO_INLINE") + public inline operator fun NumberedPolynomial.invoke(arguments: Buffer): C = substituteFully(ring, arguments) + /** + * Substitutes provided [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + @JvmName("invokePolynomial") + public inline operator fun NumberedPolynomial.invoke(arguments: Buffer>): NumberedPolynomial = substitute(ring, arguments) + /** + * Substitutes provided [arguments] into [this] polynomial. + */ + @Suppress("NOTHING_TO_INLINE") + @JvmName("invokePolynomial") + public inline operator fun NumberedPolynomial.invoke(arguments: Buffer>): NumberedRationalFunction = substitute(ring, arguments) + /** + * Substitutes provided [arguments] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + @JvmName("invokePolynomial") + public inline operator fun NumberedRationalFunction.invoke(arguments: Buffer>): NumberedRationalFunction = substitute(ring, arguments) + /** + * Substitutes provided [arguments] into [this] rational function. + */ + @Suppress("NOTHING_TO_INLINE") + @JvmName("invokePolynomial") + public inline operator fun NumberedRationalFunction.invoke(arguments: Buffer>): NumberedRationalFunction = substitute(ring, arguments) +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/RationalFunction.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/RationalFunction.kt index 01911f980..338ae9935 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/RationalFunction.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/RationalFunction.kt @@ -1076,6 +1076,14 @@ public abstract class PolynomialSpaceOfFractions< numerator * other.denominator, denominator * other.numerator ) + /** + * Raises [arg] to the integer power [exponent]. + */ + public override fun power(arg: R, exponent: UInt): R = + constructRationalFunction( + power(arg.numerator, exponent), + power(arg.denominator, exponent), + ) /** * Instance of zero rational function (zero of the rational functions ring). diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/listUtil.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/listUtil.kt index 127dd8c7a..649fc48bd 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/listUtil.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/listUtil.kt @@ -147,12 +147,17 @@ public fun > ListPolynomial.asFunctionOver(ring: A): (C) -> C /** * Represent [this] polynomial as a regular context-less function. */ -public fun > ListPolynomial.asPolynomialFunctionOver(ring: A): (ListPolynomial) -> ListPolynomial = { substitute(ring, it) } +public fun > ListPolynomial.asFunctionOfConstantOver(ring: A): (C) -> C = { substitute(ring, it) } /** * Represent [this] polynomial as a regular context-less function. */ -public fun > ListPolynomial.asFunctionOfRationalFunctionOver(ring: A): (ListPolynomial) -> ListPolynomial = { substitute(ring, it) } +public fun > ListPolynomial.asFunctionOfPolynomialOver(ring: A): (ListPolynomial) -> ListPolynomial = { substitute(ring, it) } + +/** + * Represent [this] polynomial as a regular context-less function. + */ +public fun > ListPolynomial.asFunctionOfRationalFunctionOver(ring: A): (ListRationalFunction) -> ListRationalFunction = { substitute(ring, it) } /** * Represent [this] rational function as a regular context-less function. @@ -162,12 +167,17 @@ public fun > ListRationalFunction.asFunctionOver(ring: A): (C /** * Represent [this] rational function as a regular context-less function. */ -public fun > ListRationalFunction.asPolynomialFunctionOver(ring: A): (ListPolynomial) -> ListRationalFunction = { substitute(ring, it) } +public fun > ListRationalFunction.asFunctionOfConstantOver(ring: A): (C) -> C = { substitute(ring, it) } /** * Represent [this] rational function as a regular context-less function. */ -public fun > ListRationalFunction.asFunctionOfRationalFunctionOver(ring: A): (ListPolynomial) -> ListRationalFunction = { substitute(ring, it) } +public fun > ListRationalFunction.asFunctionOfPolynomialOver(ring: A): (ListPolynomial) -> ListRationalFunction = { substitute(ring, it) } + +/** + * Represent [this] rational function as a regular context-less function. + */ +public fun > ListRationalFunction.asFunctionOfRationalFunctionOver(ring: A): (ListRationalFunction) -> ListRationalFunction = { substitute(ring, it) } /** * Returns algebraic derivative of received polynomial. @@ -242,27 +252,4 @@ public fun > ListPolynomial.integrate( ): C = ring { val antiderivative = antiderivative(ring) antiderivative.substitute(ring, range.endInclusive) - antiderivative.substitute(ring, range.start) -} - -/** - * Returns algebraic derivative of received rational function. - */ -@UnstableKMathAPI -public fun ListRationalFunction.derivative( - ring: A, -): ListRationalFunction where A : Ring, A : NumericAlgebra = ring.listRationalFunctionSpace { - ListRationalFunction( - numerator.derivative(ring) * denominator - numerator * denominator.derivative(ring), - denominator * denominator - ) -} - -/** - * Returns algebraic derivative of received rational function of specified [order]. The [order] should be non-negative integer. - */ -@UnstableKMathAPI -public tailrec fun ListRationalFunction.nthDerivative( - ring: A, - order: Int, -): ListRationalFunction where A : Ring, A : NumericAlgebra = - if (order == 0) this else derivative(ring).nthDerivative(ring, order - 1) \ No newline at end of file +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedConstructors.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedConstructors.kt new file mode 100644 index 000000000..05fff3472 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedConstructors.kt @@ -0,0 +1,478 @@ +/* + * 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.Ring + + +/** + * 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) + +/** + * Constructs [NumberedPolynomial] with provided coefficients map [coefs]. The map is used as is. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +@PublishedApi +internal inline fun NumberedPolynomialAsIs(coefs: Map, C>) : NumberedPolynomial = NumberedPolynomial(coefs) + +/** + * Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature — term's coefficient". + * The collections will be transformed to map with [toMap] and then will be used as is. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +@PublishedApi +internal inline fun NumberedPolynomialAsIs(pairs: Collection, C>>) : NumberedPolynomial = NumberedPolynomial(pairs.toMap()) + +/** + * Constructs [NumberedPolynomial] with provided array of [pairs] of pairs "term's signature — term's coefficient". + * The array will be transformed to map with [toMap] and then will be used as is. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +@PublishedApi +internal inline fun NumberedPolynomialAsIs(vararg pairs: Pair, C>) : NumberedPolynomial = NumberedPolynomial(pairs.toMap()) + +/** + * Constructs [NumberedPolynomial] with provided coefficients map [coefs]. + * + * [coefs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [coefs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName") +public fun NumberedPolynomial(coefs: Map, C>, add: (C, C) -> C) : NumberedPolynomial { + val fixedCoefs = mutableMapOf, C>() + + for (entry in coefs) { + val key = entry.key.cleanUp() + val value = entry.value + fixedCoefs[key] = if (key in fixedCoefs) add(fixedCoefs[key]!!, value) else value + } + + return NumberedPolynomial(fixedCoefs) +} + +/** + * Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName") +public fun NumberedPolynomial(pairs: Collection, C>>, add: (C, C) -> C) : NumberedPolynomial { + val fixedCoefs = mutableMapOf, C>() + + for (entry in pairs) { + val key = entry.first.cleanUp() + val value = entry.second + fixedCoefs[key] = if (key in fixedCoefs) add(fixedCoefs[key]!!, value) else value + } + + return NumberedPolynomial(fixedCoefs) +} + +/** + * Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName") +public fun NumberedPolynomial(vararg pairs: Pair, C>, add: (C, C) -> C) : NumberedPolynomial { + val fixedCoefs = mutableMapOf, C>() + + for (entry in pairs) { + val key = entry.first.cleanUp() + val value = entry.second + fixedCoefs[key] = if (key in fixedCoefs) add(fixedCoefs[key]!!, value) else value + } + + return NumberedPolynomial(fixedCoefs) +} + +// Waiting for context receivers :( FIXME: Replace with context receivers when they will be available + +/** + * Constructs [NumberedPolynomial] with provided coefficients map [coefs]. + * + * [coefs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [coefs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > A.NumberedPolynomial(coefs: Map, C>) : NumberedPolynomial = NumberedPolynomial(coefs, ::add) +/** + * Constructs [NumberedPolynomial] with provided coefficients map [coefs]. + * + * [coefs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [coefs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > NumberedPolynomialSpace.NumberedPolynomial(coefs: Map, C>) : NumberedPolynomial = NumberedPolynomial(coefs) { left: C, right: C -> left + right } + +/** + * Constructs [NumberedPolynomial] with provided coefficients map [coefs]. + * + * [coefs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [coefs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > NumberedRationalFunctionSpace.NumberedPolynomial(coefs: Map, C>) : NumberedPolynomial = NumberedPolynomial(coefs) { left: C, right: C -> left + right } + +/** + * Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > A.NumberedPolynomial(pairs: Collection, C>>) : NumberedPolynomial = NumberedPolynomial(pairs, ::add) + +/** + * Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > NumberedPolynomialSpace.NumberedPolynomial(pairs: Collection, C>>) : NumberedPolynomial = NumberedPolynomial(pairs) { left: C, right: C -> left + right } +/** + * Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > NumberedRationalFunctionSpace.NumberedPolynomial(pairs: Collection, C>>) : NumberedPolynomial = NumberedPolynomial(pairs) { left: C, right: C -> left + right } + +/** + * Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > A.NumberedPolynomial(vararg pairs: Pair, C>) : NumberedPolynomial = NumberedPolynomial(*pairs) { left: C, right: C -> left + right } +/** + * Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > NumberedPolynomialSpace.NumberedPolynomial(vararg pairs: Pair, C>) : NumberedPolynomial = NumberedPolynomial(*pairs) { left: C, right: C -> left + right } +/** + * Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature — term's coefficient". + * + * [pairs] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [pairs] keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName", "NOTHING_TO_INLINE") +public inline fun > NumberedRationalFunctionSpace.NumberedPolynomial(vararg pairs: Pair, C>) : NumberedPolynomial = NumberedPolynomial(*pairs) { left: C, right: C -> left + right } + +/** + * Converts [this] constant to [NumberedPolynomial]. + */ +@Suppress("NOTHING_TO_INLINE") +public inline fun C.asNumberedPolynomial() : NumberedPolynomial = NumberedPolynomialAsIs(mapOf(emptyList() to this)) + +/** + * Marks DSL that allows to more simply create [NumberedPolynomial]s with good performance. + * + * For example, polynomial `5 x_1^2 x_3^3 - 6 x_2` can be described as + * ``` + * Int.algebra { + * val numberedPolynomial : NumberedPolynomial = NumberedPolynomial { + * 5 { 1 inPowerOf 2u; 3 inPowerOf 3u } // 5 x_1^2 x_3^3 + + * (-6) { 2 inPowerOf 1u } // (-6) x_2^1 + * } + * } + * ``` + */ +@DslMarker +@UnstableKMathAPI +internal annotation class NumberedPolynomialConstructorDSL + +/** + * Builder of [NumberedPolynomial] signature. It should be used as an implicit context for lambdas that describe term signature. + */ +@UnstableKMathAPI +@NumberedPolynomialConstructorDSL +public class NumberedPolynomialTermSignatureBuilder { + /** + * Signature storage. Any declaration of any variable's power updates the storage by increasing corresponding value. + * Afterward the storage will be used as a resulting signature. + */ + private val signature: MutableList = ArrayList() + + /** + * Builds the resulting signature. + * + * In fact, it just returns [signature] as regular signature of type `List`. + */ + internal fun build(): List = signature + + /** + * Declares power of variable #[this] of degree [deg]. + * + * Declaring another power of the same variable will increase its degree by received degree. + */ + public infix fun Int.inPowerOf(deg: UInt) { + if (this > signature.lastIndex) { + signature.addAll(List(this - signature.lastIndex - 1) { 0u }) + signature.add(deg) + } else { + signature[this] += deg + } + } + /** + * Declares power of variable #[this] of degree [deg]. + * + * Declaring another power of the same variable will increase its degree by received degree. + */ + @Suppress("NOTHING_TO_INLINE") + public inline infix fun Int.pow(deg: UInt): Unit = this inPowerOf deg + /** + * Declares power of variable #[this] of degree [deg]. + * + * Declaring another power of the same variable will increase its degree by received degree. + */ + @Suppress("NOTHING_TO_INLINE") + public inline infix fun Int.`in`(deg: UInt): Unit = this inPowerOf deg + /** + * Declares power of variable #[this] of degree [deg]. + * + * Declaring another power of the same variable will increase its degree by received degree. + */ + @Suppress("NOTHING_TO_INLINE") + public inline infix fun Int.of(deg: UInt): Unit = this inPowerOf deg +} + +/** + * Builder of [NumberedPolynomial]. It should be used as an implicit context for lambdas that describe [NumberedPolynomial]. + */ +@UnstableKMathAPI +@NumberedPolynomialConstructorDSL +public class NumberedPolynomialBuilder( + /** + * Summation operation that will be used to sum coefficients of monomials of same signatures. + */ + private val add: (C, C) -> C, + /** + * Initial capacity of coefficients map. + */ + initialCapacity: Int = 0 +) { + /** + * Coefficients storage. Any declaration of any monomial updates the storage. + * Afterward the storage will be used as a resulting coefficients map. + */ + private val coefficients: MutableMap, C> = LinkedHashMap(initialCapacity) + + /** + * Builds the resulting coefficients map. + * + * In fact, it just returns [coefficients] as regular coefficients map of type `Map, C>`. + */ + @PublishedApi + internal fun build(): NumberedPolynomial = NumberedPolynomial(coefficients) + + /** + * Declares monomial with [this] coefficient and provided [signature]. + * + * Declaring another monomial with the same signature will add [this] coefficient to existing one. If the sum of such + * coefficients is zero at any moment the monomial won't be removed but will be left as it is. + */ + public infix fun C.with(signature: List) { + if (signature in coefficients) coefficients[signature] = add(coefficients[signature]!!, this@with) + else coefficients[signature] = this@with + } + /** + * Declares monomial with [this] coefficient and signature constructed by [block]. + * + * Declaring another monomial with the same signature will add [this] coefficient to existing one. If the sum of such + * coefficients is zero at any moment the monomial won't be removed but will be left as it is. + */ + @Suppress("NOTHING_TO_INLINE") + public inline infix fun C.with(noinline block: NumberedPolynomialTermSignatureBuilder.() -> Unit): Unit = this.invoke(block) + /** + * Declares monomial with [this] coefficient and signature constructed by [block]. + * + * Declaring another monomial with the same signature will add [this] coefficient to existing one. If the sum of such + * coefficients is zero at any moment the monomial won't be removed but will be left as it is. + */ + public operator fun C.invoke(block: NumberedPolynomialTermSignatureBuilder.() -> Unit): Unit = + this with NumberedPolynomialTermSignatureBuilder().apply(block).build() +} + +// Waiting for context receivers :( FIXME: Replace with context receivers when they will be available + +/** + * Creates [NumberedPolynomial] with lambda [block] in context of [this] ring of constants. + * + * For example, polynomial `5 x_1^2 x_3^3 - 6 x_2` can be described as + * ``` + * Int.algebra { + * val numberedPolynomial : NumberedPolynomial = NumberedPolynomial { + * 5 { 1 inPowerOf 2u; 3 inPowerOf 3u } // 5 x_1^2 x_3^3 + + * (-6) { 2 inPowerOf 1u } // (-6) x_2^1 + * } + * } + * ``` + */ +@UnstableKMathAPI +@Suppress("FunctionName") +public inline fun > A.NumberedPolynomial(initialCapacity: Int = 0, block: NumberedPolynomialBuilder.() -> Unit) : NumberedPolynomial = NumberedPolynomialBuilder(::add, initialCapacity).apply(block).build() +/** + * Creates [NumberedPolynomial] with lambda [block] in context of [this] ring of [NumberedPolynomial]s. + * + * For example, polynomial `5 x_1^2 x_3^3 - 6 x_2` can be described as + * ``` + * Int.algebra { + * val numberedPolynomial : NumberedPolynomial = NumberedPolynomial { + * 5 { 1 inPowerOf 2u; 3 inPowerOf 3u } // 5 x_1^2 x_3^3 + + * (-6) { 2 inPowerOf 1u } // (-6) x_2^1 + * } + * } + * ``` + */ +@UnstableKMathAPI +@Suppress("FunctionName") +public inline fun > NumberedPolynomialSpace.NumberedPolynomial(initialCapacity: Int, block: NumberedPolynomialBuilder.() -> Unit) : NumberedPolynomial = NumberedPolynomialBuilder({ left: C, right: C -> left + right }, initialCapacity).apply(block).build() +/** + * Creates [NumberedPolynomial] with lambda [block] in context of [this] field of [NumberedRationalFunction]s. + * + * For example, polynomial `5 x_1^2 x_3^3 - 6 x_2` can be described as + * ``` + * Int.algebra { + * val numberedPolynomial : NumberedPolynomial = NumberedPolynomial { + * 5 { 1 inPowerOf 2u; 3 inPowerOf 3u } // 5 x_1^2 x_3^3 + + * (-6) { 2 inPowerOf 1u } // (-6) x_2^1 + * } + * } + * ``` + */ +@UnstableKMathAPI +@Suppress("FunctionName") +public inline fun > NumberedRationalFunctionSpace.NumberedPolynomial(initialCapacity: Int, block: NumberedPolynomialBuilder.() -> Unit) : NumberedPolynomial = NumberedPolynomialBuilder({ left: C, right: C -> left + right }, initialCapacity).apply(block).build() + +// Waiting for context receivers :( FIXME: Replace with context receivers when they will be available + +/** + * Constructs [NumberedRationalFunction] with provided coefficients maps [numeratorCoefficients] and [denominatorCoefficients]. + * + * The maps will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. the maps' keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName") +public fun > A.NumberedRationalFunction(numeratorCoefficients: Map, C>, denominatorCoefficients: Map, C>): NumberedRationalFunction = + NumberedRationalFunction( + NumberedPolynomial(numeratorCoefficients), + NumberedPolynomial(denominatorCoefficients) + ) +/** + * Constructs [NumberedRationalFunction] with provided coefficients maps [numeratorCoefficients] and [denominatorCoefficients]. + * + * The maps will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. the maps' keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName") +public fun > NumberedRationalFunctionSpace.NumberedRationalFunction(numeratorCoefficients: Map, C>, denominatorCoefficients: Map, C>): NumberedRationalFunction = + NumberedRationalFunction( + NumberedPolynomial(numeratorCoefficients), + NumberedPolynomial(denominatorCoefficients) + ) + +/** + * Constructs [NumberedRationalFunction] with provided [numerator] and unit denominator. + */ +@Suppress("FunctionName") +public fun > A.NumberedRationalFunction(numerator: NumberedPolynomial): NumberedRationalFunction = + NumberedRationalFunction(numerator, NumberedPolynomial(mapOf(emptyList() to one))) +/** + * Constructs [NumberedRationalFunction] with provided [numerator] and unit denominator. + */ +@Suppress("FunctionName") +public fun > NumberedRationalFunctionSpace.NumberedRationalFunction(numerator: NumberedPolynomial): NumberedRationalFunction = + NumberedRationalFunction(numerator, polynomialOne) + +/** + * Constructs [NumberedRationalFunction] with provided coefficients map [numeratorCoefficients] for numerator and unit + * denominator. + * + * [numeratorCoefficients] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [numeratorCoefficients]'s keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName") +public fun > NumberedRationalFunctionSpace.NumberedRationalFunction(numeratorCoefficients: Map, C>): NumberedRationalFunction = + NumberedRationalFunction( + NumberedPolynomial(numeratorCoefficients), + polynomialOne + ) +/** + * Constructs [NumberedRationalFunction] with provided coefficients map [numeratorCoefficients] for numerator and unit + * denominator. + * + * [numeratorCoefficients] will be "cleaned up": + * 1. Zeros at the ends of terms' signatures (e.g. [numeratorCoefficients]'s keys) will be removed. (See [cleanUp].) + * 1. Terms that happen to have the same signature will be summed up. + * 1. New map will be formed of resulting terms. + */ +@Suppress("FunctionName") +public fun > A.NumberedRationalFunction(numeratorCoefficients: Map, C>): NumberedRationalFunction = + NumberedRationalFunction( + NumberedPolynomial(numeratorCoefficients), + NumberedPolynomialAsIs(mapOf(emptyList() to one)) + ) + +///** +// * Converts [this] coefficient to [NumberedRationalFunction]. +// */ +//context(A) +//public fun > C.asNumberedRationalFunction() : NumberedRationalFunction = +// NumberedRationalFunction( +// NumberedPolynomialAsIs(mapOf(emptyList() to this)), +// NumberedPolynomialAsIs(mapOf(emptyList() to one)) +// ) +///** +// * Converts [this] coefficient to [NumberedRationalFunction]. +// */ +//context(NumberedRationalFunctionSpace) +//public fun > C.asNumberedRationalFunction() : NumberedRationalFunction = +// NumberedRationalFunction( +// NumberedPolynomialAsIs(mapOf(emptyList() to this)), +// NumberedPolynomialAsIs(mapOf(emptyList() to constantOne)) +// ) \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedUtil.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedUtil.kt new file mode 100644 index 000000000..484cd11e3 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/numberedUtil.kt @@ -0,0 +1,515 @@ +/* + * 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.Field +import space.kscience.kmath.operations.Ring +import space.kscience.kmath.operations.algebra +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.Buffer +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName +import kotlin.math.max +import kotlin.math.min + + +/** + * Creates a [NumberedPolynomialSpace] over a received ring. + */ +public fun > A.numberedPolynomialSpace(): NumberedPolynomialSpace = + NumberedPolynomialSpace(this) + +/** + * Creates a [NumberedPolynomialSpace]'s scope over a received ring. + */ +public inline fun , R> A.numberedPolynomialSpace(block: NumberedPolynomialSpace.() -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return NumberedPolynomialSpace(this).block() +} + +/** + * Creates a [NumberedRationalFunctionSpace] over a received ring. + */ +public fun > A.numberedRationalFunctionSpace(): NumberedRationalFunctionSpace = + NumberedRationalFunctionSpace(this) + +/** + * Creates a [NumberedRationalFunctionSpace]'s scope over a received ring. + */ +public inline fun , R> A.numberedRationalFunctionSpace(block: NumberedRationalFunctionSpace.() -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return NumberedRationalFunctionSpace(this).block() +} + +/** + * Substitutes provided Double arguments [args] into [this] Double polynomial. + */ +public fun NumberedPolynomial.substitute(args: Map): NumberedPolynomial = Double.algebra { + NumberedPolynomial( + buildMap, 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, substitution) -> + val deg = degs.getOrElse(variable) { 0u } + if (deg == 0u) product else product * substitution.pow(deg.toInt()) + } + if (newDegs !in this) this[newDegs] = newC + else this[newDegs] = this[newDegs]!! + newC + } + } + ) +} + +/** + * Substitutes provided arguments [args] into [this] polynomial. + */ +public fun NumberedPolynomial.substitute(ring: Ring, args: Map): NumberedPolynomial = ring { + NumberedPolynomial( + buildMap, 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, substitution) -> + val deg = degs.getOrElse(variable) { 0u } + if (deg == 0u) product else product * power(substitution, deg) + } + if (newDegs !in this) this[newDegs] = newC + else this[newDegs] = this[newDegs]!! + newC + } + } + ) +} + +/** + * Substitutes provided arguments [args] into [this] polynomial. + */ // TODO: To optimize boxing +@JvmName("substitutePolynomial") +public fun NumberedPolynomial.substitute(ring: Ring, args: Map>) : NumberedPolynomial = + ring.numberedPolynomialSpace { + coefficients.entries.fold(zero) { acc, (degs, c) -> + val newDegs = degs.mapIndexed { index, deg -> if (index !in args) deg else 0u }.cleanUp() + acc + args.entries.fold(NumberedPolynomial(mapOf(newDegs to c))) { product, (variable, substitution) -> + val deg = degs.getOrElse(variable) { 0u } + if (deg == 0u) product else product * power(substitution, deg) + } + } + } + +/** + * Substitutes provided arguments [args] into [this] polynomial. + */ // TODO: To optimize boxing +@JvmName("substituteRationalFunction") +public fun NumberedPolynomial.substitute(ring: Ring, args: Map>) : NumberedRationalFunction = + ring.numberedRationalFunctionSpace { + coefficients.entries.fold(zero) { acc, (degs, c) -> + val newDegs = degs.mapIndexed { index, deg -> if (index !in args) deg else 0u }.cleanUp() + acc + args.entries.fold(NumberedRationalFunction(NumberedPolynomial(mapOf(newDegs to c)))) { product, (variable, substitution) -> + val deg = degs.getOrElse(variable) { 0u } + if (deg == 0u) product else product * power(substitution, deg) + } + } + } + +/** + * Substitutes provided Double arguments [args] into [this] Double rational function. + */ +public fun NumberedRationalFunction.substitute(args: Map): NumberedRationalFunction = + NumberedRationalFunction(numerator.substitute(args), denominator.substitute(args)) + +/** + * Substitutes provided arguments [args] into [this] rational function. + */ +public fun NumberedRationalFunction.substitute(ring: Ring, args: Map): NumberedRationalFunction = + NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args)) + +/** + * Substitutes provided arguments [args] into [this] rational function. + */ // TODO: To optimize boxing +@JvmName("substitutePolynomial") +public fun NumberedRationalFunction.substitute(ring: Ring, args: Map>) : NumberedRationalFunction = + NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args)) + +/** + * Substitutes provided arguments [args] into [this] rational function. + */ // TODO: To optimize boxing +@JvmName("substituteRationalFunction") +public fun NumberedRationalFunction.substitute(ring: Ring, args: Map>) : NumberedRationalFunction = + ring.numberedRationalFunctionSpace { + numerator.substitute(ring, args) / denominator.substitute(ring, args) + } + +/** + * Substitutes provided Double arguments [args] into [this] Double polynomial. + */ +public fun NumberedPolynomial.substitute(args: Buffer): NumberedPolynomial = Double.algebra { + val lastSubstitutionVariable = args.size - 1 + NumberedPolynomial( + buildMap(coefficients.size) { + for ((degs, c) in coefficients) { + val lastDegsIndex = degs.lastIndex + val newDegs = + if (lastDegsIndex <= lastSubstitutionVariable) emptyList() + else degs.toMutableList().apply { + for (i in 0..lastSubstitutionVariable) this[i] = 0u + } + val newC = (0..min(lastDegsIndex, lastSubstitutionVariable)).fold(c) { product, variable -> + val deg = degs[variable] + if (deg == 0u) product else product * args[variable].pow(deg.toInt()) + } + if (newDegs !in this) this[newDegs] = newC + else this[newDegs] = this[newDegs]!! + newC + } + } + ) +} + +/** + * Substitutes provided arguments [args] into [this] polynomial. + */ +public fun NumberedPolynomial.substitute(ring: Ring, args: Buffer): NumberedPolynomial = ring { + val lastSubstitutionVariable = args.size - 1 + NumberedPolynomial( + buildMap, C>(coefficients.size) { + for ((degs, c) in coefficients) { + val lastDegsIndex = degs.lastIndex + val newDegs = + if (lastDegsIndex <= lastSubstitutionVariable) emptyList() + else degs.toMutableList().apply { + for (i in 0..lastSubstitutionVariable) this[i] = 0u + } + val newC = (0..min(lastDegsIndex, lastSubstitutionVariable)).fold(c) { product, variable -> + val deg = degs[variable] + if (deg == 0u) product else product * power(args[variable], deg) + } + if (newDegs !in this) this[newDegs] = newC + else this[newDegs] = this[newDegs]!! + newC + } + } + ) +} + +/** + * Substitutes provided arguments [args] into [this] polynomial. + */ // TODO: To optimize boxing +@JvmName("substitutePolynomial") +public fun NumberedPolynomial.substitute(ring: Ring, args: Buffer>) : NumberedPolynomial = + ring.numberedPolynomialSpace { + val lastSubstitutionVariable = args.size - 1 + coefficients.entries.fold(zero) { acc, (degs, c) -> + val lastDegsIndex = degs.lastIndex + val newDegs = + if (lastDegsIndex <= lastSubstitutionVariable) emptyList() + else degs.toMutableList().apply { + for (i in 0..lastSubstitutionVariable) this[i] = 0u + } + acc + (0..min(lastDegsIndex, lastSubstitutionVariable)) + .fold(NumberedPolynomial(mapOf(newDegs to c))) { product, variable -> + val deg = degs[variable] + if (deg == 0u) product else product * power(args[variable], deg) + } + } + } + +/** + * Substitutes provided arguments [args] into [this] polynomial. + */ // TODO: To optimize boxing +@JvmName("substituteRationalFunction") +public fun NumberedPolynomial.substitute(ring: Ring, args: Buffer>) : NumberedRationalFunction = + ring.numberedRationalFunctionSpace { + val lastSubstitutionVariable = args.size - 1 + coefficients.entries.fold(zero) { acc, (degs, c) -> + val lastDegsIndex = degs.lastIndex + val newDegs = + if (lastDegsIndex <= lastSubstitutionVariable) emptyList() + else degs.toMutableList().apply { + for (i in 0..lastSubstitutionVariable) this[i] = 0u + } + acc + (0..min(lastDegsIndex, lastSubstitutionVariable)) + .fold(NumberedRationalFunction(NumberedPolynomial(mapOf(newDegs to c)))) { product, variable -> + val deg = degs[variable] + if (deg == 0u) product else product * power(args[variable], deg) + } + } + } + +/** + * Substitutes provided Double arguments [args] into [this] Double rational function. + */ +public fun NumberedRationalFunction.substitute(args: Buffer): NumberedRationalFunction = + NumberedRationalFunction(numerator.substitute(args), denominator.substitute(args)) + +/** + * Substitutes provided arguments [args] into [this] rational function. + */ +public fun NumberedRationalFunction.substitute(ring: Ring, args: Buffer): NumberedRationalFunction = + NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args)) + +/** + * Substitutes provided arguments [args] into [this] rational function. + */ // TODO: To optimize boxing +@JvmName("substitutePolynomial") +public fun NumberedRationalFunction.substitute(ring: Ring, args: Buffer>) : NumberedRationalFunction = + NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args)) + +/** + * Substitutes provided arguments [args] into [this] rational function. + */ // TODO: To optimize boxing +@JvmName("substituteRationalFunction") +public fun NumberedRationalFunction.substitute(ring: Ring, args: Buffer>) : NumberedRationalFunction = + ring.numberedRationalFunctionSpace { + numerator.substitute(ring, args) / denominator.substitute(ring, args) + } + +/** + * Substitutes provided Double arguments [args] into [this] Double polynomial. + */ +public fun NumberedPolynomial.substituteFully(args: Buffer): Double = Double.algebra { + val lastSubstitutionVariable = args.size - 1 + require(coefficients.keys.all { it.lastIndex <= lastSubstitutionVariable }) { "Fully substituting buffer should cover all variables of the polynomial." } + coefficients.entries.fold(.0) { acc, (degs, c) -> + acc + degs.foldIndexed(c) { variable, product, deg -> + if (deg == 0u) product else product * args[variable].pow(deg.toInt()) + } + } +} + +/** + * Substitutes provided arguments [args] into [this] polynomial. + */ +public fun NumberedPolynomial.substituteFully(ring: Ring, args: Buffer): C = ring { + val lastSubstitutionVariable = args.size - 1 + require(coefficients.keys.all { it.lastIndex <= lastSubstitutionVariable }) { "Fully substituting buffer should cover all variables of the polynomial." } + coefficients.entries.fold(zero) { acc, (degs, c) -> + acc + degs.foldIndexed(c) { variable, product, deg -> + if (deg == 0u) product else product * power(args[variable], deg) + } + } +} + +/** + * Substitutes provided Double arguments [args] into [this] Double rational function. + */ +public fun NumberedRationalFunction.substituteFully(args: Buffer): Double = + numerator.substituteFully(args) / denominator.substituteFully(args) + +/** + * Substitutes provided arguments [args] into [this] rational function. + */ +public fun NumberedRationalFunction.substituteFully(ring: Field, args: Buffer): C = ring { + numerator.substituteFully(ring, args) / denominator.substituteFully(ring, args) +} + +/** + * Represent [this] polynomial as a regular context-less function. + */ +public fun > NumberedPolynomial.asFunctionOver(ring: A): (Buffer) -> C = { substituteFully(ring, it) } + +/** + * Represent [this] polynomial as a regular context-less function. + */ +public fun > NumberedPolynomial.asFunctionOfConstantOver(ring: A): (Buffer) -> C = { substituteFully(ring, it) } + +/** + * Represent [this] polynomial as a regular context-less function. + */ +public fun > NumberedPolynomial.asFunctionOfPolynomialOver(ring: A): (Buffer>) -> NumberedPolynomial = { substitute(ring, it) } + +/** + * Represent [this] polynomial as a regular context-less function. + */ +public fun > NumberedPolynomial.asFunctionOfRationalFunctionOver(ring: A): (Buffer>) -> NumberedRationalFunction = { substitute(ring, it) } + +/** + * Represent [this] rational function as a regular context-less function. + */ +public fun > NumberedRationalFunction.asFunctionOver(ring: A): (Buffer) -> C = { substituteFully(ring, it) } + +/** + * Represent [this] rational function as a regular context-less function. + */ +public fun > NumberedRationalFunction.asFunctionOfConstantOver(ring: A): (Buffer) -> C = { substituteFully(ring, it) } + +/** + * Represent [this] rational function as a regular context-less function. + */ +public fun > NumberedRationalFunction.asFunctionOfPolynomialOver(ring: A): (Buffer>) -> NumberedRationalFunction = { substitute(ring, it) } + +/** + * Represent [this] rational function as a regular context-less function. + */ +public fun > NumberedRationalFunction.asFunctionOfRationalFunctionOver(ring: A): (Buffer>) -> NumberedRationalFunction = { substitute(ring, it) } + +/** + * Returns algebraic derivative of received polynomial with respect to provided variable. + */ +@UnstableKMathAPI +public fun > NumberedPolynomial.derivativeWithRespectTo( + ring: A, + variable: Int, +): NumberedPolynomial = ring { + NumberedPolynomial( + buildMap(coefficients.size) { + coefficients + .forEach { (degs, c) -> + if (degs.size > variable) return@forEach + put( + degs.mapIndexed { index, deg -> + when { + index != variable -> deg + deg > 0u -> deg - 1u + else -> return@forEach + } + }.cleanUp(), + multiplyByDoubling(c, degs[variable]) + ) + } + } + ) +} + +/** + * Returns algebraic derivative of received polynomial with respect to provided variable of specified order. + */ +@UnstableKMathAPI +public fun > NumberedPolynomial.nthDerivativeWithRespectTo( + ring: A, + variable: Int, + order: UInt +): NumberedPolynomial = ring { + if (order == 0u) return this@nthDerivativeWithRespectTo + NumberedPolynomial( + buildMap(coefficients.size) { + coefficients + .forEach { (degs, c) -> + if (degs.size > variable) return@forEach + put( + degs.mapIndexed { index, deg -> + when { + index != variable -> deg + deg >= order -> deg - order + else -> return@forEach + } + }.cleanUp(), + degs[variable].let { deg -> + (deg downTo deg - order + 1u) + .fold(c) { acc, ord -> multiplyByDoubling(acc, ord) } + } + ) + } + } + ) +} + +/** + * Returns algebraic derivative of received polynomial with respect to provided variables of specified orders. + */ +@UnstableKMathAPI +public fun > NumberedPolynomial.nthDerivativeWithRespectTo( + ring: A, + variablesAndOrders: Map, +): NumberedPolynomial = ring { + val filteredVariablesAndOrders = variablesAndOrders.filterValues { it != 0u } + if (filteredVariablesAndOrders.isEmpty()) return this@nthDerivativeWithRespectTo + val maxRespectedVariable = filteredVariablesAndOrders.keys.maxOrNull()!! + NumberedPolynomial( + buildMap(coefficients.size) { + coefficients + .forEach { (degs, c) -> + if (degs.size > maxRespectedVariable) return@forEach + put( + degs.mapIndexed { index, deg -> + if (index !in filteredVariablesAndOrders) return@mapIndexed deg + val order = filteredVariablesAndOrders[index]!! + if (deg >= order) deg - order else return@forEach + }.cleanUp(), + filteredVariablesAndOrders.entries.fold(c) { acc1, (index, order) -> + degs[index].let { deg -> + (deg downTo deg - order + 1u) + .fold(acc1) { acc2, ord -> multiplyByDoubling(acc2, ord) } + } + } + ) + } + } + ) +} + +/** + * Returns algebraic antiderivative of received polynomial with respect to provided variable. + */ +@UnstableKMathAPI +public fun > NumberedPolynomial.antiderivativeWithRespectTo( + ring: A, + variable: Int, +): NumberedPolynomial = ring { + NumberedPolynomial( + buildMap(coefficients.size) { + coefficients + .forEach { (degs, c) -> + put( + List(max(variable + 1, degs.size)) { if (it != variable) degs[it] else degs[it] + 1u }, + c / multiplyByDoubling(one, degs[variable]) + ) + } + } + ) +} + +/** + * Returns algebraic antiderivative of received polynomial with respect to provided variable of specified order. + */ +@UnstableKMathAPI +public fun > NumberedPolynomial.nthAntiderivativeWithRespectTo( + ring: A, + variable: Int, + order: UInt +): NumberedPolynomial = ring { + if (order == 0u) return this@nthAntiderivativeWithRespectTo + NumberedPolynomial( + buildMap(coefficients.size) { + coefficients + .forEach { (degs, c) -> + put( + List(max(variable + 1, degs.size)) { if (it != variable) degs[it] else degs[it] + order }, + degs[variable].let { deg -> + (deg downTo deg - order + 1u) + .fold(c) { acc, ord -> acc / multiplyByDoubling(one, ord) } + } + ) + } + } + ) +} + +/** + * Returns algebraic derivative of received polynomial with respect to provided variables of specified orders. + */ +@UnstableKMathAPI +public fun > NumberedPolynomial.nthAntiderivativeWithRespectTo( + ring: A, + variablesAndOrders: Map, +): NumberedPolynomial = ring { + val filteredVariablesAndOrders = variablesAndOrders.filterValues { it != 0u } + if (filteredVariablesAndOrders.isEmpty()) return this@nthAntiderivativeWithRespectTo + val maxRespectedVariable = filteredVariablesAndOrders.keys.maxOrNull()!! + NumberedPolynomial( + buildMap(coefficients.size) { + coefficients + .forEach { (degs, c) -> + put( + List(max(maxRespectedVariable + 1, degs.size)) { degs[it] + filteredVariablesAndOrders.getOrElse(it) { 0u } }, + filteredVariablesAndOrders.entries.fold(c) { acc1, (index, order) -> + degs[index].let { deg -> + (deg downTo deg - order + 1u) + .fold(acc1) { acc2, ord -> acc2 / multiplyByDoubling(one, ord) } + } + } + ) + } + } + ) +} \ No newline at end of file