| 1 | package com.github.sanity.pav.spline | |
| 2 | ||
| 3 | import com.github.sanity.pav.BinarySearchResult | |
| 4 | import com.github.sanity.pav.Point | |
| 5 | import com.github.sanity.pav.betterBinarySearch | |
| 6 | import java.util.* | |
| 7 | ||
| 8 | /** | |
| 9 | * Creates a monotone cubic spline from a given set of control points, implementing | |
| 10 | * the algorithm described [here](https://en.wikipedia.org/wiki/Monotone_cubic_interpolation). | |
| 11 | ||
| 12 | * The spline is guaranteed to pass through each control point exactly. | |
| 13 | * Moreover, assuming the control points are monotonic (Y is non-decreasing or | |
| 14 | * non-increasing) then the interpolated values will also be monotonic. | |
| 15 | ||
| 16 | * @param xPoints The X component of the control points, strictly increasing. | |
| 17 | * * | |
| 18 | * @param yPoints The Y component of the control points, monotonic. | |
| 19 | * * | |
| 20 | * @return | |
| 21 | * * | |
| 22 | * * | |
| 23 | * @throws IllegalArgumentException if the X or Y arrays are null, have | |
| 24 | * * different lengths or have fewer than 2 values. | |
| 25 | * * | |
| 26 | * @throws IllegalArgumentException if the control points are not monotonic. | |
| 27 | */ | |
| 28 | ||
| 29 | class MonotoneSpline(inputPoints: List<Point>, tangentStrategy: TangentStrategy = FritschCarlsonTangentStrategy()) { | |
| 30 | ||
| 31 | private val points: ArrayList<PointWithTangents> | |
| 32 | ||
| 33 | init { | |
| 34 | val pointsWithSecants = SecantsCalculator.calculate(inputPoints) | |
| 35 | points = tangentStrategy.compute(pointsWithSecants) | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * Interpolates the value of Y = f(X) for given X. | |
| 40 | * Clamps X to the domain of the spline. | |
| 41 | ||
| 42 | * @param x The X value. | |
| 43 | * * | |
| 44 | * @return The interpolated Y = f(X) value. | |
| 45 | */ | |
| 46 | fun interpolate(x: Double): Double { | |
| 47 | val firstPoint = points.first() | |
| 48 |
3
1. interpolate : changed conditional boundary → SURVIVED 2. interpolate : negated conditional → KILLED 3. interpolate : replaced return of double value with -(x + 1) for com/github/sanity/pav/spline/MonotoneSpline::interpolate → KILLED |
if (x < firstPoint.x) return firstPoint.y |
| 49 | val lastPoint = points.last() | |
| 50 |
3
1. interpolate : changed conditional boundary → SURVIVED 2. interpolate : negated conditional → KILLED 3. interpolate : replaced return of double value with -(x + 1) for com/github/sanity/pav/spline/MonotoneSpline::interpolate → KILLED |
if (x > lastPoint.x) return lastPoint.y |
| 51 | val binarySearchResult = points.map { it.x }.toDoubleArray().betterBinarySearch(x) | |
| 52 |
1
1. interpolate : replaced return of double value with -(x + 1) for com/github/sanity/pav/spline/MonotoneSpline::interpolate → KILLED |
return when (binarySearchResult) { |
| 53 |
1
1. interpolate : negated conditional → KILLED |
is BinarySearchResult.Exact -> points[binarySearchResult.index].y |
| 54 |
1
1. interpolate : negated conditional → KILLED |
is BinarySearchResult.Between -> { |
| 55 | val x1 = points[binarySearchResult.lowIndex].x | |
| 56 | val y1 = points[binarySearchResult.lowIndex].y | |
| 57 | val m1 = points[binarySearchResult.lowIndex].tangent | |
| 58 | val x2 = points[binarySearchResult.highIndex].x | |
| 59 | val y2 = points[binarySearchResult.highIndex].y | |
| 60 | val m2 = points[binarySearchResult.highIndex].tangent | |
| 61 | val chs = CubicHermiteSpline(x1 = x1, y1 = y1, m1 = m1, x2 = x2, y2 = y2, m2 = m2) | |
| 62 |
1
1. interpolate : replaced return of double value with -(x + 1) for com/github/sanity/pav/spline/MonotoneSpline::interpolate → KILLED |
return chs.interpolate(x) |
| 63 | } | |
| 64 | else -> TODO("Remove this, shouldn't be necessary") | |
| 65 | } | |
| 66 | } | |
| 67 | } | |
| 68 | ||
Mutations | ||
| 48 |
1.1 2.2 3.3 |
|
| 50 |
1.1 2.2 3.3 |
|
| 52 |
1.1 |
|
| 53 |
1.1 |
|
| 54 |
1.1 |
|
| 62 |
1.1 |
|
| 71 |
1.1 |