Skip to main content

Python library for building indexed linear and mixed-integer programs in polars; assembles into HiGHS, exportable as MPS.

Project description

polar-high

PyPI Python versions License tests docs Ruff

A Python library for building and solving large linear and mixed-integer optimisation programs, i.e. domain specific language (DSL) for algebraic modelling. Variables and parameters are polars DataFrames, expressions are joined and grouped lazily, and the matrix is assembled directly through HiGHS — or exported as MPS for any other LP/MIP solver. The kernel is intentionally domain-free: it has no opinions about energy systems, supply chains, or any specific application.

Install

pip install polar-high

Requires Python 3.11+. HiGHS ships in highspy, no separate install.

Quickstart

A tiny dispatch LP — wind + coal over three hours, minimise cost subject to capacity and per-hour demand.

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 same code lives at tests/fixtures/quickstart_example.py and is executed in the test suite, so README and docs stay in sync.

Enum dtype handling

polars 1.40 treats pl.Enum as a nominal type and refuses to join two columns when the Enums carry different categorical vocabularies — even if one's categories are a strict subset of the other's. That strict semantics gets in the way of typical LP-DSL workflows, where one Param may live on a subset of an axis (e.g. only the units with a finite capacity) while another lives on the full axis.

polar-high auto-aligns Enum-typed join keys at every internal .join() site:

  • same Enum on both sides — no change.
  • one side's categories ⊆ the other's — the narrower side is up-cast to the wider Enum dtype (with strict=False; any values outside the wider vocab become null, which inner / left joins drop or surface naturally).
  • Enum vs pl.Utf8 / pl.String — the string side is cast to the Enum dtype.
  • neither vocab is a subset of the otherValueError with actionable guidance (cast to pl.Utf8 or build a union Enum).

No try/except shims, no Utf8 fallbacks for the disjoint case. The behaviour is implemented by a generic helper that knows nothing about specific axis names or vocab contents. See tests/test_enum_dtype_align.py for the contract.

Documentation

Full docs at https://nodal-tools.fi/polar-high/ — published with MkDocs + mike for per-version reads.

  • Concepts — the indexed-frame mental model (Var, Param, Sum, Where, Lag, broadcasting/join semantics).
  • Guide — warm-starting, Lagrangian decomposition, performance tuning, debugging.
  • API reference — autogenerated from docstrings.
  • Compare — how polar-high relates to Pyomo, JuMP, gurobipy, linopy, and GNU MathProg.

Build locally: pip install -e ".[docs]" && mkdocs serve.

Used by

polar-high is the build engine behind the FlexTool energy-system modelling toolkit (still in dev branch only, 7.5.2026 situation). FlexTool's fleet of system tests (from earlier GNU MathProg to HiGHS implementation) has been used to test polar-high in real modelling use cases. In addition polar-high kernel has its own set of unit and system tests.

Created by

polar-high was created by Juha Kiviluoma of Nodal-Tools using Claude Opus.

License

Apache-2.0 — see LICENSE and NOTICE. Changelog: CHANGELOG.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polar_high-2.0.2.tar.gz (126.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

polar_high-2.0.2-py3-none-any.whl (109.7 kB view details)

Uploaded Python 3

File details

Details for the file polar_high-2.0.2.tar.gz.

File metadata

  • Download URL: polar_high-2.0.2.tar.gz
  • Upload date:
  • Size: 126.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for polar_high-2.0.2.tar.gz
Algorithm Hash digest
SHA256 57332b4b28c3c847cb10bf0a9eb29f4822d6a3a28bce842d2e370faee9ff2e1c
MD5 732f7aa63b44e821b61f10b31aae1cbe
BLAKE2b-256 96614fd51eafbcdef7ff3d5a76b2a8111260e984105af61fa072060f072e3cf1

See more details on using hashes here.

Provenance

The following attestation bundles were made for polar_high-2.0.2.tar.gz:

Publisher: release.yml on nodal-tools/polar-high

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polar_high-2.0.2-py3-none-any.whl.

File metadata

  • Download URL: polar_high-2.0.2-py3-none-any.whl
  • Upload date:
  • Size: 109.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for polar_high-2.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ae244d598bbf22b81b32cd22c7920afda5a13902a04e3e918c2ef9eeab32b24f
MD5 63765f2f17268b6890095213e77a5d30
BLAKE2b-256 d8b753158580b5789e5e51d563c7d3e00f6aebb3b5b9fb274bfc0e5069846027

See more details on using hashes here.

Provenance

The following attestation bundles were made for polar_high-2.0.2-py3-none-any.whl:

Publisher: release.yml on nodal-tools/polar-high

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page