Add adapters of scalar functions to MST and vice versa #150

Merged
CommanderTvis merged 23 commits from kotlingrad into dev 2020-11-01 21:09:45 +03:00
12 changed files with 74 additions and 44 deletions
Showing only changes of commit 33d23c8d28 - Show all commits

View File

@ -211,7 +211,15 @@ Release artifacts are accessible from bintray with following configuration (see
```kotlin ```kotlin
repositories { repositories {
jcenter()
maven("https://clojars.org/repo")
maven("https://dl.bintray.com/egor-bogomolov/astminer/")
maven("https://dl.bintray.com/hotkeytlt/maven")
maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://dl.bintray.com/kotlin/kotlinx")
maven("https://dl.bintray.com/mipt-npm/kscience") maven("https://dl.bintray.com/mipt-npm/kscience")
maven("https://jitpack.io")
mavenCentral()
} }
dependencies { dependencies {
@ -228,7 +236,15 @@ Development builds are uploaded to the separate repository:
```kotlin ```kotlin
repositories { repositories {
jcenter()
maven("https://clojars.org/repo")
maven("https://dl.bintray.com/egor-bogomolov/astminer/")
maven("https://dl.bintray.com/hotkeytlt/maven")
maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://dl.bintray.com/kotlin/kotlinx")
maven("https://dl.bintray.com/mipt-npm/dev") maven("https://dl.bintray.com/mipt-npm/dev")
maven("https://jitpack.io")
mavenCentral()
} }
``` ```

View File

@ -1,3 +1,5 @@
import ru.mipt.npm.gradle.KSciencePublishPlugin
plugins { plugins {
id("ru.mipt.npm.project") id("ru.mipt.npm.project")
} }
@ -17,6 +19,7 @@ allprojects {
maven("https://dl.bintray.com/mipt-npm/dev") maven("https://dl.bintray.com/mipt-npm/dev")
maven("https://dl.bintray.com/mipt-npm/kscience") maven("https://dl.bintray.com/mipt-npm/kscience")
maven("https://jitpack.io") maven("https://jitpack.io")
maven("http://logicrunch.research.it.uu.se/maven/")
mavenCentral() mavenCentral()
} }
@ -25,7 +28,7 @@ allprojects {
} }
subprojects { subprojects {
if (name.startsWith("kmath")) apply<ru.mipt.npm.gradle.KSciencePublishPlugin>() if (name.startsWith("kmath")) apply<KSciencePublishPlugin>()
} }
readme { readme {

View File

@ -10,6 +10,20 @@ plugins {
allOpen.annotation("org.openjdk.jmh.annotations.State") allOpen.annotation("org.openjdk.jmh.annotations.State")
sourceSets.register("benchmarks") sourceSets.register("benchmarks")
repositories {
jcenter()
altavir commented 2020-11-01 12:05:25 +03:00 (Migrated from github.com)
Review

See the comment above. I think it makes sense to leave them here for people to see which repositories to use.

See the comment above. I think it makes sense to leave them here for people to see which repositories to use.
maven("https://clojars.org/repo")
maven("https://dl.bintray.com/egor-bogomolov/astminer/")
maven("https://dl.bintray.com/hotkeytlt/maven")
maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://dl.bintray.com/kotlin/kotlinx")
maven("https://dl.bintray.com/mipt-npm/dev")
maven("https://dl.bintray.com/mipt-npm/kscience")
maven("https://jitpack.io")
maven("http://logicrunch.research.it.uu.se/maven/")
mavenCentral()
}
dependencies { dependencies {
implementation(project(":kmath-ast")) implementation(project(":kmath-ast"))
implementation(project(":kmath-kotlingrad")) implementation(project(":kmath-kotlingrad"))

View File

@ -1,9 +1,9 @@
package kscience.kmath.ast package kscience.kmath.ast
import kscience.kmath.asm.compile import kscience.kmath.asm.compile
import kscience.kmath.expressions.derivative
import kscience.kmath.expressions.invoke import kscience.kmath.expressions.invoke
import kscience.kmath.expressions.symbol import kscience.kmath.expressions.symbol
import kscience.kmath.kotlingrad.derivative
import kscience.kmath.kotlingrad.differentiable import kscience.kmath.kotlingrad.differentiable
import kscience.kmath.operations.RealField import kscience.kmath.operations.RealField

View File

@ -19,9 +19,8 @@ import kotlin.reflect.KClass
public operator fun PointValuePair.component1(): DoubleArray = point public operator fun PointValuePair.component1(): DoubleArray = point
public operator fun PointValuePair.component2(): Double = value public operator fun PointValuePair.component2(): Double = value
public class CMOptimizationProblem( public class CMOptimizationProblem(override val symbols: List<Symbol>, ) :
override val symbols: List<Symbol>, OptimizationProblem<Double>, SymbolIndexer, OptimizationFeature {
) : OptimizationProblem<Double>, SymbolIndexer, OptimizationFeature {
private val optimizationData: HashMap<KClass<out OptimizationData>, OptimizationData> = HashMap() private val optimizationData: HashMap<KClass<out OptimizationData>, OptimizationData> = HashMap()
private var optimizatorBuilder: (() -> MultivariateOptimizer)? = null private var optimizatorBuilder: (() -> MultivariateOptimizer)? = null
public var convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(DEFAULT_RELATIVE_TOLERANCE, public var convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(DEFAULT_RELATIVE_TOLERANCE,
@ -49,7 +48,7 @@ public class CMOptimizationProblem(
addOptimizationData(objectiveFunction) addOptimizationData(objectiveFunction)
} }
public override fun diffExpression(expression: DifferentiableExpression<Double>): Unit { public override fun diffExpression(expression: DifferentiableExpression<Double, Expression<Double>>) {
expression(expression) expression(expression)
val gradientFunction = ObjectiveFunctionGradient { val gradientFunction = ObjectiveFunctionGradient {
val args = it.toMap() val args = it.toMap()

View File

@ -12,7 +12,6 @@ import kscience.kmath.structures.asBuffer
import org.apache.commons.math3.analysis.differentiation.DerivativeStructure import org.apache.commons.math3.analysis.differentiation.DerivativeStructure
import org.apache.commons.math3.optim.nonlinear.scalar.GoalType import org.apache.commons.math3.optim.nonlinear.scalar.GoalType
/** /**
* Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
*/ */
@ -21,7 +20,7 @@ public fun Fitting.chiSquared(
y: Buffer<Double>, y: Buffer<Double>,
yErr: Buffer<Double>, yErr: Buffer<Double>,
model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure,
): DifferentiableExpression<Double> = chiSquared(DerivativeStructureField, x, y, yErr, model) ): DifferentiableExpression<Double, Expression<Double>> = chiSquared(DerivativeStructureField, x, y, yErr, model)
/** /**
* Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
@ -31,7 +30,7 @@ public fun Fitting.chiSquared(
y: Iterable<Double>, y: Iterable<Double>,
yErr: Iterable<Double>, yErr: Iterable<Double>,
model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure,
): DifferentiableExpression<Double> = chiSquared( ): DifferentiableExpression<Double, Expression<Double>> = chiSquared(
DerivativeStructureField, DerivativeStructureField,
x.toList().asBuffer(), x.toList().asBuffer(),
y.toList().asBuffer(), y.toList().asBuffer(),
@ -39,7 +38,6 @@ public fun Fitting.chiSquared(
model model
) )
/** /**
* Optimize expression without derivatives * Optimize expression without derivatives
*/ */
@ -48,16 +46,15 @@ public fun Expression<Double>.optimize(
configuration: CMOptimizationProblem.() -> Unit, configuration: CMOptimizationProblem.() -> Unit,
): OptimizationResult<Double> = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration) ): OptimizationResult<Double> = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration)
/** /**
* Optimize differentiable expression * Optimize differentiable expression
*/ */
public fun DifferentiableExpression<Double>.optimize( public fun DifferentiableExpression<Double, Expression<Double>>.optimize(
vararg symbols: Symbol, vararg symbols: Symbol,
configuration: CMOptimizationProblem.() -> Unit, configuration: CMOptimizationProblem.() -> Unit,
): OptimizationResult<Double> = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration) ): OptimizationResult<Double> = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration)
public fun DifferentiableExpression<Double>.minimize( public fun DifferentiableExpression<Double, Expression<Double>>.minimize(
vararg startPoint: Pair<Symbol, Double>, vararg startPoint: Pair<Symbol, Double>,
configuration: CMOptimizationProblem.() -> Unit = {}, configuration: CMOptimizationProblem.() -> Unit = {},
): OptimizationResult<Double> { ): OptimizationResult<Double> {

View File

@ -47,14 +47,17 @@ internal class OptimizeTest {
val sigma = 1.0 val sigma = 1.0
val generator = Distribution.normal(0.0, sigma) val generator = Distribution.normal(0.0, sigma)
val chain = generator.sample(RandomGenerator.default(112667)) val chain = generator.sample(RandomGenerator.default(112667))
val x = (1..100).map { it.toDouble() } val x = (1..100).map(Int::toDouble)
val y = x.map { it ->
val y = x.map {
it.pow(2) + it + 1 + chain.nextDouble() it.pow(2) + it + 1 + chain.nextDouble()
} }
val yErr = x.map { sigma }
val chi2 = Fitting.chiSquared(x, y, yErr) { x -> val yErr = List(x.size) { sigma }
val chi2 = Fitting.chiSquared(x, y, yErr) { x1 ->
val cWithDefault = bindOrNull(c) ?: one val cWithDefault = bindOrNull(c) ?: one
bind(a) * x.pow(2) + bind(b) * x + cWithDefault bind(a) * x1.pow(2) + bind(b) * x1 + cWithDefault
} }
val result = chi2.minimize(a to 1.5, b to 0.9, c to 1.0) val result = chi2.minimize(a to 1.5, b to 0.9, c to 1.0)

View File

@ -6,7 +6,7 @@ package kscience.kmath.expressions
* @param T the type this expression takes as argument and returns. * @param T the type this expression takes as argument and returns.
* @param R the type of expression this expression can be differentiated to. * @param R the type of expression this expression can be differentiated to.
*/ */
public interface DifferentiableExpression<T, R : Expression<T>> : Expression<T> { public interface DifferentiableExpression<T, out R : Expression<T>> : Expression<T> {
/** /**
* Differentiates this expression by ordered collection of [symbols]. * Differentiates this expression by ordered collection of [symbols].
* *
@ -43,6 +43,6 @@ public abstract class FirstDerivativeExpression<T, R : Expression<T>> : Differen
/** /**
* A factory that converts an expression in autodiff variables to a [DifferentiableExpression] * A factory that converts an expression in autodiff variables to a [DifferentiableExpression]
*/ */
public fun interface AutoDiffProcessor<T : Any, I : Any, A : ExpressionAlgebra<T, I>, R : Expression<T>> { public fun interface AutoDiffProcessor<T : Any, I : Any, A : ExpressionAlgebra<T, I>, out R : Expression<T>> {
public fun process(function: A.() -> I): DifferentiableExpression<T, R> public fun process(function: A.() -> I): DifferentiableExpression<T, R>
} }

View File

@ -13,13 +13,11 @@ import kotlin.test.assertTrue
import kotlin.test.fail import kotlin.test.fail
internal class AdaptingTests { internal class AdaptingTests {
private val proto: DReal = DoublePrecision.prototype
@Test @Test
fun symbol() { fun symbol() {
val c1 = MstAlgebra.symbol("x") val c1 = MstAlgebra.symbol("x")
assertTrue(c1.toSVar(proto).name == "x") assertTrue(c1.toSVar<KMathNumber<Double, RealField>>().name == "x")
val c2 = "kitten".parseMath().toSFun(proto) val c2 = "kitten".parseMath().toSFun<KMathNumber<Double, RealField>>()
if (c2 is SVar) assertTrue(c2.name == "kitten") else fail() if (c2 is SVar) assertTrue(c2.name == "kitten") else fail()
} }
@ -27,15 +25,15 @@ internal class AdaptingTests {
fun number() { fun number() {
val c1 = MstAlgebra.number(12354324) val c1 = MstAlgebra.number(12354324)
assertTrue(c1.toSConst<DReal>().doubleValue == 12354324.0) assertTrue(c1.toSConst<DReal>().doubleValue == 12354324.0)
val c2 = "0.234".parseMath().toSFun(proto) val c2 = "0.234".parseMath().toSFun<KMathNumber<Double, RealField>>()
if (c2 is SConst) assertTrue(c2.doubleValue == 0.234) else fail() if (c2 is SConst) assertTrue(c2.doubleValue == 0.234) else fail()
val c3 = "1e-3".parseMath().toSFun(proto) val c3 = "1e-3".parseMath().toSFun<KMathNumber<Double, RealField>>()
if (c3 is SConst) assertEquals(0.001, c3.value) else fail() if (c3 is SConst) assertEquals(0.001, c3.value) else fail()
} }
@Test @Test
fun simpleFunctionShape() { fun simpleFunctionShape() {
val linear = "2*x+16".parseMath().toSFun(proto) val linear = "2*x+16".parseMath().toSFun<KMathNumber<Double, RealField>>()
if (linear !is Sum) fail() if (linear !is Sum) fail()
altavir commented 2020-11-01 12:12:39 +03:00 (Migrated from github.com)
Review

Better to use AssertFalse

Better to use AssertFalse
CommanderTvis commented 2020-11-01 13:41:01 +03:00 (Migrated from github.com)
Review

No. fail is better since it returns Nothing, so Kotlin DFA makes smart-cast of linear to Sum

No. `fail` is better since it returns Nothing, so Kotlin DFA makes smart-cast of `linear` to `Sum`
if (linear.left !is Prod) fail() if (linear.left !is Prod) fail()
if (linear.right !is SConst) fail() if (linear.right !is SConst) fail()
@ -43,8 +41,8 @@ internal class AdaptingTests {
@Test @Test
fun simpleFunctionDerivative() { fun simpleFunctionDerivative() {
val x = MstAlgebra.symbol("x").toSVar(proto) val x = MstAlgebra.symbol("x").toSVar<KMathNumber<Double, RealField>>()
val quadratic = "x^2-4*x-44".parseMath().toSFun(proto) val quadratic = "x^2-4*x-44".parseMath().toSFun<KMathNumber<Double, RealField>>()
val actualDerivative = MstExpression(RealField, quadratic.d(x).toMst()).compile() val actualDerivative = MstExpression(RealField, quadratic.d(x).toMst()).compile()
val expectedDerivative = MstExpression(RealField, "2*x-4".parseMath()).compile() val expectedDerivative = MstExpression(RealField, "2*x-4".parseMath()).compile()
assertEquals(actualDerivative("x" to 123.0), expectedDerivative("x" to 123.0)) assertEquals(actualDerivative("x" to 123.0), expectedDerivative("x" to 123.0))
@ -52,8 +50,8 @@ internal class AdaptingTests {
@Test @Test
fun moreComplexDerivative() { fun moreComplexDerivative() {
val x = MstAlgebra.symbol("x").toSVar(proto) val x = MstAlgebra.symbol("x").toSVar<KMathNumber<Double, RealField>>()
val composition = "-sqrt(sin(x^2)-cos(x)^2-16*x)".parseMath().toSFun(proto) val composition = "-sqrt(sin(x^2)-cos(x)^2-16*x)".parseMath().toSFun<KMathNumber<Double, RealField>>()
val actualDerivative = MstExpression(RealField, composition.d(x).toMst()).compile() val actualDerivative = MstExpression(RealField, composition.d(x).toMst()).compile()
val expectedDerivative = MstExpression( val expectedDerivative = MstExpression(

View File

@ -12,16 +12,18 @@ public object Fitting {
* Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
*/ */
public fun <T : Any, I : Any, A> chiSquared( public fun <T : Any, I : Any, A> chiSquared(
autoDiff: AutoDiffProcessor<T, I, A>, autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
x: Buffer<T>, x: Buffer<T>,
y: Buffer<T>, y: Buffer<T>,
yErr: Buffer<T>, yErr: Buffer<T>,
model: A.(I) -> I, model: A.(I) -> I,
): DifferentiableExpression<T> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> { ): DifferentiableExpression<T, Expression<T>> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
require(x.size == y.size) { "X and y buffers should be of the same size" } require(x.size == y.size) { "X and y buffers should be of the same size" }
require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } require(y.size == yErr.size) { "Y and yErr buffer should of the same size" }
return autoDiff.process { return autoDiff.process {
var sum = zero var sum = zero
x.indices.forEach { x.indices.forEach {
val xValue = const(x[it]) val xValue = const(x[it])
val yValue = const(y[it]) val yValue = const(y[it])
@ -29,6 +31,7 @@ public object Fitting {
val modelValue = model(xValue) val modelValue = model(xValue)
sum += ((yValue - modelValue) / yErrValue).pow(2) sum += ((yValue - modelValue) / yErrValue).pow(2)
} }
sum sum
} }
} }
@ -45,6 +48,7 @@ public object Fitting {
): Expression<Double> { ): Expression<Double> {
require(x.size == y.size) { "X and y buffers should be of the same size" } require(x.size == y.size) { "X and y buffers should be of the same size" }
require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } require(y.size == yErr.size) { "Y and yErr buffer should of the same size" }
return Expression { arguments -> return Expression { arguments ->
x.indices.sumByDouble { x.indices.sumByDouble {
val xValue = x[it] val xValue = x[it]
@ -56,4 +60,4 @@ public object Fitting {
} }
} }
} }
} }

View File

@ -27,17 +27,17 @@ public interface OptimizationProblem<T : Any> {
/** /**
* Define the initial guess for the optimization problem * Define the initial guess for the optimization problem
*/ */
public fun initialGuess(map: Map<Symbol, T>): Unit public fun initialGuess(map: Map<Symbol, T>)
/** /**
* Set an objective function expression * Set an objective function expression
*/ */
public fun expression(expression: Expression<T>): Unit public fun expression(expression: Expression<T>)
/** /**
* Set a differentiable expression as objective function as function and gradient provider * Set a differentiable expression as objective function as function and gradient provider
*/ */
public fun diffExpression(expression: DifferentiableExpression<T>): Unit public fun diffExpression(expression: DifferentiableExpression<T, Expression<T>>)
/** /**
* Update the problem from previous optimization run * Update the problem from previous optimization run
@ -50,9 +50,8 @@ public interface OptimizationProblem<T : Any> {
public fun optimize(): OptimizationResult<T> public fun optimize(): OptimizationResult<T>
} }
public interface OptimizationProblemFactory<T : Any, out P : OptimizationProblem<T>> { public fun interface OptimizationProblemFactory<T : Any, out P : OptimizationProblem<T>> {
public fun build(symbols: List<Symbol>): P public fun build(symbols: List<Symbol>): P
} }
public operator fun <T : Any, P : OptimizationProblem<T>> OptimizationProblemFactory<T, P>.invoke( public operator fun <T : Any, P : OptimizationProblem<T>> OptimizationProblemFactory<T, P>.invoke(
@ -60,7 +59,6 @@ public operator fun <T : Any, P : OptimizationProblem<T>> OptimizationProblemFac
block: P.() -> Unit, block: P.() -> Unit,
): P = build(symbols).apply(block) ): P = build(symbols).apply(block)
/** /**
* Optimize expression without derivatives using specific [OptimizationProblemFactory] * Optimize expression without derivatives using specific [OptimizationProblemFactory]
*/ */
@ -78,7 +76,7 @@ public fun <T : Any, F : OptimizationProblem<T>> Expression<T>.optimizeWith(
/** /**
* Optimize differentiable expression using specific [OptimizationProblemFactory] * Optimize differentiable expression using specific [OptimizationProblemFactory]
*/ */
public fun <T : Any, F : OptimizationProblem<T>> DifferentiableExpression<T>.optimizeWith( public fun <T : Any, F : OptimizationProblem<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
factory: OptimizationProblemFactory<T, F>, factory: OptimizationProblemFactory<T, F>,
vararg symbols: Symbol, vararg symbols: Symbol,
configuration: F.() -> Unit, configuration: F.() -> Unit,
@ -88,4 +86,3 @@ public fun <T : Any, F : OptimizationProblem<T>> DifferentiableExpression<T>.op
problem.diffExpression(this) problem.diffExpression(this)
return problem.optimize() return problem.optimize()
} }

View File

@ -1,8 +1,7 @@
pluginManagement { pluginManagement {
repositories { repositories {
mavenLocal()
jcenter()
gradlePluginPortal() gradlePluginPortal()
jcenter()
maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://dl.bintray.com/mipt-npm/kscience") maven("https://dl.bintray.com/mipt-npm/kscience")
maven("https://dl.bintray.com/mipt-npm/dev") maven("https://dl.bintray.com/mipt-npm/dev")