Initial commit
[debian/openrocket] / src / net / sf / openrocket / rocketcomponent / Motor.java
1 package net.sf.openrocket.rocketcomponent;
2
3 import java.text.Collator;
4 import java.util.Comparator;
5 import java.util.Locale;
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
8
9 import net.sf.openrocket.util.Coordinate;
10 import net.sf.openrocket.util.MathUtil;
11
12
13
14 /**
15  * Abstract base class for motors.  The methods that must be implemented are
16  * {@link #getTotalTime()}, {@link #getThrust(double)} and {@link #getCG(double)}.
17  * Additionally the method {@link #getMaxThrust()} may be overridden for efficiency.
18  * <p>
19  * 
20  * NOTE:  The current implementation of {@link #getAverageTime()} and 
21  * {@link #getAverageThrust()} assume that the class is immutable!
22  * 
23  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
24  */
25 public abstract class Motor implements Comparable<Motor> {
26         
27         /**
28          * Enum of rocket motor types.
29          * 
30          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
31          */
32         public enum Type {
33                 SINGLE("Single-use", "Single-use solid propellant motor"), 
34                 RELOAD("Reloadable", "Reloadable solid propellant motor"), 
35                 HYBRID("Hybrid", "Hybrid rocket motor engine"), 
36                 UNKNOWN("Unknown", "Unknown motor type");
37                 
38                 private final String name;
39                 private final String description;
40                 
41                 Type(String name, String description) {
42                         this.name = name;
43                         this.description = description;
44                 }
45
46                 /**
47                  * Return a short name of this motor type.
48                  * @return  a short name of the motor type.
49                  */
50                 public String getName() {
51                         return name;
52                 }
53                 
54                 /**
55                  * Return a long description of this motor type.
56                  * @return  a description of the motor type.
57                  */
58                 public String getDescription() {
59                         return description;
60                 }
61                 
62                 @Override
63                 public String toString() {
64                         return name;
65                 }
66         }
67         
68         
69         /**
70          * Ejection charge delay value signifying a "plugged" motor with no ejection charge.
71          * The value is that of <code>Double.POSITIVE_INFINITY</code>.
72          */
73         public static final double PLUGGED = Double.POSITIVE_INFINITY;
74         
75         
76         /**
77          * Below what portion of maximum thrust is the motor chosen to be off when
78          * calculating average thrust and burn time.double
79          */
80         public static final double AVERAGE_MARGINAL = 0.05;
81         
82         /* All data is cached, so divisions can be very tight. */
83         private static final int DIVISIONS = 1000;
84
85         
86         //  Comparators:
87         private static final Collator COLLATOR = Collator.getInstance(Locale.US);
88         static {
89                 COLLATOR.setStrength(Collator.PRIMARY);
90         }
91         private static DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
92         
93         
94         
95         
96         private final String manufacturer;
97         private final String designation;
98         private final String description;
99         private final Type motorType;
100         
101         private final double[] delays;
102         
103         private final double diameter;
104         private final double length;
105         
106         /* Cached data */
107         private double maxThrust = -1;
108         private double avgTime = -1;
109         private double avgThrust = -1;
110         private double totalImpulse = -1;
111         
112         
113         
114         /**
115          * Sole constructor.  None of the parameters may be <code>null</code>.
116          * 
117          * @param manufacturer  the manufacturer of the motor.
118          * @param designation   the motor designation.
119          * @param description   further description, including any comments on the origin
120          *                                              of the thrust curve.
121          * @param delays                an array of the standard ejection charge delays.  A plugged
122          *                                              motor (no ejection charge) is specified by a delay of
123          *                                              {@link #PLUGGED} (<code>Double.POSITIVE_INFINITY</code>).
124          * @param diameter              maximum diameter of the motor
125          * @param length                length of the motor
126          */
127         protected Motor(String manufacturer, String designation, String description, 
128                         Type type, double[] delays, double diameter, double length) {
129
130                 if (manufacturer == null || designation == null || description == null ||
131                                 type == null || delays == null) {
132                         throw new IllegalArgumentException("Parameters cannot be null.");
133                 }
134                 
135                 this.manufacturer = manufacturer;
136                 this.designation = designation;
137                 this.description = description.trim();
138                 this.motorType = type;
139                 this.delays = delays.clone();
140                 this.diameter = diameter;
141                 this.length = length;
142         }
143
144
145         
146         /**
147          * Return the total burn time of the motor.  The method {@link #getThrust(double)}
148          * must return zero for time values greater than the return value.
149          * 
150          * @return  the total burn time of the motor.
151          */
152         public abstract double getTotalTime();
153
154
155         /**
156          * Return the thrust of the motor at the specified time.
157          * 
158          * @param time  time since the ignition of the motor.
159          * @return      the thrust at the specified time.
160          */
161         public abstract double getThrust(double time);
162         
163         
164         /**
165          * Return the average thrust of the motor between times t1 and t2.
166          * 
167          * @param t1    starting time since the ignition of the motor.
168          * @param t2    end time since the ignition of the motor.
169          * @return              the average thrust during the time period.
170          */
171         /* TODO: MEDIUM: Implement better method in subclass */
172         public double getThrust(double t1, double t2) {
173                 double f = 0;
174                 f += getThrust(t1);
175                 f += getThrust(0.8*t1 + 0.2*t2);
176                 f += getThrust(0.6*t1 + 0.4*t2);
177                 f += getThrust(0.4*t1 + 0.6*t2);
178                 f += getThrust(0.2*t1 + 0.8*t2);
179                 f += getThrust(t2);
180                 return f/6;
181         }
182
183         
184         /**
185          * Return the mass and CG of the motor at the specified time.
186          * 
187          * @param time  time since the ignition of the motor.
188          * @return      the mass and CG of the motor.
189          */
190         public abstract Coordinate getCG(double time);
191         
192         
193         
194         /**
195          * Return the mass of the motor at the specified time.  The original mass
196          * of the motor can be queried by <code>getMass(0)</code> and the burnt mass
197          * by <code>getMass(Double.MAX_VALUE)</code>.
198          * 
199          * @param time  time since the ignition of the motor.
200          * @return      the mass of the motor.
201          */
202         public double getMass(double time) {
203                 return getCG(time).weight;
204         }
205         
206         
207         /**
208          * Return the longitudal moment of inertia of the motor at the specified time.
209          * This default method assumes that the mass of the motor is evenly distributed
210          * in a cylinder with the diameter and length of the motor.
211          * 
212          * @param time  time since the ignition of the motor.
213          * @return              the longitudal moment of inertia of the motor.
214          */
215         public double getLongitudalInertia(double time) {
216                 return getMass(time) * (3.0*MathUtil.pow2(diameter/2) + MathUtil.pow2(length))/12;
217         }
218         
219         
220         
221         /**
222          * Return the rotational moment of inertia of the motor at the specified time.
223          * This default method assumes that the mass of the motor is evenly distributed
224          * in a cylinder with the diameter and length of the motor.
225          * 
226          * @param time  time since the ignition of the motor.
227          * @return              the rotational moment of inertia of the motor.
228          */
229         public double getRotationalInertia(double time) {
230                 return getMass(time) * MathUtil.pow2(diameter) / 8;
231         }
232         
233         
234         
235         
236         /**
237          * Return the maximum thrust.  This implementation slices through the thrust curve
238          * searching for the maximum thrust.  Subclasses may wish to override this with a
239          * more efficient method.
240          * 
241          * @return  the maximum thrust of the motor
242          */
243         public double getMaxThrust() {
244                 if (maxThrust < 0) {
245                         double time = getTotalTime();
246                         maxThrust = 0;
247                         
248                         for (int i=0; i < DIVISIONS; i++) {
249                                 double t = time * i / DIVISIONS;
250                                 double thrust = getThrust(t);
251                                 
252                                 if (thrust > maxThrust)
253                                         maxThrust = thrust;
254                         }
255                 }
256                 return maxThrust;
257         }
258         
259         
260         /**
261          * Return the time used in calculating the average thrust.  The time is the
262          * length of time from motor ignition until the thrust has dropped below
263          * {@link #AVERAGE_MARGINAL} times the maximum thrust.
264          * 
265          * @return  the nominal burn time.
266          */
267         public double getAverageTime() {
268                 // Compute average time lazily
269                 if (avgTime < 0) {
270                         double max = getMaxThrust();
271                         double time = getTotalTime();
272                         
273                         for (int i=DIVISIONS; i >= 0; i--) {
274                                 avgTime = time * i / DIVISIONS;
275                                 if (getThrust(avgTime) > max*AVERAGE_MARGINAL)
276                                         break;
277                         }
278                 }
279                 return avgTime;
280         }
281         
282         
283         /**
284          * Return the calculated average thrust during time from ignition to
285          * {@link #getAverageTime()}.
286          * 
287          * @return  the nominal average thrust.
288          */
289         public double getAverageThrust() {
290                 // Compute average thrust lazily
291                 if (avgThrust < 0) {
292                         double time = getAverageTime();
293                         
294                         avgThrust = 0;
295                         for (int i=0; i < DIVISIONS; i++) {
296                                 double t = time * i / DIVISIONS;
297                                 avgThrust += getThrust(t);
298                         }
299                         avgThrust /= DIVISIONS;
300                 }
301                 return avgThrust;
302         }
303         
304         
305         /**
306          * Return the total impulse of the motor.  This is calculated from the entire
307          * burn time, and therefore may differ from the value of {@link #getAverageTime()}
308          * and {@link #getAverageThrust()} multiplied together.
309          * 
310          * @return  the total impulse of the motor.
311          */
312         public double getTotalImpulse() {
313                 // Compute total impulse lazily
314                 if (totalImpulse < 0) {
315                         double time = getTotalTime();
316                         double f0, t0;
317                         
318                         totalImpulse = 0;
319                         t0 = 0;
320                         f0 = getThrust(0);
321                         for (int i=1; i < DIVISIONS; i++) {
322                                 double t1 = time * i / DIVISIONS;
323                                 double f1 = getThrust(t1); 
324                                 totalImpulse += 0.5*(f0+f1)*(t1-t0);
325                                 t0 = t1;
326                                 f0 = f1;
327                         }
328                 }
329                 return totalImpulse;
330         }
331         
332
333         /**
334          * Return the manufacturer of the motor.
335          * 
336          * @return the manufacturer
337          */
338         public String getManufacturer() {
339                 return manufacturer;
340         }
341         
342         /**
343          * Return the designation of the motor.
344          * 
345          * @return the designation
346          */
347         public String getDesignation() {
348                 return designation;
349         }
350         
351         /**
352          * Return the designation of the motor, including a delay.
353          * 
354          * @param delay  the delay of the motor.
355          * @return               designation with delay.
356          */
357         public String getDesignation(double delay) {
358                 return getDesignation() + "-" + getDelayString(delay);
359         }
360
361         
362         /**
363          * Return extra description for the motor.  This may include for example 
364          * comments on the source of the thrust curve.  The returned <code>String</code>
365          * may include new-lines.
366          * 
367          * @return the description
368          */
369         public String getDescription() {
370                 return description;
371         }
372         
373         
374         /**
375          * Return the motor type.
376          * 
377          * @return  the motorType
378          */
379         public Type getMotorType() {
380                 return motorType;
381         }
382
383
384
385         /**
386          * Return the standard ejection charge delays for the motor.  "Plugged" motors
387          * with no ejection charge are signified by the value {@link #PLUGGED}
388          * (<code>Double.POSITIVE_INFINITY</code>).
389          * 
390          * @return  the list of standard ejection charge delays, which may be empty.
391          */
392         public double[] getStandardDelays() {
393                 return delays.clone();
394         }
395
396         /**
397          * Return the maximum diameter of the motor.
398          * 
399          * @return the diameter
400          */
401         public double getDiameter() {
402                 return diameter;
403         }
404
405         /**
406          * Return the length of the motor.  This should be a "characteristic" length,
407          * and the exact definition may depend on the motor type.  Typically this should
408          * be the length from the bottom of the motor to the end of the maximum diameter
409          * portion, ignoring any smaller ejection charge compartments.
410          * 
411          * @return the length
412          */
413         public double getLength() {
414                 return length;
415         }
416         
417         
418         /**
419          * Compares two <code>Motor</code> objects.  The motors are considered equal
420          * if they have identical manufacturers, designations and types, near-identical
421          * dimensions, burn times and delays and near-identical thrust curves
422          * (sampled at 10 equidistant points).
423          * <p>
424          * The comment field is ignored when comparing equality.
425          */
426         @Override
427         public boolean equals(Object o) {
428                 if (!(o instanceof Motor))
429                         return false;
430                 
431                 Motor other = (Motor) o;
432                 
433                 // Tests manufacturer, designation, diameter and length
434                 if (this.compareTo(other) != 0)
435                         return false;
436                 
437                 if (Math.abs(this.getTotalTime() - other.getTotalTime()) > 0.5 ||
438                                 this.motorType != other.motorType ||
439                                 this.delays.length != other.delays.length) {
440                         
441                         return false;
442                 }
443                 
444                 for (int i=0; i < delays.length; i++) {
445                         // INF - INF == NaN, which produces false when compared
446                         if (Math.abs(this.delays[i] - other.delays[i]) > 0.5) {
447                                 return false;
448                         }
449                 }
450                 
451                 double time = getTotalTime();
452                 for (int i=0; i < 10; i++) {
453                         double t = time * i/10;
454                         if (Math.abs(this.getThrust(t) - other.getThrust(t)) > 1) {
455                                 return false;
456                         }
457                 }
458                 return true;
459         }
460         
461         /**
462          * A <code>hashCode</code> method compatible with the <code>equals</code>
463          * method.
464          */
465         @Override
466         public int hashCode() {
467                 return (manufacturer.hashCode() + designation.hashCode() + 
468                                 ((int)(length*1000)) + ((int)(diameter*1000)));
469         }
470         
471         
472         
473         @Override
474         public String toString() {
475                 return manufacturer + " " + designation;
476         }
477         
478         
479         //////////  Static methods
480
481         
482         /**
483          * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
484          * returns "P".
485          *  
486          * @param delay         the delay time.
487          * @return                      the <code>String</code> representation.
488          */
489         public static String getDelayString(double delay) {
490                 return getDelayString(delay,"P");
491         }
492         
493         /**
494          * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
495          * <code>plugged</code> is returned.
496          *   
497          * @param delay         the delay time.
498          * @param plugged       the return value if there is no ejection charge.
499          * @return                      the String representation.
500          */
501         public static String getDelayString(double delay, String plugged) {
502                 if (delay == PLUGGED)
503                         return plugged;
504                 delay = Math.rint(delay*10)/10;
505                 if (MathUtil.equals(delay, Math.rint(delay)))
506                         return "" + ((int)delay);
507                 return "" + delay;
508         }
509         
510
511         
512         
513         ////////////  Comparation
514         
515         
516
517         @Override
518         public int compareTo(Motor other) {
519                 int value;
520                 
521                 if (COLLATOR == null) {
522                 }
523                 
524                 // 1. Manufacturer
525                 value = COLLATOR.compare(this.manufacturer, other.manufacturer);
526                 if (value != 0)
527                         return value;
528                 
529                 // 2. Designation
530                 value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation);
531                 if (value != 0)
532                         return value;
533                 
534                 // 3. Diameter
535                 value = (int)((this.diameter - other.diameter)*1000000);
536                 if (value != 0)
537                         return value;
538                                 
539                 // 4. Length
540                 value = (int)((this.length - other.length)*1000000);
541                 if (value != 0)
542                         return value;
543                 
544                 // 5. Total impulse
545                 value = (int)((this.getTotalImpulse() - other.getTotalImpulse())*1000);
546                 return value;
547         }
548         
549         
550         
551         public static Comparator<String> getDesignationComparator() {
552                 return DESIGNATION_COMPARATOR;
553         }
554         
555         
556         /**
557          * Compares two motors by their designations.  The motors are ordered first
558          * by their motor class, second by their average thrust and lastly by any
559          * extra modifiers at the end of the designation.
560          * 
561          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
562          */
563         private static class DesignationComparator implements Comparator<String> {
564                 private Pattern pattern = 
565                         Pattern.compile("^([0-9][0-9]+|1/([1-8]))?([a-zA-Z])([0-9]+)(.*?)$");
566                 
567                 @Override
568                 public int compare(String o1, String o2) {
569                         int value;
570                         Matcher m1, m2;
571                         
572                         m1 = pattern.matcher(o1);
573                         m2 = pattern.matcher(o2);
574                         
575                         if (m1.find() && m2.find()) {
576
577                                 String o1Class = m1.group(3);
578                                 int o1Thrust = Integer.parseInt(m1.group(4));
579                                 String o1Extra = m1.group(5);
580                                 
581                                 String o2Class = m2.group(3);
582                                 int o2Thrust = Integer.parseInt(m2.group(4));
583                                 String o2Extra = m2.group(5);
584                                 
585                                 // 1. Motor class
586                                 if (o1Class.equalsIgnoreCase("A") && o2Class.equalsIgnoreCase("A")) {
587                                         //  1/2A and 1/4A comparison
588                                         String sub1 = m1.group(2);
589                                         String sub2 = m2.group(2);
590
591                                         if (sub1 != null || sub2 != null) {
592                                                 if (sub1 == null)
593                                                         sub1 = "1";
594                                                 if (sub2 == null)
595                                                         sub2 = "1";
596                                                 value = -COLLATOR.compare(sub1,sub2);
597                                                 if (value != 0)
598                                                         return value;
599                                         }
600                                 }
601                                 value = COLLATOR.compare(o1Class,o2Class);
602                                 if (value != 0)
603                                         return value;
604                                 
605                                 // 2. Average thrust
606                                 if (o1Thrust != o2Thrust)
607                                         return o1Thrust - o2Thrust;
608                                 
609                                 // 3. Extra modifier
610                                 return COLLATOR.compare(o1Extra, o2Extra);
611                                 
612                         } else {
613                                 
614                                 System.out.println("Falling back");
615                                 System.out.println("o1:"+o1 + " o2:"+o2);
616                                 
617                                 // Not understandable designation, simply compare strings
618                                 return COLLATOR.compare(o1, o2);
619                         }
620                 }
621         }
622
623 }