Skip to content

Commit 92c2b22

Browse files
Merge pull request #254 from RocketPy-Team/enh/liquid_motors_mass_flow_based_tank
ENH: Liquid Motors Mass Flow Based Tank
2 parents 542aba2 + ff15a7d commit 92c2b22

File tree

9 files changed

+10368
-23
lines changed

9 files changed

+10368
-23
lines changed

data/motors/liquid_motor_example/gas_mass_flow_in.csv

Lines changed: 2469 additions & 0 deletions
Large diffs are not rendered by default.

data/motors/liquid_motor_example/gas_mass_flow_out.csv

Lines changed: 2469 additions & 0 deletions
Large diffs are not rendered by default.

data/motors/liquid_motor_example/liquid_mass_flow_in.csv

Lines changed: 2469 additions & 0 deletions
Large diffs are not rendered by default.

data/motors/liquid_motor_example/liquid_mass_flow_out.csv

Lines changed: 2469 additions & 0 deletions
Large diffs are not rendered by default.

docs/notebooks/tank_class_usage.ipynb

Lines changed: 248 additions & 0 deletions
Large diffs are not rendered by default.

rocketpy/Function.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1876,7 +1876,7 @@ def __sub__(self, other):
18761876
# Create new Function object
18771877
return Function(source, inputs, outputs, interpolation)
18781878
else:
1879-
return Function(lambda x: (self.getValue(x) * other(x)))
1879+
return Function(lambda x: (self.getValue(x) - other(x)))
18801880
# If other is Float except...
18811881
except:
18821882
if isinstance(other, (float, int, complex)):

rocketpy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@
2626
from .Flight import Flight
2727
from .Function import Function
2828
from .motors import HybridMotor, SolidMotor, LiquidMotor, Fluid
29+
from .motors import Tank, MassFlowRateBasedTank
2930
from .Rocket import Rocket
3031
from .utilities import *

rocketpy/motors/LiquidMotor.py

Lines changed: 241 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
__license__ = "MIT"
66

77
from abc import ABC, abstractmethod
8+
import functools
89

910
import numpy as np
1011
from scipy import integrate
@@ -13,7 +14,7 @@
1314
from rocketpy.motors import Motor
1415
from rocketpy.supplement import Disk, Cylinder, Hemisphere
1516

16-
# @Stano
17+
1718
class LiquidMotor(Motor):
1819
def __init__(
1920
self,
@@ -86,26 +87,32 @@ def setTankGeometry(self):
8687
)
8788

8889
def setTankFilling(self, t):
89-
liquidVolume = self.liquidVolume(t)
90+
liquidVolume = self.liquidVolume.getValueOpt(t)
9091

9192
if liquidVolume < self.bottomCap.volume:
9293
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:
9897
self.bottomCap.filled_volume = self.bottomCap.volume
9998
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+
):
101104
self.bottomCap.filled_volume = self.bottomCap.volume
102105
self.cylinder.filled_volume = self.cylinder.volume
103106
self.upperCap.filled_volume = liquidVolume - (
104107
self.bottomCap.volume + self.cylinder.volume
105108
)
109+
else:
110+
raise ValueError(
111+
"Tank is overfilled. Check input data to make sure it is correct."
112+
)
106113

107114
@abstractmethod
108-
def mass(self, t):
115+
def mass(self):
109116
"""Returns the total mass of liquid and gases inside the tank as a
110117
function of time.
111118
@@ -122,10 +129,11 @@ def mass(self, t):
122129
pass
123130

124131
@abstractmethod
125-
def netMassFlowRate(self, t):
132+
def netMassFlowRate(self):
126133
"""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.
129137
130138
Parameters
131139
----------
@@ -136,11 +144,12 @@ def netMassFlowRate(self, t):
136144
-------
137145
Function
138146
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.
139148
"""
140149
pass
141150

142151
@abstractmethod
143-
def liquidVolume(self, t):
152+
def liquidVolume(self):
144153
"""Returns the volume of liquid inside the tank as a function
145154
of time.
146155
@@ -180,7 +189,7 @@ def centerOfMass(self, t):
180189
self.bottomCap.filled_centroid * bottomCapMass
181190
+ self.cylinder.filled_centroid * cylinderMass
182191
+ self.upperCap.filled_centroid * upperCapMass
183-
) / self.mass(t)
192+
) / (bottomCapMass + cylinderMass + upperCapMass)
184193

185194
return centerOfMass
186195

@@ -198,19 +207,20 @@ def inertiaTensor(self, t):
198207
Function
199208
Inertia tensor of the tank's fluids as a function of time.
200209
"""
210+
# TODO: compute inertia for non flat caps
201211
self.setTankFilling(t)
202212

203213
cylinder_mass = self.cylinder.filled_volume * self.liquid.density
204214

205-
# for a solid cylinder, ixx = iyy = mr²/4 + ml²/12
215+
# For a solid cylinder, ixx = iyy = mr²/4 + mh²/12
206216
self.inertiaI = cylinder_mass * (
207217
self.diameter**2 + self.cylinder.filled_height**2 / 12
208218
)
209219

210220
# fluids considered inviscid so no shear resistance from torques in z axis
211221
self.inertiaZ = 0
212222

213-
return [self.inertiaI, self.inertiaZ]
223+
return self.inertiaI, self.inertiaZ
214224

215225

216226
# @MrGribel
@@ -220,17 +230,225 @@ def __init__(
220230
name,
221231
diameter,
222232
height,
223-
endcap,
233+
bottomCap,
234+
upperCap,
235+
gas,
236+
liquid,
224237
initial_liquid_mass,
225238
initial_gas_mass,
226239
liquid_mass_flow_rate_in,
227240
gas_mass_flow_rate_in,
228241
liquid_mass_flow_rate_out,
229242
gas_mass_flow_rate_out,
230-
liquid,
231-
gas,
243+
burn_out_time=300,
232244
):
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
234452

235453

236454
# @phmbressan
@@ -256,11 +474,12 @@ def __init__(
256474
name,
257475
diameter,
258476
height,
259-
endcap,
477+
bottomCap,
478+
upperCap,
260479
liquid_mass,
261480
gas_mass,
262481
liquid,
263482
gas,
264483
):
265-
super().__init__(name, diameter, height, endcap, gas, liquid)
484+
super().__init__(name, diameter, height, bottomCap, upperCap, gas, liquid)
266485
pass

rocketpy/motors/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
from .Fluid import Fluid
33
from .SolidMotor import SolidMotor
44
from .LiquidMotor import LiquidMotor
5+
from .LiquidMotor import Tank, MassFlowRateBasedTank
56
from .HybridMotor import HybridMotor

0 commit comments

Comments
 (0)