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