e1ba1d77ca6c7b6f02d8aa4e5870e8a39b6fd107
[debian/openrocket] / core / src / net / sf / openrocket / motor / ThrustCurveMotor.java
1 package net.sf.openrocket.motor;
2
3 import java.text.Collator;
4 import java.util.Arrays;
5 import java.util.Locale;
6
7 import net.sf.openrocket.logging.LogHelper;
8 import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
9 import net.sf.openrocket.startup.Application;
10 import net.sf.openrocket.util.BugException;
11 import net.sf.openrocket.util.Coordinate;
12 import net.sf.openrocket.util.Inertia;
13 import net.sf.openrocket.util.MathUtil;
14
15
16 public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
17         private static final LogHelper log = Application.getLogger();
18         
19         public static final double MAX_THRUST = 10e6;
20         
21         //  Comparators:
22         private static final Collator COLLATOR = Collator.getInstance(Locale.US);
23         static {
24                 COLLATOR.setStrength(Collator.PRIMARY);
25         }
26         private static final DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
27         
28         private final String digest;
29
30         private final Manufacturer manufacturer;
31         private final String designation;
32         private final String description;
33         private final Motor.Type type;
34         private final double[] delays;
35         private final double diameter;
36         private final double length;
37         private final double[] time;
38         private final double[] thrust;
39         private final Coordinate[] cg;
40         
41         private double maxThrust;
42         private double burnTime;
43         private double averageThrust;
44         private double totalImpulse;
45         
46         
47         /**
48          * Sole constructor.  Sets all the properties of the motor.
49          * 
50          * @param manufacturer  the manufacturer of the motor.
51          * @param designation   the designation of the motor.
52          * @param description   extra description of the motor.
53          * @param type                  the motor type
54          * @param delays                the delays defined for this thrust curve
55          * @param diameter      diameter of the motor.
56          * @param length        length of the motor.
57          * @param time          the time points for the thrust curve.
58          * @param thrust        thrust at the time points.
59          * @param cg            cg at the time points.
60          */
61         public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description,
62                         Motor.Type type, double[] delays, double diameter, double length,
63                         double[] time, double[] thrust, Coordinate[] cg, String digest) {
64                 this.digest = digest;
65                 // Check argument validity
66                 if ((time.length != thrust.length) || (time.length != cg.length)) {
67                         throw new IllegalArgumentException("Array lengths do not match, " +
68                                         "time:" + time.length + " thrust:" + thrust.length +
69                                         " cg:" + cg.length);
70                 }
71                 if (time.length < 2) {
72                         throw new IllegalArgumentException("Too short thrust-curve, length=" +
73                                         time.length);
74                 }
75                 for (int i = 0; i < time.length - 1; i++) {
76                         if (time[i + 1] < time[i]) {
77                                 throw new IllegalArgumentException("Time goes backwards, " +
78                                                 "time[" + i + "]=" + time[i] + " " +
79                                                 "time[" + (i + 1) + "]=" + time[i + 1]);
80                         }
81                 }
82                 if (!MathUtil.equals(time[0], 0)) {
83                         throw new IllegalArgumentException("Curve starts at time " + time[0]);
84                 }
85                 if (!MathUtil.equals(thrust[0], 0)) {
86                         throw new IllegalArgumentException("Curve starts at thrust " + thrust[0]);
87                 }
88                 if (!MathUtil.equals(thrust[thrust.length - 1], 0)) {
89                         throw new IllegalArgumentException("Curve ends at thrust " +
90                                         thrust[thrust.length - 1]);
91                 }
92                 for (double t : thrust) {
93                         if (t < 0) {
94                                 throw new IllegalArgumentException("Negative thrust.");
95                         }
96                         if (t > MAX_THRUST || Double.isNaN(t)) {
97                                 throw new IllegalArgumentException("Invalid thrust " + t);
98                         }
99                 }
100                 for (Coordinate c : cg) {
101                         if (c.isNaN()) {
102                                 throw new IllegalArgumentException("Invalid CG " + c);
103                         }
104                         if (c.x < 0 || c.x > length) {
105                                 throw new IllegalArgumentException("Invalid CG position " + c.x);
106                         }
107                         if (c.weight < 0) {
108                                 throw new IllegalArgumentException("Negative mass " + c.weight);
109                         }
110                 }
111                 
112                 if (type != Motor.Type.SINGLE && type != Motor.Type.RELOAD &&
113                                 type != Motor.Type.HYBRID && type != Motor.Type.UNKNOWN) {
114                         throw new IllegalArgumentException("Illegal motor type=" + type);
115                 }
116                 
117
118                 this.manufacturer = manufacturer;
119                 this.designation = designation;
120                 this.description = description;
121                 this.type = type;
122                 this.delays = delays.clone();
123                 this.diameter = diameter;
124                 this.length = length;
125                 this.time = time.clone();
126                 this.thrust = thrust.clone();
127                 this.cg = cg.clone();
128                 
129                 computeStatistics();
130         }
131         
132         
133
134         /**
135          * Get the manufacturer of this motor.
136          * 
137          * @return the manufacturer
138          */
139         public Manufacturer getManufacturer() {
140                 return manufacturer;
141         }
142         
143         
144         /**
145          * Return the array of time points for this thrust curve.
146          * @return      an array of time points where the thrust is sampled
147          */
148         public double[] getTimePoints() {
149                 return time.clone();
150         }
151         
152         /**
153          * Returns the array of thrust points for this thrust curve.
154          * @return      an array of thrust samples
155          */
156         public double[] getThrustPoints() {
157                 return thrust.clone();
158         }
159         
160         /**
161          * Returns the array of CG points for this thrust curve.
162          * @return      an array of CG samples
163          */
164         public Coordinate[] getCGPoints() {
165                 return cg.clone();
166         }
167         
168         /**
169          * Return a list of standard delays defined for this motor.
170          * @return      a list of standard delays
171          */
172         public double[] getStandardDelays() {
173                 return delays.clone();
174         }
175         
176         
177         /**
178          * {@inheritDoc}
179          * <p>
180          * NOTE: In most cases you want to examine the motor type of the ThrustCurveMotorSet,
181          * not the ThrustCurveMotor itself.
182          */
183         @Override
184         public Type getMotorType() {
185                 return type;
186         }
187         
188         
189         @Override
190         public String getDesignation() {
191                 return designation;
192         }
193         
194         @Override
195         public String getDesignation(double delay) {
196                 return designation + "-" + getDelayString(delay);
197         }
198         
199         
200         @Override
201         public String getDescription() {
202                 return description;
203         }
204         
205         @Override
206         public double getDiameter() {
207                 return diameter;
208         }
209         
210         @Override
211         public double getLength() {
212                 return length;
213         }
214         
215         
216         @Override
217         public MotorInstance getInstance() {
218                 return new ThrustCurveMotorInstance();
219         }
220         
221         
222         @Override
223         public Coordinate getLaunchCG() {
224                 return cg[0];
225         }
226         
227         @Override
228         public Coordinate getEmptyCG() {
229                 return cg[cg.length - 1];
230         }
231         
232         
233
234
235         @Override
236         public double getBurnTimeEstimate() {
237                 return burnTime;
238         }
239         
240         @Override
241         public double getAverageThrustEstimate() {
242                 return averageThrust;
243         }
244         
245         @Override
246         public double getMaxThrustEstimate() {
247                 return maxThrust;
248         }
249         
250         @Override
251         public double getTotalImpulseEstimate() {
252                 return totalImpulse;
253         }
254         
255         @Override
256         public String getDigest() {
257                 return digest;
258         }
259         
260
261         /**
262          * Compute the general statistics of this motor.
263          */
264         private void computeStatistics() {
265                 
266                 // Maximum thrust
267                 maxThrust = 0;
268                 for (double t : thrust) {
269                         if (t > maxThrust)
270                                 maxThrust = t;
271                 }
272                 
273
274                 // Burn start time
275                 double thrustLimit = maxThrust * MARGINAL_THRUST;
276                 double burnStart, burnEnd;
277                 
278                 int pos;
279                 for (pos = 1; pos < thrust.length; pos++) {
280                         if (thrust[pos] >= thrustLimit)
281                                 break;
282                 }
283                 if (pos >= thrust.length) {
284                         throw new BugException("Could not compute burn start time, maxThrust=" + maxThrust +
285                                         " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
286                 }
287                 if (MathUtil.equals(thrust[pos - 1], thrust[pos])) {
288                         // For safety
289                         burnStart = (time[pos - 1] + time[pos]) / 2;
290                 } else {
291                         burnStart = MathUtil.map(thrustLimit, thrust[pos - 1], thrust[pos], time[pos - 1], time[pos]);
292                 }
293                 
294
295                 // Burn end time
296                 for (pos = thrust.length - 2; pos >= 0; pos--) {
297                         if (thrust[pos] >= thrustLimit)
298                                 break;
299                 }
300                 if (pos < 0) {
301                         throw new BugException("Could not compute burn end time, maxThrust=" + maxThrust +
302                                         " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
303                 }
304                 if (MathUtil.equals(thrust[pos], thrust[pos + 1])) {
305                         // For safety
306                         burnEnd = (time[pos] + time[pos + 1]) / 2;
307                 } else {
308                         burnEnd = MathUtil.map(thrustLimit, thrust[pos], thrust[pos + 1],
309                                         time[pos], time[pos + 1]);
310                 }
311                 
312
313                 // Burn time
314                 burnTime = Math.max(burnEnd - burnStart, 0);
315                 
316
317                 // Total impulse and average thrust
318                 totalImpulse = 0;
319                 averageThrust = 0;
320                 
321                 for (pos = 0; pos < time.length - 1; pos++) {
322                         double t0 = time[pos];
323                         double t1 = time[pos + 1];
324                         double f0 = thrust[pos];
325                         double f1 = thrust[pos + 1];
326                         
327                         totalImpulse += (t1 - t0) * (f0 + f1) / 2;
328                         
329                         if (t0 < burnStart && t1 > burnStart) {
330                                 double fStart = MathUtil.map(burnStart, t0, t1, f0, f1);
331                                 averageThrust += (fStart + f1) / 2 * (t1 - burnStart);
332                         } else if (t0 >= burnStart && t1 <= burnEnd) {
333                                 averageThrust += (f0 + f1) / 2 * (t1 - t0);
334                         } else if (t0 < burnEnd && t1 > burnEnd) {
335                                 double fEnd = MathUtil.map(burnEnd, t0, t1, f0, f1);
336                                 averageThrust += (f0 + fEnd) / 2 * (burnEnd - t0);
337                         }
338                 }
339                 
340                 if (burnTime > 0) {
341                         averageThrust /= burnTime;
342                 } else {
343                         averageThrust = 0;
344                 }
345                 
346         }
347         
348         
349         //////////  Static methods
350         
351         /**
352          * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
353          * returns "P".
354          *  
355          * @param delay         the delay time.
356          * @return                      the <code>String</code> representation.
357          */
358         public static String getDelayString(double delay) {
359                 return getDelayString(delay, "P");
360         }
361         
362         /**
363          * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
364          * <code>plugged</code> is returned.
365          *   
366          * @param delay         the delay time.
367          * @param plugged       the return value if there is no ejection charge.
368          * @return                      the String representation.
369          */
370         public static String getDelayString(double delay, String plugged) {
371                 if (delay == PLUGGED)
372                         return plugged;
373                 delay = Math.rint(delay * 10) / 10;
374                 if (MathUtil.equals(delay, Math.rint(delay)))
375                         return "" + ((int) delay);
376                 return "" + delay;
377         }
378         
379         
380
381         ////////  Motor instance implementation  ////////
382         private class ThrustCurveMotorInstance implements MotorInstance {
383                 
384                 private int position;
385                 
386                 // Previous time step value
387                 private double prevTime;
388                 
389                 // Average thrust during previous step
390                 private double stepThrust;
391                 // Instantaneous thrust at current time point
392                 private double instThrust;
393                 
394                 // Average CG during previous step
395                 private Coordinate stepCG;
396                 // Instantaneous CG at current time point
397                 private Coordinate instCG;
398                 
399                 private final double unitRotationalInertia;
400                 private final double unitLongitudinalInertia;
401                 
402                 private int modID = 0;
403                 
404                 public ThrustCurveMotorInstance() {
405                         log.debug("ThrustCurveMotor:  Creating motor instance of " + ThrustCurveMotor.this);
406                         position = 0;
407                         prevTime = 0;
408                         instThrust = 0;
409                         stepThrust = 0;
410                         instCG = cg[0];
411                         stepCG = cg[0];
412                         unitRotationalInertia = Inertia.filledCylinderRotational(getDiameter() / 2);
413                         unitLongitudinalInertia = Inertia.filledCylinderLongitudinal(getDiameter() / 2, getLength());
414                 }
415                 
416                 @Override
417                 public double getTime() {
418                         return prevTime;
419                 }
420                 
421                 @Override
422                 public Coordinate getCG() {
423                         return stepCG;
424                 }
425                 
426                 @Override
427                 public double getLongitudinalInertia() {
428                         return unitLongitudinalInertia * stepCG.weight;
429                 }
430                 
431                 @Override
432                 public double getRotationalInertia() {
433                         return unitRotationalInertia * stepCG.weight;
434                 }
435                 
436                 @Override
437                 public double getThrust() {
438                         return stepThrust;
439                 }
440                 
441                 @Override
442                 public boolean isActive() {
443                         return prevTime < time[time.length - 1];
444                 }
445                 
446                 @Override
447                 public void step(double nextTime, double acceleration, AtmosphericConditions cond) {
448                         
449                         if (!(nextTime >= prevTime)) {
450                                 // Also catches NaN
451                                 throw new IllegalArgumentException("Stepping backwards in time, current=" +
452                                                 prevTime + " new=" + nextTime);
453                         }
454                         if (MathUtil.equals(prevTime, nextTime)) {
455                                 return;
456                         }
457                         
458                         modID++;
459                         
460                         if (position >= time.length - 1) {
461                                 // Thrust has ended
462                                 prevTime = nextTime;
463                                 stepThrust = 0;
464                                 instThrust = 0;
465                                 stepCG = cg[cg.length - 1];
466                                 return;
467                         }
468                         
469
470                         // Compute average & instantaneous thrust
471                         if (nextTime < time[position + 1]) {
472                                 
473                                 // Time step between time points
474                                 double nextF = MathUtil.map(nextTime, time[position], time[position + 1],
475                                                 thrust[position], thrust[position + 1]);
476                                 stepThrust = (instThrust + nextF) / 2;
477                                 instThrust = nextF;
478                                 
479                         } else {
480                                 
481                                 // Portion of previous step
482                                 stepThrust = (instThrust + thrust[position + 1]) / 2 * (time[position + 1] - prevTime);
483                                 
484                                 // Whole steps
485                                 position++;
486                                 while ((position < time.length - 1) && (nextTime >= time[position + 1])) {
487                                         stepThrust += (thrust[position] + thrust[position + 1]) / 2 *
488                                                                         (time[position + 1] - time[position]);
489                                         position++;
490                                 }
491                                 
492                                 // End step
493                                 if (position < time.length - 1) {
494                                         instThrust = MathUtil.map(nextTime, time[position], time[position + 1],
495                                                         thrust[position], thrust[position + 1]);
496                                         stepThrust += (thrust[position] + instThrust) / 2 *
497                                                                         (nextTime - time[position]);
498                                 } else {
499                                         // Thrust ended during this step
500                                         instThrust = 0;
501                                 }
502                                 
503                                 stepThrust /= (nextTime - prevTime);
504                                 
505                         }
506                         
507                         // Compute average and instantaneous CG (simple average between points)
508                         Coordinate nextCG;
509                         if (position < time.length - 1) {
510                                 nextCG = MathUtil.map(nextTime, time[position], time[position + 1],
511                                                 cg[position], cg[position + 1]);
512                         } else {
513                                 nextCG = cg[cg.length - 1];
514                         }
515                         stepCG = instCG.add(nextCG).multiply(0.5);
516                         instCG = nextCG;
517                         
518                         // Update time
519                         prevTime = nextTime;
520                 }
521                 
522                 @Override
523                 public MotorInstance clone() {
524                         try {
525                                 return (MotorInstance) super.clone();
526                         } catch (CloneNotSupportedException e) {
527                                 throw new BugException("CloneNotSupportedException", e);
528                         }
529                 }
530                 
531                 @Override
532                 public int getModID() {
533                         return modID;
534                 }
535         }
536         
537         
538
539         @Override
540         public int compareTo(ThrustCurveMotor other) {
541                 
542                 int value;
543                 
544                 // 1. Manufacturer
545                 value = COLLATOR.compare(this.manufacturer.getDisplayName(),
546                                 ((ThrustCurveMotor) other).manufacturer.getDisplayName());
547                 if (value != 0)
548                         return value;
549                 
550                 // 2. Designation
551                 value = DESIGNATION_COMPARATOR.compare(this.getDesignation(), other.getDesignation());
552                 if (value != 0)
553                         return value;
554                 
555                 // 3. Diameter
556                 value = (int) ((this.getDiameter() - other.getDiameter()) * 1000000);
557                 if (value != 0)
558                         return value;
559                 
560                 // 4. Length
561                 value = (int) ((this.getLength() - other.getLength()) * 1000000);
562                 return value;
563                 
564         }
565         
566
567 }