Michael Barry 2024-03-25 23:15:24 +02:00 zatwierdzone przez GitHub
commit 723daf3b28
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
6 zmienionych plików z 1053 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,55 @@
package com.onthegomap.planetiler.benchmarks;
import com.onthegomap.planetiler.util.Scales;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Supplier;
public class BenchmarkInterpolator {
private static double sum = 0;
public static void main(String[] args) {
long times = 10_000_000;
benchmarkInterpolator("linear", times, Scales::linear);
benchmarkInterpolator("sqrt", times, Scales::sqrt);
benchmarkInterpolator("pow2", times, () -> Scales.power(2));
benchmarkInterpolator("pow10", times, () -> Scales.power(1));
benchmarkInterpolator("log", times, () -> Scales.log(10));
benchmarkInterpolator("log2", times, () -> Scales.log(2));
System.err.println(sum);
}
private static void benchmarkInterpolator(String name, long times, Supplier<Scales.DoubleContinuous> get) {
benchmarkAndInverted(name + "_2", 1, 2, times, () -> get.get().put(1, 1d).put(2, 2d));
benchmarkAndInverted(name + "_3", 1, 2, times, () -> get.get().put(1, 1d).put(1.5, 2d).put(2, 3d));
}
private static void benchmarkAndInverted(String name, double start, double end, long steps,
Supplier<Scales.DoubleContinuous> build) {
benchmark(name + "_f", start, end, steps, build::get);
benchmark(name + "_i", start, end, steps, () -> build.get().invert());
}
private static void benchmark(String name, double start, double end, long steps,
Supplier<DoubleUnaryOperator> build) {
double delta = (end - start) / steps;
double x = start;
double result = 0;
for (long i = 0; i < steps / 10_000; i++) {
result += build.get().applyAsDouble(x += delta);
}
x = start;
long a = System.currentTimeMillis();
for (long i = 0; i < steps; i++) {
result += build.get().applyAsDouble(x += delta);
}
x = start;
var preBuilt = build.get();
long b = System.currentTimeMillis();
for (long i = 0; i < steps; i++) {
result += preBuilt.applyAsDouble(x += delta);
}
long c = System.currentTimeMillis();
sum += result;
System.err.println(name + "\t" + (b - a) + "\t" + (c - b));
}
}

Wyświetl plik

@ -0,0 +1,24 @@
package com.onthegomap.planetiler.util;
import java.util.function.BiFunction;
import java.util.function.DoubleFunction;
import java.util.function.DoubleUnaryOperator;
public interface IInterpolator<T extends IInterpolator<T, V>, V> extends DoubleFunction<V> {
T self();
T put(double x, V y);
interface ValueInterpolator<V> extends BiFunction<V, V, DoubleFunction<V>> {}
interface Continuous<T extends Interpolator<T, Double> & Continuous<T>>
extends IInterpolator<T, Double>, DoubleUnaryOperator {
default DoubleUnaryOperator invert() {
return Interpolator.invertIt(this.self());
}
default T put(double x, double y) {
return put(x, Double.valueOf(y));
}
}
}

Wyświetl plik

