Add minimizeModel to orx-gradient-descent
This commit is contained in:
36
orx-gradient-descent/README.md
Normal file
36
orx-gradient-descent/README.md
Normal 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
|
||||
}
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
34
orx-gradient-descent/src/test/kotlin/TestMinimizeModel.kt
Normal file
34
orx-gradient-descent/src/test/kotlin/TestMinimizeModel.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user