Modified thrustcurve serialization to be more robust in the case of

some common (but generally not important) errors in thrustcurves, and
not enter thrustcurves that will cause simulation failures into the
database.

(1) Permits thrustcurves to start at time 0 with a nonzero thrust.
Previous code inserted a datapoint with time 0 and thrust 0; this
caused thrust interpolation code to fail while running a simulation.
If the first datapoint is at a time greater than 0 it does enter a
(0, 0) datapoint, as this is the correct documented behavior for RASP
files.

(2) Permits thrustcurves to end with a nonzero thrust.  Similarly to
the previous case, the old code entered a new datapoint at the same
time as the previous end of thrust with thrust 0, which would break
thrust interpolation.

(3) Thrustcurves that have a zero-thrust datapoint and a non-zero
thrust datapoint at time 0 have the first datapoint removed.

(4) Thrustcurves with more serious errors -- mainly multiple thrusts
at a single time -- aren't entered into the database, as they will
break thrust interpolation and there is no obvious way to guess which
of the datapoints is correct, or to see how to average them.
This commit is contained in:
JoePfeiffer 2019-02-09 18:00:30 -07:00
parent 1abf0d3834
commit 6c535bff94
7 changed files with 168 additions and 95 deletions

View File

@ -179,7 +179,14 @@ public abstract class AbstractMotorLoader implements MotorLoader {
return; return;
// Start // Start
if (!MathUtil.equals(time.get(0), 0) || !MathUtil.equals(thrust.get(0), 0)) { // If there is no datapoint at t=0, put one there (this is the
// normal case for a RASP file). If there is a nonzero thrust
// at time 0 it's an error, but not one that calls for not
// using the file. We *don't* want to also put a 0-thrust
// point at time 0 in that case, as that will cause the
// simulation to throw an exception just like in the
// commented-out case below.
if (!MathUtil.equals(time.get(0), 0)) {
time.add(0, 0.0); time.add(0, 0.0);
thrust.add(0, 0.0); thrust.add(0, 0.0);
for (List l : lists) { for (List l : lists) {
@ -188,16 +195,32 @@ public abstract class AbstractMotorLoader implements MotorLoader {
} }
} }
// Not-uncommon issue at start of thrust curves: two points
// for t=0, one with thrust zero and one non-zero. We'll throw
// out the 0-thrust point and go on.
if (MathUtil.equals(time.get(0), 0) && MathUtil.equals(time.get(1), 0)) {
time.remove(0);
thrust.remove(0);
}
// End // End
int n = time.size() - 1; // Ah, no, we don't want to do this (I'm leaving the dead code
if (!MathUtil.equals(thrust.get(n), 0)) { // in case there's a temptation to put it back in). This ends
time.add(time.get(n)); // up putting the new 0-thrust point at the same time as the
thrust.add(0.0); // previous last datapoint, which will cause
for (List l : lists) { // ThrustCurveMotor.getAverageThrust() to fail when it tries
Object o = l.get(n); // to interpolate (the exception is actually thrown by
l.add(o); // MathUtil.map())
} //
} // int n = time.size() - 1;
// if (!MathUtil.equals(thrust.get(n), 0)) {
// time.add(time.get(n));
// thrust.add(0.0);
// for (List l : lists) {
// Object o = l.get(n);
// l.add(o);
// }
// }
} }
} }

View File

