From 0c93bf7058222e4997df52981d45a9da4ad25e17 Mon Sep 17 00:00:00 2001 From: Ting-Hong Shieh <32212900+ting-hong-shieh@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:11:03 +0800 Subject: [PATCH] BUG: skip scalar statistics for structured Monte Carlo results (#1145) --- rocketpy/simulation/monte_carlo.py | 34 ++++++++++++--------- tests/unit/simulation/test_monte_carlo.py | 37 +++++++++++++++++++++++ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/rocketpy/simulation/monte_carlo.py b/rocketpy/simulation/monte_carlo.py index 21c665d01..2640bc5b1 100644 --- a/rocketpy/simulation/monte_carlo.py +++ b/rocketpy/simulation/monte_carlo.py @@ -18,6 +18,7 @@ import os import traceback import warnings +from numbers import Real from pathlib import Path from time import time @@ -1170,8 +1171,12 @@ def set_results(self): def set_processed_results(self): """ - Creates a dictionary with the mean and standard deviation of each - parameter available in the results. + Create summary statistics for scalar, real-valued results. + + Structured and non-numeric results remain available in ``results``. + Their entry in ``processed_results`` contains five ``None`` values + because a scalar mean, median, standard deviation, and prediction + interval are not defined for those values. Returns ------- @@ -1179,19 +1184,18 @@ def set_processed_results(self): """ self.processed_results = {} for result, values in self.results.items(): - try: - mean = np.mean(values) - stdev = np.std(values) - self.processed_results[result] = (mean, stdev) - pi_low = np.quantile(values, 0.025) - pi_high = np.quantile(values, 0.975) - median = np.median(values) - except TypeError: - mean = None - stdev = None - pi_low = None - pi_high = None - median = None + if not values or not all( + isinstance(value, Real) and not isinstance(value, (bool, np.bool_)) + for value in values + ): + self.processed_results[result] = (None, None, None, None, None) + continue + + mean = np.mean(values) + stdev = np.std(values) + pi_low = np.quantile(values, 0.025) + pi_high = np.quantile(values, 0.975) + median = np.median(values) self.processed_results[result] = (mean, median, stdev, pi_low, pi_high) # Import methods diff --git a/tests/unit/simulation/test_monte_carlo.py b/tests/unit/simulation/test_monte_carlo.py index 7e2e68804..d3ef02be9 100644 --- a/tests/unit/simulation/test_monte_carlo.py +++ b/tests/unit/simulation/test_monte_carlo.py @@ -252,6 +252,43 @@ def __init__(self): self.num_of_loaded_sims = 3 +def test_set_processed_results_summarizes_real_scalars(): + mc = MockMonteCarloWithLogs() + mc.results = {"value": [1, np.int64(2), np.float32(3)]} + + mc.set_processed_results() + + mean, median, stdev, pi_low, pi_high = mc.processed_results["value"] + assert mean == pytest.approx(2) + assert median == pytest.approx(2) + assert stdev == pytest.approx(np.std([1, 2, 3])) + assert pi_low == pytest.approx(np.quantile([1, 2, 3], 0.025)) + assert pi_high == pytest.approx(np.quantile([1, 2, 3], 0.975)) + + +@pytest.mark.parametrize( + "values", + [ + ["ascent", "descent"], + [[1, 2], [3, 4]], + [[1], [2, 3]], + [{"x": 1}, {"x": 2}], + [np.array([1, 2]), np.array([3, 4])], + [1, "two"], + [True, False], + [], + ], +) +def test_set_processed_results_preserves_structured_results(values): + mc = MockMonteCarloWithLogs() + mc.results = {"structured": values} + + mc.set_processed_results() + + assert mc.results["structured"] is values + assert mc.processed_results["structured"] == (None, None, None, None, None) + + def test_export_outputs_to_csv(tmp_path): """Tests that outputs are correctly exported to CSV.""" mc = MockMonteCarloWithLogs()