Skip to content

Commit 4d14ce1

Browse files
authored
Merge branch 'develop' into enh/adaptive-monte-carlo-convergence
2 parents f6166b8 + 8cdb69f commit 4d14ce1

File tree

8 files changed

+114
-58
lines changed

8 files changed

+114
-58
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Attention: The newest changes should be on top -->
6161
- BUG: energy_data plot not working for 3 dof sims [[#906](https://github.com/RocketPy-Team/RocketPy/issues/906)]
6262
- BUG: Fix CSV column header spacing in FlightDataExporter [#864](https://github.com/RocketPy-Team/RocketPy/issues/864)
6363
- BUG: Fix parallel Monte Carlo simulation showing incorrect iteration count [#806](https://github.com/RocketPy-Team/RocketPy/pull/806)
64-
64+
- BUG: Duplicate _controllers in Flight.TimeNodes.merge() [#931](https://github.com/RocketPy-Team/RocketPy/pull/931)
6565

6666
## [v1.11.0] - 2025-11-01
6767

rocketpy/environment/environment.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,22 +2444,26 @@ def add_wind_gust(self, wind_gust_x, wind_gust_y):
24442444

24452445
# Reset wind heading and velocity magnitude
24462446
self.wind_heading = Function(
2447-
lambda h: (180 / np.pi)
2448-
* np.arctan2(
2449-
self.wind_velocity_x.get_value_opt(h),
2450-
self.wind_velocity_y.get_value_opt(h),
2451-
)
2452-
% 360,
2447+
lambda h: (
2448+
(180 / np.pi)
2449+
* np.arctan2(
2450+
self.wind_velocity_x.get_value_opt(h),
2451+
self.wind_velocity_y.get_value_opt(h),
2452+
)
2453+
% 360
2454+
),
24532455
"Height (m)",
24542456
"Wind Heading (degrees)",
24552457
extrapolation="constant",
24562458
)
24572459
self.wind_speed = Function(
24582460
lambda h: (
2459-
self.wind_velocity_x.get_value_opt(h) ** 2
2460-
+ self.wind_velocity_y.get_value_opt(h) ** 2
2461-
)
2462-
** 0.5,
2461+
(
2462+
self.wind_velocity_x.get_value_opt(h) ** 2
2463+
+ self.wind_velocity_y.get_value_opt(h) ** 2
2464+
)
2465+
** 0.5
2466+
),
24632467
"Height (m)",
24642468
"Wind Speed (m/s)",
24652469
extrapolation="constant",

rocketpy/rocket/aero_surface/nose_cone.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,16 @@ def theta_lvhaack(x):
238238
return np.arccos(1 - 2 * max(min(x / self.length, 1), 0))
239239

240240
self.y_nosecone = Function(
241-
lambda x: self.base_radius
242-
* (
243-
theta_lvhaack(x)
244-
- np.sin(2 * theta_lvhaack(x)) / 2
245-
+ (np.sin(theta_lvhaack(x)) ** 3) / 3
241+
lambda x: (
242+
self.base_radius
243+
* (
244+
theta_lvhaack(x)
245+
- np.sin(2 * theta_lvhaack(x)) / 2
246+
+ (np.sin(theta_lvhaack(x)) ** 3) / 3
247+
)
248+
** (0.5)
249+
/ (np.pi**0.5)
246250
)
247-
** (0.5)
248-
/ (np.pi**0.5)
249251
)
250252

251253
case "tangent" | "tangentogive" | "ogive":
@@ -258,15 +260,19 @@ def theta_lvhaack(x):
258260
area = np.pi * self.base_radius**2
259261
self.k = 1 - volume / (area * self.length)
260262
self.y_nosecone = Function(
261-
lambda x: np.sqrt(rho**2 - (min(x - self.length, 0)) ** 2)
262-
+ (self.base_radius - rho)
263+
lambda x: (
264+
np.sqrt(rho**2 - (min(x - self.length, 0)) ** 2)
265+
+ (self.base_radius - rho)
266+
)
263267
)
264268

265269
case "elliptical":
266270
self.k = 1 / 3
267271
self.y_nosecone = Function(
268-
lambda x: self.base_radius
269-
* np.sqrt(1 - ((x - self.length) / self.length) ** 2)
272+
lambda x: (
273+
self.base_radius
274+
* np.sqrt(1 - ((x - self.length) / self.length) ** 2)
275+
)
270276
)
271277

272278
case "vonkarman":
@@ -276,15 +282,20 @@ def theta_vonkarman(x):
276282
return np.arccos(1 - 2 * max(min(x / self.length, 1), 0))
277283

278284
self.y_nosecone = Function(
279-
lambda x: self.base_radius
280-
* (theta_vonkarman(x) - np.sin(2 * theta_vonkarman(x)) / 2) ** (0.5)
281-
/ (np.pi**0.5)
285+
lambda x: (
286+
self.base_radius
287+
* (theta_vonkarman(x) - np.sin(2 * theta_vonkarman(x)) / 2)
288+
** (0.5)
289+
/ (np.pi**0.5)
290+
)
282291
)
283292
case "parabolic":
284293
self.k = 0.5
285294
self.y_nosecone = Function(
286-
lambda x: self.base_radius
287-
* ((2 * x / self.length - (x / self.length) ** 2) / (2 - 1))
295+
lambda x: (
296+
self.base_radius
297+
* ((2 * x / self.length - (x / self.length) ** 2) / (2 - 1))
298+
)
288299
)
289300
case "powerseries":
290301
self.k = (2 * self.power) / ((2 * self.power) + 1)

rocketpy/rocket/aero_surface/tail.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,12 @@ def evaluate_lift_coefficient(self):
164164
"""
165165
# Calculate clalpha
166166
self.clalpha = Function(
167-
lambda mach: 2
168-
* (
169-
(self.bottom_radius / self.rocket_radius) ** 2
170-
- (self.top_radius / self.rocket_radius) ** 2
167+
lambda mach: (
168+
2
169+
* (
170+
(self.bottom_radius / self.rocket_radius) ** 2
171+
- (self.top_radius / self.rocket_radius) ** 2
172+
)
171173
),
172174
"Mach",
173175
f"Lift coefficient derivative for {self.name}",

rocketpy/rocket/parachute.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,10 @@ def __init__(
211211
)
212212

213213
alpha, beta = self.noise_corr
214-
self.noise_function = lambda: alpha * self.noise_signal[-1][
215-
1
216-
] + beta * np.random.normal(noise[0], noise[1])
214+
self.noise_function = lambda: (
215+
alpha * self.noise_signal[-1][1]
216+
+ beta * np.random.normal(noise[0], noise[1])
217+
)
217218

218219
self.prints = _ParachutePrints(self)
219220

rocketpy/rocket/rocket.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -663,12 +663,14 @@ def evaluate_stability_margin(self):
663663
self.stability_margin.set_source(
664664
lambda mach, time: (
665665
(
666-
self.center_of_mass.get_value_opt(time)
667-
- self.cp_position.get_value_opt(mach)
666+
(
667+
self.center_of_mass.get_value_opt(time)
668+
- self.cp_position.get_value_opt(mach)
669+
)
670+
/ (2 * self.radius)
668671
)
669-
/ (2 * self.radius)
672+
* self._csys
670673
)
671-
* self._csys
672674
)
673675
return self.stability_margin
674676

@@ -685,10 +687,12 @@ def evaluate_static_margin(self):
685687
# Calculate static margin
686688
self.static_margin.set_source(
687689
lambda time: (
688-
self.center_of_mass.get_value_opt(time)
689-
- self.cp_position.get_value_opt(0)
690+
(
691+
self.center_of_mass.get_value_opt(time)
692+
- self.cp_position.get_value_opt(0)
693+
)
694+
/ (2 * self.radius)
690695
)
691-
/ (2 * self.radius)
692696
)
693697
# Change sign if coordinate system is upside down
694698
self.static_margin *= self._csys

rocketpy/simulation/flight.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -759,11 +759,12 @@ def __simulate(self, verbose):
759759
lambda self, parachute_porosity=parachute.porosity: setattr(
760760
self, "parachute_porosity", parachute_porosity
761761
),
762-
lambda self,
763-
added_mass_coefficient=parachute.added_mass_coefficient: setattr(
764-
self,
765-
"parachute_added_mass_coefficient",
766-
added_mass_coefficient,
762+
lambda self, added_mass_coefficient=parachute.added_mass_coefficient: (
763+
setattr(
764+
self,
765+
"parachute_added_mass_coefficient",
766+
added_mass_coefficient,
767+
)
767768
),
768769
]
769770
self.flight_phases.add_phase(
@@ -981,11 +982,12 @@ def __check_and_handle_parachute_triggers(
981982
lambda self, parachute_porosity=parachute.porosity: setattr(
982983
self, "parachute_porosity", parachute_porosity
983984
),
984-
lambda self,
985-
added_mass_coefficient=parachute.added_mass_coefficient: setattr(
986-
self,
987-
"parachute_added_mass_coefficient",
988-
added_mass_coefficient,
985+
lambda self, added_mass_coefficient=parachute.added_mass_coefficient: (
986+
setattr(
987+
self,
988+
"parachute_added_mass_coefficient",
989+
added_mass_coefficient,
990+
)
989991
),
990992
]
991993
self.flight_phases.add_phase(
@@ -1387,11 +1389,12 @@ def __check_overshootable_parachute_triggers(
13871389
lambda self, parachute_porosity=parachute.porosity: setattr(
13881390
self, "parachute_porosity", parachute_porosity
13891391
),
1390-
lambda self,
1391-
added_mass_coefficient=parachute.added_mass_coefficient: setattr(
1392-
self,
1393-
"parachute_added_mass_coefficient",
1394-
added_mass_coefficient,
1392+
lambda self, added_mass_coefficient=parachute.added_mass_coefficient: (
1393+
setattr(
1394+
self,
1395+
"parachute_added_mass_coefficient",
1396+
added_mass_coefficient,
1397+
)
13951398
),
13961399
]
13971400
self.flight_phases.add_phase(
@@ -3159,7 +3162,7 @@ def max_acceleration_power_off_time(self):
31593162
max_acceleration_time_index = np.argmax(
31603163
self.acceleration[burn_out_time_index:, 1]
31613164
)
3162-
return self.acceleration[max_acceleration_time_index, 0]
3165+
return self.acceleration[burn_out_time_index + max_acceleration_time_index, 0]
31633166

31643167
@cached_property
31653168
def max_acceleration_power_off(self):
@@ -4586,7 +4589,6 @@ def merge(self):
45864589
tmp_dict[time]._controllers += node._controllers
45874590
tmp_dict[time].callbacks += node.callbacks
45884591
tmp_dict[time]._component_sensors += node._component_sensors
4589-
tmp_dict[time]._controllers += node._controllers
45904592
except KeyError:
45914593
# If the node does not exist, add it to the dictionary
45924594
tmp_dict[time] = node

tests/unit/simulation/test_flight.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,3 +659,35 @@ def test_stability_static_margins(
659659
assert np.all(moments / wind_sign <= 0)
660660
else: # static_margin == 0
661661
assert np.all(np.abs(moments) <= 1e-10)
662+
663+
664+
def test_max_acceleration_power_off_time_with_controllers(
665+
flight_calisto_air_brakes,
666+
):
667+
"""Test that max_acceleration_power_off_time returns a valid time when
668+
controllers are present (e.g., air brakes). This is a regression test for
669+
a bug where the time was always returned as 0.0.
670+
671+
Parameters
672+
----------
673+
flight_calisto_air_brakes : rocketpy.Flight
674+
Flight object with air brakes. See the conftest.py file for more info
675+
regarding this pytest fixture.
676+
"""
677+
test = flight_calisto_air_brakes
678+
burn_out_time = test.rocket.motor.burn_out_time
679+
680+
# The max_acceleration_power_off_time should be at or after motor burn out
681+
# It should NOT be 0.0, which was the bug behavior
682+
assert test.max_acceleration_power_off_time > 0, (
683+
"max_acceleration_power_off_time should not be zero"
684+
)
685+
assert test.max_acceleration_power_off_time >= burn_out_time - 0.01, (
686+
f"max_acceleration_power_off_time ({test.max_acceleration_power_off_time}) "
687+
f"should be at or after burn_out_time ({burn_out_time})"
688+
)
689+
690+
# Also verify max_acceleration_power_off is positive
691+
assert test.max_acceleration_power_off > 0, (
692+
"max_acceleration_power_off should be greater than zero"
693+
)

0 commit comments

Comments
 (0)