Quickstart¶
A tiny dispatch LP. Two generation units (wind, coal) over three hours. Wind is cheap but variable; coal is expensive but firm. Pick how much each unit produces per hour to meet demand at minimum cost.
The model code below is sourced from tests/fixtures/quickstart_example.py
and is verified by tests/test_quickstart_example.py.
import polars as pl
from polar_high import Param, Problem, Sum
p = Problem()
# Index sets — declared once, reused below
unit_index = pl.DataFrame({"unit": ["wind", "coal"]})
time_index = pl.DataFrame({"hour": [1, 2, 3]})
# Decision variable v_production[unit, hour] >= 0
composite_index = unit_index.join(time_index, how="cross")
v_production = p.add_var(
"v_production",
dims=("unit", "hour"),
index=composite_index,
lower=0.0,
)
# Operating cost per unit
cost = Param(
("unit",),
pl.DataFrame({"unit": ["wind", "coal"], "value": [2.0, 8.0]}),
)
# Available capacity per unit per hour — built per-unit, then concatenated
cap_wind = time_index.with_columns(
pl.lit("wind").alias("unit"),
pl.Series("value", [3.0, 1.0, 4.0]), # wind drops in hour 2
)
cap_coal = time_index.with_columns(
pl.lit("coal").alias("unit"),
pl.Series("value", [10.0, 10.0, 10.0]),
)
cap = Param(
("unit", "hour"),
pl.concat([cap_wind, cap_coal]).select("unit", "hour", "value"),
)
# Demand per hour
demand = Param(
("hour",),
time_index.with_columns(pl.Series("value", [5.0, 6.0, 4.0])),
)
# Minimise total cost
p.set_objective(cost * v_production, sense="min")
# v_production[unit, hour] <= cap[unit, hour]
p.add_cstr(
"capacity",
over=composite_index,
lhs_terms={"production": v_production},
sense="<=",
rhs_terms={"cap": cap},
)
# Σ_unit v_production[unit, hour] == demand[hour]
p.add_cstr(
"demand_balance",
over=time_index,
lhs_terms={"production": Sum(v_production, over=("unit",))},
sense="==",
rhs_terms={"demand": demand},
)
sol = p.solve()
print(f"objective: {sol.obj}") # 72.0
print(sol.value("v_production"))
The optimum dispatches wind at full capacity in every hour (cheaper) and uses coal to fill the gap. Hour 3 leaves coal idle because wind alone covers demand.
What just happened¶
- Index frames.
add_var(... index=...)registers one LP column per row of the index frame. Each Var is internally a polars frame(*dims, col_id). - Parameters as frames. A
Paramis a frame(*dims, value).cost * v_productiondoes an inner-join on shared dims (unit) and emits anExprof(unit, hour, col_id, coef). - Aggregation as group-by.
Sum(v_production, over=("unit",))collapses theunitdim, leavinghourfor the demand constraint. Hereover=is a tuple of dim names — the dims to collapse. - Constraints.
add_cstr(..., over=hour_idx, ...)materialises one LP row per row of thehour_idxDataFrame. Hereover=is a row-index frame (not a tuple of dim names) telling the engine which cells of the constraint family to instantiate. - Solve.
Problem.solve()builds COO triples, hands HiGHS aHighsLpstruct, and returns aSolution.
Next steps¶
- The mental model: Concepts
- Idiomatic patterns:
Sum,Where,Lag— Expressions - Re-solve with parameter updates: Warm-starting
- Decompose coupled subproblems: Decomposition building blocks