Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: motor coordinates #423

Merged
merged 12 commits into from
Oct 7, 2023
3 changes: 2 additions & 1 deletion rocketpy/prints/rocket_prints.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ def rocket_geometrical_parameters(self):
"Rocket Center of Dry Mass - Nozzle Exit Distance: "
+ "{:.3f} m".format(
abs(
self.rocket.center_of_dry_mass_position - self.rocket.motor_position
self.rocket.center_of_dry_mass_position
- self.rocket.nozzle_position
)
)
)
Expand Down
26 changes: 16 additions & 10 deletions rocketpy/rocket/rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,16 @@ class Rocket:
Rocket.motor : Motor
Rocket's motor. See Motor class for more details.
Rocket.motor_position : float
Position, in m, of the motor's nozzle exit area relative to the user
defined rocket coordinate system. See
:doc:`Positions and Coordinate Systems </user/positions>`
for more information
Position, in meters, of the motor's coordinate system origin
relative to the user defined rocket coordinate system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information.
regarding the rocket's coordinate system.
Rocket.nozzle_position : float
Position, in meters, of the motor's nozzle exit relative to the user
defined rocket coordinate system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information.
Rocket.center_of_propellant_position : Function
Position of the propellant's center of mass relative to the user defined
rocket reference system. See
Expand Down Expand Up @@ -642,14 +647,15 @@ def add_motor(self, motor, position):
self.motor_position = position
_ = self._csys * self.motor._csys
self.center_of_propellant_position = (
self.motor.center_of_propellant_mass - self.motor.nozzle_position
) * _ + self.motor_position
self.motor.center_of_propellant_mass * _ + self.motor_position
)
self.motor_center_of_mass_position = (
self.motor.center_of_mass - self.motor.nozzle_position
) * _ + self.motor_position
self.motor.center_of_mass * _ + self.motor_position
)
self.motor_center_of_dry_mass_position = (
self.motor.center_of_dry_mass_position - self.motor.nozzle_position
) * _ + self.motor_position
self.motor.center_of_dry_mass_position * _ + self.motor_position
)
self.nozzle_position = self.motor.nozzle_position * _ + self.motor_position
self.evaluate_dry_mass()
self.evaluate_total_mass()
self.evaluate_center_of_dry_mass()
Expand Down
68 changes: 13 additions & 55 deletions rocketpy/simulation/flight.py
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,10 @@ def __init_equations_of_motion(self):

@cached_property
def effective_1rl(self):
nozzle = self.rocket.motor_position
"""Original rail length minus the distance measured from nozzle exit
to the upper rail button. It assumes the nozzle to be aligned with
the beginning of the rail."""
nozzle = self.rocket.nozzle_position
try:
rail_buttons = self.rocket.rail_buttons[0]
upper_r_button = (
Expand All @@ -1187,7 +1190,10 @@ def effective_1rl(self):

@cached_property
def effective_2rl(self):
nozzle = self.rocket.motor_position
"""Original rail length minus the distance measured from nozzle exit
to the lower rail button. It assumes the nozzle to be aligned with
the beginning of the rail."""
nozzle = self.rocket.nozzle_position
try:
rail_buttons = self.rocket.rail_buttons[0]
lower_r_button = rail_buttons.position
Expand Down Expand Up @@ -1377,7 +1383,7 @@ def u_dot(self, t, u, post_processing=False):
)
# c = -self.rocket.distance_rocket_nozzle
c = (
-(self.rocket.motor_position - self.rocket.center_of_dry_mass_position)
-(self.rocket.nozzle_position - self.rocket.center_of_dry_mass_position)
* self.rocket._csys
)
a = b * Mt / M
Expand Down Expand Up @@ -1632,7 +1638,7 @@ def u_dot_generalized(self, t, u, post_processing=False):
r_CM_ddot = Vector([0, 0, r_CM_z.differentiate(t, order=2)])
## Nozzle gyration tensor
r_NOZ = (
-(self.rocket.motor_position - self.rocket.center_of_dry_mass_position)
-(self.rocket.nozzle_position - self.rocket.center_of_dry_mass_position)
* self.rocket._csys
)
S_noz_33 = 0.5 * self.rocket.motor.nozzle_radius**2
Expand Down Expand Up @@ -2411,23 +2417,10 @@ def aerodynamic_spin_moment(self):
# Kinetic Energy
@funcify_method("Time (s)", "Rotational Kinetic Energy (J)")
def rotational_energy(self):
# b = -self.rocket.distanceRocketPropellant
b = (
-(self.rocket.motor_position - self.rocket.center_of_dry_mass_position)
* self.rocket._csys
)
mu = self.rocket.reduced_mass
Rz = self.rocket.dry_I_33
Ri = self.rocket.dry_I_11
Tz = self.rocket.motor.I_33
Ti = self.rocket.motor.I_11
I1, I2, I3 = (Ri + Ti + mu * b**2), (Ri + Ti + mu * b**2), (Rz + Tz)
# Redefine I1, I2 and I3 time grid to allow for efficient Function algebra
I1.set_discrete_based_on_model(self.w1)
I2.set_discrete_based_on_model(self.w1)
I3.set_discrete_based_on_model(self.w1)
rotational_energy = 0.5 * (
I1 * self.w1**2 + I2 * self.w2**2 + I3 * self.w3**2
self.rocket.I_11 * self.w1**2
+ self.rocket.I_22 * self.w2**2
+ self.rocket.I_33 * self.w3**2
)
rotational_energy.set_discrete_based_on_model(self.w1)
return rotational_energy
Expand Down Expand Up @@ -2564,41 +2557,6 @@ def static_margin(self):
"""Static margin of the rocket."""
return self.rocket.static_margin

# Rail Button Forces
@cached_property
def effective_1rl(self):
"""Original rail length minus the distance measured from nozzle exit
to the upper rail button. It assumes the nozzle to be aligned with
the beginning of the rail."""
nozzle = (
self.rocket.motor_position - self.rocket.center_of_dry_mass_position
) * self.rocket._csys # Kinda works for single nozzle
try:
rail_buttons = self.rocket.rail_buttons[0]
upper_r_button = (
rail_buttons.component.buttons_distance + rail_buttons.position
)
except IndexError: # No rail buttons defined
upper_r_button = nozzle
effective_1rl = self.rail_length - abs(nozzle - upper_r_button)
return effective_1rl

@cached_property
def effective_2rl(self):
"""Original rail length minus the distance measured from nozzle exit
to the lower rail button. It assumes the nozzle to be aligned with
the beginning of the rail."""
nozzle = (
self.rocket.motor_position - self.rocket.center_of_dry_mass_position
) * self.rocket._csys
try:
rail_buttons = self.rocket.rail_buttons[0]
lower_r_button = rail_buttons.position
except IndexError: # No rail buttons defined
lower_r_button = nozzle
effective_2rl = self.rail_length - abs(nozzle - lower_r_button)
return effective_2rl

@cached_property
def frontal_surface_wind(self):
"""Surface wind speed in m/s aligned with the launch rail."""
Expand Down
63 changes: 34 additions & 29 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,10 @@ def cesaroni_m1670(): # old name: solid_motor


@pytest.fixture
def calisto(cesaroni_m1670): # old name: rocket
def calisto_motorless():
"""Create a simple object of the Rocket class to be used in the tests. This
is the same rocket that has been used in the getting started guide for
years. The Calisto rocket is the Projeto Jupiter's project launched at the
2019 Spaceport America Cup.

Parameters
----------
cesaroni_m1670 : rocketpy.SolidMotor
An object of the SolidMotor class. This is a pytest fixture too.
is the same rocket that has been used in the getting started guide for years
but without a motor.

Returns
-------
Expand All @@ -136,60 +130,71 @@ def calisto(cesaroni_m1670): # old name: rocket
center_of_mass_without_motor=0,
coordinate_system_orientation="tail_to_nose",
)
return calisto


@pytest.fixture
def calisto(calisto_motorless, cesaroni_m1670): # old name: rocket
"""Create a simple object of the Rocket class to be used in the tests. This
is the same rocket that has been used in the getting started guide for
years. The Calisto rocket is the Projeto Jupiter's project launched at the
2019 Spaceport America Cup.

Parameters
----------
calisto_motorless : rocketpy.Rocket
An object of the Rocket class. This is a pytest fixture too.
cesaroni_m1670 : rocketpy.SolidMotor
An object of the SolidMotor class. This is a pytest fixture too.

Returns
-------
rocketpy.Rocket
A simple object of the Rocket class
"""
calisto = calisto_motorless
calisto.add_motor(cesaroni_m1670, position=-1.373)
return calisto


@pytest.fixture
def calisto_liquid_modded(liquid_motor):
def calisto_liquid_modded(calisto_motorless, liquid_motor):
"""Create a simple object of the Rocket class to be used in the tests. This
is an example of the Calisto rocket with a liquid motor.

Parameters
----------
calisto_motorless : rocketpy.Rocket
An object of the Rocket class. This is a pytest fixture too.
liquid_motor : rocketpy.LiquidMotor

Returns
-------
rocketpy.Rocket
A simple object of the Rocket class
"""
calisto = Rocket(
radius=0.0635,
mass=14.426,
inertia=(6.321, 6.321, 0.034),
power_off_drag="data/calisto/powerOffDragCurve.csv",
power_on_drag="data/calisto/powerOnDragCurve.csv",
center_of_mass_without_motor=0,
coordinate_system_orientation="tail_to_nose",
)
calisto = calisto_motorless
calisto.add_motor(liquid_motor, position=-1.373)
return calisto


@pytest.fixture
def calisto_hybrid_modded(hybrid_motor):
def calisto_hybrid_modded(calisto_motorless, hybrid_motor):
"""Create a simple object of the Rocket class to be used in the tests. This
is an example of the Calisto rocket with a hybrid motor.

Parameters
----------
calisto_motorless : rocketpy.Rocket
An object of the Rocket class. This is a pytest fixture too.
hybrid_motor : rocketpy.HybridMotor

Returns
-------
rocketpy.Rocket
A simple object of the Rocket class
"""
calisto = Rocket(
radius=0.0635,
mass=14.426,
inertia=(6.321, 6.321, 0.034),
power_off_drag="data/calisto/powerOffDragCurve.csv",
power_on_drag="data/calisto/powerOnDragCurve.csv",
center_of_mass_without_motor=0,
coordinate_system_orientation="tail_to_nose",
)
calisto = calisto_motorless
calisto.add_motor(hybrid_motor, position=-1.373)
return calisto

Expand Down
81 changes: 81 additions & 0 deletions tests/test_rocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,87 @@ def test_add_fins_assert_cp_cm_plus_fins(calisto, dimensionless_calisto, m):
)


@pytest.mark.parametrize(
"""cdm_position, grain_cm_position, nozzle_position, coord_direction,
motor_position, expected_motor_cdm, expected_motor_cpp""",
[
(0.317, 0.397, 0, "nozzle_to_combustion_chamber", -1.373, -1.056, -0.976),
(0, 0.08, -0.317, "nozzle_to_combustion_chamber", -1, -1, -0.92),
(-0.317, -0.397, 0, "combustion_chamber_to_nozzle", -1.373, -1.056, -0.976),
(0, -0.08, 0.317, "combustion_chamber_to_nozzle", -1, -1, -0.92),
(1.317, 1.397, 1, "nozzle_to_combustion_chamber", -2.373, -1.056, -0.976),
],
)
def test_add_motor_coordinates(
calisto_motorless,
cdm_position,
grain_cm_position,
nozzle_position,
coord_direction,
motor_position,
expected_motor_cdm,
expected_motor_cpp,
):
"""Test the method add_motor and related position properties in a Rocket
instance.

This test checks the correctness of the `add_motor` method and the computed
`motor_center_of_dry_mass_position` and `center_of_propellant_position`
properties in the `Rocket` class using various parameters related to the
motor's position, nozzle's position, and other related coordinates.
Different scenarios are tested using parameterization, checking scenarios
moving from the nozzle to the combustion chamber and vice versa, and with
various specific physical and geometrical characteristics of the motor.

Parameters
----------
calisto_motorless : Rocket instance
A predefined instance of a Rocket without a motor, used as a base for testing.
cdm_position : float
Position of the center of dry mass of the motor.
grain_cm_position : float
Position of the grains' center of mass.
nozzle_position : float
Position of the nozzle.
coord_direction : str
Direction for coordinate system orientation;
it can be "nozzle_to_combustion_chamber" or "combustion_chamber_to_nozzle".
motor_position : float
Position where the motor should be added to the rocket.
expected_motor_cdm : float
Expected position of the motor's center of dry mass after addition.
expected_motor_cpp : float
Expected position of the center of propellant after addition.
"""
example_motor = SolidMotor(
thrust_source="data/motors/Cesaroni_M1670.eng",
burn_time=3.9,
dry_mass=0,
dry_inertia=(0, 0, 0),
center_of_dry_mass_position=cdm_position,
nozzle_position=nozzle_position,
grain_number=5,
grain_density=1815,
nozzle_radius=33 / 1000,
throat_radius=11 / 1000,
grain_separation=5 / 1000,
grain_outer_radius=33 / 1000,
grain_initial_height=120 / 1000,
grains_center_of_mass_position=grain_cm_position,
grain_initial_inner_radius=15 / 1000,
interpolation_method="linear",
coordinate_system_orientation=coord_direction,
)
calisto = calisto_motorless
calisto.add_motor(example_motor, position=motor_position)

calculated_motor_cdm = calisto.motor_center_of_dry_mass_position
calculated_motor_cpp = calisto.center_of_propellant_position

assert pytest.approx(expected_motor_cdm) == calculated_motor_cdm
assert pytest.approx(expected_motor_cpp) == calculated_motor_cpp(0)


def test_add_cm_eccentricity_assert_properties_set(calisto):
calisto.add_cm_eccentricity(x=4, y=5)

Expand Down