@ -0,0 +1,272 @@
package com.onthegomap.planetiler.util;
import com.carrotsearch.hppc.DoubleArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.function.DoubleFunction;
import java.util.function.DoubleUnaryOperator;
import org.apache.commons.lang3.ArrayUtils;
public class Interpolator<T extends Interpolator<T, V>, V> implements IInterpolator<T, V> {
private static final ValueInterpolator<Double> INTERPOLATE_NUMERIC =
(a, b) -> t -> a * (1 - t) + b * t;
private static final DoubleUnaryOperator IDENTITY = x -> x;
private final ValueInterpolator<V> valueInterpolator;
DoubleUnaryOperator transform = IDENTITY;
DoubleUnaryOperator reverseTransform = IDENTITY;
boolean clamp = false;
private V defaultValue;
final DoubleArrayList domain = new DoubleArrayList();
final List<V> range = new ArrayList<>();
private DoubleFunction<V> fn;
double minKey = Double.POSITIVE_INFINITY;
double maxKey = Double.NEGATIVE_INFINITY;
protected Interpolator(ValueInterpolator<V> valueInterpolator) {
this.valueInterpolator = valueInterpolator;
}
T setTransforms(DoubleUnaryOperator forward, DoubleUnaryOperator reverse) {
fn = null;
this.transform = forward;
this.reverseTransform = reverse;
return self();
}
@Override
public V apply(double operand) {
if (clamp) {
operand = Math.clamp(operand, minKey, maxKey);
}
if (Double.isNaN(operand)) {
return defaultValue;
}
if (fn == null) {
fn = rescale();
}
return fn.apply(transform.applyAsDouble(operand));
}
public double applyAsDouble(double operand) {
return apply(operand) instanceof Number n ? n.doubleValue() : Double.NaN;
}
private DoubleFunction<V> rescale() {
if (domain.size() > 2) {
int j = Math.min(domain.size(), range.size()) - 1;
DoubleUnaryOperator[] d = new DoubleUnaryOperator[j];
DoubleFunction<V>[] r = new DoubleFunction[j];
int i = -1;
double[] domainItems = new double[domain.size()];
for (int k = 0; k < domainItems.length; k++) {
domainItems[k] = transform.applyAsDouble(domain.get(k));
}
List<V> rangeItems = domainItems[j] < domainItems[0] ? range.reversed() : range;
// Reverse descending domains.
if (domainItems[j] < domainItems[0]) {
ArrayUtils.reverse(domainItems);
}
while (++i < j) {
d[i] = normalize(domainItems[i], domainItems[i + 1]);
r[i] = valueInterpolator.apply(rangeItems.get(i), rangeItems.get(i + 1));
}
return x -> {
int ii = bisect(domainItems, x, 1, j) - 1;
return r[ii].apply(d[ii].applyAsDouble(x));
};
} else {
double d0 = transform.applyAsDouble(domain.get(0)), d1 = transform.applyAsDouble(domain.get(1));
V r0 = range.get(0), r1 = range.get(1);
boolean reverse = d1 < d0;
final double dlo = reverse ? d1 : d0;
final double dhi = reverse ? d0 : d1;
final V rlo = reverse ? r1 : r0;
final V rhi = reverse ? r0 : r1;
DoubleUnaryOperator normalize = normalize(dlo, dhi);
DoubleFunction<V> interpolate = valueInterpolator.apply(rlo, rhi);
return x -> interpolate.apply(normalize.applyAsDouble(x));
}
}
private static int bisect(double[] a, double x, int lo, int hi) {
if (lo < hi) {
do {
int mid = (lo + hi) >>> 1;
if (a[mid] <= x)
lo = mid + 1;
else
hi = mid;
} while (lo < hi);
}
return lo;
}
private static DoubleUnaryOperator interpolate(double a, double b) {
return t -> a * (1 - t) + b * t;
}
private static DoubleUnaryOperator normalize(double a, double b) {
double delta = b - a;
return delta == 0 ? x -> 0.5 : Double.isNaN(delta) ? x -> Double.NaN : x -> (x - a) / delta;
}
@SuppressWarnings("unchecked")
public T self() {
return (T) this;
}
public T clamp(boolean clamp) {
this.clamp = clamp;
return self();
}
public T defaultValue(V value) {
this.defaultValue = value;
return self();
}
public T put(double stop, V value) {
fn = null;
minKey = Math.min(stop, minKey);
maxKey = Math.max(stop, maxKey);
domain.add(stop);
range.add(value);
return self();
}
// TODO
// private static class Inverted extends Interpolator<Inverted, Double> {}
public static <T extends Interpolator<T, ? extends Number>> DoubleUnaryOperator invertIt(
Interpolator<T, ? extends Number> interpolator) {
var result = linear();
int j = Math.min(interpolator.domain.size(), interpolator.range.size());
for (int i = 0; i < j; i++) {
result.put(interpolator.range.get(i).doubleValue(),
interpolator.transform.applyAsDouble(interpolator.domain.get(i)));
}
DoubleUnaryOperator retVal =
interpolator.reverseTransform == IDENTITY ? result::applyAsDouble :
x -> interpolator.reverseTransform.applyAsDouble(result.apply(x));
return interpolator.clamp ? retVal.andThen(x -> Math.clamp(x, interpolator.minKey, interpolator.maxKey)) : retVal;
}
public static class Power<T extends Power<T, V>, V> extends Interpolator<T, V> {
public Power(ValueInterpolator<V> valueInterpolator) {
super(valueInterpolator);
}
public T exponent(double exponent) {
double inverse = 1d / exponent;
return setTransforms(x -> Math.pow(x, exponent), y -> Math.pow(y, inverse));
}
public static class Numeric extends Power<Numeric, Double> implements IInterpolator.Continuous<Numeric> {
public Numeric(ValueInterpolator<Double> valueInterpolator) {
super(valueInterpolator);
}
}
public static class Other<V> extends Power<Other<V>, V> {
public Other(ValueInterpolator<V> valueInterpolator) {
super(valueInterpolator);
}
}
}
public static class Log<T extends Log<T, V>, V> extends Interpolator<T, V> {
public Log(ValueInterpolator<V> valueInterpolator) {
super(valueInterpolator);
}
private static DoubleUnaryOperator log(double base) {
double logBase = Math.log(base);
return x -> Math.log(x) / logBase;
}
public T base(double base) {
DoubleUnaryOperator forward =
base == 10d ? Math::log10 :
base == Math.E ? Math::log :
log(base);
DoubleUnaryOperator reverse =
base == Math.E ? Math::exp :
x -> Math.pow(base, x);
return setTransforms(forward, reverse);
}
public static class Numeric extends Log<Log.Numeric, Double> implements IInterpolator.Continuous<Log.Numeric> {
public Numeric(ValueInterpolator<Double> valueInterpolator) {
super(valueInterpolator);
}
}
public static class Other<V> extends Log<Log.Other<V>, V> {
public Other(ValueInterpolator<V> valueInterpolator) {
super(valueInterpolator);
}
}
}
public static class Linear<T extends Linear<T, V>, V> extends Interpolator<T, V> {
public Linear(ValueInterpolator<V> valueInterpolator) {
super(valueInterpolator);
}
public static class Numeric extends Linear<Linear.Numeric, Double>
implements IInterpolator.Continuous<Linear.Numeric> {
public Numeric(ValueInterpolator<Double> valueInterpolator) {
super(valueInterpolator);
}
}
public static class Other<V> extends Linear<Linear.Other<V>, V> {
public Other(ValueInterpolator<V> valueInterpolator) {
super(valueInterpolator);
}
}
}
public static Linear.Numeric linear(ValueInterpolator<Double> valueInterpolator) {
return new Linear.Numeric(valueInterpolator);
}
public static Linear.Numeric linear() {
return new Linear.Numeric(INTERPOLATE_NUMERIC);
}
public static Log.Numeric log(ValueInterpolator<Double> valueInterpolator) {
return new Log.Numeric(valueInterpolator).base(10);
}
public static Log.Numeric log() {
return log(INTERPOLATE_NUMERIC).base(10);
}
// TODO specialized double implementation without boxing (interpolator, values?) ?
// TODO set special args before returning reusable thing? forget self type
public static Power.Numeric npower(ValueInterpolator<Double> valueInterpolator) {
return new Power.Numeric(valueInterpolator).exponent(1);
}
public static Power.Numeric power() {
return npower(INTERPOLATE_NUMERIC).exponent(1);
}
public static Power.Numeric sqrt() {
return power().exponent(0.5);
}
}