@ -166,22 +166,30 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
throw new IllegalArgumentException("Too short thrust-curve, length=" + motor.time.length); throw new IllegalArgumentException("Too short thrust-curve, length=" + motor.time.length);
} }
for (int i = 0; i < motor.time.length - 1; i++) { for (int i = 0; i < motor.time.length - 1; i++) {
if (motor.time[i + 1] < motor.time[i]) { if (motor.time[i + 1] <= motor.time[i]) {
throw new IllegalArgumentException("Time goes backwards, " + throw new IllegalArgumentException("Time stalls or goes backwards, " +
"time[" + i + "]=" + motor.time[i] + " " + "time[" + i + "]=" + motor.time[i] + " " +
"time[" + (i + 1) + "]=" + motor.time[i + 1]); "time[" + (i + 1) + "]=" + motor.time[i + 1] +
", thrust=(" + motor.thrust[i] + ", " + motor.thrust[i+1] + ")");
} }
} }
if (!MathUtil.equals(motor.time[0], 0)) { if (!MathUtil.equals(motor.time[0], 0)) {
throw new IllegalArgumentException("Curve starts at time " + motor.time[0]); throw new IllegalArgumentException("Curve starts at time " + motor.time[0]);
} }
if (!MathUtil.equals(motor.thrust[0], 0)) {
throw new IllegalArgumentException("Curve starts at thrust " + motor.thrust[0]); // these conditions actually are error, but quite a few of
} // the files on thrustcurvemotor.org have one or the
if (!MathUtil.equals(motor.thrust[motor.thrust.length - 1], 0)) { // other of them, and they make less of a difference to
throw new IllegalArgumentException("Curve ends at thrust " + // the simulation result than the normal variation between motors.
motor.thrust[motor.thrust.length - 1]); // if (!MathUtil.equals(motor.thrust[0], 0)) {
} // throw new IllegalArgumentException("Curve starts at thrust " + motor.thrust[0]);
// }
//
// if (!MathUtil.equals(motor.thrust[motor.thrust.length - 1], 0)) {
// throw new IllegalArgumentException("Curve ends at thrust " +
// motor.thrust[motor.thrust.length - 1]);
//}
for (double t : motor.thrust) { for (double t : motor.thrust) {
if (t < 0) { if (t < 0) {
throw new IllegalArgumentException("Negative thrust."); throw new IllegalArgumentException("Negative thrust.");
@ -630,45 +638,51 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
maxThrust = t; maxThrust = t;
} }
// Burn start time // Burn start time
double thrustLimit = maxThrust * MARGINAL_THRUST; double thrustLimit = maxThrust * MARGINAL_THRUST;
double burnStart, burnEnd; double burnStart, burnEnd;
int pos; if (thrust[0] >= thrustLimit)
for (pos = 1; pos < thrust.length; pos++) { burnStart = time[0];
if (thrust[pos] >= thrustLimit) else {
int startPos;
for (startPos = 1; startPos < thrust.length; startPos++) {
if (thrust[startPos] >= thrustLimit)
break; break;
} }
if (pos >= thrust.length) { if (startPos >= thrust.length) {
throw new BugException("Could not compute burn start time, maxThrust=" + maxThrust + throw new BugException("Could not compute burn start time, maxThrust=" + maxThrust +
" limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust)); " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
} }
if (MathUtil.equals(thrust[pos - 1], thrust[pos])) { if (MathUtil.equals(thrust[startPos - 1], thrust[startPos])) {
// For safety // For safety
burnStart = (time[pos - 1] + time[pos]) / 2; burnStart = (time[startPos - 1] + time[startPos]) / 2;
} else { } else {
burnStart = MathUtil.map(thrustLimit, thrust[pos - 1], thrust[pos], time[pos - 1], time[pos]); burnStart = MathUtil.map(thrustLimit, thrust[startPos - 1], thrust[startPos], time[startPos - 1], time[startPos]);
}
} }
// Burn end time // Burn end time
for (pos = thrust.length - 2; pos >= 0; pos--) { if (thrust[thrust.length-1] >= thrustLimit)
if (thrust[pos] >= thrustLimit) burnEnd = time[time.length-1];
else {
int endPos;
for (endPos = thrust.length - 2; endPos >= 0; endPos--) {
if (thrust[endPos] >= thrustLimit)
break; break;
} }
if (pos < 0) { if (endPos < 0) {
throw new BugException("Could not compute burn end time, maxThrust=" + maxThrust + throw new BugException("Could not compute burn end time, maxThrust=" + maxThrust +
" limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust)); " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
} }
if (MathUtil.equals(thrust[pos], thrust[pos + 1])) { if (MathUtil.equals(thrust[endPos], thrust[endPos + 1])) {
// For safety // For safety
burnEnd = (time[pos] + time[pos + 1]) / 2; burnEnd = (time[endPos] + time[endPos + 1]) / 2;
} else { } else {
burnEnd = MathUtil.map(thrustLimit, thrust[pos], thrust[pos + 1], burnEnd = MathUtil.map(thrustLimit, thrust[endPos], thrust[endPos + 1],
time[pos], time[pos + 1]); time[endPos], time[endPos + 1]);
}
} }
// Burn time // Burn time
burnTimeEstimate = Math.max(burnEnd - burnStart, 0); burnTimeEstimate = Math.max(burnEnd - burnStart, 0);
@ -677,12 +691,12 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
// Total impulse and average thrust // Total impulse and average thrust
totalImpulse = 0; totalImpulse = 0;
averageThrust = 0; averageThrust = 0;
int impulsePos;
for (pos = 0; pos < time.length - 1; pos++) { for (impulsePos = 0; impulsePos < time.length - 1; impulsePos++) {
double t0 = time[pos]; double t0 = time[impulsePos];
double t1 = time[pos + 1]; double t1 = time[impulsePos + 1];
double f0 = thrust[pos]; double f0 = thrust[impulsePos];
double f1 = thrust[pos + 1]; double f1 = thrust[impulsePos + 1];
totalImpulse += (t1 - t0) * (f0 + f1) / 2; totalImpulse += (t1 - t0) * (f0 + f1) / 2;

View File

@ -57,6 +57,8 @@ public class DownloadResponseParser implements ElementHandler {
response.add(motorBurnFile); response.add(motorBurnFile);
} else if (motor_id_tag.equals(element)) { } else if (motor_id_tag.equals(element)) {
motorBurnFile.setMotorId(Integer.parseInt(content)); motorBurnFile.setMotorId(Integer.parseInt(content));
} else if (simfile_id_tag.equals(element)) {
motorBurnFile.setSimfileId(Integer.parseInt(content));
} else if (format_tag.equals(element)) { } else if (format_tag.equals(element)) {
motorBurnFile.setFiletype(content); motorBurnFile.setFiletype(content);
} else if (data_tag.equals(element)) { } else if (data_tag.equals(element)) {

View File

@ -11,8 +11,10 @@ import net.sf.openrocket.motor.ThrustCurveMotor;
public class MotorBurnFile { public class MotorBurnFile {
private Integer motorId; private Integer motorId;
private Integer simfileId;
private String filetype; private String filetype;
private ThrustCurveMotor.Builder thrustCurveMotor; private ThrustCurveMotor.Builder thrustCurveMotor;
private String data;
public void init() { public void init() {
this.motorId = null; this.motorId = null;
@ -29,8 +31,9 @@ public class MotorBurnFile {
return clone; return clone;
} }
public void decodeFile(String data) throws IOException { public void decodeFile(String _data) throws IOException {
data = Base64Decoder.decodeData(data); _data = Base64Decoder.decodeData(_data);
data = _data;
try { try {
if (SupportedFileTypes.RASP_FORMAT.equals(filetype)) { if (SupportedFileTypes.RASP_FORMAT.equals(filetype)) {
RASPMotorLoader loader = new RASPMotorLoader(); RASPMotorLoader loader = new RASPMotorLoader();
@ -47,19 +50,33 @@ public class MotorBurnFile {
} }
/** /**
* @return the motor_id * @return the motor id
*/ */
public Integer getMotorId() { public Integer getMotorId() {
return motorId; return motorId;
} }
/** /**
* @param motor_id the motor_id to set * @param motorId the motor id to set
*/ */
public void setMotorId(Integer motorId) { public void setMotorId(Integer motorId) {
this.motorId = motorId; this.motorId = motorId;
} }
/**
* @return the simfile id
*/
public Integer getSimfileId() {
return simfileId;
}
/**
* @param simfileId the simfileId to set
*/
public void setSimfileId(Integer simfileId) {
this.simfileId = simfileId;
}
/** /**
* @return the filetype * @return the filetype
*/ */
@ -81,4 +98,11 @@ public class MotorBurnFile {
return thrustCurveMotor; return thrustCurveMotor;
} }
/**
* @return the file contents
*/
public String getContents() {
return data;
}
} }

View File

@ -115,9 +115,8 @@ public class SerializeThrustcurveMotors {
System.out.println(message); System.out.println(message);
List<MotorBurnFile> b = getThrustCurvesForMotorId(mi.getMotor_id()); List<MotorBurnFile> b = getThrustCurvesForMotorId(mi.getMotor_id());
for (MotorBurnFile burnFile : b) { for (MotorBurnFile burnFile : b) {
try {
ThrustCurveMotor.Builder builder = burnFile.getThrustCurveMotor(); ThrustCurveMotor.Builder builder = burnFile.getThrustCurveMotor();
if (builder == null) { if (builder == null) {
continue; continue;
@ -145,6 +144,16 @@ public class SerializeThrustcurveMotors {
} }
allMotors.add(builder.build()); allMotors.add(builder.build());
} catch (IllegalArgumentException e) {
System.out.println("\tError in simFile " + burnFile.getSimfileId() + ": " + e.getMessage() + " (continuing)");
try {
FileOutputStream out = new FileOutputStream(("simfile-" + burnFile.getSimfileId()).toString());
out.write(burnFile.getContents().getBytes());
out.close();
} catch (IOException i) {
System.out.println("unable to write bad file: " + i.getMessage());
}
}
} }
@ -160,12 +169,12 @@ public class SerializeThrustcurveMotors {
try { try {
b.addAll(ThrustCurveAPI.downloadData(motorId, "RockSim")); b.addAll(ThrustCurveAPI.downloadData(motorId, "RockSim"));
} catch (Exception ex) { } catch (Exception ex) {
System.out.println("\tError downloading RockSim"); System.out.println("\tError downloading RockSim for motorID=" + motorId);
} }
try { try {
b.addAll(ThrustCurveAPI.downloadData(motorId, "RASP")); b.addAll(ThrustCurveAPI.downloadData(motorId, "RASP"));
} catch (Exception ex) { } catch (Exception ex) {
System.out.println("\tError downloading RASP"); System.out.println("\tError downloading RASP for motorID=" + motorId);
} }
return b; return b;
} }

1
openrocket.log Normal file
View File

@ -0,0 +1 @@
Error: Unable to access jarfile OpenRocket.jar