1 package net.sf.openrocket.motor;
3 import java.text.Collator;
4 import java.util.Arrays;
5 import java.util.Locale;
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.ArrayUtils;
11 import net.sf.openrocket.util.BugException;
12 import net.sf.openrocket.util.Coordinate;
13 import net.sf.openrocket.util.Inertia;
14 import net.sf.openrocket.util.MathUtil;
17 public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
18 private static final LogHelper log = Application.getLogger();
20 public static final double MAX_THRUST = 10e6;
23 private static final Collator COLLATOR = Collator.getInstance(Locale.US);
25 COLLATOR.setStrength(Collator.PRIMARY);
27 private static final DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
29 private final String digest;
31 private final Manufacturer manufacturer;
32 private final String designation;
33 private final String description;
34 private final Motor.Type type;
35 private final double[] delays;
36 private final double diameter;
37 private final double length;
38 private final double[] time;
39 private final double[] thrust;
40 private final Coordinate[] cg;
42 private double maxThrust;
43 private double burnTime;
44 private double averageThrust;
45 private double totalImpulse;
48 * Deep copy constructor.
49 * Constructs a new ThrustCurveMotor from an existing ThrustCurveMotor.
52 protected ThrustCurveMotor( ThrustCurveMotor m ) {
53 this.digest = m.digest;
54 this.manufacturer = m.manufacturer;
55 this.designation = m.designation;
56 this.description = m.description;
58 this.delays = ArrayUtils.copyOf(m.delays, m.delays.length);
59 this.diameter = m.diameter;
60 this.length = m.length;
61 this.time = ArrayUtils.copyOf(m.time, m.time.length);
62 this.thrust = ArrayUtils.copyOf(m.thrust, m.thrust.length);
63 this.cg = new Coordinate[ m.cg.length ];
64 for( int i = 0; i< cg.length; i++ ) {
65 this.cg[i] = m.cg[i].clone();
67 this.maxThrust = m.maxThrust;
68 this.burnTime = m.burnTime;
69 this.averageThrust = m.averageThrust;
70 this.totalImpulse = m.totalImpulse;
74 * Sole constructor. Sets all the properties of the motor.
76 * @param manufacturer the manufacturer of the motor.
77 * @param designation the designation of the motor.
78 * @param description extra description of the motor.
79 * @param type the motor type
80 * @param delays the delays defined for this thrust curve
81 * @param diameter diameter of the motor.
82 * @param length length of the motor.
83 * @param time the time points for the thrust curve.
84 * @param thrust thrust at the time points.
85 * @param cg cg at the time points.
87 public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description,
88 Motor.Type type, double[] delays, double diameter, double length,
89 double[] time, double[] thrust, Coordinate[] cg, String digest) {
91 // Check argument validity
92 if ((time.length != thrust.length) || (time.length != cg.length)) {
93 throw new IllegalArgumentException("Array lengths do not match, " +
94 "time:" + time.length + " thrust:" + thrust.length +
97 if (time.length < 2) {
98 throw new IllegalArgumentException("Too short thrust-curve, length=" +
101 for (int i = 0; i < time.length - 1; i++) {
102 if (time[i + 1] < time[i]) {
103 throw new IllegalArgumentException("Time goes backwards, " +
104 "time[" + i + "]=" + time[i] + " " +
105 "time[" + (i + 1) + "]=" + time[i + 1]);
108 if (!MathUtil.equals(time[0], 0)) {
109 throw new IllegalArgumentException("Curve starts at time " + time[0]);
111 if (!MathUtil.equals(thrust[0], 0)) {
112 throw new IllegalArgumentException("Curve starts at thrust " + thrust[0]);
114 if (!MathUtil.equals(thrust[thrust.length - 1], 0)) {
115 throw new IllegalArgumentException("Curve ends at thrust " +
116 thrust[thrust.length - 1]);
118 for (double t : thrust) {
120 throw new IllegalArgumentException("Negative thrust.");
122 if (t > MAX_THRUST || Double.isNaN(t)) {
123 throw new IllegalArgumentException("Invalid thrust " + t);
126 for (Coordinate c : cg) {
128 throw new IllegalArgumentException("Invalid CG " + c);
130 if (c.x < 0 || c.x > length) {
131 throw new IllegalArgumentException("Invalid CG position " + c.x);
134 throw new IllegalArgumentException("Negative mass " + c.weight);
138 if (type != Motor.Type.SINGLE && type != Motor.Type.RELOAD &&
139 type != Motor.Type.HYBRID && type != Motor.Type.UNKNOWN) {
140 throw new IllegalArgumentException("Illegal motor type=" + type);
144 this.manufacturer = manufacturer;
145 this.designation = designation;
146 this.description = description;
148 this.delays = delays.clone();
149 this.diameter = diameter;
150 this.length = length;
151 this.time = time.clone();
152 this.thrust = thrust.clone();
153 this.cg = cg.clone();
161 * Get the manufacturer of this motor.
163 * @return the manufacturer
165 public Manufacturer getManufacturer() {
171 * Return the array of time points for this thrust curve.
172 * @return an array of time points where the thrust is sampled
174 public double[] getTimePoints() {
179 * Returns the array of thrust points for this thrust curve.
180 * @return an array of thrust samples
182 public double[] getThrustPoints() {
183 return thrust.clone();
187 * Returns the array of CG points for this thrust curve.
188 * @return an array of CG samples
190 public Coordinate[] getCGPoints() {
195 * Return a list of standard delays defined for this motor.
196 * @return a list of standard delays
198 public double[] getStandardDelays() {
199 return delays.clone();
206 * NOTE: In most cases you want to examine the motor type of the ThrustCurveMotorSet,
207 * not the ThrustCurveMotor itself.
210 public Type getMotorType() {
216 public String getDesignation() {
221 public String getDesignation(double delay) {
222 return designation + "-" + getDelayString(delay);
227 public String getDescription() {
232 public double getDiameter() {
237 public double getLength() {
243 public MotorInstance getInstance() {
244 return new ThrustCurveMotorInstance();
249 public Coordinate getLaunchCG() {
254 public Coordinate getEmptyCG() {
255 return cg[cg.length - 1];
262 public double getBurnTimeEstimate() {
267 public double getAverageThrustEstimate() {
268 return averageThrust;
272 public double getMaxThrustEstimate() {
277 public double getTotalImpulseEstimate() {
282 public String getDigest() {
288 * Compute the general statistics of this motor.
290 private void computeStatistics() {
294 for (double t : thrust) {
301 double thrustLimit = maxThrust * MARGINAL_THRUST;
302 double burnStart, burnEnd;
305 for (pos = 1; pos < thrust.length; pos++) {
306 if (thrust[pos] >= thrustLimit)
309 if (pos >= thrust.length) {
310 throw new BugException("Could not compute burn start time, maxThrust=" + maxThrust +
311 " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
313 if (MathUtil.equals(thrust[pos - 1], thrust[pos])) {
315 burnStart = (time[pos - 1] + time[pos]) / 2;
317 burnStart = MathUtil.map(thrustLimit, thrust[pos - 1], thrust[pos], time[pos - 1], time[pos]);
322 for (pos = thrust.length - 2; pos >= 0; pos--) {
323 if (thrust[pos] >= thrustLimit)
327 throw new BugException("Could not compute burn end time, maxThrust=" + maxThrust +
328 " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
330 if (MathUtil.equals(thrust[pos], thrust[pos + 1])) {
332 burnEnd = (time[pos] + time[pos + 1]) / 2;
334 burnEnd = MathUtil.map(thrustLimit, thrust[pos], thrust[pos + 1],
335 time[pos], time[pos + 1]);
340 burnTime = Math.max(burnEnd - burnStart, 0);
343 // Total impulse and average thrust
347 for (pos = 0; pos < time.length - 1; pos++) {
348 double t0 = time[pos];
349 double t1 = time[pos + 1];
350 double f0 = thrust[pos];
351 double f1 = thrust[pos + 1];
353 totalImpulse += (t1 - t0) * (f0 + f1) / 2;
355 if (t0 < burnStart && t1 > burnStart) {
356 double fStart = MathUtil.map(burnStart, t0, t1, f0, f1);
357 averageThrust += (fStart + f1) / 2 * (t1 - burnStart);
358 } else if (t0 >= burnStart && t1 <= burnEnd) {
359 averageThrust += (f0 + f1) / 2 * (t1 - t0);
360 } else if (t0 < burnEnd && t1 > burnEnd) {
361 double fEnd = MathUtil.map(burnEnd, t0, t1, f0, f1);
362 averageThrust += (f0 + fEnd) / 2 * (burnEnd - t0);
367 averageThrust /= burnTime;
375 ////////// Static methods
378 * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
381 * @param delay the delay time.
382 * @return the <code>String</code> representation.
384 public static String getDelayString(double delay) {
385 return getDelayString(delay, "P");
389 * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
390 * <code>plugged</code> is returned.
392 * @param delay the delay time.
393 * @param plugged the return value if there is no ejection charge.
394 * @return the String representation.
396 public static String getDelayString(double delay, String plugged) {
397 if (delay == PLUGGED)
399 delay = Math.rint(delay * 10) / 10;
400 if (MathUtil.equals(delay, Math.rint(delay)))
401 return "" + ((int) delay);
407 //////// Motor instance implementation ////////
408 private class ThrustCurveMotorInstance implements MotorInstance {
410 private int position;
412 // Previous time step value
413 private double prevTime;
415 // Average thrust during previous step
416 private double stepThrust;
417 // Instantaneous thrust at current time point
418 private double instThrust;
420 // Average CG during previous step
421 private Coordinate stepCG;
422 // Instantaneous CG at current time point
423 private Coordinate instCG;
425 private final double unitRotationalInertia;
426 private final double unitLongitudinalInertia;
427 private final Motor parentMotor;
429 private int modID = 0;
431 public ThrustCurveMotorInstance() {
432 log.debug("ThrustCurveMotor: Creating motor instance of " + ThrustCurveMotor.this);
439 unitRotationalInertia = Inertia.filledCylinderRotational(getDiameter() / 2);
440 unitLongitudinalInertia = Inertia.filledCylinderLongitudinal(getDiameter() / 2, getLength());
441 parentMotor = ThrustCurveMotor.this;
445 public Motor getParentMotor(){
450 public double getTime() {
455 public Coordinate getCG() {
460 public double getLongitudinalInertia() {
461 return unitLongitudinalInertia * stepCG.weight;
465 public double getRotationalInertia() {
466 return unitRotationalInertia * stepCG.weight;
470 public double getThrust() {
475 public boolean isActive() {
476 return prevTime < time[time.length - 1];
480 public void step(double nextTime, double acceleration, AtmosphericConditions cond) {
482 if (!(nextTime >= prevTime)) {
484 throw new IllegalArgumentException("Stepping backwards in time, current=" +
485 prevTime + " new=" + nextTime);
487 if (MathUtil.equals(prevTime, nextTime)) {
493 if (position >= time.length - 1) {
498 stepCG = cg[cg.length - 1];
503 // Compute average & instantaneous thrust
504 if (nextTime < time[position + 1]) {
506 // Time step between time points
507 double nextF = MathUtil.map(nextTime, time[position], time[position + 1],
508 thrust[position], thrust[position + 1]);
509 stepThrust = (instThrust + nextF) / 2;
514 // Portion of previous step
515 stepThrust = (instThrust + thrust[position + 1]) / 2 * (time[position + 1] - prevTime);
519 while ((position < time.length - 1) && (nextTime >= time[position + 1])) {
520 stepThrust += (thrust[position] + thrust[position + 1]) / 2 *
521 (time[position + 1] - time[position]);
526 if (position < time.length - 1) {
527 instThrust = MathUtil.map(nextTime, time[position], time[position + 1],
528 thrust[position], thrust[position + 1]);
529 stepThrust += (thrust[position] + instThrust) / 2 *
530 (nextTime - time[position]);
532 // Thrust ended during this step
536 stepThrust /= (nextTime - prevTime);
540 // Compute average and instantaneous CG (simple average between points)
542 if (position < time.length - 1) {
543 nextCG = MathUtil.map(nextTime, time[position], time[position + 1],
544 cg[position], cg[position + 1]);
546 nextCG = cg[cg.length - 1];
548 stepCG = instCG.add(nextCG).multiply(0.5);
556 public MotorInstance clone() {
558 return (MotorInstance) super.clone();
559 } catch (CloneNotSupportedException e) {
560 throw new BugException("CloneNotSupportedException", e);
565 public int getModID() {
573 public int compareTo(ThrustCurveMotor other) {
578 value = COLLATOR.compare(this.manufacturer.getDisplayName(),
579 ((ThrustCurveMotor) other).manufacturer.getDisplayName());
584 value = DESIGNATION_COMPARATOR.compare(this.getDesignation(), other.getDesignation());
589 value = (int) ((this.getDiameter() - other.getDiameter()) * 1000000);
594 value = (int) ((this.getLength() - other.getLength()) * 1000000);