Skip to content

No validation that Variable lower_bound/upper_bound indexing is compatible with the variable's own IndexingStructure #267

Description

@tbittar

Summary

Variable.lower_bound / Variable.upper_bound expressions are only validated for being constant with respect to decision variables (is_constant, i.e. compute_degree(expr) == 0 in variable.py). There is no check that the bound expression's own time/scenario indexing is compatible with (a subset of) the variable's structure: IndexingStructure.

Details

  • A bound expression can reference parameters that are themselves time_dependent/scenario_dependent (resolve_library.py:408-413), regardless of whether the variable itself declares time_dependent/scenario_dependent.
  • Model.__post_init__ and _make_structure_provider in model.py do cross-check indexing for objective contributions — they require compute_indexation(contribution, provider) == IndexingStructure(False, False) (model.py:139) — but no equivalent check exists for Variable.lower_bound / Variable.upper_bound against Variable.structure.
  • The mismatch only surfaces downstream, at simulation-build time, in _add_variable_for_group / _to_bound_array (optimization.py:794-985). The bound expression is vectorized against the variable's var_shape/dims (derived from var.structure), and _to_bound_array only knows how to add missing axes for dims the variable has but the bound value lacks. It has no handling for the reverse case: a bound whose underlying parameter carries a time or scenario axis that the variable's own dims don't include.

Impact

When a bound's effective indexing exceeds the variable's own indexing:

  • Usually: a numpy broadcast shape-mismatch error at solve-build time, with no context pointing back to the offending variable/bound definition.
  • Worst case: if the extra axis happens to match another axis's length (e.g. number of time steps equals number of components), np.broadcast_to silently succeeds and produces silently wrong bound values — no error at all.

Suggested fix

Add an explicit check (either in Variable.__post_init__/Model.__post_init__, or wherever the structure provider is available) that compute_indexation(bound_expr, provider) is a "subset" of var.structure for both lower_bound and upper_bound, and raise a clear ValueError at model-definition/resolution time instead of letting it fail (or silently misbehave) deep in optimization.py.

Where this came from

Surfaced while reviewing Variable (variable.py:23-33) on feat/lb_ub_operator.

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