Wyświetl plik

@ -0,0 +1,372 @@
package com.onthegomap.planetiler.util;
import com.carrotsearch.hppc.DoubleArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.DoubleFunction;
import java.util.function.DoubleUnaryOperator;
import org.apache.commons.lang3.ArrayUtils;
public class Scales {
private static final Interpolator<Double> INTERPOLATE_NUMERIC =
(a, b) -> t -> a * (1 - t) + b * t;
public static <V> ThresholdScale<V> quantize(int min, int max, V minValue, V... values) {
ThresholdScale<V> result = threshold(minValue);
int n = values.length;
for (int i = 0; i < n; i++) {
result.putAbove(((i + 1d) * max - (i - n) * min) / (n + 1), values[i]);
}
return result;
}
public static <V> ThresholdScale<V> quantize(int min, int max, List<V> values) {
ThresholdScale<V> result = threshold(values.getFirst());
int n = values.size() - 1;
int i = -1;
while (++i < n) {
result.putAbove(((i + 1d) * max - (i - n) * min) / (n + 1), values.get(i + 1));
}
return result;
}
public interface Interpolator<V> extends BiFunction<V, V, DoubleFunction<V>> {}
private interface Scale<T extends Scale<T, V>, V> extends DoubleFunction<V> {
T defaultValue(V defaultValue);
@SuppressWarnings("unchecked")
default T self() {
return (T) this;
}
}
private interface Continuous<T extends Continuous<T, V>, V> extends Scale<T, V> {
T put(double x, V y);
T clamp(boolean clamp);
}
private interface Threshold<T extends Threshold<T, V>, V> extends Scale<T, V> {
T putAbove(double x, V y);
double invertMin(V x);
double invertMax(V x);
Extent invertExtent(V x);
}
public record Extent(double min, double max) {}
public static class ThresholdScale<V> implements Threshold<ThresholdScale<V>, V> {
private V defaultValue = null;
private final List<Double> domain = new ArrayList<>();
private final List<V> range = new ArrayList<>();
private double[] domainArray = null;
private ThresholdScale(V minValue) {
range.add(minValue);
}
@Override
public ThresholdScale<V> putAbove(double x, V y) {
domainArray = null;
domain.add(x);
range.add(y);
return self();
}
@Override
public double invertMin(V x) {
int idx = range.indexOf(x);
return idx < 0 ? Double.NaN : idx == 0 ? Double.NEGATIVE_INFINITY : domain.get(idx - 1);
}
@Override
public double invertMax(V x) {
int idx = range.indexOf(x);
return idx < 0 ? Double.NaN : idx == range.size() - 1 ? Double.POSITIVE_INFINITY : domain.get(idx);
}
@Override
public Extent invertExtent(V x) {
int idx = range.indexOf(x);
return new Extent(
idx < 0 ? Double.NaN : idx == 0 ? Double.NEGATIVE_INFINITY : domain.get(idx - 1),
idx < 0 ? Double.NaN : idx == range.size() - 1 ? Double.POSITIVE_INFINITY : domain.get(idx)
);
}
@Override
public ThresholdScale<V> defaultValue(V defaultValue) {
this.defaultValue = defaultValue;
return self();
}
@Override
public V apply(double x) {
if (domainArray == null) {
domainArray = new double[domain.size()];
int i = 0;
for (Double d : domain) {
domainArray[i++] = d;
}
}
if (Double.isNaN(x)) {
return defaultValue;
}
int idx = bisect(domainArray, x, 0, Math.min(domainArray.length, range.size() - 1));
return range.get(idx);
}
}
private interface ContinuousDoubleScale<T extends ContinuousDoubleScale<T>>
extends Continuous<T, Double>, DoubleUnaryOperator {
default T put(double x, double y) {
return put(x, Double.valueOf(y));
}
@Override
default double applyAsDouble(double operand) {
return apply(operand);
}
DoubleUnaryOperator invert();
}
private static class BaseContinuous<T extends BaseContinuous<T, V>, V> implements Continuous<T, V> {
static final DoubleUnaryOperator IDENTITY = x -> x;
private final Interpolator<V> valueInterpolator;
final DoubleUnaryOperator transform;
boolean clamp = false;
private V defaultValue;
final DoubleArrayList domain = new DoubleArrayList();
final List<V> range = new ArrayList<>();
private DoubleFunction<V> fn;
double minKey = Double.POSITIVE_INFINITY;
double maxKey = Double.NEGATIVE_INFINITY;
private BaseContinuous(Interpolator<V> valueInterpolator, DoubleUnaryOperator transform) {
this.valueInterpolator = valueInterpolator;
this.transform = transform;
}
@Override
public V apply(double operand) {
if (clamp) {
operand = Math.clamp(operand, minKey, maxKey);
}
if (Double.isNaN(operand)) {
return defaultValue;
}
if (fn == null) {
fn = rescale();
}
return fn.apply(transform.applyAsDouble(operand));
}
private DoubleFunction<V> rescale() {
if (domain.size() > 2) {
int j = Math.min(domain.size(), range.size()) - 1;
DoubleUnaryOperator[] d = new DoubleUnaryOperator[j];
DoubleFunction<V>[] r = new DoubleFunction[j];
int i = -1;
double[] domainItems = new double[domain.size()];
for (int k = 0; k < domainItems.length; k++) {
domainItems[k] = transform.applyAsDouble(domain.get(k));
}
List<V> rangeItems = domainItems[j] < domainItems[0] ? range.reversed() : range;
// Reverse descending domains.
if (domainItems[j] < domainItems[0]) {
ArrayUtils.reverse(domainItems);
}
while (++i < j) {
d[i] = normalize(domainItems[i], domainItems[i + 1]);
r[i] = valueInterpolator.apply(rangeItems.get(i), rangeItems.get(i + 1));
}
return x -> {
int ii = bisect(domainItems, x, 1, j) - 1;
return r[ii].apply(d[ii].applyAsDouble(x));
};
} else {
double d0 = transform.applyAsDouble(domain.get(0)), d1 = transform.applyAsDouble(domain.get(1));
V r0 = range.get(0), r1 = range.get(1);
boolean reverse = d1 < d0;
final double dlo = reverse ? d1 : d0;
final double dhi = reverse ? d0 : d1;
final V rlo = reverse ? r1 : r0;
final V rhi = reverse ? r0 : r1;
DoubleFunction<V> interpolate = valueInterpolator.apply(rlo, rhi);
return x -> {
double value = (x - dlo) / (dhi - dlo);
return interpolate.apply(value);
};
}
}
private static DoubleUnaryOperator normalize(double a, double b) {
double delta = b - a;
return delta == 0 ? x -> 0.5 : Double.isNaN(delta) ? x -> Double.NaN : x -> (x - a) / delta;
}
@Override
public T put(double x, V y) {
fn = null;
minKey = Math.min(x, minKey);
maxKey = Math.max(x, maxKey);
domain.add(x);
range.add(y);
return self();
}
@Override
public T clamp(boolean clamp) {
this.clamp = clamp;
return self();
}
@Override
public T defaultValue(V defaultValue) {
this.defaultValue = defaultValue;
return self();
}
}
private static int bisect(double[] a, double x, int lo, int hi) {
if (lo < hi) {
do {
int mid = (lo + hi) >>> 1;
if (a[mid] <= x)
lo = mid + 1;
else
hi = mid;
} while (lo < hi);
}
return lo;
}
public static class DoubleContinuous extends BaseContinuous<DoubleContinuous, Double>
implements ContinuousDoubleScale<DoubleContinuous> {
private final DoubleUnaryOperator reverseTransform;
private DoubleContinuous(Interpolator<Double> valueInterpolator, DoubleUnaryOperator transform,
DoubleUnaryOperator reverseTransform) {
super(valueInterpolator, transform);
this.reverseTransform = reverseTransform;
}
@Override
public DoubleUnaryOperator invert() {
var result = linear();
int j = Math.min(domain.size(), range.size());
for (int i = 0; i < j; i++) {
result.put(range.get(i).doubleValue(),
transform.applyAsDouble(domain.get(i)));
}
DoubleUnaryOperator retVal =
reverseTransform == IDENTITY ? result : x -> reverseTransform.applyAsDouble(result.apply(x));
return clamp ? retVal.andThen(x -> Math.clamp(x, minKey, maxKey)) : retVal;
}
}
public static class OtherContinuous<V> extends BaseContinuous<OtherContinuous<V>, V> {
private OtherContinuous(Interpolator<V> valueInterpolator, DoubleUnaryOperator transform) {
super(valueInterpolator, transform);
}
}
public static DoubleContinuous linear() {
return new DoubleContinuous(INTERPOLATE_NUMERIC, BaseContinuous.IDENTITY, BaseContinuous.IDENTITY);
}
public static DoubleContinuous linearDouble(Interpolator<Double> interp) {
return new DoubleContinuous(interp, BaseContinuous.IDENTITY, BaseContinuous.IDENTITY);
}
public static <V> OtherContinuous<V> linear(Interpolator<V> valueInterpolator) {
return new OtherContinuous<>(valueInterpolator, BaseContinuous.IDENTITY);
}
public static DoubleContinuous power(double exponent) {
double inverse = 1d / exponent;
return new DoubleContinuous(INTERPOLATE_NUMERIC,
x -> Math.pow(x, exponent),
y -> Math.pow(y, inverse)
);
}
public static <V> OtherContinuous<V> power(double exponent, Interpolator<V> valueInterpolator) {
return new OtherContinuous<>(valueInterpolator, x -> Math.pow(x, exponent));
}
public static DoubleContinuous sqrt() {
return power(0.5);
}
public static <V> OtherContinuous<V> sqrt(Interpolator<V> valueInterpolator) {
return power(0.5, valueInterpolator);
}
private static DoubleUnaryOperator logBase(double base) {
double logBase = Math.log(base);
return x -> Math.log(x) / logBase;
}
private static DoubleUnaryOperator expFn(double base) {
return base == Math.E ? Math::exp :
x -> Math.pow(base, x);
}
private static DoubleUnaryOperator logFn(double base) {
return base == 10d ? Math::log10 :
base == Math.E ? Math::log :
logBase(base);
}
public static DoubleContinuous log(double base) {
return new DoubleContinuous(INTERPOLATE_NUMERIC, logFn(base), expFn(base));
}
public static <V> OtherContinuous<V> log(double base, Interpolator<V> valueInterpolator) {
return new OtherContinuous<>(valueInterpolator, logFn(base));
}
public static DoubleContinuous exponential(double base) {
return new DoubleContinuous(INTERPOLATE_NUMERIC, expFn(base), logFn(base));
}
public static <V> OtherContinuous<V> exponential(double base, Interpolator<V> valueInterpolator) {
return new OtherContinuous<>(valueInterpolator, expFn(base));
}
public static OtherContinuous<Double> bezier(double x1, double y1, double x2, double y2) {
var bezier = new UnitBezier(x1, y1, x2, y2);
return new OtherContinuous<>((a, b) -> {
double diff = b - a;
return t -> bezier.solve(t) * diff + a;
}, BaseContinuous.IDENTITY);
}
public static <V> ThresholdScale<V> threshold(V minValue) {
return new ThresholdScale<>(minValue);
}
}

