Add minimizeModel to orx-gradient-descent

This commit is contained in:
Edwin Jakobs
2019-09-24 20:07:54 +02:00
parent c49f14fd54
commit d7eadc172c
3 changed files with 84 additions and 5 deletions

View File

@@ -0,0 +1,36 @@
# orx-gradient-descent
A gradient descent based minimizer that is incredibly
easy to use.
## Usage
```kotlin
// define a model
class Model {
var x = 0.0
var y = 0.0
}
val model = Model()
minimizeModel(model) { m ->
(m.x-4.0)*(m.x-4.0) + (m.y-3.0)*(m.y-3.0)
}
// model.x is close to 4 and model y is close to 3 at this point
```
## Data binding
Currently we support minimizing model classes that contain
`Double`, `Vector2`, `Vector3` and `Vector4` typed properties,
other types are silently ignored.
An example of a supported model:
```kotlin
class Model {
var x = 0.0
var y = 0.0
var v2 = Vector2.ZERO
}
```

View File

@@ -96,7 +96,7 @@ fun dot(x: Array<DoubleArray>, y: DoubleArray): DoubleArray = DoubleArray(x.size
class MinimizationResult(val solution: DoubleArray, val value: Double, val gradient: DoubleArray,
val inverseHessian: Array<DoubleArray>, val iterations: Int)
fun minimize(_x0: DoubleArray, endOnLineSearch: Boolean = true, tol: Double = 1e-8, maxIterations: Int = 1000, f: (DoubleArray) -> Double): MinimizationResult {
fun minimize(_x0: DoubleArray, endOnLineSearch: Boolean = false, tol: Double = 1e-8, maxIterations: Int = 1000, f: (DoubleArray) -> Double): MinimizationResult {
val grad = { a: DoubleArray -> gradient(a, f) }
var x0 = _x0.copyOf()
var g0 = grad(x0)
@@ -108,8 +108,8 @@ fun minimize(_x0: DoubleArray, endOnLineSearch: Boolean = true, tol: Double = 1e
while (iteration < maxIterations) {
require(g0.all { it == it && it != Double.POSITIVE_INFINITY && it != Double.NEGATIVE_INFINITY })
val pstep = dot(H1, g0)
require(pstep.all { it == it }) { "pstep contains NaNs"}
require(pstep.all { it != Double.POSITIVE_INFINITY && it != Double.NEGATIVE_INFINITY }) { "pstep contains infs" }
require(pstep.all { it == it }) { "pstep contains NaNs" }
require(pstep.all { it != Double.POSITIVE_INFINITY && it != Double.NEGATIVE_INFINITY }) { "pstep contains infs" }
val step = neg(pstep)
val nstep = norm2(step)
@@ -127,7 +127,7 @@ fun minimize(_x0: DoubleArray, endOnLineSearch: Boolean = true, tol: Double = 1e
x1 = add(x0, s)
f1 = f(x1)
require(f1 == f1) { "f1 is NaN"}
require(f1 == f1) { "f1 is NaN" }
if (!(f1 - f0 >= 0.1 * t * df0)) {
break
}
@@ -144,7 +144,7 @@ fun minimize(_x0: DoubleArray, endOnLineSearch: Boolean = true, tol: Double = 1e
require(g1.all { it == it })
val y = sub(g1, g0)
val ys = dot(y, s)
if (ys==0.0) {
if (ys == 0.0) {
break
}
val Hy = dot(H1, y)
@@ -172,3 +172,12 @@ fun minimize(_x0: DoubleArray, endOnLineSearch: Boolean = true, tol: Double = 1e
return MinimizationResult(x0, f0, g0, H1, iteration)
}
fun <T : Any> minimizeModel(model: T, endOnLineSearch: Boolean = false, tol: Double = 1e-8, maxIterations: Int = 1000, function: (T) -> Double) {
val doubles = modelToArray(model)
val solution = minimize(doubles, endOnLineSearch, tol, maxIterations) {
arrayToModel(it, model)
function(model)
}
arrayToModel(solution.solution, model)
}

View File

@@ -0,0 +1,34 @@
import org.amshove.kluent.shouldBeNear
import org.openrndr.extra.gradientdescent.minimizeModel
import org.openrndr.math.Vector2
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
object TestMinimizeModel : Spek({
describe("a model") {
val m = object {
var x = 0.0
var y = 0.0
}
it("can be minimized") {
minimizeModel(m) { m->
(m.x - 4.0) * (m.x - 4.0) + (m.y - 3.0) * (m.y - 3.0)
}
m.x.shouldBeNear(4.0, 0.01)
m.y.shouldBeNear(3.0, 0.01)
}
}
describe("a model with a Vector2 property") {
val m = object {
var position = Vector2.ZERO
}
it("can be minimized") {
minimizeModel(m) { m->
(m.position.x - 4.0) * (m.position.x - 4.0) + (m.position.y - 3.0) * (m.position.y - 3.0)
}
m.position.x.shouldBeNear(4.0, 0.01)
m.position.y.shouldBeNear(3.0, 0.01)
}
}
})