Skip to content

Commit d945ea0

Browse files
MNT: make Parachute base serialization inheritance-safe
The abstract Parachute.to_dict/from_dict referenced model-specific attributes (cd_s, radius, drag_coefficient, height, porosity) that the base no longer owns, and from_dict forwarded a `parachute_type` kwarg that concrete constructors do not accept. They worked only because HemisphericalParachute fully overrode them; any future parachute model inheriting them would hit AttributeError/TypeError. Reduce the base methods to the fields the base actually owns and expose a shared `_decode_trigger` helper. HemisphericalParachute now builds on `super().to_dict()` (matching the repo-wide serialization convention) and adds only its own fields, removing the verbatim duplication. Serialized output and round-trip behavior are unchanged. Drop the now-unused imports.
1 parent 45eb473 commit d945ea0

2 files changed

Lines changed: 29 additions & 63 deletions

File tree

rocketpy/rocket/parachutes/hemispherical_parachute.py

Lines changed: 12 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import numpy as np
22

3-
from rocketpy.tools import from_hex_decode, to_hex_encode
4-
5-
from ...mathutils.function import Function
63
from .parachute import Parachute
74

85

@@ -374,54 +371,24 @@ def u_dot(self, t, u, flight_information, post_processing=False):
374371

375372
# serialization methods
376373
def to_dict(self, **kwargs):
377-
allow_pickle = kwargs.get("allow_pickle", True)
378-
trigger = self.trigger
379-
380-
if callable(self.trigger) and not isinstance(self.trigger, Function):
381-
if allow_pickle:
382-
trigger = to_hex_encode(trigger)
383-
else:
384-
trigger = trigger.__name__
385-
386-
data = {
387-
"name": self.name,
388-
"parachute_type": self.parachute_type,
389-
"cd_s": self.cd_s,
390-
"trigger": trigger,
391-
"sampling_rate": self.sampling_rate,
392-
"lag": self.lag,
393-
"noise": self.noise,
394-
"radius": self.radius,
395-
"drag_coefficient": self.drag_coefficient,
396-
"height": self.height,
397-
"porosity": self.porosity,
398-
}
399-
400-
if kwargs.get("include_outputs", False):
401-
data["noise_signal"] = self.noise_signal
402-
data["noise_function"] = (
403-
to_hex_encode(self.noise_function)
404-
if allow_pickle
405-
else self.noise_function.__name__
406-
)
407-
data["noisy_pressure_signal"] = self.noisy_pressure_signal
408-
data["clean_pressure_signal"] = self.clean_pressure_signal
409-
374+
data = super().to_dict(**kwargs)
375+
data.update(
376+
{
377+
"cd_s": self.cd_s,
378+
"radius": self.radius,
379+
"drag_coefficient": self.drag_coefficient,
380+
"height": self.height,
381+
"porosity": self.porosity,
382+
}
383+
)
410384
return data
411385

412386
@classmethod
413387
def from_dict(cls, data):
414-
trigger = data["trigger"]
415-
416-
try:
417-
trigger = from_hex_decode(trigger)
418-
except (TypeError, ValueError):
419-
pass
420-
421-
parachute = cls(
388+
return cls(
422389
name=data["name"],
423390
cd_s=data["cd_s"],
424-
trigger=trigger,
391+
trigger=cls._decode_trigger(data["trigger"]),
425392
sampling_rate=data["sampling_rate"],
426393
lag=data["lag"],
427394
noise=data["noise"],
@@ -430,5 +397,3 @@ def from_dict(cls, data):
430397
height=data.get("height", None),
431398
porosity=data.get("porosity", 0.0432),
432399
)
433-
434-
return parachute

rocketpy/rocket/parachutes/parachute.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,9 @@ def u_dot(self, t, u, flight_information, post_processing=False):
351351
"""
352352

353353
def to_dict(self, **kwargs):
354+
"""Serializes the fields shared by every parachute model. Subclasses
355+
should call ``super().to_dict(**kwargs)`` and add their model-specific
356+
attributes to the returned dictionary."""
354357
allow_pickle = kwargs.get("allow_pickle", True)
355358
trigger = self.trigger
356359

@@ -363,15 +366,10 @@ def to_dict(self, **kwargs):
363366
data = {
364367
"name": self.name,
365368
"parachute_type": self.parachute_type,
366-
"cd_s": self.cd_s,
367369
"trigger": trigger,
368370
"sampling_rate": self.sampling_rate,
369371
"lag": self.lag,
370372
"noise": self.noise,
371-
"radius": self.radius,
372-
"drag_coefficient": self.drag_coefficient,
373-
"height": self.height,
374-
"porosity": self.porosity,
375373
}
376374

377375
if kwargs.get("include_outputs", False):
@@ -386,22 +384,25 @@ def to_dict(self, **kwargs):
386384

387385
return data
388386

389-
@classmethod
390-
def from_dict(cls, data):
391-
trigger = data["trigger"]
392-
387+
@staticmethod
388+
def _decode_trigger(trigger):
389+
"""Decodes a (possibly hex-encoded) serialized trigger back into a
390+
callable, leaving numeric/string triggers untouched."""
393391
try:
394-
trigger = from_hex_decode(trigger)
392+
return from_hex_decode(trigger)
395393
except (TypeError, ValueError):
396-
pass
394+
return trigger
397395

398-
parachute = cls(
396+
@classmethod
397+
def from_dict(cls, data):
398+
"""Reconstructs a parachute from the fields shared by every model.
399+
Subclasses with additional constructor arguments (e.g. ``cd_s``) must
400+
override this method; ``parachute_type`` is not forwarded because each
401+
concrete model sets it itself."""
402+
return cls(
399403
name=data["name"],
400-
parachute_type=data["parachute_type"],
401-
trigger=trigger,
404+
trigger=cls._decode_trigger(data["trigger"]),
402405
sampling_rate=data["sampling_rate"],
403406
lag=data["lag"],
404407
noise=data["noise"],
405408
)
406-
407-
return parachute

0 commit comments

Comments
 (0)