Skip to content

Cleaning the way redispatching is handled - #757

Open
BDonnot wants to merge 9 commits into
Grid2op:dev_1.12.6from
BDonnot:test_new_redisp
Open

BDonnot wants to merge 9 commits into
Grid2op:dev_1.12.6from
BDonnot:test_new_redisp

Conversation

@BDonnot

@BDonnot BDonnot commented May 15, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

BDonnot added 2 commits May 15, 2026 10:55
Signed-off-by: DONNOT Benjamin <benjamin.donnot@rte-france.com>
Signed-off-by: DONNOT Benjamin <benjamin.donnot@rte-france.com>
@codacy-production

codacy-production Bot commented May 15, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 93 complexity · 1 duplication

Metric Results
Complexity 93
Duplication 1

View in Codacy

🟢 Coverage 97.41% diff coverage

Metric Results
Coverage variation Report missing for 98870801
Diff coverage ✅ 97.41% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (9887080) Report Missing Report Missing Report Missing
Head commit (809336f) 26055 21349 81.94%

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#757) 695 677 97.41%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

1 Codacy didn't receive coverage data for the commit, or there was an error processing the received data. Check your integration for errors and validate that your coverage setup is correct.

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Signed-off-by: DONNOT Benjamin <benjamin.donnot@rte-france.com>
@sonarqubecloud

Copy link
Copy Markdown

@BDonnot
BDonnot changed the base branch from dev_1.12.5 to dev_1.12.6 September 8, 2026 08:22

@BDonnot BDonnot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some changes to be made

Comment on lines +171 to +227
except_ = None
cls = type(self.env)
this_dt_float = float
new_p = constraints.new_p
if self.env.nb_time_step == 0:
state.gen_activeprod_t_redisp[:] = new_p

gen_participating = constraints.gen_participating.copy()
incr_in_chronics = new_p - (state.gen_activeprod_t_redisp - state.actual_dispatch)

p_min_down = cls.gen_pmin[gen_participating] - state.gen_activeprod_t_redisp[gen_participating]
avail_down = np.maximum(p_min_down, -cls.gen_max_ramp_down[gen_participating])
p_max_up = cls.gen_pmax[gen_participating] - state.gen_activeprod_t_redisp[gen_participating]
avail_up = np.minimum(p_max_up, cls.gen_max_ramp_up[gen_participating])
except_ = self._detect_infeasible_dispatch(
constraints,
incr_in_chronics[gen_participating],
avail_down,
avail_up,
state,
)
if except_ is not None:
if (
self.env._parameters.IGNORE_MIN_UP_DOWN_TIME
and self.env._parameters.ALLOW_DISPATCH_GEN_SWITCH_OFF
):
gen_participating_tmp = self.env.gen_redispatchable.copy()
if cls.detachment_is_allowed:
gen_participating_tmp[constraints.gen_detached] = False
p_min_down_tmp = (
cls.gen_pmin[gen_participating_tmp]
- state.gen_activeprod_t_redisp[gen_participating_tmp]
)
avail_down_tmp = np.maximum(
p_min_down_tmp, -cls.gen_max_ramp_down[gen_participating_tmp]
)
p_max_up_tmp = (
cls.gen_pmax[gen_participating_tmp]
- state.gen_activeprod_t_redisp[gen_participating_tmp]
)
avail_up_tmp = np.minimum(
p_max_up_tmp, cls.gen_max_ramp_up[gen_participating_tmp]
)
except_tmp = self._detect_infeasible_dispatch(
constraints,
incr_in_chronics[gen_participating_tmp],
avail_down_tmp,
avail_up_tmp,
state,
)
if except_tmp is None:
gen_participating = gen_participating_tmp
except_ = None
else:
return except_tmp
else:
return except_

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this part deserves its function, even in baseResdispatchSolver call something like _prepare_solver_inputs or something

Comment on lines +229 to +243
target_vals = state.target_dispatch[gen_participating] - state.actual_dispatch[gen_participating]
already_modified_gen_me = state.already_modified_gen[gen_participating]
target_vals_me = target_vals[already_modified_gen_me]
nb_dispatchable = gen_participating.sum()
tmp_zeros = np.zeros((1, nb_dispatchable), dtype=this_dt_float)
coeffs = 1.0 / (self.env.gen_max_ramp_up + self.env.gen_max_ramp_down + self.env._epsilon_poly)
weights = np.ones(nb_dispatchable) * coeffs[gen_participating]
weights /= weights.sum()

if target_vals_me.shape[0] == 0:
already_modified_gen_me[:] = True
target_vals_me = target_vals[already_modified_gen_me]

scale_x = max(np.max(np.abs(state.actual_dispatch)), 1.0)
scale_x = this_dt_float(scale_x)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also leave in its own function, for this class only though, called "scale_solver_input" or something

Comment thread CHANGELOG.rst Outdated
Comment on lines 107 to 110
- [IMPROVED] handling of redispatching as a separate module now
(grid2op/Environment/dispatch)
- [IMPROVED] remove the use of "assert" block in the main codebase

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong place

Signed-off-by: Benjamin Donnot <benjamin.donnot@rte-france.com>
The environment owns the redispatching rules again: tracking the target
dispatch, validating redispatching actions and the min up / down times of
the generators are back in BaseEnv. A solver only has to implement
solve() (reset() is optional), so a subclass of BaseRedispatchSolver
that implements the abstract API no longer crashes on env.reset().

- RedispatchConstraints.from_state carries everything the solver needs
  (limits, tolerances, whether all generators may be used), so the
  default solver no longer reads the environment. It is built only when
  a dispatch has to be computed.
- review: the participating generators / feasibility check live in
  BaseRedispatchSolver._prepare_solver_inputs, the scaling in
  DefaultRedispatchSolver._scale_solver_input.
- redispatch_solver is accepted by grid2op.make (and config.py) and is
  propagated to obs.simulate, the forecast env, env.copy(), the Runner,
  MultiMix, MaskedEnvironment and TimedOutEnvironment. Each env works on
  its own copy of the solver.
- the fallback that uses every redispatchable generator copied
  gen_redispatchable instead of modifying the class attribute in place.
- remove the BaseEnv wrappers that no longer had any caller.
- tests: test_redispatch_solver.py (custom solver smoke test and a
  regression test for gen_redispatchable, which fails on dev_1.12.6).
- docs: document the redispatch solver in docs/user/environment.rst.

Assisted-by: Claude Code
Claude-Session: https://claude.ai/code/session_01SjJybpBWCD2gf6AJ1AFKfD
Signed-off-by: Benjamin Donnot <benjamin.donnot@rte-france.com>
They were under 1.12.5, which is already released (review comment).
Also drop the "remove assert" line (already listed in 1.12.5) and
add the entries for the gen_redispatchable fix and the new
redispatch_solver argument.

Assisted-by: Claude Code
Claude-Session: https://claude.ai/code/session_01SjJybpBWCD2gf6AJ1AFKfD
Signed-off-by: Benjamin Donnot <benjamin.donnot@rte-france.com>
…ctions without redispatch

- simulate: _reset_to_orig_state restored _detached_elements_mw_prev from
  the "_detached_elements_mw" key. The environment used by simulate also
  never knew the power of the detached loads (its injections are in
  _backend_action_set, not _env_modification). The two errors cancelled on
  steady steps, but simulate on the observation right after a detachment
  predicted a dispatch that no longer compensated the detached load.
- LIMIT_INFEASIBLE_CURTAILMENT_STORAGE_ACTION: the feasibility guard now
  uses the same generators as the solver (detached ones excluded) and
  counts the detached power, so a storage action that is only infeasible
  together with a detachment is limited instead of causing a game over.
  What the guard takes back is capped at what storage and curtailment
  contributed (they are cancelled, never reversed), a full cancellation
  now updates the storage power, and the state of charge uses the right
  efficiency in that case.
- the injections of an action were overridden by the time series on grids
  without redispatching data and without storage units
  (_aux_handle_act_inj was only called in _aux_apply_redisp).

Regression tests in test_redispatch_solver.py fail before these changes.

Assisted-by: Claude Code
Claude-Session: https://claude.ai/code/session_01SjJybpBWCD2gf6AJ1AFKfD
Signed-off-by: Benjamin Donnot <benjamin.donnot@rte-france.com>
The forecast env performs an internal step when it is created. Its backend
is a copy of the one of the observation (so detached elements are already
detached) but the state of the environment (dispatch, generator setpoints,
detached power...) was only restored from the observation in `reset`, not
before this first step. With a detached element the dispatch then saw the
whole production as a variation from 0 MW and raised ImpossibleRedispatching.

Restore the state of the observation before this first step too, as reset
does.

Assisted-by: Claude Code
Claude-Session: https://claude.ai/code/session_01SjJybpBWCD2gf6AJ1AFKfD
Signed-off-by: Benjamin Donnot <benjamin.donnot@rte-france.com>
Solver contract changes, done before the API is released:

- RedispatchConstraints carries a single power_to_compensate_mw (positive:
  the generators must produce more) instead of the storage, curtailment and
  detachment amounts, each in its own sign convention. The split is kept, in
  that same convention, in `contributions`, for information. Both come from
  dispatch_contributions(), the only place a new source (load shedding,
  deferred loads...) has to be declared.
- solve() returns a RedispatchResult (success, the new actual_dispatch, the
  exception, and unserved_mw: the power the generators cannot compensate)
  and no longer modifies the state: the environment applies the dispatch.
  A solver returning anything else raises an EnvError.

The default solver computes the same dispatch (the power to compensate is
summed in the same order and precision as the former equality constraint).

Assisted-by: Claude Code
Claude-Session: https://claude.ai/code/session_01SjJybpBWCD2gf6AJ1AFKfD
Signed-off-by: Benjamin Donnot <benjamin.donnot@rte-france.com>

This branch has not been deployed

No deployments
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.

1 participant