Skip to content

Coming from linopy

For a reader who writes models in linopy. specsolve solves the same models with linopy's names where it can, but the math lives in a YAML file rather than in Python.

The same model

The dispatch model of Your first model, written both ways. tests/test_ports.py holds both to the same objective and prices.

m = linopy.Model()
p = m.add_variables(lower=0, upper=p_max, coords=[load.index, p_max.index], name='p')
m.add_constraints(p.sum('generator') == load, name='power_balance')
m.add_objective((p * cost).sum())

description: Least-cost dispatch of a generator fleet against an hourly load.

dimensions:
  snapshot:
    description: dispatch periods
    dtype: int
  generator:
    description: generating units

parameters:
  p_max:
    description: installed capacity
    dims: [generator]
  load:
    description: demand to be met
    dims: [snapshot]
  cost:
    description: marginal cost
    dims: [generator]

variables:
  p:
    description: output of a generator in a snapshot
    dims: [snapshot, generator]
    where: "p_max > 0"
    bounds:
      lower: 0
      upper: p_max

constraints:
  power_balance:
    dims: [snapshot]
    expression: sum(p, over=generator) == load

objective:
  sense: minimize
  description: total cost of generation over the horizon
  expression: sum(p * cost)

result = sps.solve('dispatch.yaml', sources)

What maps to what

linopy specsolve
add_variables(lower, upper, coords, mask) variables: with dims, bounds and where
add_constraints(lhs == rhs) constraints: with an expression
add_objective(expr) objective:
coords from pandas indexes sources: one table per parameter, and the labels of each dimension (the data contract)
m.solve() sps.solve, or sps.build and model.solve() to solve again
p.solution, con.dual result.primal('p'), result.dual('power_balance'), as polars tables
an xarray answer result.to_dataarray('p') (pandas or xarray)
fix, relax, remove_constraints data and spec edits (fixing, relaxing and removing)
a constraint's rows model.row
compute_infeasibilities model.infeasible_subsystem

Solve statuses take linopy's names: status and termination_condition, with is_ok as the rollup.

What is different

  • The model is a file. There is no Python API that builds math. sps.check validates the file with no data.
  • Data is attached by name, and a missing row is a missing coordinate. A model holds only the rows that exist, which is what keeps a sparse model small.
  • linopy is not a dependency. Only the test suite uses it, to build every file a second time and compare (verification).
  • Some of linopy is not taken: array operations such as merge and reindex are data preparation, and the solver layer is specsolve's own (#106).

Where linopy is ahead

linopy reaches twelve solvers against specsolve's three, plus the LP and MPS files. It has fix and relax as methods, and answers in xarray.