Cleaned up many warnings
[sw/motorsim] / src / com / billkuker / rocketry / motorsim / Burn.java
1 package com.billkuker.rocketry.motorsim;\r
2 \r
3 \r
4 \r
5 import java.util.Date;\r
6 import java.util.SortedMap;\r
7 import java.util.TreeMap;\r
8 \r
9 import javax.measure.quantity.Area;\r
10 import javax.measure.quantity.Dimensionless;\r
11 import javax.measure.quantity.Duration;\r
12 import javax.measure.quantity.Force;\r
13 import javax.measure.quantity.Length;\r
14 import javax.measure.quantity.Mass;\r
15 import javax.measure.quantity.MassFlowRate;\r
16 import javax.measure.quantity.Pressure;\r
17 import javax.measure.quantity.Quantity;\r
18 import javax.measure.quantity.Temperature;\r
19 import javax.measure.quantity.Velocity;\r
20 import javax.measure.quantity.Volume;\r
21 import javax.measure.quantity.VolumetricDensity;\r
22 import javax.measure.unit.SI;\r
23 \r
24 import org.apache.log4j.Logger;\r
25 import org.jscience.physics.amount.Amount;\r
26 import org.jscience.physics.amount.Constants;\r
27 \r
28 public class Burn {\r
29         //Some constants to tune adaptive regression step\r
30         private static final double regStepIncreaseFactor = 1.01;\r
31         private static final double regStepDecreaseFactor = .5;\r
32         private static final Amount<Pressure> chamberPressureMaxDelta = Amount.valueOf(.5, SI.MEGA(SI.PASCAL));\r
33         \r
34         private static Logger log = Logger.getLogger(Burn.class);\r
35         protected final Motor motor;\r
36         \r
37         public interface BurnProgressListener{\r
38                 public void setProgress(float p);\r
39         }\r
40         \r
41         private BurnProgressListener bpl = null;\r
42         \r
43         private static final Amount<Pressure> atmosphereicPressure = Amount.valueOf(101000, SI.PASCAL);\r
44         \r
45         public class Interval{\r
46                 public Amount<Duration> time;\r
47                 public Amount<Duration> dt;\r
48                 public Amount<Length> regression;\r
49                 public Amount<Pressure> chamberPressure;\r
50                 Amount<Mass> chamberProduct;\r
51                 public Amount<Force> thrust;\r
52 \r
53                 public String toString(){\r
54                         return time + " " + dt + " " + regression + " " + chamberPressure + " " + chamberProduct;\r
55                 }\r
56         }\r
57         \r
58         protected SortedMap<Amount<Duration>,Interval> data = new TreeMap<Amount<Duration>, Interval>();\r
59         \r
60         public SortedMap<Amount<Duration>,Interval> getData(){\r
61                 return data;\r
62         }\r
63         \r
64         public Motor getMotor(){\r
65                 return motor;\r
66         }\r
67 \r
68         public Amount<Duration> burnTime(){\r
69                 return data.lastKey();\r
70         }\r
71         \r
72         public Burn(Motor m){\r
73                 motor = m;\r
74                 burn();\r
75         }\r
76         \r
77         public Burn(Motor m, BurnProgressListener bpl){\r
78                 motor = m;\r
79                 this.bpl = bpl;\r
80                 burn();\r
81         }\r
82         \r
83         private void burn(){\r
84                 log.info("Starting burn...");\r
85                 long start = new Date().getTime();\r
86                 \r
87                 Amount<Length> regStep = Amount.valueOf(0.01, SI.MILLIMETER);\r
88 \r
89                 Interval initial = new Interval();\r
90                 initial.time = Amount.valueOf(0, SI.SECOND);\r
91                 initial.dt = Amount.valueOf(0, SI.SECOND);\r
92                 initial.regression = Amount.valueOf(0, SI.MILLIMETER);\r
93                 initial.chamberPressure = atmosphereicPressure;\r
94                 initial.chamberProduct = Amount.valueOf(0, SI.KILOGRAM);\r
95                 initial.thrust = Amount.valueOf(0, SI.NEWTON);\r
96                 \r
97                 data.put(Amount.valueOf(0, SI.SECOND), initial);\r
98                 \r
99                 step:\r
100                 for ( int i = 0; i < 5000; i++ ) {\r
101                         assert(positive(regStep));\r
102                         regStep = regStep.times(regStepIncreaseFactor);\r
103                         \r
104                         Interval prev = data.get(data.lastKey());\r
105                         log.debug(prev);\r
106                         log.debug("Step " + i + " ==============================");\r
107                         Interval next = new Interval();\r
108                         \r
109                         Amount<Velocity> burnRate = motor.getFuel().burnRate(prev.chamberPressure);\r
110                         assert(positive(burnRate));\r
111                         \r
112                         log.debug("Burn Rate: " + burnRate);\r
113                         \r
114                         Amount<Duration> dt = regStep.divide(burnRate).to(Duration.UNIT);\r
115                         assert(positive(dt));\r
116                         next.dt = dt;\r
117                         \r
118 \r
119                         \r
120                         log.debug("Dt: " + dt);\r
121                         \r
122                         next.regression = prev.regression.plus(regStep);\r
123                         assert(positive(next.regression));\r
124                         \r
125                         log.debug("Regression: " + next.regression);\r
126                         \r
127                         if ( bpl != null ){\r
128                                 Amount<Dimensionless> a = next.regression.divide(motor.getGrain().webThickness()).to(Dimensionless.UNIT);\r
129                                 bpl.setProgress((float)a.doubleValue(Dimensionless.UNIT));\r
130                         }\r
131                         \r
132                         next.time = prev.time.plus(dt);\r
133                         \r
134                         //log.debug("Vold: " + motor.getGrain().volume(prev.regression).to(SI.MILLIMETER.pow(3)));\r
135                         \r
136                         //log.debug("Vnew: " + motor.getGrain().volume(next.regression).to(SI.MILLIMETER.pow(3)));\r
137                         \r
138                         //TODO Amount<Volume> volumeBurnt = motor.getGrain().volume(prev.regression).minus(motor.getGrain().volume(next.regression));\r
139                         Amount<Volume> volumeBurnt = motor.getGrain().surfaceArea(prev.regression).times(regStep).to(Volume.UNIT);\r
140                         assert(positive(volumeBurnt));\r
141                         //log.info("Volume Burnt: " + volumeBurnt.to(SI.MILLIMETER.pow(3)));\r
142                         \r
143                         Amount<MassFlowRate> mGenRate = volumeBurnt.times(motor.getFuel().getIdealDensity().times(motor.getFuel().getDensityRatio())).divide(dt).to(MassFlowRate.UNIT);\r
144                         assert(positive(mGenRate));\r
145                         \r
146                         //log.debug("Mass Gen Rate: " + mGenRate);\r
147                         \r
148                         //Calculate specific gas constant\r
149                         Amount<?> specificGasConstant = Constants.R.divide(motor.getFuel().getCombustionProduct().getEffectiveMolarWeight());\r
150                         //This unit conversion helps JScience to convert nozzle flow rate to\r
151                         //kg/s a little later on I verified the conversion by hand and\r
152                         //JScience checks it too.\r
153                         specificGasConstant = convertSpecificGasConstantUnits(specificGasConstant);\r
154                         \r
155                         //Calculate chamber temperature\r
156                         Amount<Temperature> chamberTemp = motor.getFuel().getCombustionProduct().getIdealCombustionTemperature().times(motor.getFuel().getCombustionEfficiency());\r
157                         \r
158                         Amount<MassFlowRate> mNozzle;\r
159                         {\r
160                                 Amount<Pressure> pDiff = prev.chamberPressure.minus(atmosphereicPressure);\r
161                                 //log.debug("Pdiff: " + pDiff);\r
162                                 Amount<Area> aStar = motor.getNozzle().throatArea();\r
163                                 double k = motor.getFuel().getCombustionProduct().getRatioOfSpecificHeats();\r
164                                 double kSide = Math.sqrt(k) * Math.pow((2/(k+1)) , (((k+1)/2)/(k-1)));\r
165                                 Amount<?> sqrtPart = specificGasConstant.times(chamberTemp).sqrt();\r
166                                 mNozzle = pDiff.times(aStar).times(kSide).divide(sqrtPart).to(MassFlowRate.UNIT);\r
167                                 //log.debug("Mass Exit Rate: " + mNozzle.to(MassFlowRate.UNIT));                \r
168                         }\r
169                         assert(positive(mNozzle));\r
170                         \r
171                         Amount<MassFlowRate> massStorageRate = mGenRate.minus(mNozzle);\r
172                         \r
173                         //log.debug("Mass Storage Rate: " + massStorageRate);\r
174 \r
175                         next.chamberProduct = prev.chamberProduct.plus(massStorageRate.times(dt));\r
176                         \r
177                         //Product can not go negative!\r
178                         if ( !positive(next.chamberProduct) ){\r
179                                 log.warn("ChamberProduct Negative on step " + i + "!, Adjusting regstep down and repeating step!");\r
180                                 regStep = regStep.times(regStepDecreaseFactor);\r
181                                 continue step;\r
182                         }\r
183                         assert(positive(next.chamberProduct));\r
184                         if ( next.chamberProduct.isLessThan(Amount.valueOf(0, SI.KILOGRAM)) )\r
185                                 next.chamberProduct = Amount.valueOf(0, SI.KILOGRAM);\r
186                         \r
187                         //log.debug("Chamber Product: " + next.chamberProduct);\r
188                         \r
189                         Amount<VolumetricDensity> combustionProductDensity = next.chamberProduct.divide(motor.getChamber().chamberVolume().minus(motor.getGrain().volume(next.regression))).to(VolumetricDensity.UNIT);\r
190                         \r
191                         log.debug("Product Density: " + combustionProductDensity);\r
192                         \r
193                         next.chamberPressure = combustionProductDensity.times(specificGasConstant).times(chamberTemp).plus(atmosphereicPressure).to(Pressure.UNIT);\r
194                         assert(positive(next.chamberPressure));\r
195                         \r
196                         next.chamberPressure = Amount.valueOf(\r
197                                         next.chamberPressure.doubleValue(SI.PASCAL),\r
198                                         SI.PASCAL);\r
199                         \r
200                         Amount<Pressure> dp = next.chamberPressure.minus(prev.chamberPressure);\r
201                         if ( dp.abs().isGreaterThan(chamberPressureMaxDelta)){\r
202                                 log.warn("DP " + dp + " too big!, Adjusting regstep down and repeating step!");\r
203                                 regStep = regStep.times(regStepDecreaseFactor);\r
204                                 continue step;\r
205                         }\r
206                         \r
207                         next.thrust = motor.getNozzle().thrust(next.chamberPressure, atmosphereicPressure, atmosphereicPressure, motor.getFuel().getCombustionProduct().getRatioOfSpecificHeats2Phase());\r
208                         assert(positive(next.thrust));\r
209                         \r
210                         if ( i > 100 && next.chamberPressure.approximates(atmosphereicPressure)){\r
211                                 log.info("Pressure at Patm on step " + i);\r
212                                 break;\r
213                         }\r
214                         \r
215                         data.put(data.lastKey().plus(dt), next);\r
216                 }\r
217 \r
218                 long time = new Date().getTime() - start;\r
219                 log.info("Burn took " + time + " millis.");\r
220         }\r
221         \r
222         @SuppressWarnings("unchecked")\r
223         /*\r
224          * This converts the units of this constant to something JScience is able\r
225          * to work from. This conversion is unchecked at compile time, but\r
226          * JScience keeps me honest at runtime.\r
227          */\r
228         private Amount convertSpecificGasConstantUnits(Amount a){\r
229                 return a.to(\r
230                                 SI.METER.pow(2).divide(SI.SECOND.pow(2).times(SI.KELVIN)));\r
231         }\r
232         \r
233         public Amount<Pressure> pressure(Amount<Duration> time){\r
234                 return data.get(time).chamberPressure;\r
235         }\r
236         \r
237         public Amount<Force> thrust(Amount<Duration> time){\r
238                 return data.get(time).thrust;\r
239         }\r
240         \r
241         public Amount<Dimensionless> kn(Amount<Length> regression){\r
242                 return motor.getGrain().surfaceArea(regression).divide(motor.getNozzle().throatArea()).to(Dimensionless.UNIT);\r
243         }\r
244         \r
245         \r
246         private <Q extends Quantity> boolean positive(Amount<Q> a){\r
247                 return ( a.isGreaterThan(a.minus(a)) || a.equals(a.minus(a)));\r
248         }\r
249 \r
250 }\r