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.BugException;
11 import net.sf.openrocket.util.Coordinate;
12 import net.sf.openrocket.util.Inertia;
13 import net.sf.openrocket.util.MathUtil;
16 public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
17 private static final LogHelper log = Application.getLogger();
19 public static final double MAX_THRUST = 10e6;
22 private static final Collator COLLATOR = Collator.getInstance(Locale.US);
24 COLLATOR.setStrength(Collator.PRIMARY);
26 private static final DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
28 private final String digest;
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;
41 private double maxThrust;
42 private double burnTime;
43 private double averageThrust;
44 private double totalImpulse;
48 * Sole constructor. Sets all the properties of the motor.
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.
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) {
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 +
71 if (time.length < 2) {
72 throw new IllegalArgumentException("Too short thrust-curve, length=" +
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]);
82 if (!MathUtil.equals(time[0], 0)) {
83 throw new IllegalArgumentException("Curve starts at time " + time[0]);
85 if (!MathUtil.equals(thrust[0], 0)) {
86 throw new IllegalArgumentException("Curve starts at thrust " + thrust[0]);
88 if (!MathUtil.equals(thrust[thrust.length - 1], 0)) {
89 throw new IllegalArgumentException("Curve ends at thrust " +
90 thrust[thrust.length - 1]);
92 for (double t : thrust) {
94 throw new IllegalArgumentException("Negative thrust.");
96 if (t > MAX_THRUST || Double.isNaN(t)) {
97 throw new IllegalArgumentException("Invalid thrust " + t);
100 for (Coordinate c : cg) {
102 throw new IllegalArgumentException("Invalid CG " + c);
104 if (c.x < 0 || c.x > length) {
105 throw new IllegalArgumentException("Invalid CG position " + c.x);
108 throw new IllegalArgumentException("Negative mass " + c.weight);
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);
118 this.manufacturer = manufacturer;
119 this.designation = designation;
120 this.description = description;
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();
135 * Get the manufacturer of this motor.
137 * @return the manufacturer
139 public Manufacturer getManufacturer() {
145 * Return the array of time points for this thrust curve.
146 * @return an array of time points where the thrust is sampled
148 public double[] getTimePoints() {
153 * Returns the array of thrust points for this thrust curve.
154 * @return an array of thrust samples
156 public double[] getThrustPoints() {
157 return thrust.clone();
161 * Returns the array of CG points for this thrust curve.
162 * @return an array of CG samples
164 public Coordinate[] getCGPoints() {
169 * Return a list of standard delays defined for this motor.
170 * @return a list of standard delays
172 public double[] getStandardDelays() {
173 return delays.clone();
180 * NOTE: In most cases you want to examine the motor type of the ThrustCurveMotorSet,
181 * not the ThrustCurveMotor itself.
184 public Type getMotorType() {
190 public String getDesignation() {
195 public String getDesignation(double delay) {
196 return designation + "-" + getDelayString(delay);
201 public String getDescription() {
206 public double getDiameter() {
211 public double getLength() {
217 public MotorInstance getInstance() {
218 return new ThrustCurveMotorInstance();
223 public Coordinate getLaunchCG() {
228 public Coordinate getEmptyCG() {
229 return cg[cg.length - 1];
236 public double getBurnTimeEstimate() {
241 public double getAverageThrustEstimate() {
242 return averageThrust;
246 public double getMaxThrustEstimate() {
251 public double getTotalImpulseEstimate() {
256 public String getDigest() {
262 * Compute the general statistics of this motor.
264 private void computeStatistics() {
268 for (double t : thrust) {
275 double thrustLimit = maxThrust * MARGINAL_THRUST;
276 double burnStart, burnEnd;
279 for (pos = 1; pos < thrust.length; pos++) {
280 if (thrust[pos] >= thrustLimit)
283 if (pos >= thrust.length) {
284 throw new BugException("Could not compute burn start time, maxThrust=" + maxThrust +
285 " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
287 if (MathUtil.equals(thrust[pos - 1], thrust[pos])) {
289 burnStart = (time[pos - 1] + time[pos]) / 2;
291 burnStart = MathUtil.map(thrustLimit, thrust[pos - 1], thrust[pos], time[pos - 1], time[pos]);
296 for (pos = thrust.length - 2; pos >= 0; pos--) {
297 if (thrust[pos] >= thrustLimit)
301 throw new BugException("Could not compute burn end time, maxThrust=" + maxThrust +
302 " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
304 if (MathUtil.equals(thrust[pos], thrust[pos + 1])) {
306 burnEnd = (time[pos] + time[pos + 1]) / 2;
308 burnEnd = MathUtil.map(thrustLimit, thrust[pos], thrust[pos + 1],
309 time[pos], time[pos + 1]);
314 burnTime = Math.max(burnEnd - burnStart, 0);
317 // Total impulse and average thrust
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];
327 totalImpulse += (t1 - t0) * (f0 + f1) / 2;
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);
341 averageThrust /= burnTime;
349 ////////// Static methods
352 * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
355 * @param delay the delay time.
356 * @return the <code>String</code> representation.
358 public static String getDelayString(double delay) {
359 return getDelayString(delay, "P");
363 * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
364 * <code>plugged</code> is returned.
366 * @param delay the delay time.
367 * @param plugged the return value if there is no ejection charge.
368 * @return the String representation.
370 public static String getDelayString(double delay, String plugged) {
371 if (delay == PLUGGED)
373 delay = Math.rint(delay * 10) / 10;
374 if (MathUtil.equals(delay, Math.rint(delay)))
375 return "" + ((int) delay);
381 //////// Motor instance implementation ////////
382 private class ThrustCurveMotorInstance implements MotorInstance {
384 private int position;
386 // Previous time step value
387 private double prevTime;
389 // Average thrust during previous step
390 private double stepThrust;
391 // Instantaneous thrust at current time point
392 private double instThrust;
394 // Average CG during previous step
395 private Coordinate stepCG;
396 // Instantaneous CG at current time point
397 private Coordinate instCG;
399 private final double unitRotationalInertia;
400 private final double unitLongitudinalInertia;
402 private int modID = 0;
404 public ThrustCurveMotorInstance() {
405 log.debug("ThrustCurveMotor: Creating motor instance of " + ThrustCurveMotor.this);
412 unitRotationalInertia = Inertia.filledCylinderRotational(getDiameter() / 2);
413 unitLongitudinalInertia = Inertia.filledCylinderLongitudinal(getDiameter() / 2, getLength());
417 public double getTime() {
422 public Coordinate getCG() {
427 public double getLongitudinalInertia() {
428 return unitLongitudinalInertia * stepCG.weight;
432 public double getRotationalInertia() {
433 return unitRotationalInertia * stepCG.weight;
437 public double getThrust() {
442 public boolean isActive() {
443 return prevTime < time[time.length - 1];
447 public void step(double nextTime, double acceleration, AtmosphericConditions cond) {
449 if (!(nextTime >= prevTime)) {
451 throw new IllegalArgumentException("Stepping backwards in time, current=" +
452 prevTime + " new=" + nextTime);
454 if (MathUtil.equals(prevTime, nextTime)) {
460 if (position >= time.length - 1) {
465 stepCG = cg[cg.length - 1];
470 // Compute average & instantaneous thrust
471 if (nextTime < time[position + 1]) {
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;
481 // Portion of previous step
482 stepThrust = (instThrust + thrust[position + 1]) / 2 * (time[position + 1] - prevTime);
486 while ((position < time.length - 1) && (nextTime >= time[position + 1])) {
487 stepThrust += (thrust[position] + thrust[position + 1]) / 2 *
488 (time[position + 1] - time[position]);
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]);
499 // Thrust ended during this step
503 stepThrust /= (nextTime - prevTime);
507 // Compute average and instantaneous CG (simple average between points)
509 if (position < time.length - 1) {
510 nextCG = MathUtil.map(nextTime, time[position], time[position + 1],
511 cg[position], cg[position + 1]);
513 nextCG = cg[cg.length - 1];
515 stepCG = instCG.add(nextCG).multiply(0.5);
523 public MotorInstance clone() {
525 return (MotorInstance) super.clone();
526 } catch (CloneNotSupportedException e) {
527 throw new BugException("CloneNotSupportedException", e);
532 public int getModID() {
540 public int compareTo(ThrustCurveMotor other) {
545 value = COLLATOR.compare(this.manufacturer.getDisplayName(),
546 ((ThrustCurveMotor) other).manufacturer.getDisplayName());
551 value = DESIGNATION_COMPARATOR.compare(this.getDesignation(), other.getDesignation());
556 value = (int) ((this.getDiameter() - other.getDiameter()) * 1000000);
561 value = (int) ((this.getLength() - other.getLength()) * 1000000);