|
1 | 1 | import matplotlib as plt |
2 | 2 | import numpy as np |
| 3 | +import pytest |
| 4 | + |
| 5 | +from rocketpy.simulation import MonteCarlo |
3 | 6 |
|
4 | 7 | plt.rcParams.update({"figure.max_open_warning": 0}) |
5 | 8 |
|
@@ -60,3 +63,125 @@ def test_stochastic_calisto_create_object_with_static_margin(stochastic_calisto) |
60 | 63 |
|
61 | 64 | assert np.isclose(np.mean(all_margins), 2.2625350013000434, rtol=0.15) |
62 | 65 | assert np.isclose(np.std(all_margins), 0.1, atol=0.2) |
| 66 | + |
| 67 | + |
| 68 | +class MockMonteCarlo(MonteCarlo): |
| 69 | + """Create a mock class to test the method without running a real simulation""" |
| 70 | + |
| 71 | + def __init__(self): |
| 72 | + # pylint: disable=super-init-not-called |
| 73 | + |
| 74 | + # Simulate pre-calculated results |
| 75 | + # Example: a normal distribution centered on 100 for the apogee |
| 76 | + self.results = { |
| 77 | + "apogee": [98, 102, 100, 99, 101, 100, 97, 103], |
| 78 | + "max_velocity": [250, 255, 245, 252, 248], |
| 79 | + "single_point": [100], |
| 80 | + "empty_attribute": [], |
| 81 | + } |
| 82 | + |
| 83 | + |
| 84 | +def test_estimate_confidence_interval_contains_known_mean(): |
| 85 | + """Checks that the confidence interval contains the known mean.""" |
| 86 | + mc = MockMonteCarlo() |
| 87 | + |
| 88 | + ci = mc.estimate_confidence_interval("apogee", confidence_level=0.95) |
| 89 | + |
| 90 | + assert ci.low < 100 < ci.high |
| 91 | + assert ci.low < ci.high |
| 92 | + |
| 93 | + |
| 94 | +def test_estimate_confidence_interval_supports_custom_statistic(): |
| 95 | + """Checks that the statistic can be changed (e.g., standard deviation instead of mean).""" |
| 96 | + mc = MockMonteCarlo() |
| 97 | + |
| 98 | + ci_std = mc.estimate_confidence_interval("apogee", statistic=np.std) |
| 99 | + |
| 100 | + assert ci_std.low > 0 |
| 101 | + assert ci_std.low < ci_std.high |
| 102 | + |
| 103 | + |
| 104 | +def test_estimate_confidence_interval_raises_value_error_when_attribute_missing(): |
| 105 | + """Checks that the code raises an error if the key does not exist.""" |
| 106 | + mc = MockMonteCarlo() |
| 107 | + |
| 108 | + # Request a variable that does not exist ("altitude" is not in our mock) |
| 109 | + with pytest.raises(ValueError) as excinfo: |
| 110 | + mc.estimate_confidence_interval("altitude") |
| 111 | + |
| 112 | + assert "not found in results" in str(excinfo.value) |
| 113 | + |
| 114 | + |
| 115 | +def test_estimate_confidence_interval_increases_width_with_higher_confidence_level(): |
| 116 | + """Checks that a higher confidence level yields a wider interval.""" |
| 117 | + mc = MockMonteCarlo() |
| 118 | + |
| 119 | + ci_90 = mc.estimate_confidence_interval("apogee", confidence_level=0.90) |
| 120 | + width_90 = ci_90.high - ci_90.low |
| 121 | + |
| 122 | + ci_99 = mc.estimate_confidence_interval("apogee", confidence_level=0.99) |
| 123 | + width_99 = ci_99.high - ci_99.low |
| 124 | + |
| 125 | + # The more confident we want to be (99%), the wider the interval must be |
| 126 | + assert width_99 >= width_90 |
| 127 | + |
| 128 | + |
| 129 | +def test_estimate_confidence_interval_raises_value_error_when_confidence_level_out_of_bounds(): |
| 130 | + """Checks that validation fails if confidence_level is not strictly between 0 and 1.""" |
| 131 | + mc = MockMonteCarlo() |
| 132 | + |
| 133 | + # Case 1: Value <= 0 |
| 134 | + with pytest.raises(ValueError, match="confidence_level must be between 0 and 1"): |
| 135 | + mc.estimate_confidence_interval("apogee", confidence_level=0) |
| 136 | + |
| 137 | + with pytest.raises(ValueError, match="confidence_level must be between 0 and 1"): |
| 138 | + mc.estimate_confidence_interval("apogee", confidence_level=-0.5) |
| 139 | + |
| 140 | + # Case 2: Value >= 1 |
| 141 | + with pytest.raises(ValueError, match="confidence_level must be between 0 and 1"): |
| 142 | + mc.estimate_confidence_interval("apogee", confidence_level=1) |
| 143 | + |
| 144 | + with pytest.raises(ValueError, match="confidence_level must be between 0 and 1"): |
| 145 | + mc.estimate_confidence_interval("apogee", confidence_level=1.5) |
| 146 | + |
| 147 | + |
| 148 | +def test_estimate_confidence_interval_raises_value_error_when_n_resamples_invalid(): |
| 149 | + """Checks that validation fails if n_resamples is not a positive integer.""" |
| 150 | + mc = MockMonteCarlo() |
| 151 | + |
| 152 | + # Case 1: Not an integer (e.g. float) |
| 153 | + with pytest.raises(ValueError, match="n_resamples must be a positive integer"): |
| 154 | + mc.estimate_confidence_interval("apogee", n_resamples=1000.5) |
| 155 | + |
| 156 | + # Case 2: Zero or Negative |
| 157 | + with pytest.raises(ValueError, match="n_resamples must be a positive integer"): |
| 158 | + mc.estimate_confidence_interval("apogee", n_resamples=0) |
| 159 | + |
| 160 | + with pytest.raises(ValueError, match="n_resamples must be a positive integer"): |
| 161 | + mc.estimate_confidence_interval("apogee", n_resamples=-100) |
| 162 | + |
| 163 | + |
| 164 | +def test_estimate_confidence_interval_raises_value_error_on_empty_data_list(): |
| 165 | + """Checks behavior when the attribute exists but contains no data (empty list).""" |
| 166 | + mc = MockMonteCarlo() |
| 167 | + |
| 168 | + with pytest.raises(ValueError): |
| 169 | + mc.estimate_confidence_interval("empty_attribute") |
| 170 | + |
| 171 | + |
| 172 | +def test_estimate_confidence_interval_handles_single_data_point(): |
| 173 | + """Checks behavior with only one data point. The CI should be [val, val].""" |
| 174 | + mc = MockMonteCarlo() |
| 175 | + |
| 176 | + with pytest.raises(ValueError): # two or more value |
| 177 | + mc.estimate_confidence_interval("single_point", n_resamples=50) |
| 178 | + |
| 179 | + |
| 180 | +def test_estimate_confidence_interval_raises_type_error_for_invalid_statistic(): |
| 181 | + """Checks that passing a non-callable object (like a string/int) as statistic raises TypeError.""" |
| 182 | + mc = MockMonteCarlo() |
| 183 | + with pytest.raises(TypeError): |
| 184 | + mc.estimate_confidence_interval("apogee", statistic=1) |
| 185 | + |
| 186 | + with pytest.raises(TypeError): |
| 187 | + mc.estimate_confidence_interval("apogee", statistic="not_a_function") |
0 commit comments