Distribution implementations for commons-math

This commit is contained in:
Alexander Nozik 2019-06-08 14:26:44 +03:00
parent 2e76073712
commit 9c2164cd64
24 changed files with 174 additions and 93 deletions

View File

@ -1,6 +1,9 @@
package scientifik.kmath.linear
import koma.matrix.ejml.EJMLMatrixFactory
import scientifik.kmath.commons.linear.CMMatrixContext
import scientifik.kmath.commons.linear.inverse
import scientifik.kmath.commons.linear.toCM
import scientifik.kmath.operations.RealField
import scientifik.kmath.structures.Matrix
import kotlin.contracts.ExperimentalContracts

View File

@ -1,6 +1,8 @@
package scientifik.kmath.linear
import koma.matrix.ejml.EJMLMatrixFactory
import scientifik.kmath.commons.linear.CMMatrixContext
import scientifik.kmath.commons.linear.toCM
import scientifik.kmath.operations.RealField
import scientifik.kmath.structures.Matrix
import kotlin.random.Random

View File

@ -8,6 +8,7 @@ description = "Commons math binding for kmath"
dependencies {
api(project(":kmath-core"))
api(project(":kmath-coroutines"))
api(project(":kmath-prob"))
api("org.apache.commons:commons-math3:3.6.1")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")

View File

@ -1,6 +1,8 @@
package scientifik.kmath.expressions
package scientifik.kmath.commons.expressions
import org.apache.commons.math3.analysis.differentiation.DerivativeStructure
import scientifik.kmath.expressions.Expression
import scientifik.kmath.expressions.ExpressionContext
import scientifik.kmath.operations.ExtendedField
import scientifik.kmath.operations.Field
import kotlin.properties.ReadOnlyProperty
@ -81,8 +83,12 @@ class DerivativeStructureField(
/**
* A constructs that creates a derivative structure with required order on-demand
*/
class DiffExpression(val function: DerivativeStructureField.() -> DerivativeStructure) : Expression<Double> {
override fun invoke(arguments: Map<String, Double>): Double = DerivativeStructureField(0, arguments)
class DiffExpression(val function: DerivativeStructureField.() -> DerivativeStructure) :
Expression<Double> {
override fun invoke(arguments: Map<String, Double>): Double = DerivativeStructureField(
0,
arguments
)
.run(function).value
/**
@ -109,21 +115,27 @@ fun DiffExpression.derivative(name: String) = derivative(name to 1)
* A context for [DiffExpression] (not to be confused with [DerivativeStructure])
*/
object DiffExpressionContext : ExpressionContext<Double>, Field<DiffExpression> {
override fun variable(name: String, default: Double?) = DiffExpression { variable(name, default?.const()) }
override fun variable(name: String, default: Double?) =
DiffExpression { variable(name, default?.const()) }
override fun const(value: Double): DiffExpression = DiffExpression { value.const() }
override fun const(value: Double): DiffExpression =
DiffExpression { value.const() }
override fun add(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) + b.function(this) }
override fun add(a: DiffExpression, b: DiffExpression) =
DiffExpression { a.function(this) + b.function(this) }
override val zero = DiffExpression { 0.0.const() }
override fun multiply(a: DiffExpression, k: Number) = DiffExpression { a.function(this) * k }
override fun multiply(a: DiffExpression, k: Number) =
DiffExpression { a.function(this) * k }
override val one = DiffExpression { 1.0.const() }
override fun multiply(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) * b.function(this) }
override fun multiply(a: DiffExpression, b: DiffExpression) =
DiffExpression { a.function(this) * b.function(this) }
override fun divide(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) / b.function(this) }
override fun divide(a: DiffExpression, b: DiffExpression) =
DiffExpression { a.function(this) / b.function(this) }
}

View File

@ -1,11 +1,13 @@
package scientifik.kmath.linear
package scientifik.kmath.commons.linear
import org.apache.commons.math3.linear.*
import org.apache.commons.math3.linear.RealMatrix
import org.apache.commons.math3.linear.RealVector
import scientifik.kmath.linear.*
import scientifik.kmath.structures.Matrix
class CMMatrix(val origin: RealMatrix, features: Set<MatrixFeature>? = null) : FeaturedMatrix<Double> {
class CMMatrix(val origin: RealMatrix, features: Set<MatrixFeature>? = null) :
FeaturedMatrix<Double> {
override val rowNum: Int get() = origin.rowDimension
override val colNum: Int get() = origin.columnDimension
@ -70,10 +72,14 @@ object CMMatrixContext : MatrixContext<Double> {
override fun multiply(a: Matrix<Double>, k: Number) =
CMMatrix(a.toCM().origin.scalarMultiply(k.toDouble()))
override fun Matrix<Double>.times(value: Double): Matrix<Double> = produce(rowNum,colNum){i,j-> get(i,j)*value}
override fun Matrix<Double>.times(value: Double): Matrix<Double> =
produce(rowNum, colNum) { i, j -> get(i, j) * value }
}
operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin))
operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.subtract(other.origin))
operator fun CMMatrix.plus(other: CMMatrix): CMMatrix =
CMMatrix(this.origin.add(other.origin))
operator fun CMMatrix.minus(other: CMMatrix): CMMatrix =
CMMatrix(this.origin.subtract(other.origin))
infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(this.origin.multiply(other.origin))
infix fun CMMatrix.dot(other: CMMatrix): CMMatrix =
CMMatrix(this.origin.multiply(other.origin))

