Feature: Polynomials and rational functions #469

Merged
lounres merged 132 commits from feature/polynomials into dev 2022-07-28 18:04:06 +03:00
51 changed files with 36727 additions and 116 deletions

View File

@ -214,6 +214,28 @@ One can still use generic algebras though.
> >
> **Maturity**: EXPERIMENTAL > **Maturity**: EXPERIMENTAL
### [kmath-polynomial](kmath-polynomial)
>
>
> **Maturity**: PROTOTYPE
>
> **Features:**
> - [polynomial abstraction](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt) : Abstraction for polynomial spaces.
> - [rational function abstraction](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/RationalFunction.kt) : Abstraction for rational functions spaces.
> - ["list" polynomials](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/ListRationalFunction.kt) : List implementation of univariate polynomials.
> - ["list" rational functions](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/ListPolynomial.kt) : List implementation of univariate rational functions.
> - ["list" polynomials and rational functions constructors](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/listConstructors.kt) : Constructors for list polynomials and rational functions.
> - ["list" polynomials and rational functions utilities](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/listUtil.kt) : Utilities for list polynomials and rational functions.
> - ["numbered" polynomials](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedRationalFunction.kt) : Numbered implementation of multivariate polynomials.
> - ["numbered" rational functions](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt) : Numbered implementation of multivariate rational functions.
> - ["numbered" polynomials and rational functions constructors](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/numberedConstructors.kt) : Constructors for numbered polynomials and rational functions.
> - ["numbered" polynomials and rational functions utilities](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/numberedUtil.kt) : Utilities for numbered polynomials and rational functions.
> - ["labeled" polynomials](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/LabeledRationalFunction.kt) : Labeled implementation of multivariate polynomials.
> - ["labeled" rational functions](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/LabeledPolynomial.kt) : Labeled implementation of multivariate rational functions.
> - ["labeled" polynomials and rational functions constructors](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/labeledConstructors.kt) : Constructors for labeled polynomials and rational functions.
> - ["labeled" polynomials and rational functions utilities](kmath-polynomial/src/commonMain/kotlin/space/kscience/kmath/functions/labeledUtil.kt) : Utilities for labeled polynomials and rational functions.
### [kmath-stat](kmath-stat) ### [kmath-stat](kmath-stat)
> >
> >

172
docs/polynomials.md Normal file
View File

@ -0,0 +1,172 @@
# Polynomials and Rational Functions
KMath provides a way to work with uni- and multivariate polynomials and rational functions. It includes full support of arithmetic operations of integers, **constants** (elements of ring polynomials are build over), variables (for certain multivariate implementations), polynomials and rational functions encapsulated in so-called **polynomial space** and **rational function space** and some other utilities such as algebraic differentiation and substitution.
## Concrete realizations
There are 3 approaches to represent polynomials:
1. For univariate polynomials one can represent and store polynomial as a list of coefficients for each power of the variable. I.e. polynomial $a_0 + \dots + a_n x^n $ can be represented as a finite sequence $(a_0; \dots; a_n)$. (Compare to sequential definition of polynomials.)
2. For multivariate polynomials one can represent and store polynomial as a matching (in programming it is called "map" or "dictionary", in math it is called [functional relation](https://en.wikipedia.org/wiki/Binary_relation#Special_types_of_binary_relations)) of each "**term signature**" (that describes what variables and in what powers appear in the term) with corresponding coefficient of the term. But there are 2 possible approaches of term signature representation:
1. One can number all the variables, so term signature can be represented as a sequence describing powers of the variables. I.e. signature of term $c \\; x_0^{d_0} \dots x_n^{d_n} $ (for natural or zero $d_i $) can be represented as a finite sequence $(d_0; \dots; d_n)$.
2. One can represent variables as objects ("**labels**"), so term signature can be also represented as a matching of each appeared variable with its power in the term. I.e. signature of term $c \\; x_0^{d_0} \dots x_n^{d_n} $ (for natural non-zero $d_i $) can be represented as a finite matching $(x_0 \to d_1; \dots; x_n \to d_n)$.
All that three approaches are implemented by "list", "numbered", and "labeled" versions of polynomials and polynomial spaces respectively. Whereas all rational functions are represented as fractions with corresponding polynomial numerator and denominator, and rational functions' spaces are implemented in the same way as usual field of rational numbers (or more precisely, as any field of fractions over integral domain) should be implemented.
So here are a bit of details. Let `C` by type of constants. Then:
1. `ListPolynomial`, `ListPolynomialSpace`, `ListRationalFunction` and `ListRationalFunctionSpace` implement the first scenario. `ListPolynomial` stores polynomial $a_0 + \dots + a_n x^n $ as a coefficients list `listOf(a_0, ..., a_n)` (of type `List<C>`).
They also have variation `ScalableListPolynomialSpace` that replaces former polynomials and implements `ScaleOperations`.
2. `NumberedPolynomial`, `NumberedPolynomialSpace`, `NumberedRationalFunction` and `NumberedRationalFunctionSpace` implement second scenario. `NumberedPolynomial` stores polynomials as structures of type `Map<List<UInt>, C>`. Signatures are stored as `List<UInt>`. To prevent ambiguity signatures should not end with zeros.
3. `LabeledPolynomial`, `LabeledPolynomialSpace`, `LabeledRationalFunction` and `LabeledRationalFunctionSpace` implement third scenario using common `Symbol` as variable type. `LabeledPolynomial` stores polynomials as structures of type `Map<Map<Symbol, UInt>, C>`. Signatures are stored as `Map<Symbol, UInt>`. To prevent ambiguity each signature should not map any variable to zero.
### Example: `ListPolynomial`
For example, polynomial $2 - 3x + x^2 $ (with `Int` coefficients) is represented
```kotlin
val polynomial: ListPolynomial<Int> = ListPolynomial(listOf(2, -3, 1))
// or
val polynomial: ListPolynomial<Int> = ListPolynomial(2, -3, 1)
```
All algebraic operations can be used in corresponding space:
```kotlin
val computationResult = Int.algebra.listPolynomialSpace {
ListPolynomial(2, -3, 1) + ListPolynomial(0, 6) == ListPolynomial(2, 3, 1)
}
println(computationResult) // true
```
For more see [examples](../examples/src/main/kotlin/space/kscience/kmath/functions/polynomials.kt).
### Example: `NumberedPolynomial`
For example, polynomial $3 + 5 x_1 - 7 x_0^2 x_2 $ (with `Int` coefficients) is represented
```kotlin
val polynomial: NumberedPolynomial<Int> = NumberedPolynomial(
mapOf(
listOf<UInt>() to 3,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to -7,
)
)
// or
val polynomial: NumberedPolynomial<Int> = NumberedPolynomial(
listOf<UInt>() to 3,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to -7,
)
```
All algebraic operations can be used in corresponding space:
```kotlin
val computationResult = Int.algebra.numberedPolynomialSpace {
NumberedPolynomial(
listOf<UInt>() to 3,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to -7,
) + NumberedPolynomial(
listOf(0u, 1u) to -5,
listOf(0u, 0u, 0u, 4u) to 4,
) == NumberedPolynomial(
listOf<UInt>() to 3,
listOf(0u, 1u) to 0,
listOf(2u, 0u, 1u) to -7,
listOf(0u, 0u, 0u, 4u) to 4,
)
}
println(computationResult) // true
```
For more see [examples](../examples/src/main/kotlin/space/kscience/kmath/functions/polynomials.kt).
### Example: `LabeledPolynomial`
For example, polynomial $3 + 5 y - 7 x^2 z $ (with `Int` coefficients) is represented
```kotlin
val polynomial: LabeledPolynomial<Int> = LabeledPolynomial(
mapOf(
mapOf<Symbol, UInt>() to 3,
mapOf(y to 1u) to 5,
mapOf(x to 2u, z to 1u) to -7,
)
)
// or
val polynomial: LabeledPolynomial<Int> = LabeledPolynomial(
mapOf<Symbol, UInt>() to 3,
mapOf(y to 1u) to 5,
mapOf(x to 2u, z to 1u) to -7,
)
```
All algebraic operations can be used in corresponding space:
```kotlin
val computationResult = Int.algebra.labeledPolynomialSpace {
LabeledPolynomial(
listOf<UInt>() to 3,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to -7,
) + LabeledPolynomial(
listOf(0u, 1u) to -5,
listOf(0u, 0u, 0u, 4u) to 4,
) == LabeledPolynomial(
listOf<UInt>() to 3,
listOf(0u, 1u) to 0,
listOf(2u, 0u, 1u) to -7,
listOf(0u, 0u, 0u, 4u) to 4,
)
}
println(computationResult) // true
```
For more see [examples](../examples/src/main/kotlin/space/kscience/kmath/functions/polynomials.kt).
## Abstract entities (interfaces and abstract classes)
```mermaid
classDiagram
Polynomial <|-- ListPolynomial
Polynomial <|-- NumberedPolynomial
Polynomial <|-- LabeledPolynomial
RationalFunction <|-- ListRationalFunction
RationalFunction <|-- NumberedRationalFunction
RationalFunction <|-- LabeledRationalFunction
Ring <|-- PolynomialSpace
PolynomialSpace <|-- MultivariatePolynomialSpace
PolynomialSpace <|-- PolynomialSpaceOverRing
Ring <|-- RationalFunctionSpace
RationalFunctionSpace <|-- MultivariateRationalFunctionSpace
RationalFunctionSpace <|-- RationalFunctionSpaceOverRing
RationalFunctionSpace <|-- RationalFunctionSpaceOverPolynomialSpace
RationalFunctionSpace <|-- PolynomialSpaceOfFractions
RationalFunctionSpaceOverPolynomialSpace <|-- MultivariateRationalFunctionSpaceOverMultivariatePolynomialSpace
MultivariateRationalFunctionSpace <|-- MultivariateRationalFunctionSpaceOverMultivariatePolynomialSpace
MultivariateRationalFunctionSpace <|-- MultivariatePolynomialSpaceOfFractions
PolynomialSpaceOfFractions <|-- MultivariatePolynomialSpaceOfFractions
```
There are implemented `Polynomial` and `RationalFunction` interfaces as abstractions of polynomials and rational functions respectively (although, there is not a lot of logic in them) and `PolynomialSpace` and `RationalFunctionSpace` (that implement `Ring` interface) as abstractions of polynomials' and rational functions' spaces respectively. More precisely, that means they allow to declare common logic of interaction with such objects and spaces:
- `Polynomial` does not provide any logic. It is marker interface.
- `RationalFunction` provides numerator and denominator of rational function and destructuring declaration for them.
- `PolynomialSpace` provides all possible arithmetic interactions of integers, constants (of type `C`), and polynomials (of type `P`) like addition, subtraction, multiplication, and some others and common properties like degree of polynomial.
- `RationalFunctionSpace` provides the same as `PolynomialSpace` but also for rational functions: all possible arithmetic interactions of integers, constants (of type `C`), polynomials (of type `P`), and rational functions (of type `R`) like addition, subtraction, multiplication, division (in some cases), and some others and common properties like degree of polynomial.
Then to add abstraction of similar behaviour with variables (in multivariate case) there are implemented `MultivariatePolynomialSpace` and `MultivariateRationalFunctionSpace`. They just include variables (of type `V`) in the interactions of the entities.
Also, to remove boilerplates there were provided helping subinterfaces and abstract subclasses:
- `PolynomialSpaceOverRing` allows to replace implementation of interactions of integers and constants with implementations from provided ring over constants (of type `A: Ring<C>`).
- `RationalFunctionSpaceOverRing` &mdash; the same but for `RationalFunctionSpace`.
- `RationalFunctionSpaceOverPolynomialSpace` &mdash; the same but "the inheritance" includes interactions with polynomials from provided `PolynomialSpace`.
- `PolynomialSpaceOfFractions` is actually abstract subclass of `RationalFunctionSpace` that implements all fractions boilerplates with provided (`protected`) constructor of rational functions by polynomial numerator and denominator.
- `MultivariateRationalFunctionSpaceOverMultivariatePolynomialSpace` and `MultivariatePolynomialSpaceOfFractions` &mdash; the same stories of operators inheritance and fractions boilerplates respectively but in multivariate case.
## Utilities
For all kinds of polynomials there are provided (implementation details depend on kind of polynomials) such common utilities as:
1. differentiation and anti-differentiation,
2. substitution, invocation and functional representation.

View File

@ -15,6 +15,8 @@ dependencies {
implementation(project(":kmath-coroutines")) implementation(project(":kmath-coroutines"))
implementation(project(":kmath-commons")) implementation(project(":kmath-commons"))
implementation(project(":kmath-complex")) implementation(project(":kmath-complex"))
implementation(project(":kmath-functions"))
implementation(project(":kmath-polynomial"))
implementation(project(":kmath-optimization")) implementation(project(":kmath-optimization"))
implementation(project(":kmath-stat")) implementation(project(":kmath-stat"))
implementation(project(":kmath-viktor")) implementation(project(":kmath-viktor"))

View File

@ -0,0 +1,399 @@
/*
* 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.
*/
@file:Suppress("LocalVariableName")
package space.kscience.kmath.functions
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.expressions.symbol
import space.kscience.kmath.operations.algebra
import space.kscience.kmath.operations.invoke
/**
* Shows [ListPolynomial]s' and [ListRationalFunction]s' capabilities.
*/
fun listPolynomialsExample() {
// [ListPolynomial] is a representation of a univariate polynomial as a list of coefficients from the least term to
// the greatest term. For example,
val polynomial1: ListPolynomial<Int> = ListPolynomial(listOf(2, -3, 1))
// represents polynomial 2 + (-3) x + x^2
// There are also shortcut fabrics:
val polynomial2: ListPolynomial<Int> = ListPolynomial(2, -3, 1)
println(polynomial1 == polynomial2) // true
// and even
val polynomial3: ListPolynomial<Int> = 57.asListPolynomial()
val polynomial4: ListPolynomial<Int> = ListPolynomial(listOf(57))
println(polynomial3 == polynomial4) // true
val polynomial5: ListPolynomial<Int> = ListPolynomial(3, -1)
// For every ring there can be provided a polynomial ring:
Int.algebra.listPolynomialSpace {
println(-polynomial5 == ListPolynomial(-3, 1)) // true
println(polynomial1 + polynomial5 == ListPolynomial(5, -4, 1)) // true
println(polynomial1 - polynomial5 == ListPolynomial(-1, -2, 1)) // true
println(polynomial1 * polynomial5 == ListPolynomial(6, -11, 6, -1)) // true
}
// You can even write
val x: ListPolynomial<Double> = ListPolynomial(0.0, 1.0)
val polynomial6: ListPolynomial<Double> = ListPolynomial(2.0, -3.0, 1.0)
Double.algebra.listPolynomialSpace {
println(2 - 3 * x + x * x == polynomial6)
println(2.0 - 3.0 * x + x * x == polynomial6)
}
// Also there are some utilities for polynomials:
println(polynomial1.substitute(Int.algebra, 1) == 0) // true, because 2 + (-3) * 1 + 1^2 = 0
println(polynomial1.substitute(Int.algebra, polynomial5) == polynomial1) // true, because 2 + (-3) * (3-x) + (3-x)^2 = 2 - 3x + x^2
println(polynomial1.derivative(Int.algebra) == ListPolynomial(-3, 2)) // true, (2 - 3x + x^2)' = -3 + 2x
println(polynomial1.nthDerivative(Int.algebra, 2) == 2.asListPolynomial()) // true, (2 - 3x + x^2)'' = 2
// Lastly, there are rational functions and some other utilities:
Double.algebra.listRationalFunctionSpace {
val rationalFunction1: ListRationalFunction<Double> = ListRationalFunction(listOf(2.0, -3.0, 1.0), listOf(3.0, -1.0))
// It's just (2 - 3x + x^2)/(3 - x)
val rationalFunction2 : ListRationalFunction<Double> = ListRationalFunction(listOf(5.0, -4.0, 1.0), listOf(3.0, -1.0))
// It's just (5 - 4x + x^2)/(3 - x)
println(rationalFunction1 + 1 == rationalFunction2)
}
}
/**
* Shows [NumberedPolynomial]s' and [NumberedRationalFunction]s' capabilities.
*/
fun numberedPolynomialsExample() {
// Consider polynomial
// 3 + 5 x_2 - 7 x_1^2 x_3
// Consider, for example, its term -7 x_1^2 x_3. -7 is a coefficient of the term, whereas (2, 0, 1, 0, 0, ...) is
// description of degrees of variables x_1, x_2, ... in the term. Such description with removed leading zeros
// [2, 0, 1] is called "signature" of the term -7 x_1^2 x_3.
val polynomial1: NumberedPolynomial<Int>
with(Int.algebra) {
// [NumberedPolynomial] is a representation of a multivariate polynomial, that stores terms in a map with terms'
// signatures as the map's keys and terms' coefficients as corresponding values. For example,
polynomial1 = NumberedPolynomial(
mapOf(
listOf<UInt>() to 3,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to -7,
)
)
// represents polynomial 3 + 5 x_2 - 7 x_1^2 x_3
// This `NumberedPolynomial` function needs context of either ring of constant (as `Int.algebra` in this example)
// or space of NumberedPolynomials over it. To understand why it is like this see documentations of functions
// NumberedPolynomial and NumberedPolynomialWithoutCheck
// There are also shortcut fabrics:
val polynomial2: NumberedPolynomial<Int> = NumberedPolynomial(
listOf<UInt>() to 3,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to -7,
)
println(polynomial1 == polynomial2) // true
// and even
val polynomial3: NumberedPolynomial<Int> = 57.asNumberedPolynomial() // This one actually does not algebraic context!
val polynomial4: NumberedPolynomial<Int> = NumberedPolynomial(listOf<UInt>() to 57)
println(polynomial3 == polynomial4) // true
numberedPolynomialSpace {
// Also there is DSL for constructing NumberedPolynomials:
val polynomial5: NumberedPolynomial<Int> = NumberedPolynomialDSL1 {
3 {}
5 { 1 inPowerOf 1u }
-7 with { 0 pow 2u; 2 pow 1u }
// `pow` and `inPowerOf` are the same
// `with` is omittable
}
println(polynomial1 == polynomial5) // true
// Unfortunately the DSL does not work good in bare context of constants' ring, so for now it's disabled and
// works only in NumberedPolynomialSpace and NumberedRationalFunctionSpace
}
}
val polynomial6: NumberedPolynomial<Int> = Int.algebra {
NumberedPolynomial(
listOf<UInt>() to 7,
listOf(0u, 1u) to -5,
listOf(2u, 0u, 1u) to 0,
listOf(0u, 0u, 0u, 4u) to 4,
)
}
// For every ring there can be provided a polynomial ring:
Int.algebra.numberedPolynomialSpace {
println(
-polynomial6 == NumberedPolynomial(
listOf<UInt>() to -7,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to 0,
listOf(0u, 0u, 0u, 4u) to (-4),
)
) // true
println(
polynomial1 + polynomial6 == NumberedPolynomial(
listOf<UInt>() to 10,
listOf(0u, 1u) to 0,
listOf(2u, 0u, 1u) to -7,
listOf(0u, 0u, 0u, 4u) to 4,
)
) // true
println(
polynomial1 - polynomial6 == NumberedPolynomial(
listOf<UInt>() to -4,
listOf(0u, 1u) to 10,
listOf(2u, 0u, 1u) to -7,
listOf(0u, 0u, 0u, 4u) to -4,
)
) // true
polynomial1 * polynomial6 // Multiplication works too
}
Double.algebra.numberedPolynomialSpace {
// You can even write
val x_1: NumberedPolynomial<Double> = NumberedPolynomial(listOf(1u) to 1.0)
val x_2: NumberedPolynomial<Double> = NumberedPolynomial(listOf(0u, 1u) to 1.0)
val x_3: NumberedPolynomial<Double> = NumberedPolynomial(listOf(0u, 0u, 1u) to 1.0)
val polynomial7: NumberedPolynomial<Double> = NumberedPolynomial(
listOf<UInt>() to 3.0,
listOf(0u, 1u) to 5.0,
listOf(2u, 0u, 1u) to -7.0,
)
Double.algebra.listPolynomialSpace {
println(3 + 5 * x_2 - 7 * x_1 * x_1 * x_3 == polynomial7)
println(3.0 + 5.0 * x_2 - 7.0 * x_1 * x_1 * x_3 == polynomial7)
}
}
Int.algebra.numberedPolynomialSpace {
val x_4: NumberedPolynomial<Int> = NumberedPolynomial(listOf(0u, 0u, 0u, 4u) to 1)
// Also there are some utilities for polynomials:
println(polynomial1.substitute(mapOf(0 to 1, 1 to -2, 2 to -1)) == 0.asNumberedPolynomial()) // true,
// because it's substitution x_1 -> 1, x_2 -> -2, x_3 -> -1,
// so 3 + 5 x_2 - 7 x_1^2 x_3 = 3 + 5 * (-2) - 7 * 1^2 * (-1) = 3 - 10 + 7 = 0
println(
polynomial1.substitute(mapOf(1 to x_4)) == NumberedPolynomial(
listOf<UInt>() to 3,
listOf(0u, 1u) to 5,
listOf(2u, 0u, 1u) to -7,
)
) // true, because it's substitution x_2 -> x_4, so result is 3 + 5 x_4 - 7 x_1^2 x_3
println(
polynomial1.derivativeWithRespectTo(Int.algebra, 1) ==
NumberedPolynomial(listOf<UInt>() to 5)
) // true, d/dx_2 (3 + 5 x_2 - 7 x_1^2 x_3) = 5
}
// Lastly, there are rational functions and some other utilities:
Double.algebra.numberedRationalFunctionSpace {
val rationalFunction1: NumberedRationalFunction<Double> = NumberedRationalFunction(
NumberedPolynomial(
listOf<UInt>() to 2.0,
listOf(1u) to -3.0,
listOf(2u) to 1.0,
),
NumberedPolynomial(
listOf<UInt>() to 3.0,
listOf(1u) to -1.0,
)
)
// It's just (2 - 3x + x^2)/(3 - x) where x = x_1
val rationalFunction2: NumberedRationalFunction<Double> = NumberedRationalFunction(
NumberedPolynomial(
listOf<UInt>() to 5.0,
listOf(1u) to -4.0,
listOf(2u) to 1.0,
),
NumberedPolynomial(
listOf<UInt>() to 3.0,
listOf(1u) to -1.0,
)
)
// It's just (5 - 4x + x^2)/(3 - x) where x = x_1
println(rationalFunction1 + 1 == rationalFunction2)
}
}
/**
* Shows [LabeledPolynomial]s' and [LabeledRationalFunction]s' capabilities.
*/
fun labeledPolynomialsExample() {
val x by symbol
val y by symbol
val z by symbol
val t by symbol
// Consider polynomial
// 3 + 5 y - 7 x^2 z
// Consider, for example, its term -7 x^2 z. -7 is a coefficient of the term, whereas matching (x -> 2, z -> 3) is
// description of degrees of variables x_1, x_2, ... in the term. Such description is called "signature" of the
// term -7 x_1^2 x_3.
val polynomial1: LabeledPolynomial<Int>
with(Int.algebra) {
// [LabeledPolynomial] is a representation of a multivariate polynomial, that stores terms in a map with terms'
// signatures as the map's keys and terms' coefficients as corresponding values. For example,
polynomial1 = LabeledPolynomial(
mapOf(
mapOf<Symbol, UInt>() to 3,
mapOf(y to 1u) to 5,
mapOf(x to 2u, z to 1u) to -7,
)
)
// represents polynomial 3 + 5 y - 7 x^2 z
// This `LabeledPolynomial` function needs context of either ring of constant (as `Int.algebra` in this example)
// or space of LabeledPolynomials over it. To understand why it is like this see documentations of functions
// LabeledPolynomial and LabeledPolynomialWithoutCheck
// There are also shortcut fabrics:
val polynomial2: LabeledPolynomial<Int> = LabeledPolynomial(
mapOf<Symbol, UInt>() to 3,
mapOf(y to 1u) to 5,
mapOf(x to 2u, z to 1u) to -7,
)
println(polynomial1 == polynomial2) // true
// and even
val polynomial3: LabeledPolynomial<Int> = 57.asLabeledPolynomial() // This one actually does not algebraic context!
val polynomial4: LabeledPolynomial<Int> = LabeledPolynomial(mapOf<Symbol, UInt>() to 57)
println(polynomial3 == polynomial4) // true
labeledPolynomialSpace {
// Also there is DSL for constructing NumberedPolynomials:
val polynomial5: LabeledPolynomial<Int> = LabeledPolynomialDSL1 {
3 {}
5 { y inPowerOf 1u }
-7 with { x pow 2u; z pow 1u }
// `pow` and `inPowerOf` are the same
// `with` is omittable
}
println(polynomial1 == polynomial5) // true
// Unfortunately the DSL does not work good in bare context of constants' ring, so for now it's disabled and
// works only in NumberedPolynomialSpace and NumberedRationalFunctionSpace
}
}
val polynomial6: LabeledPolynomial<Int> = Int.algebra {
LabeledPolynomial(
mapOf<Symbol, UInt>() to 7,
mapOf(y to 1u) to -5,
mapOf(x to 2u, z to 1u) to 0,
mapOf(t to 4u) to 4,
)
}
// For every ring there can be provided a polynomial ring:
Int.algebra.labeledPolynomialSpace {
println(
-polynomial6 == LabeledPolynomial(
mapOf<Symbol, UInt>() to -7,
mapOf(y to 1u) to 5,
mapOf(x to 2u, z to 1u) to 0,
mapOf(t to 4u) to -4,
)
) // true
println(
polynomial1 + polynomial6 == LabeledPolynomial(
mapOf<Symbol, UInt>() to 10,
mapOf(y to 1u) to 0,
mapOf(x to 2u, z to 1u) to -7,
mapOf(t to 4u) to 4,
)
) // true
println(
polynomial1 - polynomial6 == LabeledPolynomial(
mapOf<Symbol, UInt>() to -4,
mapOf(y to 1u) to 10,
mapOf(x to 2u, z to 1u) to -7,
mapOf(t to 4u) to -4,
)
) // true
polynomial1 * polynomial6 // Multiplication works too
}
Double.algebra.labeledPolynomialSpace {
// You can even write
val polynomial7: LabeledPolynomial<Double> = LabeledPolynomial(
mapOf<Symbol, UInt>() to 3.0,
mapOf(y to 1u) to 5.0,
mapOf(x to 2u, z to 1u) to -7.0,
)
Double.algebra.listPolynomialSpace {
println(3 + 5 * y - 7 * x * x * z == polynomial7)
println(3.0 + 5.0 * y - 7.0 * x * x * z == polynomial7)
}
}
Int.algebra.labeledPolynomialSpace {
// Also there are some utilities for polynomials:
println(polynomial1.substitute(mapOf(x to 1, y to -2, z to -1)) == 0.asLabeledPolynomial()) // true,
// because it's substitution x -> 1, y -> -2, z -> -1,
// so 3 + 5 y - 7 x^2 z = 3 + 5 * (-2) - 7 * 1^2 * (-1) = 3 - 10 + 7 = 0
println(
polynomial1.substitute(mapOf(y to t.asPolynomial())) == LabeledPolynomial(
mapOf<Symbol, UInt>() to 3,
mapOf(t to 1u) to 5,
mapOf(x to 2u, z to 1u) to -7,
)
) // true, because it's substitution y -> t, so result is 3 + 5 t - 7 x^2 z
println(
polynomial1.derivativeWithRespectTo(Int.algebra, y) == LabeledPolynomial(mapOf<Symbol, UInt>() to 5)
) // true, d/dy (3 + 5 y - 7 x^2 z) = 5
}
// Lastly, there are rational functions and some other utilities:
Double.algebra.labeledRationalFunctionSpace {
val rationalFunction1: LabeledRationalFunction<Double> = LabeledRationalFunction(
LabeledPolynomial(
mapOf<Symbol, UInt>() to 2.0,
mapOf(x to 1u) to -3.0,
mapOf(x to 2u) to 1.0,
),
LabeledPolynomial(
mapOf<Symbol, UInt>() to 3.0,
mapOf(x to 1u) to -1.0,
)
)
// It's just (2 - 3x + x^2)/(3 - x)
val rationalFunction2: LabeledRationalFunction<Double> = LabeledRationalFunction(
LabeledPolynomial(
mapOf<Symbol, UInt>() to 5.0,
mapOf(x to 1u) to -4.0,
mapOf(x to 2u) to 1.0,
),
LabeledPolynomial(
mapOf<Symbol, UInt>() to 3.0,
mapOf(x to 1u) to -1.0,
)
)
// It's just (5 - 4x + x^2)/(3 - x)
println(rationalFunction1 + 1 == rationalFunction2)
}
}
fun main() {
println("ListPolynomials:")
listPolynomialsExample()
println()
println("NumberedPolynomials:")
numberedPolynomialsExample()
println()
println("ListPolynomials:")
labeledPolynomialsExample()
println()
}

View File

@ -99,6 +99,15 @@ public inline fun <T, R> Buffer<T>.fold(initial: R, operation: (acc: R, T) -> R)
return accumulator return accumulator
} }
/**
* Fold given buffer according to indexed [operation]
*/
public inline fun <T : Any, R> Buffer<T>.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]. * Zip two buffers using given [transform].
*/ */

View File

@ -6,10 +6,16 @@ plugins {
description = "Functions, integration and interpolation" description = "Functions, integration and interpolation"
kotlin.sourceSets.commonMain { kotlin.sourceSets {
commonMain {
dependencies { dependencies {
api(project(":kmath-core")) api(project(":kmath-core"))
} }
}
}
dependencies {
dokkaPlugin("org.jetbrains.dokka:mathjax-plugin:${npmlibs.versions.dokka.get()}")
} }
readme { readme {

View File

@ -3,130 +3,278 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress", "PARAMETER_NAME_CHANGED_ON_OVERRIDE")
package space.kscience.kmath.functions package space.kscience.kmath.functions
import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.* import space.kscience.kmath.operations.ScaleOperations
import kotlin.contracts.InvocationKind import space.kscience.kmath.operations.invoke
import kotlin.contracts.contract
import kotlin.math.max import kotlin.math.max
import kotlin.math.pow import kotlin.math.min
/** /**
* Polynomial coefficients model without fixation on specific context they are applied to. * Represents univariate polynomial that stores its coefficients in a [List].
* *
* @param coefficients constant is the leftmost coefficient. * @param C the type of constants.
*/ */
public class Polynomial<out T>(public val coefficients: List<T>) { public data class Polynomial<out C>(
/**
* List that contains coefficients of the polynomial.
*
* Every monomial \(a x^d\) is stored as a coefficient \(a\) placed
* into the list at index \(d\). For example, coefficients of a polynomial \(5 x^2 - 6\) can be represented as
* ```
* listOf(
* -6, // -6 +
* 0, // 0 x +
* 5, // 5 x^2
* )
* ```
* and also as
* ```
* listOf(
* -6, // -6 +
* 0, // 0 x +
* 5, // 5 x^2
* 0, // 0 x^3
* 0, // 0 x^4
* )
* ```
* It is not prohibited to put extra zeros at end of the list (as for \(0x^3\) and \(0x^4\) in the example). But the
* longer the coefficients list the worse performance of arithmetical operations performed on it. Thus, it is
* recommended not to put (or even to remove) extra (or useless) coefficients at the end of the coefficients list.
*
* @usesMathJax
*/
public val coefficients: List<C>
) {
override fun toString(): String = "Polynomial$coefficients" override fun toString(): String = "Polynomial$coefficients"
} }
/** /**
* Returns a [Polynomial] instance with given [coefficients]. * Arithmetic context for univariate polynomials with coefficients stored as a [List] constructed with the provided
*/ * [ring] of constants.
@Suppress("FunctionName")
public fun <T> Polynomial(vararg coefficients: T): Polynomial<T> = Polynomial(coefficients.toList())
/**
* Evaluates the value of the given double polynomial for given double argument.
*/
public fun Polynomial<Double>.value(arg: Double): Double = coefficients.reduceIndexed { index, acc, c ->
acc + c * arg.pow(index)
}
/**
* Evaluates the value of the given polynomial for given argument.
* https://en.wikipedia.org/wiki/Horner%27s_method
*/
public fun <T, C : Ring<T>> Polynomial<T>.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 <T, C : Ring<T>> Polynomial<T>.asFunction(ring: C): (T) -> T = { value(ring, it) }
/**
* Create a polynomial witch represents differentiated version of this polynomial
*/
@UnstableKMathAPI
public fun <T, A> Polynomial<T>.differentiate(
algebra: A,
): Polynomial<T> where A : Ring<T>, A : NumericAlgebra<T> = 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 <T, A> Polynomial<T>.integrate(
algebra: A,
): Polynomial<T> where A : Field<T>, A : NumericAlgebra<T> = 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 <T : Comparable<T>> Polynomial<T>.integrate(
algebra: Field<T>,
range: ClosedRange<T>,
): T = algebra {
val integral = integrate(algebra)
integral.value(algebra, range.endInclusive) - integral.value(algebra, range.start)
}
/**
* Space of polynomials.
* *
* @param T the type of operated polynomials. * @param C the type of constants. Polynomials have them a coefficients in their terms.
* @param C the intersection of [Ring] of [T] and [ScaleOperations] of [T]. * @param A type of provided underlying ring of constants. It's [Ring] of [C].
* @param ring the [C] instance. * @param ring underlying ring of constants of type [A].
*/ */
public class PolynomialSpace<T, C>( public open class PolynomialSpace<C, A>(
private val ring: C, /**
) : Group<Polynomial<T>>, ScaleOperations<Polynomial<T>> where C : Ring<T>, C : ScaleOperations<T> { * Underlying ring of constants. Its operations on constants are used by local operations on constants and polynomials.
override val zero: Polynomial<T> = Polynomial(emptyList()) */
public val ring: A,
override fun Polynomial<T>.unaryMinus(): Polynomial<T> = ring { ) : Ring<Polynomial<C>>, ScaleOperations<Polynomial<C>> where A : Ring<C>, A : ScaleOperations<C> {
Polynomial(coefficients.map { -it })
}
override fun add(left: Polynomial<T>, right: Polynomial<T>): Polynomial<T> {
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 }
})
}
}
override fun scale(a: Polynomial<T>, value: Double): Polynomial<T> =
ring { Polynomial(List(a.coefficients.size) { index -> a.coefficients[index] * value }) }
/** /**
* Evaluates the polynomial for the given value [arg]. * Instance of zero constant (zero of the underlying ring).
*/ */
public operator fun Polynomial<T>.invoke(arg: T): T = value(ring, arg) public val constantZero: C get() = ring.zero
/**
* Instance of unit constant (unit of the underlying ring).
*/
public val constantOne: C get() = ring.one
public fun Polynomial<T>.asFunction(): (T) -> T = asFunction(ring) /**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
public operator fun C.plus(other: Polynomial<C>): Polynomial<C> =
with(ring) {
with(other.coefficients) {
if (isEmpty()) Polynomial(listOf(this@plus))
else Polynomial(
toMutableList()
.apply {
val result = if (size == 0) this@plus else this@plus + get(0)
} if (size == 0) add(result)
else this[0] = result
public inline fun <T, C, R> C.polynomial(block: PolynomialSpace<T, C>.() -> R): R where C : Ring<T>, C : ScaleOperations<T> { }
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } )
return PolynomialSpace(this).block() }
}
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
public operator fun C.minus(other: Polynomial<C>): Polynomial<C> =
with(ring) {
with(other.coefficients) {
if (isEmpty()) Polynomial(listOf(this@minus))
else Polynomial(
toMutableList()
.apply {
(1..lastIndex).forEach { this[it] = -this[it] }
val result = if (size == 0) this@minus else this@minus - get(0)
if (size == 0) add(result)
else this[0] = result
}
)
}
}
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
public operator fun C.times(other: Polynomial<C>): Polynomial<C> =
with(ring) {
Polynomial(
other.coefficients
.toMutableList()
.apply {
for (deg in indices) this[deg] = this@times * this[deg]
}
)
}
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
public operator fun Polynomial<C>.plus(other: C): Polynomial<C> =
with(ring) {
with(coefficients) {
if (isEmpty()) Polynomial(listOf(other))
else Polynomial(
toMutableList()
.apply {
val result = if (size == 0) other else get(0) + other
if (size == 0) add(result)
else this[0] = result
}
)
}
}
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
public operator fun Polynomial<C>.minus(other: C): Polynomial<C> =
with(ring) {
with(coefficients) {
if (isEmpty()) Polynomial(listOf(-other))
else Polynomial(
toMutableList()
.apply {
val result = if (size == 0) other else get(0) - other
if (size == 0) add(result)
else this[0] = result
}
)
}
}
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
public operator fun Polynomial<C>.times(other: C): Polynomial<C> =
with(ring) {
Polynomial(
coefficients
.toMutableList()
.apply {
for (deg in indices) this[deg] = this[deg] * other
}
)
}
/**
* Converts the constant [value] to polynomial.
*/
public fun number(value: C): Polynomial<C> = Polynomial(listOf(value))
/**
* Converts the constant to polynomial.
*/
public fun C.asPolynomial(): Polynomial<C> = number(this)
/**
* Returns negation of the polynomial.
*/
public override operator fun Polynomial<C>.unaryMinus(): Polynomial<C> = ring {
Polynomial(coefficients.map { -it })
}
/**
* Returns sum of the polynomials.
*/
public override operator fun Polynomial<C>.plus(other: Polynomial<C>): Polynomial<C> = ring {
val thisDegree = degree
val otherDegree = other.degree
return Polynomial(
List(max(thisDegree, otherDegree) + 1) {
when {
it > thisDegree -> other.coefficients[it]
it > otherDegree -> coefficients[it]
else -> coefficients[it] + other.coefficients[it]
}
}
)
}
/**
* Returns difference of the polynomials.
*/
public override operator fun Polynomial<C>.minus(other: Polynomial<C>): Polynomial<C> = ring {
val thisDegree = degree
val otherDegree = other.degree
return Polynomial(
List(max(thisDegree, otherDegree) + 1) {
when {
it > thisDegree -> -other.coefficients[it]
it > otherDegree -> coefficients[it]
else -> coefficients[it] - other.coefficients[it]
}
}
)
}
/**
* Returns product of the polynomials.
*/
public override operator fun Polynomial<C>.times(other: Polynomial<C>): Polynomial<C> = ring {
val thisDegree = degree
val otherDegree = other.degree
return Polynomial(
List(thisDegree + otherDegree + 1) { d ->
(max(0, d - otherDegree)..min(thisDegree, d))
.map { coefficients[it] * other.coefficients[d - it] }
.reduce { acc, rational -> acc + rational }
}
)
}
/**
* Instance of zero polynomial (zero of the polynomial ring).
*/
override val zero: Polynomial<C> = Polynomial(emptyList())
/**
* Instance of unit polynomial (unit of the polynomial ring).
*/
override val one: Polynomial<C> by lazy { Polynomial(listOf(constantOne)) }
/**
* 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 Polynomial<C>.degree: Int get() = coefficients.lastIndex
override fun add(left: Polynomial<C>, right: Polynomial<C>): Polynomial<C> = left + right
override fun multiply(left: Polynomial<C>, right: Polynomial<C>): Polynomial<C> = left * right
override fun scale(a: Polynomial<C>, value: Double): Polynomial<C> =
ring { Polynomial(a.coefficients.map { scale(it, value) }) }
// 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].
*/
public inline fun Polynomial<C>.substitute(argument: C): C = value(ring, argument)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun Polynomial<C>.asFunction(): (C) -> C = asFunctionOver(ring)
/**
* Evaluates value of [this] polynomial on provided [argument].
*/
public inline operator fun Polynomial<C>.invoke(argument: C): C = value(ring, argument)
} }

View File

@ -0,0 +1,28 @@
/*
* 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
/**
* Constructs a [Polynomial] instance with provided [coefficients]. The collection of coefficients will be reversed
* if [reverse] parameter is true.
*/
@Suppress("FunctionName")
public fun <C> Polynomial(coefficients: List<C>, reverse: Boolean = false): Polynomial<C> =
Polynomial(with(coefficients) { if (reverse) reversed() else this })
/**
* Constructs a [Polynomial] instance with provided [coefficients]. The collection of coefficients will be reversed
* if [reverse] parameter is true.
*/
@Suppress("FunctionName")
public fun <C> Polynomial(vararg coefficients: C, reverse: Boolean = false): Polynomial<C> =
Polynomial(with(coefficients) { if (reverse) reversed() else toList() })
/**
* Represents [this] constant as a [Polynomial].
*/
public fun <C> C.asPolynomial() : Polynomial<C> = Polynomial(listOf(this))

View File

@ -0,0 +1,97 @@
/*
* 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.math.max
import kotlin.math.pow
/**
* Creates a [PolynomialSpace] over a received ring.
*/
public inline val <C, A> A.polynomialSpace: PolynomialSpace<C, A> where A : Ring<C>, A : ScaleOperations<C>
get() = PolynomialSpace(this)
/**
* Creates a [PolynomialSpace]'s scope over a received ring.
*/ // TODO: When context will be ready move [ListPolynomialSpace] and add [A] to context receivers of [block]
public inline fun <C, A, R> A.polynomialSpace(block: PolynomialSpace<C, A>.() -> R): R where A : Ring<C>, A : ScaleOperations<C> {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return PolynomialSpace(this).block()
}
/**
* Evaluates value of [this] Double polynomial on provided Double argument.
*/
public fun Polynomial<Double>.value(arg: Double): Double =
coefficients.reduceIndexedOrNull { index, acc, c ->
acc + c * arg.pow(index)
} ?: .0
/**
* Evaluates value of [this] polynomial on provided argument.
*
* It is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method).
*/
public fun <C> Polynomial<C>.value(ring: Ring<C>, arg: C): C = ring {
if (coefficients.isEmpty()) return zero
var result: C = coefficients.last()
for (j in coefficients.size - 2 downTo 0) {
result = (arg * result) + coefficients[j]
}
return result
}
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> Polynomial<C>.asFunctionOver(ring: A): (C) -> C = { value(ring, it) }
/**
* Returns algebraic derivative of received polynomial.
*/
@UnstableKMathAPI
public fun <C, A> Polynomial<C>.differentiate(
ring: A,
): Polynomial<C> where A : Ring<C>, A : NumericAlgebra<C> = ring {
Polynomial(
buildList(max(0, coefficients.size - 1)) {
for (deg in 1 .. coefficients.lastIndex) add(number(deg) * coefficients[deg])
}
)
}
/**
* Returns algebraic antiderivative of received polynomial.
*/
@UnstableKMathAPI
public fun <C, A> Polynomial<C>.integrate(
ring: A,
): Polynomial<C> where A : Field<C>, A : NumericAlgebra<C> = ring {
Polynomial(
buildList(coefficients.size + 1) {
add(zero)
coefficients.mapIndexedTo(this) { index, t -> t / number(index + 1) }
}
)
}
/**
* Computes a definite integral of [this] polynomial in the specified [range].
*/
@UnstableKMathAPI
public fun <C : Comparable<C>> Polynomial<C>.integrate(
ring: Field<C>,
range: ClosedRange<C>,
): C = ring {
val antiderivative = integrate(ring)
return antiderivative.value(ring, range.endInclusive) - antiderivative.value(ring, range.start)
}

View File

@ -3,15 +3,301 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@file:Suppress("LocalVariableName")
package space.kscience.kmath.functions package space.kscience.kmath.functions
import kotlin.test.Test import space.kscience.kmath.functions.testUtils.*
import kotlin.test.assertEquals import kotlin.test.*
class PolynomialTest { class PolynomialTest {
@Test @Test
fun testIntegration() { fun test_Polynomial_Constant_plus() {
val polynomial = Polynomial(1.0, -2.0, 1.0) RationalField.polynomialSpace {
assertEquals(0.0, polynomial.value(1.0), 0.001) assertEquals(
Polynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7)),
Polynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) + Rational(-3),
"test 1"
)
assertEquals(
Polynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
Polynomial(Rational(-2), Rational(0), Rational(0), Rational(0)) + Rational(2),
"test 2"
)
assertEquals(
Polynomial(Rational(0)),
Polynomial(Rational(-2)) + Rational(2),
"test 3"
)
assertEquals(
Polynomial(Rational(0)),
Polynomial<Rational>() + Rational(0),
"test 4"
)
assertEquals(
Polynomial(Rational(-1), Rational(0), Rational(0), Rational(0)),
Polynomial(Rational(-2), Rational(0), Rational(0), Rational(0)) + Rational(1),
"test 5"
)
assertEquals(
Polynomial(Rational(-1)),
Polynomial(Rational(-2)) + Rational(1),
"test 6"
)
assertEquals(
Polynomial(Rational(2)),
Polynomial<Rational>() + Rational(2),
"test 7"
)
}
}
@Test
fun test_Polynomial_Constant_minus() {
RationalField.polynomialSpace {
assertEquals(
Polynomial(Rational(32, 9), Rational(-8, 9), Rational(-8, 7)),
Polynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) - Rational(-3),
"test 1"
)
assertEquals(
Polynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
Polynomial(Rational(2), Rational(0), Rational(0), Rational(0)) - Rational(2),
"test 2"
)
assertEquals(
Polynomial(Rational(0)),
Polynomial(Rational(2)) - Rational(2),
"test 3"
)
assertEquals(
Polynomial(Rational(0)),
Polynomial<Rational>() - Rational(0),
"test 4"
)
assertEquals(
Polynomial(Rational(1), Rational(0), Rational(0), Rational(0)),
Polynomial(Rational(2), Rational(0), Rational(0), Rational(0)) - Rational(1),
"test 5"
)
assertEquals(
Polynomial(Rational(1)),
Polynomial(Rational(2)) - Rational(1),
"test 6"
)
assertEquals(
Polynomial(Rational(-2)),
Polynomial<Rational>() - Rational(2),
"test 7"
)
}
}
@Test
fun test_Polynomial_Constant_times() {
IntModuloRing(35).polynomialSpace {
assertEquals(
Polynomial(34, 2, 1, 20, 2),
Polynomial(22, 26, 13, 15, 26) * m(27),
"test 1"
)
assertEquals(
Polynomial(0, 0, 0, 0, 0),
Polynomial(7, 0, 49, 21, 14) * m(15),
"test 2"
)
}
}
@Test
fun test_Constant_Polynomial_plus() {
RationalField.polynomialSpace {
assertEquals(
Polynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7)),
Rational(-3) + Polynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)),
"test 1"
)
assertEquals(
Polynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
Rational(2) + Polynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 2"
)
assertEquals(
Polynomial(Rational(0)),
Rational(2) + Polynomial(Rational(-2)),
"test 3"
)
assertEquals(
Polynomial(Rational(0)),
Rational(0) + Polynomial(),
"test 4"
)
assertEquals(
Polynomial(Rational(-1), Rational(0), Rational(0), Rational(0)),
Rational(1) + Polynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 5"
)
assertEquals(
Polynomial(Rational(-1)),
Rational(1) + Polynomial(Rational(-2)),
"test 6"
)
assertEquals(
Polynomial(Rational(2)),
Rational(2) + Polynomial(),
"test 7"
)
}
}
@Test
fun test_Constant_Polynomial_minus() {
RationalField.polynomialSpace {
assertEquals(
Polynomial(Rational(32, 9), Rational(-8, 9), Rational(-8, 7)),
Rational(3) - Polynomial(Rational(-5, 9), Rational(8, 9), Rational(8, 7)),
"test 1"
)
assertEquals(
Polynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
Rational(-2) - Polynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 2"
)
assertEquals(
Polynomial(Rational(0)),
Rational(-2) - Polynomial(Rational(-2)),
"test 3"
)
assertEquals(
Polynomial(Rational(0)),
Rational(0) - Polynomial(),
"test 4"
)
assertEquals(
Polynomial(Rational(1), Rational(0), Rational(0), Rational(0)),
Rational(-1) - Polynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 5"
)
assertEquals(
Polynomial(Rational(1)),
Rational(-1) - Polynomial(Rational(-2)),
"test 6"
)
assertEquals(
Polynomial(Rational(-2)),
Rational(-2) - Polynomial(),
"test 7"
)
}
}
@Test
fun test_Constant_Polynomial_times() {
IntModuloRing(35).polynomialSpace {
assertEquals(
Polynomial(34, 2, 1, 20, 2),
m(27) * Polynomial(22, 26, 13, 15, 26),
"test 1"
)
assertEquals(
Polynomial(0, 0, 0, 0, 0),
m(15) * Polynomial(7, 0, 49, 21, 14),
"test 2"
)
}
}
@Test
fun test_Polynomial_unaryMinus() {
RationalField.polynomialSpace {
assertEquals(
Polynomial(Rational(-5, 9), Rational(8, 9), Rational(8, 7)),
-Polynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)),
"test 1"
)
assertEquals(
Polynomial(Rational(-5, 9), Rational(8, 9), Rational(8, 7), Rational(0), Rational(0)),
-Polynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7), Rational(0), Rational(0)),
"test 2"
)
}
}
@Test
fun test_Polynomial_Polynomial_plus() {
RationalField.polynomialSpace {
// (5/9 - 8/9 x - 8/7 x^2) + (-5/7 + 5/1 x + 5/8 x^2) ?= -10/63 + 37/9 x - 29/56 x^2
assertEquals(
Polynomial(Rational(-10, 63), Rational(37, 9), Rational(-29, 56)),
Polynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) +
Polynomial(Rational(-5, 7), Rational(5, 1), Rational(5, 8)),
"test 1"
)
// (-2/9 - 8/3 x) + (0 + 9/4 x + 2/4 x^2) ?= -2/9 - 5/12 x + 2/4 x^2
assertEquals(
Polynomial(Rational(-2, 9), Rational(-5, 12), Rational(2, 4)),
Polynomial(Rational(-2, 9), Rational(-8, 3)) +
Polynomial(Rational(0), Rational(9, 4), Rational(2, 4)),
"test 2"
)
// (-4/7 - 2/6 x + 0 x^2 + 0 x^3) + (-6/3 - 7/2 x + 2/3 x^2) ?= -18/7 - 23/6 x + 2/3 x^2
assertEquals(
Polynomial(Rational(-18, 7), Rational(-23, 6), Rational(2, 3), Rational(0)),
Polynomial(Rational(-4, 7), Rational(-2, 6), Rational(0), Rational(0)) +
Polynomial(Rational(-6, 3), Rational(-7, 2), Rational(2, 3)),
"test 3"
)
// (-2/4 - 6/9 x - 4/9 x^2) + (2/4 + 6/9 x + 4/9 x^2) ?= 0
assertEquals(
Polynomial(Rational(0), Rational(0), Rational(0)),
Polynomial(Rational(-2, 4), Rational(-6, 9), Rational(-4, 9)) +
Polynomial(Rational(2, 4), Rational(6, 9), Rational(4, 9)),
"test 4"
)
}
}
@Test
fun test_Polynomial_Polynomial_minus() {
RationalField.polynomialSpace {
// (5/9 - 8/9 x - 8/7 x^2) - (-5/7 + 5/1 x + 5/8 x^2) ?= 80/63 - 53/9 x - 99/56 x^2
assertEquals(
Polynomial(Rational(80, 63), Rational(-53, 9), Rational(-99, 56)),
Polynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) -
Polynomial(Rational(-5, 7), Rational(5, 1), Rational(5, 8)),
"test 1"
)
// (-2/9 - 8/3 x) - (0 + 9/4 x + 2/4 x^2) ?= -2/9 - 59/12 x - 2/4 x^2
assertEquals(
Polynomial(Rational(-2, 9), Rational(-59, 12), Rational(-2, 4)),
Polynomial(Rational(-2, 9), Rational(-8, 3)) -
Polynomial(Rational(0), Rational(9, 4), Rational(2, 4)),
"test 2"
)
// (-4/7 - 2/6 x + 0 x^2 + 0 x^3) - (-6/3 - 7/2 x + 2/3 x^2) ?= 10/7 + 19/6 x - 2/3 x^2
assertEquals(
Polynomial(Rational(10, 7), Rational(19, 6), Rational(-2, 3), Rational(0)),
Polynomial(Rational(-4, 7), Rational(-2, 6), Rational(0), Rational(0)) -
Polynomial(Rational(-6, 3), Rational(-7, 2), Rational(2, 3)),
"test 3"
)
// (-2/4 - 6/9 x - 4/9 x^2) - (-2/4 - 6/9 x - 4/9 x^2) ?= 0
assertEquals(
Polynomial(Rational(0), Rational(0), Rational(0)),
Polynomial(Rational(-2, 4), Rational(-6, 9), Rational(-4, 9)) -
Polynomial(Rational(-2, 4), Rational(-6, 9), Rational(-4, 9)),
"test 4"
)
}
}
@Test
fun test_Polynomial_Polynomial_times() {
IntModuloRing(35).polynomialSpace {
// (1 + x + x^2) * (1 - x + x^2) ?= 1 + x^2 + x^4
assertEquals(
Polynomial(1, 0, 1, 0, 1),
Polynomial(1, -1, 1) * Polynomial(1, 1, 1),
"test 1"
)
// Spoiler: 5 * 7 = 0
assertEquals(
Polynomial(0, 0, 0, 0, 0),
Polynomial(5, -25, 10) * Polynomial(21, 14, -7),
"test 2"
)
}
} }
} }

View File

@ -0,0 +1,134 @@
/*
* 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.testUtils.Rational
import space.kscience.kmath.functions.testUtils.RationalField
import space.kscience.kmath.misc.UnstableKMathAPI
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(UnstableKMathAPI::class)
class PolynomialUtilTest {
@Test
fun test_Polynomial_value_Double() {
assertEquals(
0.0,
Polynomial(1.0, -2.0, 1.0).value(1.0),
0.001,
"test 1"
)
assertEquals(
0.0,
Polynomial(1.0, -2.0, 1.0).value(1.0),
0.001,
"test 1"
)
assertEquals(
1.1931904761904761,
Polynomial(0.625, 2.6666666666666665, 0.5714285714285714, 1.5).value(0.2),
0.001,
"test 2"
)
assertEquals(
0.5681904761904762,
Polynomial(0.0, 2.6666666666666665, 0.5714285714285714, 1.5).value(0.2),
0.001,
"test 3"
)
assertEquals(
1.1811904761904761,
Polynomial(0.625, 2.6666666666666665, 0.5714285714285714, 0.0).value(0.2),
0.001,
"test 4"
)
assertEquals(
1.1703333333333332,
Polynomial(0.625, 2.6666666666666665, 0.0, 1.5).value(0.2),
0.001,
"test 5"
)
}
@Test
fun test_Polynomial_value_Constant() {
assertEquals(
Rational(0),
Polynomial(Rational(1), Rational(-2), Rational(1)).value(RationalField, Rational(1)),
"test 1"
)
assertEquals(
Rational(25057, 21000),
Polynomial(Rational(5, 8), Rational(8, 3), Rational(4, 7), Rational(3, 2))
.value(RationalField, Rational(1, 5)),
"test 2"
)
assertEquals(
Rational(2983, 5250),
Polynomial(Rational(0), Rational(8, 3), Rational(4, 7), Rational(3, 2))
.value(RationalField, Rational(1, 5)),
"test 3"
)
assertEquals(
Rational(4961, 4200),
Polynomial(Rational(5, 8), Rational(8, 3), Rational(4, 7), Rational(0))
.value(RationalField, Rational(1, 5)),
"test 4"
)
assertEquals(
Rational(3511, 3000),
Polynomial(Rational(5, 8), Rational(8, 3), Rational(0), Rational(3, 2))
.value(RationalField, Rational(1, 5)),
"test 5"
)
}
@Test
fun test_Polynomial_differentiate() {
assertEquals(
Polynomial(Rational(-2), Rational(2)),
Polynomial(Rational(1), Rational(-2), Rational(1)).differentiate(RationalField),
"test 1"
)
assertEquals(
Polynomial(Rational(-8, 3), Rational(8, 9), Rational(15, 7), Rational(-20, 9)),
Polynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).differentiate(RationalField),
"test 2"
)
assertEquals(
Polynomial(Rational(0), Rational(8, 9), Rational(15, 7), Rational(-20, 9)),
Polynomial(Rational(0), Rational(0), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).differentiate(RationalField),
"test 3"
)
assertEquals(
Polynomial(Rational(-8, 3), Rational(8, 9), Rational(15, 7), Rational(0)),
Polynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(0)).differentiate(RationalField),
"test 4"
)
}
@Test
fun test_Polynomial_integrate() {
assertEquals(
Polynomial(Rational(0), Rational(1), Rational(-1), Rational(1, 3)),
Polynomial(Rational(1), Rational(-2), Rational(1)).integrate(RationalField),
"test 1"
)
assertEquals(
Polynomial(Rational(0), Rational(1, 5), Rational(-4, 3), Rational(4, 27), Rational(5, 28), Rational(-1, 9)),
Polynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).integrate(RationalField),
"test 2"
)
assertEquals(
Polynomial(Rational(0), Rational(0), Rational(0), Rational(4, 27), Rational(5, 28), Rational(-1, 9)),
Polynomial(Rational(0), Rational(0), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).integrate(RationalField),
"test 3"
)
assertEquals(
Polynomial(Rational(0), Rational(1, 5), Rational(-4, 3), Rational(4, 27), Rational(5, 28), Rational(0)),
Polynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(0)).integrate(RationalField),
"test 4"
)
}
}

View File

@ -0,0 +1,136 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions.testUtils
import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.ScaleOperations
class IntModulo {
val residue: Int
val modulus: Int
@PublishedApi
internal constructor(residue: Int, modulus: Int, toCheckInput: Boolean = true) {
if (toCheckInput) {
require(modulus != 0) { "modulus can not be zero" }
this.modulus = if (modulus < 0) -modulus else modulus
this.residue = residue.mod(this.modulus)
} else {
this.residue = residue
this.modulus = modulus
}
}
constructor(residue: Int, modulus: Int) : this(residue, modulus, true)
operator fun unaryPlus(): IntModulo = this
operator fun unaryMinus(): IntModulo =
IntModulo(
if (residue == 0) 0 else modulus - residue,
modulus,
toCheckInput = false
)
operator fun plus(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not add two residue different modulo" }
return IntModulo(
(residue + other.residue) % modulus,
modulus,
toCheckInput = false
)
}
operator fun plus(other: Int): IntModulo =
IntModulo(
(residue + other) % modulus,
modulus,
toCheckInput = false
)
operator fun minus(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not subtract two residue different modulo" }
return IntModulo(
(residue - other.residue) % modulus,
modulus,
toCheckInput = false
)
}
operator fun minus(other: Int): IntModulo =
IntModulo(
(residue - other) % modulus,
modulus,
toCheckInput = false
)
operator fun times(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not multiply two residue different modulo" }
return IntModulo(
(residue * other.residue) % modulus,
modulus,
toCheckInput = false
)
}
operator fun times(other: Int): IntModulo =
IntModulo(
(residue * other) % modulus,
modulus,
toCheckInput = false
)
operator fun div(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not divide two residue different modulo" }
val (reciprocalCandidate, gcdOfOtherResidueAndModulus) = bezoutIdentityWithGCD(other.residue, modulus)
require(gcdOfOtherResidueAndModulus == 1) { "can not divide to residue that has non-trivial GCD with modulo" }
return IntModulo(
(residue * reciprocalCandidate) % modulus,
modulus,
toCheckInput = false
)
}
operator fun div(other: Int): IntModulo {
val (reciprocalCandidate, gcdOfOtherResidueAndModulus) = bezoutIdentityWithGCD(other, modulus)
require(gcdOfOtherResidueAndModulus == 1) { "can not divide to residue that has non-trivial GCD with modulo" }
return IntModulo(
(residue * reciprocalCandidate) % modulus,
modulus,
toCheckInput = false
)
}
override fun equals(other: Any?): Boolean =
when (other) {
is IntModulo -> residue == other.residue && modulus == other.modulus
else -> false
}
override fun hashCode(): Int = residue.hashCode()
override fun toString(): String = "$residue mod $modulus"
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE")
class IntModuloRing : Ring<IntModulo>, ScaleOperations<IntModulo> {
val modulus: Int
constructor(modulus: Int) {
require(modulus != 0) { "modulus can not be zero" }
this.modulus = if (modulus < 0) -modulus else modulus
}
override inline val zero: IntModulo get() = IntModulo(0, modulus, toCheckInput = false)
override inline val one: IntModulo get() = IntModulo(1, modulus, toCheckInput = false)
fun number(arg: Int): IntModulo = IntModulo(arg, modulus, toCheckInput = false)
override inline fun add(left: IntModulo, right: IntModulo): IntModulo = left + right
override inline fun multiply(left: IntModulo, right: IntModulo): IntModulo = left * right
override inline fun IntModulo.unaryMinus(): IntModulo = -this
override inline fun IntModulo.plus(arg: IntModulo): IntModulo = this + arg
override inline fun IntModulo.minus(arg: IntModulo): IntModulo = this - arg
override inline fun IntModulo.times(arg: IntModulo): IntModulo = this * arg
inline fun IntModulo.div(arg: IntModulo): IntModulo = this / arg
override fun scale(a: IntModulo, value: Double): IntModulo = a * value.toInt()
}

View File

@ -0,0 +1,18 @@
/*
* 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.testUtils
import space.kscience.kmath.functions.Polynomial
import space.kscience.kmath.functions.PolynomialSpace
fun PolynomialSpace<IntModulo, IntModuloRing>.Polynomial(vararg coefs: Int): Polynomial<IntModulo> =
Polynomial(coefs.map { IntModulo(it, ring.modulus) })
fun IntModuloRing.Polynomial(vararg coefs: Int): Polynomial<IntModulo> =
Polynomial(coefs.map { IntModulo(it, modulus) })
fun IntModuloRing.m(arg: Int): IntModulo = IntModulo(arg, modulus)
fun PolynomialSpace<IntModulo, IntModuloRing>.m(arg: Int): IntModulo = IntModulo(arg, ring.modulus)

View File

@ -0,0 +1,177 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions.testUtils
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.Field
import space.kscience.kmath.operations.NumbersAddOps
@Suppress("NAME_SHADOWING")
class Rational {
companion object {
val ZERO: Rational = Rational(0L)
val ONE: Rational = Rational(1L)
}
val numerator: Long
val denominator: Long
internal constructor(numerator: Long, denominator: Long, toCheckInput: Boolean = true) {
if (toCheckInput) {
if (denominator == 0L) throw ArithmeticException("/ by zero")
val greatestCommonDivider = gcd(numerator, denominator).let { if (denominator < 0L) -it else it }
this.numerator = numerator / greatestCommonDivider
this.denominator = denominator / greatestCommonDivider
} else {
this.numerator = numerator
this.denominator = denominator
}
}
constructor(numerator: Int, denominator: Int) : this(numerator.toLong(), denominator.toLong(), true)
constructor(numerator: Int, denominator: Long) : this(numerator.toLong(), denominator, true)
constructor(numerator: Long, denominator: Int) : this(numerator, denominator.toLong(), true)
constructor(numerator: Long, denominator: Long) : this(numerator, denominator, true)
constructor(numerator: Int) : this(numerator.toLong(), 1L, false)
constructor(numerator: Long) : this(numerator, 1L, false)
operator fun unaryPlus(): Rational = this
operator fun unaryMinus(): Rational = Rational(-this.numerator, this.denominator)
operator fun plus(other: Rational): Rational {
val denominatorsGcd = gcd(denominator, other.denominator)
val dividedThisDenominator = denominator / denominatorsGcd
val dividedOtherDenominator = other.denominator / denominatorsGcd
val numeratorCandidate = numerator * dividedOtherDenominator + dividedThisDenominator * other.numerator
val secondGcd = gcd(numeratorCandidate, denominatorsGcd)
return Rational(
numeratorCandidate / secondGcd,
dividedThisDenominator * (other.denominator / secondGcd),
toCheckInput = false
)
}
operator fun plus(other: Int): Rational =
Rational(
numerator + denominator * other.toLong(),
denominator,
toCheckInput = false
)
operator fun plus(other: Long): Rational =
Rational(
numerator + denominator * other,
denominator,
toCheckInput = false
)
operator fun minus(other: Rational): Rational {
val denominatorsGcd = gcd(denominator, other.denominator)
val dividedThisDenominator = denominator / denominatorsGcd
val dividedOtherDenominator = other.denominator / denominatorsGcd
val numeratorCandidate = numerator * dividedOtherDenominator - dividedThisDenominator * other.numerator
val secondGcd = gcd(numeratorCandidate, denominatorsGcd)
return Rational(
numeratorCandidate / secondGcd,
dividedThisDenominator * (other.denominator / secondGcd),
toCheckInput = false
)
}
operator fun minus(other: Int): Rational =
Rational(
numerator - denominator * other.toLong(),
denominator,
toCheckInput = false
)
operator fun minus(other: Long): Rational =
Rational(
numerator - denominator * other,
denominator,
toCheckInput = false
)
operator fun times(other: Rational): Rational {
val thisDenominatorAndOtherNumeratorGcd = gcd(denominator, other.numerator)
val otherDenominatorAndThisNumeratorGcd = gcd(other.denominator, numerator)
return Rational(
(numerator / otherDenominatorAndThisNumeratorGcd) * (other.numerator / thisDenominatorAndOtherNumeratorGcd),
(denominator / thisDenominatorAndOtherNumeratorGcd) * (other.denominator / otherDenominatorAndThisNumeratorGcd),
toCheckInput = false
)
}
operator fun times(other: Int): Rational {
val other = other.toLong()
val denominatorAndOtherGcd = gcd(denominator, other)
return Rational(
numerator * (other / denominatorAndOtherGcd),
denominator / denominatorAndOtherGcd,
toCheckInput = false
)
}
operator fun times(other: Long): Rational {
val denominatorAndOtherGcd = gcd(denominator, other)
return Rational(
numerator * (other / denominatorAndOtherGcd),
denominator / denominatorAndOtherGcd,
toCheckInput = false
)
}
operator fun div(other: Rational): Rational {
val denominatorsGcd = gcd(denominator, other.denominator)
val numeratorsGcd = gcd(numerator, other.numerator)
return Rational(
(numerator / numeratorsGcd) * (other.denominator / denominatorsGcd),
(denominator / denominatorsGcd) * (other.numerator / numeratorsGcd)
)
}
operator fun div(other: Int): Rational {
val other = other.toLong()
val numeratorAndOtherGcd = gcd(numerator, other)
return Rational(
numerator / numeratorAndOtherGcd,
denominator * (other / numeratorAndOtherGcd),
toCheckInput = false
)
}
operator fun div(other: Long): Rational {
val numeratorAndOtherGcd = gcd(numerator, other)
return Rational(
numerator / numeratorAndOtherGcd,
denominator * (other / numeratorAndOtherGcd),
toCheckInput = false
)
}
override fun equals(other: Any?): Boolean =
when (other) {
is Rational -> numerator == other.numerator && denominator == other.denominator
is Int -> numerator == other && denominator == 1L
is Long -> numerator == other && denominator == 1L
else -> false
}
override fun hashCode(): Int = 31 * numerator.hashCode() + denominator.hashCode()
override fun toString(): String = if (denominator == 1L) "$numerator" else "$numerator/$denominator"
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE")
@OptIn(UnstableKMathAPI::class)
object RationalField : Field<Rational>, NumbersAddOps<Rational> {
override inline val zero: Rational get() = Rational.ZERO
override inline val one: Rational get() = Rational.ONE
override inline fun number(value: Number): Rational = Rational(value.toLong())
override inline fun add(left: Rational, right: Rational): Rational = left + right
override inline fun multiply(left: Rational, right: Rational): Rational = left * right
override inline fun divide(left: Rational, right: Rational): Rational = left / right
override inline fun scale(a: Rational, value: Double): Rational = a * number(value)
override inline fun Rational.unaryMinus(): Rational = -this
override inline fun Rational.plus(arg: Rational): Rational = this + arg
override inline fun Rational.minus(arg: Rational): Rational = this - arg
override inline fun Rational.times(arg: Rational): Rational = this * arg
override inline fun Rational.div(arg: Rational): Rational = this / arg
}

View File

@ -0,0 +1,29 @@
/*
* 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.testUtils
import kotlin.math.abs
internal data class BezoutIdentityWithGCD<T>(val first: T, val second: T, val gcd: T)
internal tailrec fun gcd(a: Long, b: Long): Long = if (a == 0L) abs(b) else gcd(b % a, a)
internal fun bezoutIdentityWithGCD(a: Int, b: Int): BezoutIdentityWithGCD<Int> =
when {
a < 0 && b < 0 -> with(bezoutIdentityWithGCDInternalLogic(-a, -b, 1, 0, 0, 1)) { BezoutIdentityWithGCD(-first, -second, gcd) }
a < 0 -> with(bezoutIdentityWithGCDInternalLogic(-a, b, 1, 0, 0, 1)) { BezoutIdentityWithGCD(-first, second, gcd) }
b < 0 -> with(bezoutIdentityWithGCDInternalLogic(a, -b, 1, 0, 0, 1)) { BezoutIdentityWithGCD(first, -second, gcd) }
else -> bezoutIdentityWithGCDInternalLogic(a, b, 1, 0, 0, 1)
}
internal tailrec fun bezoutIdentityWithGCDInternalLogic(a: Int, b: Int, m1: Int, m2: Int, m3: Int, m4: Int): BezoutIdentityWithGCD<Int> =
if (b == 0) BezoutIdentityWithGCD(m1, m3, a)
else {
val quotient = a / b
val reminder = a % b
bezoutIdentityWithGCDInternalLogic(b, reminder, m2, m1 - quotient * m2, m4, m3 - quotient * m4)
}

View File

@ -0,0 +1,50 @@
# Module kmath-polynomial
Polynomials, rational functions, and utilities
## Features
- [polynomial abstraction](src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt) : Abstraction for polynomial spaces.
- [rational function abstraction](src/commonMain/kotlin/space/kscience/kmath/functions/RationalFunction.kt) : Abstraction for rational functions spaces.
- ["list" polynomials](src/commonMain/kotlin/space/kscience/kmath/functions/ListRationalFunction.kt) : List implementation of univariate polynomials.
- ["list" rational functions](src/commonMain/kotlin/space/kscience/kmath/functions/ListPolynomial.kt) : List implementation of univariate rational functions.
- ["list" polynomials and rational functions constructors](src/commonMain/kotlin/space/kscience/kmath/functions/listConstructors.kt) : Constructors for list polynomials and rational functions.
- ["list" polynomials and rational functions utilities](src/commonMain/kotlin/space/kscience/kmath/functions/listUtil.kt) : Utilities for list polynomials and rational functions.
- ["numbered" polynomials](src/commonMain/kotlin/space/kscience/kmath/functions/NumberedRationalFunction.kt) : Numbered implementation of multivariate polynomials.
- ["numbered" rational functions](src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt) : Numbered implementation of multivariate rational functions.
- ["numbered" polynomials and rational functions constructors](src/commonMain/kotlin/space/kscience/kmath/functions/numberedConstructors.kt) : Constructors for numbered polynomials and rational functions.
- ["numbered" polynomials and rational functions utilities](src/commonMain/kotlin/space/kscience/kmath/functions/numberedUtil.kt) : Utilities for numbered polynomials and rational functions.
- ["labeled" polynomials](src/commonMain/kotlin/space/kscience/kmath/functions/LabeledRationalFunction.kt) : Labeled implementation of multivariate polynomials.
- ["labeled" rational functions](src/commonMain/kotlin/space/kscience/kmath/functions/LabeledPolynomial.kt) : Labeled implementation of multivariate rational functions.
- ["labeled" polynomials and rational functions constructors](src/commonMain/kotlin/space/kscience/kmath/functions/labeledConstructors.kt) : Constructors for labeled polynomials and rational functions.
- ["labeled" polynomials and rational functions utilities](src/commonMain/kotlin/space/kscience/kmath/functions/labeledUtil.kt) : Utilities for labeled polynomials and rational functions.
## Usage
## Artifact:
The Maven coordinates of this project are `space.kscience:kmath-polynomial:0.3.1-dev-1`.
**Gradle Groovy:**
```groovy
repositories {
maven { url 'https://repo.kotlin.link' }
mavenCentral()
}
dependencies {
implementation 'space.kscience:kmath-polynomial:0.3.1-dev-1'
}
```
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("space.kscience:kmath-polynomial:0.3.1-dev-1")
}
```

View File

@ -0,0 +1,67 @@
plugins {
kotlin("multiplatform")
id("ru.mipt.npm.gradle.common")
id("ru.mipt.npm.gradle.native")
}
description = "Polynomials, rational functions, and utilities"
kotlin.sourceSets {
commonMain {
dependencies {
api(projects.kmathCore)
}
}
}
dependencies {
dokkaPlugin("org.jetbrains.dokka:mathjax-plugin:${npmlibs.versions.dokka.get()}")
}
readme {
maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE
propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md"))
feature("polynomial abstraction", "src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt") {
"Abstraction for polynomial spaces."
}
feature("rational function abstraction", "src/commonMain/kotlin/space/kscience/kmath/functions/RationalFunction.kt") {
"Abstraction for rational functions spaces."
}
feature("\"list\" polynomials", "src/commonMain/kotlin/space/kscience/kmath/functions/ListRationalFunction.kt") {
"List implementation of univariate polynomials."
}
feature("\"list\" rational functions", "src/commonMain/kotlin/space/kscience/kmath/functions/ListPolynomial.kt") {
"List implementation of univariate rational functions."
}
feature("\"list\" polynomials and rational functions constructors", "src/commonMain/kotlin/space/kscience/kmath/functions/listConstructors.kt") {
"Constructors for list polynomials and rational functions."
}
feature("\"list\" polynomials and rational functions utilities", "src/commonMain/kotlin/space/kscience/kmath/functions/listUtil.kt") {
"Utilities for list polynomials and rational functions."
}
feature("\"numbered\" polynomials", "src/commonMain/kotlin/space/kscience/kmath/functions/NumberedRationalFunction.kt") {
"Numbered implementation of multivariate polynomials."
}
feature("\"numbered\" rational functions", "src/commonMain/kotlin/space/kscience/kmath/functions/NumberedPolynomial.kt") {
"Numbered implementation of multivariate rational functions."
}
feature("\"numbered\" polynomials and rational functions constructors", "src/commonMain/kotlin/space/kscience/kmath/functions/numberedConstructors.kt") {
"Constructors for numbered polynomials and rational functions."
}
feature("\"numbered\" polynomials and rational functions utilities", "src/commonMain/kotlin/space/kscience/kmath/functions/numberedUtil.kt") {
"Utilities for numbered polynomials and rational functions."
}
feature("\"labeled\" polynomials", "src/commonMain/kotlin/space/kscience/kmath/functions/LabeledRationalFunction.kt") {
"Labeled implementation of multivariate polynomials."
}
feature("\"labeled\" rational functions", "src/commonMain/kotlin/space/kscience/kmath/functions/LabeledPolynomial.kt") {
"Labeled implementation of multivariate rational functions."
}
feature("\"labeled\" polynomials and rational functions constructors", "src/commonMain/kotlin/space/kscience/kmath/functions/labeledConstructors.kt") {
"Constructors for labeled polynomials and rational functions."
}
feature("\"labeled\" polynomials and rational functions utilities", "src/commonMain/kotlin/space/kscience/kmath/functions/labeledUtil.kt") {
"Utilities for labeled polynomials and rational functions."
}
}

View File

@ -0,0 +1,529 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.operations.Ring
import kotlin.jvm.JvmName
import kotlin.math.max
/**
* Represents multivariate polynomial that stores its coefficients in a [Map] and terms' signatures in a [Map] that
* associates variables (of type [Symbol]) with their degree.
*
* @param C the type of constants.
*/
public data class LabeledPolynomial<out C>
@PublishedApi
internal constructor(
/**
* Map that contains coefficients of the polynomial.
*
* Every monomial \(a x_1^{d_1} ... x_n^{d_n}\) is stored as a pair "key-value" in the map, where the value is the
* coefficient \(a\) and the key is a map that associates variables in the monomial with their degree in the monomial.
* For example, coefficients of a polynomial \(5 a^2 c^3 - 6 b\) can be represented as
* ```
* mapOf(
* mapOf(
* a to 2,
* c to 3
* ) to 5,
* mapOf(
* b to 1
* ) to (-6)
* )
* ```
* and also as
* ```
* mapOf(
* mapOf(
* a to 2,
* c to 3
* ) to 5,
* mapOf(
* b to 1
* ) to (-6),
* mapOf(
* b to 1,
* c to 1
* ) to 0
* )
* ```
* where \(a\), \(b\) and \(c\) are corresponding [Symbol] objects.
*
* It is not prohibited to put extra zero monomials into the map (as for \(0 b c\) 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.
* @usesMathJax
*/
public val coefficients: Map<Map<Symbol, UInt>, C>
) {
override fun toString(): String = "LabeledPolynomial$coefficients"
}
/**
* Arithmetic context for multivariate polynomials with coefficients stored as a [Map] and terms' signatures stored as a
* [Map] 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 LabeledPolynomialSpace<C, out A : Ring<C>>(
public override val ring: A,
) : MultivariatePolynomialSpace<C, Symbol, LabeledPolynomial<C>>, PolynomialSpaceOverRing<C, LabeledPolynomial<C>, A> {
/**
* Returns sum of the variable represented as a monic monomial and the integer represented as a constant polynomial.
*/
public override operator fun Symbol.plus(other: Int): LabeledPolynomial<C> =
if (other == 0) LabeledPolynomialAsIs(
mapOf(this@plus to 1U) to constantOne,
)
else LabeledPolynomialAsIs(
mapOf(this@plus to 1U) to constantOne,
emptyMap<Symbol, UInt>() to other.asConstant(),
)
/**
* Returns difference between the variable represented as a monic monomial and the integer represented as a constant polynomial.
*/
public override operator fun Symbol.minus(other: Int): LabeledPolynomial<C> =
if (other == 0) LabeledPolynomialAsIs(
mapOf(this@minus to 1U) to constantOne,
)
else LabeledPolynomialAsIs(
mapOf(this@minus to 1U) to constantOne,
emptyMap<Symbol, UInt>() to (-other).asConstant(),
)
/**
* Returns product of the variable represented as a monic monomial and the integer represented as a constant polynomial.
*/
public override operator fun Symbol.times(other: Int): LabeledPolynomial<C> =
if (other == 0) zero
else LabeledPolynomialAsIs(
mapOf(this to 1U) to other.asConstant(),
)
/**
* Returns sum of the integer represented as a constant polynomial and the variable represented as a monic monomial.
*/
public override operator fun Int.plus(other: Symbol): LabeledPolynomial<C> =
if (this == 0) LabeledPolynomialAsIs(
mapOf(other to 1U) to constantOne,
)
else LabeledPolynomialAsIs(
mapOf(other to 1U) to constantOne,
emptyMap<Symbol, UInt>() to this@plus.asConstant(),
)
/**
* Returns difference between the integer represented as a constant polynomial and the variable represented as a monic monomial.
*/
public override operator fun Int.minus(other: Symbol): LabeledPolynomial<C> =
if (this == 0) LabeledPolynomialAsIs(
mapOf(other to 1U) to -constantOne,
)
else LabeledPolynomialAsIs(
mapOf(other to 1U) to -constantOne,
emptyMap<Symbol, UInt>() to constantOne * this@minus,
)
/**
* Returns product of the integer represented as a constant polynomial and the variable represented as a monic monomial.
*/
public override operator fun Int.times(other: Symbol): LabeledPolynomial<C> =
if (this == 0) zero
else LabeledPolynomialAsIs(
mapOf(other to 1U) to this@times.asConstant(),
)
/**
* 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 LabeledPolynomial<C>.plus(other: Int): LabeledPolynomial<C> =
when {
other == 0 -> this
coefficients.isEmpty() -> other.asPolynomial()
else -> LabeledPolynomialAsIs(
coefficients.withPutOrChanged(emptyMap(), other.asConstant()) { it -> it + 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 LabeledPolynomial<C>.minus(other: Int): LabeledPolynomial<C> =
when {
other == 0 -> this
coefficients.isEmpty() -> other.asPolynomial()
else -> LabeledPolynomialAsIs(
coefficients.withPutOrChanged(emptyMap(), (-other).asConstant()) { it -> it - 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 LabeledPolynomial<C>.times(other: Int): LabeledPolynomial<C> =
when(other) {
0 -> zero
1 -> this
else -> LabeledPolynomialAsIs(
coefficients.mapValues { (_, value) -> value * 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: LabeledPolynomial<C>): LabeledPolynomial<C> =
when {
this == 0 -> other
other.coefficients.isEmpty() -> this@plus.asPolynomial()
else -> LabeledPolynomialAsIs(
other.coefficients.withPutOrChanged(emptyMap(), this@plus.asConstant()) { it -> this@plus + it }
)
}
/**
* 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: LabeledPolynomial<C>): LabeledPolynomial<C> =
when {
this == 0 -> -other
other.coefficients.isEmpty() -> this@minus.asPolynomial()
else -> LabeledPolynomialAsIs(
buildMap(other.coefficients.size + 1) {
put(emptyMap(), asConstant())
other.coefficients.copyMapToBy(this, { _, c -> -c }, { currentC, newC -> currentC - newC })
}
)
}
/**
* 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: LabeledPolynomial<C>): LabeledPolynomial<C> =
when(this) {
0 -> zero
1 -> other
else -> LabeledPolynomialAsIs(
other.coefficients.mapValues { (_, value) -> this@times * value }
)
}
/**
* Returns sum of the variable represented as a monic monomial and the constant represented as a constant polynomial.
*/
public override operator fun Symbol.plus(other: C): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(this@plus to 1U) to constantOne,
emptyMap<Symbol, UInt>() to other,
)
/**
* Returns difference between the variable represented as a monic monomial and the constant represented as a constant polynomial.
*/
public override operator fun Symbol.minus(other: C): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(this@minus to 1U) to constantOne,
emptyMap<Symbol, UInt>() to -other,
)
/**
* Returns product of the variable represented as a monic monomial and the constant represented as a constant polynomial.
*/
public override operator fun Symbol.times(other: C): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(this@times to 1U) to other,
)
/**
* Returns sum of the constant represented as a constant polynomial and the variable represented as a monic monomial.
*/
public override operator fun C.plus(other: Symbol): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(other to 1U) to constantOne,
emptyMap<Symbol, UInt>() to this@plus,
)
/**
* Returns difference between the constant represented as a constant polynomial and the variable represented as a monic monomial.
*/
public override operator fun C.minus(other: Symbol): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(other to 1U) to -constantOne,
emptyMap<Symbol, UInt>() to this@minus,
)
/**
* Returns product of the constant represented as a constant polynomial and the variable represented as a monic monomial.
*/
public override operator fun C.times(other: Symbol): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(other to 1U) to this@times,
)
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
override operator fun C.plus(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
if (other.coefficients.isEmpty()) this@plus.asLabeledPolynomial()
else LabeledPolynomialAsIs(
other.coefficients.withPutOrChanged(emptyMap(), this@plus) { it -> this@plus + it }
)
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
override operator fun C.minus(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
if (other.coefficients.isEmpty()) this@minus.asPolynomial()
else LabeledPolynomialAsIs(
buildMap(other.coefficients.size + 1) {
put(emptyMap(), this@minus)
other.coefficients.copyMapToBy(this, { _, c -> -c }, { currentC, newC -> currentC - newC })
}
)
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
override operator fun C.times(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
other.coefficients.mapValues { this@times * it.value }
)
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
override operator fun LabeledPolynomial<C>.plus(other: C): LabeledPolynomial<C> =
if (coefficients.isEmpty()) other.asLabeledPolynomial()
else LabeledPolynomialAsIs(
coefficients.withPutOrChanged(emptyMap(), other) { it -> it + other }
)
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
override operator fun LabeledPolynomial<C>.minus(other: C): LabeledPolynomial<C> =
if (coefficients.isEmpty()) other.asLabeledPolynomial()
else LabeledPolynomialAsIs(
coefficients.withPutOrChanged(emptyMap(), -other) { it -> it - other }
)
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
override operator fun LabeledPolynomial<C>.times(other: C): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
coefficients.mapValues { it.value * other }
)
/**
* Converts the constant [value] to polynomial.
*/
public override fun number(value: C): LabeledPolynomial<C> = value.asLabeledPolynomial()
/**
* Represents the variable as a monic monomial.
*/
public override operator fun Symbol.unaryPlus(): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(this to 1U) to constantOne,
)
/**
* Returns negation of representation of the variable as a monic monomial.
*/
public override operator fun Symbol.unaryMinus(): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mapOf(this to 1U) to -constantOne,
)
/**
* Returns sum of the variables represented as monic monomials.
*/
public override operator fun Symbol.plus(other: Symbol): LabeledPolynomial<C> =
if (this == other) LabeledPolynomialAsIs(
mapOf(this to 1U) to constantOne * 2
)
else LabeledPolynomialAsIs(
mapOf(this to 1U) to constantOne,
mapOf(other to 1U) to constantOne,
)
/**
* Returns difference between the variables represented as monic monomials.
*/
public override operator fun Symbol.minus(other: Symbol): LabeledPolynomial<C> =
if (this == other) zero
else LabeledPolynomialAsIs(
mapOf(this to 1U) to constantOne,
mapOf(other to 1U) to -constantOne,
)
/**
* Returns product of the variables represented as monic monomials.
*/
public override operator fun Symbol.times(other: Symbol): LabeledPolynomial<C> =
if (this == other) LabeledPolynomialAsIs(
mapOf(this to 2U) to constantOne
)
else LabeledPolynomialAsIs(
mapOf(this to 1U, other to 1U) to constantOne,
)
/**
* Returns sum of the variable represented as a monic monomial and the polynomial.
*/
public override operator fun Symbol.plus(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
if (other.coefficients.isEmpty()) this@plus.asPolynomial()
else LabeledPolynomialAsIs(
other.coefficients.withPutOrChanged(mapOf(this@plus to 1U), constantOne) { it -> constantOne + it }
)
/**
* Returns difference between the variable represented as a monic monomial and the polynomial.
*/
public override operator fun Symbol.minus(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
if (other.coefficients.isEmpty()) this@minus.asPolynomial()
else LabeledPolynomialAsIs(
buildMap(other.coefficients.size + 1) {
put(mapOf(this@minus to 1U), constantOne)
other.coefficients.copyMapToBy(this, { _, c -> -c }) { currentC, newC -> currentC - newC }
}
)
/**
* Returns product of the variable represented as a monic monomial and the polynomial.
*/
public override operator fun Symbol.times(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
other.coefficients
.mapKeys { (degs, _) -> degs.withPutOrChanged(this, 1u) { it -> it + 1u } }
)
/**
* Returns sum of the polynomial and the variable represented as a monic monomial.
*/
public override operator fun LabeledPolynomial<C>.plus(other: Symbol): LabeledPolynomial<C> =
if (coefficients.isEmpty()) other.asPolynomial()
else LabeledPolynomialAsIs(
coefficients.withPutOrChanged(mapOf(other to 1U), constantOne) { it -> it + constantOne }
)
/**
* Returns difference between the polynomial and the variable represented as a monic monomial.
*/
public override operator fun LabeledPolynomial<C>.minus(other: Symbol): LabeledPolynomial<C> =
if (coefficients.isEmpty()) other.asPolynomial()
else LabeledPolynomialAsIs(
coefficients.withPutOrChanged(mapOf(other to 1U), -constantOne) { it -> it - constantOne }
)
/**
* Returns product of the polynomial and the variable represented as a monic monomial.
*/
public override operator fun LabeledPolynomial<C>.times(other: Symbol): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
coefficients
.mapKeys { (degs, _) -> degs.withPutOrChanged(other, 1u) { it -> it + 1u } }
)
/**
* Returns negation of the polynomial.
*/
override fun LabeledPolynomial<C>.unaryMinus(): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
coefficients.mapValues { -it.value }
)
/**
* Returns sum of the polynomials.
*/
override operator fun LabeledPolynomial<C>.plus(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
mergeBy(coefficients, other.coefficients) { c1, c2 -> c1 + c2 }
)
/**
* Returns difference of the polynomials.
*/
override operator fun LabeledPolynomial<C>.minus(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
buildMap(coefficients.size + other.coefficients.size) {
coefficients.copyTo(this)
other.coefficients.copyMapToBy(this, { _, c -> -c }, { currentC, newC -> currentC - newC })
}
)
/**
* Returns product of the polynomials.
*/
override operator fun LabeledPolynomial<C>.times(other: LabeledPolynomial<C>): LabeledPolynomial<C> =
LabeledPolynomialAsIs(
buildMap(coefficients.size * other.coefficients.size) {
for ((degs1, c1) in coefficients) for ((degs2, c2) in other.coefficients) {
val degs = mergeBy(degs1, degs2) { deg1, deg2 -> deg1 + deg2 }
val c = c1 * c2
this.putOrChange(degs, c) { it -> it + c }
}
}
)
/**
* Instance of zero polynomial (zero of the polynomial ring).
*/
override val zero: LabeledPolynomial<C> = LabeledPolynomialAsIs()
/**
* Instance of unit polynomial (unit of the polynomial ring).
*/
override val one: LabeledPolynomial<C> = constantOne.asLabeledPolynomial()
/**
* 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<C>.degree: Int
get() = coefficients.entries.maxOfOrNull { (degs, _) -> 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 override val LabeledPolynomial<C>.degrees: Map<Symbol, UInt>
get() =
buildMap {
coefficients.keys.forEach { degs ->
degs.copyToBy(this, ::max)
}
}
/**
* Counts degree of the polynomial by the specified [variable].
*/
public override fun LabeledPolynomial<C>.degreeBy(variable: Symbol): UInt =
coefficients.entries.maxOfOrNull { (degs, _) -> degs.getOrElse(variable) { 0u } } ?: 0u
/**
* Counts degree of the polynomial by the specified [variables].
*/
public override fun LabeledPolynomial<C>.degreeBy(variables: Collection<Symbol>): UInt =
coefficients.entries.maxOfOrNull { (degs, _) -> degs.filterKeys { it in variables }.values.sum() } ?: 0u
/**
* Set of all variables that appear in the polynomial in positive exponents.
*/
public override val LabeledPolynomial<C>.variables: Set<Symbol>
get() =
buildSet {
coefficients.entries.forEach { (degs, _) -> addAll(degs.keys) }
}
/**
* Count of all variables that appear in the polynomial in positive exponents.
*/
public override val LabeledPolynomial<C>.countOfVariables: Int get() = variables.size
// 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.
*/
public inline fun LabeledPolynomial<C>.substitute(arguments: Map<Symbol, C>): LabeledPolynomial<C> = substitute(ring, arguments)
/**
* Substitutes provided arguments [arguments] into [this] polynomial.
*/
@JvmName("substitutePolynomial")
public inline fun LabeledPolynomial<C>.substitute(arguments: Map<Symbol, LabeledPolynomial<C>>) : LabeledPolynomial<C> = substitute(ring, arguments)
}

View File

@ -0,0 +1,92 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.operations.Ring
import kotlin.jvm.JvmName
/**
* Represents multivariate rational function that stores its numerator and denominator as [LabeledPolynomial]s.
*/
public data class LabeledRationalFunction<C>(
public override val numerator: LabeledPolynomial<C>,
public override val denominator: LabeledPolynomial<C>
) : RationalFunction<C, LabeledPolynomial<C>> {
override fun toString(): String = "LabeledRationalFunction${numerator.coefficients}/${denominator.coefficients}"
}
/**
* Arithmetic context for univariate rational functions with numerator and denominator represented as [LabeledPolynomial]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 LabeledRationalFunctionSpace<C, A: Ring<C>>(
public val ring: A,
) :
MultivariateRationalFunctionSpaceOverMultivariatePolynomialSpace<
C,
Symbol,
LabeledPolynomial<C>,
LabeledRationalFunction<C>,
LabeledPolynomialSpace<C, A>,
>,
MultivariatePolynomialSpaceOfFractions<
C,
Symbol,
LabeledPolynomial<C>,
LabeledRationalFunction<C>,
>() {
/**
* Underlying polynomial ring. Its polynomial operations are inherited by local polynomial operations.
*/
override val polynomialRing : LabeledPolynomialSpace<C, A> = LabeledPolynomialSpace(ring)
/**
* Constructor of rational functions (of type [LabeledRationalFunction]) from numerator and denominator (of type [LabeledPolynomial]).
*/
override fun constructRationalFunction(
numerator: LabeledPolynomial<C>,
denominator: LabeledPolynomial<C>
): LabeledRationalFunction<C> =
LabeledRationalFunction<C>(numerator, denominator)
// 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.
*/
public inline fun LabeledPolynomial<C>.substitute(argument: Map<Symbol, C>): LabeledPolynomial<C> = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] polynomial.
*/
@JvmName("substitutePolynomial")
public inline fun LabeledPolynomial<C>.substitute(argument: Map<Symbol, LabeledPolynomial<C>>): LabeledPolynomial<C> = substitute(ring, argument)
/**
* Substitutes provided rational function [argument] into [this] polynomial.
*/
@JvmName("substituteRationalFunction")
public inline fun LabeledPolynomial<C>.substitute(argument: Map<Symbol, LabeledRationalFunction<C>>): LabeledRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided constant [argument] into [this] rational function.
*/
public inline fun LabeledRationalFunction<C>.substitute(argument: Map<Symbol, C>): LabeledRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] rational function.
*/
@JvmName("substitutePolynomial")
public inline fun LabeledRationalFunction<C>.substitute(argument: Map<Symbol, LabeledPolynomial<C>>): LabeledRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided rational function [argument] into [this] rational function.
*/
@JvmName("substituteRationalFunction")
public inline fun LabeledRationalFunction<C>.substitute(argument: Map<Symbol, LabeledRationalFunction<C>>): LabeledRationalFunction<C> = substitute(ring, argument)
}

View File

@ -0,0 +1,374 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions
import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.ScaleOperations
import space.kscience.kmath.operations.invoke
import kotlin.math.max
import kotlin.math.min
/**
* Represents univariate polynomial that stores its coefficients in a [List].
*
* @param C the type of constants.
*/
public data class ListPolynomial<out C>(
/**
* List that contains coefficients of the polynomial.
*
* Every monomial \(a x^d\) is stored as a coefficient \(a\) placed
* into the list at index \(d\). For example, coefficients of a polynomial \(5 x^2 - 6\) can be represented as
* ```
* listOf(
* -6, // -6 +
* 0, // 0 x +
* 5, // 5 x^2
* )
* ```
* and also as
* ```
* listOf(
* -6, // -6 +
* 0, // 0 x +
* 5, // 5 x^2
* 0, // 0 x^3
* 0, // 0 x^4
* )
* ```
* It is not prohibited to put extra zeros at end of the list (as for \(0x^3\) and \(0x^4\) in the example). But the
* longer the coefficients list the worse performance of arithmetical operations performed on it. Thus, it is
* recommended not to put (or even to remove) extra (or useless) coefficients at the end of the coefficients list.
* @usesMathJax
*/
public val coefficients: List<C>
) {
override fun toString(): String = "ListPolynomial$coefficients"
}
/**
* 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].
* @param ring underlying ring of constants of type [A].
*/
public open class ListPolynomialSpace<C, out A : Ring<C>>(
public override val ring: A,
) : PolynomialSpaceOverRing<C, ListPolynomial<C>, 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 ListPolynomial<C>.plus(other: Int): ListPolynomial<C> =
if (other == 0) this
else
ListPolynomial(
coefficients
.toMutableList()
.apply {
val result = getOrElse(0) { constantZero } + other
if(size == 0) add(result)
else this[0] = result
}
)
/**
* 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 ListPolynomial<C>.minus(other: Int): ListPolynomial<C> =
if (other == 0) this
else
ListPolynomial(
coefficients
.toMutableList()
.apply {
val result = getOrElse(0) { constantZero } - other
if(size == 0) add(result)
else this[0] = result
}
)
/**
* 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 ListPolynomial<C>.times(other: Int): ListPolynomial<C> =
when (other) {
0 -> zero
1 -> this
else -> ListPolynomial(
coefficients.map { it * 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: ListPolynomial<C>): ListPolynomial<C> =
if (this == 0) other
else
ListPolynomial(
other.coefficients
.toMutableList()
.apply {
val result = this@plus + getOrElse(0) { constantZero }
if(size == 0) add(result)
else this[0] = result
}
)
/**
* 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: ListPolynomial<C>): ListPolynomial<C> =
ListPolynomial(
other.coefficients
.toMutableList()
.apply {
if (this@minus == 0) {
indices.forEach { this[it] = -this[it] }
} else {
(1..lastIndex).forEach { this[it] = -this[it] }
val result = this@minus - getOrElse(0) { constantZero }
if (size == 0) add(result)
else this[0] = result
}
}
)
/**
* 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: ListPolynomial<C>): ListPolynomial<C> =
when (this) {
0 -> zero
1 -> other
else -> ListPolynomial(
other.coefficients.map { this@times * it }
)
}
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
public override operator fun C.plus(other: ListPolynomial<C>): ListPolynomial<C> =
with(other.coefficients) {
if (isEmpty()) ListPolynomial(listOf(this@plus))
else ListPolynomial(
toMutableList()
.apply {
val result = if (size == 0) this@plus else this@plus + get(0)
if(size == 0) add(result)
else this[0] = result
}
)
}
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
public override operator fun C.minus(other: ListPolynomial<C>): ListPolynomial<C> =
with(other.coefficients) {
if (isEmpty()) ListPolynomial(listOf(this@minus))
else ListPolynomial(
toMutableList()
.apply {
(1 .. lastIndex).forEach { this[it] = -this[it] }
val result = if (size == 0) this@minus else this@minus - get(0)
if(size == 0) add(result)
else this[0] = result
}
)
}
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
public override operator fun C.times(other: ListPolynomial<C>): ListPolynomial<C> =
ListPolynomial(
other.coefficients.map { this@times * it }
)
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
public override operator fun ListPolynomial<C>.plus(other: C): ListPolynomial<C> =
with(coefficients) {
if (isEmpty()) ListPolynomial(listOf(other))
else ListPolynomial(
toMutableList()
.apply {
val result = if (size == 0) other else get(0) + other
if(size == 0) add(result)
else this[0] = result
}
)
}
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
public override operator fun ListPolynomial<C>.minus(other: C): ListPolynomial<C> =
with(coefficients) {
if (isEmpty()) ListPolynomial(listOf(-other))
else ListPolynomial(
toMutableList()
.apply {
val result = if (size == 0) other else get(0) - other
if(size == 0) add(result)
else this[0] = result
}
)
}
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
public override operator fun ListPolynomial<C>.times(other: C): ListPolynomial<C> =
ListPolynomial(
coefficients.map { it * other }
)
/**
* Converts the constant [value] to polynomial.
*/
public override fun number(value: C): ListPolynomial<C> = ListPolynomial(listOf(value))
/**
* Returns negation of the polynomial.
*/
public override operator fun ListPolynomial<C>.unaryMinus(): ListPolynomial<C> =
ListPolynomial(coefficients.map { -it })
/**
* Returns sum of the polynomials.
*/
public override operator fun ListPolynomial<C>.plus(other: ListPolynomial<C>): ListPolynomial<C> {
val thisDegree = degree
val otherDegree = other.degree
return ListPolynomial(
List(max(thisDegree, otherDegree) + 1) {
when {
it > thisDegree -> other.coefficients[it]
it > otherDegree -> coefficients[it]
else -> coefficients[it] + other.coefficients[it]
}
}
)
}
/**
* Returns difference of the polynomials.
*/
public override operator fun ListPolynomial<C>.minus(other: ListPolynomial<C>): ListPolynomial<C> {
val thisDegree = degree
val otherDegree = other.degree
return ListPolynomial(
List(max(thisDegree, otherDegree) + 1) {
when {
it > thisDegree -> -other.coefficients[it]
it > otherDegree -> coefficients[it]
else -> coefficients[it] - other.coefficients[it]
}
}
)
}
/**
* Returns product of the polynomials.
*/
public override operator fun ListPolynomial<C>.times(other: ListPolynomial<C>): ListPolynomial<C> {
val thisDegree = degree
val otherDegree = other.degree
return ListPolynomial(
List(thisDegree + otherDegree + 1) { d ->
(max(0, d - otherDegree)..min(thisDegree, d))
.map { coefficients[it] * other.coefficients[d - it] }
.reduce { acc, rational -> acc + rational }
}
)
}
/**
* Raises [arg] to the integer power [exponent].
*/ // TODO: To optimize boxing
override fun power(arg: ListPolynomial<C>, exponent: UInt): ListPolynomial<C> = super.power(arg, exponent)
/**
* Instance of zero polynomial (zero of the polynomial ring).
*/
override val zero: ListPolynomial<C> = ListPolynomial(emptyList())
/**
* Instance of unit polynomial (unit of the polynomial ring).
*/
override val one: ListPolynomial<C> by lazy { ListPolynomial(listOf(constantOne)) }
/**
* Degree of the polynomial, [see also](https://en.wikipedia.org/wiki/Degree_of_a_polynomial). If the polynomial is
* zero, degree is -1.
*/
public override val ListPolynomial<C>.degree: Int get() = coefficients.lastIndex
// 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].
*/
public inline fun ListPolynomial<C>.substitute(argument: C): C = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] polynomial.
*/
public inline fun ListPolynomial<C>.substitute(argument: ListPolynomial<C>): ListPolynomial<C> = substitute(ring, argument)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun ListPolynomial<C>.asFunction(): (C) -> C = asFunctionOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun ListPolynomial<C>.asFunctionOfConstant(): (C) -> C = asFunctionOfConstantOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun ListPolynomial<C>.asFunctionOfPolynomial(): (ListPolynomial<C>) -> ListPolynomial<C> = asFunctionOfPolynomialOver(ring)
/**
* Evaluates value of [this] polynomial on provided [argument].
*/
public inline operator fun ListPolynomial<C>.invoke(argument: C): C = substitute(ring, argument)
/**
* Evaluates value of [this] polynomial on provided [argument].
*/
public inline operator fun ListPolynomial<C>.invoke(argument: ListPolynomial<C>): ListPolynomial<C> = substitute(ring, argument)
}
/**
* 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 ScalableListPolynomialSpace<C, out A>(
ring: A,
) : ListPolynomialSpace<C, A>(ring), ScaleOperations<ListPolynomial<C>> where A : Ring<C>, A : ScaleOperations<C> {
override fun scale(a: ListPolynomial<C>, value: Double): ListPolynomial<C> =
ring { ListPolynomial(a.coefficients.map { scale(it, value) }) }
}

View File

@ -0,0 +1,123 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions
import space.kscience.kmath.operations.Ring
/**
* Represents univariate rational function that stores its numerator and denominator as [ListPolynomial]s.
*/
public data class ListRationalFunction<C>(
public override val numerator: ListPolynomial<C>,
public override val denominator: ListPolynomial<C>
) : RationalFunction<C, ListPolynomial<C>> {
override fun toString(): String = "ListRationalFunction${numerator.coefficients}/${denominator.coefficients}"
}
/**
* Arithmetic context for univariate rational functions with numerator and denominator represented as [ListPolynomial]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 ListRationalFunctionSpace<C, A : Ring<C>> (
public val ring: A,
) :
RationalFunctionSpaceOverPolynomialSpace<
C,
ListPolynomial<C>,
ListRationalFunction<C>,
ListPolynomialSpace<C, A>,
>,
PolynomialSpaceOfFractions<
C,
ListPolynomial<C>,
ListRationalFunction<C>,
>() {
/**
* Underlying polynomial ring. Its polynomial operations are inherited by local polynomial operations.
*/
override val polynomialRing : ListPolynomialSpace<C, A> = ListPolynomialSpace(ring)
/**
* Constructor of [ListRationalFunction] from numerator and denominator [ListPolynomial].
*/
override fun constructRationalFunction(numerator: ListPolynomial<C>, denominator: ListPolynomial<C>): ListRationalFunction<C> =
ListRationalFunction(numerator, denominator)
// 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.
*/
public inline fun ListPolynomial<C>.substitute(argument: C): C = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] polynomial.
*/
public inline fun ListPolynomial<C>.substitute(argument: ListPolynomial<C>): ListPolynomial<C> = substitute(ring, argument)
/**
* Substitutes provided rational function [argument] into [this] polynomial.
*/
public inline fun ListPolynomial<C>.substitute(argument: ListRationalFunction<C>): ListRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] rational function.
*/
public inline fun ListRationalFunction<C>.substitute(argument: ListPolynomial<C>): ListRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided rational function [argument] into [this] rational function.
*/
public inline fun ListRationalFunction<C>.substitute(argument: ListRationalFunction<C>): ListRationalFunction<C> = substitute(ring, argument)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun ListPolynomial<C>.asFunction(): (C) -> C = { substitute(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun ListPolynomial<C>.asFunctionOfConstant(): (C) -> C = { substitute(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun ListPolynomial<C>.asFunctionOfPolynomial(): (ListPolynomial<C>) -> ListPolynomial<C> = { substitute(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun ListPolynomial<C>.asFunctionOfRationalFunction(): (ListRationalFunction<C>) -> ListRationalFunction<C> = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public inline fun ListRationalFunction<C>.asFunctionOfPolynomial(): (ListPolynomial<C>) -> ListRationalFunction<C> = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public inline fun ListRationalFunction<C>.asFunctionOfRationalFunction(): (ListRationalFunction<C>) -> ListRationalFunction<C> = { substitute(ring, it) }
/**
* Evaluates value of [this] polynomial on provided argument.
*/
public inline operator fun ListPolynomial<C>.invoke(argument: C): C = substitute(ring, argument)
/**
* Evaluates value of [this] polynomial on provided argument.
*/
public inline operator fun ListPolynomial<C>.invoke(argument: ListPolynomial<C>): ListPolynomial<C> = substitute(ring, argument)
/**
* Evaluates value of [this] polynomial on provided argument.
*/
public inline operator fun ListPolynomial<C>.invoke(argument: ListRationalFunction<C>): ListRationalFunction<C> = substitute(ring, argument)
/**
* Evaluates value of [this] rational function on provided argument.
*/
public inline operator fun ListRationalFunction<C>.invoke(argument: ListPolynomial<C>): ListRationalFunction<C> = substitute(ring, argument)
/**
* Evaluates value of [this] rational function on provided argument.
*/
public inline operator fun ListRationalFunction<C>.invoke(argument: ListRationalFunction<C>): ListRationalFunction<C> = substitute(ring, argument)
}

View File

@ -0,0 +1,350 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
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 multivariate polynomial that stores its coefficients in a [Map] and terms' signatures in a [List].
*
* @param C the type of constants.
*/
public data class NumberedPolynomial<out C>
@PublishedApi
internal constructor(
/**
* Map that contains coefficients of the polynomial.
*
* Every monomial \(a x_1^{d_1} ... x_n^{d_n}\) is stored as a pair "key-value" in the map, where the value is the
* coefficient \(a\) and the key is a list that associates index of every variable in the monomial with their degree
* 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.
* @usesMathJax
*/
public val coefficients: Map<List<UInt>, C>
) {
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<C, out A : Ring<C>>(
public override val ring: A,
) : PolynomialSpaceOverRing<C, NumberedPolynomial<C>, 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<C>.plus(other: Int): NumberedPolynomial<C> =
if (other == 0) this
else NumberedPolynomialAsIs(
coefficients.withPutOrChanged(emptyList(), other.asConstant()) { it -> it + 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<C>.minus(other: Int): NumberedPolynomial<C> =
if (other == 0) this
else NumberedPolynomialAsIs(
coefficients.withPutOrChanged(emptyList(), (-other).asConstant()) { it -> it - 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<C>.times(other: Int): NumberedPolynomial<C> =
when (other) {
0 -> zero
1 -> this
else -> NumberedPolynomialAsIs(
coefficients.mapValues { it.value * 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<C>): NumberedPolynomial<C> =
if (this == 0) other
else NumberedPolynomialAsIs(
other.coefficients.withPutOrChanged(emptyList(), this@plus.asConstant()) { it -> this@plus + it }
)
/**
* 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<C>): NumberedPolynomial<C> =
when {
this == 0 -> -other
other.coefficients.isEmpty() -> this.asPolynomial()
else -> NumberedPolynomialAsIs(
buildMap(other.coefficients.size + 1) {
put(emptyList(), other.coefficients.computeOnOrElse(emptyList(), { this@minus.asConstant() }, { it -> this@minus - it}))
other.coefficients.copyMapToBy(this, { _, c -> -c }) { currentC, _ -> currentC }
}
)
}
/**
* 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<C>): NumberedPolynomial<C> =
when (this) {
0 -> zero
1 -> other
else -> NumberedPolynomialAsIs(
other.coefficients.mapValues { this@times * it.value }
)
}
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
override operator fun C.plus(other: NumberedPolynomial<C>): NumberedPolynomial<C> =
if (other.coefficients.isEmpty()) this@plus.asPolynomial()
else NumberedPolynomialAsIs(
other.coefficients.withPutOrChanged(emptyList(), this@plus) { it -> this@plus + it }
)
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
override operator fun C.minus(other: NumberedPolynomial<C>): NumberedPolynomial<C> =
if (other.coefficients.isEmpty()) this@minus.asPolynomial()
else NumberedPolynomialAsIs(
buildMap(other.coefficients.size) {
put(emptyList(), other.coefficients.computeOnOrElse(emptyList(), this@minus) { it -> this@minus - it })
other.coefficients.copyMapToBy(this, { _, c -> -c }, { currentC, _ -> currentC })
}
)
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
override operator fun C.times(other: NumberedPolynomial<C>): NumberedPolynomial<C> =
NumberedPolynomialAsIs(
other.coefficients.mapValues { this@times * it.value }
)
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
override operator fun NumberedPolynomial<C>.plus(other: C): NumberedPolynomial<C> =
if (coefficients.isEmpty()) other.asPolynomial()
else NumberedPolynomialAsIs(
coefficients.withPutOrChanged(emptyList(), other) { it -> it + other }
)
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
override operator fun NumberedPolynomial<C>.minus(other: C): NumberedPolynomial<C> =
if (coefficients.isEmpty()) other.asPolynomial()
else NumberedPolynomialAsIs(
coefficients.withPutOrChanged(emptyList(), -other) { it -> it - other }
)
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
override operator fun NumberedPolynomial<C>.times(other: C): NumberedPolynomial<C> =
NumberedPolynomialAsIs(
coefficients.mapValues { it.value * other }
)
/**
* Converts the constant [value] to polynomial.
*/
public override fun number(value: C): NumberedPolynomial<C> =
NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to value))
/**
* Returns negation of the polynomial.
*/
override fun NumberedPolynomial<C>.unaryMinus(): NumberedPolynomial<C> =
NumberedPolynomialAsIs(
coefficients.mapValues { -it.value }
)
/**
* Returns sum of the polynomials.
*/
override operator fun NumberedPolynomial<C>.plus(other: NumberedPolynomial<C>): NumberedPolynomial<C> =
NumberedPolynomialAsIs(
mergeBy(coefficients, other.coefficients) { c1, c2 -> c1 + c2 }
)
/**
* Returns difference of the polynomials.
*/
override operator fun NumberedPolynomial<C>.minus(other: NumberedPolynomial<C>): NumberedPolynomial<C> =
NumberedPolynomialAsIs(
buildMap(coefficients.size + other.coefficients.size) {
coefficients.copyTo(this)
other.coefficients.copyMapToBy(this, { _, c -> -c }, { currentC, newC -> currentC - newC })
}
)
/**
* Returns product of the polynomials.
*/
override operator fun NumberedPolynomial<C>.times(other: NumberedPolynomial<C>): NumberedPolynomial<C> =
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
putOrChange(degs, c) { it -> it + c }
}
}
)
/**
* Raises [arg] to the integer power [exponent].
*/ // TODO: To optimize boxing
override fun power(arg: NumberedPolynomial<C>, exponent: UInt): NumberedPolynomial<C> = super.power(arg, exponent)
/**
* Instance of zero polynomial (zero of the polynomial ring).
*/
override val zero: NumberedPolynomial<C> = NumberedPolynomialAsIs(emptyMap())
/**
* Instance of unit polynomial (unit of the polynomial ring).
*/
override val one: NumberedPolynomial<C> by lazy { NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to constantOne)) }
/**
* 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<C>.lastVariable: Int
get() = coefficients.keys.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<C>.degree: Int
get() = coefficients.keys.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<C>.degrees: List<UInt>
get() =
MutableList(lastVariable + 1) { 0u }.apply {
coefficients.keys.forEach { degs ->
degs.forEachIndexed { index, deg ->
this[index] = max(this[index], deg)
}
}
}
/**
* Counts degree of the polynomial by the specified [variable].
*/
public fun NumberedPolynomial<C>.degreeBy(variable: Int): UInt =
coefficients.keys.maxOfOrNull { degs -> degs.getOrElse(variable) { 0u } } ?: 0u
/**
* Counts degree of the polynomial by the specified [variables].
*/
public fun NumberedPolynomial<C>.degreeBy(variables: Collection<Int>): UInt =
coefficients.keys.maxOfOrNull { degs ->
degs.withIndex().fold(0u) { acc, (index, value) -> if (index in variables) acc + value else acc }
} ?: 0u
/**
* Count of variables occurring in the polynomial with positive power. If there is no such variable,
* the result is 0.
*/
public val NumberedPolynomial<C>.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.
*/
public inline fun NumberedPolynomial<C>.substitute(arguments: Map<Int, C>): NumberedPolynomial<C> = substitute(ring, arguments)
/**
* Substitutes provided arguments [arguments] into [this] polynomial.
*/
@JvmName("substitutePolynomial")
public inline fun NumberedPolynomial<C>.substitute(arguments: Map<Int, NumberedPolynomial<C>>) : NumberedPolynomial<C> = substitute(ring, arguments)
/**
* Substitutes provided arguments [arguments] into [this] polynomial.
*/
public inline fun NumberedPolynomial<C>.substitute(arguments: Buffer<C>): NumberedPolynomial<C> = substitute(ring, arguments)
/**
* Substitutes provided arguments [arguments] into [this] polynomial.
*/
@JvmName("substitutePolynomial")
public inline fun NumberedPolynomial<C>.substitute(arguments: Buffer<NumberedPolynomial<C>>) : NumberedPolynomial<C> = substitute(ring, arguments)
/**
* Substitutes provided arguments [arguments] into [this] polynomial.
*/
public inline fun NumberedPolynomial<C>.substituteFully(arguments: Buffer<C>): C = this.substituteFully(ring, arguments)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedPolynomial<C>.asFunction(): (Buffer<C>) -> C = asFunctionOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedPolynomial<C>.asFunctionOfConstant(): (Buffer<C>) -> C = asFunctionOfConstantOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedPolynomial<C>.asFunctionOfPolynomial(): (Buffer<NumberedPolynomial<C>>) -> NumberedPolynomial<C> = asFunctionOfPolynomialOver(ring)
/**
* Evaluates value of [this] polynomial on provided [arguments].
*/
public inline operator fun NumberedPolynomial<C>.invoke(arguments: Buffer<C>): C = substituteFully(ring, arguments)
/**
* Substitutes provided [arguments] into [this] polynomial.
*/
@JvmName("invokePolynomial")
public inline operator fun NumberedPolynomial<C>.invoke(arguments: Buffer<NumberedPolynomial<C>>): NumberedPolynomial<C> = substitute(ring, arguments)
}

View File

@ -0,0 +1,225 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
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 data class NumberedRationalFunction<C>(
public override val numerator: NumberedPolynomial<C>,
public override val denominator: NumberedPolynomial<C>
) : RationalFunction<C, NumberedPolynomial<C>> {
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<C, A: Ring<C>> (
public val ring: A,
) :
RationalFunctionSpaceOverPolynomialSpace<
C,
NumberedPolynomial<C>,
NumberedRationalFunction<C>,
NumberedPolynomialSpace<C, A>,
>,
PolynomialSpaceOfFractions<
C,
NumberedPolynomial<C>,
NumberedRationalFunction<C>,
>() {
/**
* Underlying polynomial ring. Its polynomial operations are inherited by local polynomial operations.
*/
public override val polynomialRing : NumberedPolynomialSpace<C, A> = NumberedPolynomialSpace(ring)
/**
* Constructor of rational functions (of type [NumberedRationalFunction]) from numerator and denominator (of type [NumberedPolynomial]).
*/
protected override fun constructRationalFunction(
numerator: NumberedPolynomial<C>,
denominator: NumberedPolynomial<C>
): NumberedRationalFunction<C> =
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<C>.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<C>.degrees: List<UInt> get() = polynomialRing { degrees }
/**
* Counts degree of the polynomial by the specified [variable].
*/
public fun NumberedPolynomial<C>.degreeBy(variable: Int): UInt = polynomialRing { degreeBy(variable) }
/**
* Counts degree of the polynomial by the specified [variables].
*/
public fun NumberedPolynomial<C>.degreeBy(variables: Collection<Int>): 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<C>.countOfVariables: Int get() = polynomialRing { countOfVariables }
/**
* Count of all variables that appear in the polynomial in positive exponents.
*/
public val NumberedRationalFunction<C>.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<C>.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.
*/
public inline fun NumberedPolynomial<C>.substitute(argument: Map<Int, C>): NumberedPolynomial<C> = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] polynomial.
*/
@JvmName("substitutePolynomial")
public inline fun NumberedPolynomial<C>.substitute(argument: Map<Int, NumberedPolynomial<C>>): NumberedPolynomial<C> = substitute(ring, argument)
/**
* Substitutes provided rational function [argument] into [this] polynomial.
*/
@JvmName("substituteRationalFunction")
public inline fun NumberedPolynomial<C>.substitute(argument: Map<Int, NumberedRationalFunction<C>>): NumberedRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided constant [argument] into [this] rational function.
*/
public inline fun NumberedRationalFunction<C>.substitute(argument: Map<Int, C>): NumberedRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] rational function.
*/
@JvmName("substitutePolynomial")
public inline fun NumberedRationalFunction<C>.substitute(argument: Map<Int, NumberedPolynomial<C>>): NumberedRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided rational function [argument] into [this] rational function.
*/
@JvmName("substituteRationalFunction")
public inline fun NumberedRationalFunction<C>.substitute(argument: Map<Int, NumberedRationalFunction<C>>): NumberedRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided constant [argument] into [this] polynomial.
*/
public inline fun NumberedPolynomial<C>.substitute(argument: Buffer<C>): NumberedPolynomial<C> = substitute(ring, argument)
/**
* Substitutes provided polynomial [argument] into [this] polynomial.
*/
@JvmName("substitutePolynomial")
public inline fun NumberedPolynomial<C>.substitute(argument: Buffer<NumberedPolynomial<C>>): NumberedPolynomial<C> = substitute(ring, argument)
/**
* Substitutes provided rational function [argument] into [this] polynomial.
*/
@JvmName("substituteRationalFunction")
public inline fun NumberedPolynomial<C>.substitute(argument: Buffer<NumberedRationalFunction<C>>): NumberedRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided constant [argument] into [this] rational function.
*/
public inline fun NumberedRationalFunction<C>.substitute(argument: Buffer<C>): NumberedRationalFunction<C> = substitute(ring, argument)
/**
* Substitutes provided polynomial [arguments] into [this] rational function.
*/
@JvmName("substitutePolynomial")
public inline fun NumberedRationalFunction<C>.substitute(arguments: Buffer<NumberedPolynomial<C>>): NumberedRationalFunction<C> = substitute(ring, arguments)
/**
* Substitutes provided rational function [arguments] into [this] rational function.
*/
@JvmName("substituteRationalFunction")
public inline fun NumberedRationalFunction<C>.substitute(arguments: Buffer<NumberedRationalFunction<C>>): NumberedRationalFunction<C> = substitute(ring, arguments)
/**
* Substitutes provided constant [arguments] into [this] polynomial.
*/
public inline fun NumberedPolynomial<C>.substituteFully(arguments: Buffer<C>): C = substituteFully(ring, arguments)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedPolynomial<C>.asFunction(): (Buffer<C>) -> C = asFunctionOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedPolynomial<C>.asFunctionOfConstant(): (Buffer<C>) -> C = asFunctionOfConstantOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedPolynomial<C>.asFunctionOfPolynomial(): (Buffer<NumberedPolynomial<C>>) -> NumberedPolynomial<C> = asFunctionOfPolynomialOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedPolynomial<C>.asFunctionOfRationalFunction(): (Buffer<NumberedRationalFunction<C>>) -> NumberedRationalFunction<C> = asFunctionOfRationalFunctionOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedRationalFunction<C>.asFunctionOfPolynomial(): (Buffer<NumberedPolynomial<C>>) -> NumberedRationalFunction<C> = asFunctionOfPolynomialOver(ring)
/**
* Represent [this] polynomial as a regular context-less function.
*/
public inline fun NumberedRationalFunction<C>.asFunctionOfRationalFunction(): (Buffer<NumberedRationalFunction<C>>) -> NumberedRationalFunction<C> = asFunctionOfRationalFunctionOver(ring)
/**
* Evaluates value of [this] polynomial on provided [arguments].
*/
public inline operator fun NumberedPolynomial<C>.invoke(arguments: Buffer<C>): C = substituteFully(ring, arguments)
/**
* Substitutes provided [arguments] into [this] polynomial.
*/
@JvmName("invokePolynomial")
public inline operator fun NumberedPolynomial<C>.invoke(arguments: Buffer<NumberedPolynomial<C>>): NumberedPolynomial<C> = substitute(ring, arguments)
/**
* Substitutes provided [arguments] into [this] polynomial.
*/
@JvmName("invokeRationalFunction")
public inline operator fun NumberedPolynomial<C>.invoke(arguments: Buffer<NumberedRationalFunction<C>>): NumberedRationalFunction<C> = substitute(ring, arguments)
/**
* Substitutes provided [arguments] into [this] rational function.
*/
@JvmName("invokePolynomial")
public inline operator fun NumberedRationalFunction<C>.invoke(arguments: Buffer<NumberedPolynomial<C>>): NumberedRationalFunction<C> = substitute(ring, arguments)
/**
* Substitutes provided [arguments] into [this] rational function.
*/
@JvmName("invokeRationalFunction")
public inline operator fun NumberedRationalFunction<C>.invoke(arguments: Buffer<NumberedRationalFunction<C>>): NumberedRationalFunction<C> = substitute(ring, arguments)
}

View File

@ -0,0 +1,527 @@
/*
* 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 kotlin.js.JsName
import kotlin.jvm.JvmName
/**
* 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 coefficients in their terms.
* @param P the type of polynomials.
*/
@Suppress("INAPPLICABLE_JVM_NAME", "PARAMETER_NAME_CHANGED_ON_OVERRIDE") // FIXME: Waiting for KT-31420
public interface PolynomialSpace<C, P> : Ring<P> {
/**
* Returns sum of the constant and the integer represented as a constant (member of underlying ring).
*
* The operation is equivalent to adding [other] copies of unit of underlying ring to [this].
*/
@JvmName("plusConstantInt")
public operator fun C.plus(other: Int): C
/**
* Returns difference between the constant and the integer represented as a constant (member of underlying ring).
*
* The operation is equivalent to subtraction [other] copies of unit of underlying ring from [this].
*/
@JvmName("minusConstantInt")
public operator fun C.minus(other: Int): C
/**
* Returns product of the constant and the integer represented as a constant (member of underlying ring).
*
* The operation is equivalent to sum of [other] copies of [this].
*/
@JvmName("timesConstantInt")
public operator fun C.times(other: Int): C
/**
* Returns sum of the integer represented as a constant (member of underlying ring) and the constant.
*
* The operation is equivalent to adding [this] copies of unit of underlying ring to [other].
*/
@JvmName("plusIntConstant")
public operator fun Int.plus(other: C): C
/**
* Returns difference between the integer represented as a constant (member of underlying ring) and the constant.
*
* The operation is equivalent to subtraction [this] copies of unit of underlying ring from [other].
*/
@JvmName("minusIntConstant")
public operator fun Int.minus(other: C): C
/**
* Returns product of the integer represented as a constant (member of underlying ring) and the constant.
*
* The operation is equivalent to sum of [this] copies of [other].
*/
@JvmName("timesIntConstant")
public operator fun Int.times(other: C): C
/**
* Converts the integer [value] to constant.
*/
public fun constantNumber(value: Int): C = constantOne * value
/**
* Converts the integer to constant.
*/
public fun Int.asConstant(): C = constantNumber(this)
/**
* 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 operator fun P.plus(other: Int): P = addMultipliedByDoubling(this, one, 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 operator fun P.minus(other: Int): P = addMultipliedByDoubling(this, one, -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 operator fun P.times(other: Int): P = multiplyByDoubling(this, 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 operator fun Int.plus(other: P): P = addMultipliedByDoubling(other, one, this)
/**
* 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 operator fun Int.minus(other: P): P = addMultipliedByDoubling(-other, one, this)
/**
* Returns product of the integer represented as a polynomial and the polynomial.
*
* The operation is equivalent to sum of [this] copies of [other].
*/
public operator fun Int.times(other: P): P = multiplyByDoubling(other, this)
/**
* Converts the integer [value] to polynomial.
*/
public fun number(value: Int): P = number(constantNumber(value))
/**
* Converts the integer to polynomial.
*/
public fun Int.asPolynomial(): P = number(this)
/**
* Returns the same constant.
*/
@JvmName("unaryPlusConstant")
@JsName("unaryPlusConstant")
public operator fun C.unaryPlus(): C = this
/**
* Returns negation of the constant.
*/
@JvmName("unaryMinusConstant")
@JsName("unaryMinusConstant")
public operator fun C.unaryMinus(): C
/**
* Returns sum of the constants.
*/
@JvmName("plusConstantConstant")
@JsName("plusConstantConstant")
public operator fun C.plus(other: C): C
/**
* Returns difference of the constants.
*/
@JvmName("minusConstantConstant")
@JsName("minusConstantConstant")
public operator fun C.minus(other: C): C
/**
* Returns product of the constants.
*/
@JvmName("timesConstantConstant")
@JsName("timesConstantConstant")
public operator fun C.times(other: C): C
/**
* Raises [arg] to the integer power [exponent].
*/
@JvmName("powerConstant")
@JsName("powerConstant")
public fun power(arg: C, exponent: UInt) : C
/**
* Instance of zero constant (zero of the underlying ring).
*/
public val constantZero: C
/**
* Instance of unit constant (unit of the underlying ring).
*/
public val constantOne: C
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
@JvmName("plusConstantPolynomial")
public operator fun C.plus(other: P): P
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
@JvmName("minusConstantPolynomial")
public operator fun C.minus(other: P): P
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
@JvmName("timesConstantPolynomial")
public operator fun C.times(other: P): P
/**
* Returns sum of the constant represented as a polynomial and the polynomial.
*/
@JvmName("plusPolynomialConstant")
public operator fun P.plus(other: C): P
/**
* Returns difference between the constant represented as a polynomial and the polynomial.
*/
@JvmName("minusPolynomialConstant")
public operator fun P.minus(other: C): P
/**
* Returns product of the constant represented as a polynomial and the polynomial.
*/
@JvmName("timesPolynomialConstant")
public operator fun P.times(other: C): P
/**
* Converts the constant [value] to polynomial.
*/
public fun number(value: C): P = one * value
/**
* Converts the constant to polynomial.
*/
public fun C.asPolynomial(): P = number(this)
/**
* 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
/**
* Raises [arg] to the integer power [exponent].
*/
public override fun power(arg: P, exponent: UInt) : P = exponentiateBySquaring(arg, exponent)
/**
* Instance of zero polynomial (zero of the polynomial ring).
*/
public override val zero: P
/**
* Instance of unit polynomial (unit of the polynomial ring).
*/
public override val one: P
/**
* 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
override fun add(left: P, right: P): P = left + right
override fun multiply(left: P, right: P): P = left * right
}
/**
* Abstraction of ring of polynomials of type [P] over ring of constants of type [C]. It also assumes that there is
* provided [ring] (of type [A]), that provides constant-wise operations.
*
* @param C the type of constants. Polynomials have them as coefficients in their terms.
* @param P the type of polynomials.
* @param A the type of algebraic structure (precisely, of ring) provided for constants.
*/
@Suppress("INAPPLICABLE_JVM_NAME") // FIXME: Waiting for KT-31420
public interface PolynomialSpaceOverRing<C, P, out A: Ring<C>> : PolynomialSpace<C, P> {
/**
* Underlying ring of constants. Its operations on constants are inherited by local operations on constants.
*/
public val ring: A
/**
* Returns sum of the constant and the integer represented as a constant (member of underlying ring).
*
* The operation is equivalent to adding [other] copies of unit of underlying ring to [this].
*/
@JvmName("plusConstantInt")
public override operator fun C.plus(other: Int): C = ring { addMultipliedByDoubling(this@plus, one, other) }
/**
* Returns difference between the constant and the integer represented as a constant (member of underlying ring).
*
* The operation is equivalent to subtraction [other] copies of unit of underlying ring from [this].
*/
@JvmName("minusConstantInt")
public override operator fun C.minus(other: Int): C = ring { addMultipliedByDoubling(this@minus, one, -other) }
/**
* Returns product of the constant and the integer represented as a constant (member of underlying ring).
*
* The operation is equivalent to sum of [other] copies of [this].
*/
@JvmName("timesConstantInt")
public override operator fun C.times(other: Int): C = ring { multiplyByDoubling(this@times, other) }
/**
* Returns sum of the integer represented as a constant (member of underlying ring) and the constant.
*
* The operation is equivalent to adding [this] copies of unit of underlying ring to [other].
*/
@JvmName("plusIntConstant")
public override operator fun Int.plus(other: C): C = ring { addMultipliedByDoubling(other, one, this@plus) }
/**
* Returns difference between the integer represented as a constant (member of underlying ring) and the constant.
*
* The operation is equivalent to subtraction [this] copies of unit of underlying ring from [other].
*/
@JvmName("minusIntConstant")
public override operator fun Int.minus(other: C): C = ring { addMultipliedByDoubling(-other, one, this@minus) }
/**
* Returns product of the integer represented as a constant (member of underlying ring) and the constant.
*
* The operation is equivalent to sum of [this] copies of [other].
*/
@JvmName("timesIntConstant")
public override operator fun Int.times(other: C): C = ring { multiplyByDoubling(other, this@times) }
/**
* Returns negation of the constant.
*/
@JvmName("unaryMinusConstant")
public override operator fun C.unaryMinus(): C = ring { -this@unaryMinus }
/**
* Returns sum of the constants.
*/
@JvmName("plusConstantConstant")
public override operator fun C.plus(other: C): C = ring { this@plus + other }
/**
* Returns difference of the constants.
*/
@JvmName("minusConstantConstant")
public override operator fun C.minus(other: C): C = ring { this@minus - other }
/**
* Returns product of the constants.
*/
@JvmName("timesConstantConstant")
public override operator fun C.times(other: C): C = ring { this@times * other }
/**
* Raises [arg] to the integer power [exponent].
*/
@JvmName("powerConstant")
override fun power(arg: C, exponent: UInt): C = ring { power(arg, exponent) }
/**
* Instance of zero constant (zero of the underlying ring).
*/
public override val constantZero: C get() = ring.zero
/**
* Instance of unit constant (unit of the underlying ring).
*/
public override val constantOne: C get() = ring.one
}
/**
* Abstraction of ring of polynomials of type [P] of variables of type [V] and over ring of constants of type [C].
*
* @param C the type of constants. Polynomials have them as coefficients in their terms.
* @param V the type of variables. Polynomials have them in representations of terms.
* @param P the type of polynomials.
*/
@Suppress("INAPPLICABLE_JVM_NAME") // FIXME: Waiting for KT-31420
public interface MultivariatePolynomialSpace<C, V, P>: PolynomialSpace<C, P> {
/**
* Returns sum of the variable represented as a monic monomial and the integer represented as a constant polynomial.
*/
@JvmName("plusVariableInt")
@JsName("plusVariableInt")
public operator fun V.plus(other: Int): P
/**
* Returns difference between the variable represented as a monic monomial and the integer represented as a constant polynomial.
*/
@JvmName("minusVariableInt")
@JsName("minusVariableInt")
public operator fun V.minus(other: Int): P
/**
* Returns product of the variable represented as a monic monomial and the integer represented as a constant polynomial.
*/
@JvmName("timesVariableInt")
@JsName("timesVariableInt")
public operator fun V.times(other: Int): P
/**
* Returns sum of the integer represented as a constant polynomial and the variable represented as a monic monomial.
*/
@JvmName("plusIntVariable")
@JsName("plusIntVariable")
public operator fun Int.plus(other: V): P
/**
* Returns difference between the integer represented as a constant polynomial and the variable represented as a monic monomial.
*/
@JvmName("minusIntVariable")
@JsName("minusIntVariable")
public operator fun Int.minus(other: V): P
/**
* Returns product of the integer represented as a constant polynomial and the variable represented as a monic monomial.
*/
@JvmName("timesIntVariable")
@JsName("timesIntVariable")
public operator fun Int.times(other: V): P
/**
* Returns sum of the variable represented as a monic monomial and the constant represented as a constant polynomial.
*/
@JvmName("plusVariableConstant")
@JsName("plusVariableConstant")
public operator fun V.plus(other: C): P
/**
* Returns difference between the variable represented as a monic monomial and the constant represented as a constant polynomial.
*/
@JvmName("minusVariableConstant")
@JsName("minusVariableConstant")
public operator fun V.minus(other: C): P
/**
* Returns product of the variable represented as a monic monomial and the constant represented as a constant polynomial.
*/
@JvmName("timesVariableConstant")
@JsName("timesVariableConstant")
public operator fun V.times(other: C): P
/**
* Returns sum of the constant represented as a constant polynomial and the variable represented as a monic monomial.
*/
@JvmName("plusConstantVariable")
@JsName("plusConstantVariable")
public operator fun C.plus(other: V): P
/**
* Returns difference between the constant represented as a constant polynomial and the variable represented as a monic monomial.
*/
@JvmName("minusConstantVariable")
@JsName("minusConstantVariable")
public operator fun C.minus(other: V): P
/**
* Returns product of the constant represented as a constant polynomial and the variable represented as a monic monomial.
*/
@JvmName("timesConstantVariable")
@JsName("timesConstantVariable")
public operator fun C.times(other: V): P
/**
* Represents the variable as a monic monomial.
*/
@JvmName("unaryPlusVariable")
public operator fun V.unaryPlus(): P
/**
* Returns negation of representation of the variable as a monic monomial.
*/
@JvmName("unaryMinusVariable")
public operator fun V.unaryMinus(): P
/**
* Returns sum of the variables represented as monic monomials.
*/
@JvmName("plusVariableVariable")
public operator fun V.plus(other: V): P
/**
* Returns difference between the variables represented as monic monomials.
*/
@JvmName("minusVariableVariable")
public operator fun V.minus(other: V): P
/**
* Returns product of the variables represented as monic monomials.
*/
@JvmName("timesVariableVariable")
public operator fun V.times(other: V): P
/**
* Represents the [variable] as a monic monomial.
*/
@JvmName("numberVariable")
public fun number(variable: V): P = +variable
/**
* Represents the variable as a monic monomial.
*/
@JvmName("asPolynomialVariable")
public fun V.asPolynomial(): P = number(this)
/**
* Returns sum of the variable represented as a monic monomial and the polynomial.
*/
@JvmName("plusVariablePolynomial")
public operator fun V.plus(other: P): P
/**
* Returns difference between the variable represented as a monic monomial and the polynomial.
*/
@JvmName("minusVariablePolynomial")
public operator fun V.minus(other: P): P
/**
* Returns product of the variable represented as a monic monomial and the polynomial.
*/
@JvmName("timesVariablePolynomial")
public operator fun V.times(other: P): P
/**
* Returns sum of the polynomial and the variable represented as a monic monomial.
*/
@JvmName("plusPolynomialVariable")
public operator fun P.plus(other: V): P
/**
* Returns difference between the polynomial and the variable represented as a monic monomial.
*/
@JvmName("minusPolynomialVariable")
public operator fun P.minus(other: V): P
/**
* Returns product of the polynomial and the variable represented as a monic monomial.
*/
@JvmName("timesPolynomialVariable")
public operator fun P.times(other: V): P
/**
* 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 P.degrees: Map<V, UInt>
/**
* Counts degree of the polynomial by the specified [variable].
*/
public fun P.degreeBy(variable: V): UInt = degrees.getOrElse(variable) { 0u }
/**
* Counts degree of the polynomial by the specified [variables].
*/
public fun P.degreeBy(variables: Collection<V>): UInt
/**
* Set of all variables that appear in the polynomial in positive exponents.
*/
public val P.variables: Set<V> get() = degrees.keys
/**
* Count of all variables that appear in the polynomial in positive exponents.
*/
public val P.countOfVariables: Int get() = variables.size
}

View File

@ -0,0 +1,146 @@
/*
* 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.*
// TODO: All of this should be moved to algebraic structures' place for utilities
// FIXME: Move receiver to context receiver
/**
* Returns product of [arg] and integer [multiplier].
*
* @param arg the multiplicand.
* @param multiplier the integer multiplier.
* @return product of the multiplicand [arg] and the multiplier [multiplier].
* @author Gleb Minaev
*/
internal fun <C> Group<C>.multiplyByDoubling(arg: C, multiplier: Int): C =
if (multiplier >= 0) multiplyByDoubling(arg, multiplier.toUInt())
else multiplyByDoubling(-arg, (-multiplier).toUInt())
// FIXME: Move receiver to context receiver
/**
* Adds product of [arg] and [multiplier] to [base].
*
* @param base the augend.
* @param arg the multiplicand.
* @param multiplier the integer multiplier.
* @return sum of the augend [base] and product of the multiplicand [arg] and the multiplier [multiplier].
* @author Gleb Minaev
*/
internal fun <C> GroupOps<C>.addMultipliedByDoubling(base: C, arg: C, multiplier: Int): C =
if (multiplier >= 0) addMultipliedByDoubling(base, arg, multiplier.toUInt())
else addMultipliedByDoubling(base, -arg, (-multiplier).toUInt())
// FIXME: Move receiver to context receiver
/**
* Returns product of [arg] and integer [multiplier].
*
* This is implementation of variation of [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
*
* @param arg the multiplicand.
* @param multiplier the integer multiplier.
* @return product of the multiplicand [arg] and the multiplier [multiplier].
* @author Gleb Minaev
*/
internal tailrec fun <C> Group<C>.multiplyByDoubling(arg: C, multiplier: UInt): C =
when {
multiplier == 0u -> zero
multiplier == 1u -> arg
multiplier and 1u == 0u -> multiplyByDoubling(arg + arg, multiplier shr 1)
multiplier and 1u == 1u -> addMultipliedByDoubling(arg, arg + arg, multiplier shr 1)
else -> error("Error in multiplication group instant by unsigned integer: got reminder by division by 2 different from 0 and 1")
}
// FIXME: Move receiver to context receiver
/**
* Adds product of [arg] and [multiplier] to [base].
*
* This is implementation of variation of [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
*
* @param base the augend.
* @param arg the multiplicand.
* @param multiplier the integer multiplier.
* @return sum of the augend [base] and product of the multiplicand [arg] and the multiplier [multiplier].
* @author Gleb Minaev
*/
internal tailrec fun <C> GroupOps<C>.addMultipliedByDoubling(base: C, arg: C, multiplier: UInt): C =
when {
multiplier == 0u -> base
multiplier == 1u -> base + arg
multiplier and 1u == 0u -> addMultipliedByDoubling(base, arg + arg, multiplier shr 1)
multiplier and 1u == 1u -> addMultipliedByDoubling(base + arg, arg + arg, multiplier shr 1)
else -> error("Error in multiplication group instant by unsigned integer: got reminder by division by 2 different from 0 and 1")
}
// FIXME: Move receiver to context receiver
/**
* Raises [arg] to the integer power [exponent].
*
* @param arg the base of the power.
* @param exponent the exponent of the power.
* @return [arg] raised to the power [exponent].
* @author Gleb Minaev
*/
internal fun <C> Field<C>.exponentiateBySquaring(arg: C, exponent: Int): C =
if (exponent >= 0) exponentiateBySquaring(arg, exponent.toUInt())
else exponentiateBySquaring(one / arg, (-exponent).toUInt())
// FIXME: Move receiver to context receiver
/**
* Multiplies [base] and [arg] raised to the integer power [exponent].
*
* @param base the multiplicand.
* @param arg the base of the power.
* @param exponent the exponent of the power.
* @return product of [base] and [arg] raised to the power [exponent].
* @author Gleb Minaev
*/
internal fun <C> Field<C>.multiplyExponentiatedBySquaring(base: C, arg: C, exponent: Int): C =
if (exponent >= 0) multiplyExponentiatedBySquaring(base, arg, exponent.toUInt())
else multiplyExponentiatedBySquaring(base, one / arg, (-exponent).toUInt())
// FIXME: Move receiver to context receiver
/**
* Raises [arg] to the integer power [exponent].
*
* This is implementation of variation of [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
*
* @param arg the base of the power.
* @param exponent the exponent of the power.
* @return [arg] raised to the power [exponent].
* @author Gleb Minaev
*/
internal tailrec fun <C> Ring<C>.exponentiateBySquaring(arg: C, exponent: UInt): C =
when {
exponent == 0u -> zero
exponent == 1u -> arg
exponent and 1u == 0u -> exponentiateBySquaring(arg * arg, exponent shr 1)
exponent and 1u == 1u -> multiplyExponentiatedBySquaring(arg, arg * arg, exponent shr 1)
else -> error("Error in multiplication group instant by unsigned integer: got reminder by division by 2 different from 0 and 1")
}
// FIXME: Move receiver to context receiver
/**
* Multiplies [base] and [arg] raised to the integer power [exponent].
*
* This is implementation of variation of [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
*
* @param base the multiplicand.
* @param arg the base of the power.
* @param exponent the exponent of the power.
* @return product of [base] and [arg] raised to the power [exponent].
* @author Gleb Minaev
*/
internal tailrec fun <C> RingOps<C>.multiplyExponentiatedBySquaring(base: C, arg: C, exponent: UInt): C =
when {
exponent == 0u -> base
exponent == 1u -> base * arg
exponent and 1u == 0u -> multiplyExponentiatedBySquaring(base, arg * arg, exponent shr 1)
exponent and 1u == 1u -> multiplyExponentiatedBySquaring(base * arg, arg * arg, exponent shr 1)
else -> error("Error in multiplication group instant by unsigned integer: got reminder by division by 2 different from 0 and 1")
}

View File

@ -0,0 +1,906 @@
/*
* 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 kotlin.contracts.InvocationKind.*
import kotlin.contracts.contract
/**
* Computes the given lambda [compute] on value corresponding to the provided [key] or `null` if the key is not present.
*
* @param key key which corresponding value will be used if it's present.
* @param compute lambda that is computed on the received value.
* @return result of the computation of the lambda.
*/
internal inline fun <K, V, R> Map<in K, V>.computeOn(key: K, compute: (V?) -> R): R {
contract {
callsInPlace(compute, EXACTLY_ONCE)
}
return compute(get(key))
}
/**
* Computes the given lambda [compute] on value corresponding to the provided [key] or computes the given lambda
* [defaultResult] if the key is not present.
*
* @param key key which corresponding value will be used if it's present.
* @param compute lambda that is computed on the value corresponding to the [key].
* @param defaultResult lambda that is computed if the [key] is not present.
* @return result of [compute] lambda if the [key] is present or result of [defaultResult] otherwise.
*/
internal inline fun <K, V, R> Map<K, V>.computeOnOrElse(key: K, defaultResult: () -> R, compute: (value: V) -> R): R {
contract {
callsInPlace(defaultResult, AT_MOST_ONCE)
callsInPlace(compute, AT_MOST_ONCE)
}
@Suppress("UNCHECKED_CAST")
return (if (key !in this) defaultResult() else compute(get(key) as V))
}
/**
* Computes the given lambda [compute] on value corresponding to the provided [key] or computes the given lambda
* [defaultResult] if the key is not present.
*
* @param key key which corresponding value will be used if it's present.
* @param compute lambda that is computed on the value corresponding to the [key].
* @param defaultResult default result that is returned in case of the [key]'s absence.
* @return result of [compute] lambda if the [key] is present or [defaultResult] otherwise.
*/
internal inline fun <K, V, R> Map<K, V>.computeOnOrElse(key: K, defaultResult: R, compute: (value: V) -> R): R {
contract {
callsInPlace(compute, AT_MOST_ONCE)
}
return computeOnOrElse(key, { defaultResult }, compute)
}
/**
* Computes the given lambda [compute] on value corresponding to the provided [key] or computes the given lambda
* [defaultResult] if the key is not present.
*
* @param key key which corresponding value will be used if it's present.
* @param compute lambda that is computed on the value corresponding to the [key].
* @param defaultResult default result that is returned in case of the [key]'s absence.
* @return result of [compute] lambda if the [key] is present or [defaultResult] otherwise.
*/
internal inline fun <K, V, R> Map<K, V>.computeOnOrElse(key: K, defaultResult: R, compute: (key: K, value: V) -> R): R {
contract {
callsInPlace(compute, AT_MOST_ONCE)
}
return computeOnOrElse(key, { defaultResult }, { it -> compute(key, it) })
}
/**
* Applies the [transformation][transform] to the value corresponding to the given [key] or null instead if it's not
* present.
*
* @param key key to check.
* @param transform transformation to apply.
* @return result of the transformation
*/
internal inline fun <K, V> MutableMap<in K, V>.applyToKey(key: K, transform: (currentValue: V?) -> V): V {
contract {
callsInPlace(transform, EXACTLY_ONCE)
}
return computeOn(key, transform).also { this[key] = it }
}
/**
* Depending on presence of value corresponding to the given [key] either puts new value calculated by [valueOnPut] or
* changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut lazily calculated value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* current value as a parameter.
* @return result value corresponding to the [key].
*/
internal inline fun <K, V> MutableMap<K, V>.putOrChange(key: K, valueOnPut: () -> V, transformOnChange: (currentValue: V) -> V): V {
contract {
callsInPlace(valueOnPut, AT_MOST_ONCE)
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return computeOnOrElse(key, valueOnPut, transformOnChange).also { this[key] = it }
}
/**
* Depending on presence of value corresponding to the given [key] either puts new value [valueOnPut] or
* changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* current value as a parameter.
* @return result value corresponding to the [key].
*/
internal inline fun <K, V> MutableMap<K, V>.putOrChange(key: K, valueOnPut: V, transformOnChange: (currentValue: V) -> V): V {
contract {
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return putOrChange<K, V>(key, { valueOnPut }, transformOnChange)
}
/**
* Depending on presence of value corresponding to the given [key] either puts new value [valueOnPut] or
* changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* current value and new value as parameters.
* @return result value corresponding to the [key].
*/
internal inline fun <K, V> MutableMap<K, V>.putOrChange(key: K, valueOnPut: V, transformOnChange: (currentValue: V, newValue: V) -> V): V {
contract {
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return putOrChange<K, V>(key, { valueOnPut }, { transformOnChange(it, valueOnPut) })
}
/**
* Depending on presence of value corresponding to the given [key] either puts new value [valueOnPut] or
* changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* the [key], current value, and new value as parameters.
* @return result value corresponding to the [key].
*/
internal inline fun <K, V> MutableMap<K, V>.putOrChange(key: K, valueOnPut: V, transformOnChange: (key: K, currentValue: V, newValue: V) -> V): V {
contract {
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return putOrChange<K, V>(key, { valueOnPut }, { transformOnChange(key, it, valueOnPut) })
}
/**
* Creates copy of [the map][this] and applies the [transformation][transform] to the value corresponding to the given
* [key] in the copy or null instead if it's not present.
*
* @param key key to check.
* @param transform transformation to apply.
* @return the copy of [the map][this].
*/
internal inline fun <K, V> Map<in K, V>.withAppliedToKey(key: K, transform: (currentValue: V?) -> V): Map<K, V> {
contract {
callsInPlace(transform, EXACTLY_ONCE)
}
return buildMap(size) {
putAll(this)
applyToKey(key, transform)
}
}
/**
* Creates copy of [the map][this] and depending on presence of value corresponding to the given [key] either puts new
* value calculated by [valueOnPut] or changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut lazily calculated value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* current value as a parameter.
* @return the copy of [the map][this].
*/
internal inline fun <K, V> Map<K, V>.withPutOrChanged(key: K, valueOnPut: () -> V, transformOnChange: (currentValue: V) -> V): Map<K, V> {
contract {
callsInPlace(valueOnPut, AT_MOST_ONCE)
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return buildMap(size + 1) {
putAll(this@withPutOrChanged)
putOrChange(key, valueOnPut, transformOnChange)
}
}
/**
* Creates copy of [the map][this] and depending on presence of value corresponding to the given [key] either puts new
* value [valueOnPut] or changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* current value as a parameter.
* @return the copy of [the map][this].
*/
internal inline fun <K, V> Map<K, V>.withPutOrChanged(key: K, valueOnPut: V, transformOnChange: (currentValue: V) -> V): Map<K, V> {
contract {
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return withPutOrChanged<K, V>(key, { valueOnPut }, transformOnChange)
}
/**
* Creates copy of [the map][this] and depending on presence of value corresponding to the given [key] either puts new
* value [valueOnPut] or changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* current value and new value as parameters.
* @return the copy of [the map][this].
*/
internal inline fun <K, V> Map<K, V>.withPutOrChanged(key: K, valueOnPut: V, transformOnChange: (currentValue: V, newValue: V) -> V): Map<K, V> {
contract {
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return withPutOrChanged<K, V>(key, { valueOnPut }, { transformOnChange(it, valueOnPut) })
}
/**
* Creates copy of [the map][this] and depending on presence of value corresponding to the given [key] either puts new
* value [valueOnPut] or changes the present value with [transformOnChange].
*
* @param key key to check.
* @param valueOnPut value to put in case of absence of the [key].
* @param transformOnChange transform to apply to current value corresponding to the [key] in case of its presence. Uses
* the [key], current value, and new value as parameters.
* @return the copy of [the map][this].
*/
internal inline fun <K, V> Map<K, V>.withPutOrChanged(key: K, valueOnPut: V, transformOnChange: (key: K, currentValue: V, newValue: V) -> V): Map<K, V> {
contract {
callsInPlace(transformOnChange, AT_MOST_ONCE)
}
return withPutOrChanged<K, V>(key, { valueOnPut }, { transformOnChange(key, it, valueOnPut) })
}
/**
* Copies entries of [this map][this] to the [destination] map overriding present ones if needed.
*
* @receiver map to be copied.
* @param destination map to receive copies.
* @return the [destination].
*/
internal fun <K, V, D: MutableMap<K, V>> Map<K, V>.copyTo(destination: D): D {
for ((key, value) in this) {
destination[key] = value
}
return destination
}
/**
* Copies entries of [this map][this] to the [destination] map merging present entries with new ones using [resolve]
* lambda.
*
* @receiver map to be copied.
* @param destination map to receive copies.
* @param resolve lambda function that resolves overriding. It takes a key, current value corresponding to the key, and
* a new one and returns value to associate to the key.
* @return the [destination].
*/
internal inline fun <K, V: W, W, D: MutableMap<K, W>> Map<out K, V>.copyToBy(destination: D, resolve: (key: K, currentValue: W, newValue: V) -> W): D {
for ((key, value) in this) {
destination.putOrChange(key, value) { it -> resolve(key, it, value) }
}
return destination
}
/**
* Copies entries of [this map][this] to the [destination] map merging present entries with new ones using [resolve]
* lambda.
*
* @receiver map to be copied.
* @param destination map to receive copies.
* @param resolve lambda function that resolves overriding. It takes current value corresponding to some key, and
* a new one and returns value to associate to the key.
* @return the [destination].
*/
internal inline fun <K, V: W, W, D: MutableMap<K, W>> Map<out K, V>.copyToBy(destination: D, resolve: (currentValue: W, newValue: V) -> W): D =
copyToBy(destination) { _, currentValue, newValue -> resolve(currentValue, newValue) }
/**
* Transforms values of entries of [this map][this] with [the given transformation][transform] and copies resulting
* entries to the [destination] map overriding present ones if needed. Is equivalent to
* ```kotlin
* this.mapValues(transform).copyTo(destination)
* ```
*
* @receiver map to be transformed and copied.
* @param destination map to receive copies.
* @param transform generates value of transformed entry using initial entry as an argument. Key of transformed entry is
* the same as initial entry.
* @return the [destination].
*/
internal inline fun <K, V, W, D: MutableMap<K, W>> Map<out K, V>.copyMapTo(destination: D, transform: (Map.Entry<K, V>) -> W): D {
for (entry in this) {
destination[entry.key] = transform(entry)
}
return destination
}
/**
* Transforms values of entries of [this map][this] with [the given transformation][transform] and copies resulting
* entries to the [destination] map overriding present ones if needed. Is equivalent to
* ```kotlin
* this.mapValues(transform).copyTo(destination)
* ```
*
* @receiver map to be transformed and copied.
* @param destination map to receive copies.
* @param transform generates value of transformed entry using initial entry as an argument. Key of transformed entry is
* the same as initial entry.
* @return the [destination].
*/
internal inline fun <K, V, W, D: MutableMap<K, W>> Map<out K, V>.copyMapTo(destination: D, transform: (key: K, value: V) -> W): D =
copyMapTo(destination) { (key, value) -> transform(key, value) }
/**
* Transforms values of entries of [this map][this] with [the given transformation][transform] and copies resulting
* entries to the [destination] map merging present entries with new ones using [resolve] lambda. Is equivalent to
* ```kotlin
* this.mapValues(transform).copyToBy(destination, resolve)
* ```
*
* @receiver map to be transformed and copied.
* @param destination map to receive copies.
* @param transform generates value of transformed entry using initial entry as an argument. Key of transformed entry is
* the same as initial entry.
* @param resolve lambda function that resolves overriding. It takes a key, current value corresponding to the key, and
* a new one and returns value to associate to the key.
* @return the [destination].
*/
internal inline fun <K, V, W, D: MutableMap<K, W>> Map<out K, V>.copyMapToBy(destination: D, transform: (Map.Entry<K, V>) -> W, resolve: (key: K, currentValue: W, newValue: V) -> W): D {
for (entry in this) {
val (key, value) = entry
destination.putOrChange(key, transform(entry)) { it -> resolve(key, it, value) }
}
return destination
}
/**
* Transforms values of entries of [this map][this] with [the given transformation][transform] and copies resulting
* entries to the [destination] map merging present entries with new ones using [resolve] lambda. Is equivalent to
* ```kotlin
* this.mapValues(transform).copyToBy(destination, resolve)
* ```
*
* @receiver map to be transformed and copied.
* @param destination map to receive copies.
* @param transform generates value of transformed entry using initial entry as an argument. Key of transformed entry is
* the same as initial entry.
* @param resolve lambda function that resolves overriding. It takes a key, current value corresponding to the key, and
* a new one and returns value to associate to the key.
* @return the [destination].
*/
internal inline fun <K, V, W, D: MutableMap<K, W>> Map<out K, V>.copyMapToBy(destination: D, transform: (key: K, value: V) -> W, resolve: (key: K, currentValue: W, newValue: V) -> W): D =
copyMapToBy(destination, { (key, value) -> transform(key, value) }, resolve)
/**
* Transforms values of entries of [this map][this] with [the given transformation][transform] and copies resulting
* entries to the [destination] map merging present entries with new ones using [resolve] lambda. Is equivalent to
* ```kotlin
* this.mapValues(transform).copyToBy(destination, resolve)
* ```
*
* @receiver map to be transformed and copied.
* @param destination map to receive copies.
* @param transform generates value of transformed entry using initial entry as an argument. Key of transformed entry is
* the same as initial entry.
* @param resolve lambda function that resolves overriding. It takes current value corresponding to some key, and
* a new one and returns value to associate to the key.
* @return the [destination].
*/
internal inline fun <K, V, W, D: MutableMap<K, W>> Map<out K, V>.copyMapToBy(destination: D, transform: (Map.Entry<K, V>) -> W, resolve: (currentValue: W, newValue: V) -> W): D =
copyMapToBy(destination, transform, { _, currentValue, newValue -> resolve(currentValue, newValue) })
/**
* Transforms values of entries of [this map][this] with [the given transformation][transform] and copies resulting
* entries to the [destination] map merging present entries with new ones using [resolve] lambda. Is equivalent to
* ```kotlin
* this.mapValues(transform).copyToBy(destination, resolve)
* ```
*
* @receiver map to be transformed and copied.
* @param destination map to receive copies.
* @param transform generates value of transformed entry using initial entry as an argument. Key of transformed entry is
* the same as initial entry.
* @param resolve lambda function that resolves overriding. It takes current value corresponding to some key, and
* a new one and returns value to associate to the key.
* @return the [destination].
*/
internal inline fun <K, V, W, D: MutableMap<K, W>> Map<out K, V>.copyMapToBy(destination: D, transform: (key: K, value: V) -> W, resolve: (currentValue: W, newValue: V) -> W): D =
copyMapToBy(destination, { (key, value) -> transform(key, value) }, { _, currentValue, newValue -> resolve(currentValue, newValue) })
/**
* Merges [the first map][map1] and [the second map][map2] prioritising the second one, puts result to the [destination]
* and returns the [destination].
*
* Precisely, corresponding keys and values of the received maps are put into the destination overriding existing values
* in the [destination] if needed. For every key appearing in both maps corresponding value from the second map is
* chosen.
*
* @param map1 the first (less prioritised) map to merge.
* @param map2 the second (more prioritised) map to merge.
* @param destination the map where result of the merge is put.
* @return the destination.
*/
internal fun <K, V, D: MutableMap<in K, in V>> mergeTo(map1: Map<out K, V>, map2: Map<out K, V>, destination: D): D {
for ((key, value) in map1) {
destination.put(key, value)
}
for ((key, value) in map2) {
destination.put(key, value)
}
return destination
}
/**
* Merges [the first map][map1] and [the second map][map2] resolving conflicts with [resolve] lambda, puts result to the
* [destination] and returns the [destination].
*
* Precisely, corresponding keys and values of the received maps are put into the destination overriding existing values
* in the [destination] if needed. For every key appearing in both maps corresponding value is a result of the [resolve]
* lambda calculated on the key and its corresponding values from the merged maps.
*
* @param map1 the first (less prioritised) map to merge.
* @param map2 the second (more prioritised) map to merge.
* @param resolve lambda function that resolves merge conflicts.
* @param destination the map where result of the merge is put.
* @return the destination.
*/
internal inline fun <K, V1: W, V2: W, W, D: MutableMap<K, W>> mergeToBy(map1: Map<out K, V1>, map2: Map<out K, V2>, destination: D, resolve: (key: K, value1: V1, value2: V2) -> W): D {
for (key in map2.keys) {
destination.remove(key)
}
for ((key, value) in map1) {
destination.put(key, value)
}
for ((key, value) in map2) {
@Suppress("UNCHECKED_CAST")
destination.putOrChange(key, value) { it -> resolve(key, it as V1, value) }
}
return destination
}
/**
* Merges [the first map][map1] and [the second map][map2] resolving conflicts with [resolve] lambda, puts result to the
* [destination] and returns the [destination].
*
* Precisely, corresponding keys and values of the received maps are put into the destination overriding existing values
* in the [destination] if needed. For every key appearing in both maps corresponding value is a result of the [resolve]
* lambda calculated on the key's corresponding values from the merged maps.
*
* @param map1 the first (less prioritised) map to merge.
* @param map2 the second (more prioritised) map to merge.
* @param resolve lambda function that resolves merge conflicts.
* @param destination the map where result of the merge is put.
* @return the destination.
*/
internal inline fun <K, V1: W, V2: W, W, D: MutableMap<K, W>> mergeToBy(map1: Map<K, V1>, map2: Map<K, V2>, destination: D, resolve: (value1: V1, value2: V2) -> W): D =
mergeToBy(map1, map2, destination) { _, value1, value2 -> resolve(value1, value2) }
/**
* Merges [the first map][map1] and [the second map][map2] prioritising the second one.
*
* Precisely, corresponding keys and values of the received maps are put into a new empty map which is returned after
* afterwards. For every key appearing in both maps corresponding value from the second map is chosen.
*
* @param map1 the first (less prioritised) map to merge.
* @param map2 the second (more prioritised) map to merge.
* @return the result of the merge.
*/
internal fun <K, V1: W, V2: W, W> merge(map1: Map<K, V1>, map2: Map<K, V2>): Map<K, W> {
val result = LinkedHashMap<K, W>(map1.size + map2.size)
return mergeTo(map1, map2, result)
}
/**
* Merges [the first map][map1] and [the second map][map2] resolving conflicts with [resolve] lambda.
*
* Precisely, corresponding keys and values of the received maps are put into a new empty map which is returned after
* afterwards. For every key appearing in both maps corresponding value is a result of the [resolve] lambda calculated
* on the key and its corresponding values from the merged maps.
*
* @param map1 the first (less prioritised) map to merge.
* @param map2 the second (more prioritised) map to merge.
* @param resolve lambda function that resolves merge conflicts.
* @return the result of the merge.
*/
internal inline fun <K, V1: W, V2: W, W> mergeBy(map1: Map<K, V1>, map2: Map<K, V2>, resolve: (key: K, value1: V1, value2: V2) -> W): Map<K, W> {
val result = LinkedHashMap<K, W>(map1.size + map2.size)
return mergeToBy(map1, map2, result, resolve)
}
/**
* Merges [the first map][map1] and [the second map][map2] resolving conflicts with [resolve] lambda.
*
* Precisely, corresponding keys and values of the received maps are put into a new empty map which is returned after
* afterwards. For every key appearing in both maps corresponding value is a result of the [resolve] lambda calculated
* on the key's corresponding values from the merged maps.
*
* @param map1 the first (less prioritised) map to merge.
* @param map2 the second (more prioritised) map to merge.
* @param resolve lambda function that resolves merge conflicts.
* @return the result of the merge.
*/
internal inline fun <K, V1: W, V2: W, W> mergeBy(map1: Map<K, V1>, map2: Map<K, V2>, resolve: (value1: V1, value2: V2) -> W): Map<K, W> =
mergeBy(map1, map2) { _, value1, value2 -> resolve(value1, value2) }
/**
* Populates the [destination] map with key-value pairs provided by [transform] function applied to each element of the
* given collection resolving conflicts with [resolve] function and returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each element to key-value.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <T, K, V, D : MutableMap<K, V>> Iterable<T>.associateTo(destination: D, transform: (T) -> Pair<K, V>, resolve: (key: K, currentValue: V, newValue: V) -> V): D {
for (element in this) {
val (key, value) = transform(element)
destination.putOrChange(key, value, resolve)
}
return destination
}
/**
* Populates the [destination] map with key-value pairs, where key is provided by [keySelector] function and value is
* provided by [valueTransform] applied to each element of the given collection, resolving conflicts with [resolve]
* function and returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param valueTransform lambda functions that generates value for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <T, K, V, D : MutableMap<K, V>> Iterable<T>.associateByTo(destination: D, keySelector: (T) -> K, valueTransform: (T) -> V, resolve: (key: K, currentValue: V, newValue: V) -> V): D {
for (element in this) {
val key = keySelector(element)
val value = valueTransform(element)
destination.putOrChange(key, value, resolve)
}
return destination
}
/**
* Populates the [destination] map with key-value pairs, where key is provided by [keySelector] function applied to each
* element of the given collection and value is the element itself, resolving conflicts with [resolve] function and
* returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <T, K, D : MutableMap<K, T>> Iterable<T>.associateByTo(destination: D, keySelector: (T) -> K, resolve: (key: K, currentValue: T, newValue: T) -> T): D {
for (element in this) {
val key = keySelector(element)
destination.putOrChange(key, element, resolve)
}
return destination
}
/**
* Populates the [destination] map with key-value pairs provided by [transform] function applied to each element of the
* given collection resolving conflicts with [resolve] function and returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each element to key-value pair.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <T, K, V, D : MutableMap<K, V>> Iterable<T>.associateTo(destination: D, transform: (T) -> Pair<K, V>, resolve: (currentValue: V, newValue: V) -> V): D =
associateTo(destination, transform) { _, currentValue, newValue -> resolve(currentValue, newValue) }
/**
* Populates the [destination] map with key-value pairs, where key is provided by [keySelector] function and value is
* provided by [valueTransform] applied to each element of the given collection, resolving conflicts with [resolve]
* function and returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param valueTransform lambda functions that generates value for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <T, K, V, D : MutableMap<K, V>> Iterable<T>.associateByTo(destination: D, keySelector: (T) -> K, valueTransform: (T) -> V, resolve: (currentValue: V, newValue: V) -> V): D =
associateByTo(destination, keySelector, valueTransform) { _, currentValue, newValue -> resolve(currentValue, newValue) }
/**
* Populates the [destination] map with key-value pairs, where key is provided by [keySelector] function applied to each
* element of the given collection and value is the element itself, resolving conflicts with [resolve] function and
* returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <T, K, D : MutableMap<K, T>> Iterable<T>.associateByTo(destination: D, keySelector: (T) -> K, resolve: (currentValue: T, newValue: T) -> T): D =
associateByTo(destination, keySelector) { _, currentValue, newValue -> resolve(currentValue, newValue) }
/**
* Returns a map containing key-value pairs provided by [transform] function applied to elements of the given collection.
*
* All pairs are added in order of iteration. If some key is already added to the map, adding new key-value pair with the
* key is resolved with [resolve] function which takes the key, current value corresponding to the key, and new value
* from the pair.
*
* @param transform function which transforms each element to key-value pair.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <T, K, V> Iterable<T>.associate(transform: (T) -> Pair<K, V>, resolve: (key: K, currentValue: V, newValue: V) -> V): Map<K, V> =
associateTo(LinkedHashMap(), transform, resolve)
/**
* Returns a map containing the values provided by [valueTransform] and indexed by [keySelector] functions applied to
* elements of the given collection.
*
* All pairs are added in order of iteration. If some key is already added to the map, adding new key-value pair with
* the key is resolved with [resolve] function which takes the key, current value corresponding to the key, and new
* value from the pair.
*
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param valueTransform lambda functions that generates value for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <T, K, V> Iterable<T>.associateBy(keySelector: (T) -> K, valueTransform: (T) -> V, resolve: (key: K, currentValue: V, newValue: V) -> V): Map<K, V> =
associateByTo(LinkedHashMap(), keySelector, valueTransform, resolve)
/**
* Returns a map containing the elements from the given collection indexed by the key returned from [keySelector]
* function applied to each element.
*
* All pairs are added in order of iteration. If some key is already added to the map, adding new key-value pair with
* the key is resolved with [resolve] function which takes the key, current value corresponding to the key, and new
* value from the pair.
*
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <T, K> Iterable<T>.associateBy(keySelector: (T) -> K, resolve: (key: K, currentValue: T, newValue: T) -> T): Map<K, T> =
associateByTo(LinkedHashMap(), keySelector, resolve)
/**
* Returns a map containing key-value pairs provided by [transform] function applied to elements of the given collection.
*
* All pairs are added in order of iteration. If some key is already added to the map, adding new key-value pair with
* the key is resolved with [resolve] function which takes current value corresponding to the key and new value from the
* pair.
*
* @param transform function which transforms each element to key-value pair.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <T, K, V> Iterable<T>.associate(transform: (T) -> Pair<K, V>, resolve: (currentValue: V, newValue: V) -> V): Map<K, V> =
associateTo(LinkedHashMap(), transform, resolve)
/**
* Returns a map containing the values provided by [valueTransform] and indexed by [keySelector] functions applied to
* elements of the given collection.
*
* All pairs are added in order of iteration. If some key is already added to the map, adding new key-value pair with
* the key is resolved with [resolve] function which takes current value corresponding to the key and new value from the
* pair.
*
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param valueTransform lambda functions that generates value for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <T, K, V> Iterable<T>.associateBy(keySelector: (T) -> K, valueTransform: (T) -> V, resolve: (currentValue: V, newValue: V) -> V): Map<K, V> =
associateByTo(LinkedHashMap(), keySelector, valueTransform, resolve)
/**
* Returns a map containing the elements from the given collection indexed by the key returned from [keySelector]
* function applied to each element.
*
* All pairs are added in order of iteration. If some key is already added to the map, adding new key-value pair with
* the key is resolved with [resolve] function which takes current value corresponding to the key and new value from the
* pair.
*
* @param keySelector lambda functions that generates keys for the key-value pairs.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <T, K> Iterable<T>.associateBy(keySelector: (T) -> K, resolve: (currentValue: T, newValue: T) -> T): Map<K, T> =
associateByTo(LinkedHashMap(), keySelector, resolve)
/**
* Populates the given [destination] map with entries having the keys of this map and the values obtained
* by applying the [transform] function to each entry in this map resolving conflicts with [resolve] function and
* returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new value.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <K, V, W, D : MutableMap<K, W>> Map<out K, V>.mapValuesTo(destination: D, transform: (Map.Entry<K, V>) -> W, resolve: (key: K, currentValue: W, newValue: W) -> W): D =
entries.associateByTo(destination, { it.key }, transform, resolve)
/**
* Populates the given [destination] map with entries having the keys of this map and the values obtained
* by applying the [transform] function to each entry in this map resolving conflicts with [resolve] function and
* returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new value.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <K, V, W, D : MutableMap<K, W>> Map<out K, V>.mapValuesTo(destination: D, transform: (key: K, value: V) -> W, resolve: (key: K, currentValue: W, newValue: W) -> W): D =
entries.associateByTo(destination, { it.key }, { (key, value) -> transform(key, value) }, resolve)
/**
* Populates the given [destination] map with entries having the keys of this map and the values obtained
* by applying the [transform] function to each entry in this map resolving conflicts with [resolve] function and
* returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new value.
* @param resolve lambda function that resolves merge conflicts which current and new values corresponding to some key.
* @return the [destination].
*/
internal inline fun <K, V, W, D : MutableMap<K, W>> Map<out K, V>.mapValuesTo(destination: D, transform: (Map.Entry<K, V>) -> W, resolve: (currentValue: W, newValue: W) -> W): D =
entries.associateByTo(destination, { it.key }, transform, resolve)
/**
* Populates the given [destination] map with entries having the keys of this map and the values obtained
* by applying the [transform] function to each entry in this map resolving conflicts with [resolve] function and
* returns the [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new value.
* @param resolve lambda function that resolves merge conflicts which current and new values corresponding to some key.
* @return the [destination].
*/
internal inline fun <K, V, W, D : MutableMap<K, W>> Map<out K, V>.mapValuesTo(destination: D, transform: (key: K, value: V) -> W, resolve: (currentValue: W, newValue: W) -> W): D =
entries.associateByTo(destination, { it.key }, { (key, value) -> transform(key, value) }, resolve)
/**
* Populates the given [destination] map with entries having the keys obtained by applying the [transform] function to
* each entry in this map and the values of this map, resolving conflicts with [resolve] function and returns the
* [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <K, V, L, D : MutableMap<L, V>> Map<out K, V>.mapKeysTo(destination: D, transform: (Map.Entry<K, V>) -> L, resolve: (key: L, currentValue: V, newValue: V) -> V): D =
entries.associateByTo(destination, transform, { it.value }, resolve)
/**
* Populates the given [destination] map with entries having the keys obtained by applying the [transform] function to
* each entry in this map and the values of this map, resolving conflicts with [resolve] function and returns the
* [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the [destination].
*/
internal inline fun <K, V, L, D : MutableMap<L, V>> Map<out K, V>.mapKeysTo(destination: D, transform: (key: K, value: V) -> L, resolve: (key: L, currentValue: V, newValue: V) -> V): D =
entries.associateByTo(destination, { (key, value) -> transform(key, value) }, { it.value }, resolve)
/**
* Populates the given [destination] map with entries having the keys obtained by applying the [transform] function to
* each entry in this map and the values of this map, resolving conflicts with [resolve] function and returns the
* [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which current and new values corresponding to some key.
* @return the [destination].
*/
internal inline fun <K, V, L, D : MutableMap<L, V>> Map<out K, V>.mapKeysTo(destination: D, transform: (Map.Entry<K, V>) -> L, resolve: (currentValue: V, newValue: V) -> V): D =
entries.associateByTo(destination, transform, { it.value }, resolve)
/**
* Populates the given [destination] map with entries having the keys obtained by applying the [transform] function to
* each entry in this map and the values of this map, resolving conflicts with [resolve] function and returns the
* [destination].
*
* All pairs are added and resolved in order of iteration.
*
* @param destination the destination of the generated key-value pairs.
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which current and new values corresponding to some key.
* @return the [destination].
*/
internal inline fun <K, V, L, D : MutableMap<L, V>> Map<out K, V>.mapKeysTo(destination: D, transform: (key: K, value: V) -> L, resolve: (currentValue: V, newValue: V) -> V): D =
entries.associateByTo(destination, { (key, value) -> transform(key, value) }, { it.value }, resolve)
/**
* Returns a new map with entries having the keys obtained by applying the [transform] function to each entry in this
* map and the values of this map and resolving conflicts with [resolve] function.
*
* All pairs are added and resolved in order of iteration.
*
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <K, V, L> Map<out K, V>.mapKeys(transform: (Map.Entry<K, V>) -> L, resolve: (key: L, currentValue: V, newValue: V) -> V): Map<L, V> =
mapKeysTo(LinkedHashMap(size), transform, resolve)
/**
* Returns a new map with entries having the keys obtained by applying the [transform] function to each entry in this
* map and the values of this map and resolving conflicts with [resolve] function.
*
* All pairs are added and resolved in order of iteration.
*
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which receives some key, its current, and new
* corresponding values.
* @return the result map.
*/
internal inline fun <K, V, L> Map<out K, V>.mapKeys(transform: (key: K, value: V) -> L, resolve: (key: L, currentValue: V, newValue: V) -> V): Map<L, V> =
mapKeysTo(LinkedHashMap(size), transform, resolve)
/**
* Returns a new map with entries having the keys obtained by applying the [transform] function to each entry in this
* map and the values of this map and resolving conflicts with [resolve] function.
*
* All pairs are added and resolved in order of iteration.
*
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which current and new values corresponding to some key.
* @return the result map.
*/
internal inline fun <K, V, L> Map<out K, V>.mapKeys(transform: (Map.Entry<K, V>) -> L, resolve: (currentValue: V, newValue: V) -> V): Map<L, V> =
mapKeysTo(LinkedHashMap(size), transform, resolve)
/**
* Returns a new map with entries having the keys obtained by applying the [transform] function to each entry in this
* map and the values of this map and resolving conflicts with [resolve] function.
*
* All pairs are added and resolved in order of iteration.
*
* @param transform function which transforms each key-value pair to new key.
* @param resolve lambda function that resolves merge conflicts which current and new values corresponding to some key.
* @return the result map.
*/
internal inline fun <K, V, L> Map<out K, V>.mapKeys(transform: (key: K, value: V) -> L, resolve: (currentValue: V, newValue: V) -> V): Map<L, V> =
mapKeysTo(LinkedHashMap(size), transform, resolve)

View File

@ -0,0 +1,779 @@
/*
* 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.
*/
@file:Suppress("FunctionName", "NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.invoke
/**
* Returns the same degrees' description of the monomial, but without zero degrees.
*/
internal fun Map<Symbol, UInt>.cleanUp() = filterValues { it > 0U }
/**
* Constructs [LabeledPolynomial] with provided coefficients map [coefs]. The map is used as is.
*/
@PublishedApi
internal inline fun <C> LabeledPolynomialAsIs(coefs: Map<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial<C>(coefs)
/**
* Constructs [LabeledPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The collections will be transformed to map with [toMap] and then will be used as is.
*/
@PublishedApi
internal inline fun <C> LabeledPolynomialAsIs(pairs: Collection<Pair<Map<Symbol, UInt>, C>>) : LabeledPolynomial<C> = LabeledPolynomial<C>(pairs.toMap())
/**
* Constructs [LabeledPolynomial] with provided array of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The array will be transformed to map with [toMap] and then will be used as is.
*/
@PublishedApi
internal inline fun <C> LabeledPolynomialAsIs(vararg pairs: Pair<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial<C>(pairs.toMap())
/**
* Constructs [LabeledPolynomial] with provided coefficients map [coefs]. The map is used as is.
*
* **Be sure you read description of [LabeledPolynomial.coefficients]. Otherwise, you may make a mistake that will
* cause wrong computation result or even runtime error.**
*/
@DelicatePolynomialAPI
public inline fun <C> LabeledPolynomialWithoutCheck(coefs: Map<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial<C>(coefs)
/**
* Constructs [LabeledPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The collections will be transformed to map with [toMap] and then will be used as is.
*
* **Be sure you read description of [LabeledPolynomial.coefficients]. Otherwise, you may make a mistake that will
* cause wrong computation result or even runtime error.**
*/
@DelicatePolynomialAPI
public inline fun <C> LabeledPolynomialWithoutCheck(pairs: Collection<Pair<Map<Symbol, UInt>, C>>) : LabeledPolynomial<C> = LabeledPolynomial<C>(pairs.toMap())
/**
* Constructs [LabeledPolynomial] with provided array of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The array will be transformed to map with [toMap] and then will be used as is.
*
* **Be sure you read description of [LabeledPolynomial.coefficients]. Otherwise, you may make a mistake that will
* cause wrong computation result or even runtime error.**
*/
@DelicatePolynomialAPI
public inline fun <C> LabeledPolynomialWithoutCheck(vararg pairs: Pair<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial<C>(pairs.toMap())
/**
* Constructs [LabeledPolynomial] 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public fun <C> LabeledPolynomial(coefs: Map<Map<Symbol, UInt>, C>, add: (C, C) -> C) : LabeledPolynomial<C> =
LabeledPolynomialAsIs(
coefs.mapKeys({ key, _ -> key.cleanUp() }, add)
)
/**
* Constructs [LabeledPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public fun <C> LabeledPolynomial(pairs: Collection<Pair<Map<Symbol, UInt>, C>>, add: (C, C) -> C) : LabeledPolynomial<C> =
LabeledPolynomialAsIs(
pairs.associateBy({ it.first.cleanUp() }, { it.second }, add)
)
/**
* Constructs [LabeledPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public fun <C> LabeledPolynomial(vararg pairs: Pair<Map<Symbol, UInt>, C>, add: (C, C) -> C) : LabeledPolynomial<C> =
LabeledPolynomialAsIs(
pairs.asIterable().associateBy({ it.first.cleanUp() }, { it.second }, add)
)
// Waiting for context receivers :( FIXME: Replace with context receivers when they will be available
/**
* Constructs [LabeledPolynomial] 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> A.LabeledPolynomial(coefs: Map<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial(coefs, ::add)
/**
* Constructs [LabeledPolynomial] 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> LabeledPolynomialSpace<C, A>.LabeledPolynomial(coefs: Map<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial(coefs) { left: C, right: C -> left + right }
/**
* Constructs [LabeledPolynomial] 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledPolynomial(coefs: Map<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial(coefs) { left: C, right: C -> left + right }
/**
* Constructs [LabeledPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> A.LabeledPolynomial(pairs: Collection<Pair<Map<Symbol, UInt>, C>>) : LabeledPolynomial<C> = LabeledPolynomial(pairs, ::add)
/**
* Constructs [LabeledPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> LabeledPolynomialSpace<C, A>.LabeledPolynomial(pairs: Collection<Pair<Map<Symbol, UInt>, C>>) : LabeledPolynomial<C> = LabeledPolynomial(pairs) { left: C, right: C -> left + right }
/**
* Constructs [LabeledPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledPolynomial(pairs: Collection<Pair<Map<Symbol, UInt>, C>>) : LabeledPolynomial<C> = LabeledPolynomial(pairs) { left: C, right: C -> left + right }
/**
* Constructs [LabeledPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> A.LabeledPolynomial(vararg pairs: Pair<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial(*pairs) { left: C, right: C -> left + right }
/**
* Constructs [LabeledPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> LabeledPolynomialSpace<C, A>.LabeledPolynomial(vararg pairs: Pair<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial(*pairs) { left: C, right: C -> left + right }
/**
* Constructs [LabeledPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see LabeledPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledPolynomial(vararg pairs: Pair<Map<Symbol, UInt>, C>) : LabeledPolynomial<C> = LabeledPolynomial(*pairs) { left: C, right: C -> left + right }
/**
* Converts [this] constant to [LabeledPolynomial].
*/
public inline fun <C> C.asLabeledPolynomial() : LabeledPolynomial<C> = LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to this))
///**
//// * Converts [this] variable to [LabeledPolynomial].
//// */
//context(A)
//public inline fun <C, A: Ring<C>> Symbol.asLabeledPolynomial() : LabeledPolynomial<C> = LabeledPolynomial<C>(mapOf(mapOf(this to 1u) to one))
///**
// * Converts [this] variable to [LabeledPolynomial].
// */
//context(LabeledPolynomialSpace<C, A>)
//public inline fun <C, A: Ring<C>> Symbol.asLabeledPolynomial() : LabeledPolynomial<C> = LabeledPolynomial<C>(mapOf(mapOf(this to 1u) to constantOne))
///**
// * Converts [this] variable to [LabeledPolynomial].
// */
//context(LabeledRationalFunctionSpace<C, A>)
//public inline fun <C, A: Ring<C>> Symbol.asLabeledPolynomial() : LabeledPolynomial<C> = LabeledPolynomial<C>(mapOf(mapOf(this to 1u) to constantOne))
/**
* Marks DSL that allows to more simply create [LabeledPolynomial]s with good performance.
*
* For example, polynomial \(5 a^2 c^3 - 6 b\) can be described as
* ```
* Int.algebra {
* val labeledPolynomial : LabeledPolynomial<Int> = LabeledPolynomialDSL1 {
* 5 { a inPowerOf 2u; c inPowerOf 3u } // 5 a^2 c^3 +
* (-6) { b inPowerOf 1u } // (-6) b^1
* }
* }
* ```
* @usesMathJax
*/
@DslMarker
@UnstableKMathAPI
internal annotation class LabeledPolynomialConstructorDSL1
/**
* Builder of [LabeledPolynomial] signature. It should be used as an implicit context for lambdas that describe term signature.
*/
@UnstableKMathAPI
@LabeledPolynomialConstructorDSL1
public class DSL1LabeledPolynomialTermSignatureBuilder {
/**
* 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: MutableMap<Symbol, UInt> = LinkedHashMap()
/**
* Builds the resulting signature.
*
* In fact, it just returns [signature] as regular signature of type `List<UInt>`.
*/
@PublishedApi
internal fun build(): Map<Symbol, UInt> = signature
/**
* Declares power of [this] variable of degree [deg].
*
* Declaring another power of the same variable will increase its degree by received degree.
*/
public infix fun Symbol.inPowerOf(deg: UInt) {
if (deg == 0u) return
signature.putOrChange(this, deg) { it -> it + deg }
}
/**
* Declares power of [this] variable of degree [deg].
*
* Declaring another power of the same variable will increase its degree by received degree.
*/
public inline infix fun Symbol.pow(deg: UInt): Unit = this inPowerOf deg
/**
* Declares power of [this] variable of degree [deg].
*
* Declaring another power of the same variable will increase its degree by received degree.
*/
public inline infix fun Symbol.`in`(deg: UInt): Unit = this inPowerOf deg
/**
* Declares power of [this] variable of degree [deg].
*
* Declaring another power of the same variable will increase its degree by received degree.
*/
public inline infix fun Symbol.of(deg: UInt): Unit = this inPowerOf deg
}
/**
* Builder of [LabeledPolynomial]. It should be used as an implicit context for lambdas that describe [LabeledPolynomial].
*/
@UnstableKMathAPI
@LabeledPolynomialConstructorDSL1
public class DSL1LabeledPolynomialBuilder<C>(
/**
* 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? = null
) {
/**
* 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<Map<Symbol, UInt>, C> = if (initialCapacity != null) LinkedHashMap(initialCapacity) else LinkedHashMap()
/**
* Builds the resulting coefficients map.
*
* In fact, it just returns [coefficients] as regular coefficients map of type `Map<Map<Symbol, UInt>, C>`.
*/
@PublishedApi
internal fun build(): LabeledPolynomial<C> = LabeledPolynomial<C>(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: Map<Symbol, UInt>) {
coefficients.putOrChange(signature, this@with, add)
}
/**
* 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 inline infix fun C.with(noinline block: DSL1LabeledPolynomialTermSignatureBuilder.() -> 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 inline operator fun C.invoke(block: DSL1LabeledPolynomialTermSignatureBuilder.() -> Unit): Unit =
this with DSL1LabeledPolynomialTermSignatureBuilder().apply(block).build()
}
// Waiting for context receivers :( FIXME: Replace with context receivers when they will be available
///**
// * Creates [LabeledPolynomial] with lambda [block] in context of [this] ring of constants.
// *
// * For example, polynomial \(5 a^2 c^3 - 6 b\) can be described as
// * ```
// * Int.algebra {
// * val labeledPolynomial : LabeledPolynomial<Int> = LabeledPolynomialDSL1 {
// * 5 { a inPowerOf 2u; c inPowerOf 3u } // 5 a^2 c^3 +
// * (-6) { b inPowerOf 1u } // (-6) b^1
// * }
// * }
// * ```
// * @usesMathJax
// */
// FIXME: For now this fabric does not let next two fabrics work. (See KT-52803.) Possible feature solutions:
// 1. `LowPriorityInOverloadResolution` becomes public. Then it should be applied to this function.
// 2. Union types are implemented. Then all three functions should be rewritten
// as one with single union type as a (context) receiver.
//@UnstableKMathAPI
//public inline fun <C, A: Ring<C>> A.LabeledPolynomialDSL1(initialCapacity: Int? = null, block: LabeledPolynomialBuilder<C>.() -> Unit) : LabeledPolynomial<C> = LabeledPolynomialBuilder(::add, initialCapacity).apply(block).build()
/**
* Creates [LabeledPolynomial] with lambda [block] in context of [this] ring of [LabeledPolynomial]s.
*
* For example, polynomial \(5 a^2 c^3 - 6 b\) can be described as
* ```
* Int.algebra {
* val labeledPolynomial : LabeledPolynomial<Int> = LabeledPolynomialDSL1 {
* 5 { a inPowerOf 2u; c inPowerOf 3u } // 5 a^2 c^3 +
* (-6) { b inPowerOf 1u } // (-6) b^1
* }
* }
* ```
* @usesMathJax
*/
@UnstableKMathAPI
public inline fun <C, A: Ring<C>> LabeledPolynomialSpace<C, A>.LabeledPolynomialDSL1(initialCapacity: Int? = null, block: DSL1LabeledPolynomialBuilder<C>.() -> Unit) : LabeledPolynomial<C> = DSL1LabeledPolynomialBuilder({ left: C, right: C -> left + right }, initialCapacity).apply(block).build()
/**
* Creates [LabeledPolynomial] with lambda [block] in context of [this] field of [LabeledRationalFunction]s.
*
* For example, polynomial \(5 a^2 c^3 - 6 b\) can be described as
* ```
* Int.algebra {
* val labeledPolynomial : LabeledPolynomial<Int> = LabeledPolynomialDSL1 {
* 5 { a inPowerOf 2u; c inPowerOf 3u } // 5 a^2 c^3 +
* (-6) { b inPowerOf 1u } // (-6) b^1
* }
* }
* ```
* @usesMathJax
*/
@UnstableKMathAPI
public inline fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledPolynomialDSL1(initialCapacity: Int? = null, block: DSL1LabeledPolynomialBuilder<C>.() -> Unit) : LabeledPolynomial<C> = DSL1LabeledPolynomialBuilder({ left: C, right: C -> left + right }, initialCapacity).apply(block).build()
/**
* Marks DSL that allows to more simply create [LabeledPolynomial]s with good performance.
*
* For example, polynomial \(5 a^2 c^3 - 6 b\) can be described as
* ```
* Int.algebra {
* val numberedPolynomial : NumberedPolynomial<Int> = NumberedPolynomial {
* 5 { a inPowerOf 2u; c inPowerOf 3u } // 5 a^2 c^3 +
* (-6) { b inPowerOf 1u } // (-6) b^1
* }
* }
* ```
* @usesMathJax
*/
@DslMarker
@UnstableKMathAPI
internal annotation class LabeledPolynomialBuilderDSL2
/**
* Builder of [LabeledPolynomial]. It should be used as an implicit context for lambdas that describe [LabeledPolynomial].
*/
@UnstableKMathAPI
@LabeledPolynomialBuilderDSL2
public class DSL2LabeledPolynomialBuilder<C>(
private val ring: Ring<C>,
/**
* Initial capacity of coefficients map.
*/
initialCapacity: Int? = null
) {
/**
* 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<Map<Symbol, UInt>, C> = if (initialCapacity != null) LinkedHashMap(initialCapacity) else LinkedHashMap()
/**
* Builds the resulting coefficients map.
*
* In fact, it just returns [coefficients] as regular coefficients map of type `Map<Map<Symbol, UInt>, C>`.
*/
@PublishedApi
internal fun build(): LabeledPolynomial<C> = LabeledPolynomial<C>(coefficients)
public inner class Term internal constructor(
internal val signature: Map<Symbol, UInt> = HashMap(),
internal val coefficient: C
)
private inline fun submit(signature: Map<Symbol, UInt>, onPut: Ring<C>.() -> C, onChange: Ring<C>.(C) -> C) {
coefficients.putOrChange<_, C>(signature, { ring.onPut() }, { ring.onChange(it) })
}
private inline fun submit(signature: Map<Symbol, UInt>, lazyCoefficient: Ring<C>.() -> C) {
submit(signature, lazyCoefficient, { it + lazyCoefficient() })
}
private fun submit(signature: Map<Symbol, UInt>, coefficient: C) {
submit(signature) { coefficient }
}
// TODO: `@submit` will be resolved differently. Change it to `@C`.
private fun C.submit() = submit(emptyMap(), { this@submit })
private fun Symbol.submit() = submit(mapOf(this to 1u), { one })
private fun Term.submit(): Submit {
submit(signature, coefficient)
return Submit
}
public object Submit
public operator fun C.unaryPlus(): Submit {
submit()
return Submit
}
public operator fun C.unaryMinus(): Submit {
submit(emptyMap(), { -this@unaryMinus }, { it - this@unaryMinus })
return Submit
}
public operator fun C.plus(other: C): Submit {
submit(emptyMap(), { this@plus + other })
return Submit
}
public operator fun C.minus(other: C): Submit {
submit(emptyMap(), { this@minus - other })
return Submit
}
public operator fun C.times(other: C): C = ring { this@times * other }
public operator fun C.plus(other: Symbol): Submit {
submit(emptyMap(), this)
submit(mapOf(other to 1u), ring.one)
return Submit
}
public operator fun C.minus(other: Symbol): Submit {
submit(emptyMap(), this)
submit(mapOf(other to 1u), { -one }, { it - one })
return Submit
}
public operator fun C.times(other: Symbol): Term = Term(mapOf(other to 1u), this)
public operator fun C.plus(other: Term): Submit {
submit(emptyMap(), this)
other.submit()
return Submit
}
public operator fun C.minus(other: Term): Submit {
submit(emptyMap(), this)
submit(other.signature, { -other.coefficient }, { it - other.coefficient })
return Submit
}
public operator fun C.times(other: Term): Term = Term(other.signature, ring { this@times * other.coefficient })
public operator fun Symbol.plus(other: C): Submit {
this.submit()
other.submit()
return Submit
}
public operator fun Symbol.minus(other: C): Submit {
this.submit()
submit(emptyMap(), { -other }, { it - other })
return Submit
}
public operator fun Symbol.times(other: C): Term = Term(mapOf(this to 1u), other)
public operator fun Symbol.unaryPlus(): Submit {
this.submit()
return Submit
}
public operator fun Symbol.unaryMinus(): Submit {
submit(mapOf(this to 1u), { -one }, { it - one })
return Submit
}
public operator fun Symbol.plus(other: Symbol): Submit {
this.submit()
other.submit()
return Submit
}
public operator fun Symbol.minus(other: Symbol): Submit {
this.submit()
submit(mapOf(other to 1u), { -one }, { it - one })
return Submit
}
public operator fun Symbol.times(other: Symbol): Term =
if (this == other) Term(mapOf(this to 2u), ring.one)
else Term(mapOf(this to 1u, other to 1u), ring.one)
public operator fun Symbol.plus(other: Term): Submit {
this.submit()
other.submit()
return Submit
}
public operator fun Symbol.minus(other: Term): Submit {
this.submit()
submit(other.signature, { -other.coefficient }, { it - other.coefficient })
return Submit
}
public operator fun Symbol.times(other: Term): Term =
Term(
other.signature.withPutOrChanged(this, 1u) { it -> it + 1u },
other.coefficient
)
public operator fun Term.plus(other: C): Submit {
this.submit()
other.submit()
return Submit
}
public operator fun Term.minus(other: C): Submit {
this.submit()
submit(emptyMap(), { -other }, { it - other })
return Submit
}
public operator fun Term.times(other: C): Term =
Term(
signature,
ring { coefficient * other }
)
public operator fun Term.plus(other: Symbol): Submit {
this.submit()
other.submit()
return Submit
}
public operator fun Term.minus(other: Symbol): Submit {
this.submit()
submit(mapOf(other to 1u), { -one }, { it - one })
return Submit
}
public operator fun Term.times(other: Symbol): Term =
Term(
signature.withPutOrChanged(other, 1u) { it -> it + 1u },
coefficient
)
public operator fun Term.unaryPlus(): Submit {
this.submit()
return Submit
}
public operator fun Term.unaryMinus(): Submit {
submit(signature, { -coefficient }, { it - coefficient })
return Submit
}
public operator fun Term.plus(other: Term): Submit {
this.submit()
other.submit()
return Submit
}
public operator fun Term.minus(other: Term): Submit {
this.submit()
submit(other.signature, { -other.coefficient }, { it - other.coefficient })
return Submit
}
public operator fun Term.times(other: Term): Term =
Term(
mergeBy(signature, other.signature) { deg1, deg2 -> deg1 + deg2 },
ring { coefficient * other.coefficient }
)
}
//@UnstableKMathAPI
//public fun <C> Ring<C>.LabeledPolynomialDSL2(initialCapacity: Int? = null, block: DSL2LabeledPolynomialBuilder<C>.() -> Unit): LabeledPolynomial<C> = DSL2LabeledPolynomialBuilder(this, initialCapacity).apply(block).build()
@UnstableKMathAPI
public fun <C, A: Ring<C>> LabeledPolynomialSpace<C, A>.LabeledPolynomialDSL2(initialCapacity: Int? = null, block: DSL2LabeledPolynomialBuilder<C>.() -> Unit): LabeledPolynomial<C> = DSL2LabeledPolynomialBuilder(ring, initialCapacity).apply(block).build()
@UnstableKMathAPI
public fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledPolynomialDSL2(initialCapacity: Int? = null, block: DSL2LabeledPolynomialBuilder<C>.() -> Unit): LabeledPolynomial<C> = DSL2LabeledPolynomialBuilder(ring, initialCapacity).apply(block).build()
// Waiting for context receivers :( FIXME: Replace with context receivers when they will be available
/**
* Constructs [LabeledRationalFunction] 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.
*/
public fun <C, A: Ring<C>> A.LabeledRationalFunction(numeratorCoefficients: Map<Map<Symbol, UInt>, C>, denominatorCoefficients: Map<Map<Symbol, UInt>, C>): LabeledRationalFunction<C> =
LabeledRationalFunction<C>(
LabeledPolynomial(numeratorCoefficients),
LabeledPolynomial(denominatorCoefficients)
)
/**
* Constructs [LabeledRationalFunction] 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.
*/
public fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledRationalFunction(numeratorCoefficients: Map<Map<Symbol, UInt>, C>, denominatorCoefficients: Map<Map<Symbol, UInt>, C>): LabeledRationalFunction<C> =
LabeledRationalFunction<C>(
LabeledPolynomial(numeratorCoefficients),
LabeledPolynomial(denominatorCoefficients)
)
/**
* Constructs [LabeledRationalFunction] with provided [numerator] and unit denominator.
*/
public fun <C, A: Ring<C>> A.LabeledRationalFunction(numerator: LabeledPolynomial<C>): LabeledRationalFunction<C> =
LabeledRationalFunction<C>(numerator, LabeledPolynomial(mapOf(emptyMap<Symbol, UInt>() to one)))
/**
* Constructs [LabeledRationalFunction] with provided [numerator] and unit denominator.
*/
public fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledRationalFunction(numerator: LabeledPolynomial<C>): LabeledRationalFunction<C> =
LabeledRationalFunction<C>(numerator, polynomialOne)
/**
* Constructs [LabeledRationalFunction] 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.
*/
public fun <C, A: Ring<C>> LabeledRationalFunctionSpace<C, A>.LabeledRationalFunction(numeratorCoefficients: Map<Map<Symbol, UInt>, C>): LabeledRationalFunction<C> =
LabeledRationalFunction<C>(
LabeledPolynomial(numeratorCoefficients),
polynomialOne
)
/**
* Constructs [LabeledRationalFunction] 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.
*/
public fun <C, A: Ring<C>> A.LabeledRationalFunction(numeratorCoefficients: Map<Map<Symbol, UInt>, C>): LabeledRationalFunction<C> =
LabeledRationalFunction<C>(
LabeledPolynomial(numeratorCoefficients),
LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to one))
)
///**
// * Converts [this] constant to [LabeledRationalFunction].
// */
//context(A)
//public fun <C, A: Ring<C>> C.asLabeledRationalFunction() : LabeledRationalFunction<C> =
// LabeledRationalFunction(
// LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to this)),
// LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to one))
// )
///**
// * Converts [this] constant to [LabeledRationalFunction].
// */
//context(LabeledRationalFunctionSpace<C, A>)
//public fun <C, A: Ring<C>> C.asLabeledRationalFunction() : LabeledRationalFunction<C> =
// LabeledRationalFunction(
// LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to this)),
// LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to constantOne))
// )
///**
// * Converts [this] variable to [LabeledRationalFunction].
// */
//context(A)
//public fun <C, A: Ring<C>> Symbol.asLabeledRationalFunction() : LabeledRationalFunction<C> =
// LabeledRationalFunction(
// LabeledPolynomialAsIs(mapOf(mapOf(this to 1u) to one)),
// LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to one))
// )
///**
// * Converts [this] variable to [LabeledRationalFunction].
// */
//context(LabeledRationalFunctionSpace<C, A>)
//public fun <C, A: Ring<C>> Symbol.asLabeledRationalFunction() : LabeledRationalFunction<C> =
// LabeledRationalFunction(
// LabeledPolynomialAsIs(mapOf(mapOf(this to 1u) to constantOne)),
// LabeledPolynomialAsIs(mapOf(emptyMap<Symbol, UInt>() to constantOne))
// )

View File

@ -0,0 +1,321 @@
/*
* 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.expressions.Symbol
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmName
/**
* Creates a [LabeledPolynomialSpace] over a received ring.
*/
public inline val <C, A : Ring<C>> A.labeledPolynomialSpace: LabeledPolynomialSpace<C, A>
get() = LabeledPolynomialSpace(this)
/**
* Creates a [LabeledPolynomialSpace]'s scope over a received ring.
*/
public inline fun <C, A : Ring<C>, R> A.labeledPolynomialSpace(block: LabeledPolynomialSpace<C, A>.() -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return LabeledPolynomialSpace(this).block()
}
/**
* Creates a [LabeledRationalFunctionSpace] over a received ring.
*/
public inline val <C, A : Ring<C>> A.labeledRationalFunctionSpace: LabeledRationalFunctionSpace<C, A>
get() = LabeledRationalFunctionSpace(this)
/**
* Creates a [LabeledRationalFunctionSpace]'s scope over a received ring.
*/
public inline fun <C, A : Ring<C>, R> A.labeledRationalFunctionSpace(block: LabeledRationalFunctionSpace<C, A>.() -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return LabeledRationalFunctionSpace(this).block()
}
/**
* Substitutes provided Double arguments [args] into [this] Double polynomial.
*/
public fun LabeledPolynomial<Double>.substitute(args: Map<Symbol, Double>): LabeledPolynomial<Double> = Double.algebra {
if (coefficients.isEmpty()) return this@substitute
LabeledPolynomial<Double>(
buildMap {
coefficients.forEach { (degs, c) ->
val newDegs = degs.filterKeys { it !in args }
val newC = args.entries.fold(c) { product, (variable, substitution) ->
val deg = degs.getOrElse(variable) { 0u }
if (deg == 0u) product else product * power(substitution, deg)
}
putOrChange(newDegs, newC, ::add)
}
}
)
}
/**
* Substitutes provided arguments [args] into [this] polynomial.
*/
public fun <C> LabeledPolynomial<C>.substitute(ring: Ring<C>, args: Map<Symbol, C>): LabeledPolynomial<C> = ring {
if (coefficients.isEmpty()) return this@substitute
LabeledPolynomial<C>(
buildMap {
coefficients.forEach { (degs, c) ->
val newDegs = degs.filterKeys { it !in args }
val newC = args.entries.fold(c) { product, (variable, substitution) ->
val deg = degs.getOrElse(variable) { 0u }
if (deg == 0u) product else product * power(substitution, deg)
}
putOrChange(newDegs, newC, ::add)
}
}
)
}
/**
* Substitutes provided arguments [args] into [this] polynomial.
*/ // TODO: To optimize boxing
@JvmName("substitutePolynomial")
public fun <C> LabeledPolynomial<C>.substitute(ring: Ring<C>, args: Map<Symbol, LabeledPolynomial<C>>) : LabeledPolynomial<C> =
ring.labeledPolynomialSpace {
coefficients.entries.fold(zero) { acc, (degs, c) ->
val newDegs = degs.filterKeys { it !in args }
acc + args.entries.fold(LabeledPolynomial<C>(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 <C> LabeledPolynomial<C>.substitute(ring: Ring<C>, args: Map<Symbol, LabeledRationalFunction<C>>) : LabeledRationalFunction<C> =
ring.labeledRationalFunctionSpace {
coefficients.entries.fold(zero) { acc, (degs, c) ->
val newDegs = degs.filterKeys { it !in args }
acc + args.entries.fold(LabeledRationalFunction(LabeledPolynomial<C>(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 LabeledRationalFunction<Double>.substitute(args: Map<Symbol, Double>): LabeledRationalFunction<Double> =
LabeledRationalFunction(numerator.substitute(args), denominator.substitute(args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/
public fun <C> LabeledRationalFunction<C>.substitute(ring: Ring<C>, args: Map<Symbol, C>): LabeledRationalFunction<C> =
LabeledRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/ // TODO: To optimize calculation
@JvmName("substitutePolynomial")
public fun <C> LabeledRationalFunction<C>.substitute(ring: Ring<C>, args: Map<Symbol, LabeledPolynomial<C>>) : LabeledRationalFunction<C> =
LabeledRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/ // TODO: To optimize calculation
@JvmName("substituteRationalFunction")
public fun <C> LabeledRationalFunction<C>.substitute(ring: Ring<C>, args: Map<Symbol, LabeledRationalFunction<C>>) : LabeledRationalFunction<C> =
ring.labeledRationalFunctionSpace {
numerator.substitute(ring, args) / denominator.substitute(ring, args)
}
/**
* Returns algebraic derivative of received polynomial with respect to provided variable.
*/
@UnstableKMathAPI
public fun <C, A : Ring<C>> LabeledPolynomial<C>.derivativeWithRespectTo(
algebra: A,
variable: Symbol,
): LabeledPolynomial<C> = algebra {
LabeledPolynomial<C>(
buildMap(coefficients.count { it.key.getOrElse(variable) { 0u } >= 1u }) {
coefficients
.forEach { (degs, c) ->
if (variable !in degs) return@forEach
put(
buildMap {
degs.forEach { (vari, deg) ->
when {
vari != variable -> put(vari, deg)
deg > 1u -> put(vari, deg - 1u)
}
}
},
multiplyByDoubling(c, degs[variable]!!)
)
}
}
)
}
/**
* Returns algebraic derivative of received polynomial with respect to provided variable of specified order.
*/
@UnstableKMathAPI
public fun <C, A : Ring<C>> LabeledPolynomial<C>.nthDerivativeWithRespectTo(
algebra: A,
variable: Symbol,
order: UInt
): LabeledPolynomial<C> = algebra {
if (order == 0u) return this@nthDerivativeWithRespectTo
LabeledPolynomial<C>(
buildMap(coefficients.count { it.key.getOrElse(variable) { 0u } >= order }) {
coefficients
.forEach { (degs, c) ->
if (degs.getOrElse(variable) { 0u } < order) return@forEach
put(
buildMap {
degs.forEach { (vari, deg) ->
when {
vari != variable -> put(vari, deg)
deg > order -> put(vari, deg - order)
}
}
},
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 <C, A : Ring<C>> LabeledPolynomial<C>.nthDerivativeWithRespectTo(
algebra: A,
variablesAndOrders: Map<Symbol, UInt>,
): LabeledPolynomial<C> = algebra {
val filteredVariablesAndOrders = variablesAndOrders.filterValues { it != 0u }
if (filteredVariablesAndOrders.isEmpty()) return this@nthDerivativeWithRespectTo
LabeledPolynomial<C>(
buildMap(
coefficients.count {
variablesAndOrders.all { (variable, order) ->
it.key.getOrElse(variable) { 0u } >= order
}
}
) {
coefficients
.forEach { (degs, c) ->
if (filteredVariablesAndOrders.any { (variable, order) -> degs.getOrElse(variable) { 0u } < order }) return@forEach
put(
buildMap {
degs.forEach { (vari, deg) ->
if (vari !in filteredVariablesAndOrders) put(vari, deg)
else {
val order = filteredVariablesAndOrders[vari]!!
if (deg > order) put(vari, deg - order)
}
}
},
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 <C, A : Field<C>> LabeledPolynomial<C>.antiderivativeWithRespectTo(
algebra: A,
variable: Symbol,
): LabeledPolynomial<C> = algebra {
LabeledPolynomial<C>(
buildMap(coefficients.size) {
coefficients
.forEach { (degs, c) ->
val newDegs = degs.withPutOrChanged(variable, 1u) { it -> it + 1u }
put(
newDegs,
c / multiplyByDoubling(one, newDegs[variable]!!)
)
}
}
)
}
/**
* Returns algebraic antiderivative of received polynomial with respect to provided variable of specified order.
*/
@UnstableKMathAPI
public fun <C, A : Field<C>> LabeledPolynomial<C>.nthAntiderivativeWithRespectTo(
algebra: A,
variable: Symbol,
order: UInt
): LabeledPolynomial<C> = algebra {
if (order == 0u) return this@nthAntiderivativeWithRespectTo
LabeledPolynomial<C>(
buildMap(coefficients.size) {
coefficients
.forEach { (degs, c) ->
val newDegs = degs.withPutOrChanged(variable, order) { it -> it + order }
put(
newDegs,
newDegs[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 <C, A : Field<C>> LabeledPolynomial<C>.nthAntiderivativeWithRespectTo(
algebra: A,
variablesAndOrders: Map<Symbol, UInt>,
): LabeledPolynomial<C> = algebra {
val filteredVariablesAndOrders = variablesAndOrders.filterValues { it != 0u }
if (filteredVariablesAndOrders.isEmpty()) return this@nthAntiderivativeWithRespectTo
LabeledPolynomial<C>(
buildMap(coefficients.size) {
coefficients
.forEach { (degs, c) ->
val newDegs = mergeBy(degs, filteredVariablesAndOrders) { deg, order -> deg + order }
put(
newDegs,
filteredVariablesAndOrders.entries.fold(c) { acc1, (index, order) ->
newDegs[index]!!.let { deg ->
(deg downTo deg - order + 1u)
.fold(acc1) { acc2, ord -> acc2 / multiplyByDoubling(one, ord) }
}
}
)
}
}
)
}

View File

@ -0,0 +1,92 @@
/*
* 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
/**
* Constructs a [ListPolynomial] instance with provided [coefficients]. The collection of coefficients will be reversed
* if [reverse] parameter is true.
*/
@Suppress("FunctionName")
public fun <C> ListPolynomial(coefficients: List<C>, reverse: Boolean = false): ListPolynomial<C> =
ListPolynomial(with(coefficients) { if (reverse) reversed() else this })
/**
* Constructs a [ListPolynomial] instance with provided [coefficients]. The collection of coefficients will be reversed
* if [reverse] parameter is true.
*/
@Suppress("FunctionName")
public fun <C> ListPolynomial(vararg coefficients: C, reverse: Boolean = false): ListPolynomial<C> =
ListPolynomial(with(coefficients) { if (reverse) reversed() else toList() })
/**
* Represents [this] constant as a [ListPolynomial].
*/
public fun <C> C.asListPolynomial() : ListPolynomial<C> = ListPolynomial(listOf(this))
// Waiting for context receivers :( FIXME: Replace with context receivers when they will be available
/**
* Constructs [ListRationalFunction] instance with numerator and denominator constructed with provided
* [numeratorCoefficients] and [denominatorCoefficients]. The both collections of coefficients will be reversed if
* [reverse] parameter is true.
*/
@Suppress("FunctionName")
public fun <C> ListRationalFunction(numeratorCoefficients: List<C>, denominatorCoefficients: List<C>, reverse: Boolean = false): ListRationalFunction<C> =
ListRationalFunction<C>(
ListPolynomial( with(numeratorCoefficients) { if (reverse) reversed() else this } ),
ListPolynomial( with(denominatorCoefficients) { if (reverse) reversed() else this } )
)
/**
* Constructs [ListRationalFunction] instance with provided [numerator] and unit denominator.
*/
@Suppress("FunctionName")
public fun <C, A: Ring<C>> A.ListRationalFunction(numerator: ListPolynomial<C>): ListRationalFunction<C> =
ListRationalFunction<C>(numerator, ListPolynomial(listOf(one)))
/**
* Constructs [ListRationalFunction] instance with provided [numerator] and unit denominator.
*/
@Suppress("FunctionName")
public fun <C, A: Ring<C>> ListRationalFunctionSpace<C, A>.ListRationalFunction(numerator: ListPolynomial<C>): ListRationalFunction<C> =
ListRationalFunction<C>(numerator, polynomialOne)
/**
* Constructs [ListRationalFunction] instance with numerator constructed with provided [numeratorCoefficients] and unit
* denominator. The collection of numerator coefficients will be reversed if [reverse] parameter is true.
*/
@Suppress("FunctionName")
public fun <C, A: Ring<C>> A.ListRationalFunction(numeratorCoefficients: List<C>, reverse: Boolean = false): ListRationalFunction<C> =
ListRationalFunction<C>(
ListPolynomial( with(numeratorCoefficients) { if (reverse) reversed() else this } ),
ListPolynomial(listOf(one))
)
/**
* Constructs [ListRationalFunction] instance with numerator constructed with provided [numeratorCoefficients] and unit
* denominator. The collection of numerator coefficients will be reversed if [reverse] parameter is true.
*/
@Suppress("FunctionName")
public fun <C, A: Ring<C>> ListRationalFunctionSpace<C, A>.ListRationalFunction(numeratorCoefficients: List<C>, reverse: Boolean = false): ListRationalFunction<C> =
ListRationalFunction<C>(
ListPolynomial( with(numeratorCoefficients) { if (reverse) reversed() else this } ),
polynomialOne
)
/**
* Represents [this] constant as a rational function.
*/ // FIXME: When context receivers will be ready, delete this function and uncomment the following two
public fun <C, A: Ring<C>> C.asListRationalFunction(ring: A) : ListRationalFunction<C> = ring.ListRationalFunction(asListPolynomial())
///**
// * Represents [this] constant as a rational function.
// */
//context(A)
//public fun <C, A: Ring<C>> C.asListRationalFunction() : ListRationalFunction<C> = ListRationalFunction(asListPolynomial())
///**
// * Represents [this] constant as a rational function.
// */
//context(ListRationalFunctionSpace<C, A>)
//public fun <C, A: Ring<C>> C.asListRationalFunction() : ListRationalFunction<C> = ListRationalFunction(asListPolynomial())

View File

@ -0,0 +1,255 @@
/*
* 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.math.max
import kotlin.math.pow
/**
* Creates a [ListPolynomialSpace] over a received ring.
*/
public inline val <C, A : Ring<C>> A.listPolynomialSpace: ListPolynomialSpace<C, A>
get() = ListPolynomialSpace(this)
/**
* Creates a [ListPolynomialSpace]'s scope over a received ring.
*/ // TODO: When context will be ready move [ListPolynomialSpace] and add [A] to context receivers of [block]
public inline fun <C, A : Ring<C>, R> A.listPolynomialSpace(block: ListPolynomialSpace<C, A>.() -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListPolynomialSpace(this).block()
}
/**
* Creates a [ScalableListPolynomialSpace] over a received scalable ring.
*/
public inline val <C, A> A.scalableListPolynomialSpace: ScalableListPolynomialSpace<C, A> where A : Ring<C>, A : ScaleOperations<C>
get() = ScalableListPolynomialSpace(this)
/**
* Creates a [ScalableListPolynomialSpace]'s scope over a received scalable ring.
*/ // TODO: When context will be ready move [ListPolynomialSpace] and add [A] to context receivers of [block]
public inline fun <C, A, R> A.scalableListPolynomialSpace(block: ScalableListPolynomialSpace<C, A>.() -> R): R where A : Ring<C>, A : ScaleOperations<C> {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ScalableListPolynomialSpace(this).block()
}
/**
* Creates a [ListRationalFunctionSpace] over a received ring.
*/
public inline val <C, A : Ring<C>> A.listRationalFunctionSpace: ListRationalFunctionSpace<C, A>
get() = ListRationalFunctionSpace(this)
/**
* Creates a [ListRationalFunctionSpace]'s scope over a received ring.
*/ // TODO: When context will be ready move [ListRationalFunctionSpace] and add [A] to context receivers of [block]
public inline fun <C, A : Ring<C>, R> A.listRationalFunctionSpace(block: ListRationalFunctionSpace<C, A>.() -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListRationalFunctionSpace(this).block()
}
/**
* Evaluates value of [this] Double polynomial on provided Double argument.
*/
public fun ListPolynomial<Double>.substitute(arg: Double): Double =
coefficients.reduceIndexedOrNull { index, acc, c ->
acc + c * arg.pow(index)
} ?: .0
/**
* Evaluates value of [this] polynomial on provided argument.
*
* It is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method).
*/
public fun <C> ListPolynomial<C>.substitute(ring: Ring<C>, arg: C): C = ring {
if (coefficients.isEmpty()) return zero
var result: C = coefficients.last()
for (j in coefficients.size - 2 downTo 0) {
result = (arg * result) + coefficients[j]
}
return result
}
/**
* Substitutes provided polynomial [arg] into [this] polynomial.
*
* It is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method).
*/ // TODO: To optimize boxing
public fun <C> ListPolynomial<C>.substitute(ring: Ring<C>, arg: ListPolynomial<C>) : ListPolynomial<C> =
ring.listPolynomialSpace {
if (coefficients.isEmpty()) return zero
var result: ListPolynomial<C> = coefficients.last().asPolynomial()
for (j in coefficients.size - 2 downTo 0) {
result = (arg * result) + coefficients[j]
}
return result
}
/**
* Substitutes provided rational function [arg] into [this] polynomial.
*
* It is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method).
*/ // TODO: To optimize boxing
public fun <C> ListPolynomial<C>.substitute(ring: Ring<C>, arg: ListRationalFunction<C>) : ListRationalFunction<C> =
ring.listRationalFunctionSpace {
if (coefficients.isEmpty()) return zero
var result: ListRationalFunction<C> = coefficients.last().asRationalFunction()
for (j in coefficients.size - 2 downTo 0) {
result = (arg * result) + coefficients[j]
}
return result
}
/**
* Evaluates value of [this] Double rational function in provided Double argument.
*/
public fun ListRationalFunction<Double>.substitute(arg: Double): Double =
numerator.substitute(arg) / denominator.substitute(arg)
/**
* Evaluates value of [this] polynomial for provided argument.
*
* It is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method).
*/
public fun <C> ListRationalFunction<C>.substitute(ring: Field<C>, arg: C): C = ring {
numerator.substitute(ring, arg) / denominator.substitute(ring, arg)
}
/**
* Substitutes provided polynomial [arg] into [this] rational function.
*/ // TODO: To optimize boxing
public fun <C> ListRationalFunction<C>.substitute(ring: Ring<C>, arg: ListPolynomial<C>) : ListRationalFunction<C> =
ring.listRationalFunctionSpace {
numerator.substitute(ring, arg) / denominator.substitute(ring, arg)
}
/**
* Substitutes provided rational function [arg] into [this] rational function.
*/ // TODO: To optimize boxing
public fun <C> ListRationalFunction<C>.substitute(ring: Ring<C>, arg: ListRationalFunction<C>) : ListRationalFunction<C> =
ring.listRationalFunctionSpace {
numerator.substitute(ring, arg) / denominator.substitute(ring, arg)
}
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> ListPolynomial<C>.asFunctionOver(ring: A): (C) -> C = { substitute(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> ListPolynomial<C>.asFunctionOfConstantOver(ring: A): (C) -> C = { substitute(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> ListPolynomial<C>.asFunctionOfPolynomialOver(ring: A): (ListPolynomial<C>) -> ListPolynomial<C> = { substitute(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> ListPolynomial<C>.asFunctionOfRationalFunctionOver(ring: A): (ListRationalFunction<C>) -> ListRationalFunction<C> = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Field<C>> ListRationalFunction<C>.asFunctionOver(ring: A): (C) -> C = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Field<C>> ListRationalFunction<C>.asFunctionOfConstantOver(ring: A): (C) -> C = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Ring<C>> ListRationalFunction<C>.asFunctionOfPolynomialOver(ring: A): (ListPolynomial<C>) -> ListRationalFunction<C> = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Ring<C>> ListRationalFunction<C>.asFunctionOfRationalFunctionOver(ring: A): (ListRationalFunction<C>) -> ListRationalFunction<C> = { substitute(ring, it) }
/**
* Returns algebraic derivative of received polynomial.
*/
@UnstableKMathAPI
public fun <C, A> ListPolynomial<C>.derivative(
ring: A,
): ListPolynomial<C> where A : Ring<C>, A : NumericAlgebra<C> = ring {
ListPolynomial(
buildList(max(0, coefficients.size - 1)) {
for (deg in 1 .. coefficients.lastIndex) add(number(deg) * coefficients[deg])
}
)
}
/**
* Returns algebraic derivative of received polynomial of specified [order]. The [order] should be non-negative integer.
*/
@UnstableKMathAPI
public fun <C, A> ListPolynomial<C>.nthDerivative(
ring: A,
order: Int,
): ListPolynomial<C> where A : Ring<C>, A : NumericAlgebra<C> = ring {
require(order >= 0) { "Order of derivative must be non-negative" }
ListPolynomial(
buildList(max(0, coefficients.size - order)) {
for (deg in order.. coefficients.lastIndex)
add((deg - order + 1 .. deg).fold(coefficients[deg]) { acc, d -> acc * number(d) })
}
)
}
/**
* Returns algebraic antiderivative of received polynomial.
*/
@UnstableKMathAPI
public fun <C, A> ListPolynomial<C>.antiderivative(
ring: A,
): ListPolynomial<C> where A : Field<C>, A : NumericAlgebra<C> = ring {
ListPolynomial(
buildList(coefficients.size + 1) {
add(zero)
coefficients.mapIndexedTo(this) { index, t -> t / number(index + 1) }
}
)
}
/**
* Returns algebraic antiderivative of received polynomial of specified [order]. The [order] should be non-negative integer.
*/
@UnstableKMathAPI
public fun <C, A> ListPolynomial<C>.nthAntiderivative(
ring: A,
order: Int,
): ListPolynomial<C> where A : Field<C>, A : NumericAlgebra<C> = ring {
require(order >= 0) { "Order of antiderivative must be non-negative" }
ListPolynomial(
buildList(coefficients.size + order) {
repeat(order) { add(zero) }
coefficients.mapIndexedTo(this) { index, c -> (1..order).fold(c) { acc, i -> acc / number(index + i) } }
}
)
}
/**
* Computes a definite integral of [this] polynomial in the specified [range].
*/
@UnstableKMathAPI
public fun <C : Comparable<C>> ListPolynomial<C>.integrate(
ring: Field<C>,
range: ClosedRange<C>,
): C = ring {
val antiderivative = antiderivative(ring)
antiderivative.substitute(ring, range.endInclusive) - antiderivative.substitute(ring, range.start)
}

View File

@ -0,0 +1,22 @@
/*
* 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
/**
* Marks declarations that give access to internal entities of polynomials delicate structure. Thus, it allows to
* optimize performance a bit by skipping standard steps, but such skips may cause critical errors if something is
* implemented badly. Make sure you fully read and understand documentation and don't break internal contracts.
*/
@RequiresOptIn(
message = "This declaration gives access to delicate internal structure of polynomials. " +
"It allows to optimize performance by skipping unnecessary arguments check. " +
"But at the same time makes it easy to make a mistake " +
"that will cause wrong computation result or even runtime error. " +
"Make sure you fully read and understand documentation.",
level = RequiresOptIn.Level.ERROR
)
public annotation class DelicatePolynomialAPI

View File

@ -0,0 +1,491 @@
/*
* 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.
*/
@file:Suppress("FunctionName", "NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
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<UInt>.cleanUp() = subList(0, indexOfLast { it != 0U } + 1)
/**
* Constructs [NumberedPolynomial] with provided coefficients map [coefs]. The map is used as is.
*/
@PublishedApi
internal inline fun <C> NumberedPolynomialAsIs(coefs: Map<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial<C>(coefs)
/**
* Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The collections will be transformed to map with [toMap] and then will be used as is.
*/
@PublishedApi
internal inline fun <C> NumberedPolynomialAsIs(pairs: Collection<Pair<List<UInt>, C>>) : NumberedPolynomial<C> = NumberedPolynomial<C>(pairs.toMap())
/**
* Constructs [NumberedPolynomial] with provided array of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The array will be transformed to map with [toMap] and then will be used as is.
*/
@PublishedApi
internal inline fun <C> NumberedPolynomialAsIs(vararg pairs: Pair<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial<C>(pairs.toMap())
/**
* Constructs [NumberedPolynomial] with provided coefficients map [coefs]. The map is used as is.
*
* **Be sure you read description of [NumberedPolynomial.coefficients]. Otherwise, you may make a mistake that will
* cause wrong computation result or even runtime error.**
*/
@DelicatePolynomialAPI
public inline fun <C> NumberedPolynomialWithoutCheck(coefs: Map<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial<C>(coefs)
/**
* Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The collections will be transformed to map with [toMap] and then will be used as is.
*
* **Be sure you read description of [NumberedPolynomial.coefficients]. Otherwise, you may make a mistake that will
* cause wrong computation result or even runtime error.**
*/
@DelicatePolynomialAPI
public inline fun <C> NumberedPolynomialWithoutCheck(pairs: Collection<Pair<List<UInt>, C>>) : NumberedPolynomial<C> = NumberedPolynomial<C>(pairs.toMap())
/**
* Constructs [NumberedPolynomial] with provided array of [pairs] of pairs "term's signature &mdash; term's coefficient".
* The array will be transformed to map with [toMap] and then will be used as is.
*
* **Be sure you read description of [NumberedPolynomial.coefficients]. Otherwise, you may make a mistake that will
* cause wrong computation result or even runtime error.**
*/
@DelicatePolynomialAPI
public inline fun <C> NumberedPolynomialWithoutCheck(vararg pairs: Pair<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial<C>(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.
*
* @see NumberedPolynomialWithoutCheck
*/
public fun <C> NumberedPolynomial(coefs: Map<List<UInt>, C>, add: (C, C) -> C) : NumberedPolynomial<C> =
NumberedPolynomialAsIs(
coefs.mapKeys({ key, _ -> key.cleanUp() }, add)
)
/**
* Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public fun <C> NumberedPolynomial(pairs: Collection<Pair<List<UInt>, C>>, add: (C, C) -> C) : NumberedPolynomial<C> =
NumberedPolynomialAsIs(
pairs.associateBy({ it.first.cleanUp() }, { it.second }, add)
)
/**
* Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public fun <C> NumberedPolynomial(vararg pairs: Pair<List<UInt>, C>, add: (C, C) -> C) : NumberedPolynomial<C> =
NumberedPolynomialAsIs(
pairs.asIterable().associateBy({ it.first.cleanUp() }, { it.second }, add)
)
// 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> A.NumberedPolynomial(coefs: Map<List<UInt>, C>) : NumberedPolynomial<C> = 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> NumberedPolynomialSpace<C, A>.NumberedPolynomial(coefs: Map<List<UInt>, C>) : NumberedPolynomial<C> = 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> NumberedRationalFunctionSpace<C, A>.NumberedPolynomial(coefs: Map<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial(coefs) { left: C, right: C -> left + right }
/**
* Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> A.NumberedPolynomial(pairs: Collection<Pair<List<UInt>, C>>) : NumberedPolynomial<C> = NumberedPolynomial(pairs, ::add)
/**
* Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> NumberedPolynomialSpace<C, A>.NumberedPolynomial(pairs: Collection<Pair<List<UInt>, C>>) : NumberedPolynomial<C> = NumberedPolynomial(pairs) { left: C, right: C -> left + right }
/**
* Constructs [NumberedPolynomial] with provided collection of [pairs] of pairs "term's signature &mdash; 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.
*/
public inline fun <C, A: Ring<C>> NumberedRationalFunctionSpace<C, A>.NumberedPolynomial(pairs: Collection<Pair<List<UInt>, C>>) : NumberedPolynomial<C> = NumberedPolynomial(pairs) { left: C, right: C -> left + right }
/**
* Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> A.NumberedPolynomial(vararg pairs: Pair<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial(*pairs) { left: C, right: C -> left + right }
/**
* Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> NumberedPolynomialSpace<C, A>.NumberedPolynomial(vararg pairs: Pair<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial(*pairs) { left: C, right: C -> left + right }
/**
* Constructs [NumberedPolynomial] with provided array [pairs] of pairs "term's signature &mdash; 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.
*
* @see NumberedPolynomialWithoutCheck
*/
public inline fun <C, A: Ring<C>> NumberedRationalFunctionSpace<C, A>.NumberedPolynomial(vararg pairs: Pair<List<UInt>, C>) : NumberedPolynomial<C> = NumberedPolynomial(*pairs) { left: C, right: C -> left + right }
/**
* Converts [this] constant to [NumberedPolynomial].
*/
public inline fun <C> C.asNumberedPolynomial() : NumberedPolynomial<C> = NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to this))
/**
* Marks DSL that allows to more simply create [NumberedPolynomial]s with good performance.
*
* For example, polynomial \(5 x_0^2 x_2^3 - 6 x_1\) can be described as
* ```
* Int.algebra {
* val numberedPolynomial : NumberedPolynomial<Int> = NumberedPolynomial {
* 5 { 0 inPowerOf 2u; 2 inPowerOf 3u } // 5 x_0^2 x_2^3 +
* (-6) { 1 inPowerOf 1u } // (-6) x_1^1
* }
* }
* ```
* @usesMathJax
*/
@DslMarker
@UnstableKMathAPI
internal annotation class NumberedPolynomialConstructorDSL1
/**
* Builder of [NumberedPolynomial] signature. It should be used as an implicit context for lambdas that describe term signature.
*/
@UnstableKMathAPI
@NumberedPolynomialConstructorDSL1
public class DSL1NumberedPolynomialTermSignatureBuilder {
/**
* 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<UInt> = ArrayList()
/**
* Builds the resulting signature.
*
* In fact, it just returns [signature] as regular signature of type `List<UInt>`.
*/
@PublishedApi
internal fun build(): List<UInt> = 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 (deg == 0u) return
val index = this
if (index > signature.lastIndex) {
signature.addAll(List(index - signature.lastIndex - 1) { 0u })
signature.add(deg)
} else {
signature[index] += deg
}
}
/**
* Declares power of variable #[this] of degree [deg].
*
* Declaring another power of the same variable will increase its degree by received degree.
*/
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.
*/
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.
*/
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
@NumberedPolynomialConstructorDSL1
public class DSL1NumberedPolynomialBuilder<C>(
/**
* 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? = null
) {
/**
* 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<List<UInt>, C> = if (initialCapacity != null) LinkedHashMap(initialCapacity) else LinkedHashMap()
/**
* Builds the resulting coefficients map.
*
* In fact, it just returns [coefficients] as regular coefficients map of type `Map<List<UInt>, C>`.
*/
@PublishedApi
internal fun build(): NumberedPolynomial<C> = NumberedPolynomial<C>(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<UInt>) {
coefficients.putOrChange(signature, this@with, add)
}
/**
* 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 inline infix fun C.with(noinline block: DSL1NumberedPolynomialTermSignatureBuilder.() -> 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 inline operator fun C.invoke(block: DSL1NumberedPolynomialTermSignatureBuilder.() -> Unit): Unit =
this with DSL1NumberedPolynomialTermSignatureBuilder().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_0^2 x_2^3 - 6 x_1\) can be described as
// * ```
// * Int.algebra {
// * val numberedPolynomial : NumberedPolynomial<Int> = NumberedPolynomial {
// * 5 { 0 inPowerOf 2u; 2 inPowerOf 3u } // 5 x_0^2 x_2^3 +
// * (-6) { 1 inPowerOf 1u } // (-6) x_1^1
// * }
// * }
// * ```
// * @usesMathJax
// */
// FIXME: For now this fabric does not let next two fabrics work. (See KT-52803.) Possible feature solutions:
// 1. `LowPriorityInOverloadResolution` becomes public. Then it should be applied to this function.
// 2. Union types are implemented. Then all three functions should be rewritten
// as one with single union type as a (context) receiver.
//@UnstableKMathAPI
//public inline fun <C, A: Ring<C>> A.NumberedPolynomialDSL1(initialCapacity: Int? = null, block: NumberedPolynomialBuilder<C>.() -> Unit) : NumberedPolynomial<C> = NumberedPolynomialBuilder(::add, initialCapacity).apply(block).build()
/**
* Creates [NumberedPolynomial] with lambda [block] in context of [this] ring of [NumberedPolynomial]s.
*
* For example, polynomial \(5 x_0^2 x_2^3 - 6 x_1\) can be described as
* ```
* Int.algebra {
* val numberedPolynomial : NumberedPolynomial<Int> = NumberedPolynomial {
* 5 { 0 inPowerOf 2u; 2 inPowerOf 3u } // 5 x_0^2 x_2^3 +
* (-6) { 1 inPowerOf 1u } // (-6) x_1^1
* }
* }
* ```
* @usesMathJax
*/
@UnstableKMathAPI
public inline fun <C, A: Ring<C>> NumberedPolynomialSpace<C, A>.NumberedPolynomialDSL1(initialCapacity: Int? = null, block: DSL1NumberedPolynomialBuilder<C>.() -> Unit) : NumberedPolynomial<C> = DSL1NumberedPolynomialBuilder({ 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_0^2 x_2^3 - 6 x_1\) can be described as
* ```
* Int.algebra {
* val numberedPolynomial : NumberedPolynomial<Int> = NumberedPolynomial {
* 5 { 0 inPowerOf 2u; 2 inPowerOf 3u } // 5 x_0^2 x_2^3 +
* (-6) { 1 inPowerOf 1u } // (-6) x_1^1
* }
* }
* ```
* @usesMathJax
*/
@UnstableKMathAPI
public inline fun <C, A: Ring<C>> NumberedRationalFunctionSpace<C, A>.NumberedPolynomialDSL1(initialCapacity: Int? = null, block: DSL1NumberedPolynomialBuilder<C>.() -> Unit) : NumberedPolynomial<C> = DSL1NumberedPolynomialBuilder({ 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.
*/
public fun <C, A: Ring<C>> A.NumberedRationalFunction(numeratorCoefficients: Map<List<UInt>, C>, denominatorCoefficients: Map<List<UInt>, C>): NumberedRationalFunction<C> =
NumberedRationalFunction<C>(
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.
*/
public fun <C, A: Ring<C>> NumberedRationalFunctionSpace<C, A>.NumberedRationalFunction(numeratorCoefficients: Map<List<UInt>, C>, denominatorCoefficients: Map<List<UInt>, C>): NumberedRationalFunction<C> =
NumberedRationalFunction<C>(
NumberedPolynomial(numeratorCoefficients),
NumberedPolynomial(denominatorCoefficients)
)
/**
* Constructs [NumberedRationalFunction] with provided [numerator] and unit denominator.
*/
public fun <C, A: Ring<C>> A.NumberedRationalFunction(numerator: NumberedPolynomial<C>): NumberedRationalFunction<C> =
NumberedRationalFunction<C>(numerator, NumberedPolynomial(mapOf(emptyList<UInt>() to one)))
/**
* Constructs [NumberedRationalFunction] with provided [numerator] and unit denominator.
*/
public fun <C, A: Ring<C>> NumberedRationalFunctionSpace<C, A>.NumberedRationalFunction(numerator: NumberedPolynomial<C>): NumberedRationalFunction<C> =
NumberedRationalFunction<C>(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.
*/
public fun <C, A: Ring<C>> NumberedRationalFunctionSpace<C, A>.NumberedRationalFunction(numeratorCoefficients: Map<List<UInt>, C>): NumberedRationalFunction<C> =
NumberedRationalFunction<C>(
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.
*/
public fun <C, A: Ring<C>> A.NumberedRationalFunction(numeratorCoefficients: Map<List<UInt>, C>): NumberedRationalFunction<C> =
NumberedRationalFunction<C>(
NumberedPolynomial(numeratorCoefficients),
NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to one))
)
///**
// * Converts [this] constant to [NumberedRationalFunction].
// */
//context(A)
//public fun <C, A: Ring<C>> C.asNumberedRationalFunction() : NumberedRationalFunction<C> =
// NumberedRationalFunction(
// NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to this)),
// NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to one))
// )
///**
// * Converts [this] constant to [NumberedRationalFunction].
// */
//context(NumberedRationalFunctionSpace<C, A>)
//public fun <C, A: Ring<C>> C.asNumberedRationalFunction() : NumberedRationalFunction<C> =
// NumberedRationalFunction(
// NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to this)),
// NumberedPolynomialAsIs(mapOf(emptyList<UInt>() to constantOne))
// )

View File

@ -0,0 +1,513 @@
/*
* 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 inline val <C, A : Ring<C>> A.numberedPolynomialSpace: NumberedPolynomialSpace<C, A>
get() = NumberedPolynomialSpace(this)
/**
* Creates a [NumberedPolynomialSpace]'s scope over a received ring.
*/
public inline fun <C, A : Ring<C>, R> A.numberedPolynomialSpace(block: NumberedPolynomialSpace<C, A>.() -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return NumberedPolynomialSpace(this).block()
}
/**
* Creates a [NumberedRationalFunctionSpace] over a received ring.
*/
public inline val <C, A : Ring<C>> A.numberedRationalFunctionSpace: NumberedRationalFunctionSpace<C, A>
get() = NumberedRationalFunctionSpace(this)
/**
* Creates a [NumberedRationalFunctionSpace]'s scope over a received ring.
*/
public inline fun <C, A : Ring<C>, R> A.numberedRationalFunctionSpace(block: NumberedRationalFunctionSpace<C, A>.() -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return NumberedRationalFunctionSpace(this).block()
}
/**
* Substitutes provided Double arguments [args] into [this] Double polynomial.
*/
public fun NumberedPolynomial<Double>.substitute(args: Map<Int, Double>): NumberedPolynomial<Double> = Double.algebra {
NumberedPolynomial<Double>(
buildMap(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())
}
putOrChange(newDegs, newC) { it -> it + newC }
}
}
)
}
/**
* Substitutes provided arguments [args] into [this] polynomial.
*/
public fun <C> NumberedPolynomial<C>.substitute(ring: Ring<C>, args: Map<Int, C>): NumberedPolynomial<C> = ring {
NumberedPolynomial<C>(
buildMap(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)
}
putOrChange(newDegs, newC) { it -> it + newC }
}
}
)
}
/**
* Substitutes provided arguments [args] into [this] polynomial.
*/ // TODO: To optimize boxing
@JvmName("substitutePolynomial")
public fun <C> NumberedPolynomial<C>.substitute(ring: Ring<C>, args: Map<Int, NumberedPolynomial<C>>) : NumberedPolynomial<C> =
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<C>(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 <C> NumberedPolynomial<C>.substitute(ring: Ring<C>, args: Map<Int, NumberedRationalFunction<C>>) : NumberedRationalFunction<C> =
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<C>(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<Double>.substitute(args: Map<Int, Double>): NumberedRationalFunction<Double> =
NumberedRationalFunction(numerator.substitute(args), denominator.substitute(args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/
public fun <C> NumberedRationalFunction<C>.substitute(ring: Ring<C>, args: Map<Int, C>): NumberedRationalFunction<C> =
NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/ // TODO: To optimize calculation
@JvmName("substitutePolynomial")
public fun <C> NumberedRationalFunction<C>.substitute(ring: Ring<C>, args: Map<Int, NumberedPolynomial<C>>) : NumberedRationalFunction<C> =
NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/ // TODO: To optimize calculation
@JvmName("substituteRationalFunction")
public fun <C> NumberedRationalFunction<C>.substitute(ring: Ring<C>, args: Map<Int, NumberedRationalFunction<C>>) : NumberedRationalFunction<C> =
ring.numberedRationalFunctionSpace {
numerator.substitute(ring, args) / denominator.substitute(ring, args)
}
/**
* Substitutes provided Double arguments [args] into [this] Double polynomial.
*/
public fun NumberedPolynomial<Double>.substitute(args: Buffer<Double>): NumberedPolynomial<Double> = Double.algebra {
val lastSubstitutionVariable = args.size - 1
NumberedPolynomial<Double>(
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())
}
putOrChange(newDegs, newC) { it -> it + newC }
}
}
)
}
/**
* Substitutes provided arguments [args] into [this] polynomial.
*/
public fun <C> NumberedPolynomial<C>.substitute(ring: Ring<C>, args: Buffer<C>): NumberedPolynomial<C> = ring {
val lastSubstitutionVariable = args.size - 1
NumberedPolynomial<C>(
buildMap<List<UInt>, 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)
}
putOrChange(newDegs, newC) { it -> it + newC }
}
}
)
}
/**
* Substitutes provided arguments [args] into [this] polynomial.
*/ // TODO: To optimize boxing
@JvmName("substitutePolynomial")
public fun <C> NumberedPolynomial<C>.substitute(ring: Ring<C>, args: Buffer<NumberedPolynomial<C>>) : NumberedPolynomial<C> =
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<C>(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 <C> NumberedPolynomial<C>.substitute(ring: Ring<C>, args: Buffer<NumberedRationalFunction<C>>) : NumberedRationalFunction<C> =
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<C>(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<Double>.substitute(args: Buffer<Double>): NumberedRationalFunction<Double> =
NumberedRationalFunction(numerator.substitute(args), denominator.substitute(args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/
public fun <C> NumberedRationalFunction<C>.substitute(ring: Ring<C>, args: Buffer<C>): NumberedRationalFunction<C> =
NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/ // TODO: To optimize calculation
@JvmName("substitutePolynomial")
public fun <C> NumberedRationalFunction<C>.substitute(ring: Ring<C>, args: Buffer<NumberedPolynomial<C>>) : NumberedRationalFunction<C> =
NumberedRationalFunction(numerator.substitute(ring, args), denominator.substitute(ring, args))
/**
* Substitutes provided arguments [args] into [this] rational function.
*/ // TODO: To optimize calculation
@JvmName("substituteRationalFunction")
public fun <C> NumberedRationalFunction<C>.substitute(ring: Ring<C>, args: Buffer<NumberedRationalFunction<C>>) : NumberedRationalFunction<C> =
ring.numberedRationalFunctionSpace {
numerator.substitute(ring, args) / denominator.substitute(ring, args)
}
internal const val fullSubstitutionExceptionMessage: String = "Fully substituting buffer should cover all variables of the polynomial."
/**
* Substitutes provided Double arguments [args] into [this] Double polynomial.
*/
public fun NumberedPolynomial<Double>.substituteFully(args: Buffer<Double>): Double = Double.algebra {
val lastSubstitutionVariable = args.size - 1
require(coefficients.keys.all { it.lastIndex <= lastSubstitutionVariable }) { fullSubstitutionExceptionMessage }
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 <C> NumberedPolynomial<C>.substituteFully(ring: Ring<C>, args: Buffer<C>): C = ring {
val lastSubstitutionVariable = args.size - 1
require(coefficients.keys.all { it.lastIndex <= lastSubstitutionVariable }) { fullSubstitutionExceptionMessage }
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<Double>.substituteFully(args: Buffer<Double>): Double =
numerator.substituteFully(args) / denominator.substituteFully(args)
/**
* Substitutes provided arguments [args] into [this] rational function.
*/
public fun <C> NumberedRationalFunction<C>.substituteFully(ring: Field<C>, args: Buffer<C>): C = ring {
numerator.substituteFully(ring, args) / denominator.substituteFully(ring, args)
}
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> NumberedPolynomial<C>.asFunctionOver(ring: A): (Buffer<C>) -> C = { substituteFully(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> NumberedPolynomial<C>.asFunctionOfConstantOver(ring: A): (Buffer<C>) -> C = { substituteFully(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> NumberedPolynomial<C>.asFunctionOfPolynomialOver(ring: A): (Buffer<NumberedPolynomial<C>>) -> NumberedPolynomial<C> = { substitute(ring, it) }
/**
* Represent [this] polynomial as a regular context-less function.
*/
public fun <C, A : Ring<C>> NumberedPolynomial<C>.asFunctionOfRationalFunctionOver(ring: A): (Buffer<NumberedRationalFunction<C>>) -> NumberedRationalFunction<C> = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Field<C>> NumberedRationalFunction<C>.asFunctionOver(ring: A): (Buffer<C>) -> C = { substituteFully(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Field<C>> NumberedRationalFunction<C>.asFunctionOfConstantOver(ring: A): (Buffer<C>) -> C = { substituteFully(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Ring<C>> NumberedRationalFunction<C>.asFunctionOfPolynomialOver(ring: A): (Buffer<NumberedPolynomial<C>>) -> NumberedRationalFunction<C> = { substitute(ring, it) }
/**
* Represent [this] rational function as a regular context-less function.
*/
public fun <C, A : Ring<C>> NumberedRationalFunction<C>.asFunctionOfRationalFunctionOver(ring: A): (Buffer<NumberedRationalFunction<C>>) -> NumberedRationalFunction<C> = { substitute(ring, it) }
/**
* Returns algebraic derivative of received polynomial with respect to provided variable.
*/
@UnstableKMathAPI
public fun <C, A : Ring<C>> NumberedPolynomial<C>.derivativeWithRespectTo(
ring: A,
variable: Int,
): NumberedPolynomial<C> = ring {
NumberedPolynomial<C>(
buildMap(coefficients.count { it.key.getOrElse(variable) { 0u } >= 1u }) {
coefficients
.forEach { (degs, c) ->
if (degs.lastIndex < 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 <C, A : Ring<C>> NumberedPolynomial<C>.nthDerivativeWithRespectTo(
ring: A,
variable: Int,
order: UInt
): NumberedPolynomial<C> = ring {
if (order == 0u) return this@nthDerivativeWithRespectTo
NumberedPolynomial<C>(
buildMap(coefficients.count { it.key.getOrElse(variable) { 0u } >= order }) {
coefficients
.forEach { (degs, c) ->
if (degs.lastIndex < 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 <C, A : Ring<C>> NumberedPolynomial<C>.nthDerivativeWithRespectTo(
ring: A,
variablesAndOrders: Map<Int, UInt>,
): NumberedPolynomial<C> = ring {
val filteredVariablesAndOrders = variablesAndOrders.filterValues { it != 0u }
if (filteredVariablesAndOrders.isEmpty()) return this@nthDerivativeWithRespectTo
val maxRespectedVariable = filteredVariablesAndOrders.keys.maxOrNull()!!
NumberedPolynomial<C>(
buildMap(coefficients.size) {
coefficients
.forEach { (degs, c) ->
if (degs.lastIndex < 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 <C, A : Field<C>> NumberedPolynomial<C>.antiderivativeWithRespectTo(
ring: A,
variable: Int,
): NumberedPolynomial<C> = ring {
NumberedPolynomial<C>(
buildMap(coefficients.size) {
coefficients
.forEach { (degs, c) ->
put(
List(max(variable + 1, degs.size)) { degs.getOrElse(it) { 0u } + if (it != variable) 0u else 1u },
c / multiplyByDoubling(one, degs.getOrElse(variable) { 0u } + 1u)
)
}
}
)
}
/**
* Returns algebraic antiderivative of received polynomial with respect to provided variable of specified order.
*/
@UnstableKMathAPI
public fun <C, A : Field<C>> NumberedPolynomial<C>.nthAntiderivativeWithRespectTo(
ring: A,
variable: Int,
order: UInt
): NumberedPolynomial<C> = ring {
if (order == 0u) return this@nthAntiderivativeWithRespectTo
NumberedPolynomial<C>(
buildMap(coefficients.size) {
coefficients
.forEach { (degs, c) ->
put(
List(max(variable + 1, degs.size)) { degs.getOrElse(it) { 0u } + if (it != variable) 0u else order },
degs.getOrElse(variable) { 0u }.let { deg ->
(deg + 1u .. deg + order)
.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 <C, A : Field<C>> NumberedPolynomial<C>.nthAntiderivativeWithRespectTo(
ring: A,
variablesAndOrders: Map<Int, UInt>,
): NumberedPolynomial<C> = ring {
val filteredVariablesAndOrders = variablesAndOrders.filterValues { it != 0u }
if (filteredVariablesAndOrders.isEmpty()) return this@nthAntiderivativeWithRespectTo
val maxRespectedVariable = filteredVariablesAndOrders.keys.maxOrNull()!!
NumberedPolynomial<C>(
buildMap(coefficients.size) {
coefficients
.forEach { (degs, c) ->
put(
List(max(maxRespectedVariable + 1, degs.size)) { degs.getOrElse(it) { 0u } + filteredVariablesAndOrders.getOrElse(it) { 0u } },
filteredVariablesAndOrders.entries.fold(c) { acc1, (variable, order) ->
degs.getOrElse(variable) { 0u }.let { deg ->
(deg + 1u .. deg + order)
.fold(acc1) { acc, ord -> acc / multiplyByDoubling(one, ord) }
}
}
)
}
}
)
}

View File

@ -0,0 +1,589 @@
/*
* 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.invoke
import space.kscience.kmath.operations.Field
import kotlin.jvm.JvmInline
import kotlin.test.Test
import kotlin.test.assertEquals
@JvmInline
value class Expr(val expr: String)
object ExprRing : Field<Expr> {
override fun Expr.unaryMinus(): Expr = Expr("-${expr}")
override fun add(left: Expr, right: Expr): Expr = Expr("(${left.expr} + ${right.expr})")
override fun multiply(left: Expr, right: Expr): Expr = Expr("(${left.expr} * ${right.expr})")
override val zero: Expr = Expr("0")
override val one: Expr = Expr("1")
override fun divide(left: Expr, right: Expr): Expr = Expr("(${left.expr} / ${right.expr})")
override fun scale(a: Expr, value: Double): Expr = Expr("(${a.expr} / $value)")
}
class AlgebraicStubTest {
@Test
fun test_addMultipliedBySquaring_for_UInt() {
ExprRing {
assertEquals(
"57",
addMultipliedByDoubling(Expr("57"), Expr("179"), 0u).expr,
"tried addMultipliedBySquaring(57, 179, 0u)"
)
assertEquals(
"(57 + 179)",
addMultipliedByDoubling(Expr("57"), Expr("179"), 1u).expr,
"tried addMultipliedBySquaring(57, 179, 1u)"
)
assertEquals(
"(57 + (179 + 179))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 2u).expr,
"tried addMultipliedBySquaring(57, 179, 2u)"
)
assertEquals(
"((57 + 179) + (179 + 179))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 3u).expr,
"tried addMultipliedBySquaring(57, 179, 3u)"
)
assertEquals(
"(57 + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 4u).expr,
"tried addMultipliedBySquaring(57, 179, 4u)"
)
assertEquals(
"((57 + 179) + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 5u).expr,
"tried addMultipliedBySquaring(57, 179, 5u)"
)
assertEquals(
"((57 + (179 + 179)) + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 6u).expr,
"tried addMultipliedBySquaring(57, 179, 6u)"
)
assertEquals(
"(((57 + 179) + (179 + 179)) + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 7u).expr,
"tried addMultipliedBySquaring(57, 179, 7u)"
)
assertEquals(
"(57 + (((179 + 179) + (179 + 179)) + ((179 + 179) + (179 + 179))))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 8u).expr,
"tried addMultipliedBySquaring(57, 179, 8u)"
)
}
}
@Test
fun test_multiplyBySquaring_for_UInt() {
ExprRing {
assertEquals(
"0",
multiplyByDoubling(Expr("57"), 0u).expr,
"tried multiplyBySquaring(57, 0u)"
)
assertEquals(
"57",
multiplyByDoubling(Expr("57"), 1u).expr,
"tried multiplyBySquaring(57, 1u)"
)
assertEquals(
"(57 + 57)",
multiplyByDoubling(Expr("57"), 2u).expr,
"tried multiplyBySquaring(57, 2u)"
)
assertEquals(
"(57 + (57 + 57))",
multiplyByDoubling(Expr("57"), 3u).expr,
"tried multiplyBySquaring(57, 3u)"
)
assertEquals(
"((57 + 57) + (57 + 57))",
multiplyByDoubling(Expr("57"), 4u).expr,
"tried multiplyBySquaring(57, 4u)"
)
assertEquals(
"(57 + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 5u).expr,
"tried multiplyBySquaring(57, 5u)"
)
assertEquals(
"((57 + 57) + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 6u).expr,
"tried multiplyBySquaring(57, 6u)"
)
assertEquals(
"((57 + (57 + 57)) + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 7u).expr,
"tried multiplyBySquaring(57, 7u)"
)
assertEquals(
"(((57 + 57) + (57 + 57)) + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 8u).expr,
"tried multiplyBySquaring(57, 8u)"
)
}
}
@Test
fun test_addMultipliedBySquaring_for_Int() {
ExprRing {
assertEquals(
"57",
addMultipliedByDoubling(Expr("57"), Expr("179"), 0).expr,
"tried addMultipliedBySquaring(57, 179, 0)"
)
assertEquals(
"(57 + 179)",
addMultipliedByDoubling(Expr("57"), Expr("179"), 1).expr,
"tried addMultipliedBySquaring(57, 179, 1)"
)
assertEquals(
"(57 + -179)",
addMultipliedByDoubling(Expr("57"), Expr("179"), -1).expr,
"tried addMultipliedBySquaring(57, 179, -1)"
)
assertEquals(
"(57 + (179 + 179))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 2).expr,
"tried addMultipliedBySquaring(57, 179, 2)"
)
assertEquals(
"(57 + (-179 + -179))",
addMultipliedByDoubling(Expr("57"), Expr("179"), -2).expr,
"tried addMultipliedBySquaring(57, 179, -2)"
)
assertEquals(
"((57 + 179) + (179 + 179))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 3).expr,
"tried addMultipliedBySquaring(57, 179, 3)"
)
assertEquals(
"((57 + -179) + (-179 + -179))",
addMultipliedByDoubling(Expr("57"), Expr("179"), -3).expr,
"tried addMultipliedBySquaring(57, 179, -3)"
)
assertEquals(
"(57 + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 4).expr,
"tried addMultipliedBySquaring(57, 179, 4)"
)
assertEquals(
"(57 + ((-179 + -179) + (-179 + -179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), -4).expr,
"tried addMultipliedBySquaring(57, 179, -4)"
)
assertEquals(
"((57 + 179) + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 5).expr,
"tried addMultipliedBySquaring(57, 179, 5)"
)
assertEquals(
"((57 + -179) + ((-179 + -179) + (-179 + -179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), -5).expr,
"tried addMultipliedBySquaring(57, 179, -5)"
)
assertEquals(
"((57 + (179 + 179)) + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 6).expr,
"tried addMultipliedBySquaring(57, 179, 6)"
)
assertEquals(
"((57 + (-179 + -179)) + ((-179 + -179) + (-179 + -179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), -6).expr,
"tried addMultipliedBySquaring(57, 179, -6)"
)
assertEquals(
"(((57 + 179) + (179 + 179)) + ((179 + 179) + (179 + 179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 7).expr,
"tried addMultipliedBySquaring(57, 179, 7)"
)
assertEquals(
"(((57 + -179) + (-179 + -179)) + ((-179 + -179) + (-179 + -179)))",
addMultipliedByDoubling(Expr("57"), Expr("179"), -7).expr,
"tried addMultipliedBySquaring(57, 179, -7)"
)
assertEquals(
"(57 + (((179 + 179) + (179 + 179)) + ((179 + 179) + (179 + 179))))",
addMultipliedByDoubling(Expr("57"), Expr("179"), 8).expr,
"tried addMultipliedBySquaring(57, 179, 8)"
)
assertEquals(
"(57 + (((-179 + -179) + (-179 + -179)) + ((-179 + -179) + (-179 + -179))))",
addMultipliedByDoubling(Expr("57"), Expr("179"), -8).expr,
"tried addMultipliedBySquaring(57, 179, -8)"
)
}
}
@Test
fun test_multiplyBySquaring_for_Int() {
ExprRing {
assertEquals(
"0",
multiplyByDoubling(Expr("57"), 0).expr,
"tried multiplyBySquaring(57, 0)"
)
assertEquals(
"57",
multiplyByDoubling(Expr("57"), 1).expr,
"tried multiplyBySquaring(57, 1)"
)
assertEquals(
"-57",
multiplyByDoubling(Expr("57"), -1).expr,
"tried multiplyBySquaring(57, -1)"
)
assertEquals(
"(57 + 57)",
multiplyByDoubling(Expr("57"), 2).expr,
"tried multiplyBySquaring(57, 2)"
)
assertEquals(
"(-57 + -57)",
multiplyByDoubling(Expr("57"), -2).expr,
"tried multiplyBySquaring(57, -2)"
)
assertEquals(
"(57 + (57 + 57))",
multiplyByDoubling(Expr("57"), 3).expr,
"tried multiplyBySquaring(57, 3)"
)
assertEquals(
"(-57 + (-57 + -57))",
multiplyByDoubling(Expr("57"), -3).expr,
"tried multiplyBySquaring(57, -3)"
)
assertEquals(
"((57 + 57) + (57 + 57))",
multiplyByDoubling(Expr("57"), 4).expr,
"tried multiplyBySquaring(57, 4)"
)
assertEquals(
"((-57 + -57) + (-57 + -57))",
multiplyByDoubling(Expr("57"), -4).expr,
"tried multiplyBySquaring(57, -4)"
)
assertEquals(
"(57 + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 5).expr,
"tried multiplyBySquaring(57, 5)"
)
assertEquals(
"(-57 + ((-57 + -57) + (-57 + -57)))",
multiplyByDoubling(Expr("57"), -5).expr,
"tried multiplyBySquaring(57, -5)"
)
assertEquals(
"((57 + 57) + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 6).expr,
"tried multiplyBySquaring(57, 6)"
)
assertEquals(
"((-57 + -57) + ((-57 + -57) + (-57 + -57)))",
multiplyByDoubling(Expr("57"), -6).expr,
"tried multiplyBySquaring(57, -6)"
)
assertEquals(
"((57 + (57 + 57)) + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 7).expr,
"tried multiplyBySquaring(57, 7)"
)
assertEquals(
"((-57 + (-57 + -57)) + ((-57 + -57) + (-57 + -57)))",
multiplyByDoubling(Expr("57"), -7).expr,
"tried multiplyBySquaring(57, -7)"
)
assertEquals(
"(((57 + 57) + (57 + 57)) + ((57 + 57) + (57 + 57)))",
multiplyByDoubling(Expr("57"), 8).expr,
"tried multiplyBySquaring(57, 8)"
)
assertEquals(
"(((-57 + -57) + (-57 + -57)) + ((-57 + -57) + (-57 + -57)))",
multiplyByDoubling(Expr("57"), -8).expr,
"tried multiplyBySquaring(57, -8)"
)
}
}
@Test
fun test_multiplyExponentiationBySquaring_for_UInt() {
ExprRing {
assertEquals(
"57",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 0u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 0u)"
)
assertEquals(
"(57 * 179)",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 1u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 1u)"
)
assertEquals(
"(57 * (179 * 179))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 2u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 2u)"
)
assertEquals(
"((57 * 179) * (179 * 179))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 3u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 3u)"
)
assertEquals(
"(57 * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 4u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 4u)"
)
assertEquals(
"((57 * 179) * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 5u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 5u)"
)
assertEquals(
"((57 * (179 * 179)) * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 6u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 6u)"
)
assertEquals(
"(((57 * 179) * (179 * 179)) * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 7u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 7u)"
)
assertEquals(
"(57 * (((179 * 179) * (179 * 179)) * ((179 * 179) * (179 * 179))))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 8u).expr,
"tried multiplyExponentiationBySquaring(57, 179, 8u)"
)
}
}
@Test
fun test_exponentiationBySquaring_for_UInt() {
ExprRing {
assertEquals(
"0",
exponentiateBySquaring(Expr("57"), 0u).expr,
"tried exponentiationBySquaring(57, 0u)"
)
assertEquals(
"57",
exponentiateBySquaring(Expr("57"), 1u).expr,
"tried exponentiationBySquaring(57, 1u)"
)
assertEquals(
"(57 * 57)",
exponentiateBySquaring(Expr("57"), 2u).expr,
"tried exponentiationBySquaring(57, 2u)"
)
assertEquals(
"(57 * (57 * 57))",
exponentiateBySquaring(Expr("57"), 3u).expr,
"tried exponentiationBySquaring(57, 3u)"
)
assertEquals(
"((57 * 57) * (57 * 57))",
exponentiateBySquaring(Expr("57"), 4u).expr,
"tried exponentiationBySquaring(57, 4u)"
)
assertEquals(
"(57 * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 5u).expr,
"tried exponentiationBySquaring(57, 5u)"
)
assertEquals(
"((57 * 57) * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 6u).expr,
"tried exponentiationBySquaring(57, 6u)"
)
assertEquals(
"((57 * (57 * 57)) * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 7u).expr,
"tried exponentiationBySquaring(57, 7u)"
)
assertEquals(
"(((57 * 57) * (57 * 57)) * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 8u).expr,
"tried exponentiationBySquaring(57, 8u)"
)
}
}
@Test
fun test_multiplyExponentiationBySquaring_for_Int() {
ExprRing {
assertEquals(
"57",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 0).expr,
"tried multiplyExponentiationBySquaring(57, 179, 0)"
)
assertEquals(
"(57 * 179)",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 1).expr,
"tried multiplyExponentiationBySquaring(57, 179, 1)"
)
assertEquals(
"(57 * (1 / 179))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -1).expr,
"tried multiplyExponentiationBySquaring(57, 179, -1)"
)
assertEquals(
"(57 * (179 * 179))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 2).expr,
"tried multiplyExponentiationBySquaring(57, 179, 2)"
)
assertEquals(
"(57 * ((1 / 179) * (1 / 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -2).expr,
"tried multiplyExponentiationBySquaring(57, 179, -2)"
)
assertEquals(
"((57 * 179) * (179 * 179))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 3).expr,
"tried multiplyExponentiationBySquaring(57, 179, 3)"
)
assertEquals(
"((57 * (1 / 179)) * ((1 / 179) * (1 / 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -3).expr,
"tried multiplyExponentiationBySquaring(57, 179, -3)"
)
assertEquals(
"(57 * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 4).expr,
"tried multiplyExponentiationBySquaring(57, 179, 4)"
)
assertEquals(
"(57 * (((1 / 179) * (1 / 179)) * ((1 / 179) * (1 / 179))))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -4).expr,
"tried multiplyExponentiationBySquaring(57, 179, -4)"
)
assertEquals(
"((57 * 179) * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 5).expr,
"tried multiplyExponentiationBySquaring(57, 179, 5)"
)
assertEquals(
"((57 * (1 / 179)) * (((1 / 179) * (1 / 179)) * ((1 / 179) * (1 / 179))))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -5).expr,
"tried multiplyExponentiationBySquaring(57, 179, -5)"
)
assertEquals(
"((57 * (179 * 179)) * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 6).expr,
"tried multiplyExponentiationBySquaring(57, 179, 6)"
)
assertEquals(
"((57 * ((1 / 179) * (1 / 179))) * (((1 / 179) * (1 / 179)) * ((1 / 179) * (1 / 179))))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -6).expr,
"tried multiplyExponentiationBySquaring(57, 179, -6)"
)
assertEquals(
"(((57 * 179) * (179 * 179)) * ((179 * 179) * (179 * 179)))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 7).expr,
"tried multiplyExponentiationBySquaring(57, 179, 7)"
)
assertEquals(
"(((57 * (1 / 179)) * ((1 / 179) * (1 / 179))) * (((1 / 179) * (1 / 179)) * ((1 / 179) * (1 / 179))))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -7).expr,
"tried multiplyExponentiationBySquaring(57, 179, -7)"
)
assertEquals(
"(57 * (((179 * 179) * (179 * 179)) * ((179 * 179) * (179 * 179))))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), 8).expr,
"tried multiplyExponentiationBySquaring(57, 179, 8)"
)
assertEquals(
"(57 * ((((1 / 179) * (1 / 179)) * ((1 / 179) * (1 / 179))) * (((1 / 179) * (1 / 179)) * ((1 / 179) * (1 / 179)))))",
multiplyExponentiatedBySquaring(Expr("57"), Expr("179"), -8).expr,
"tried multiplyExponentiationBySquaring(57, 179, -8)"
)
}
}
@Test
fun test_exponentiationBySquaring_for_Int() {
ExprRing {
assertEquals(
"0",
exponentiateBySquaring(Expr("57"), 0).expr,
"tried exponentiationBySquaring(57, 0)"
)
assertEquals(
"57",
exponentiateBySquaring(Expr("57"), 1).expr,
"tried exponentiationBySquaring(57, 1)"
)
assertEquals(
"(1 / 57)",
exponentiateBySquaring(Expr("57"), -1).expr,
"tried exponentiationBySquaring(57, -1)"
)
assertEquals(
"(57 * 57)",
exponentiateBySquaring(Expr("57"), 2).expr,
"tried exponentiationBySquaring(57, 2)"
)
assertEquals(
"((1 / 57) * (1 / 57))",
exponentiateBySquaring(Expr("57"), -2).expr,
"tried exponentiationBySquaring(57, -2)"
)
assertEquals(
"(57 * (57 * 57))",
exponentiateBySquaring(Expr("57"), 3).expr,
"tried exponentiationBySquaring(57, 3)"
)
assertEquals(
"((1 / 57) * ((1 / 57) * (1 / 57)))",
exponentiateBySquaring(Expr("57"), -3).expr,
"tried exponentiationBySquaring(57, -3)"
)
assertEquals(
"((57 * 57) * (57 * 57))",
exponentiateBySquaring(Expr("57"), 4).expr,
"tried exponentiationBySquaring(57, 4)"
)
assertEquals(
"(((1 / 57) * (1 / 57)) * ((1 / 57) * (1 / 57)))",
exponentiateBySquaring(Expr("57"), -4).expr,
"tried exponentiationBySquaring(57, -4)"
)
assertEquals(
"(57 * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 5).expr,
"tried exponentiationBySquaring(57, 5)"
)
assertEquals(
"((1 / 57) * (((1 / 57) * (1 / 57)) * ((1 / 57) * (1 / 57))))",
exponentiateBySquaring(Expr("57"), -5).expr,
"tried exponentiationBySquaring(57, -5)"
)
assertEquals(
"((57 * 57) * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 6).expr,
"tried exponentiationBySquaring(57, 6)"
)
assertEquals(
"(((1 / 57) * (1 / 57)) * (((1 / 57) * (1 / 57)) * ((1 / 57) * (1 / 57))))",
exponentiateBySquaring(Expr("57"), -6).expr,
"tried exponentiationBySquaring(57, -6)"
)
assertEquals(
"((57 * (57 * 57)) * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 7).expr,
"tried exponentiationBySquaring(57, 7)"
)
assertEquals(
"(((1 / 57) * ((1 / 57) * (1 / 57))) * (((1 / 57) * (1 / 57)) * ((1 / 57) * (1 / 57))))",
exponentiateBySquaring(Expr("57"), -7).expr,
"tried exponentiationBySquaring(57, -7)"
)
assertEquals(
"(((57 * 57) * (57 * 57)) * ((57 * 57) * (57 * 57)))",
exponentiateBySquaring(Expr("57"), 8).expr,
"tried exponentiationBySquaring(57, 8)"
)
assertEquals(
"((((1 / 57) * (1 / 57)) * ((1 / 57) * (1 / 57))) * (((1 / 57) * (1 / 57)) * ((1 / 57) * (1 / 57))))",
exponentiateBySquaring(Expr("57"), -8).expr,
"tried exponentiationBySquaring(57, -8)"
)
}
}
}

View File

@ -0,0 +1,127 @@
/*
* 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.expressions.Symbol
import space.kscience.kmath.functions.testUtils.t
import space.kscience.kmath.functions.testUtils.x
import space.kscience.kmath.functions.testUtils.y
import space.kscience.kmath.functions.testUtils.z
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.algebra
import space.kscience.kmath.operations.invoke
import kotlin.test.Test
import kotlin.test.assertEquals
class LabeledConstructorsTest {
@Test
@UnstableKMathAPI
fun testDSL1() {
assertEquals(
LabeledPolynomialAsIs(
mapOf(x to 2u, z to 3u) to 5,
mapOf(y to 1u) to -6,
),
Int.algebra.labeledPolynomialSpace {
LabeledPolynomialDSL1 {
5 { x pow 2u; z pow 3u }
(-6) { y pow 1u }
}
},
"test 1"
)
assertEquals(
LabeledPolynomialAsIs(
mapOf<Symbol, UInt>() to -1,
),
Int.algebra.labeledPolynomialSpace {
LabeledPolynomialDSL1 {
5 { }
(-6) { }
}
},
"test 2"
)
assertEquals(
LabeledPolynomialAsIs(
mapOf(x to 2u) to -1,
),
Int.algebra.labeledPolynomialSpace {
LabeledPolynomialDSL1 {
5 { x pow 1u; x pow 1u }
(-6) { x pow 2u }
}
},
"test 3"
)
assertEquals(
LabeledPolynomialAsIs(
mapOf(x to 2u) to -1,
),
Int.algebra.labeledPolynomialSpace {
LabeledPolynomialDSL1 {
5 { x pow 1u; x pow 1u }
(-6) { x pow 2u; z pow 0u }
}
},
"test 3"
)
}
@Test
@UnstableKMathAPI
fun testFabric() {
assertEquals(
LabeledPolynomialAsIs(
mapOf(x to 2u, z to 3u) to 5,
mapOf(y to 1u) to -6,
),
Int.algebra {
LabeledPolynomial(
mapOf(x to 2u, z to 3u) to 5,
mapOf(y to 1u) to -6,
)
},
"test 1"
)
assertEquals(
LabeledPolynomialAsIs(
mapOf(x to 2u, z to 3u) to 5,
mapOf(y to 1u) to -6,
),
Int.algebra {
LabeledPolynomial(
mapOf(x to 2u, y to 0u, z to 3u, t to 0u) to 5,
mapOf(x to 0u, y to 1u, z to 0u, t to 0u) to -6,
)
},
"test 2"
)
assertEquals(
LabeledPolynomialAsIs(
mapOf<Symbol, UInt>() to -1,
),
Int.algebra {
LabeledPolynomial(
mapOf(x to 0u) to 5,
mapOf(y to 0u, z to 0u) to -6,
)
},
"test 3"
)
assertEquals(
LabeledPolynomialAsIs(
mapOf<Symbol, UInt>() to 0,
),
Int.algebra {
LabeledPolynomial(
mapOf(x to 0u) to 5,
mapOf(z to 0u, t to 0u) to -5,
)
},
"test 4"
)
}
}

View File

@ -0,0 +1,544 @@
/*
* 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.
*/
@file:Suppress("LocalVariableName")
package space.kscience.kmath.functions
import space.kscience.kmath.functions.testUtils.IntModuloRing
import space.kscience.kmath.functions.testUtils.ListPolynomial
import space.kscience.kmath.functions.testUtils.Rational
import space.kscience.kmath.functions.testUtils.RationalField
import kotlin.test.*
class ListPolynomialTest {
@Test
fun test_Polynomial_Int_plus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7)),
ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) + -3,
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)) + 2,
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
ListPolynomial(Rational(-2)) + 2,
"test 3"
)
val polynomial_4 = ListPolynomial<Rational>()
assertSame(
polynomial_4,
polynomial_4 + 0,
"test 4"
)
val polynomial_5 = ListPolynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7))
assertSame(
polynomial_5,
polynomial_5 + 0,
"test 5"
)
assertEquals(
ListPolynomial(Rational(-1), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)) + 1,
"test 6"
)
assertEquals(
ListPolynomial(Rational(-1)),
ListPolynomial(Rational(-2)) + 1,
"test 7"
)
assertEquals(
ListPolynomial(Rational(2)),
ListPolynomial<Rational>() + 2,
"test 8"
)
}
}
@Test
fun test_Polynomial_Int_minus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(32, 9), Rational(-8, 9), Rational(-8, 7)),
ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) - -3,
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(2), Rational(0), Rational(0), Rational(0)) - 2,
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
ListPolynomial(Rational(2)) - 2,
"test 3"
)
val polynomial_4 = ListPolynomial<Rational>()
assertSame(
polynomial_4,
polynomial_4 - 0,
"test 4"
)
val polynomial_5 = ListPolynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7))
assertEquals(
polynomial_5,
polynomial_5 - 0,
"test 5"
)
assertEquals(
ListPolynomial(Rational(1), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(2), Rational(0), Rational(0), Rational(0)) - 1,
"test 6"
)
assertEquals(
ListPolynomial(Rational(1)),
ListPolynomial(Rational(2)) - 1,
"test 7"
)
assertEquals(
ListPolynomial(Rational(-2)),
ListPolynomial<Rational>() - 2,
"test 8"
)
}
}
@Test
fun test_Polynomial_Int_times() {
IntModuloRing(35).listPolynomialSpace {
assertEquals(
ListPolynomial(34, 2, 1, 20, 2),
ListPolynomial(22, 26, 13, 15, 26) * 27,
"test 1"
)
assertEquals(
ListPolynomial(0, 0, 0, 0, 0),
ListPolynomial(7, 0, 49, 21, 14) * 15,
"test 2"
)
val polynomial = ListPolynomial(22, 26, 13, 15, 26)
assertSame(
zero,
polynomial * 0,
"test 3"
)
assertSame(
polynomial,
polynomial * 1,
"test 4"
)
}
}
@Test
fun test_Int_Polynomial_plus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7)),
-3 + ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)),
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
2 + ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
2 + ListPolynomial(Rational(-2)),
"test 3"
)
val polynomial_4 = ListPolynomial<Rational>()
assertSame(
polynomial_4,
0 + polynomial_4,
"test 4"
)
val polynomial_5 = ListPolynomial<Rational>(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7))
assertSame(
polynomial_5,
0 + polynomial_5,
"test 5"
)
assertEquals(
ListPolynomial(Rational(-1), Rational(0), Rational(0), Rational(0)),
1 + ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 6"
)
assertEquals(
ListPolynomial(Rational(-1)),
1 + ListPolynomial(Rational(-2)),
"test 7"
)
assertEquals(
ListPolynomial(Rational(2)),
2 + ListPolynomial(),
"test 8"
)
}
}
@Test
fun test_Int_Polynomial_minus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(32, 9), Rational(-8, 9), Rational(-8, 7)),
3 - ListPolynomial(Rational(-5, 9), Rational(8, 9), Rational(8, 7)),
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
-2 - ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
-2 - ListPolynomial(Rational(-2)),
"test 3"
)
assertEquals(
ListPolynomial(Rational(-32, 9), Rational(-8, -9), Rational(8, 7)),
0 - ListPolynomial(Rational(32, 9), Rational(-8, 9), Rational(-8, 7)),
"test 4"
)
assertEquals(
ListPolynomial(),
0 - ListPolynomial(),
"test 5"
)
assertEquals(
ListPolynomial(Rational(1), Rational(0), Rational(0), Rational(0)),
-1 - ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 6"
)
assertEquals(
ListPolynomial(Rational(1)),
-1 - ListPolynomial(Rational(-2)),
"test 7"
)
assertEquals(
ListPolynomial(Rational(-2)),
-2 - ListPolynomial(),
"test 8"
)
}
}
@Test
fun test_Int_Polynomial_times() {
IntModuloRing(35).listPolynomialSpace {
assertEquals(
ListPolynomial(34, 2, 1, 20, 2),
27 * ListPolynomial(22, 26, 13, 15, 26),
"test 1"
)
assertEquals(
ListPolynomial(0, 0, 0, 0, 0),
15 * ListPolynomial(7, 0, 49, 21, 14),
"test 2"
)
val polynomial = ListPolynomial(22, 26, 13, 15, 26)
assertSame(
zero,
0 * polynomial,
"test 3"
)
assertSame(
polynomial,
1 * polynomial,
"test 4"
)
}
}
@Test
fun test_Polynomial_Constant_plus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7)),
ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) + Rational(-3),
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)) + Rational(2),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
ListPolynomial(Rational(-2)) + Rational(2),
"test 3"
)
assertEquals(
ListPolynomial(Rational(0)),
ListPolynomial<Rational>() + Rational(0),
"test 4"
)
assertEquals(
ListPolynomial(Rational(-1), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)) + Rational(1),
"test 5"
)
assertEquals(
ListPolynomial(Rational(-1)),
ListPolynomial(Rational(-2)) + Rational(1),
"test 6"
)
assertEquals(
ListPolynomial(Rational(2)),
ListPolynomial<Rational>() + Rational(2),
"test 7"
)
}
}
@Test
fun test_Polynomial_Constant_minus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(32, 9), Rational(-8, 9), Rational(-8, 7)),
ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) - Rational(-3),
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(2), Rational(0), Rational(0), Rational(0)) - Rational(2),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
ListPolynomial(Rational(2)) - Rational(2),
"test 3"
)
assertEquals(
ListPolynomial(Rational(0)),
ListPolynomial<Rational>() - Rational(0),
"test 4"
)
assertEquals(
ListPolynomial(Rational(1), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(2), Rational(0), Rational(0), Rational(0)) - Rational(1),
"test 5"
)
assertEquals(
ListPolynomial(Rational(1)),
ListPolynomial(Rational(2)) - Rational(1),
"test 6"
)
assertEquals(
ListPolynomial(Rational(-2)),
ListPolynomial<Rational>() - Rational(2),
"test 7"
)
}
}
@Test
fun test_Polynomial_Constant_times() {
IntModuloRing(35).listPolynomialSpace {
assertEquals(
ListPolynomial(34, 2, 1, 20, 2),
ListPolynomial(22, 26, 13, 15, 26) * 27.asConstant(),
"test 1"
)
assertEquals(
ListPolynomial(0, 0, 0, 0, 0),
ListPolynomial(7, 0, 49, 21, 14) * 15.asConstant(),
"test 2"
)
}
}
@Test
fun test_Constant_Polynomial_plus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(-22, 9), Rational(-8, 9), Rational(-8, 7)),
Rational(-3) + ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)),
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
Rational(2) + ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
Rational(2) + ListPolynomial(Rational(-2)),
"test 3"
)
assertEquals(
ListPolynomial(Rational(0)),
Rational(0) + ListPolynomial(),
"test 4"
)
assertEquals(
ListPolynomial(Rational(-1), Rational(0), Rational(0), Rational(0)),
Rational(1) + ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 5"
)
assertEquals(
ListPolynomial(Rational(-1)),
Rational(1) + ListPolynomial(Rational(-2)),
"test 6"
)
assertEquals(
ListPolynomial(Rational(2)),
Rational(2) + ListPolynomial(),
"test 7"
)
}
}
@Test
fun test_Constant_Polynomial_minus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(32, 9), Rational(-8, 9), Rational(-8, 7)),
Rational(3) - ListPolynomial(Rational(-5, 9), Rational(8, 9), Rational(8, 7)),
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0)),
Rational(-2) - ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0)),
Rational(-2) - ListPolynomial(Rational(-2)),
"test 3"
)
assertEquals(
ListPolynomial(Rational(0)),
Rational(0) - ListPolynomial(),
"test 4"
)
assertEquals(
ListPolynomial(Rational(1), Rational(0), Rational(0), Rational(0)),
Rational(-1) - ListPolynomial(Rational(-2), Rational(0), Rational(0), Rational(0)),
"test 5"
)
assertEquals(
ListPolynomial(Rational(1)),
Rational(-1) - ListPolynomial(Rational(-2)),
"test 6"
)
assertEquals(
ListPolynomial(Rational(-2)),
Rational(-2) - ListPolynomial(),
"test 7"
)
}
}
@Test
fun test_Constant_Polynomial_times() {
IntModuloRing(35).listPolynomialSpace {
assertEquals(
ListPolynomial(34, 2, 1, 20, 2),
27 * ListPolynomial(22, 26, 13, 15, 26),
"test 1"
)
assertEquals(
ListPolynomial(0, 0, 0, 0, 0),
15 * ListPolynomial(7, 0, 49, 21, 14),
"test 2"
)
}
}
@Test
fun test_Polynomial_unaryMinus() {
RationalField.listPolynomialSpace {
assertEquals(
ListPolynomial(Rational(-5, 9), Rational(8, 9), Rational(8, 7)),
-ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)),
"test 1"
)
assertEquals(
ListPolynomial(Rational(-5, 9), Rational(8, 9), Rational(8, 7), Rational(0), Rational(0)),
-ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7), Rational(0), Rational(0)),
"test 2"
)
}
}
@Test
fun test_Polynomial_Polynomial_plus() {
RationalField.listPolynomialSpace {
// (5/9 - 8/9 x - 8/7 x^2) + (-5/7 + 5/1 x + 5/8 x^2) ?= -10/63 + 37/9 x - 29/56 x^2
assertEquals(
ListPolynomial(Rational(-10, 63), Rational(37, 9), Rational(-29, 56)),
ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) +
ListPolynomial(Rational(-5, 7), Rational(5, 1), Rational(5, 8)),
"test 1"
)
// (-2/9 - 8/3 x) + (0 + 9/4 x + 2/4 x^2) ?= -2/9 - 5/12 x + 2/4 x^2
assertEquals(
ListPolynomial(Rational(-2, 9), Rational(-5, 12), Rational(2, 4)),
ListPolynomial(Rational(-2, 9), Rational(-8, 3)) +
ListPolynomial(Rational(0), Rational(9, 4), Rational(2, 4)),
"test 2"
)
// (-4/7 - 2/6 x + 0 x^2 + 0 x^3) + (-6/3 - 7/2 x + 2/3 x^2) ?= -18/7 - 23/6 x + 2/3 x^2
assertEquals(
ListPolynomial(Rational(-18, 7), Rational(-23, 6), Rational(2, 3), Rational(0)),
ListPolynomial(Rational(-4, 7), Rational(-2, 6), Rational(0), Rational(0)) +
ListPolynomial(Rational(-6, 3), Rational(-7, 2), Rational(2, 3)),
"test 3"
)
// (-2/4 - 6/9 x - 4/9 x^2) + (2/4 + 6/9 x + 4/9 x^2) ?= 0
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(-2, 4), Rational(-6, 9), Rational(-4, 9)) +
ListPolynomial(Rational(2, 4), Rational(6, 9), Rational(4, 9)),
"test 4"
)
}
}
@Test
fun test_Polynomial_Polynomial_minus() {
RationalField.listPolynomialSpace {
// (5/9 - 8/9 x - 8/7 x^2) - (-5/7 + 5/1 x + 5/8 x^2) ?= 80/63 - 53/9 x - 99/56 x^2
assertEquals(
ListPolynomial(Rational(80, 63), Rational(-53, 9), Rational(-99, 56)),
ListPolynomial(Rational(5, 9), Rational(-8, 9), Rational(-8, 7)) -
ListPolynomial(Rational(-5, 7), Rational(5, 1), Rational(5, 8)),
"test 1"
)
// (-2/9 - 8/3 x) - (0 + 9/4 x + 2/4 x^2) ?= -2/9 - 59/12 x - 2/4 x^2
assertEquals(
ListPolynomial(Rational(-2, 9), Rational(-59, 12), Rational(-2, 4)),
ListPolynomial(Rational(-2, 9), Rational(-8, 3)) -
ListPolynomial(Rational(0), Rational(9, 4), Rational(2, 4)),
"test 2"
)
// (-4/7 - 2/6 x + 0 x^2 + 0 x^3) - (-6/3 - 7/2 x + 2/3 x^2) ?= 10/7 + 19/6 x - 2/3 x^2
assertEquals(
ListPolynomial(Rational(10, 7), Rational(19, 6), Rational(-2, 3), Rational(0)),
ListPolynomial(Rational(-4, 7), Rational(-2, 6), Rational(0), Rational(0)) -
ListPolynomial(Rational(-6, 3), Rational(-7, 2), Rational(2, 3)),
"test 3"
)
// (-2/4 - 6/9 x - 4/9 x^2) - (-2/4 - 6/9 x - 4/9 x^2) ?= 0
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(-2, 4), Rational(-6, 9), Rational(-4, 9)) -
ListPolynomial(Rational(-2, 4), Rational(-6, 9), Rational(-4, 9)),
"test 4"
)
}
}
@Test
fun test_Polynomial_Polynomial_times() {
IntModuloRing(35).listPolynomialSpace {
// (1 + x + x^2) * (1 - x + x^2) ?= 1 + x^2 + x^4
assertEquals(
ListPolynomial(1, 0, 1, 0, 1),
ListPolynomial(1, -1, 1) * ListPolynomial(1, 1, 1),
"test 1"
)
// Spoiler: 5 * 7 = 0
assertEquals(
ListPolynomial(0, 0, 0, 0, 0),
ListPolynomial(5, -25, 10) * ListPolynomial(21, 14, -7),
"test 2"
)
}
}
}

View File

@ -0,0 +1,982 @@
/*
* 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.functions.testUtils.Rational
import space.kscience.kmath.functions.testUtils.RationalField
import space.kscience.kmath.functions.testUtils.assertFailsWithTypeAndMessage
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(UnstableKMathAPI::class)
class ListPolynomialUtilTest {
@Test
fun test_Polynomial_substitute_Double() {
assertEquals(
0.0,
ListPolynomial(1.0, -2.0, 1.0).substitute(1.0),
0.001,
"test 1"
)
assertEquals(
0.0,
ListPolynomial(1.0, -2.0, 1.0).substitute(1.0),
0.001,
"test 1"
)
assertEquals(
1.1931904761904761,
ListPolynomial(0.625, 2.6666666666666665, 0.5714285714285714, 1.5).substitute(0.2),
0.001,
"test 2"
)
assertEquals(
0.5681904761904762,
ListPolynomial(0.0, 2.6666666666666665, 0.5714285714285714, 1.5).substitute(0.2),
0.001,
"test 3"
)
assertEquals(
1.1811904761904761,
ListPolynomial(0.625, 2.6666666666666665, 0.5714285714285714, 0.0).substitute(0.2),
0.001,
"test 4"
)
assertEquals(
1.1703333333333332,
ListPolynomial(0.625, 2.6666666666666665, 0.0, 1.5).substitute(0.2),
0.001,
"test 5"
)
}
@Test
fun test_Polynomial_substitute_Constant() {
assertEquals(
Rational(0),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).substitute(RationalField, Rational(1)),
"test 1"
)
assertEquals(
Rational(25057, 21000),
ListPolynomial(Rational(5, 8), Rational(8, 3), Rational(4, 7), Rational(3, 2))
.substitute(RationalField, Rational(1, 5)),
"test 2"
)
assertEquals(
Rational(2983, 5250),
ListPolynomial(Rational(0), Rational(8, 3), Rational(4, 7), Rational(3, 2))
.substitute(RationalField, Rational(1, 5)),
"test 3"
)
assertEquals(
Rational(4961, 4200),
ListPolynomial(Rational(5, 8), Rational(8, 3), Rational(4, 7), Rational(0))
.substitute(RationalField, Rational(1, 5)),
"test 4"
)
assertEquals(
Rational(3511, 3000),
ListPolynomial(Rational(5, 8), Rational(8, 3), Rational(0), Rational(3, 2))
.substitute(RationalField, Rational(1, 5)),
"test 5"
)
}
@Test
fun test_Polynomial_substitute_Polynomial() {
assertEquals(
ListPolynomial(Rational(0)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).substitute(RationalField, ListPolynomial(Rational(1))),
"test 1"
)
assertEquals(
ListPolynomial(Rational(709, 378), Rational(155, 252), Rational(19, 525), Rational(2, 875)),
ListPolynomial(Rational(1, 7), Rational(9, 4), Rational(1, 3), Rational(2, 7))
.substitute(RationalField, ListPolynomial(Rational(6, 9), Rational(1, 5))),
"test 2"
)
assertEquals(
ListPolynomial(Rational(655, 378), Rational(155, 252), Rational(19, 525), Rational(2, 875)),
ListPolynomial(Rational(0), Rational(9, 4), Rational(1, 3), Rational(2, 7))
.substitute(RationalField, ListPolynomial(Rational(6, 9), Rational(1, 5))),
"test 3"
)
assertEquals(
ListPolynomial(Rational(677, 378), Rational(97, 180), Rational(1, 75), Rational(0)),
ListPolynomial(Rational(1, 7), Rational(9, 4), Rational(1, 3), Rational(0))
.substitute(RationalField, ListPolynomial(Rational(6, 9), Rational(1, 5))),
"test 4"
)
assertEquals(
ListPolynomial(Rational(653, 378), Rational(221, 420), Rational(4, 175), Rational(2, 875)),
ListPolynomial(Rational(1, 7), Rational(9, 4), Rational(0), Rational(2, 7))
.substitute(RationalField, ListPolynomial(Rational(6, 9), Rational(1, 5))),
"test 5"
)
assertEquals(
ListPolynomial(Rational(89, 54), Rational(0), Rational(0), Rational(0)),
ListPolynomial(Rational(0), Rational(9, 4), Rational(1, 3), Rational(0))
.substitute(RationalField, ListPolynomial(Rational(6, 9), Rational(0))),
"test 6"
)
}
@Test
@Ignore // FIXME: This tests work only for sane realisations of the substitutions. Currently, it is not.
// Sane algorithm for substitution p(q/r) (p, q, and r are polynomials) should return denominator r^deg(p),
// not r^(deg(p)(deg(p)+1)/2) as it is now.
fun test_Polynomial_substitute_RationalFunction() {
assertEquals(
ListRationalFunction(ListPolynomial(Rational(0)), ListPolynomial(Rational(1))),
ListPolynomial(Rational(1), Rational(-2), Rational(1))
.substitute(RationalField, ListRationalFunction(ListPolynomial(Rational(1)), ListPolynomial(Rational(1)))),
"test 1"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(66349, 243),
Rational(-17873, 405),
Rational(173533, 3780),
Rational(-91141, 567),
Rational(5773909, 105840),
Rational(-23243, 630),
Rational(1573, 27)
),
ListPolynomial(
Rational(169, 81),
Rational(-130, 27),
Rational(115, 18),
Rational(-797, 54),
Rational(1985, 144),
Rational(-55, 6),
Rational(121, 9)
)
),
ListPolynomial(
Rational(13, 3),
Rational(-9, 5),
Rational(5, 5)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(15, 1),
Rational(6, 9),
Rational(-3, 7)
),
ListPolynomial(
Rational(-13, 9),
Rational(10, 6),
Rational(-10, 8),
Rational(11, 3)
)
)
),
"test 2"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(0, 1),
Rational(0, 1),
Rational(-14, 9),
Rational(31, 14),
Rational(-5077, 980),
Rational(99, 35)
),
ListPolynomial(
Rational(0, 1),
Rational(0, 1),
Rational(25, 9),
Rational(-25, 6),
Rational(1985, 144),
Rational(-55, 6),
Rational(121, 9)
)
),
ListPolynomial(
Rational(0),
Rational(-9, 5),
Rational(5, 5)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(0),
Rational(6, 9),
Rational(-3, 7)
),
ListPolynomial(
Rational(0),
Rational(10, 6),
Rational(-10, 8),
Rational(11, 3)
)
)
),
"test 3"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(-898, 27),
Rational(271, 45),
Rational(-65, 12) ,
Rational(0),
Rational(0),
Rational(0),
Rational(0)
),
ListPolynomial(
Rational(-13, 9),
Rational(5, 3),
Rational(-5, 4),
Rational(0),
Rational(0),
Rational(0),
Rational(0)
)
),
ListPolynomial(
Rational(13, 3),
Rational(-9, 5),
Rational(0)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(15, 1),
Rational(6, 9),
Rational(0)
),
ListPolynomial(
Rational(-13, 9),
Rational(10, 6),
Rational(-10, 8),
Rational(0)
)
)
),
"test 4"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(56872, 243),
Rational(0, 1),
Rational(-90, 7),
Rational(-3718, 81),
Rational(9, 49),
Rational(0, 1),
Rational(1573, 27)
),
ListPolynomial(
Rational(169, 81),
Rational(0, 1),
Rational(0, 1),
Rational(-286, 27),
Rational(0, 1),
Rational(0, 1),
Rational(121, 9)
)
),
ListPolynomial(
Rational(13, 3),
Rational(0),
Rational(5, 5)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(15, 1),
Rational(0),
Rational(-3, 7)
),
ListPolynomial(
Rational(-13, 9),
Rational(0),
Rational(0),
Rational(11, 3)
)
)
),
"test 5"
)
}
@Test
fun test_RationalFunction_substitute_Double() {
assertEquals(
0.0,
ListRationalFunction(
ListPolynomial(1.0, -2.0, 1.0),
ListPolynomial(-6.302012278484357, 5.831971885376948, -9.271604788393432, 5.494387848015814, -3.7187384450880785)
).substitute(1.0),
0.001,
"test 1"
)
assertEquals(
2.693702616649797,
ListRationalFunction(
ListPolynomial(-5.848840571263625, -1.660411278951134, -3.793740946372443, -9.624569269490076),
ListPolynomial(-2.9680680215311073, -1.862973627119981, 4.776550592888336, -2.7320154512368466)
).substitute(-7.53452770353279),
0.001,
"test 2"
)
assertEquals(
2.692226268901378,
ListRationalFunction(
ListPolynomial(0.0, -1.660411278951134, -3.793740946372443, -9.624569269490076),
ListPolynomial(0.0, -1.862973627119981, 4.776550592888336, -2.7320154512368466)
).substitute(-7.53452770353279),
0.001,
"test 3"
)
assertEquals(
-0.7394904842099175,
ListRationalFunction(
ListPolynomial(-5.848840571263625, -1.660411278951134, -3.793740946372443, 0.0),
ListPolynomial(-2.9680680215311073, -1.862973627119981, 4.776550592888336, 0.0)
).substitute(-7.53452770353279),
0.001,
"test 4"
)
assertEquals(
3.526835209398159,
ListRationalFunction(
ListPolynomial(-5.848840571263625, 0.0, 0.0, -9.624569269490076),
ListPolynomial(-2.9680680215311073, 0.0, 0.0, -2.7320154512368466)
).substitute(-7.53452770353279),
0.001,
"test 5"
)
}
@Test
fun test_RationalFunction_substitute_Constant() {
assertEquals(
Rational(0),
ListRationalFunction(
ListPolynomial(Rational(1), Rational(-2), Rational(1)),
ListPolynomial(Rational(1)),
).substitute(RationalField, Rational(1)),
"test 1"
)
assertEquals(
Rational(1149615, 61306),
ListRationalFunction(
ListPolynomial(Rational(17, 7), Rational(18, 3), Rational(18, 8), Rational(9, 1)),
ListPolynomial(Rational(11, 9), Rational(-6, 5), Rational(-12, 7), Rational(2, 1)),
).substitute(RationalField, Rational(-7, 8)),
"test 2"
)
assertEquals(
Rational(3495, 586),
ListRationalFunction(
ListPolynomial(Rational(0), Rational(18, 3), Rational(18, 8), Rational(9, 1)),
ListPolynomial(Rational(0), Rational(-6, 5), Rational(-12, 7), Rational(2, 1)),
).substitute(RationalField, Rational(-7, 8)),
"test 3"
)
assertEquals(
Rational(-88605, 77392),
ListRationalFunction(
ListPolynomial(Rational(17, 7), Rational(18, 3), Rational(18, 8), Rational(0)),
ListPolynomial(Rational(11, 9), Rational(-6, 5), Rational(-12, 7), Rational(0)),
).substitute(RationalField, Rational(-7, 8)),
"test 4"
)
assertEquals(
Rational(116145, 3794),
ListRationalFunction(
ListPolynomial(Rational(17, 7), Rational(0), Rational(0), Rational(9, 1)),
ListPolynomial(Rational(11, 9), Rational(0), Rational(0), Rational(2, 1)),
).substitute(RationalField, Rational(-7, 8)),
"test 5"
)
}
@Test
fun test_RationalFunction_substitute_Polynomial() {
assertEquals(
ListRationalFunction(
ListPolynomial(Rational(0)),
ListPolynomial(Rational(1))
),
ListRationalFunction(
ListPolynomial(Rational(1), Rational(-2), Rational(1)),
ListPolynomial(Rational(1)),
).substitute(RationalField, ListPolynomial(Rational(1))),
"test 1"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(-283303, 36),
Rational(-23593, 24),
Rational(368713, 192),
Rational(1455, 8),
Rational(-272171, 1536),
Rational(-2149, 192),
Rational(469, 64),
Rational(11, 48),
Rational(-11, 96)
),
ListPolynomial(
Rational(5797, 12),
Rational(595, 16),
Rational(-5285, 72),
Rational(-745, 192),
Rational(1105, 288),
Rational(5, 48),
Rational(-5, 72)
)
),
ListRationalFunction(
ListPolynomial(
Rational(2, 9),
Rational(11, 3),
Rational(-9, 4),
Rational(-6, 1),
Rational(-11, 6)
),
ListPolynomial(
Rational(-2, 3),
Rational(-15, 4),
Rational(5, 9),
Rational(-5, 9)
)
).substitute(RationalField,
ListPolynomial(
Rational(-9, 1),
Rational(-1, 4),
Rational(2, 4)
)
),
"test 2"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(0, 1),
Rational(-11, 12),
Rational(325, 192),
Rational(21, 32),
Rational(-1739, 1536),
Rational(227, 192),
Rational(-59, 64),
Rational(11, 48),
Rational(-11, 96)
),
ListPolynomial(
Rational(0, 1),
Rational(15, 16),
Rational(-265, 144),
Rational(-25, 192),
Rational(25, 288),
Rational(5, 48),
Rational(-5, 72)
)
),
ListRationalFunction(
ListPolynomial(
Rational(0, 9),
Rational(11, 3),
Rational(-9, 4),
Rational(-6, 1),
Rational(-11, 6)
),
ListPolynomial(
Rational(0, 3),
Rational(-15, 4),
Rational(5, 9),
Rational(-5, 9)
)
).substitute(RationalField,
ListPolynomial(
Rational(0, 1),
Rational(-1, 4),
Rational(2, 4)
)
),
"test 3"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(149723, 36),
Rational(8483, 24),
Rational(639, 64),
Rational(3, 32),
Rational(0),
Rational(0),
Rational(0),
Rational(0),
Rational(0)
),
ListPolynomial(
Rational(937, 12),
Rational(55, 16),
Rational(5, 144),
Rational(0),
Rational(0),
Rational(0),
Rational(0)
)
),
ListRationalFunction(
ListPolynomial(
Rational(2, 9),
Rational(11, 3),
Rational(-9, 4),
Rational(-6, 1),
Rational(0)
),
ListPolynomial(
Rational(-2, 3),
Rational(-15, 4),
Rational(5, 9),
Rational(0)
)
).substitute(RationalField,
ListPolynomial(
Rational(-9, 1),
Rational(-1, 4),
Rational(0)
)
),
"test 4"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(-216509, 18),
Rational(0, 1),
Rational(2673, 1),
Rational(0, 1),
Rational(-891, 4),
Rational(0, 1),
Rational(33, 4),
Rational(0, 1),
Rational(-11, 96)
),
ListPolynomial(
Rational(1213, 3),
Rational(0, 1),
Rational(-135, 2),
Rational(0, 1),
Rational(15, 4),
Rational(0, 1),
Rational(-5, 72)
)
),
ListRationalFunction(
ListPolynomial(
Rational(2, 9),
Rational(0),
Rational(0),
Rational(0),
Rational(-11, 6)
),
ListPolynomial(
Rational(-2, 3),
Rational(0),
Rational(0),
Rational(-5, 9)
)
).substitute(RationalField,
ListPolynomial(
Rational(-9, 1),
Rational(0),
Rational(2, 4)
)
),
"test 5"
)
}
@Test
@Ignore // FIXME: This tests work only for sane realisations of the substitutions. Currently, it is not.
// Sane algorithm for substitution p(q/r) (p, q, and r are polynomials) should return denominator r^deg(p),
// not r^(deg(p)(deg(p)+1)/2) as it is now.
fun test_RationalFunction_substitute_RationalFunction() {
assertEquals(
ListRationalFunction(
ListPolynomial(Rational(0)),
ListPolynomial(Rational(1))
),
ListRationalFunction(
ListPolynomial(Rational(1), Rational(-2), Rational(1)),
ListPolynomial(Rational(1))
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(Rational(1)),
ListPolynomial(Rational(1))
)
),
"test 1"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(130087, 3888),
Rational(-2866333, 65610),
Rational(-5076229, 97200),
Rational(222136997, 3280500),
Rational(754719329, 20995200),
Rational(-12010283, 324000),
Rational(-2011967, 172800),
Rational(18607, 2880),
Rational(4705, 4096)
),
ListPolynomial(
Rational(-143820355, 3779136),
Rational(73886869, 1574640),
Rational(1440175193, 15746400),
Rational(-5308968857, 52488000),
Rational(-186910083731, 2099520000),
Rational(125043463, 1555200),
Rational(5299123, 388800),
Rational(-213757, 15360),
Rational(1380785, 147456)
)
),
ListRationalFunction(
ListPolynomial(
Rational(1, 1),
Rational(-10, 5),
Rational(18, 8),
Rational(-8, 8)
),
ListPolynomial(
Rational(-14, 8),
Rational(-14, 8),
Rational(-19, 6),
Rational(14, 3),
Rational(8, 9)
)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(14, 9),
Rational(-2, 5),
Rational(-14, 7)
),
ListPolynomial(
Rational(-6, 4),
Rational(5, 9),
Rational(1, 8)
)
)
),
"test 2"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(0, 1),
Rational(0, 1),
Rational(0, 1),
Rational(0, 1),
Rational(5173, 18225),
Rational(904291, 364500),
Rational(283127, 43200),
Rational(37189, 5760),
Rational(147, 128)
),
ListPolynomial(
Rational(0, 1),
Rational(0, 1),
Rational(0, 1),
Rational(0, 1),
Rational(-163589, 911250),
Rational(-881831, 291600),
Rational(-10722229, 777600),
Rational(-640921, 46080),
Rational(86303, 9216)
)
),
ListRationalFunction(
ListPolynomial(
Rational(0),
Rational(-10, 5),
Rational(18, 8),
Rational(-8, 8)
),
ListPolynomial(
Rational(0),
Rational(-14, 8),
Rational(-19, 6),
Rational(14, 3),
Rational(8, 9)
)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(0),
Rational(-2, 5),
Rational(-14, 7)
),
ListPolynomial(
Rational(0),
Rational(5, 9),
Rational(1, 8)
)
)
),
"test 3"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(445, 16),
Rational(-2011, 54),
Rational(1359199, 72900),
Rational(-135733, 32805),
Rational(2254, 6561),
Rational(0, 1),
Rational(0, 1),
Rational(0, 1),
Rational(0, 1)
),
ListPolynomial(
Rational(-2018387, 46656),
Rational(82316437, 1574640),
Rational(-9335047, 393660),
Rational(15765889, 3280500),
Rational(-242089, 656100),
Rational(0, 1),
Rational(0, 1),
Rational(0, 1),
Rational(0, 1)
)
),
ListRationalFunction(
ListPolynomial(
Rational(1, 1),
Rational(-10, 5),
Rational(18, 8),
Rational(0)
),
ListPolynomial(
Rational(-14, 8),
Rational(-14, 8),
Rational(-19, 6),
Rational(14, 3),
Rational(0)
)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(14, 9),
Rational(-2, 5),
Rational(0)
),
ListPolynomial(
Rational(-6, 4),
Rational(5, 9),
Rational(0)
)
)
),
"test 4"
)
assertEquals(
ListRationalFunction(
ListPolynomial(
Rational(41635, 3888),
Rational(0, 1),
Rational(-279187, 11664),
Rational(0, 1),
Rational(103769, 3456),
Rational(0, 1),
Rational(-11017, 768),
Rational(0, 1),
Rational(4097, 4096)
),
ListPolynomial(
Rational(-13811791, 3779136),
Rational(0, 1),
Rational(-9999395, 419904),
Rational(0, 1),
Rational(6376601, 124416),
Rational(0, 1),
Rational(-3668315, 82944),
Rational(0, 1),
Rational(2097089, 147456)
)
),
ListRationalFunction(
ListPolynomial(
Rational(1, 1),
Rational(0),
Rational(0),
Rational(-8, 8)
),
ListPolynomial(
Rational(-14, 8),
Rational(0),
Rational(0),
Rational(0),
Rational(8, 9)
)
).substitute(RationalField,
ListRationalFunction(
ListPolynomial(
Rational(14, 9),
Rational(0),
Rational(-14, 7)
),
ListPolynomial(
Rational(-6, 4),
Rational(0),
Rational(1, 8)
)
)
),
"test 5"
)
}
@Test
fun test_Polynomial_derivative() {
assertEquals(
ListPolynomial(Rational(-2), Rational(2)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).derivative(RationalField),
"test 1"
)
assertEquals(
ListPolynomial(Rational(-8, 3), Rational(8, 9), Rational(15, 7), Rational(-20, 9)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).derivative(RationalField),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0), Rational(8, 9), Rational(15, 7), Rational(-20, 9)),
ListPolynomial(Rational(0), Rational(0), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).derivative(RationalField),
"test 3"
)
assertEquals(
ListPolynomial(Rational(-8, 3), Rational(8, 9), Rational(15, 7), Rational(0)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(0)).derivative(RationalField),
"test 4"
)
}
@Test
fun test_Polynomial_nthDerivative() {
assertEquals(
ListPolynomial(Rational(-2), Rational(2)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthDerivative(RationalField, 1),
"test 1"
)
assertFailsWithTypeAndMessage<IllegalArgumentException>(
"Order of derivative must be non-negative",
"test2"
) {
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthDerivative(RationalField, -1)
}
assertEquals(
ListPolynomial(Rational(1), Rational(-2), Rational(1)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthDerivative(RationalField, 0),
"test 3"
)
assertEquals(
ListPolynomial(Rational(2)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthDerivative(RationalField, 2),
"test 4"
)
assertEquals(
ListPolynomial(),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthDerivative(RationalField, 3),
"test 5"
)
assertEquals(
ListPolynomial(),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthDerivative(RationalField, 4),
"test 6"
)
assertEquals(
ListPolynomial(Rational(8, 9), Rational(30, 7), Rational(-20, 3)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).nthDerivative(RationalField, 2),
"test 7"
)
assertEquals(
ListPolynomial(Rational(8, 9), Rational(30, 7), Rational(-20, 3)),
ListPolynomial(Rational(0), Rational(0), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).nthDerivative(RationalField, 2),
"test 8"
)
assertEquals(
ListPolynomial(Rational(8, 9), Rational(30, 7), Rational(0)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(0)).nthDerivative(RationalField, 2),
"test 9"
)
}
@Test
fun test_Polynomial_antiderivative() {
assertEquals(
ListPolynomial(Rational(0), Rational(1), Rational(-1), Rational(1, 3)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).antiderivative(RationalField),
"test 1"
)
assertEquals(
ListPolynomial(Rational(0), Rational(1, 5), Rational(-4, 3), Rational(4, 27), Rational(5, 28), Rational(-1, 9)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).antiderivative(RationalField),
"test 2"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(4, 27), Rational(5, 28), Rational(-1, 9)),
ListPolynomial(Rational(0), Rational(0), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).antiderivative(RationalField),
"test 3"
)
assertEquals(
ListPolynomial(Rational(0), Rational(1, 5), Rational(-4, 3), Rational(4, 27), Rational(5, 28), Rational(0)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(0)).antiderivative(RationalField),
"test 4"
)
}
@Test
fun test_Polynomial_nthAntiderivative() {
assertEquals(
ListPolynomial(Rational(0), Rational(1), Rational(-1), Rational(1, 3)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthAntiderivative(RationalField, 1),
"test 1"
)
assertFailsWithTypeAndMessage<IllegalArgumentException>(
"Order of antiderivative must be non-negative",
"test2"
) {
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthAntiderivative(RationalField, -1)
}
assertEquals(
ListPolynomial(Rational(1), Rational(-2), Rational(1)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthAntiderivative(RationalField, 0),
"test 3"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(1, 2), Rational(-1, 3), Rational(1, 12)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthAntiderivative(RationalField, 2),
"test 4"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(1, 6), Rational(-1, 12), Rational(1, 60)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthAntiderivative(RationalField, 3),
"test 5"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0), Rational(1, 24), Rational(-1, 60), Rational(1, 360)),
ListPolynomial(Rational(1), Rational(-2), Rational(1)).nthAntiderivative(RationalField, 4),
"test 6"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(1, 10), Rational(-4, 9), Rational(1, 27), Rational(1, 28), Rational(-1, 54)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).nthAntiderivative(RationalField, 2),
"test 7"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(0), Rational(0), Rational(1, 27), Rational(1, 28), Rational(-1, 54)),
ListPolynomial(Rational(0), Rational(0), Rational(4, 9), Rational(5, 7), Rational(-5, 9)).nthAntiderivative(RationalField, 2),
"test 8"
)
assertEquals(
ListPolynomial(Rational(0), Rational(0), Rational(1, 10), Rational(-4, 9), Rational(1, 27), Rational(1, 28), Rational(0)),
ListPolynomial(Rational(1, 5), Rational(-8, 3), Rational(4, 9), Rational(5, 7), Rational(0)).nthAntiderivative(RationalField, 2),
"test 9"
)
}
}

View File

@ -0,0 +1,123 @@
/*
* 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.algebra
import space.kscience.kmath.operations.invoke
import kotlin.test.Test
import kotlin.test.assertEquals
class NumberedConstructorsTest {
@Test
@UnstableKMathAPI
fun testDSL1() {
assertEquals(
NumberedPolynomialAsIs(
listOf(2u, 0u, 3u) to 5,
listOf(0u, 1u) to -6,
),
Int.algebra.numberedPolynomialSpace {
NumberedPolynomialDSL1 {
5 { 0 pow 2u; 2 pow 3u }
(-6) { 1 pow 1u }
}
},
"test 1"
)
assertEquals(
NumberedPolynomialAsIs(
listOf<UInt>() to -1,
),
Int.algebra.numberedPolynomialSpace {
NumberedPolynomialDSL1 {
5 { }
(-6) { }
}
},
"test 2"
)
assertEquals(
NumberedPolynomialAsIs(
listOf(2u) to -1,
),
Int.algebra.numberedPolynomialSpace {
NumberedPolynomialDSL1 {
5 { 0 pow 1u; 0 pow 1u }
(-6) { 0 pow 2u }
}
},
"test 3"
)
assertEquals(
NumberedPolynomialAsIs(
listOf(2u) to -1,
),
Int.algebra.numberedPolynomialSpace {
NumberedPolynomialDSL1 {
5 { 0 pow 1u; 0 pow 1u }
(-6) { 0 pow 2u; 2 pow 0u }
}
},
"test 3"
)
}
@Test
@UnstableKMathAPI
fun testFabric() {
assertEquals(
NumberedPolynomialAsIs(
listOf(2u, 0u, 3u) to 5,
listOf(0u, 1u) to -6,
),
Int.algebra {
NumberedPolynomial(
listOf(2u, 0u, 3u) to 5,
listOf(0u, 1u) to -6,
)
},
"test 1"
)
assertEquals(
NumberedPolynomialAsIs(
listOf(2u, 0u, 3u) to 5,
listOf(0u, 1u) to -6,
),
Int.algebra {
NumberedPolynomial(
listOf(2u, 0u, 3u, 0u) to 5,
listOf(0u, 1u, 0u, 0u) to -6,
)
},
"test 2"
)
assertEquals(
NumberedPolynomialAsIs(
listOf<UInt>() to -1,
),
Int.algebra {
NumberedPolynomial(
listOf(0u) to 5,
listOf(0u, 0u) to -6,
)
},
"test 3"
)
assertEquals(
NumberedPolynomialAsIs(
listOf<UInt>() to 0,
),
Int.algebra {
NumberedPolynomial(
listOf(0u) to 5,
listOf(0u, 0u) to -5,
)
},
"test 4"
)
}
}

View File

@ -0,0 +1,12 @@
/*
* 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.testUtils
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.asBuffer
fun <T> bufferOf(vararg elements: T): Buffer<T> = elements.asBuffer()

View File

@ -0,0 +1,133 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions.testUtils
import space.kscience.kmath.operations.Ring
class IntModulo {
val residue: Int
val modulus: Int
@PublishedApi
internal constructor(residue: Int, modulus: Int, toCheckInput: Boolean = true) {
if (toCheckInput) {
require(modulus != 0) { "modulus can not be zero" }
this.modulus = if (modulus < 0) -modulus else modulus
this.residue = residue.mod(this.modulus)
} else {
this.residue = residue
this.modulus = modulus
}
}
constructor(residue: Int, modulus: Int) : this(residue, modulus, true)
operator fun unaryPlus(): IntModulo = this
operator fun unaryMinus(): IntModulo =
IntModulo(
if (residue == 0) 0 else modulus - residue,
modulus,
toCheckInput = false
)
operator fun plus(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not add two residue different modulo" }
return IntModulo(
(residue + other.residue) % modulus,
modulus,
toCheckInput = false
)
}
operator fun plus(other: Int): IntModulo =
IntModulo(
(residue + other) % modulus,
modulus,
toCheckInput = false
)
operator fun minus(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not subtract two residue different modulo" }
return IntModulo(
(residue - other.residue) % modulus,
modulus,
toCheckInput = false
)
}
operator fun minus(other: Int): IntModulo =
IntModulo(
(residue - other) % modulus,
modulus,
toCheckInput = false
)
operator fun times(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not multiply two residue different modulo" }
return IntModulo(
(residue * other.residue) % modulus,
modulus,
toCheckInput = false
)
}
operator fun times(other: Int): IntModulo =
IntModulo(
(residue * other) % modulus,
modulus,
toCheckInput = false
)
operator fun div(other: IntModulo): IntModulo {
require(modulus == other.modulus) { "can not divide two residue different modulo" }
val (reciprocalCandidate, gcdOfOtherResidueAndModulus) = bezoutIdentityWithGCD(other.residue, modulus)
require(gcdOfOtherResidueAndModulus == 1) { "can not divide to residue that has non-trivial GCD with modulo" }
return IntModulo(
(residue * reciprocalCandidate) % modulus,
modulus,
toCheckInput = false
)
}
operator fun div(other: Int): IntModulo {
val (reciprocalCandidate, gcdOfOtherResidueAndModulus) = bezoutIdentityWithGCD(other, modulus)
require(gcdOfOtherResidueAndModulus == 1) { "can not divide to residue that has non-trivial GCD with modulo" }
return IntModulo(
(residue * reciprocalCandidate) % modulus,
modulus,
toCheckInput = false
)
}
override fun equals(other: Any?): Boolean =
when (other) {
is IntModulo -> residue == other.residue && modulus == other.modulus
else -> false
}
override fun hashCode(): Int = residue.hashCode()
override fun toString(): String = "$residue mod $modulus"
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE")
class IntModuloRing : Ring<IntModulo> {
val modulus: Int
constructor(modulus: Int) {
require(modulus != 0) { "modulus can not be zero" }
this.modulus = if (modulus < 0) -modulus else modulus
}
override inline val zero: IntModulo get() = IntModulo(0, modulus, toCheckInput = false)
override inline val one: IntModulo get() = IntModulo(1, modulus, toCheckInput = false)
fun number(arg: Int): IntModulo = IntModulo(arg, modulus, toCheckInput = false)
override inline fun add(left: IntModulo, right: IntModulo): IntModulo = left + right
override inline fun multiply(left: IntModulo, right: IntModulo): IntModulo = left * right
override inline fun IntModulo.unaryMinus(): IntModulo = -this
override inline fun IntModulo.plus(arg: IntModulo): IntModulo = this + arg
override inline fun IntModulo.minus(arg: IntModulo): IntModulo = this - arg
override inline fun IntModulo.times(arg: IntModulo): IntModulo = this * arg
inline fun IntModulo.div(arg: IntModulo): IntModulo = this / arg
}

View File

@ -0,0 +1,19 @@
/*
* 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.testUtils
import space.kscience.kmath.functions.ListPolynomial
import space.kscience.kmath.functions.ListPolynomialSpace
import space.kscience.kmath.functions.PolynomialSpaceOverRing
fun ListPolynomialSpace<IntModulo, IntModuloRing>.ListPolynomial(vararg coefs: Int): ListPolynomial<IntModulo> =
ListPolynomial(coefs.map { IntModulo(it, ring.modulus) })
fun IntModuloRing.ListPolynomial(vararg coefs: Int): ListPolynomial<IntModulo> =
ListPolynomial(coefs.map { IntModulo(it, modulus) })
fun IntModuloRing.m(arg: Int): IntModulo = IntModulo(arg, modulus)
fun PolynomialSpaceOverRing<IntModulo, *, IntModuloRing>.m(arg: Int): IntModulo = IntModulo(arg, ring.modulus)

View File

@ -0,0 +1,29 @@
/*
* 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.testUtils
import kotlin.math.abs
internal data class BezoutIdentityWithGCD<T>(val first: T, val second: T, val gcd: T)
internal tailrec fun gcd(a: Long, b: Long): Long = if (a == 0L) abs(b) else gcd(b % a, a)
internal fun bezoutIdentityWithGCD(a: Int, b: Int): BezoutIdentityWithGCD<Int> =
when {
a < 0 && b < 0 -> with(bezoutIdentityWithGCDInternalLogic(-a, -b, 1, 0, 0, 1)) { BezoutIdentityWithGCD(-first, -second, gcd) }
a < 0 -> with(bezoutIdentityWithGCDInternalLogic(-a, b, 1, 0, 0, 1)) { BezoutIdentityWithGCD(-first, second, gcd) }
b < 0 -> with(bezoutIdentityWithGCDInternalLogic(a, -b, 1, 0, 0, 1)) { BezoutIdentityWithGCD(first, -second, gcd) }
else -> bezoutIdentityWithGCDInternalLogic(a, b, 1, 0, 0, 1)
}
internal tailrec fun bezoutIdentityWithGCDInternalLogic(a: Int, b: Int, m1: Int, m2: Int, m3: Int, m4: Int): BezoutIdentityWithGCD<Int> =
if (b == 0) BezoutIdentityWithGCD(m1, m3, a)
else {
val quotient = a / b
val reminder = a % b
bezoutIdentityWithGCDInternalLogic(b, reminder, m2, m1 - quotient * m2, m4, m3 - quotient * m4)
}

View File

@ -0,0 +1,177 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
package space.kscience.kmath.functions.testUtils
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.Field
import space.kscience.kmath.operations.NumbersAddOps
@Suppress("NAME_SHADOWING")
class Rational {
companion object {
val ZERO: Rational = Rational(0L)
val ONE: Rational = Rational(1L)
}
val numerator: Long
val denominator: Long
internal constructor(numerator: Long, denominator: Long, toCheckInput: Boolean = true) {
if (toCheckInput) {
if (denominator == 0L) throw ArithmeticException("/ by zero")
val greatestCommonDivider = gcd(numerator, denominator).let { if (denominator < 0L) -it else it }
this.numerator = numerator / greatestCommonDivider
this.denominator = denominator / greatestCommonDivider
} else {
this.numerator = numerator
this.denominator = denominator
}
}
constructor(numerator: Int, denominator: Int) : this(numerator.toLong(), denominator.toLong(), true)
constructor(numerator: Int, denominator: Long) : this(numerator.toLong(), denominator, true)
constructor(numerator: Long, denominator: Int) : this(numerator, denominator.toLong(), true)
constructor(numerator: Long, denominator: Long) : this(numerator, denominator, true)
constructor(numerator: Int) : this(numerator.toLong(), 1L, false)
constructor(numerator: Long) : this(numerator, 1L, false)
operator fun unaryPlus(): Rational = this
operator fun unaryMinus(): Rational = Rational(-this.numerator, this.denominator)
operator fun plus(other: Rational): Rational {
val denominatorsGcd = gcd(denominator, other.denominator)
val dividedThisDenominator = denominator / denominatorsGcd
val dividedOtherDenominator = other.denominator / denominatorsGcd
val numeratorCandidate = numerator * dividedOtherDenominator + dividedThisDenominator * other.numerator
val secondGcd = gcd(numeratorCandidate, denominatorsGcd)
return Rational(
numeratorCandidate / secondGcd,
dividedThisDenominator * (other.denominator / secondGcd),
toCheckInput = false
)
}
operator fun plus(other: Int): Rational =
Rational(
numerator + denominator * other.toLong(),
denominator,
toCheckInput = false
)
operator fun plus(other: Long): Rational =
Rational(
numerator + denominator * other,
denominator,
toCheckInput = false
)
operator fun minus(other: Rational): Rational {
val denominatorsGcd = gcd(denominator, other.denominator)
val dividedThisDenominator = denominator / denominatorsGcd
val dividedOtherDenominator = other.denominator / denominatorsGcd
val numeratorCandidate = numerator * dividedOtherDenominator - dividedThisDenominator * other.numerator
val secondGcd = gcd(numeratorCandidate, denominatorsGcd)
return Rational(
numeratorCandidate / secondGcd,
dividedThisDenominator * (other.denominator / secondGcd),
toCheckInput = false
)
}
operator fun minus(other: Int): Rational =
Rational(
numerator - denominator * other.toLong(),
denominator,
toCheckInput = false
)
operator fun minus(other: Long): Rational =
Rational(
numerator - denominator * other,
denominator,
toCheckInput = false
)
operator fun times(other: Rational): Rational {
val thisDenominatorAndOtherNumeratorGcd = gcd(denominator, other.numerator)
val otherDenominatorAndThisNumeratorGcd = gcd(other.denominator, numerator)
return Rational(
(numerator / otherDenominatorAndThisNumeratorGcd) * (other.numerator / thisDenominatorAndOtherNumeratorGcd),
(denominator / thisDenominatorAndOtherNumeratorGcd) * (other.denominator / otherDenominatorAndThisNumeratorGcd),
toCheckInput = false
)
}
operator fun times(other: Int): Rational {
val other = other.toLong()
val denominatorAndOtherGcd = gcd(denominator, other)
return Rational(
numerator * (other / denominatorAndOtherGcd),
denominator / denominatorAndOtherGcd,
toCheckInput = false
)
}
operator fun times(other: Long): Rational {
val denominatorAndOtherGcd = gcd(denominator, other)
return Rational(
numerator * (other / denominatorAndOtherGcd),
denominator / denominatorAndOtherGcd,
toCheckInput = false
)
}
operator fun div(other: Rational): Rational {
val denominatorsGcd = gcd(denominator, other.denominator)
val numeratorsGcd = gcd(numerator, other.numerator)
return Rational(
(numerator / numeratorsGcd) * (other.denominator / denominatorsGcd),
(denominator / denominatorsGcd) * (other.numerator / numeratorsGcd)
)
}
operator fun div(other: Int): Rational {
val other = other.toLong()
val numeratorAndOtherGcd = gcd(numerator, other)
return Rational(
numerator / numeratorAndOtherGcd,
denominator * (other / numeratorAndOtherGcd),
toCheckInput = false
)
}
operator fun div(other: Long): Rational {
val numeratorAndOtherGcd = gcd(numerator, other)
return Rational(
numerator / numeratorAndOtherGcd,
denominator * (other / numeratorAndOtherGcd),
toCheckInput = false
)
}
override fun equals(other: Any?): Boolean =
when (other) {
is Rational -> numerator == other.numerator && denominator == other.denominator
is Int -> numerator == other && denominator == 1L
is Long -> numerator == other && denominator == 1L
else -> false
}
override fun hashCode(): Int = 31 * numerator.hashCode() + denominator.hashCode()
override fun toString(): String = if (denominator == 1L) "$numerator" else "$numerator/$denominator"
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE")
@OptIn(UnstableKMathAPI::class)
object RationalField : Field<Rational>, NumbersAddOps<Rational> {
override inline val zero: Rational get() = Rational.ZERO
override inline val one: Rational get() = Rational.ONE
override inline fun number(value: Number): Rational = Rational(value.toLong())
override inline fun add(left: Rational, right: Rational): Rational = left + right
override inline fun multiply(left: Rational, right: Rational): Rational = left * right
override inline fun divide(left: Rational, right: Rational): Rational = left / right
override inline fun scale(a: Rational, value: Double): Rational = a * number(value)
override inline fun Rational.unaryMinus(): Rational = -this
override inline fun Rational.plus(arg: Rational): Rational = this + arg
override inline fun Rational.minus(arg: Rational): Rational = this - arg
override inline fun Rational.times(arg: Rational): Rational = this * arg
override inline fun Rational.div(arg: Rational): Rational = this / arg
}

View File

@ -0,0 +1,104 @@
/*
* 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.testUtils
import space.kscience.kmath.functions.LabeledPolynomial
import space.kscience.kmath.functions.LabeledRationalFunction
import space.kscience.kmath.functions.NumberedPolynomial
import space.kscience.kmath.functions.NumberedRationalFunction
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
fun <T> assertContentEquals(
expected: Map<T, Double>,
actual: Map<T, Double>,
absoluteTolerance: Double,
message: String? = null
) {
assertEquals(expected.keys, actual.keys, message)
for ((key, expectedValue) in expected) assertEquals(expectedValue, actual[key]!!, absoluteTolerance, message)
}
fun assertEquals(
expected: NumberedPolynomial<Double>,
actual: NumberedPolynomial<Double>,
absoluteTolerance: Double,
message: String? = null
) {
assertContentEquals(
expected.coefficients,
actual.coefficients,
absoluteTolerance,
message
)
}
fun assertEquals(
expected: LabeledPolynomial<Double>,
actual: LabeledPolynomial<Double>,
absoluteTolerance: Double,
message: String? = null
) {
assertContentEquals(
expected.coefficients,
actual.coefficients,
absoluteTolerance,
message
)
}
fun assertEquals(
expected: NumberedRationalFunction<Double>,
actual: NumberedRationalFunction<Double>,
absoluteTolerance: Double,
message: String? = null
) {
assertEquals(
expected.numerator,
actual.numerator,
absoluteTolerance,
message
)
assertEquals(
expected.denominator,
actual.denominator,
absoluteTolerance,
message
)
}
fun assertEquals(
expected: LabeledRationalFunction<Double>,
actual: LabeledRationalFunction<Double>,
absoluteTolerance: Double,
message: String? = null
) {
assertEquals(
expected.numerator,
actual.numerator,
absoluteTolerance,
message
)
assertEquals(
expected.denominator,
actual.denominator,
absoluteTolerance,
message
)
}
inline fun <reified T : Throwable> assertFailsWithTypeAndMessage(
expectedMessage: String? = null,
assertionMessage: String? = null,
block: () -> Unit
) {
assertEquals(
expectedMessage,
assertFailsWith(T::class, assertionMessage, block).message,
assertionMessage
)
}

View File

@ -0,0 +1,19 @@
/*
* 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.testUtils
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.expressions.symbol
val o: Rational = Rational(0)
val x: Symbol by symbol
val y: Symbol by symbol
val z: Symbol by symbol
val t: Symbol by symbol
val s: Symbol by symbol
val iota: Symbol by symbol

View File

@ -26,6 +26,7 @@ include(
":kmath-core", ":kmath-core",
":kmath-coroutines", ":kmath-coroutines",
":kmath-functions", ":kmath-functions",
":kmath-polynomial",
":kmath-histograms", ":kmath-histograms",
":kmath-commons", ":kmath-commons",
":kmath-viktor", ":kmath-viktor",