Skip to content

BUG: draw the eccentricities again, whichever way they were added - #1167

Merged
Gui-FernandesBR merged 1 commit into
RocketPy-Team:developfrom
thc1006:bug/eccentricities-reach-the-draw-again
Aug 15, 2026
Merged

BUG: draw the eccentricities again, whichever way they were added#1167
Gui-FernandesBR merged 1 commit into
RocketPy-Team:developfrom
thc1006:bug/eccentricities-reach-the-draw-again

Conversation

@thc1006

@thc1006 thc1006 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Pull request type

  • Code changes (bugfix, features)

Checklist

  • Tests for the changes have been added (if needed)
  • Docs have been reviewed and added / updated
  • Lint (ruff check / ruff format --check, pylint) has passed locally
  • All tests (pytest tests/unit tests/integration) have passed locally

Current behavior

#1122 made dict_generator walk the inputs a model declared rather than every attribute on it. That is the right shape for #1109, and initial_solution is correctly out of the draw now.

The declared list is built in __init__. add_cp_eccentricity and add_thrust_eccentricity run after that, so what they install is never declared, and _set_stochastic(seed) does not re-validate it.

That matters because validation is what binds a distribution to a generator. An undeclared eccentricity keeps the one built in __init__, which no seed reaches, so a fixed seed does not reproduce a run whose eccentricities were added through add_*. The values are not frozen: create_object reaches them through _create_eccentricities and _randomize_position, which draws afresh every simulation. They simply do not answer to random_seed.

Four create_object() calls with add_cp_eccentricity(x=(0.0, 0.01), y=(0.0, 0.01)), reading cp_eccentricity_x:

develop    seed=42, run 1:  -0.002798  -0.005315   0.003486  -0.012569
develop    seed=42, run 2:   0.002131   0.009386   0.014345  -0.001072   <- same seed, different draws
this PR    seed=42, run 1:  -0.013022   0.000660  -0.000499   0.005323
this PR    seed=42, run 2:  -0.013022   0.000660  -0.000499   0.005323   <- reproducible

Thanks to @Gui-FernandesBR for catching that my first description had the symptom wrong. I had read the eccentricities out of dict_generator, seen them absent and concluded the value was stuck, without checking what create_object actually does with them.

Bisected on develop by reading the generated dictionary, which is where the declaration went missing:

