diff options
| author | Juan Marin Noguera <juan@mnpi.eu> | 2023-07-25 18:22:04 +0200 |
|---|---|---|
| committer | Juan Marin Noguera <juan@mnpi.eu> | 2023-07-25 18:22:04 +0200 |
| commit | 46afb6bf501f4001c4bdb7bd2f2db7c466f95554 (patch) | |
| tree | 38700d5545a2cf172434aa656825371ff9563825 /dense.go | |
Solvned project from 2020
Note that Go didn't have generics back then.
Diffstat (limited to 'dense.go')
| -rw-r--r-- | dense.go | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/dense.go b/dense.go new file mode 100644 index 0000000..8bfe9c5 --- /dev/null +++ b/dense.go @@ -0,0 +1,174 @@ +// Dense solutions. +// +// Copyright (C) 2020 Juan Marín Noguera +// +// This file is part of Solvned. +// +// Solvned is free software: you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// Solvned is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +// A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Solvned. If not, see <https://www.gnu.org/licenses/>. + +package mned + +// A DenseSolution is a structure with precalculated points of a solution within +// a given interval. It allows getting the value of any point within that +// interval via interpolation. The zero value is meaningless. +type DenseSolution struct { + points []Point // This can't be empty. Points are ordered increasingly. + interp Interpolator +} + +// Get the earliest time of the points in the solution. +func (d *DenseSolution) Start() float64 { + return d.points[0].Time +} + +// Get the latest time of the points in the solution. +func (d *DenseSolution) End() float64 { + return d.points[len(d.points)-1].Time +} + +func (d *DenseSolution) search(time float64, min int, max int) []float64 { + if min == max { + return d.points[min].Value + } + if max-min == 1 { + switch time { + case d.points[min].Time: + return d.points[min].Clone().Value + case d.points[max].Time: + return d.points[max].Clone().Value + } + return d.interp.FindValue(&d.points[min], &d.points[max], time) + } + mid := (max - min) / 2 + midtime := d.points[mid].Time + switch { + case time == midtime: + return d.points[mid].Value + case time > midtime: + return d.search(time, mid, max) + default: + return d.search(time, min, mid) + } +} + +// Get the value for a point in the solution with a given time between +// `d.Start()` and `d.End()`. If the second return value is false, the point +// was not in the interval and the first return value is meaningless. +func (d *DenseSolution) Get(time float64) ([]float64, bool) { + if time < d.Start() { + return d.points[0].Value, false + } + if time > d.End() { + return d.points[len(d.points)-1].Value, false + } + return d.search(time, 0, len(d.points)-1), true +} + +// Return the list of all explicitly-calculated points. This slice *MUST NOT* +// be modified, nor any of the points it contains. +func (d *DenseSolution) InnerPoints() []Point { + return d.points +} + +// Like CacheSolver.PointCoords but for a DenseSolution. +func (d *DenseSolution) PointCoords() [][]float64 { + size := len(d.points[0].Value) + result := make([][]float64, size+1) + for i := range result { + result[i] = make([]float64, len(d.points)) + } + for i, p := range d.points { + result[0][i] = p.Time + for j, v := range p.Value { + result[j+1][i] = v + } + } + return result +} + +// Solve an initial value problem in an interval `[start, end]` with the given +// method. The points are calculated eagerly and, when a point is requested not +// given by the method, interpolation is used with the given interpolator. +// +// If the second return value is false, the method didn't get to generate any +// point and the first return value is meaningless. If it's true, the first +// return value contains a solution for an interval that contains at most two +// explicitly-stored points outside the interval requested (the two extremes) +// but which might not contain the whole interval requested if the method +// stopped. +func DenseSolve( + method Method, + ivp *IVP, + start float64, + end float64, + interp Interpolator, +) (DenseSolution, bool) { + forward := method.Forward(ivp) + backward := method.Backward(ivp) + points := make([]Point, 0) + current := &ivp.Start + pendingAddInitial := true + var prev Point + var ok bool + + if end < current.Time { // Special case: end is on the left + for end < current.Time { + prev = current.Clone() + if current, ok = backward.Next(); !ok { + return DenseSolution{}, false + } + } + if current.Time < end { + points = append(points, prev) + } + points = append(points, current.Clone()) + pendingAddInitial = false + } + for start < current.Time { // Move backwards until the start + if current, ok = backward.Next(); !ok { + break + } + points = append(points, current.Clone()) + } + size := len(points) // Reverse backwards points + for i := 0; i < size/2; i++ { + points[i], points[size-i] = points[size-i], points[i] + } + + current = &ivp.Start + if start > current.Time { // Special case: start is on the right + for start > current.Time { + prev = current.Clone() + if current, ok = forward.Next(); !ok { + return DenseSolution{}, false + } + } + if current.Time > start { + points = append(points, prev) + } + points = append(points, current.Clone()) + pendingAddInitial = false + } + if pendingAddInitial { + points = append(points, ivp.Start) + } + for end > current.Time { // Move forwards until the end + if current, ok = forward.Next(); !ok { + break + } + points = append(points, current.Clone()) + } + + return DenseSolution{points: points, interp: interp}, true +} |
