1 package net.sf.openrocket.rocketcomponent;
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;
9 import net.sf.openrocket.util.Coordinate;
10 import net.sf.openrocket.util.MathUtil;
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.
20 * NOTE: The current implementation of {@link #getAverageTime()} and
21 * {@link #getAverageThrust()} assume that the class is immutable!
23 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
25 public abstract class Motor implements Comparable<Motor> {
28 * Enum of rocket motor types.
30 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
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");
38 private final String name;
39 private final String description;
41 Type(String name, String description) {
43 this.description = description;
47 * Return a short name of this motor type.
48 * @return a short name of the motor type.
50 public String getName() {
55 * Return a long description of this motor type.
56 * @return a description of the motor type.
58 public String getDescription() {
63 public String toString() {
70 * Ejection charge delay value signifying a "plugged" motor with no ejection charge.
71 * The value is that of <code>Double.POSITIVE_INFINITY</code>.
73 public static final double PLUGGED = Double.POSITIVE_INFINITY;
77 * Below what portion of maximum thrust is the motor chosen to be off when
78 * calculating average thrust and burn time.double
80 public static final double AVERAGE_MARGINAL = 0.05;
82 /* All data is cached, so divisions can be very tight. */
83 private static final int DIVISIONS = 1000;
87 private static final Collator COLLATOR = Collator.getInstance(Locale.US);
89 COLLATOR.setStrength(Collator.PRIMARY);
91 private static DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
96 private final String manufacturer;
97 private final String designation;
98 private final String description;
99 private final Type motorType;
101 private final double[] delays;
103 private final double diameter;
104 private final double length;
107 private double maxThrust = -1;
108 private double avgTime = -1;
109 private double avgThrust = -1;
110 private double totalImpulse = -1;
115 * Sole constructor. None of the parameters may be <code>null</code>.
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
127 protected Motor(String manufacturer, String designation, String description,
128 Type type, double[] delays, double diameter, double length) {
130 if (manufacturer == null || designation == null || description == null ||
131 type == null || delays == null) {
132 throw new IllegalArgumentException("Parameters cannot be null.");
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;
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.
150 * @return the total burn time of the motor.
152 public abstract double getTotalTime();
156 * Return the thrust of the motor at the specified time.
158 * @param time time since the ignition of the motor.
159 * @return the thrust at the specified time.
161 public abstract double getThrust(double time);
165 * Return the average thrust of the motor between times t1 and t2.
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.
171 /* TODO: MEDIUM: Implement better method in subclass */
172 public double getThrust(double t1, double t2) {
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);
185 * Return the mass and CG of the motor at the specified time.
187 * @param time time since the ignition of the motor.
188 * @return the mass and CG of the motor.
190 public abstract Coordinate getCG(double time);
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>.
199 * @param time time since the ignition of the motor.
200 * @return the mass of the motor.
202 public double getMass(double time) {
203 return getCG(time).weight;
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.
212 * @param time time since the ignition of the motor.
213 * @return the longitudal moment of inertia of the motor.
215 public double getLongitudalInertia(double time) {
216 return getMass(time) * (3.0*MathUtil.pow2(diameter/2) + MathUtil.pow2(length))/12;
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.
226 * @param time time since the ignition of the motor.
227 * @return the rotational moment of inertia of the motor.
229 public double getRotationalInertia(double time) {
230 return getMass(time) * MathUtil.pow2(diameter) / 8;
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.
241 * @return the maximum thrust of the motor
243 public double getMaxThrust() {
245 double time = getTotalTime();
248 for (int i=0; i < DIVISIONS; i++) {
249 double t = time * i / DIVISIONS;
250 double thrust = getThrust(t);
252 if (thrust > maxThrust)
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.
265 * @return the nominal burn time.
267 public double getAverageTime() {
268 // Compute average time lazily
270 double max = getMaxThrust();
271 double time = getTotalTime();
273 for (int i=DIVISIONS; i >= 0; i--) {
274 avgTime = time * i / DIVISIONS;
275 if (getThrust(avgTime) > max*AVERAGE_MARGINAL)
284 * Return the calculated average thrust during time from ignition to
285 * {@link #getAverageTime()}.
287 * @return the nominal average thrust.
289 public double getAverageThrust() {
290 // Compute average thrust lazily
292 double time = getAverageTime();
295 for (int i=0; i < DIVISIONS; i++) {
296 double t = time * i / DIVISIONS;
297 avgThrust += getThrust(t);
299 avgThrust /= DIVISIONS;
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.
310 * @return the total impulse of the motor.
312 public double getTotalImpulse() {
313 // Compute total impulse lazily
314 if (totalImpulse < 0) {
315 double time = getTotalTime();
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);
334 * Return the manufacturer of the motor.
336 * @return the manufacturer
338 public String getManufacturer() {
343 * Return the designation of the motor.
345 * @return the designation
347 public String getDesignation() {
352 * Return the designation of the motor, including a delay.
354 * @param delay the delay of the motor.
355 * @return designation with delay.
357 public String getDesignation(double delay) {
358 return getDesignation() + "-" + getDelayString(delay);
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.
367 * @return the description
369 public String getDescription() {
375 * Return the motor type.
377 * @return the motorType
379 public Type getMotorType() {
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>).
390 * @return the list of standard ejection charge delays, which may be empty.
392 public double[] getStandardDelays() {
393 return delays.clone();
397 * Return the maximum diameter of the motor.
399 * @return the diameter
401 public double getDiameter() {
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.
413 public double getLength() {
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).
424 * The comment field is ignored when comparing equality.
427 public boolean equals(Object o) {
428 if (!(o instanceof Motor))
431 Motor other = (Motor) o;
433 // Tests manufacturer, designation, diameter and length
434 if (this.compareTo(other) != 0)
437 if (Math.abs(this.getTotalTime() - other.getTotalTime()) > 0.5 ||
438 this.motorType != other.motorType ||
439 this.delays.length != other.delays.length) {
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) {
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) {
462 * A <code>hashCode</code> method compatible with the <code>equals</code>
466 public int hashCode() {
467 return (manufacturer.hashCode() + designation.hashCode() +
468 ((int)(length*1000)) + ((int)(diameter*1000)));
474 public String toString() {
475 return manufacturer + " " + designation;
479 ////////// Static methods
483 * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
486 * @param delay the delay time.
487 * @return the <code>String</code> representation.
489 public static String getDelayString(double delay) {
490 return getDelayString(delay,"P");
494 * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
495 * <code>plugged</code> is returned.
497 * @param delay the delay time.
498 * @param plugged the return value if there is no ejection charge.
499 * @return the String representation.
501 public static String getDelayString(double delay, String plugged) {
502 if (delay == PLUGGED)
504 delay = Math.rint(delay*10)/10;
505 if (MathUtil.equals(delay, Math.rint(delay)))
506 return "" + ((int)delay);
513 //////////// Comparation
518 public int compareTo(Motor other) {
521 if (COLLATOR == null) {
525 value = COLLATOR.compare(this.manufacturer, other.manufacturer);
530 value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation);
535 value = (int)((this.diameter - other.diameter)*1000000);
540 value = (int)((this.length - other.length)*1000000);
545 value = (int)((this.getTotalImpulse() - other.getTotalImpulse())*1000);
551 public static Comparator<String> getDesignationComparator() {
552 return DESIGNATION_COMPARATOR;
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.
561 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
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]+)(.*?)$");
568 public int compare(String o1, String o2) {
572 m1 = pattern.matcher(o1);
573 m2 = pattern.matcher(o2);
575 if (m1.find() && m2.find()) {
577 String o1Class = m1.group(3);
578 int o1Thrust = Integer.parseInt(m1.group(4));
579 String o1Extra = m1.group(5);
581 String o2Class = m2.group(3);
582 int o2Thrust = Integer.parseInt(m2.group(4));
583 String o2Extra = m2.group(5);
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);
591 if (sub1 != null || sub2 != null) {
596 value = -COLLATOR.compare(sub1,sub2);
601 value = COLLATOR.compare(o1Class,o2Class);
606 if (o1Thrust != o2Thrust)
607 return o1Thrust - o2Thrust;
610 return COLLATOR.compare(o1Extra, o2Extra);
614 System.out.println("Falling back");
615 System.out.println("o1:"+o1 + " o2:"+o2);
617 // Not understandable designation, simply compare strings
618 return COLLATOR.compare(o1, o2);