5
5
__license__ = "MIT"
6
6
7
7
from abc import ABC , abstractmethod
8
+ import functools
8
9
9
10
import numpy as np
10
11
from scipy import integrate
13
14
from rocketpy .motors import Motor
14
15
from rocketpy .supplement import Disk , Cylinder , Hemisphere
15
16
16
- # @Stano
17
+
17
18
class LiquidMotor (Motor ):
18
19
def __init__ (
19
20
self ,
@@ -86,26 +87,32 @@ def setTankGeometry(self):
86
87
)
87
88
88
89
def setTankFilling (self , t ):
89
- liquidVolume = self .liquidVolume (t )
90
+ liquidVolume = self .liquidVolume . getValueOpt (t )
90
91
91
92
if liquidVolume < self .bottomCap .volume :
92
93
self .bottomCap .filled_volume = liquidVolume
93
- elif (
94
- self .bottomCap .volume
95
- <= liquidVolume
96
- <= self .bottomCap .volume + self .cylinder .volume
97
- ):
94
+ self .cylinder .filled_volume = 0
95
+ self .upperCap .filled_volume = 0
96
+ elif liquidVolume <= self .bottomCap .volume + self .cylinder .volume :
98
97
self .bottomCap .filled_volume = self .bottomCap .volume
99
98
self .cylinder .filled_volume = liquidVolume - self .bottomCap .volume
100
- else :
99
+ self .upperCap .filled_volume = 0
100
+ elif (
101
+ liquidVolume
102
+ <= self .bottomCap .volume + self .cylinder .volume + self .upperCap .volume
103
+ ):
101
104
self .bottomCap .filled_volume = self .bottomCap .volume
102
105
self .cylinder .filled_volume = self .cylinder .volume
103
106
self .upperCap .filled_volume = liquidVolume - (
104
107
self .bottomCap .volume + self .cylinder .volume
105
108
)
109
+ else :
110
+ raise ValueError (
111
+ "Tank is overfilled. Check input data to make sure it is correct."
112
+ )
106
113
107
114
@abstractmethod
108
- def mass (self , t ):
115
+ def mass (self ):
109
116
"""Returns the total mass of liquid and gases inside the tank as a
110
117
function of time.
111
118
@@ -122,10 +129,11 @@ def mass(self, t):
122
129
pass
123
130
124
131
@abstractmethod
125
- def netMassFlowRate (self , t ):
132
+ def netMassFlowRate (self ):
126
133
"""Returns the net mass flow rate of the tank as a function of time.
127
- Net mass flow rate is the mass flow rate exiting the tank minus the
128
- mass flow rate entering the tank, including liquids and gases.
134
+ Net mass flow rate is the mass flow rate entering the tank minus the
135
+ mass flow rate exiting the tank, including liquids and gases. Positive
136
+ is defined as a net mass flow rate entering the tank.
129
137
130
138
Parameters
131
139
----------
@@ -136,11 +144,12 @@ def netMassFlowRate(self, t):
136
144
-------
137
145
Function
138
146
Net mass flow rate of the tank as a function of time.
147
+ Positive is defined as a net mass flow rate entering the tank.
139
148
"""
140
149
pass
141
150
142
151
@abstractmethod
143
- def liquidVolume (self , t ):
152
+ def liquidVolume (self ):
144
153
"""Returns the volume of liquid inside the tank as a function
145
154
of time.
146
155
@@ -180,7 +189,7 @@ def centerOfMass(self, t):
180
189
self .bottomCap .filled_centroid * bottomCapMass
181
190
+ self .cylinder .filled_centroid * cylinderMass
182
191
+ self .upperCap .filled_centroid * upperCapMass
183
- ) / self . mass ( t )
192
+ ) / ( bottomCapMass + cylinderMass + upperCapMass )
184
193
185
194
return centerOfMass
186
195
@@ -198,19 +207,20 @@ def inertiaTensor(self, t):
198
207
Function
199
208
Inertia tensor of the tank's fluids as a function of time.
200
209
"""
210
+ # TODO: compute inertia for non flat caps
201
211
self .setTankFilling (t )
202
212
203
213
cylinder_mass = self .cylinder .filled_volume * self .liquid .density
204
214
205
- # for a solid cylinder, ixx = iyy = mr²/4 + ml ²/12
215
+ # For a solid cylinder, ixx = iyy = mr²/4 + mh ²/12
206
216
self .inertiaI = cylinder_mass * (
207
217
self .diameter ** 2 + self .cylinder .filled_height ** 2 / 12
208
218
)
209
219
210
220
# fluids considered inviscid so no shear resistance from torques in z axis
211
221
self .inertiaZ = 0
212
222
213
- return [ self .inertiaI , self .inertiaZ ]
223
+ return self .inertiaI , self .inertiaZ
214
224
215
225
216
226
# @MrGribel
@@ -220,17 +230,225 @@ def __init__(
220
230
name ,
221
231
diameter ,
222
232
height ,
223
- endcap ,
233
+ bottomCap ,
234
+ upperCap ,
235
+ gas ,
236
+ liquid ,
224
237
initial_liquid_mass ,
225
238
initial_gas_mass ,
226
239
liquid_mass_flow_rate_in ,
227
240
gas_mass_flow_rate_in ,
228
241
liquid_mass_flow_rate_out ,
229
242
gas_mass_flow_rate_out ,
230
- liquid ,
231
- gas ,
243
+ burn_out_time = 300 ,
232
244
):
233
- super ().__init__ (name , diameter , height , endcap , gas , liquid )
245
+ """A motor tank defined based on liquid and gas mass flow rates.
246
+
247
+ Parameters
248
+ ----------
249
+ name : str
250
+ Name of the tank.
251
+ diameter : float
252
+ Diameter of the tank in meters.
253
+ height : float
254
+ Height of the tank in meters.
255
+ bottomCap : str
256
+ Type of bottom cap. Options are "flat" and "spherical".
257
+ upperCap : str
258
+ Type of upper cap. Options are "flat" and "spherical".
259
+ gas : Gas
260
+ motor.Gas object.
261
+ liquid : Liquid
262
+ motor.Liquid object.
263
+ initial_liquid_mass : float
264
+ Initial mass of liquid in the tank in kg.
265
+ initial_gas_mass : float
266
+ Initial mass of gas in the tank in kg.
267
+ liquid_mass_flow_rate_in : str, float, array_like or callable
268
+ Liquid mass flow rate entering the tank as a function of time.
269
+ All values should be positive.
270
+ If string is given, it should be the filepath of a csv file
271
+ containing the data. For more information, see Function.
272
+ gas_mass_flow_rate_in : str, float, array_like or callable
273
+ Gas mass flow rate entering the tank as a function of time.
274
+ All values should be positive.
275
+ If string is given, it should be the filepath of a csv file
276
+ containing the data. For more information, see Function.
277
+ liquid_mass_flow_rate_out : str, float, array_like or callable
278
+ Liquid mass flow rate exiting the tank as a function of time.
279
+ All values should be positive.
280
+ If string is given, it should be the filepath of a csv file
281
+ containing the data. For more information, see Function.
282
+ gas_mass_flow_rate_out : str, float, array_like or callable
283
+ Gas mass flow rate exiting the tank as a function of time.
284
+ All values should be positive.
285
+ If string is given, it should be the filepath of a csv file
286
+ containing the data. For more information, see Function.
287
+ burn_out_time : float, optional
288
+ Time in seconds greater than motor burn out time to use for
289
+ numerical integration stopping criteria. Default is 300.
290
+ """
291
+ super ().__init__ (name , diameter , height , gas , liquid , bottomCap , upperCap )
292
+
293
+ self .initial_liquid_mass = initial_liquid_mass
294
+ self .initial_gas_mass = initial_gas_mass
295
+ self .burn_out_time = burn_out_time
296
+
297
+ self .gas_mass_flow_rate_in = Function (
298
+ gas_mass_flow_rate_in ,
299
+ "Time (s)" ,
300
+ "Inlet Gas Propellant Mass Flow Rate (kg/s)" ,
301
+ "linear" ,
302
+ "zero" ,
303
+ )
304
+
305
+ self .gas_mass_flow_rate_out = Function (
306
+ gas_mass_flow_rate_out ,
307
+ "Time (s)" ,
308
+ "Outlet Gas Propellant Mass Flow Rate (kg/s)" ,
309
+ "linear" ,
310
+ "zero" ,
311
+ )
312
+
313
+ self .liquid_mass_flow_rate_in = Function (
314
+ liquid_mass_flow_rate_in ,
315
+ "Time (s)" ,
316
+ "Inlet Liquid Propellant Mass Flow Rate (kg/s)" ,
317
+ "linear" ,
318
+ "zero" ,
319
+ )
320
+
321
+ self .liquid_mass_flow_rate_out = Function (
322
+ liquid_mass_flow_rate_out ,
323
+ "Time (s)" ,
324
+ "Outlet Liquid Propellant Mass Flow Rate (kg/s)" ,
325
+ "linear" ,
326
+ "zero" ,
327
+ )
328
+
329
+ @functools .cached_property
330
+ def netMassFlowRate (self ):
331
+ """Returns the net mass flow rate of the tank as a function of time.
332
+ Net mass flow rate is the mass flow rate entering the tank minus the
333
+ mass flow rate exiting the tank, including liquids and gases. Positive
334
+ is defined as a net mass flow rate entering the tank.
335
+
336
+ Parameters
337
+ ----------
338
+ time : float
339
+ Time in seconds.
340
+
341
+ Returns
342
+ -------
343
+ Function
344
+ Net mass flow rate of the tank as a function of time.
345
+ Positive is defined as a net mass flow rate entering the tank.
346
+ """
347
+ self .liquid_net_mass_flow_rate = (
348
+ self .liquid_mass_flow_rate_in - self .liquid_mass_flow_rate_out
349
+ )
350
+
351
+ self .liquid_net_mass_flow_rate .setOutputs (
352
+ "Net Liquid Propellant Mass Flow Rate (kg/s)"
353
+ )
354
+ self .liquid_net_mass_flow_rate .setExtrapolation ("zero" )
355
+
356
+ self .gas_net_mass_flow_rate = (
357
+ self .gas_mass_flow_rate_in - self .gas_mass_flow_rate_out
358
+ )
359
+
360
+ self .gas_net_mass_flow_rate .setOutputs (
361
+ "Net Gas Propellant Mass Flow Rate (kg/s)"
362
+ )
363
+ self .gas_net_mass_flow_rate .setExtrapolation ("zero" )
364
+
365
+ self .net_mass_flow_rate = (
366
+ self .liquid_net_mass_flow_rate + self .gas_net_mass_flow_rate
367
+ )
368
+
369
+ self .net_mass_flow_rate .setOutputs (
370
+ "Net Propellant Mass Flow Rate Entering Tank (kg/s)"
371
+ )
372
+ self .net_mass_flow_rate .setExtrapolation ("zero" )
373
+
374
+ return self .net_mass_flow_rate
375
+
376
+ @functools .cached_property
377
+ def mass (self ):
378
+ """Returns the total mass of liquid and gases inside the tank as a
379
+ function of time.
380
+
381
+ Parameters
382
+ ----------
383
+ time : float
384
+ Time in seconds.
385
+
386
+ Returns
387
+ -------
388
+ Function
389
+ Mass of the tank as a function of time. Units in kg.
390
+ """
391
+ # Create an event function for solve_ivp
392
+
393
+ def stopping_criteria (t , y ):
394
+ if y [0 ] / self .initial_liquid_mass > 0.95 :
395
+ return - 1
396
+ else :
397
+ return self .netMassFlowRate (t )
398
+
399
+ stopping_criteria .terminal = True
400
+
401
+ # solve ODE's for liquid and gas masses
402
+ sol = integrate .solve_ivp (
403
+ lambda t , y : (
404
+ self .liquid_net_mass_flow_rate (t ),
405
+ self .gas_net_mass_flow_rate (t ),
406
+ ),
407
+ (0 , self .burn_out_time ),
408
+ (self .initial_liquid_mass , self .initial_gas_mass ),
409
+ first_step = 1e-3 ,
410
+ vectorized = True ,
411
+ events = stopping_criteria ,
412
+ method = "LSODA" ,
413
+ )
414
+
415
+ self .liquid_mass = Function (
416
+ np .column_stack ((sol .t , sol .y [0 ])),
417
+ "Time (s)" ,
418
+ "Liquid Propellant Mass In Tank (kg)" ,
419
+ )
420
+
421
+ self .gas_mass = Function (
422
+ np .column_stack ((sol .t , sol .y [1 ])),
423
+ "Time (s)" ,
424
+ "Gas Propellant Mass In Tank (kg)" ,
425
+ )
426
+
427
+ self .mass = self .liquid_mass + self .gas_mass
428
+ self .mass .setOutputs ("Total Propellant Mass In Tank (kg)" )
429
+ self .mass .setExtrapolation ("constant" )
430
+
431
+ return self .mass
432
+
433
+ @functools .cached_property
434
+ def liquidVolume (self ):
435
+ """Returns the volume of liquid inside the tank as a function of time.
436
+
437
+ Parameters
438
+ ----------
439
+ time : float
440
+ Time in seconds.
441
+
442
+ Returns
443
+ -------
444
+ Function
445
+ Volume of liquid inside the tank as a function of time. Units in m^3.
446
+ """
447
+ self .liquid_volume = self .liquid_mass / self .liquid .density
448
+ self .liquid_volume .setOutputs ("Liquid Propellant Volume In Tank (m^3)" )
449
+ self .liquid_volume .setExtrapolation ("constant" )
450
+
451
+ return self .liquid_volume
234
452
235
453
236
454
# @phmbressan
@@ -256,11 +474,12 @@ def __init__(
256
474
name ,
257
475
diameter ,
258
476
height ,
259
- endcap ,
477
+ bottomCap ,
478
+ upperCap ,
260
479
liquid_mass ,
261
480
gas_mass ,
262
481
liquid ,
263
482
gas ,
264
483
):
265
- super ().__init__ (name , diameter , height , endcap , gas , liquid )
484
+ super ().__init__ (name , diameter , height , bottomCap , upperCap , gas , liquid )
266
485
pass
0 commit comments