Space Enterprise at Berkeley (Liquid Motor)#

Here we go with another flight simulation example using RocketPy. This time, we use data from the Space Enterprise at Berkeley team to perform a trajectory simulation using the LiquidMotor. Let’s check it out!

[ ]:
# These lines are here for debugging purposes only
%load_ext autoreload
%autoreload 2
[ ]:
from rocketpy import (
    Function,
    LiquidMotor,
    UllageBasedTank,
    MassBasedTank,
    Fluid,
    Rocket,
    Flight,
    Environment,
    CylindricalTank,
)

Input Curves#

First, we need to load the curves generated by the team during the motor testings.

The information that we are using are basically the volume of LOX and Propane in the tanks

[ ]:
LOX_Volume_Liters = Function(
    "../../data/SEBLM/test124_Lox_Volume.csv", extrapolation="zero"
)
LOX_Volume = LOX_Volume_Liters * 0.001  # convert to m^3
LOX_Volume.set_discrete(8.003, 19.984, 40, interpolation="linear")
LOX_Volume.plot(forceData=True)
LOX_tank_ullage = 0.013167926436231077 - LOX_Volume
LOX_tank_ullage.plot(8, 8.5, forceData=True)
[ ]:
Propane_Volume_Liters = Function("../../data/SEBLM/test124_Propane_Volume.csv")
Propane_Volume = Propane_Volume_Liters * 0.001  # convert to m^3
Propane_Volume.set_discrete(8.003, 19.984, 40, "linear")
Propane_Volume.plot(forceData=True)
Propane_tank_ullage = 0.013167926436231077 - Propane_Volume
Propane_tank_ullage.plot(forceData=True)

Fluids#

Now it’s time to define the fluids that we are going to use in the simulation.

[ ]:
LOX = Fluid("LOX", 1024, 1)
Propane = Fluid("Propane", 566, 1)
LOXTankPressurizingGas = Fluid("N2", 31.3 / 28, 1)  # 450 PSI
PropaneTankPressurizingGas = Fluid("N2", 313 * 300 / 4500 / 28, 1)  # 300 PSI
PressurizingGas = Fluid("N2", 313 / 28, 1)  # 4500 PSI

Tanks#

After the fluids, it is time to define all the 3 tanks that we have in the motor.

LOX Tank#

We first start defining the tank geometry, which is a cylinder with a spherical head.

[ ]:
LOX_tank_geometry = CylindricalTank(0.0744, 0.658, spherical_caps=True)

Next, we use the tank geometry to define the tank itself.

[ ]:
LOX_tank = UllageBasedTank(
    name="LOX Tank",
    flux_time=(8, 20),
    geometry=LOX_tank_geometry,
    gas=LOXTankPressurizingGas,
    liquid=LOX,
    ullage=LOX_tank_ullage,
)

After defining, we stop for a minute to appreciate the evolution of mass and mass flow rate that were calculated by RocketPy.

Isn’t it beautiful?

[ ]:
LOX_tank.fluid_mass()
LOX_tank.net_mass_flow_rate()
LOX_tank.liquid_height()
LOX_tank.gas_height()
LOX_tank.center_of_mass()
LOX_tank.inertia()

Propane Tank#

Our setup work is not done yet. We still need to define the propane tank, Which is a cylinder with a spherical head.

The propane has the role of pressurizing the LOX tank.

[ ]:
Propane_tank_geometry = CylindricalTank(0.0744, 0.658, spherical_caps=True)
Propane_tank = UllageBasedTank(
    name="Propane Tank",
    flux_time=(8, 20),
    geometry=Propane_tank_geometry,
    gas=PropaneTankPressurizingGas,
    liquid=Propane,
    ullage=Propane_tank_ullage,
)

Again, let’s visualize the partial results.

[ ]:
Propane_tank.fluid_mass()
Propane_tank.net_mass_flow_rate()
Propane_tank.liquid_height()
Propane_tank.gas_height()
Propane_tank.center_of_mass()
Propane_tank.inertia()

Pressure Tank#

