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 |