Wyświetl plik

@ -0,0 +1,94 @@
package com.onthegomap.planetiler.util;
public class UnitBezier {
private final double ax;
private final double bx;
private final double cx;
private final double ay;
private final double by;
private final double cy;
public UnitBezier(double p1x, double p1y, double p2x, double p2y) {
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
cx = 3.0 * p1x;
bx = 3.0 * (p2x - p1x) - cx;
ax = 1.0 - cx - bx;
cy = 3.0 * p1y;
by = 3.0 * (p2y - p1y) - cy;
ay = 1.0 - cy - by;
}
double sampleCurveX(double t) {
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax * t + bx) * t + cx) * t;
}
double sampleCurveY(double t) {
return ((ay * t + by) * t + cy) * t;
}
double sampleCurveDerivativeX(double t) {
return (3.0 * ax * t + 2.0 * bx) * t + cx;
}
// Given an x value, find a parametric value it came from.
double solveCurveX(double x, double epsilon) {
double t0;
double t1;
double t2;
double x2;
double d2;
int i;
if (x < 0)
return 0;
if (x > 1)
return 1;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++) {
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < epsilon)
return t2;
d2 = sampleCurveDerivativeX(t2);
if (Math.abs(d2) < 1e-6)
break;
t2 = t2 - x2 / d2;
}
// Fall back to the bisection method for reliability.
t0 = 0.0;
t1 = 1.0;
t2 = x;
if (t2 < t0)
return t0;
if (t2 > t1)
return t1;
while (t0 < t1) {
x2 = sampleCurveX(t2);
if (Math.abs(x2 - x) < epsilon)
return t2;
if (x > x2)
t0 = t2;
else
t1 = t2;
t2 = (t1 - t0) * .5 + t0;
}
// Failure.
return t2;
}
double solve(double x, double epsilon) {
return sampleCurveY(solveCurveX(x, epsilon));
}
double solve(double x) {
return solve(x, 1e-6);
}
}

Wyświetl plik

@ -0,0 +1,236 @@
package com.onthegomap.planetiler.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class InterpolatorTest {
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testLinear(boolean clamp) {
var interpolator = Scales.linear()
.put(0, 10)
.put(1, 20)
.clamp(clamp);
assertTransform(interpolator, 0, 10);
assertTransform(interpolator, 1, 20);
assertTransform(interpolator, 0.5, 15);
// clamp
assertClose(clamp ? 20 : 30, interpolator.applyAsDouble(2));
assertClose(clamp ? 1 : 2, interpolator.invert().applyAsDouble(30));
assertClose(clamp ? 10 : 0, interpolator.applyAsDouble(-1));
assertClose(clamp ? 0 : -1, interpolator.invert().applyAsDouble(0));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testLog(boolean clamp) {
var interpolator = Scales.log(10)
.put(10, 1d)
.put(1_000, 2d)
.clamp(clamp);
assertTransform(interpolator, 10, 1);
assertTransform(interpolator, 100, 1.5);
assertTransform(interpolator, 1_000, 2);
// clamp
assertClose(clamp ? 1 : 0.5, interpolator.applyAsDouble(1));
assertClose(clamp ? 10 : 1, interpolator.invert().applyAsDouble(0.5));
assertClose(clamp ? 2 : 2.5, interpolator.applyAsDouble(10_000));
assertClose(clamp ? 1_000 : 10_000, interpolator.invert().applyAsDouble(2.5));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testLog2(boolean clamp) {
var interpolator = Scales.log(2)
.put(2, 1d)
.put(8, 2d)
.clamp(clamp);
assertTransform(interpolator, 2, 1);
assertTransform(interpolator, 4, 1.5);
assertTransform(interpolator, 8, 2);
// clamp
assertClose(clamp ? 1 : 0.5, interpolator.applyAsDouble(1));
assertClose(clamp ? 2 : 1, interpolator.invert().applyAsDouble(0.5));
assertClose(clamp ? 2 : 2.5, interpolator.applyAsDouble(16));
assertClose(clamp ? 8 : 16, interpolator.invert().applyAsDouble(2.5));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testPower(boolean clamp) {
var interpolator = Scales.power(2)
.put(Math.sqrt(2), 1d)
.put(Math.sqrt(4), 2d)
.clamp(clamp);
assertTransform(interpolator, Math.sqrt(2), 1);
assertTransform(interpolator, Math.sqrt(3), 1.5);
assertTransform(interpolator, Math.sqrt(4), 2);
// clamp
assertClose(clamp ? 1 : 0.5, interpolator.applyAsDouble(1));
assertClose(clamp ? Math.sqrt(2) : 1, interpolator.invert().applyAsDouble(0.5));
assertClose(clamp ? 2 : 2.5, interpolator.applyAsDouble(Math.sqrt(5)));
assertClose(clamp ? Math.sqrt(4) : Math.sqrt(5), interpolator.invert().applyAsDouble(2.5));
}
@Test
void testNegativePower() {
var interpolator = Scales.power(-1)
.put(1, 0d)
.put(2, 1d);
assertTransform(interpolator, 1, 0);
assertTransform(interpolator, 1.5, 2d / 3);
assertTransform(interpolator, 2, 1);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testSqrt(boolean clamp) {
var interpolator = Scales.sqrt()
.put(4, 1d)
.put(16, 2d)
.clamp(clamp);
assertTransform(interpolator, 4, 1);
assertTransform(interpolator, 9, 1.5);
assertTransform(interpolator, 16, 2);
// clamp
assertClose(clamp ? 1 : 0.5, interpolator.applyAsDouble(1));
assertClose(clamp ? 4 : 1, interpolator.invert().applyAsDouble(0.5));
assertClose(clamp ? 2 : 2.5, interpolator.applyAsDouble(25));
assertClose(clamp ? 16 : 25, interpolator.invert().applyAsDouble(2.5));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testMultipartDescending(boolean clamp) {
var interpolator = Scales.linear()
.put(4, 1d)
.put(2, 2d)
.put(1, 4d)
.clamp(clamp);
assertTransform(interpolator, 4, 1);
assertTransform(interpolator, 3, 1.5);
assertTransform(interpolator, 2, 2);
assertTransform(interpolator, 1.5, 3);
assertTransform(interpolator, 1, 4);
// clamp
assertClose(clamp ? 1 : 0.5, interpolator.applyAsDouble(5));
assertClose(clamp ? 4 : 5, interpolator.invert().applyAsDouble(0.5));
assertClose(clamp ? 4 : 6, interpolator.applyAsDouble(0));
assertClose(clamp ? 1 : 0, interpolator.invert().applyAsDouble(6));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testMultipartAscending(boolean clamp) {
var interpolator = Scales.linear()
.put(1, 4d)
.put(2, 2d)
.put(4, 1d)
.clamp(clamp);
assertTransform(interpolator, 1, 4);
assertTransform(interpolator, 1.5, 3);
assertTransform(interpolator, 2, 2);
assertTransform(interpolator, 3, 1.5);
assertTransform(interpolator, 4, 1);
// clamp
assertClose(clamp ? 1 : 0.5, interpolator.invert().applyAsDouble(5));
assertClose(clamp ? 4 : 5, interpolator.applyAsDouble(0.5));
assertClose(clamp ? 4 : 6, interpolator.invert().applyAsDouble(0));
assertClose(clamp ? 1 : 0, interpolator.applyAsDouble(6));
}
@Test
void testUnknown() {
var interpolator = Scales.linear()
.put(0, 10d)
.put(1, 20d)
.defaultValue(100d);
assertEquals(100, interpolator.applyAsDouble(Double.NaN));
}
@Test
void testThreshold() {
var scale = Scales.threshold("a")
.putAbove(1.5, "b")
.putAbove(3, "c");
assertEquals("a", scale.apply(1.4));
assertEquals("b", scale.apply(1.5));
assertEquals("b", scale.apply(2.99));
assertEquals("c", scale.apply(3));
assertEquals("c", scale.apply(3.1));
testInvert(scale, "a", Double.NEGATIVE_INFINITY, 1.5);
testInvert(scale, "b", 1.5, 3);
testInvert(scale, "c", 3, Double.POSITIVE_INFINITY);
testInvert(scale, "d", Double.NaN, Double.NaN);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testQuantize(boolean list) {
var scale = list ?
Scales.quantize(0, 3, List.of("a", "b", "c")) :
Scales.quantize(0, 3, "a", "b", "c");
assertEquals("a", scale.apply(0));
assertEquals("a", scale.apply(0.99));
assertEquals("b", scale.apply(1));
assertEquals("b", scale.apply(1.01));
assertEquals("b", scale.apply(1.99));
assertEquals("c", scale.apply(2));
assertEquals("c", scale.apply(2.01));
assertEquals("c", scale.apply(99));
}
@Test
void testExponential() {
var scale = Scales.exponential(2)
.put(1, 2)
.put(3, 6)
.clamp(true);
assertClose(2, scale.apply(0));
assertClose(2, scale.apply(1));
assertClose(3.3333333333, scale.apply(2));
assertClose(6, scale.apply(3));
assertClose(6, scale.apply(5));
}
@Test
void testBezier() {
var scale = Scales.bezier(0.42, 0, 0.58, 1)
.put(0, 0d)
.put(100, 100d)
.clamp(true);
assertEquals(0, scale.apply(0), 1e-4);
assertEquals(1.97224, scale.apply(10), 1e-4);
assertEquals(8.16597, scale.apply(20), 1e-4);
assertEquals(18.7395, scale.apply(30), 1e-4);
assertEquals(33.1883, scale.apply(40), 1e-4);
assertEquals(50, scale.apply(50), 1e-4);
assertEquals(66.8116, scale.apply(60), 1e-4);
assertEquals(81.2604, scale.apply(70), 1e-4);
assertEquals(91.834, scale.apply(80), 1e-4);
assertEquals(98.0277, scale.apply(90), 1e-4);
assertEquals(100, scale.apply(100), 1e-4);
}
private static <V> void testInvert(Scales.ThresholdScale<V> scale, V val, double min, double max) {
assertEquals(min, scale.invertMin(val));
assertEquals(max, scale.invertMax(val));
assertEquals(max, scale.invertMax(val));
}
private static void assertClose(double expected, double actual) {
assertEquals(expected, actual, 1e-10);
}
private static void assertTransform(Scales.DoubleContinuous interp, double x, double y) {
assertClose(y, interp.applyAsDouble(x));
assertClose(x, interp.invert().applyAsDouble(y));
}
}