The third tank is the pressure tank, which is a cylinder with a spherical head.

[ ]:
Pressure_tank_geometry = CylindricalTank(0.135 / 2, 0.846, spherical_caps=True)
Pressure_tank = MassBasedTank(
    name="Pressure Tank",
    geometry=Pressure_tank_geometry,
    liquid_mass=0,
    flux_time=(8, 20),
    gas_mass="../../data/SEBLM/pressurantMassFiltered.csv",
    gas=PressurizingGas,
    liquid=PressurizingGas,
)
[ ]:
Pressure_tank.fluid_mass()
Pressure_tank.net_mass_flow_rate()
Pressure_tank.liquid_height()
Pressure_tank.gas_height()
Pressure_tank.center_of_mass()
Pressure_tank.inertia()

Liquid Motor#

After defining the three tanks, we can finally define the liquid motor object, just check how simple it is!

[ ]:
SEBLM = LiquidMotor(
    thrust_source="../../data/SEBLM/test124_Thrust_Curve.csv",
    center_of_dry_mass=0,
    dry_inertia=(0, 0, 0),
    dry_mass=0,
    burn_time=(8, 20),
    nozzle_radius=0.069 / 2,
    nozzle_position=-1.364,
    coordinate_system_orientation="nozzle_to_combustion_chamber",
)

SEBLM.add_tank(Propane_tank, position=-1.048)
SEBLM.add_tank(LOX_tank, position=0.711)
SEBLM.add_tank(Pressure_tank, position=2.007)

After defining it, we can check all the important information about the motor, such as the thrust curve, the mass flow rate curve, the specific impulse curve, and the burn time.

[ ]:
SEBLM.all_info()

Rocket Definition#

The motor is ready to go, but we still need to define the rocket itself. Let’s see how it is done:

[ ]:
SEBRocket = Rocket(
    radius=0.098,
    mass=63.4,
    inertia=(25, 25, 1),
    power_off_drag="../../data/SEBLM/drag.csv",
    power_on_drag="../../data/SEBLM/drag.csv",
    center_of_mass_without_motor=3.23,
    coordinate_system_orientation="nose_to_tail",
)
SEBRocket.add_motor(SEBLM, position=5.75)
SEBRocket.add_nose(length=0.7, kind="vonKarman", position=0)
SEBRocket.add_tail(
    top_radius=0.098, bottom_radius=0.058, length=0.198, position=5.69 - 0.198
)

SEBRocket.add_trapezoidal_fins(
    n=4,
    rootChord=0.355,
    tip_chord=0.0803,
    span=0.156,
    position=5.25,
    cantAngle=0,
)

SEBRocket.setRailButtons(lower_button_position=-1, upper_button_position=1)
[ ]:
SEBRocket.all_info()

Environment#

I swear this is the last step before actually flying the rocket. We need to define the environment in which the rocket will fly.

[ ]:
env = Environment(
    latitude=35.347122986338356, longitude=-117.80893423073582
)

env.set_date((2022, 12, 3, 14 + 7, 0, 0))  # UTC

env.set_atmospheric_model(
    type="custom_atmosphere",
    pressure=None,
    temperature=None,
    wind_u=[(0, 1), (500, 0), (1000, 5), (2500, 5.0), (5000, 10)],
    wind_v=[(0, 0), (500, 3), (1600, 2), (2500, -3), (5000, 10)],
)

env.maxExpectedHeight = 8000
[ ]:
env.info()

Flight Simulation#

Finally, here we go with our flight simulation. We are going to run the simulation only until the apogee, since we are not interested in the landing phase.

The maxTimeStep parameter was set to a low value to ensure there won’t be any numerical instability during the launch rail phase.

[ ]:
test_flight = Flight(
    rocket=SEBRocket,
    environment=env,
    railLength=18.28,
    inclination=90,
    heading=23,
    maxTimeStep=0.1,
    terminate_on_apogee=True,
)
[ ]:
test_flight.angleOfAttack.plot(test_flight.outOfRailTime, 15)
[ ]:
test_flight.all_info()