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