Skip to content

Commit 3273cc6

Browse files
caioessouzaGui-FernandesBR
authored andcommitted
ENH: Enable only radial burning
1 parent 170e89c commit 3273cc6

File tree

7 files changed

+238
-58
lines changed

7 files changed

+238
-58
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Attention: The newest changes should be on top -->
4141

4242
### Added
4343

44+
- ENH: Enable only radial burning [#815](https://github.com/RocketPy-Team/RocketPy/pull/815)
4445
- ENH: Tank Fluids with Variable Density from Temperature and Pressure [#852](https://github.com/RocketPy-Team/RocketPy/pull/852)
4546
- ENH: Controller (AirBrakes) and Sensors Encoding [#849](https://github.com/RocketPy-Team/RocketPy/pull/849)
4647
- EHN: Addition of ensemble variable to ECMWF dictionaries [#842](https://github.com/RocketPy-Team/RocketPy/pull/842)
@@ -71,6 +72,7 @@ Attention: The newest changes should be on top -->
7172
## [v1.10.0] - 2025-05-16
7273

7374
### Added
75+
- ENH: Enable only radial burning [#815](https://github.com/RocketPy-Team/RocketPy/pull/815)
7476
- ENH: Support for ND arithmetic in Function class. [#810] (https://github.com/RocketPy-Team/RocketPy/pull/810)
7577
- ENH: allow users to provide custom samplers [#803](https://github.com/RocketPy-Team/RocketPy/pull/803)
7678
- ENH: Implement Multivariate Rejection Sampling (MRS) [#738] (https://github.com/RocketPy-Team/RocketPy/pull/738)
@@ -493,4 +495,4 @@ You can install this version by running `pip install rocketpy==1.0.1`
493495
### Fixed
494496

495497
- BUG: Remove NoseCone Warning [#428](https://github.com/RocketPy-Team/RocketPy/pull/428)
496-
- BUG: motor coordinates [#423](https://github.com/RocketPy-Team/RocketPy/pull/423)
498+
- BUG: motor coordinates [#423](https://github.com/RocketPy-Team/RocketPy/pull/423)

rocketpy/motors/hybrid_motor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,12 @@ class HybridMotor(Motor):
193193
HybridMotor.reference_pressure : int, float
194194
Atmospheric pressure in Pa at which the thrust data was recorded.
195195
It will allow to obtain the net thrust in the Flight class.
196+
SolidMotor.only_radial_burn : bool
197+
If True, grain regression is restricted to radial burn only (inner radius growth).
198+
Grain length remains constant throughout the burn. Default is False.
196199
"""
197200

201+
# pylint: disable=too-many-arguments
198202
def __init__( # pylint: disable=too-many-arguments
199203
self,
200204
thrust_source,
@@ -216,6 +220,7 @@ def __init__( # pylint: disable=too-many-arguments
216220
interpolation_method="linear",
217221
coordinate_system_orientation="nozzle_to_combustion_chamber",
218222
reference_pressure=None,
223+
only_radial_burn=True,
219224
):
220225
"""Initialize Motor class, process thrust curve and geometrical
221226
parameters and store results.
@@ -313,6 +318,11 @@ class Function. Thrust units are Newtons.
313318
"nozzle_to_combustion_chamber".
314319
reference_pressure : int, float, optional
315320
Atmospheric pressure in Pa at which the thrust data was recorded.
321+
only_radial_burn : boolean, optional
322+
If True, inhibits the grain from burning axially, only computing
323+
radial burn. If False, allows the grain to also burn
324+
axially. May be useful for axially inhibited grains or hybrid motors.
325+
Default is False.
316326
317327
Returns
318328
-------
@@ -364,6 +374,7 @@ class Function. Thrust units are Newtons.
364374
interpolation_method,
365375
coordinate_system_orientation,
366376
reference_pressure,
377+
only_radial_burn,
367378
)
368379

369380
self.positioned_tanks = self.liquid.positioned_tanks

rocketpy/motors/solid_motor.py

Lines changed: 101 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ class SolidMotor(Motor):
193193
SolidMotor.reference_pressure : int, float
194194
Atmospheric pressure in Pa at which the thrust data was recorded.
195195
It will allow to obtain the net thrust in the Flight class.
196+
SolidMotor.only_radial_burn : bool
197+
If True, grain regression is restricted to radial burn only (inner radius growth).
198+
Grain length remains constant throughout the burn. Default is False.
196199
"""
197200

198201
# pylint: disable=too-many-arguments
@@ -217,6 +220,7 @@ def __init__(
217220
interpolation_method="linear",
218221
coordinate_system_orientation="nozzle_to_combustion_chamber",
219222
reference_pressure=None,
223+
only_radial_burn=False,
220224
):
221225
"""Initialize Motor class, process thrust curve and geometrical
222226
parameters and store results.
@@ -314,6 +318,11 @@ class Function. Thrust units are Newtons.
314318
"nozzle_to_combustion_chamber".
315319
reference_pressure : int, float, optional
316320
Atmospheric pressure in Pa at which the thrust data was recorded.
321+
only_radial_burn : boolean, optional
322+
If True, inhibits the grain from burning axially, only computing
323+
radial burn. If False, allows the grain to also burn
324+
axially. May be useful for axially inhibited grains or hybrid motors.
325+
Default is False.
317326
318327
Returns
319328
-------
@@ -331,6 +340,7 @@ class Function. Thrust units are Newtons.
331340
interpolation_method=interpolation_method,
332341
coordinate_system_orientation=coordinate_system_orientation,
333342
reference_pressure=reference_pressure,
343+
only_radial_burn = only_radial_burn,
334344
)
335345
# Nozzle parameters
336346
self.throat_radius = throat_radius
@@ -353,6 +363,9 @@ class Function. Thrust units are Newtons.
353363
)
354364
self.grain_initial_mass = self.grain_density * self.grain_initial_volume
355365

366+
# Burn surface definition
367+
self.only_radial_burn = only_radial_burn
368+
356369
self.evaluate_geometry()
357370

358371
# Initialize plots and prints object
@@ -405,6 +418,10 @@ def exhaust_velocity(self):
405418
self.total_impulse / self.propellant_initial_mass
406419
).set_discrete_based_on_model(self.thrust)
407420

421+
# @property
422+
# def only_radial_burn(self):
423+
# return self._only_radial_burn
424+
408425
@property
409426
def propellant_initial_mass(self):
410427
"""Returns the initial propellant mass.
@@ -500,17 +517,25 @@ def geometry_dot(t, y):
500517

501518
# Compute state vector derivative
502519
grain_inner_radius, grain_height = y
503-
burn_area = (
504-
2
505-
* np.pi
506-
* (
507-
grain_outer_radius**2
508-
- grain_inner_radius**2
509-
+ grain_inner_radius * grain_height
520+
if self.only_radial_burn:
521+
burn_area = 2 * np.pi * (grain_inner_radius * grain_height)
522+
523+
grain_inner_radius_derivative = -volume_diff / burn_area
524+
grain_height_derivative = 0 # Set to zero to disable axial burning
525+
526+
else:
527+
burn_area = (
528+
2
529+
* np.pi
530+
* (
531+
grain_outer_radius**2
532+
- grain_inner_radius**2
533+
+ grain_inner_radius * grain_height
534+
)
510535
)
511-
)
512-
grain_inner_radius_derivative = -volume_diff / burn_area
513-
grain_height_derivative = -2 * grain_inner_radius_derivative
536+
537+
grain_inner_radius_derivative = -volume_diff / burn_area
538+
grain_height_derivative = -2 * grain_inner_radius_derivative
514539

515540
return [grain_inner_radius_derivative, grain_height_derivative]
516541

@@ -521,32 +546,55 @@ def geometry_jacobian(t, y):
521546

522547
# Compute jacobian
523548
grain_inner_radius, grain_height = y
524-
factor = volume_diff / (
525-
2
526-
* np.pi
527-
* (
528-
grain_outer_radius**2
529-
- grain_inner_radius**2
530-
+ grain_inner_radius * grain_height
549+
if self.only_radial_burn:
550+
factor = volume_diff / (
551+
2 * np.pi * (grain_inner_radius * grain_height) ** 2
531552
)
532-
** 2
533-
)
534-
inner_radius_derivative_wrt_inner_radius = factor * (
535-
grain_height - 2 * grain_inner_radius
536-
)
537-
inner_radius_derivative_wrt_height = factor * grain_inner_radius
538-
height_derivative_wrt_inner_radius = (
539-
-2 * inner_radius_derivative_wrt_inner_radius
540-
)
541-
height_derivative_wrt_height = -2 * inner_radius_derivative_wrt_height
542553

543-
return [
544-
[
545-
inner_radius_derivative_wrt_inner_radius,
546-
inner_radius_derivative_wrt_height,
547-
],
548-
[height_derivative_wrt_inner_radius, height_derivative_wrt_height],
549-
]
554+
inner_radius_derivative_wrt_inner_radius = factor * (
555+
grain_height - 2 * grain_inner_radius
556+
)
557+
inner_radius_derivative_wrt_height = 0
558+
height_derivative_wrt_inner_radius = 0
559+
height_derivative_wrt_height = 0
560+
# Height is a constant, so all the derivatives with respect to it are set to zero
561+
562+
return [
563+
[
564+
inner_radius_derivative_wrt_inner_radius,
565+
inner_radius_derivative_wrt_height,
566+
],
567+
[height_derivative_wrt_inner_radius, height_derivative_wrt_height],
568+
]
569+
570+
else:
571+
factor = volume_diff / (
572+
2
573+
* np.pi
574+
* (
575+
grain_outer_radius**2
576+
- grain_inner_radius**2
577+
+ grain_inner_radius * grain_height
578+
)
579+
** 2
580+
)
581+
582+
inner_radius_derivative_wrt_inner_radius = factor * (
583+
grain_height - 2 * grain_inner_radius
584+
)
585+
inner_radius_derivative_wrt_height = factor * grain_inner_radius
586+
height_derivative_wrt_inner_radius = (
587+
-2 * inner_radius_derivative_wrt_inner_radius
588+
)
589+
height_derivative_wrt_height = -2 * inner_radius_derivative_wrt_height
590+
591+
return [
592+
[
593+
inner_radius_derivative_wrt_inner_radius,
594+
inner_radius_derivative_wrt_height,
595+
],
596+
[height_derivative_wrt_inner_radius, height_derivative_wrt_height],
597+
]
550598

551599
def terminate_burn(t, y): # pylint: disable=unused-argument
552600
end_function = (self.grain_outer_radius - y[0]) * y[1]
@@ -597,16 +645,24 @@ def burn_area(self):
597645
burn_area : Function
598646
Function representing the burn area progression with the time.
599647
"""
600-
burn_area = (
601-
2
602-
* np.pi
603-
* (
604-
self.grain_outer_radius**2
605-
- self.grain_inner_radius**2
606-
+ self.grain_inner_radius * self.grain_height
648+
if self.only_radial_burn:
649+
burn_area = (
650+
2
651+
* np.pi
652+
* (self.grain_inner_radius * self.grain_height)
653+
* self.grain_number
654+
)
655+
else:
656+
burn_area = (
657+
2
658+
* np.pi
659+
* (
660+
self.grain_outer_radius**2
661+
- self.grain_inner_radius**2
662+
+ self.grain_inner_radius * self.grain_height
663+
)
664+
* self.grain_number
607665
)
608-
* self.grain_number
609-
)
610666
return burn_area
611667

612668
@funcify_method("Time (s)", "burn rate (m/s)")
@@ -778,6 +834,7 @@ def to_dict(self, **kwargs):
778834
"grain_initial_height": self.grain_initial_height,
779835
"grain_separation": self.grain_separation,
780836
"grains_center_of_mass_position": self.grains_center_of_mass_position,
837+
"only_radial_burn": self.only_radial_burn,
781838
}
782839
)
783840

@@ -827,4 +884,5 @@ def from_dict(cls, data):
827884
interpolation_method=data["interpolate"],
828885
coordinate_system_orientation=data["coordinate_system_orientation"],
829886
reference_pressure=data.get("reference_pressure"),
887+
only_radial_burn=data.get("only_radial_burn", False),
830888
)

tests/fixtures/motor/hybrid_fixtures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
@pytest.fixture
7-
def hybrid_motor(spherical_oxidizer_tank):
7+
def hybrid_motor(oxidizer_tank):
88
"""An example of a hybrid motor with spherical oxidizer
99
tank and fuel grains.
1010
@@ -35,6 +35,6 @@ def hybrid_motor(spherical_oxidizer_tank):
3535
grains_center_of_mass_position=-0.1,
3636
)
3737

38-
motor.add_tank(spherical_oxidizer_tank, position=0.3)
38+
motor.add_tank(oxidizer_tank, position=0.3)
3939

4040
return motor
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from unittest.mock import patch
2+
3+
4+
@patch("matplotlib.pyplot.show")
5+
def test_solid_motor_info(mock_show, cesaroni_m1670): # pylint: disable=unused-argument
6+
"""Tests the SolidMotor.all_info() method.
7+
8+
Parameters
9+
----------
10+
mock_show : mock
11+
Mock of the matplotlib.pyplot.show function.
12+
cesaroni_m1670 : rocketpy.SolidMotor
13+
The SolidMotor object to be used in the tests.
14+
"""
15+
assert cesaroni_m1670.info() is None
16+
assert cesaroni_m1670.all_info() is None

0 commit comments

Comments
 (0)