Skip to content

Commit a127ac5

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

File tree

7 files changed

+234
-57
lines changed

7 files changed

+234
-57
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Attention: The newest changes should be on top -->
3232

3333
### Added
3434

35+
- ENH: Enable only radial burning [#815](https://github.com/RocketPy-Team/RocketPy/pull/815)
36+
3537
### Changed
3638

3739
### Fixed
@@ -71,6 +73,7 @@ Attention: The newest changes should be on top -->
7173
## [v1.10.0] - 2025-05-16
7274

7375
### Added
76+
7477
- ENH: Support for ND arithmetic in Function class. [#810] (https://github.com/RocketPy-Team/RocketPy/pull/810)
7578
- ENH: allow users to provide custom samplers [#803](https://github.com/RocketPy-Team/RocketPy/pull/803)
7679
- ENH: Implement Multivariate Rejection Sampling (MRS) [#738] (https://github.com/RocketPy-Team/RocketPy/pull/738)

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: 97 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
@@ -500,17 +513,25 @@ def geometry_dot(t, y):
500513

501514
# Compute state vector derivative
502515
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
516+
if self.only_radial_burn:
517+
burn_area = 2 * np.pi * (grain_inner_radius * grain_height)
518+
519+
grain_inner_radius_derivative = -volume_diff / burn_area
520+
grain_height_derivative = 0 # Set to zero to disable axial burning
521+
522+
else:
523+
burn_area = (
524+
2
525+
* np.pi
526+
* (
527+
grain_outer_radius**2
528+
- grain_inner_radius**2
529+
+ grain_inner_radius * grain_height
530+
)
510531
)
511-
)
512-
grain_inner_radius_derivative = -volume_diff / burn_area
513-
grain_height_derivative = -2 * grain_inner_radius_derivative
532+
533+
grain_inner_radius_derivative = -volume_diff / burn_area
534+
grain_height_derivative = -2 * grain_inner_radius_derivative
514535

515536
return [grain_inner_radius_derivative, grain_height_derivative]
516537

@@ -521,32 +542,55 @@ def geometry_jacobian(t, y):
521542

522543
# Compute jacobian
523544
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
545+
if self.only_radial_burn:
546+
factor = volume_diff / (
547+
2 * np.pi * (grain_inner_radius * grain_height) ** 2
531548
)
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
542549

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-
]
550+
inner_radius_derivative_wrt_inner_radius = factor * (
551+
grain_height - 2 * grain_inner_radius
552+
)
553+
inner_radius_derivative_wrt_height = 0
554+
height_derivative_wrt_inner_radius = 0
555+
height_derivative_wrt_height = 0
556+
# Height is a constant, so all the derivatives with respect to it are set to zero
557+
558+
return [
559+
[
560+
inner_radius_derivative_wrt_inner_radius,
561+
inner_radius_derivative_wrt_height,
562+
],
563+
[height_derivative_wrt_inner_radius, height_derivative_wrt_height],
564+
]
565+
566+
else:
567+
factor = volume_diff / (
568+
2
569+
* np.pi
570+
* (
571+
grain_outer_radius**2
572+
- grain_inner_radius**2
573+
+ grain_inner_radius * grain_height
574+
)
575+
** 2
576+
)
577+
578+
inner_radius_derivative_wrt_inner_radius = factor * (
579+
grain_height - 2 * grain_inner_radius
580+
)
581+
inner_radius_derivative_wrt_height = factor * grain_inner_radius
582+
height_derivative_wrt_inner_radius = (
583+
-2 * inner_radius_derivative_wrt_inner_radius
584+
)
585+
height_derivative_wrt_height = -2 * inner_radius_derivative_wrt_height
586+
587+
return [
588+
[
589+
inner_radius_derivative_wrt_inner_radius,
590+
inner_radius_derivative_wrt_height,
591+
],
592+
[height_derivative_wrt_inner_radius, height_derivative_wrt_height],
593+
]
550594

551595
def terminate_burn(t, y): # pylint: disable=unused-argument
552596
end_function = (self.grain_outer_radius - y[0]) * y[1]
@@ -597,16 +641,24 @@ def burn_area(self):
597641
burn_area : Function
598642
Function representing the burn area progression with the time.
599643
"""
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
644+
if self.only_radial_burn:
645+
burn_area = (
646+
2
647+
* np.pi
648+
* (self.grain_inner_radius * self.grain_height)
649+
* self.grain_number
650+
)
651+
else:
652+
burn_area = (
653+
2
654+
* np.pi
655+
* (
656+
self.grain_outer_radius**2
657+
- self.grain_inner_radius**2
658+
+ self.grain_inner_radius * self.grain_height
659+
)
660+
* self.grain_number
607661
)
608-
* self.grain_number
609-
)
610662
return burn_area
611663

612664
@funcify_method("Time (s)", "burn rate (m/s)")
@@ -778,6 +830,7 @@ def to_dict(self, **kwargs):
778830
"grain_initial_height": self.grain_initial_height,
779831
"grain_separation": self.grain_separation,
780832
"grains_center_of_mass_position": self.grains_center_of_mass_position,
833+
"only_radial_burn": self.only_radial_burn,
781834
}
782835
)
783836

@@ -827,4 +880,5 @@ def from_dict(cls, data):
827880
interpolation_method=data["interpolate"],
828881
coordinate_system_orientation=data["coordinate_system_orientation"],
829882
reference_pressure=data.get("reference_pressure"),
883+
only_radial_burn=data.get("only_radial_burn", False),
830884
)

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)