Skip to content

Native sparse soften for frozen constraints (penalty= with freeze=True) #975

Description

@FabianHofmann

Note

The following content was generated by AI.

Describe the feature you'd like to see

Soften a frozen CSRConstraint natively, so add_constraints(..., penalty=..., freeze=True) works without densifying. Follow-up of #970 and #974.

Today a frozen constraint cannot be softened by any route:

import pandas as pd
import linopy

linopy.options["semantics"] = "v1"

m = linopy.Model()
x = m.add_variables(lower=0, coords=[pd.RangeIndex(4, name="i")], name="x")
m.add_objective(x.sum())

m.add_constraints(x >= 1, name="c", freeze=True, penalty=10)
# ValueError: `penalty` cannot be combined with `freeze=True` ...

c = m.add_constraints(x >= 1, name="d", freeze=True)
c.soften(penalty=10)
# AttributeError: CSRConstraint.soften is not supported on a frozen constraint; add the constraint with freeze=False to soften it.
c.mutable().soften(penalty=10)
# ValueError: Constraint 'd' is not the constraint registered in the model ...

The only route is to keep the constraint unfrozen for its whole life, which loses the sparse path for large models.

Implementation ideas

Constraint.soften (linopy/constraints.py) adds a slack variable (two for ==), rewrites the lhs through update(lhs=lhs - slack), stores it as slack and adds penalty * slack to the objective. On the CSR store this is one extra term per active row:

  • add the slack variable(s) over the constraint grid with mask=con.mask, as today;
  • return a new CSRConstraint whose CSR has one appended entry per active row: the slack label with coefficient -1 (<=), +1 (>=), and both for ==; replace the registered constraint in model.constraints;
  • add the penalty term to the objective, which stays sparse since Sparse path for the objective #967;
  • keep the current rules: positive penalty, objective must exist, no double softening, no mixed signs, max_violation as slack upper bound.

Then add_constraints can drop the penalty + freeze error and soften the frozen constraint directly. Tests should compare against the dense soften cell by cell for <=, >=, ==, with a mask and with max_violation, and check that no densify notice is emitted.

A cheaper intermediate step is possible: with penalty= and freeze=True, build the mutable constraint, soften it, then freeze it. That densifies each softened constraint once, with a notice.

Activity

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

    performanceThis improves performance while not (meaningfully) altering behaviour for userssparseSparse / CSR-backed expressions and constraints

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions