Skip to content

BUG: setting a declared eccentricity back to None leaves the old distribution in place #1171

Open

Description

@thc1006

What happens

add_cp_eccentricity and add_thrust_eccentricity accept None to mean an input is not randomized, and their docstrings document x and y as optional. Passing None after a distribution was configured does not remove the earlier one, so the next reseed brings it back.

stochastic = StochasticRocket(rocket=calisto, radius=0.0127 / 2)

stochastic.add_cp_eccentricity(x=0.001)
stochastic.add_cp_eccentricity(x=None)

stochastic._set_stochastic(7)

On develop at 7822ab1d:

after the None call, still declared:      True
after the reseed, the attribute:          (0, 0.001, Generator.normal)
in the generated dict:                    True

The caller asked for the uncertainty to go away and it is still being drawn.

Why

StochasticModel._declare_stochastic_input returns early on None rather than removing the entry:

if input_value is None:
    return
self.__stochastic_dict[input_name] = input_value

dict_generator walks __stochastic_dict, so the stale declaration reaches the draw. The early return is right for the case it was written for, which is add_cp_eccentricity(x=0.001) with no y, where None means "was never given" rather than "take it away". The two meanings are not distinguished.

Fix

Removing the entry instead of returning early covers the second meaning, and leaves the first alone as long as removal only happens for a name that is being configured. #1169 adds _reconfigure_stochastic_input, which already owns the replacement half of this, so the removal fits there in about a line.

Worth a test for each meaning: add_cp_eccentricity(x=0.001) with no y must not declare y, and add_cp_eccentricity(x=None) after x=0.001 must remove x.

Where it came from

The behaviour arrived with #1167, which declared these inputs so a fixed seed could reproduce them. Reconfiguration was not part of that change. Raised separately rather than folded into #1169, which fixes the other half of the same lifecycle and is under review.

Activity

  1. thc1006 commented on Aug 16, 2026

    @thc1006
    ContributorAuthor

    Worth recording what this costs, because I tried folding it into #1169 and took it back out.

    add_cp_eccentricity and add_thrust_eccentricity both default x and y to None, so an omitted axis and an explicit None arrive identically. Removing on None therefore takes away an axis the caller never mentioned:

    rocket.add_cp_eccentricity(x=0.001, y=0.002)
    rocket.add_cp_eccentricity(x=0.005)     # y quietly gone

    On develop the second call leaves y alone, which is what anything already written depends on.

    So removal needs an argument that omission cannot supply. A private sentinel as the default distinguishes the two readings:

    _OMITTED = object()
    
    def add_cp_eccentricity(self, x=_OMITTED, y=_OMITTED):

    That fixes it, and it is a signature change on a public method. inspect.signature stops reporting None, the documented default changes, and a caller who currently writes y=None because it computed to nothing would start losing their declaration rather than keeping it. None of that is wrong, but it is a decision about the public API rather than a bug fix, which is why it does not belong inside a change about nominal ownership.

    #1169 keeps develop's behaviour and has a regression test for the omitted axis, so nothing here gets worse in the meantime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions