1 package org.djutils.draw.function;
2
3 import java.util.Iterator;
4 import java.util.Map;
5 import java.util.Map.Entry;
6 import java.util.NavigableMap;
7 import java.util.NoSuchElementException;
8 import java.util.TreeMap;
9 import java.util.function.Function;
10
11 import org.djutils.exceptions.Throw;
12 import org.djutils.math.functions.MathFunction.TupleSt;
13
14
15
16
17
18
19
20
21
22
23
24 public class ContinuousPiecewiseLinearFunction implements Iterable<TupleSt>, Function<Double, Double>
25 {
26
27
28 private final NavigableMap<Double, Double> data = new TreeMap<>();
29
30
31
32
33
34
35
36
37 public ContinuousPiecewiseLinearFunction(final double... data)
38 {
39 Throw.when(data.length < 2 || data.length % 2 > 0, IllegalArgumentException.class,
40 "Number of input values must be even and at least 2");
41 for (int i = 0; i < data.length; i = i + 2)
42 {
43 Throw.when(data[i] < 0.0 || data[i] > 1.0, IllegalArgumentException.class,
44 "Fractional length %f is outside of range [0 ... 1]", data[i]);
45 Throw.when(1 / data[0] < 0, IllegalArgumentException.class, "Fractional length data may not contain -0.0 fraction");
46 Throw.when(!Double.isFinite(data[i + 1]), IllegalArgumentException.class, "values must be finite (got %f)",
47 data[i + 1]);
48 Throw.when(this.data.get(data[i]) != null, IllegalArgumentException.class, "Duplicate fraction is not permitted");
49 this.data.put(data[i], data[i + 1]);
50 }
51 }
52
53
54
55
56
57
58
59 public ContinuousPiecewiseLinearFunction(final Map<Double, Double> data)
60 {
61 Throw.whenNull(data, "data");
62 Throw.when(data.isEmpty(), IllegalArgumentException.class, "Input data is empty");
63 for (Entry<Double, Double> entry : data.entrySet())
64 {
65 Double key = entry.getKey();
66 Throw.whenNull(key, "key in provided data may not be null");
67 Throw.when(key < 0.0 || entry.getKey() > 1.0, IllegalArgumentException.class,
68 "Fractional length %s is outside of range [0 ... 1].", entry.getKey());
69 Throw.when(1 / key < 0, IllegalArgumentException.class, "Fractional length data may not contain -0.0 fraction");
70 Double value = entry.getValue();
71 Throw.whenNull(value, "value in provided map may not be null");
72 Throw.when(!Double.isFinite(value), IllegalArgumentException.class, "values must be finite (got %f)", value);
73 this.data.put(key, value);
74 }
75 }
76
77
78
79
80
81
82
83
84 public double get(final double fractionalLength)
85 {
86 Double exact = this.data.get(fractionalLength);
87 if (exact != null)
88 {
89 return exact;
90 }
91 Entry<Double, Double> ceiling = this.data.ceilingEntry(fractionalLength);
92 if (ceiling == null)
93 {
94 return this.data.lastEntry().getValue();
95 }
96 Entry<Double, Double> floor = this.data.floorEntry(fractionalLength);
97 if (floor == null)
98 {
99 return this.data.firstEntry().getValue();
100 }
101 double w = (fractionalLength - floor.getKey()) / (ceiling.getKey() - floor.getKey());
102 return (1.0 - w) * floor.getValue() + w * ceiling.getValue();
103 }
104
105
106
107
108
109
110
111
112 @Override
113 public Double apply(final Double fractionalLength)
114 {
115 return get(fractionalLength);
116 }
117
118
119
120
121
122
123 public double getDerivative(final double fractionalLength)
124 {
125 Entry<Double, Double> ceiling, floor;
126 if (fractionalLength == 0.0)
127 {
128 ceiling = this.data.higherEntry(fractionalLength);
129 floor = this.data.floorEntry(fractionalLength);
130 }
131 else
132 {
133 ceiling = this.data.ceilingEntry(fractionalLength);
134 floor = this.data.lowerEntry(fractionalLength);
135 }
136 if (ceiling == null || floor == null)
137 {
138 return 0.0;
139 }
140 return (ceiling.getValue() - floor.getValue()) / (ceiling.getKey() - floor.getKey());
141 }
142
143
144
145
146
147 public int size()
148 {
149 return this.data.size();
150 }
151
152
153
154
155
156
157
158
159 public static ContinuousPiecewiseLinearFunction of(final double... data)
160 {
161 return new ContinuousPiecewiseLinearFunction(data);
162 }
163
164 @Override
165 public Iterator<TupleSt> iterator()
166 {
167 return new Iterator<TupleSt>()
168 {
169 private Entry<Double, Double> nextEntry = ContinuousPiecewiseLinearFunction.this.data.firstEntry();
170
171 @Override
172 public boolean hasNext()
173 {
174 return this.nextEntry != null;
175 }
176
177 @Override
178 public TupleSt next()
179 {
180 Throw.when(null == this.nextEntry, NoSuchElementException.class, "Iterator is exhausted");
181 TupleSt result = new TupleSt(this.nextEntry.getKey(), this.nextEntry.getValue());
182 this.nextEntry = ContinuousPiecewiseLinearFunction.this.data.higherEntry(result.s());
183 return result;
184 }
185 };
186 }
187
188 }