Skip to content

Commit e0173e2

Browse files
Malmahrouqi3Malmahrouqi3
andauthored
ENH: Adaptive Monte Carlo via Convergence Criteria (#922)
* ENH: added a new function (simulate_convergence) * DOC: added a cell to show simulate_convergence function usage * TST: integration test for simulate_convergence * DOC: updated changelog for this PR * ENH: ran black to lint intg test file * new fixes thx to copilot comments * linted rocketpy/simulation/monte_carlo.py --------- Co-authored-by: Malmahrouqi3 <mohdsaid497566@gmail.com>
1 parent 9cd2d34 commit e0173e2

4 files changed

Lines changed: 118 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ Attention: The newest changes should be on top -->
3232

3333
### Added
3434

35-
-
35+
- ENH: Adaptive Monte Carlo via Convergence Criteria [#922](https://github.com/RocketPy-Team/RocketPy/pull/922)
36+
- TST: Add acceptance tests for 3DOF flight simulation based on Bella Lui rocket [#914](https://github.com/RocketPy-Team/RocketPy/pull/914)
3637

3738
### Changed
3839

docs/notebooks/monte_carlo_analysis/monte_carlo_class_usage.ipynb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,28 @@
800800
")"
801801
]
802802
},
803+
{
804+
"cell_type": "markdown",
805+
"metadata": {},
806+
"source": [
807+
"Alternatively, we can target an attribute using the method `MonteCarlo.simulate_convergence()` such that when the tolerance is met, the flight simulations would terminate early."
808+
]
809+
},
810+
{
811+
"cell_type": "code",
812+
"execution_count": null,
813+
"metadata": {},
814+
"outputs": [],
815+
"source": [
816+
"test_dispersion.simulate_convergence(\n",
817+
" target_attribute=\"apogee_time\",\n",
818+
" target_confidence=0.95,\n",
819+
" tolerance=0.5, # in seconds\n",
820+
" max_simulations=1000,\n",
821+
" batch_size=50,\n",
822+
")"
823+
]
824+
},
803825
{
804826
"attachments": {},
805827
"cell_type": "markdown",

rocketpy/simulation/monte_carlo.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,73 @@ def estimate_confidence_interval(
525525

526526
return res.confidence_interval
527527

528+
def simulate_convergence(
529+
self,
530+
target_attribute="apogee_time",
531+
target_confidence=0.95,
532+
tolerance=0.5,
533+
max_simulations=1000,
534+
batch_size=50,
535+
parallel=False,
536+
n_workers=None,
537+
):
538+
"""Run Monte Carlo simulations in batches until the confidence interval
539+
width converges within the specified tolerance or the maximum number of
540+
simulations is reached.
541+
542+
Parameters
543+
----------
544+
target_attribute : str
545+
The target attribute to track its convergence (e.g., "apogee", "apogee_time", etc.).
546+
target_confidence : float, optional
547+
The confidence level for the interval (between 0 and 1). Default is 0.95.
548+
tolerance : float, optional
549+
The desired width of the confidence interval in seconds, meters, or other units. Default is 0.5.
550+
max_simulations : int, optional
551+
The maximum number of simulations to run to avoid infinite loops. Default is 1000.
552+
batch_size : int, optional
553+
The number of simulations to run in each batch. Default is 50.
554+
parallel : bool, optional
555+
Whether to run simulations in parallel. Default is False.
556+
n_workers : int, optional
557+
The number of worker processes to use if running in parallel. Default is None.
558+
559+
Returns
560+
-------
561+
confidence_interval_history : list of float
562+
History of confidence interval widths, one value per batch of simulations.
563+
The last element corresponds to the width when the simulation stopped for
564+
either meeting the tolerance or reaching the maximum number of simulations.
565+
"""
566+
567+
self.import_outputs(self.filename.with_suffix(".outputs.txt"))
568+
confidence_interval_history = []
569+
570+
while self.num_of_loaded_sims < max_simulations:
571+
total_sims = min(self.num_of_loaded_sims + batch_size, max_simulations)
572+
573+
self.simulate(
574+
number_of_simulations=total_sims,
575+
append=True,
576+
include_function_data=False,
577+
parallel=parallel,
578+
n_workers=n_workers,
579+
)
580+
581+
self.import_outputs(self.filename.with_suffix(".outputs.txt"))
582+
583+
ci = self.estimate_confidence_interval(
584+
attribute=target_attribute,
585+
confidence_level=target_confidence,
586+
)
587+
588+
confidence_interval_history.append(float(ci.high - ci.low))
589+
590+
if float(ci.high - ci.low) <= tolerance:
591+
break
592+
593+
return confidence_interval_history
594+
528595
def __evaluate_flight_inputs(self, sim_idx):
529596
"""Evaluates the inputs of a single flight simulation.
530597

tests/integration/simulation/test_monte_carlo.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,30 @@ def invalid_data_collector(flight):
236236
monte_carlo_calisto.simulate(number_of_simulations=10, append=False)
237237
finally:
238238
_post_test_file_cleanup()
239+
240+
241+
@pytest.mark.slow
242+
def test_monte_carlo_simulate_convergence(monte_carlo_calisto):
243+
"""Tests the simulate_convergence method of the MonteCarlo class.
244+
245+
Parameters
246+
----------
247+
monte_carlo_calisto : MonteCarlo
248+
The MonteCarlo object, this is a pytest fixture.
249+
"""
250+
try:
251+
ci_history = monte_carlo_calisto.simulate_convergence(
252+
target_attribute="apogee",
253+
target_confidence=0.95,
254+
tolerance=5.0,
255+
max_simulations=20,
256+
batch_size=5,
257+
parallel=False,
258+
)
259+
260+
assert isinstance(ci_history, list)
261+
assert all(isinstance(width, float) for width in ci_history)
262+
assert len(ci_history) >= 1
263+
assert monte_carlo_calisto.num_of_loaded_sims <= 20
264+
finally:
265+
_post_test_file_cleanup()

0 commit comments

Comments
 (0)