Skip to content

Tighten type hints on add_variables / add_constraints / add_objective #734

@FBumann

Description

@FBumann

While reviewing linopy/model.py, several typing improvements stood out on the main Model.add_* entry points. One is a latent correctness bug in the overloads; the rest are consistency / precision fixes that take advantage of aliases that already live in linopy/types.py.

1. add_constraints overload bug (correctness)

Model.add_constraints has overloads for freeze: Literal[False] = ... and freeze: Literal[True] = ... (model.py:884–913), but the runtime default is freeze: bool | None = None (model.py:927). None is the sentinel that means "use self.freeze_constraints".

Consequences:

  • A caller passing freeze=None explicitly matches neither overload.
  • The overloads claim the default is Literal[False], which is wrong: when self.freeze_constraints=True and the caller omits freeze, mypy infers Constraint but the runtime returns CSRConstraint.

Fix: add a third overload for freeze: None = ... (or no-arg) returning ConstraintBase, and drop the misleading defaults from the two typed-bool overloads (use :, not = ...).

2. add_variables uses bespoke types where aliases exist (model.py:656)

  • lower: Any = -inf, upper: Any = inf → should be ConstantLike. This matches what as_dataarray actually accepts at model.py:774–775, and Any defeats type-checking on the most common entry point.
  • coords: Sequence[Sequence | pd.Index | DataArray] | Mapping | NoneCoordsLike | None. The inline form is also slightly narrower — it omits DataArrayCoordinates / DatasetCoordinates.
  • mask: DataArray | ndarray | Series | NoneMaskLike | None. The current inline form drops DataFrame even though as_dataarray accepts it (used at model.py:784).

3. add_constraints runtime signature (model.py:915)

coords: Sequence[Sequence | pd.Index | DataArray] | Mapping | None should be CoordsLike | None, matching the overloads and as_dataarray.

4. add_objective (model.py:1093)

  • sense: str = \"min\"Literal[\"min\", \"max\"]. The setter at objective.py:216–217 raises on anything else, so the wider str is just hiding a runtime error from the type-checker.
  • expr includes Sequence[tuple[ConstantLike, VariableLike]] but the body at model.py:1122–1124 only handles Variable | LinearExpression | QuadraticExpression. Either the type is too wide or the body is missing a branch — worth verifying whether the tuple-sequence path is reachable or dead.

Lower-priority

  • add_constraints lhs: ... | CallableCallable is unparameterized. Tightening to Callable[..., ConstraintLike | AnonymousScalarConstraint] would document the rule contract but may break existing callers that return raw expressions/tuples.
  • Consider introducing a BoundLike = ConstantLike alias for symmetry — small, but it documents intent at add_variables better than reusing ConstantLike.

Suggested order

  1. Fix add_constraints freeze overloads (correctness, not just hygiene).
  2. Tighten add_objective.sense to Literal[\"min\", \"max\"].
  3. Replace inline coords / mask / bound types with CoordsLike / MaskLike / ConstantLike across the three functions.
  4. Audit whether Sequence[tuple[ConstantLike, VariableLike]] in add_objective is reachable or dead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions