Skip to content

Commit c197dd4

Browse files
Merge pull request #612 from RocketPy-Team/enh/show-variables-monte-carlo
ENH: Adds StochasticModel.visualize_attributes() method
2 parents b3f6ca7 + 46f4311 commit c197dd4

File tree

11 files changed

+2567
-2373
lines changed

11 files changed

+2567
-2373
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
4141

4242
### Changed
4343

44+
- ENH: Adds StochasticModel.visualize_attributes() method [#612](https://github.com/RocketPy-Team/RocketPy/pull/612)
4445
- DOC: Monte carlo documentation updates [#607](https://github.com/RocketPy-Team/RocketPy/pull/607)
4546
- MNT: refactor u_dot parachute method [#596](https://github.com/RocketPy-Team/RocketPy/pull/596)
4647
- BLD: Change setup.py to pyproject.toml [#589](https://github.com/RocketPy-Team/RocketPy/pull/589)

docs/notebooks/monte_carlo_analysis/monte_carlo_analysis_outputs/monte_carlo_class_example.inputs.txt

Lines changed: 1000 additions & 1000 deletions
Large diffs are not rendered by default.

docs/notebooks/monte_carlo_analysis/monte_carlo_analysis_outputs/monte_carlo_class_example.kml

Lines changed: 3 additions & 3 deletions
Large diffs are not rendered by default.

docs/notebooks/monte_carlo_analysis/monte_carlo_analysis_outputs/monte_carlo_class_example.outputs.txt

Lines changed: 1000 additions & 1000 deletions
Large diffs are not rendered by default.

docs/notebooks/monte_carlo_analysis/monte_carlo_class_usage.ipynb

Lines changed: 474 additions & 276 deletions
Large diffs are not rendered by default.

rocketpy/plots/monte_carlo_plots.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import matplotlib.pyplot as plt
2+
23
from ..tools import generate_monte_carlo_ellipses
34

45

rocketpy/stochastic/stochastic_environment.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,6 @@ def __init__(
8787
)
8888
self._validate_ensemble(ensemble_member, environment)
8989

90-
def __str__(self):
91-
# special str for environment because of datetime
92-
s = ""
93-
for key, value in self.__dict__.items():
94-
if key.startswith("_"):
95-
continue # Skip attributes starting with underscore
96-
if isinstance(value, tuple):
97-
try:
98-
# Format the tuple as a string with the mean and standard deviation.
99-
value_str = (
100-
f"{value[0]:.5f} ± {value[1]:.5f} "
101-
f"(numpy.random.{value[2].__name__})"
102-
)
103-
except AttributeError:
104-
# treats date attribute
105-
value_str = str(value)
106-
else:
107-
# Otherwise, just use the default string representation of the value.
108-
value_str = str(value)
109-
s += f"{key}: {value_str}\n"
110-
return s.strip()
111-
11290
def _validate_ensemble(self, ensemble_member, environment):
11391
"""Validates the ensemble member input argument. If the environment
11492
does not have ensemble members, the ensemble member input argument

rocketpy/stochastic/stochastic_model.py

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,28 +89,8 @@ def __init__(self, object, **kwargs):
8989
attr_value = [getattr(self.object, input_name)]
9090
setattr(self, input_name, attr_value)
9191

92-
def __str__(self):
93-
"""
94-
Returns a string representation of the StochasticModel object.
95-
96-
Returns
97-
-------
98-
str
99-
String containing the class attributes and their values.
100-
"""
101-
s = ""
102-
for key, value in self.__dict__.items():
103-
if key.startswith("_"):
104-
continue
105-
if isinstance(value, tuple):
106-
value_str = (
107-
f"{value[0]:.5f} ± {value[1]:.5f} "
108-
f"(numpy.random.{value[2].__name__})"
109-
)
110-
else:
111-
value_str = str(value)
112-
s += f"{key}: {value_str}\n"
113-
return s.strip()
92+
def __repr__(self):
93+
return f"'{self.__class__.__name__}() object'"
11494

11595
def _validate_tuple(self, input_name, input_value, getattr=getattr):
11696
"""
@@ -492,3 +472,69 @@ def dict_generator(self):
492472
generated_dict[arg] = choice(value) if value else value
493473
self.last_rnd_dict = generated_dict
494474
yield generated_dict
475+
476+
def visualize_attributes(self):
477+
"""
478+
This method prints a report of the attributes stored in the Stochastic
479+
Model object. The report includes the variable name, the nominal value,
480+
the standard deviation, and the distribution function used to generate
481+
the random attributes.
482+
"""
483+
484+
def format_attribute(attr, value):
485+
if isinstance(value, list):
486+
return (
487+
f"\t{attr.ljust(max_str_length)} {value[0]}"
488+
if len(value) == 1
489+
else f"\t{attr} {value}"
490+
)
491+
elif isinstance(value, tuple):
492+
nominal_value, std_dev, dist_func = value
493+
return (
494+
f"\t{attr.ljust(max_str_length)} "
495+
f"{nominal_value:.5f} ± "
496+
f"{std_dev:.5f} ({dist_func.__name__})"
497+
)
498+
return None
499+
500+
attributes = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
501+
max_str_length = max(len(var) for var in attributes) + 2
502+
503+
report = [
504+
f"Reporting the attributes of the `{self.__class__.__name__}` object:"
505+
]
506+
507+
# Sorting alphabetically makes the report more readable
508+
items = attributes.items()
509+
items = sorted(items, key=lambda x: x[0])
510+
511+
to_exclude = ["object", "last_rnd_dict", "exception_list", "parachutes"]
512+
items = [item for item in items if item[0] not in to_exclude]
513+
514+
constant_attributes = [
515+
attr for attr, val in items if isinstance(val, list) and len(val) == 1
516+
]
517+
tuple_attributes = [attr for attr, val in items if isinstance(val, tuple)]
518+
list_attributes = [
519+
attr for attr, val in items if isinstance(val, list) and len(val) > 1
520+
]
521+
522+
if constant_attributes:
523+
report.append("\nConstant Attributes:")
524+
report.extend(
525+
format_attribute(attr, attributes[attr]) for attr in constant_attributes
526+
)
527+
528+
if tuple_attributes:
529+
report.append("\nStochastic Attributes:")
530+
report.extend(
531+
format_attribute(attr, attributes[attr]) for attr in tuple_attributes
532+
)
533+
534+
if list_attributes:
535+
report.append("\nStochastic Attributes with choice of values:")
536+
report.extend(
537+
format_attribute(attr, attributes[attr]) for attr in list_attributes
538+
)
539+
540+
print("\n".join(filter(None, report)))

rocketpy/stochastic/stochastic_parachute.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,6 @@ def __init__(
8383
name=None,
8484
)
8585

86-
def __repr__(self):
87-
return (
88-
f"StochasticParachute("
89-
f"parachute={self.object}, "
90-
f"cd_s={self.cd_s}, "
91-
f"trigger={self.trigger}, "
92-
f"sampling_rate={self.sampling_rate}, "
93-
f"lag={self.lag}, "
94-
f"noise={self.noise})"
95-
)
96-
9786
def _validate_trigger(self, trigger):
9887
"""Validates the trigger input. If the trigger input argument is not
9988
None, it must be:

rocketpy/stochastic/stochastic_rocket.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -163,45 +163,6 @@ def __init__(
163163
self.rail_buttons = Components()
164164
self.parachutes = []
165165

166-
def __str__(self):
167-
# special str for rocket because of the components and parachutes
168-
s = ""
169-
for key, value in self.__dict__.items():
170-
if key.startswith("_"):
171-
continue # Skip attributes starting with underscore
172-
if isinstance(value, tuple):
173-
# Format the tuple as a string with the mean and standard deviation.
174-
value_str = (
175-
f"{value[0]:.5f} ± {value[1]:.5f} "
176-
f"(numpy.random.{value[2].__name__})"
177-
)
178-
s += f"{key}: {value_str}\n"
179-
elif isinstance(value, Components):
180-
# Format Components as string with the mean and std deviation
181-
s += f"{key}:\n"
182-
if len(value) == 0:
183-
s += "\tNone\n"
184-
for component in value:
185-
s += f"\t{component.component.__class__.__name__} "
186-
if isinstance(component.position, tuple):
187-
s += f"at position: {component.position[0]:.5f} ± "
188-
s += f"{component.position[1]:.5f} "
189-
s += f"(numpy.random.{component.position[2].__name__})\n"
190-
elif isinstance(component.position, list):
191-
s += f"at position: {component.position}\n"
192-
else:
193-
s += f"at position: {component.position:.5f}\n"
194-
else:
195-
# Otherwise, just use the default string representation of the value.
196-
value_str = str(value)
197-
if isinstance(value, list) and len(value) > 0:
198-
if isinstance(value[0], (StochasticParachute)):
199-
value_str = ""
200-
for parachute in value:
201-
value_str += f"\n\t{parachute.name[0]} "
202-
s += f"{key}: {value_str}\n"
203-
return s.strip()
204-
205166
def add_motor(self, motor, position=None):
206167
"""Adds a stochastic motor to the stochastic rocket. If a motor is
207168
already present, it will be replaced.

tests/unit/test_stochastic_model.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import pytest
2+
3+
4+
@pytest.mark.parametrize(
5+
"fixture_name",
6+
[
7+
"stochastic_rail_buttons",
8+
"stochastic_main_parachute",
9+
"stochastic_environment",
10+
"stochastic_tail",
11+
"stochastic_calisto",
12+
],
13+
)
14+
def test_visualize_attributes(request, fixture_name):
15+
"""Tests the visualize_attributes method of the StochasticModel class. This
16+
test verifies if the method returns None, which means that the method is
17+
running without breaking.
18+
"""
19+
fixture = request.getfixturevalue(fixture_name)
20+
assert fixture.visualize_attributes() is None

0 commit comments

Comments
 (0)