Skip to content

Solver Refactor and Extension #628

@FabianHofmann

Description

@FabianHofmann

Currently information about solver state and meta info is split across multiple places:

  • Model: solver_model, solver_name, status, termination_condition
  • Result: status, solution, solver_model
  • Solver: only solver_options
  • Model.solve(): inlined result-to-model mapping logic (normal, remote, and mock paths each have independent mapping)
  • solver_capabilities.py: global capability lookup
  • direct conversion entry points live on Model (delegating to linopy/io.py functions)

We want to refactor to better encapsulate needed information.

Changes to Solver Class

The following new attributes:

  • options: dict[str, Any]
  • status: Status | None
  • solution: Solution | None
  • solver_model: Any
  • io_api: str | None
  • capability: SolverInfo | None
  • env: gurobipy.Env | None (and other solver envs)
  • solver_name: str (current)

and the following functions:

  • to_solver_model(model, explicit_coordinate_names=False, env=None): thin wrapper around existing linopy/io.py conversion functions (model.to_gurobipy(), model.to_highspy(), raises NotImplementedError for solver wo interface
  • update_solver_model(model, explicit_coordinate_names=False, env=None): synchronize native solver model after linopy model changes. Not implemented now
  • resolve(sense) -> Result: solve or re-solve self.solver_model. Stores result on the solver instance (self.status, self.solution, self.solver_model) and returns Result. Raises if self.solver_model is None.

(Note currently the Gurobi Env is created inside a contextlib.ExitStack scoped to solve_problem_from_model() — if solver_model persists beyond solve(), the env must also persist)

Assign the Solver instance to the linopy model at model.solver (add "solver" to __slots__), None at initialization

Changes to Result Class

Add solver_name: str to the Result dataclass. Currently Result holds status, solution, and solver_model. Adding solver_name makes Result fully self-describing — no need to pass solver identity out-of-band.

Changes to Model class

  • Add apply_result(result: Result | None = None) -> tuple[str, str] as proposed in Solver Refactor and Extension #628 but witht the difference when result is None, pull the result from self.solver (reads self.solver.status, self.solver.solution, self.solver.solver_model). This enables a clean post-resolve() flow where the solver already has the result. When result is provided explicitly, use it directly (for remote solve, mock, or detached workflows). Used for status and termination updates, objective assignment, primal and dual value assignments.

  • Model.solver_model should point to Model.solver.solver_model.

  • Model.solver_name should point to Model.solver.solver_name.

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