From: Bill Kuker Date: Sat, 7 Mar 2009 03:16:19 +0000 (+0000) Subject: Initial Checkin X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=7b4b14447a44c31481b2311bae2d59893f7a48a4;p=sw%2Fmotorsim Initial Checkin --- diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..dadcce8 --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..5fc6d6f --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + MotorSim + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/jcommon-1.0.15.jar b/jcommon-1.0.15.jar new file mode 100644 index 0000000..d0dc26d Binary files /dev/null and b/jcommon-1.0.15.jar differ diff --git a/jfreechart-1.0.12.jar b/jfreechart-1.0.12.jar new file mode 100644 index 0000000..73be90f Binary files /dev/null and b/jfreechart-1.0.12.jar differ diff --git a/jscience.jar b/jscience.jar new file mode 100644 index 0000000..52be1f1 Binary files /dev/null and b/jscience.jar differ diff --git a/log4j-1.2.15.jar b/log4j-1.2.15.jar new file mode 100644 index 0000000..c930a6a Binary files /dev/null and b/log4j-1.2.15.jar differ diff --git a/log4j.properties b/log4j.properties new file mode 100644 index 0000000..23a7afe --- /dev/null +++ b/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootCategory=INFO, O + +# Stdout +log4j.appender.O=org.apache.log4j.ConsoleAppender +log4j.appender.O.layout=org.apache.log4j.PatternLayout \ No newline at end of file diff --git a/src/Test.java b/src/Test.java new file mode 100644 index 0000000..f46a3c7 --- /dev/null +++ b/src/Test.java @@ -0,0 +1,16 @@ +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Length; +import javax.measure.unit.SI; + +import org.jscience.physics.amount.Amount; + + +public class Test { + public static void main(String args[]){ + Amount l = (Amount)Amount.valueOf(1, SI.METER); + System.out.println(l.divide(l).to(Dimensionless.UNIT)); + Amount m = (Amount)Amount.valueOf(1, 10000000000000000.0, SI.METER); + System.out.println(m); + + } +} diff --git a/src/com/billkuker/rocketry/motorsim/Burn.java b/src/com/billkuker/rocketry/motorsim/Burn.java new file mode 100644 index 0000000..b0958e9 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/Burn.java @@ -0,0 +1,262 @@ +package com.billkuker.rocketry.motorsim; + + + +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Duration; +import javax.measure.quantity.Force; +import javax.measure.quantity.Length; +import javax.measure.quantity.Mass; +import javax.measure.quantity.MassFlowRate; +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.Velocity; +import javax.measure.quantity.Volume; +import javax.measure.quantity.VolumetricDensity; +import javax.measure.unit.SI; + +import org.apache.log4j.Logger; +import org.jscience.physics.amount.Amount; +import org.jscience.physics.amount.Constants; + +import com.billkuker.rocketry.motorsim.fuel.KNSU; +import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain; +import com.billkuker.rocketry.motorsim.grain.ExtrudedGrain; +import com.billkuker.rocketry.motorsim.visual.Chart; + +public class Burn { + + private static Logger log = Logger.getLogger(Burn.class); + protected final Motor motor; + + private static final Amount atmosphereicPressure = Amount.valueOf(101000, SI.PASCAL); + + + private static double combustionEfficency = 0.97; + + private static double densityRatio = 0.96; + + protected class Interval{ + Amount time; + Amount regression; + Amount chamberPressure; + Amount chamberProduct; + Amount thrust; + + public String toString(){ + return time + " " + regression + " " + chamberPressure + " " + chamberProduct; + } + } + + protected SortedMap,Interval> data = new TreeMap, Interval>(); + + public Burn(Motor m){ + motor = m; + } + + private void burn(){ + log.info("Starting burn..."); + + Amount regStep = Amount.valueOf(0.0119904077, SI.MILLIMETER); + + //if ( motor.getGrain() instanceof Grain.DiscreteRegression ) + //regStep = ((Grain.DiscreteRegression)motor.getGrain()).optimalRegressionStep(); + + Interval initial = new Interval(); + initial.time = Amount.valueOf(0, SI.SECOND); + initial.regression = Amount.valueOf(0, SI.MILLIMETER); + initial.chamberPressure = atmosphereicPressure; + initial.chamberProduct = Amount.valueOf(0, SI.KILOGRAM); + initial.thrust = Amount.valueOf(0, SI.NEWTON); + + data.put(Amount.valueOf(0, SI.SECOND), initial); + + for ( int i = 0; i < 5000; i++ ){ + + Interval prev = data.get(data.lastKey()); + log.debug(prev); + log.debug("Step " + i + " =============================="); + Interval next = new Interval(); + + Amount burnRate = motor.getFuel().burnRate(prev.chamberPressure); + + log.debug("Burn Rate: " + burnRate); + + Amount dt = regStep.divide(burnRate).to(Duration.UNIT); + + data.put(data.lastKey().plus(dt), next); + + log.debug("Dt: " + dt); + + next.regression = prev.regression.plus(regStep); + + log.info("Regression: " + next.regression); + + next.time = prev.time.plus(dt); + + log.debug("Vold: " + motor.getGrain().volume(prev.regression).to(SI.MILLIMETER.pow(3))); + + log.debug("Vnew: " + motor.getGrain().volume(next.regression).to(SI.MILLIMETER.pow(3))); + + //TODO Amount volumeBurnt = motor.getGrain().volume(prev.regression).minus(motor.getGrain().volume(next.regression)); + Amount volumeBurnt = motor.getGrain().surfaceArea(prev.regression).times(regStep).to(Volume.UNIT); + + log.info("Volume Burnt: " + volumeBurnt.to(SI.MILLIMETER.pow(3))); + + Amount mGenRate = volumeBurnt.times(motor.getFuel().idealDensity().times(densityRatio)).divide(dt).to(MassFlowRate.UNIT); + + log.debug("Mass Gen Rate: " + mGenRate); + + Amount specificGasConstant = Constants.R.divide(motor.getFuel().getCombustionProduct().effectiveMolarWeight()); + Amount chamberTemp = motor.getFuel().getCombustionProduct().idealCombustionTemperature().times(combustionEfficency); + + Amount mNozzle; + { + Amount pDiff = prev.chamberPressure.minus(atmosphereicPressure); + + //pDiff = Amount.valueOf(.7342, MPA).minus(atmosphereicPressure); + + log.debug("Pdiff: " + pDiff); + + Amount aStar = motor.getNozzle().throatArea(); + + double k = motor.getFuel().getCombustionProduct().ratioOfSpecificHeats(); + + log.debug("K: " + k); + + double kSide = Math.sqrt(k) * Math.pow((2/(k+1)) , (((k+1)/2)/(k-1))); //Math.pow(2/k+1, (k+1)/(2*(k-1))); + + log.debug("K-Part: (good)" + kSide); + + + + //This unit conversion helps JScience to convert nozzle flow rate to + //kg/s a little later on I verified the conversion by hand and + //JScience checks it too. + specificGasConstant = specificGasConstant.to( + SI.METER.pow(2).divide(SI.SECOND.pow(2).times(SI.KELVIN))); + + log.debug("Specific Gas Constant: (good)" + specificGasConstant); + + Amount sqrtPart = specificGasConstant.times(chamberTemp).sqrt(); + + //Unit x = SI.JOULE.divide(SI.KILOGRAM).root(2); + + //sqrtPart = sqrtPart.times(Amount.valueOf(1, x)); + + log.debug("Square Root Part: " + sqrtPart); + + mNozzle = pDiff.times(aStar).times(kSide).divide(sqrtPart).to(MassFlowRate.UNIT); + + log.debug("Nozzle Flow: " + mNozzle); + + log.debug("Nozzle Flow: " + mNozzle.to(MassFlowRate.UNIT)); + + + + + } + + Amount massStorageRate = mGenRate.minus(mNozzle); + + log.debug("Chamber Product rate: " + massStorageRate); + + next.chamberProduct = prev.chamberProduct.plus(massStorageRate.times(dt)); + + log.debug("Chamber Product: " + next.chamberProduct); + + Amount combustionProductDensity = next.chamberProduct.divide(motor.getChamber().chamberVolume().minus(motor.getGrain().volume(next.regression))).to(VolumetricDensity.UNIT); + + log.debug("Product Density: " + combustionProductDensity); + + next.chamberPressure = combustionProductDensity.times(specificGasConstant).times(chamberTemp).plus(atmosphereicPressure).to(Pressure.UNIT); + + next.chamberPressure = Amount.valueOf( + next.chamberPressure.doubleValue(SI.PASCAL), + SI.PASCAL); + + next.thrust = motor.getNozzle().thrust(next.chamberPressure, atmosphereicPressure, atmosphereicPressure, motor.getFuel().getCombustionProduct().ratioOfSpecificHeats2Phase()); + + if ( next.chamberPressure.approximates(atmosphereicPressure)){ + log.info("Pressure at Patm on step " + i); + break; + } + } + + } + + public Amount pressure(Amount time){ + return data.get(time).chamberPressure; + } + + public Amount thrust(Amount time){ + return data.get(time).thrust; + } + + public Amount kn(Amount regression){ + return motor.getGrain().surfaceArea(regression).divide(motor.getNozzle().throatArea()).to(Dimensionless.UNIT); + } + + public static void main( String args[]) throws Exception{ + Motor m = new Motor(); + m.setFuel(new KNSU()); + + CylindricalChamber c = new CylindricalChamber(); + c.setLength(Amount.valueOf(100, SI.MILLIMETER)); + c.setID(Amount.valueOf(30, SI.MILLIMETER)); + m.setChamber(c); + + CoredCylindricalGrain g = new CoredCylindricalGrain(); + g.setLength(Amount.valueOf(70, SI.MILLIMETER)); + g.setOD(Amount.valueOf(30, SI.MILLIMETER)); + g.setID(Amount.valueOf(10, SI.MILLIMETER)); + m.setGrain(g); + + m.setGrain(new ExtrudedGrain()); + + ConvergentDivergentNozzle n = new ConvergentDivergentNozzle(); + n.setThroatDiameter(Amount.valueOf(6.600, SI.MILLIMETER)); + n.setExitDiameter(Amount.valueOf(20.87, SI.MILLIMETER)); + n.setEfficiency(.87); + m.setNozzle(n); + + Burn b = new Burn(m); + + b.burn(); + + Chart r = new Chart( + SI.SECOND, + SI.MEGA(SI.PASCAL), + b, + "pressure"); + r.setDomain(b.data.keySet()); + r.show(); + + Chart t = new Chart( + SI.SECOND, + SI.NEWTON, + b, + "thrust"); + t.setDomain(b.data.keySet()); + t.show(); + + /* + Chart s = new Chart(SI.MILLIMETER, + SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea"); + s.setDomain(s.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g + .webThickness())); + s.show(); + */ + + Chart kn = new Chart(SI.MILLIMETER, + Dimensionless.UNIT, b, "kn"); + kn.setDomain(kn.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), m.getGrain() + .webThickness())); + kn.show(); + + } +} diff --git a/src/com/billkuker/rocketry/motorsim/Chamber.java b/src/com/billkuker/rocketry/motorsim/Chamber.java new file mode 100644 index 0000000..28c833e --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/Chamber.java @@ -0,0 +1,13 @@ +package com.billkuker.rocketry.motorsim; + +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Volume; + +import org.jscience.physics.amount.Amount; + +public interface Chamber { + public Amount chamberVolume(); + + public Amount burstPressure(); + +} diff --git a/src/com/billkuker/rocketry/motorsim/ConvergentDivergentNozzle.java b/src/com/billkuker/rocketry/motorsim/ConvergentDivergentNozzle.java new file mode 100644 index 0000000..c9978ed --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/ConvergentDivergentNozzle.java @@ -0,0 +1,82 @@ +package com.billkuker.rocketry.motorsim; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Force; +import javax.measure.quantity.Length; +import javax.measure.quantity.Pressure; + +import org.jscience.physics.amount.Amount; + +public class ConvergentDivergentNozzle implements Nozzle { + + private Amount throatDiameter; + + private Amount exitDiameter; + + private double efficiency = 1.0; + + + @Override + public Amount throatArea() { + return throatDiameter.divide(2).pow(2).times(Math.PI).to(Area.UNIT); + } + + public Amount exitArea() { + return exitDiameter.divide(2).pow(2).times(Math.PI).to(Area.UNIT); + } + + + public Amount getThroatDiameter() { + return throatDiameter; + } + + + public void setThroatDiameter(Amount throatDiameter) { + this.throatDiameter = throatDiameter; + } + + + public Amount getExitDiameter() { + return exitDiameter; + } + + + public void setExitDiameter(Amount exitDiameter) { + this.exitDiameter = exitDiameter; + } + + @Override + public Amount thrust(Amount Po, Amount Pe, Amount Patm, final double k ){ + double cF = thrustCoefficient(Po, Pe, Patm, k); + return Po.times(throatArea()).times(cF).to(Force.UNIT); + } + + public double thrustCoefficient(Amount Po, Amount Pe, Amount Patm, final double k ){ + double pRatio = Pe.divide(Po).to(Dimensionless.UNIT).doubleValue(Dimensionless.UNIT); + + double a = (2*k*k)/(k-1); + + double b = Math.pow(2/(k+1), (k+1)/(k-1) ); + + double c = 1.0 - Math.pow(pRatio, (k-1)/k); + + Amount pDiff = Pe.minus(Patm); + + double d = pDiff.times(exitArea()).divide(Po.times(throatArea())).to(Dimensionless.UNIT).doubleValue(Dimensionless.UNIT); + + double Cf = efficiency * Math.sqrt(a*b*c)+d; + + return Cf; + } + + public double getEfficiency() { + return efficiency; + } + + public void setEfficiency(double efficiency) { + this.efficiency = efficiency; + } + + +} diff --git a/src/com/billkuker/rocketry/motorsim/CylindricalChamber.java b/src/com/billkuker/rocketry/motorsim/CylindricalChamber.java new file mode 100644 index 0000000..cfac36d --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/CylindricalChamber.java @@ -0,0 +1,42 @@ +package com.billkuker.rocketry.motorsim; + +import javax.measure.quantity.Length; +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Volume; +import javax.measure.unit.SI; + +import org.jscience.physics.amount.Amount; + +public class CylindricalChamber implements Chamber { + + private Amount length; + + private Amount iD; + + @Override + public Amount burstPressure() { + return null; + } + + @Override + public Amount chamberVolume() { + return iD.divide(2).pow(2).times(Math.PI).times(length).to(SI.CUBIC_METRE); + } + + public Amount getLength() { + return length; + } + + public void setLength(Amount length) { + this.length = length; + } + + public Amount getID() { + return iD; + } + + public void setID(Amount id) { + iD = id; + } + +} diff --git a/src/com/billkuker/rocketry/motorsim/Fuel.java b/src/com/billkuker/rocketry/motorsim/Fuel.java new file mode 100644 index 0000000..976ebd9 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/Fuel.java @@ -0,0 +1,43 @@ +package com.billkuker.rocketry.motorsim; + +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.Velocity; +import javax.measure.quantity.VolumetricDensity; + +import org.jscience.physics.amount.Amount; + +import com.billkuker.rocketry.motorsim.RocketScience.MolarWeight; + +public interface Fuel { + + public Amount idealDensity(); + + public Amount burnRate(Amount pressure); + + public CombustionProduct getCombustionProduct(); + + public interface CombustionProduct { + public Amount idealCombustionTemperature(); + + public Amount effectiveMolarWeight(); + + public double ratioOfSpecificHeats(); + + public double ratioOfSpecificHeats2Phase(); + } + + /* + @Deprecated + public Amount idealCombustionTemperature(); + + @Deprecated + public Amount effectiveMolarWeight(); + + @Deprecated + public double ratioOfSpecificHeats(); + + @Deprecated + public double ratioOfSpecificHeats2Phase();*/ + +} diff --git a/src/com/billkuker/rocketry/motorsim/Grain.java b/src/com/billkuker/rocketry/motorsim/Grain.java new file mode 100644 index 0000000..8d36df3 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/Grain.java @@ -0,0 +1,27 @@ +package com.billkuker.rocketry.motorsim; + +import java.awt.Graphics2D; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Length; +import javax.measure.quantity.Volume; + +import org.jscience.physics.amount.Amount; + +public interface Grain { + + public interface Graphical{ + public void draw( Graphics2D g, Amount regression ); + } + + public interface DiscreteRegression{ + public Amount optimalRegressionStep(); + } + + public Amount surfaceArea(Amount regression); + + public Amount volume(Amount regression); + + public Amount webThickness(); + +} diff --git a/src/com/billkuker/rocketry/motorsim/Motor.java b/src/com/billkuker/rocketry/motorsim/Motor.java new file mode 100644 index 0000000..68a84ac --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/Motor.java @@ -0,0 +1,40 @@ +package com.billkuker.rocketry.motorsim; + +public class Motor { + private Chamber chamber; + private Grain grain; + private Nozzle nozzle; + private Fuel fuel; + + public Chamber getChamber() { + return chamber; + } + + public void setChamber(Chamber chamber) { + this.chamber = chamber; + } + + public Grain getGrain() { + return grain; + } + + public void setGrain(Grain grain) { + this.grain = grain; + } + + public Nozzle getNozzle() { + return nozzle; + } + + public void setNozzle(Nozzle nozzle) { + this.nozzle = nozzle; + } + + public Fuel getFuel() { + return fuel; + } + + public void setFuel(Fuel fuel) { + this.fuel = fuel; + } +} diff --git a/src/com/billkuker/rocketry/motorsim/Nozzle.java b/src/com/billkuker/rocketry/motorsim/Nozzle.java new file mode 100644 index 0000000..e11a76c --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/Nozzle.java @@ -0,0 +1,13 @@ +package com.billkuker.rocketry.motorsim; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Force; +import javax.measure.quantity.Pressure; + +import org.jscience.physics.amount.Amount; + +public interface Nozzle { + public Amount throatArea(); + + public Amount thrust(Amount Po, Amount Pe, Amount Patm, final double k ); +} diff --git a/src/com/billkuker/rocketry/motorsim/RocketScience.java b/src/com/billkuker/rocketry/motorsim/RocketScience.java new file mode 100644 index 0000000..0300923 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/RocketScience.java @@ -0,0 +1,16 @@ +package com.billkuker.rocketry.motorsim; + +import javax.measure.quantity.Quantity; +import javax.measure.unit.ProductUnit; +import javax.measure.unit.SI; +import javax.measure.unit.Unit; + +public class RocketScience { + + public interface MolarWeight extends Quantity { + public static final Unit UNIT = new ProductUnit( + SI.KILOGRAM.divide(SI.MOLE)); + } + + +} diff --git a/src/com/billkuker/rocketry/motorsim/fuel/KNSU.java b/src/com/billkuker/rocketry/motorsim/fuel/KNSU.java new file mode 100644 index 0000000..bcd0cf0 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/fuel/KNSU.java @@ -0,0 +1,59 @@ +package com.billkuker.rocketry.motorsim.fuel; + +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.VolumetricDensity; +import javax.measure.unit.SI; + +import org.jscience.physics.amount.Amount; + +import com.billkuker.rocketry.motorsim.RocketScience.MolarWeight; + +public class KNSU extends SaintRobertFuel { + + public KNSU(){ + super(Type.English); + } + + //@Override + public Amount idealDensity() { + //return Amount.valueOf(1.889, 0, SI.GRAM.divide(SI.CENTIMETER.pow(3))).to(VolumetricDensity.UNIT); + return Amount.valueOf(1889, 0, SI.KILOGRAM.divide(SI.METER.pow(3))).to(VolumetricDensity.UNIT); + } + + @Override + protected double burnrateCoefficient(Amount pressure) { + return 0.0665; + } + + @Override + protected double burnrateExponent(Amount pressure) { + return 0.319; + } + + public CombustionProduct getCombustionProduct(){ + return new CombustionProduct(){ + + @Override + public Amount idealCombustionTemperature() { + return Amount.valueOf(1720, SI.KELVIN); + } + + @Override + public Amount effectiveMolarWeight() { + return Amount.valueOf("41.98 kg/kmol").to(MolarWeight.UNIT); + } + + @Override + public double ratioOfSpecificHeats() { + return 1.133; + } + + @Override + public double ratioOfSpecificHeats2Phase() { + return 1.044; + } + }; + } + +} diff --git a/src/com/billkuker/rocketry/motorsim/fuel/SaintRobertFuel.java b/src/com/billkuker/rocketry/motorsim/fuel/SaintRobertFuel.java new file mode 100644 index 0000000..c6c3621 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/fuel/SaintRobertFuel.java @@ -0,0 +1,58 @@ +package com.billkuker.rocketry.motorsim.fuel; + +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Velocity; +import javax.measure.quantity.VolumetricDensity; +import javax.measure.unit.NonSI; +import javax.measure.unit.SI; +import javax.measure.unit.Unit; + +import org.jscience.physics.amount.Amount; + +import com.billkuker.rocketry.motorsim.Fuel; + +public abstract class SaintRobertFuel implements Fuel { + + protected enum Type{ + Si(SI.MILLIMETER.divide(SI.SECOND), SI.MEGA(SI.PASCAL)), + English(NonSI.INCH.divide(SI.SECOND), NonSI.POUND_FORCE.divide(NonSI.INCH.pow(2))) + ; + + private final Unit v; + private final Unit p; + + Type( Unit v, Unit p){ + this.p = p; + this.v = v; + } + } + + private Type t = Type.Si; + + public SaintRobertFuel(Type t){ + this.t = t; + } + + @Override + public Amount burnRate(Amount pressure) { + + + double p = pressure.doubleValue(t.p); + double a = burnrateCoefficient(pressure); + double n = burnrateExponent(pressure); + + return burnrateConstant().plus(Amount.valueOf( a*Math.pow(p,n), t.v )); + } + + protected abstract double burnrateCoefficient(Amount pressure); + + protected abstract double burnrateExponent(Amount pressure); + + protected Amount burnrateConstant(){ + return Amount.valueOf(0, Velocity.UNIT); + } + + @Override + public abstract Amount idealDensity(); + +} diff --git a/src/com/billkuker/rocketry/motorsim/grain/CoredCylindricalGrain.java b/src/com/billkuker/rocketry/motorsim/grain/CoredCylindricalGrain.java new file mode 100644 index 0000000..8727657 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/grain/CoredCylindricalGrain.java @@ -0,0 +1,162 @@ +package com.billkuker.rocketry.motorsim.grain; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Length; +import javax.measure.quantity.Volume; +import javax.measure.unit.SI; + +import org.jscience.physics.amount.Amount; + +import com.billkuker.rocketry.motorsim.Grain; +import com.billkuker.rocketry.motorsim.validation.Validating; +import com.billkuker.rocketry.motorsim.validation.ValidationException; + +public class CoredCylindricalGrain implements Grain, Validating { + + private Amount length, oD, iD; + private boolean oInh = true, iInh = false, eInh = false; + + public CoredCylindricalGrain() { + + } + + @Override + public Amount surfaceArea(Amount regression) { + Amount zero = Amount.valueOf(0, SI.MILLIMETER); + + //Calculated regressed length + Amount cLength = length; + if ( !eInh ){ + cLength = cLength.minus(regression.times(2)); + } + + //Calculate regressed iD + Amount cID = iD; + if ( !iInh ){ + cID = iD.plus(regression.times(2)); + } + + //Calculate regressed oD + Amount cOD = oD; + if ( !oInh ){ + cOD = oD.minus(regression.times(2)); + } + + if ( cID.isGreaterThan(cOD) ) + return Amount.valueOf(0, SI.SQUARE_METRE); + if ( cOD.isLessThan(cID) ) + return Amount.valueOf(0, SI.SQUARE_METRE); + if ( cLength.isLessThan(zero) ) + return Amount.valueOf(0, SI.SQUARE_METRE); + + Amount inner = cID.times(Math.PI).times(cLength).to(SI.SQUARE_METRE); + + Amount outer = cOD.times(Math.PI).times(cLength).to(SI.SQUARE_METRE); + + Amount ends = (cOD.divide(2).pow(2).times(Math.PI)).minus(cID.divide(2).pow(2).times(Math.PI)).times(2).to(SI.SQUARE_METRE); + + Amount total = inner.times(iInh?0:1).plus(outer.times(oInh?0:1)).plus(ends.times(eInh?0:1)); + + return total; + } + + @Override + public Amount volume(Amount regression) { + Amount zero = Amount.valueOf(0, SI.MILLIMETER); + + //Calculated regressed length + Amount cLength = length; + if ( !eInh ){ + cLength = cLength.minus(regression.times(2)); + } + + //Calculate regressed iD + Amount cID = iD; + if ( !iInh ){ + cID = iD.plus(regression.times(2)); + } + + //Calculate regressed oD + Amount cOD = oD; + if ( !oInh ){ + cOD = oD.minus(regression.times(2)); + } + + if ( cID.isGreaterThan(cOD) ) + return Amount.valueOf(0, SI.CUBIC_METRE); + if ( cOD.isLessThan(cID) ) + return Amount.valueOf(0, SI.CUBIC_METRE); + if ( cLength.isLessThan(zero) ) + return Amount.valueOf(0, SI.CUBIC_METRE); + + + Amount end = (cOD.divide(2).pow(2).times(Math.PI)).minus(cID.divide(2).pow(2).times(Math.PI)).to(SI.SQUARE_METRE); + + return end.times(cLength).to(SI.CUBIC_METRE); + } + + public void setLength(Amount length) { + this.length = length; + } + + public void setOD(Amount od) { + oD = od; + } + + public void setID(Amount id) { + iD = id; + } + + public void checkValidity() throws ValidationException{ + if ( iD.equals(Amount.ZERO) ) + throw new ValidationException(this, "Invalid iD"); + if ( oD.equals(Amount.ZERO) ) + throw new ValidationException(this, "Invalid oD"); + if ( length.equals(Amount.ZERO) ) + throw new ValidationException(this, "Invalid Length"); + if ( iD.isGreaterThan(oD) ) + throw new ValidationException(this, "iD > oD"); + + if ( iInh && oInh && eInh ) + throw new ValidationException(this, "No exposed grain surface"); + + } + + @Override + public Amount webThickness() { + Amount radial = null; + if ( !iInh && !oInh ) + radial = oD.minus(iD).divide(4); //Outer and inner exposed + else if ( !iInh || !oInh ) + radial = oD.minus(iD).divide(2); //Outer or inner exposed + + Amount axial = null; + + if ( !eInh ) + axial = length.divide(2); + + if ( axial == null ) + return radial; + if ( radial == null ) + return axial; + if ( radial.isLessThan(axial) ) + return radial; + return axial; + } + + public Amount getLength() { + return length; + } + + public Amount getOD() { + return oD; + } + + public Amount getID() { + return iD; + } + + + + +} diff --git a/src/com/billkuker/rocketry/motorsim/grain/ExtrudedGrain.java b/src/com/billkuker/rocketry/motorsim/grain/ExtrudedGrain.java new file mode 100644 index 0000000..70ae575 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/grain/ExtrudedGrain.java @@ -0,0 +1,311 @@ +package com.billkuker.rocketry.motorsim.grain; + +import java.awt.BasicStroke; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Length; +import javax.measure.quantity.Volume; +import javax.measure.unit.SI; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JSplitPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.jscience.physics.amount.Amount; + +import com.billkuker.rocketry.motorsim.Grain; +import com.billkuker.rocketry.motorsim.visual.Chart; + +public class ExtrudedGrain implements Grain, Grain.Graphical { + + Set plus = new HashSet(); + Set minus = new HashSet(); + Set inhibited = new HashSet(); + + Amount length = Amount.valueOf(25, SI.MILLIMETER); + + Amount rStep; + + private class RegEntry{ + Amount surfaceArea; + Amount volume; + } + + SortedMap, RegEntry> data = new TreeMap, RegEntry>(); + + Amount webThickness; + + { + /* Similar test grain*/ + Shape outside = new Ellipse2D.Double(50,50,30,30); + plus.add(outside); + minus.add(new Ellipse2D.Double(50,60,10,10)); + inhibited.add(outside); + length = Amount.valueOf(70, SI.MILLIMETER); + /*/ + + /*Big c-slot + Shape outside = new Ellipse2D.Double(0,0,30,30); + plus.add(outside); + inhibited.add(outside); + minus.add(new Rectangle2D.Double(12,12,6,20)); + length = Amount.valueOf(70, SI.MILLIMETER); + */ + + /*Plus sign + Shape outside = new Ellipse2D.Double(0,0,200,200); + plus.add(outside); + inhibited.add(outside); + minus.add(new Rectangle2D.Double(90,40,20,120)); + minus.add(new Rectangle2D.Double(40,90,120,20)); + */ + + findWebThickness(); + + fillInData(); + } + + @Override + public Amount surfaceArea(Amount regression) { + if ( regression.isGreaterThan(webThickness) ) + return Amount.valueOf(0, Area.UNIT); + Amount highKey = data.tailMap(regression).firstKey(); + Amount lowKey; + try{ + lowKey = data.headMap(regression).lastKey(); + } catch ( NoSuchElementException e ){ + return data.get(highKey).surfaceArea; + } + + double lp = regression.minus(lowKey).divide(highKey.minus(lowKey)).to(Dimensionless.UNIT).doubleValue(Dimensionless.UNIT); + + Amount lowVal = data.get(lowKey).surfaceArea; + Amount highVal = data.get(highKey).surfaceArea; + + return lowVal.times(1-lp).plus(highVal.times(lp)); + } + + @Override + public Amount volume(Amount regression) { + if ( regression.isGreaterThan(webThickness) ) + return Amount.valueOf(0, Volume.UNIT); + Amount highKey = data.tailMap(regression).firstKey(); + Amount lowKey; + try{ + lowKey = data.headMap(regression).lastKey(); + } catch ( NoSuchElementException e ){ + return data.get(highKey).volume; + } + + double lp = regression.minus(lowKey).divide(highKey.minus(lowKey)).to(Dimensionless.UNIT).doubleValue(Dimensionless.UNIT); + + Amount lowVal = data.get(lowKey).volume; + Amount highVal = data.get(highKey).volume; + + return lowVal.times(1-lp).plus(highVal.times(lp)); + } + + private void fillInData(){ + double max = webThickness().doubleValue(SI.MILLIMETER); + double delta = max / 5; + rStep = Amount.valueOf(delta, SI.MILLIMETER).divide(10); + for( double r = 0; r <= max+1; r += delta){ + RegEntry e = new RegEntry(); + Amount regression = Amount.valueOf(r, SI.MILLIMETER); + + + Amount rLen = length.minus(regression.times(2)); + if ( rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER))) + break; + + System.out.println("Calculating area for regression " + regression); + + java.awt.geom.Area burn = getArea(regression); + if ( burn.isEmpty() ) + break; + burn.subtract(getArea(regression.plus(Amount.valueOf(.001, SI.MILLIMETER)))); + + Amount xSection = crossSectionArea(getArea(regression)); + + e.volume = xSection.times(rLen).to(Volume.UNIT); + + e.surfaceArea = perimeter(burn).divide(2).times(rLen).plus(xSection.times(2)).to(Area.UNIT); + + data.put(regression, e); + + } + + RegEntry e = new RegEntry(); + e.surfaceArea = Amount.valueOf(0, Area.UNIT); + e.volume = Amount.valueOf(0, Volume.UNIT); + data.put( webThickness(), e ); + } + + private Amount crossSectionArea(java.awt.geom.Area a ) { + Rectangle r = a.getBounds(); + int cnt = 0; + int total = 0; + double e = .1; + for( double x = r.getMinX(); x < r.getMaxX(); x += e){ + for( double y = r.getMinY(); y < r.getMaxY(); y += e){ + if ( a.contains(x, y) ) + cnt++; + total++; + } + } + System.out.println(cnt + " out of " + total); + return Amount.valueOf(r.getWidth() * r.getHeight() * cnt / total, SI.MILLIMETER.pow(2)).to(Area.UNIT); + + } + + + + + + @Override + public Amount webThickness() { + return webThickness; + } + + private Amount perimeter(java.awt.geom.Area a){ + PathIterator i = a.getPathIterator(new AffineTransform(), .001); + double x=0, y=0; + double len = 0; + while ( !i.isDone() ){ + double coords[] = new double[6]; + int type = i.currentSegment(coords); + if ( type == PathIterator.SEG_LINETO ){ + //System.out.println("Line"); + double nx = coords[0]; + double ny = coords[1]; + //System.out.println(x+","+y+ " to " + nx+"," + ny); + len += Math.sqrt(Math.pow(x-nx, 2) + Math.pow(y-ny,2)); + x = nx; + y = ny; + } else if ( type == PathIterator.SEG_MOVETO ){ + //System.out.println("Move"); + x = coords[0]; + y = coords[1]; + } else { + //System.err.println("Got " + type); + } + i.next(); + } + return Amount.valueOf(len, SI.MILLIMETER); + } + + private void findWebThickness(){ + java.awt.geom.Area a = getArea(Amount.valueOf(0, SI.MILLIMETER)); + Rectangle r = a.getBounds(); + double max = r.getWidth() regression){ + java.awt.geom.Area a = new java.awt.geom.Area(); + for ( Shape s: plus ) + a.add(new java.awt.geom.Area(regress(s, regression.doubleValue(SI.MILLIMETER), true))); + for ( Shape s: minus ) + a.subtract(new java.awt.geom.Area(regress(s, regression.doubleValue(SI.MILLIMETER), false))); + return a; + } + + private Shape regress(Shape s, double mm, boolean plus){ + if ( inhibited.contains(s) ) + return s; + if ( s instanceof Ellipse2D ){ + Ellipse2D e = (Ellipse2D)s; + + double d = plus?-2*mm:2*mm; + + double w = e.getWidth() + d; + double h = e.getHeight() + d; + double x = e.getX() - d/2; + double y = e.getY() - d/2; + + return new Ellipse2D.Double(x,y,w,h); + } else if ( s instanceof Rectangle2D ){ + Rectangle2D r = (Rectangle2D)s; + + double d = plus?-2*mm:2*mm; + + double w = r.getWidth() + d; + double h = r.getHeight() + d; + double x = r.getX() - d/2; + double y = r.getY() - d/2; + + return new Rectangle2D.Double(x,y,w,h); + } + return null; + } + + + public static void main(String args[]) throws Exception { + ExtrudedGrain e = new ExtrudedGrain(); + new GrainPanel(e).show(); + + } + + @Override + public void draw(Graphics2D g2d, Amount regression) { + + java.awt.geom.Area reg = getArea(regression); + java.awt.geom.Area burn = getArea(regression); + burn.subtract(getArea(regression.plus(Amount.valueOf(.001, SI.MILLIMETER)))); + java.awt.geom.Area noreg = getArea(Amount.valueOf(0,SI.MILLIMETER)); + + Rectangle bounds = noreg.getBounds(); + g2d.scale(200/bounds.getWidth(), 200/bounds.getHeight()); + g2d.translate(-bounds.getX(), -bounds.getY()); + + + g2d.setStroke(new BasicStroke(0.5f)); + + g2d.setColor(Color.GRAY); + g2d.fill(reg); + g2d.setColor(Color.RED); + g2d.draw(burn); + g2d.setColor(Color.BLACK); + g2d.draw(noreg); + + } + + +} diff --git a/src/com/billkuker/rocketry/motorsim/grain/GrainPanel.java b/src/com/billkuker/rocketry/motorsim/grain/GrainPanel.java new file mode 100644 index 0000000..dd72821 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/grain/GrainPanel.java @@ -0,0 +1,130 @@ +package com.billkuker.rocketry.motorsim.grain; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Length; +import javax.measure.quantity.Volume; +import javax.measure.unit.SI; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JSplitPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.jscience.physics.amount.Amount; + +import com.billkuker.rocketry.motorsim.Grain; +import com.billkuker.rocketry.motorsim.visual.Chart; + +public class GrainPanel extends JPanel { + private static final long serialVersionUID = 1L; + private Amount displayedRegression = Amount.valueOf(0, SI.MILLIMETER); + private JLabel l = new JLabel(); + private Chart area; + Chart volume; + private XC xc; + private SL sl; + private Grain grain; + + public GrainPanel(Grain g){ + super(new BorderLayout()); + + grain = g; + + try { + + area = new Chart( + SI.MILLIMETER, + SI.MILLIMETER.pow(2).asType(Area.class), + grain, + "surfaceArea"); + area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness())); + + volume = new Chart( + SI.MILLIMETER, + SI.MILLIMETER.pow(3).asType(Volume.class), + grain, + "volume"); + volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness())); + + area.setMaximumSize(new Dimension(200,100)); + volume.setMaximumSize(new Dimension(200,100)); + + + } catch (ClassCastException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + JSplitPane charts = new JSplitPane(JSplitPane.VERTICAL_SPLIT, area, volume); + charts.setDividerLocation(.5); + + + if ( grain instanceof Grain.Graphical) + add(xc = new XC((Grain.Graphical)grain), BorderLayout.CENTER); + + JPanel left = new JPanel(new BorderLayout()); + left.add(xc); + left.add(l, BorderLayout.NORTH); + left.add( new SL(), BorderLayout.SOUTH); + + add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, charts)); + + } + + private class XC extends JPanel{ + private static final long serialVersionUID = 1L; + Grain.Graphical grain; + public XC(Grain.Graphical g){ + setMinimumSize(new Dimension(120,120)); + grain = g; + } + public void paint(Graphics g){ + super.paint(g); + Graphics2D g2d = (Graphics2D)g; + g2d.translate(10, 30); + grain.draw(g2d, displayedRegression ); + } + } + + private class SL extends JSlider implements ChangeListener{ + private static final long serialVersionUID = 1L; + private static final int STEPS = 20; + public SL(){ + addChangeListener(this); + setMinimum(0); + setMaximum(STEPS); + setValue(0); + } + + @Override + public void stateChanged(ChangeEvent e) { + double r = ((SL)e.getSource()).getValue(); + displayedRegression = grain.webThickness().divide(STEPS).times(r); + l.setText("Regression: " + displayedRegression); + area.mark(displayedRegression); + volume.mark(displayedRegression); + if ( xc != null ) + xc.repaint(); + } + } + + public void show(){ + JFrame f = new JFrame(); + f.setSize(1024,600); + f.setContentPane(this); + f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE); + f.setVisible(true); + } + +} diff --git a/src/com/billkuker/rocketry/motorsim/test/ConvergentDivergentNozzleTest.java b/src/com/billkuker/rocketry/motorsim/test/ConvergentDivergentNozzleTest.java new file mode 100644 index 0000000..df97885 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/test/ConvergentDivergentNozzleTest.java @@ -0,0 +1,69 @@ +package com.billkuker.rocketry.motorsim.test; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Force; +import javax.measure.quantity.Pressure; +import javax.measure.unit.SI; + +import junit.framework.Assert; + +import org.jscience.physics.amount.Amount; +import org.junit.Test; + +import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle; +import com.billkuker.rocketry.motorsim.fuel.KNSU; + +public class ConvergentDivergentNozzleTest extends RocketTest { + + ConvergentDivergentNozzle n = new ConvergentDivergentNozzle(); + + { + n.setThroatDiameter(Amount.valueOf(6.6, SI.MILLIMETER)); + n.setExitDiameter(Amount.valueOf(20.87, SI.MILLIMETER)); + n.setEfficiency(0.85); + } + + @Test + public void testThroatArea() { + Amount a = n.throatArea().to(SI.MILLIMETER.pow(2).asType(Area.class)); + assertApproximate(Amount.valueOf(34.2, .1, SI.MILLIMETER.pow(2)), a); + } + + @Test + public void testExitArea() { + Amount a = n.exitArea(); + assertApproximate(Amount.valueOf(342, .1, SI.MILLIMETER.pow(2)), a); + } + + @Test + public void testThrust() { + Amount Patm = Amount.valueOf(101000, SI.PASCAL); + Amount Po = Amount.valueOf(2046491, SI.PASCAL); + Amount Pe = Patm; + + KNSU f = new KNSU(); + + Amount t = n.thrust(Po, Pe, Patm, f.getCombustionProduct().ratioOfSpecificHeats2Phase()); + + Amount expected = Amount.valueOf(87.2, .1, SI.NEWTON); + + assertApproximate(t, expected); + + System.out.println(t); + } + + @Test + public void testThrustCoefficient() { + Amount Patm = Amount.valueOf(101000, SI.PASCAL); + Amount Po = Amount.valueOf(2046491, SI.PASCAL); + Amount Pe = Patm; + + KNSU f = new KNSU(); + + double cF = n.thrustCoefficient(Po, Pe, Patm, f.getCombustionProduct().ratioOfSpecificHeats2Phase()); + + + Assert.assertEquals(cF, 1.2454812344324655); + } + +} diff --git a/src/com/billkuker/rocketry/motorsim/test/CoredCylindricalGrainTest.java b/src/com/billkuker/rocketry/motorsim/test/CoredCylindricalGrainTest.java new file mode 100644 index 0000000..a2fddab --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/test/CoredCylindricalGrainTest.java @@ -0,0 +1,74 @@ +package com.billkuker.rocketry.motorsim.test; + +import javax.measure.quantity.Length; +import javax.measure.unit.SI; + +import org.jscience.physics.amount.Amount; +import org.junit.Test; + +import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain; +import com.billkuker.rocketry.motorsim.validation.ValidationException; + +public class CoredCylindricalGrainTest extends RocketTest { + + @Test + public void testSurfaceArea() { + CoredCylindricalGrain g = new CoredCylindricalGrain(); + + g.setLength(Amount.valueOf(70, SI.MILLIMETER)); + + g.setOD(Amount.valueOf(23.5, SI.MILLIMETER)); + + g.setID(Amount.valueOf(7.9375, SI.MILLIMETER)); + + for ( int mm = 0; mm < 15; mm++ ){ + Amount r = Amount.valueOf(mm, SI.MILLIMETER); + System.out.println( r + ", " + g.surfaceArea(r).to(SI.MILLIMETER.pow(2)) + ", " + g.volume(r) ); + } + } + + + @Test + public void testWebThickness() { + CoredCylindricalGrain g = new CoredCylindricalGrain(); + + //thin and long + g.setLength(Amount.valueOf(100, SI.MILLIMETER)); + g.setOD(Amount.valueOf(30, SI.MILLIMETER)); + g.setID(Amount.valueOf(10, SI.MILLIMETER)); + + assertApproximate(g.webThickness(), Amount.valueOf("10mm")); + + //thick and short + g.setLength(Amount.valueOf(100, SI.MILLIMETER)); + g.setOD(Amount.valueOf(300, SI.MILLIMETER)); + g.setID(Amount.valueOf(100, SI.MILLIMETER)); + + assertApproximate(g.webThickness(), Amount.valueOf("50mm")); + } + + @Test + public void testVolume() { + CoredCylindricalGrain g = new CoredCylindricalGrain(); + + //thin and long + g.setLength(Amount.valueOf(100, SI.MILLIMETER)); + g.setOD(Amount.valueOf(30, SI.MILLIMETER)); + g.setID(Amount.valueOf(10, SI.MILLIMETER)); + + System.out.println(g.volume(Amount.valueOf(0, SI.MILLIMETER))); + } + + @Test(expected=ValidationException.class) + public void testCheckValidity() throws ValidationException{ + CoredCylindricalGrain g = new CoredCylindricalGrain(); + + //thin and long + g.setLength(Amount.valueOf(100, SI.MILLIMETER)); + g.setOD(Amount.valueOf(10, SI.MILLIMETER)); + g.setID(Amount.valueOf(30, SI.MILLIMETER)); + + g.checkValidity(); + } + +} diff --git a/src/com/billkuker/rocketry/motorsim/test/CylindricalChamberTest.java b/src/com/billkuker/rocketry/motorsim/test/CylindricalChamberTest.java new file mode 100644 index 0000000..60600a7 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/test/CylindricalChamberTest.java @@ -0,0 +1,28 @@ +package com.billkuker.rocketry.motorsim.test; + +import javax.measure.unit.SI; + +import org.jscience.physics.amount.Amount; +import org.junit.Assert; +import org.junit.Test; + +import com.billkuker.rocketry.motorsim.CylindricalChamber; + +public class CylindricalChamberTest extends RocketTest { + + @Test + public void testBurstPressure() { + Assert.assertNull( new CylindricalChamber().burstPressure() ); + } + + @Test + public void testChamberVolume() { + CylindricalChamber c = new CylindricalChamber(); + + c.setLength(Amount.valueOf(100, SI.MILLIMETER)); + c.setID(Amount.valueOf(20, SI.MILLIMETER)); + + assertApproximate(c.chamberVolume(), Amount.valueOf(10000*Math.PI, SI.MILLIMETER.pow(3))); + } + +} diff --git a/src/com/billkuker/rocketry/motorsim/test/KNSUTest.java b/src/com/billkuker/rocketry/motorsim/test/KNSUTest.java new file mode 100644 index 0000000..917dc77 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/test/KNSUTest.java @@ -0,0 +1,30 @@ +package com.billkuker.rocketry.motorsim.test; + +import javax.measure.unit.NonSI; +import javax.measure.unit.SI; + +import org.jscience.physics.amount.Amount; +import org.junit.Test; + +import com.billkuker.rocketry.motorsim.fuel.KNSU; + +public class KNSUTest extends RocketTest { + + @Test + public void testBurnRate() { + KNSU f = new KNSU(); + + System.out.println(f.burnRate(Amount.valueOf(6.89, SI.MEGA(SI.PASCAL) )).to(NonSI.INCH.divide(SI.SECOND))); + } + + @Test + public void testEffectiveMolarWeight(){ + (new KNSU()).getCombustionProduct().effectiveMolarWeight(); + } + + @Test + public void testIdealDensity(){ + System.out.println((new KNSU()).idealDensity()); + System.out.println((new KNSU()).idealDensity().isExact()); + } +} diff --git a/src/com/billkuker/rocketry/motorsim/test/RocketTest.java b/src/com/billkuker/rocketry/motorsim/test/RocketTest.java new file mode 100644 index 0000000..99f1cb8 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/test/RocketTest.java @@ -0,0 +1,11 @@ +package com.billkuker.rocketry.motorsim.test; + +import org.jscience.physics.amount.Amount; +import org.junit.Assert; + +public class RocketTest { + + protected void assertApproximate(Amount a, Amount b){ + Assert.assertTrue("" + a + " !~ " + b , a.approximates(b)); + } +} diff --git a/src/com/billkuker/rocketry/motorsim/validation/Validating.java b/src/com/billkuker/rocketry/motorsim/validation/Validating.java new file mode 100644 index 0000000..f84a742 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/validation/Validating.java @@ -0,0 +1,5 @@ +package com.billkuker.rocketry.motorsim.validation; + +public interface Validating { + void checkValidity() throws ValidationException; +} diff --git a/src/com/billkuker/rocketry/motorsim/validation/ValidationException.java b/src/com/billkuker/rocketry/motorsim/validation/ValidationException.java new file mode 100644 index 0000000..88ae846 --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/validation/ValidationException.java @@ -0,0 +1,12 @@ +package com.billkuker.rocketry.motorsim.validation; + +public class ValidationException extends Exception { + /** + * + */ + private static final long serialVersionUID = 1L; + + public ValidationException(Validating v, String error) { + super(v.toString() + ": " + error); + } +} diff --git a/src/com/billkuker/rocketry/motorsim/visual/Chart.java b/src/com/billkuker/rocketry/motorsim/visual/Chart.java new file mode 100644 index 0000000..c4919fb --- /dev/null +++ b/src/com/billkuker/rocketry/motorsim/visual/Chart.java @@ -0,0 +1,185 @@ +package com.billkuker.rocketry.motorsim.visual; +import java.awt.BorderLayout; +import java.awt.Color; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Iterator; + +import javax.measure.quantity.Area; +import javax.measure.quantity.Length; +import javax.measure.quantity.Quantity; +import javax.measure.quantity.Volume; +import javax.measure.unit.SI; +import javax.measure.unit.Unit; +import javax.swing.JFrame; +import javax.swing.JPanel; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.Marker; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; +import org.jscience.physics.amount.Amount; + +import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain; + +public class Chart extends JPanel { + + + public class IntervalDomain implements Iterable>{ + + Amount low, high, delta; + int steps = 100; + + public IntervalDomain(Amount low, Amount high) { + this.low = low; + this.high = high; + delta = high.minus(low).divide(steps); + } + + public IntervalDomain(Amount low, Amount high, int steps) { + this.steps = steps; + this.low = low; + this.high = high; + delta = high.minus(low).divide(steps); + } + + + @Override + public Iterator> iterator() { + return new Iterator>(){ + Amount current = low; + + @Override + public boolean hasNext() { + return current.isLessThan(high.plus(delta)); + } + + @Override + public Amount next() { + current = current.plus(delta); + return current; + } + @Override + public final void remove() { + throw new UnsupportedOperationException("Chart domain iterators are not modifiable."); + } + }; + } + + } + + + XYSeries series; + XYSeriesCollection dataset = new XYSeriesCollection(); + JFreeChart chart; + + Unit xUnit; + Unit yUnit; + + Object source; + Method f; + + + public Chart(Unit xUnit, Unit yUnit, Object source, String method) + throws NoSuchMethodException { + super(new BorderLayout()); + f = source.getClass().getMethod(method, Amount.class); + + series = new XYSeries(f.getName()); + this.source = source; + + dataset.addSeries(series); + + this.xUnit = xUnit; + this.yUnit = yUnit; + chart = ChartFactory.createXYLineChart(source.getClass() + .getSimpleName(), // Title + xUnit.toString(), // x-axis Label + yUnit.toString(), // y-axis Label + dataset, + PlotOrientation.VERTICAL, // Plot Orientation + true, // Show Legend + true, // Use tool tips + false // Configure chart to generate URLs? + ); + add(new ChartPanel(chart)); + } + + private Marker marker; + public void mark(Amount m){ + if ( marker != null ) + chart.getXYPlot().removeDomainMarker(marker); + if ( m != null ){ + marker = new ValueMarker(m.doubleValue(xUnit)); + marker.setPaint(Color.blue); + marker.setAlpha(0.8f); + chart.getXYPlot().addDomainMarker(marker); + } + } + + public void setDomain(Iterable> d) { + + //double low = d.low.doubleValue(xUnit); + //double high = d.high.doubleValue(xUnit); + //double step = (high - low) / 50; + //for (double x = low; x < high; x += step) { + //Amount ax = Amount.valueOf(x, xUnit); + for( Amount ax: d){ + try { + Amount y = (Amount) f.invoke(source, ax); + add(ax, y); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + private void add(Amount x, Amount y) { + series.add(x.doubleValue(xUnit), y.doubleValue(yUnit)); + } + + public void show(){ + new JFrame(){ + { + setContentPane(Chart.this); + setSize(640,480); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + } + }.setVisible(true); + } + + public static void main(String args[]) throws Exception { + CoredCylindricalGrain g = new CoredCylindricalGrain(); + g.setLength(Amount.valueOf(70, SI.MILLIMETER)); + g.setOD(Amount.valueOf(30, SI.MILLIMETER)); + g.setID(Amount.valueOf(10, SI.MILLIMETER)); + + Chart c = new Chart(SI.MILLIMETER, + SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea"); + + c.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g + .webThickness())); + + c.show(); + + Chart v = new Chart(SI.MILLIMETER, + SI.MILLIMETER.pow(3).asType(Volume.class), g, "volume"); + + v.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g + .webThickness())); + + v.show(); + } + +}