View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math4.legacy.ode.events;
18  
19  import org.apache.commons.math4.legacy.analysis.solvers.BracketingNthOrderBrentSolver;
20  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
21  import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
22  import org.apache.commons.math4.legacy.exception.NoBracketingException;
23  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
24  import org.apache.commons.math4.legacy.ode.FirstOrderDifferentialEquations;
25  import org.apache.commons.math4.legacy.ode.FirstOrderIntegrator;
26  import org.apache.commons.math4.legacy.ode.nonstiff.DormandPrince853Integrator;
27  import org.apache.commons.rng.UniformRandomProvider;
28  import org.apache.commons.rng.simple.RandomSource;
29  import org.apache.commons.math4.core.jdkmath.JdkMath;
30  import org.junit.Assert;
31  import org.junit.Test;
32  
33  public class EventFilterTest {
34  
35      @Test
36      public void testHistoryIncreasingForward() {
37  
38          // start point: g > 0
39          testHistory(FilterType.TRIGGER_ONLY_INCREASING_EVENTS,
40                      0.5 * JdkMath.PI, 30.5 * JdkMath.PI, JdkMath.PI, -1);
41  
42          // start point: g = 0
43          testHistory(FilterType.TRIGGER_ONLY_INCREASING_EVENTS,
44                      0, 30.5 * JdkMath.PI, JdkMath.PI, -1);
45  
46          // start point: g < 0
47          testHistory(FilterType.TRIGGER_ONLY_INCREASING_EVENTS,
48                      1.5 * JdkMath.PI, 30.5 * JdkMath.PI, JdkMath.PI, +1);
49      }
50  
51      @Test
52      public void testHistoryIncreasingBackward() {
53  
54          // start point: g > 0
55          testHistory(FilterType.TRIGGER_ONLY_INCREASING_EVENTS,
56                      0.5 * JdkMath.PI, -30.5 * JdkMath.PI, JdkMath.PI, -1);
57  
58          // start point: g = 0
59          testHistory(FilterType.TRIGGER_ONLY_INCREASING_EVENTS,
60                      0, -30.5 * JdkMath.PI, JdkMath.PI, +1);
61  
62          // start point: g < 0
63          testHistory(FilterType.TRIGGER_ONLY_INCREASING_EVENTS,
64                      1.5 * JdkMath.PI, -30.5 * JdkMath.PI, JdkMath.PI, -1);
65      }
66  
67      @Test
68      public void testHistoryDecreasingForward() {
69  
70          // start point: g > 0
71          testHistory(FilterType.TRIGGER_ONLY_DECREASING_EVENTS,
72                      0.5 * JdkMath.PI, 30.5 * JdkMath.PI, 0, +1);
73  
74          // start point: g = 0
75          testHistory(FilterType.TRIGGER_ONLY_DECREASING_EVENTS,
76                      0, 30.5 * JdkMath.PI, 0, +1);
77  
78          // start point: g < 0
79          testHistory(FilterType.TRIGGER_ONLY_DECREASING_EVENTS,
80                      1.5 * JdkMath.PI, 30.5 * JdkMath.PI, 0, +1);
81      }
82  
83      @Test
84      public void testHistoryDecreasingBackward() {
85  
86          // start point: g > 0
87          testHistory(FilterType.TRIGGER_ONLY_DECREASING_EVENTS,
88                      0.5 * JdkMath.PI, -30.5 * JdkMath.PI, 0, -1);
89  
90          // start point: g = 0
91          testHistory(FilterType.TRIGGER_ONLY_DECREASING_EVENTS,
92                      0, -30.5 * JdkMath.PI, 0, -1);
93  
94          // start point: g < 0
95          testHistory(FilterType.TRIGGER_ONLY_DECREASING_EVENTS,
96                      1.5 * JdkMath.PI, -30.5 * JdkMath.PI, 0, +1);
97      }
98  
99      public void testHistory(FilterType type, double t0, double t1, double refSwitch, double signEven) {
100         Event onlyIncreasing = new Event(false, true);
101         EventFilter eventFilter =
102                 new EventFilter(onlyIncreasing, type);
103         eventFilter.init(t0, new double[] {1.0,  0.0}, t1);
104 
105         // first pass to set up switches history for a long period
106         double h = JdkMath.copySign(0.05, t1 - t0);
107         double n = (int) JdkMath.floor((t1 - t0) / h);
108         for (int i = 0; i < n; ++i) {
109             double t = t0 + i * h;
110             eventFilter.g(t, new double[] { JdkMath.sin(t), JdkMath.cos(t) });
111         }
112 
113         // verify old events are preserved, even if randomly accessed
114         UniformRandomProvider rng = RandomSource.TWO_CMRES.create(0xb0e7401265af8cd3L);
115         for (int i = 0; i < 5000; i++) {
116             double t = t0 + (t1 - t0) * rng.nextDouble();
117             double g = eventFilter.g(t, new double[] { JdkMath.sin(t), JdkMath.cos(t) });
118             int turn = (int) JdkMath.floor((t - refSwitch) / (2 * JdkMath.PI));
119             if ((turn & 1) == 0) {
120                 Assert.assertEquals( signEven * JdkMath.sin(t), g, 1.0e-10);
121             } else {
122                 Assert.assertEquals(-signEven * JdkMath.sin(t), g, 1.0e-10);
123             }
124         }
125     }
126 
127     @Test
128     public void testIncreasingOnly()
129         throws DimensionMismatchException, NumberIsTooSmallException,
130                MaxCountExceededException, NoBracketingException {
131         double e = 1e-15;
132         FirstOrderIntegrator integrator;
133         integrator = new DormandPrince853Integrator(1.0e-3, 100.0, 1e-7, 1e-7);
134         Event allEvents = new Event(true, true);
135         integrator.addEventHandler(allEvents, 0.1, e, 1000,
136                                    new BracketingNthOrderBrentSolver(1.0e-7, 5));
137         Event onlyIncreasing = new Event(false, true);
138         integrator.addEventHandler(new EventFilter(onlyIncreasing,
139                                                    FilterType.TRIGGER_ONLY_INCREASING_EVENTS),
140                                    0.1, e, 100,
141                                    new BracketingNthOrderBrentSolver(1.0e-7, 5));
142         double t0 = 0.5 * JdkMath.PI;
143         double tEnd = 5.5 * JdkMath.PI;
144         double[] y = { 0.0, 1.0 };
145         Assert.assertEquals(tEnd,
146                             integrator.integrate(new SineCosine(), t0, y, tEnd, y),
147                             1.0e-7);
148 
149         Assert.assertEquals(5, allEvents.getEventCount());
150         Assert.assertEquals(2, onlyIncreasing.getEventCount());
151     }
152 
153     @Test
154     public void testDecreasingOnly()
155         throws DimensionMismatchException, NumberIsTooSmallException,
156                MaxCountExceededException, NoBracketingException {
157         double e = 1e-15;
158         FirstOrderIntegrator integrator;
159         integrator = new DormandPrince853Integrator(1.0e-3, 100.0, 1e-7, 1e-7);
160         Event allEvents = new Event(true, true);
161         integrator.addEventHandler(allEvents, 0.1, e, 1000,
162                                    new BracketingNthOrderBrentSolver(1.0e-7, 5));
163         Event onlyDecreasing = new Event(true, false);
164         integrator.addEventHandler(new EventFilter(onlyDecreasing,
165                                                    FilterType.TRIGGER_ONLY_DECREASING_EVENTS),
166                                    0.1, e, 1000,
167                                    new BracketingNthOrderBrentSolver(1.0e-7, 5));
168         double t0 = 0.5 * JdkMath.PI;
169         double tEnd = 5.5 * JdkMath.PI;
170         double[] y = { 0.0, 1.0 };
171         Assert.assertEquals(tEnd,
172                             integrator.integrate(new SineCosine(), t0, y, tEnd, y),
173                             1.0e-7);
174 
175         Assert.assertEquals(5, allEvents.getEventCount());
176         Assert.assertEquals(3, onlyDecreasing.getEventCount());
177     }
178 
179     @Test
180     public void testTwoOppositeFilters()
181         throws DimensionMismatchException, NumberIsTooSmallException,
182                MaxCountExceededException, NoBracketingException {
183         double e = 1e-15;
184         FirstOrderIntegrator integrator;
185         integrator = new DormandPrince853Integrator(1.0e-3, 100.0, 1e-7, 1e-7);
186         Event allEvents = new Event(true, true);
187         integrator.addEventHandler(allEvents, 0.1, e, 1000,
188                                    new BracketingNthOrderBrentSolver(1.0e-7, 5));
189         Event onlyIncreasing = new Event(false, true);
190         integrator.addEventHandler(new EventFilter(onlyIncreasing,
191                                                    FilterType.TRIGGER_ONLY_INCREASING_EVENTS),
192                                    0.1, e, 1000,
193                                    new BracketingNthOrderBrentSolver(1.0e-7, 5));
194         Event onlyDecreasing = new Event(true, false);
195         integrator.addEventHandler(new EventFilter(onlyDecreasing,
196                                                    FilterType.TRIGGER_ONLY_DECREASING_EVENTS),
197                                    0.1, e, 1000,
198                                    new BracketingNthOrderBrentSolver(1.0e-7, 5));
199         double t0 = 0.5 * JdkMath.PI;
200         double tEnd = 5.5 * JdkMath.PI;
201         double[] y = { 0.0, 1.0 };
202         Assert.assertEquals(tEnd,
203                             integrator.integrate(new SineCosine(), t0, y, tEnd, y),
204                             1.0e-7);
205 
206         Assert.assertEquals(5, allEvents.getEventCount());
207         Assert.assertEquals(2, onlyIncreasing.getEventCount());
208         Assert.assertEquals(3, onlyDecreasing.getEventCount());
209     }
210 
211     private static final class SineCosine implements FirstOrderDifferentialEquations {
212         @Override
213         public int getDimension() {
214             return 2;
215         }
216 
217         @Override
218         public void computeDerivatives(double t, double[] y, double[] yDot) {
219             yDot[0] =  y[1];
220             yDot[1] = -y[0];
221         }
222     }
223 
224     /** State events for this unit test. */
225     protected static class Event implements EventHandler {
226 
227         private final boolean expectDecreasing;
228         private final boolean expectIncreasing;
229         private int eventCount;
230 
231         public Event(boolean expectDecreasing, boolean expectIncreasing) {
232             this.expectDecreasing = expectDecreasing;
233             this.expectIncreasing = expectIncreasing;
234         }
235 
236         public int getEventCount() {
237             return eventCount;
238         }
239 
240         @Override
241         public void init(double t0, double[] y0, double t) {
242             eventCount = 0;
243         }
244 
245         @Override
246         public double g(double t, double[] y) {
247             return y[0];
248         }
249 
250         @Override
251         public Action eventOccurred(double t, double[] y, boolean increasing) {
252             if (increasing) {
253                 Assert.assertTrue(expectIncreasing);
254             } else {
255                 Assert.assertTrue(expectDecreasing);
256             }
257             eventCount++;
258             return Action.RESET_STATE;
259         }
260 
261         @Override
262         public void resetState(double t, double[] y) {
263             // in fact, we don't really reset anything for this test
264         }
265     }
266 }