Glossary¶
The one definition of each name this project uses. The rest hang off one distinction:
A spec is what you write: the math, with no data. A model is a spec with your data attached, a
Modelin Python. A result is one answer read back.
spec ──▶ build ──▶ Model ──▶ solve ──▶ Result
│ (+data) (one answer)
└─────▶ check ──▶ Program
(the plan, for reading)
The chain¶
- Spec
- Short for specification. The math before any data: a YAML file, a
mapping, or a
Specfrommathspec.to_spec. It carries no numbers, and every verb takes it first. ASpeccarries its own program, so one handed back to a verb is not read again. What it may contain is the language. - Program
- The spec lowered to the plan a build reads its rows off: what
checkreturns, still with no data, for reading the plan. No verb takes one back: lowering has no inverse, so keep theSpec(check). The two states are the language's (SpecandProgram). - Formulation
- A block that states rows nothing lowers,
piecewise:today. Every verb refuses a spec still carrying one;to_spec(spec).expand('piecewise')or.expand()writes it out first (check). - Model
- A spec with data attached, the language's own meaning of the word
(glossary).
These docs use it in no other sense.
specsolve.types.Model, whatbuildreturns, is one. OneModelfeeds any sink throughsolve()orwrite(path);row(...)anddiagnostics()read it without solving.update(...)puts new numbers on it in place. - Result
- One answer read back from a solve:
objective,primal(name),dual(name),evaluate(expression)and the rest ofResult. It owns its tables, so it outlives its model. - Answer
- What came back, whichever verb asked: a
Resultfor one solve, aSweepfor a sweep.Result.savewrites one as a directory —record.parquetfor how it terminated,spec.yamlfor the spec it answered, thenprimal/,dual/,expression/and a directory for each output the solve asked for withoutputs=, such asactivity/— and an archive holds that directory asanswer/. The archive of a sweep holds its answer at the same paths, one file per name. - Archive
- A spec, the data it was solved with and what came back, written together as
one zip or one directory by
archive=(archiving). It reads back as aResultArchive, or aSweepArchivewhere the sources were cut. Every table it holds carriesspecsolve_run, the archive's own name, stamped when it is written. Never "artifact".
The verbs¶
- check · build · solve · write
check(spec)validates and lowers.build(spec, sources)returns a Model, whosecheck(sink)asks whether a sink takes it.solveandwritebuild and then solve or stream in one call. There is no Python API for constructing a spec. Each has its entry.- evaluate
evaluate(spec, sources, expression)values one expression of a spec that declares no variables: arithmetic on the attached data, no solver.result.evaluate(expression)is the same read at a solution.- update
model.update(sources)puts new numbers on a built model in place, naming only what changed. A change that moves a mask rebuilds and solves cold.- load · scan
- The two ways a saved answer is read back.
load_result,load_sweepandload_archiveread whole, so the directory is free afterwards.scan_result,scan_sweepandscan_archiveread each frame at the call that asks for it, so the files have to outlive the value (reading one too big to hold). Never "open". - Buildable
- The type alias for a spec argument:
str | Path | Mapping | Spec. The loweredProgramthatcheckreturns is not one. - Source
- The type alias for one value of
sources. The shapes it covers are the data contract. - Label
- One member of a dimension,
windsay, and its type alias:int | float | str | datetime. A sweep's slice key is a label too, andEachCoordinate(dim)slices on one label ofdimat a time.
The data¶
- Index
- A dimension's labels in order, each once, supplied under the dimension's
own key in
sources.shiftreads that order positionally (the data contract). - Coordinate
- One point of a declaration's dimensions: one snapshot for one generator. A parameter has a value at each coordinate it covers, or no row there. The language calls the dimensions themselves the declaration's frame (named expressions).
- Table
- A polars
DataFramewith one column per dimension, avaluecolumn and one row per coordinate: what a parameter arrives as, and whatprimalhands back. A relation's table is the exception, one column per column it declares. The code calls one a frame and means the same thing. - Relation
- A named map between dimensions, supplied under its own key as a table of the rows it has. A keyed relation declares key columns and value columns and holds one row per key; a bare one holds each row at most once (the data contract).
- Assumption
- An
assumptions:entry: a predicate on the data that only the numbers can answer, checked when data attaches and refused as aDataErrorwhere it fails. The conditions apiecewise:method puts on its breakpoints arrive the same way. - Mask
- The
where:on a declaration. What an excluded coordinate means is absence.
How it runs¶
- Lane
- A way a spec is executed. specsolve's is the relational lane: it
validates at load time, lowers to the plan and streams on polars. The test
suite's linopy lane builds the same spec as a
linopy.Model, as the oracle the relational lane is checked against (relationship to linopy). - Engine
- The relational lane's builder: it fills the model's tables from the attached data and hands them to a sink.
- Sink
- Where the handoff lands: a solver (
highs,gurobi,xpress) or a file writer (.lp,.mps).linopyis a lane, not a sink. What a sink can ingest is its capability: a special-ordered set is one, and a sink without it refuses a built model carrying a set rather than rewriting it (what each sink takes). - Sources
- The data you attach: parameter, dimension and relation names to tables, and dimension names to their labels.
- attach
- Fitting sources onto a spec to make a Model; what
builddoes andupdatedoes again. Never "bind", so thatboundmeans one thing.
The built form¶
- Handoff
- The built model as a sink sees it, and all a sink sees:
cols(bounds, type),obj,rows,matrix(CSR),quad(the objective's quadratic part),qmatrix(the constraints') andsos. Handing it over is the phase the metrics clock ashandoff_seconds. - start · previous
- What a solve begins from:
'previous', the default, carries on from the solve before,Nonebegins from nothing, and an earlier answer or tables underprimal,variable_basisandconstraint_basisare matched by coordinate. A start changes how soon the solver reaches the optimum, never the optimum (warm-starting). Never "prior". Unlike a carry, it changes no data.
Sweeps¶
- solve_over (a sweep)
- Solve one spec once per slice of an axis and fold the answers into a
Sweep, releasing each slice's model as it goes. A sweep, never a "study" (sweeps). - Axis · slice · key
- An axis says how the sources split:
EachCoordinate(dim), one slice per label;EachWindow(dim, ...), one per window of consecutive labels; a tuple of them, outer first; or a hand-built list of(key, sources)pairs. A slice is one set of sources, solved as one model, and its key is the label its rows are prefixed with in every table the sweep hands back. - Sweep · per window
- What
solve_overreturns:Result's readers, each returning the answer. A windowed sweep answers over the labels of the dimension it sliced. Any other sweep answers keyed by slice, the key column first.per_window=Truereads a windowed sweep one window at a time, keyed by where each window started, lookahead rows included. - Chain
- The slices of a sweep over several axes that share the keys of every axis outside the outermost one that carries, one scenario's windows for instance. They run in order, and an executor runs chains concurrently, each one as a task (several axes).
- carry
- An axis's
carry={parameter: variable}hands one slice's solution to the next slice of that axis as data, in slice order (carrying state). - held · spilled
- Where a sweep's frames are. A held sweep carries them in memory, and
sweep.primal(name)and the exports — the frame readers, the ones that hand back a table — answer off them. A spilled sweep left them in a directory, which is whatspill_to=writes and whatscan_sweepreads: there a frame reader reads the one name it is asked for, andsweep.scan(name)hands it back lazily (spilling).
Row types¶
- Record · Metrics
- The two saved rows, each a
NamedTuplethat names its own columns:Record, how a solve terminated, andMetrics, what it took. A sweep writes one of each per slice, with the same columns as a single solve;slice_axisandslicesay which slice.
A row is a value and gets a type; a table stays a
Table. So a result hands back its one Record, while
Record and Metrics are the rows behind sweep.record and
sweep.metrics rather than what those hand back, and a reader that wants
one row of a table asks the frame for it.
bound means one thing¶
- bound
- A lower or upper limit on a variable or a constraint row: the
bounds:of a declaration, theBOUNDSsection of an.mpsfile, an absent bound the solver reads as infinity. Nothing else; data is attached, never bound.