major optimization updates
[debian/openrocket] / src / net / sf / openrocket / simulation / SimulationOptions.java
1 package net.sf.openrocket.simulation;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Random;
6
7 import javax.swing.event.ChangeEvent;
8 import javax.swing.event.ChangeListener;
9
10 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
11 import net.sf.openrocket.masscalc.BasicMassCalculator;
12 import net.sf.openrocket.models.atmosphere.AtmosphericModel;
13 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
14 import net.sf.openrocket.models.gravity.BasicGravityModel;
15 import net.sf.openrocket.models.wind.PinkNoiseWindModel;
16 import net.sf.openrocket.rocketcomponent.Rocket;
17 import net.sf.openrocket.util.BugException;
18 import net.sf.openrocket.util.ChangeSource;
19 import net.sf.openrocket.util.MathUtil;
20
21 /**
22  * A class holding simulation options in basic parameter form and which functions
23  * as a ChangeSource.  A SimulationConditions instance is generated from this class
24  * using {@link #toSimulationConditions()}.
25  * 
26  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
27  */
28 public class SimulationOptions implements ChangeSource, Cloneable {
29         
30         public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
31         
32         /**
33          * The ISA standard atmosphere.
34          */
35         private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
36         
37
38         private final Rocket rocket;
39         private String motorID = null;
40         
41
42         /*
43          * NOTE:  When adding/modifying parameters, they must also be added to the
44          * equals and copyFrom methods!!
45          */
46
47         // TODO: HIGH: Fetch default values from Prefs!
48         
49         private double launchRodLength = 1;
50         
51         /** Launch rod angle > 0, radians from vertical */
52         private double launchRodAngle = 0;
53         
54         /** Launch rod direction, 0 = upwind, PI = downwind. */
55         private double launchRodDirection = 0;
56         
57
58         private double windAverage = 2.0;
59         private double windTurbulence = 0.1;
60         
61         private double launchAltitude = 0;
62         private double launchLatitude = 45;
63         
64         private boolean useISA = true;
65         private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
66         private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
67         
68
69         private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
70         private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
71         
72         private int randomSeed = new Random().nextInt();
73         
74         private boolean calculateExtras = true;
75         
76
77         private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
78         
79         
80
81         public SimulationOptions(Rocket rocket) {
82                 this.rocket = rocket;
83         }
84         
85         
86
87         public Rocket getRocket() {
88                 return rocket;
89         }
90         
91         
92         public String getMotorConfigurationID() {
93                 return motorID;
94         }
95         
96         /**
97          * Set the motor configuration ID.  This must be a valid motor configuration ID of
98          * the rocket, otherwise the configuration is set to <code>null</code>.
99          * 
100          * @param id    the configuration to set.
101          */
102         public void setMotorConfigurationID(String id) {
103                 if (id != null)
104                         id = id.intern();
105                 if (!rocket.isMotorConfigurationID(id))
106                         id = null;
107                 if (id == motorID)
108                         return;
109                 motorID = id;
110                 fireChangeEvent();
111         }
112         
113         
114         public double getLaunchRodLength() {
115                 return launchRodLength;
116         }
117         
118         public void setLaunchRodLength(double launchRodLength) {
119                 if (MathUtil.equals(this.launchRodLength, launchRodLength))
120                         return;
121                 this.launchRodLength = launchRodLength;
122                 fireChangeEvent();
123         }
124         
125         
126         public double getLaunchRodAngle() {
127                 return launchRodAngle;
128         }
129         
130         public void setLaunchRodAngle(double launchRodAngle) {
131                 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
132                 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
133                         return;
134                 this.launchRodAngle = launchRodAngle;
135                 fireChangeEvent();
136         }
137         
138         
139         public double getLaunchRodDirection() {
140                 return launchRodDirection;
141         }
142         
143         public void setLaunchRodDirection(double launchRodDirection) {
144                 launchRodDirection = MathUtil.reduce180(launchRodDirection);
145                 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
146                         return;
147                 this.launchRodDirection = launchRodDirection;
148                 fireChangeEvent();
149         }
150         
151         
152
153         public double getWindSpeedAverage() {
154                 return windAverage;
155         }
156         
157         public void setWindSpeedAverage(double windAverage) {
158                 if (MathUtil.equals(this.windAverage, windAverage))
159                         return;
160                 this.windAverage = MathUtil.max(windAverage, 0);
161                 fireChangeEvent();
162         }
163         
164         
165         public double getWindSpeedDeviation() {
166                 return windAverage * windTurbulence;
167         }
168         
169         public void setWindSpeedDeviation(double windDeviation) {
170                 if (windAverage < 0.1) {
171                         windAverage = 0.1;
172                 }
173                 setWindTurbulenceIntensity(windDeviation / windAverage);
174         }
175         
176         
177         /**
178          * Return the wind turbulence intensity (standard deviation / average).
179          * 
180          * @return  the turbulence intensity
181          */
182         public double getWindTurbulenceIntensity() {
183                 return windTurbulence;
184         }
185         
186         /**
187          * Set the wind standard deviation to match the given turbulence intensity.
188          * 
189          * @param intensity   the turbulence intensity
190          */
191         public void setWindTurbulenceIntensity(double intensity) {
192                 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
193                 this.windTurbulence = intensity;
194                 fireChangeEvent();
195         }
196         
197         
198
199
200
201         public double getLaunchAltitude() {
202                 return launchAltitude;
203         }
204         
205         public void setLaunchAltitude(double altitude) {
206                 if (MathUtil.equals(this.launchAltitude, altitude))
207                         return;
208                 this.launchAltitude = altitude;
209                 fireChangeEvent();
210         }
211         
212         
213         public double getLaunchLatitude() {
214                 return launchLatitude;
215         }
216         
217         public void setLaunchLatitude(double launchLatitude) {
218                 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
219                 if (MathUtil.equals(this.launchLatitude, launchLatitude))
220                         return;
221                 this.launchLatitude = launchLatitude;
222                 fireChangeEvent();
223         }
224         
225         
226
227
228
229         public boolean isISAAtmosphere() {
230                 return useISA;
231         }
232         
233         public void setISAAtmosphere(boolean isa) {
234                 if (isa == useISA)
235                         return;
236                 useISA = isa;
237                 fireChangeEvent();
238         }
239         
240         
241         public double getLaunchTemperature() {
242                 return launchTemperature;
243         }
244         
245         
246
247         public void setLaunchTemperature(double launchTemperature) {
248                 if (MathUtil.equals(this.launchTemperature, launchTemperature))
249                         return;
250                 this.launchTemperature = launchTemperature;
251                 fireChangeEvent();
252         }
253         
254         
255
256         public double getLaunchPressure() {
257                 return launchPressure;
258         }
259         
260         
261
262         public void setLaunchPressure(double launchPressure) {
263                 if (MathUtil.equals(this.launchPressure, launchPressure))
264                         return;
265                 this.launchPressure = launchPressure;
266                 fireChangeEvent();
267         }
268         
269         
270         /**
271          * Returns an atmospheric model corresponding to the launch conditions.  The
272          * atmospheric models may be shared between different calls.
273          * 
274          * @return      an AtmosphericModel object.
275          */
276         private AtmosphericModel getAtmosphericModel() {
277                 if (useISA) {
278                         return ISA_ATMOSPHERIC_MODEL;
279                 }
280                 return new ExtendedISAModel(launchAltitude, launchTemperature, launchPressure);
281         }
282         
283         
284         public double getTimeStep() {
285                 return timeStep;
286         }
287         
288         public void setTimeStep(double timeStep) {
289                 if (MathUtil.equals(this.timeStep, timeStep))
290                         return;
291                 this.timeStep = timeStep;
292                 fireChangeEvent();
293         }
294         
295         public double getMaximumStepAngle() {
296                 return maximumAngle;
297         }
298         
299         public void setMaximumStepAngle(double maximumAngle) {
300                 maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
301                 if (MathUtil.equals(this.maximumAngle, maximumAngle))
302                         return;
303                 this.maximumAngle = maximumAngle;
304                 fireChangeEvent();
305         }
306         
307         
308
309         public boolean getCalculateExtras() {
310                 return calculateExtras;
311         }
312         
313         
314
315         public void setCalculateExtras(boolean calculateExtras) {
316                 if (this.calculateExtras == calculateExtras)
317                         return;
318                 this.calculateExtras = calculateExtras;
319                 fireChangeEvent();
320         }
321         
322         
323
324         public int getRandomSeed() {
325                 return randomSeed;
326         }
327         
328         public void setRandomSeed(int randomSeed) {
329                 if (this.randomSeed == randomSeed) {
330                         return;
331                 }
332                 this.randomSeed = randomSeed;
333                 /*
334                  * This does not fire an event since we don't want to invalidate simulation results
335                  * due to changing the seed value.  This needs to be revisited if the user is ever
336                  * allowed to select the seed value.
337                  */
338                 //              fireChangeEvent();
339         }
340         
341         /**
342          * Randomize the random seed value.
343          */
344         public void randomizeSeed() {
345                 this.randomSeed = new Random().nextInt();
346                 //              fireChangeEvent();
347         }
348         
349         
350
351         @Override
352         public SimulationOptions clone() {
353                 try {
354                         SimulationOptions copy = (SimulationOptions) super.clone();
355                         copy.listeners = new ArrayList<ChangeListener>();
356                         return copy;
357                 } catch (CloneNotSupportedException e) {
358                         throw new BugException(e);
359                 }
360         }
361         
362         
363         public void copyFrom(SimulationOptions src) {
364                 
365                 if (this.rocket == src.rocket) {
366                         
367                         this.motorID = src.motorID;
368                         
369                 } else {
370                         
371                         if (src.rocket.hasMotors(src.motorID)) {
372                                 // Try to find a matching motor ID
373                                 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
374                                 String matchID = null;
375                                 
376                                 for (String id : this.rocket.getMotorConfigurationIDs()) {
377                                         if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
378                                                 matchID = id;
379                                                 break;
380                                         }
381                                 }
382                                 
383                                 this.motorID = matchID;
384                         } else {
385                                 this.motorID = null;
386                         }
387                 }
388                 
389                 this.launchAltitude = src.launchAltitude;
390                 this.launchLatitude = src.launchLatitude;
391                 this.launchPressure = src.launchPressure;
392                 this.launchRodAngle = src.launchRodAngle;
393                 this.launchRodDirection = src.launchRodDirection;
394                 this.launchRodLength = src.launchRodLength;
395                 this.launchTemperature = src.launchTemperature;
396                 this.maximumAngle = src.maximumAngle;
397                 this.timeStep = src.timeStep;
398                 this.windAverage = src.windAverage;
399                 this.windTurbulence = src.windTurbulence;
400                 this.calculateExtras = src.calculateExtras;
401                 this.randomSeed = src.randomSeed;
402                 
403                 fireChangeEvent();
404         }
405         
406         
407
408         /**
409          * Compares whether the two simulation conditions are equal.  The two are considered
410          * equal if the rocket, motor id and all variables are equal.
411          */
412         @Override
413         public boolean equals(Object other) {
414                 if (!(other instanceof SimulationOptions))
415                         return false;
416                 SimulationOptions o = (SimulationOptions) other;
417                 return ((this.rocket == o.rocket) &&
418                                 this.motorID.equals(o.motorID) &&
419                                 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
420                                 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
421                                 MathUtil.equals(this.launchPressure, o.launchPressure) &&
422                                 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
423                                 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
424                                 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
425                                 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
426                                 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
427                                 MathUtil.equals(this.timeStep, o.timeStep) &&
428                                 MathUtil.equals(this.windAverage, o.windAverage) &&
429                                 MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
430                                 this.calculateExtras == o.calculateExtras && this.randomSeed == o.randomSeed);
431         }
432         
433         /**
434          * Hashcode method compatible with {@link #equals(Object)}.
435          */
436         @Override
437         public int hashCode() {
438                 if (motorID == null)
439                         return rocket.hashCode();
440                 return rocket.hashCode() + motorID.hashCode();
441         }
442         
443         @Override
444         public void addChangeListener(ChangeListener listener) {
445                 listeners.add(listener);
446         }
447         
448         @Override
449         public void removeChangeListener(ChangeListener listener) {
450                 listeners.remove(listener);
451         }
452         
453         private final ChangeEvent event = new ChangeEvent(this);
454         
455         private void fireChangeEvent() {
456                 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
457                 
458                 for (int i = array.length - 1; i >= 0; i--) {
459                         array[i].stateChanged(event);
460                 }
461         }
462         
463         
464         // TODO: HIGH: Clean up
465         public SimulationConditions toSimulationConditions() {
466                 SimulationConditions conditions = new SimulationConditions();
467                 
468                 conditions.setRocket((Rocket) getRocket().copy());
469                 conditions.setMotorConfigurationID(getMotorConfigurationID());
470                 conditions.setLaunchRodLength(getLaunchRodLength());
471                 conditions.setLaunchRodAngle(getLaunchRodAngle());
472                 conditions.setLaunchRodDirection(getLaunchRodDirection());
473                 conditions.setLaunchAltitude(getLaunchAltitude());
474                 conditions.setLaunchLatitude(getLaunchLatitude());
475                 conditions.setRandomSeed(randomSeed);
476                 
477                 PinkNoiseWindModel windModel = new PinkNoiseWindModel(randomSeed);
478                 windModel.setAverage(getWindSpeedAverage());
479                 windModel.setStandardDeviation(getWindSpeedDeviation());
480                 conditions.setWindModel(windModel);
481                 
482                 conditions.setAtmosphericModel(getAtmosphericModel());
483                 
484                 BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
485                 conditions.setGravityModel(gravityModel);
486                 
487                 conditions.setAerodynamicCalculator(new BarrowmanCalculator());
488                 conditions.setMassCalculator(new BasicMassCalculator());
489                 
490                 conditions.setTimeStep(getTimeStep());
491                 conditions.setMaximumAngleStep(getMaximumStepAngle());
492                 
493                 conditions.setCalculateExtras(getCalculateExtras());
494                 
495                 return conditions;
496         }
497         
498 }