3e16c9fc  (before #1122)  cp_eccentricity_x, cp_eccentricity_y,
                          thrust_eccentricity_x, thrust_eccentricity_y
5a71eb93  (#1122)         none of them

ensemble_member was fine either way: StochasticEnvironment passes it through the constructor, so it is declared, and the hasattr guard #1122 added covers _validate_ensemble not having set it yet. It is the fields no constructor declares that fall out.

New behavior

An add_* method that installs a distribution declares it, so the draw reaches it again.

What is declared is the argument as given, not the validated form. _set_stochastic validates every declared input again on each reseed, and validation binds a distribution to the generator that is live at the time, so handing it the raw argument is what ties the draw to the reseed rather than to whichever one happened to come first. _validate_eccentricity dispatches to the same _validate_tuple / _validate_scalar / _validate_list that _set_stochastic uses, so the two agree on what a spec means.

None is not declared, since there is nothing to draw.

Breaking change

  • No

Runs that set an eccentricity spread were not reproducible from their seed. Their values change, which is the bug being fixed.

Additional information

Three tests: the four names appear in the generated dictionary for each add_* method, the values are numbers, and the same seed twice gives the same eccentricity while a different seed moves it. Dropping any one of the four declarations turns them red.

Local run against develop at be195d4: ruff clean, pylint exit 0, pytest tests/unit tests/integration 2261 passed, 51 skipped.

Found while rebasing #1054, which has an eccentricity regression test of its own that went red on the same change.

RocketPy-Team#1122 made dict_generator walk the inputs a model declared instead of every
attribute on it, which is the right shape for RocketPy-Team#1109. add_cp_eccentricity and
add_thrust_eccentricity run after __init__ has built that list, so their
distributions stopped being drawn from: the value was set on the instance and
every simulation used the same one, with nothing to say so.

Bisected: at 3e16c9f all four eccentricities appear in the generated
dictionary, at 5a71eb9 none of them do.

An add_* method now declares what it installed, with the argument as given
rather than the validated form, so _set_stochastic validates it again on each
reseed and binds the distribution to the generator that is live then.
ensemble_member was already fine, since StochasticEnvironment passes it through
the constructor and the hasattr guard covers it not being set yet.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 requested a review from a team as a code owner August 15, 2026 13:46
@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 84.38%. Comparing base (be195d4) to head (eb2d1e7).

Files with missing lines Patch % Lines
rocketpy/stochastic/stochastic_model.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1167      +/-   ##
===========================================
+ Coverage    84.33%   84.38%   +0.04%     
===========================================
  Files          130      130              
  Lines        17266    17274       +8     
===========================================
+ Hits         14562    14577      +15     
+ Misses        2704     2697       -7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Gui-FernandesBR

Copy link
Copy Markdown
Member

Verified and merging. One correction for the record, because it matters for the changelog entry: the mechanism you describe is right, but the symptom in the PR description is not.

The description says "every simulation uses the same one". It does not — create_object does not read the eccentricity out of dict_generator at all, it calls _create_eccentricities_randomize_position, which draws afresh on every simulation. So the value does vary from run to run even on develop today.

What actually breaks is seed reproducibility. Because the eccentricities are not declared, _set_stochastic(seed) never re-validates them, so their distribution stays bound to the unseeded generator built in __init__. Measured on develop, four create_object() calls, add_cp_eccentricity(x=(0.0, 0.01), y=(0.0, 0.01)):

develop   seed=42 run 1:  0.000384  -0.002502   0.007438  -0.000716
develop   seed=42 run 2:  0.002923   0.010395  -0.018107   0.011367   <-- same seed, different draws
this PR   seed=42 run 1: -0.013022   0.000660  -0.000499   0.005323
this PR   seed=42 run 2: -0.013022   0.000660  -0.000499   0.005323   <-- reproducible

So the user-visible bug is "a fixed seed did not reproduce a Monte Carlo run whose eccentricities were added via add_*", which is exactly what your reseed-binding explanation predicts and what this fixes. Worth phrasing the changelog that way rather than "the value was frozen".

Also worth a follow-up (not blocking): now that the eccentricities are declared, dict_generator draws them and _create_eccentricities immediately draws them again and overwrites last_rnd_dict. The exported value stays consistent with the applied one, since the second write wins, but it burns four extra draws per simulation and shifts the stream. Having _create_eccentricities consume the already-generated dict would be tidier.

Local checks on your branch: ruff check clean, ruff format clean, pylint rocketpy/stochastic/ 10.00/10, tests/unit/stochastic/ 113 passed.

@Gui-FernandesBR
Gui-FernandesBR merged commit c006a0d into RocketPy-Team:develop Aug 15, 2026
10 checks passed
@thc1006
thc1006 deleted the bug/eccentricities-reach-the-draw-again branch August 15, 2026 15:30
Gui-FernandesBR pushed a commit that referenced this pull request Aug 15, 2026
#1167 declared the eccentricities that add_cp_eccentricity and
add_thrust_eccentricity install, so dict_generator draws them.
_create_eccentricities then drew them a second time and overwrote the first
value. The exported inputs matched the applied ones only because the second
write wins, and the extra draw moved every component position create_object
places after it.

Read what has been drawn already, and draw only a half the caller left out,
which is not a declared input and so never reaches dict_generator.

The docstring correction is the explanation #1167 landed with, which named
the wrong symptom: the value did vary between simulations, what a fixed seed
failed to do was reproduce it.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants