Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ name is the *caller*, not the implementer.
that), so a function whose signature already says it takes the one-line
docstring and no block at all — the guide's own escape, and what most private
helpers here want. Half a signature restated is what neither rule accepts.
- **A name is linked as the site links it**: ``[`name`][]`` where the module
imports it, ``[`name`][dotted.path]`` where it does not. A name from another
package is plain code. The docstrings are
[the Python API](docs/reference/api.md), and `pixi run docs-test` refuses a
link that lands nowhere, rendered or not; the suite refuses a Sphinx role.
- **The gate is `src/`**, where a docstring is a contract with a caller. Under
`tests/`, `bench/`, `tools/` and `examples/` the `D` rules are off and so are
the bullets above: there a docstring argues for one assertion or narrates a
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ it releases that version ([RELEASING.md](https://github.com/fluxopt/specsolve/bl

## Upcoming version

- docs: the Python API page renders every public name from its docstring ([#1766](https://github.com/fluxopt/specsolve/pull/1766))
- docs(sweeps): the sweep reference says how many sessions a sweep opens on a remote Gurobi ([#1771](https://github.com/fluxopt/specsolve/pull/1771))

## 0.1.0 (2026-09-25)
Expand Down
4 changes: 3 additions & 1 deletion docs/about/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ reaches the plan. The names, by role:
- the three types a verb hands back, `Model`, `Result` and `Sweep`;
- the error tree under `SpecsolveError`, `NoSolutionError` and `SpecsolveWarning`.

What each one takes and returns is [the Python API](../reference/api.md).
What each one takes and returns is its docstring, which
[the Python API](../reference/api.md) renders. The docstrings are the reference,
so there is no second hand-written copy of it to drift.
`evaluate`, which reads a spec of parameters and expressions as arithmetic,
needs no solver installed.

Expand Down
188 changes: 150 additions & 38 deletions docs/reference/api.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Python API

This page describes what each verb takes, returns, guarantees and refuses, for
anyone who runs a spec from Python. A *spec* is the YAML file; what it may
contain is
This page is the reference for running a spec from Python: every public name,
rendered from its docstring. A *spec* is the YAML file; what it may contain is
[the language](https://mathspec.readthedocs.io/en/latest/reference/language/).

```python
Expand All @@ -16,45 +15,158 @@ result.primal('p') # a polars.DataFrame
result.dual('power_balance')
```

## The verbs
## Reference

Every verb takes the spec first and, except `check`, the *sources* second: the
tables that carry its numbers. The [glossary](glossary.md) defines *model*,
*result*, *sink* and the other house terms this page uses.
Every public name, rendered from its docstring. The [glossary](glossary.md)
defines *model*, *result*, *sink* and the other house terms the entries use.

| | |
|---|---|
| `sps.check(spec, sink=None)` | parse, validate and lower; attach no data. With a `sink`, also say whether that sink takes it. Returns the lowered `Program`, for reading the plan — no verb takes one back |
| `mathspec.to_spec(spec)` | the file as written, for editing and typesetting; the language's own verb |
| `sps.build(spec, sources)` | attach data and build; returns a `Model` |
| `sps.solve(spec, sources, solver_name='highs', solver_options=None)` | build and solve in one call; returns a `Result` |
| `sps.evaluate(spec, sources, expression)` | a spec of parameters and expressions, no variables: one expression read as arithmetic, with no solver; returns its frame |
| `sps.solve_over(spec, sources, axis, ...)` | solve once per slice and fold the answers: [sweeps](sweeps.md) |
| `sps.write(spec, sources, out)` | build and stream to a file; the suffix picks the format |
| `archive=` on `sps.solve`, `model.solve`, `sps.solve_over` | write the spec, its data and this answer as one zip: [Archiving a model](#archiving-a-model) |
| `sps.load_archive(path, into=None)` | an archive back whole as a `SolveArchive`, or a `SweepArchive` where its sources were cut |
| `sps.load_result(directory)` | an answer `result.save(dir)` wrote, back as a `Result` |
| `sps.load_sweep(directory)` | a sweep `sweep.save(dir)` or `solve_over(spill_to=)` wrote, back as a `Sweep` |
| `sps.scan_archive` / `scan_result` / `scan_sweep` | the same three left on disk and read as they are asked for: [loading or scanning](#loading-or-scanning) |
| `model.row(name, **coordinate)` | one built constraint row: terms, comparison, right-hand side |
| `mathspec.to_latex` / `to_typst` / `to_markdown` | the math as a document: [typeset](https://mathspec.readthedocs.io/en/latest/reference/typeset/) |
| `sps.Model` / `sps.Result` / `sps.Sweep` | the types the verbs hand back, importable so a wrapper can annotate its signature. The spec going *in* is `mathspec.Spec` |

## Errors and warnings

**Every error is one tree, rooted at `SpecsolveError`.** `LanguageError` (with
`SchemaError`, `DimensionError`) is a fault in the
spec. `DataError` is a fault in the data attached to it. `LayoutError` is a
directory or an archive that is not a layout this package reads.
`NoSolutionError` is a solve that left nothing to read. A spec the language
### Run a spec

::: specsolve.check
options:
heading_level: 4

::: specsolve.build
options:
heading_level: 4

::: specsolve.solve
options:
heading_level: 4

::: specsolve.write
options:
heading_level: 4

::: specsolve.evaluate
options:
heading_level: 4

### Run it many times

The fold and its two axes; [sweeps](sweeps.md) says how a sweep is cut and read.

::: specsolve.solve_over
options:
heading_level: 4

::: specsolve.EachCoordinate
options:
heading_level: 4

::: specsolve.EachWindow
options:
heading_level: 4

### What comes back

::: specsolve.Model
options:
heading_level: 4

::: specsolve.Result
options:
heading_level: 4

::: specsolve.Sweep
options:
heading_level: 4

The rows and frames those hand back: how a solve terminated, what the build
and its solves took, and what a slice of a sweep took.

::: specsolve.relational.result.Diagnostics
options:
heading_level: 4

::: specsolve.relational.parquet.Record
options:
heading_level: 4

::: specsolve.relational.parquet.Metrics
options:
heading_level: 4

::: specsolve.relational.parquet.SliceMetrics
options:
heading_level: 4

### Carry an answer

::: specsolve.SolveArchive
options:
heading_level: 4

::: specsolve.SweepArchive
options:
heading_level: 4

::: specsolve.load_archive
options:
heading_level: 4

::: specsolve.load_result
options:
heading_level: 4

::: specsolve.load_sweep
options:
heading_level: 4

::: specsolve.scan_archive
options:
heading_level: 4

::: specsolve.scan_result
options:
heading_level: 4

::: specsolve.scan_sweep
options:
heading_level: 4

### Errors and warnings

Every error is one tree, rooted at `SpecsolveError`. A spec the language
accepts and specsolve cannot build raises `SpecsolveError` itself, and its
message names the rewrite that builds the same model.
Which one you get:
[errors](https://mathspec.readthedocs.io/en/latest/reference/language/errors/#which-error-you-get).
message names the rewrite. `LanguageError`, with `SchemaError` and
`DimensionError`, is a fault in the spec, and is the language's own:
[which error you get](https://mathspec.readthedocs.io/en/latest/reference/language/errors/#which-error-you-get).

::: specsolve.SpecsolveError
options:
heading_level: 4

::: specsolve.LanguageError
options:
heading_level: 4

::: specsolve.SchemaError
options:
heading_level: 4

::: specsolve.DimensionError
options:
heading_level: 4

The rest are specsolve's:

::: specsolve.DataError
options:
heading_level: 4

::: specsolve.LayoutError
options:
heading_level: 4

::: specsolve.NoSolutionError
options:
heading_level: 4

::: specsolve.SpecsolveWarning
options:
heading_level: 4

**`SpecsolveWarning` is the one warning category**, and carries `check`'s advice.
`warnings.simplefilter('error', sps.SpecsolveWarning)` makes a spec repository
fail CI on it.

## The spec argument

Expand Down
26 changes: 26 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,32 @@ markdown_extensions:
plugins:
- search
- markdown-exec
- autorefs
- mkdocstrings:
default_handler: python
handlers:
python:
paths: [src]
# The language's errors are re-exported from mathspec, and render from its source.
load_external_modules: true
options:
# The tree documents attributes with `#:` comments.
extensions:
- griffe_sphinx
docstring_style: google
docstring_section_style: spacy
filters:
- "!^_"
merge_init_into_class: true
show_root_heading: true
show_root_full_path: false
show_source: false
show_bases: false
show_if_no_docstring: true
signature_crossrefs: true
scoped_crossrefs: true
show_signature_annotations: false
separate_signature: true

extra_css:
- stylesheets/extra.css
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ codspeed = [
]
docs = [
"zensical==0.0.62",
"mkdocstrings==1.0.6",
"mkdocstrings-python==2.0.9",
"griffe-sphinx==0.3.0",
"markdown-exec==1.12.3",
"pymdown-extensions==11.0.1",
"pygments==2.20.0",
Expand Down Expand Up @@ -167,7 +170,7 @@ cmd = "pytest tests/test_expression_sweep.py -q -n auto --sweep-depth 3 --sweep-
[tool.pixi.feature.docs.tasks]
docs = { cmd = "python -m zensical serve", description = "The site at 127.0.0.1:8000, live-reloading" }
docs-build = { cmd = "python -m zensical build --strict", description = "The site, built the way CI builds it" }
docs-test = { cmd = "pytest tests/test_docs_math.py -q", description = "The site's math, rendered — what a strict build does not check" }
docs-test = { cmd = "pytest tests/test_docs_math.py tests/test_docstring_links.py -q", description = "The site's math, rendered, and every docstring link — what a strict build does not check" }

[tool.pixi.feature.bench.tasks]
density = { cmd = "pytest bench --benchmark-memory --sizes d100 d50 d25 d08 --benchmark-json=bench/results/density.json", description = "The density sweep" }
Expand Down
Loading
Loading