View File

@ -1,6 +1,7 @@
package scientifik.kmath.linear
package scientifik.kmath.commons.linear
import org.apache.commons.math3.linear.*
import scientifik.kmath.linear.Point
import scientifik.kmath.structures.Matrix
enum class CMDecomposition {

View File

@ -0,0 +1,18 @@
package scientifik.kmath.commons.prob
import org.apache.commons.math3.random.RandomGenerator
inline class CMRandomGeneratorWrapper(val generator: RandomGenerator) : scientifik.kmath.prob.RandomGenerator {
override fun nextDouble(): Double = generator.nextDouble()
override fun nextInt(): Int = generator.nextInt()
override fun nextLong(): Long = generator.nextLong()
override fun nextBlock(size: Int): ByteArray = ByteArray(size).apply { generator.nextBytes(this) }
}
fun RandomGenerator.asKmathGenerator() = CMRandomGeneratorWrapper(this)
fun scientifik.kmath.prob.RandomGenerator.asCMGenerator() =
(this as? CMRandomGeneratorWrapper)?.generator ?: TODO("Implement reverse CM wrapper")

View File

@ -0,0 +1,84 @@
package scientifik.kmath.commons.prob
import org.apache.commons.math3.distribution.*
import scientifik.kmath.chains.Chain
import scientifik.kmath.chains.SimpleChain
import scientifik.kmath.prob.Distribution
import scientifik.kmath.prob.RandomChain
import scientifik.kmath.prob.RandomGenerator
import scientifik.kmath.prob.UnivariateDistribution
import org.apache.commons.math3.random.RandomGenerator as CMRandom
class CMRealDistributionWrapper(val builder: (CMRandom?) -> RealDistribution) : UnivariateDistribution<Double> {
private val defaultDistribution by lazy { builder(null) }
override fun probability(arg: Double): Double = defaultDistribution.probability(arg)
override fun cumulative(arg: Double): Double = defaultDistribution.cumulativeProbability(arg)
override fun sample(generator: RandomGenerator): RandomChain<Double> {
val distribution = builder(generator.asCMGenerator())
return RandomChain(generator) { distribution.sample() }
}
}
class CMIntDistributionWrapper(val builder: (CMRandom?) -> IntegerDistribution) : UnivariateDistribution<Int> {
private val defaultDistribution by lazy { builder(null) }
override fun probability(arg: Int): Double = defaultDistribution.probability(arg)
override fun cumulative(arg: Int): Double = defaultDistribution.cumulativeProbability(arg)
override fun sample(generator: RandomGenerator): RandomChain<Int> {
val distribution = builder(generator.asCMGenerator())
return RandomChain(generator) { distribution.sample() }
}
}
fun Distribution.Companion.normal(mean: Double, sigma: Double): UnivariateDistribution<Double> =
CMRealDistributionWrapper { generator -> NormalDistribution(generator, mean, sigma) }
fun Distribution.Companion.poisson(mean: Double): UnivariateDistribution<Int> = CMIntDistributionWrapper { generator ->
PoissonDistribution(
generator,
mean,
PoissonDistribution.DEFAULT_EPSILON,
PoissonDistribution.DEFAULT_MAX_ITERATIONS
)
}
fun Distribution.Companion.binomial(trials: Int, p: Double): UnivariateDistribution<Int> =
CMIntDistributionWrapper { generator ->
BinomialDistribution(generator, trials, p)
}
fun Distribution.Companion.student(degreesOfFreedom: Double): UnivariateDistribution<Double> =
CMRealDistributionWrapper { generator ->
TDistribution(generator, degreesOfFreedom, TDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
}
fun Distribution.Companion.chi2(degreesOfFreedom: Double): UnivariateDistribution<Double> =
CMRealDistributionWrapper { generator ->
ChiSquaredDistribution(generator, degreesOfFreedom)
}
fun Distribution.Companion.fisher(
numeratorDegreesOfFreedom: Double,
denominatorDegreesOfFreedom: Double
): UnivariateDistribution<Double> =
CMRealDistributionWrapper { generator ->
FDistribution(generator, numeratorDegreesOfFreedom, denominatorDegreesOfFreedom)
}
fun Distribution.Companion.exponential(mean: Double): UnivariateDistribution<Double> =
CMRealDistributionWrapper { generator ->
ExponentialDistribution(generator, mean)
}
fun Distribution.Companion.uniform(a: Double, b: Double): UnivariateDistribution<Double> =
CMRealDistributionWrapper { generator ->
UniformRealDistribution(generator, a, b)
}

View File

@ -1,4 +1,4 @@
package scientifik.kmath.transform
package scientifik.kmath.commons.transform
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow

View File

@ -1,6 +1,9 @@
package scientifik.kmath.expressions
package scientifik.kmath.commons.expressions
import org.junit.Test
import scientifik.kmath.commons.expressions.DerivativeStructureField
import scientifik.kmath.commons.expressions.DiffExpression
import scientifik.kmath.commons.expressions.derivative
import kotlin.test.assertEquals
inline fun <R> diff(order: Int, vararg parameters: Pair<String, Double>, block: DerivativeStructureField.() -> R) =

View File

@ -13,7 +13,7 @@ kotlin.sourceSets {
jvmMain {
dependencies {
// https://mvnrepository.com/artifact/org.apache.commons/commons-rng-simple
api("org.apache.commons:commons-rng-sampling:1.2")
//api("org.apache.commons:commons-rng-sampling:1.2")
compileOnly("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfuVersion}")
}
}

View File

@ -16,8 +16,13 @@ interface Distribution<T : Any> {
* Create a chain of samples from this distribution.
* The chain is not guaranteed to be stateless.
*/
fun sample(generator: RandomGenerator): Chain<T>
fun sample(generator: RandomGenerator): RandomChain<T>
//TODO add sample bunch generator
/**
* An empty companion. Distribution factories should be written as its extensions
*/
companion object
}
interface UnivariateDistribution<T : Comparable<T>> : Distribution<T> {

View File

@ -0,0 +1,17 @@
package scientifik.kmath.prob
import kotlinx.atomicfu.atomic
import scientifik.kmath.chains.Chain
/**
* A possibly stateful chain producing random values.
* TODO make random chain properly fork generator
*/
class RandomChain<out R>(val generator: RandomGenerator, private val gen: suspend RandomGenerator.() -> R) : Chain<R> {
private val atomicValue = atomic<R?>(null)
override fun peek(): R? = atomicValue.value
override suspend fun next(): R = generator.gen().also { atomicValue.lazySet(it) }
override fun fork(): Chain<R> = RandomChain(generator, gen)
}

View File

@ -1,53 +0,0 @@
package scientifik.kmath.prob
import org.apache.commons.rng.sampling.distribution.*
import scientifik.kmath.chains.Chain
import scientifik.kmath.chains.SimpleChain
import kotlin.math.PI
import kotlin.math.exp
import kotlin.math.sqrt
class NormalDistribution(val mean: Double, val sigma: Double) : UnivariateDistribution<Double> {
enum class Sampler {
BoxMuller,
Marsaglia,
Ziggurat
}
override fun probability(arg: Double): Double {
val d = (arg - mean) / sigma
return 1.0 / sqrt(2.0 * PI * sigma) * exp(-d * d / 2)
}
override fun cumulative(arg: Double): Double {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
fun sample(generator: RandomGenerator, sampler: Sampler): Chain<Double> {
val normalized = when (sampler) {
Sampler.BoxMuller -> BoxMullerNormalizedGaussianSampler(generator.asProvider())
Sampler.Marsaglia -> MarsagliaNormalizedGaussianSampler(generator.asProvider())
Sampler.Ziggurat -> ZigguratNormalizedGaussianSampler(generator.asProvider())
}
val gauss = GaussianSampler(normalized, mean, sigma)
//TODO add generator to chain state to allow stateful forks
return SimpleChain { gauss.sample() }
}
override fun sample(generator: RandomGenerator): Chain<Double> = sample(generator, Sampler.BoxMuller)
}
class PoissonDistribution(val mean: Double): UnivariateDistribution<Int>{
override fun probability(arg: Int): Double {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun cumulative(arg: Int): Double {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun sample(generator: RandomGenerator): Chain<Int> {
val sampler = PoissonSampler(generator.asProvider(), mean)
return SimpleChain{sampler.sample()}
}
}

View File

@ -1,18 +0,0 @@
package scientifik.kmath.prob
import org.apache.commons.rng.UniformRandomProvider
inline class CommonsRandomProviderWrapper(val provider: UniformRandomProvider) : RandomGenerator {
override fun nextDouble(): Double = provider.nextDouble()
override fun nextInt(): Int = provider.nextInt()
override fun nextLong(): Long = provider.nextLong()
override fun nextBlock(size: Int): ByteArray = ByteArray(size).also { provider.nextBytes(it) }
}
fun UniformRandomProvider.asGenerator(): RandomGenerator = CommonsRandomProviderWrapper(this)
fun RandomGenerator.asProvider(): UniformRandomProvider =
(this as? CommonsRandomProviderWrapper)?.provider ?: TODO("implement reverse wrapper")

View File

@ -28,5 +28,5 @@ include(
":kmath-commons",
":kmath-koma",
":kmath-prob",
":benchmarks"
":examples"
)