Skip to content

Simplify linopy.spec: one constructor, data in model.parameters, no accessor - #1068

Merged
FabianHofmann merged 3 commits into
spec-whole-modelfrom
spec-simplification
Oct 27, 2026
Merged

FabianHofmann merged 3 commits into
spec-whole-modelfrom
spec-simplification

Conversation

@FabianHofmann

@FabianHofmann FabianHofmann commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Note

The following content was generated by AI.

Stack 2/2, depends on #958

Changes proposed in this Pull Request

Three commits over linopy/spec/ on top of #958. The first (commit 09fd02e) trimmed the accessor: one constructor, no stamp, no presence dataset. The second (commit e87a489) removes the accessor entirely after an API review: a spec-built model is a plain linopy model whose data lives in model.parameters, and results come from two functions of (spec, model). The spec feature is unreleased, so nothing is deprecated; it is removed.

What changed for the user

  • One constructor. Model.from_spec(spec, sources, **model_kwargs) is the only entry point. Model.add_spec and model.spec are gone. A str is YAML text; a file goes in as a Path, and a one-line str ending in .yaml/.yml raises a ValueError that says so.
  • Data lives in model.parameters. from_spec writes every parameter unfilled (NaN holes, int/bool with holes as float) and every relation into model.parameters under their declared names. Model-wide operations such as assign_coords, copy and the netcdf round trip therefore apply to the spec data for free.
  • Two functions instead of an accessor. linopy.spec.evaluate(spec, model, *names) -> dict[str, DataArray] folds the named expressions over the solution and the parameters. linopy.spec.parameter(spec, model, name) returns the filled values with the declared dtype. linopy.spec.__all__ is SpecDataError, evaluate, parameter.
  • Typesetting is mathspec's. mathspec.typeset(spec, fmt) and typeset_line replace model.spec.typeset() and declaration(). The undeclared-name warning is gone with the accessor.
  • Rebuild from a saved model. Model.from_spec(path, read_netcdf(f).parameters) rebuilds a model without the original sources.
  • Sources. NaN in a DataArray, Dataset or wide DataFrame is a missing row under the missing: policy; long tables still refuse it. A long table may carry its values in a column named after the parameter. Sets, extension arrays, Categorical and masked indexes work as dimension sources. Each source key is read once, so a lazy Mapping runs one query per parameter. Labels that differ only by type get a "differ in type, not spelling" error with a datetime hint.
  • synthetic_sources in linopy.testing takes every spec form from_spec takes.
  • missing: refused only raises for demanded holes (commit 746f873). A hole is excused where the term is absent anyway: a where: mask, a masked variable, an absent row of another parameter, an unfilled shift edge or a failed lookup. Build and evaluate share one check that names the parameter, the declaration and the missing labels. parameter() returns NaN at excused holes.

Changes for all models

  • Object-dtype parameters are stored in netcdf as codes plus categories, so NaN holes round-trip instead of coming back as ''.
  • Mixed-type object parameters (for example ['a', 1]) now raise on write instead of being silently written as strings.
  • read_netcdf keeps parameter coordinates that no data variable uses.

Docs

New page doc/spec-data.rst (source contract, SQLite example, holes, rebuild, variants via mathspec). Notebook, doc/api.rst, doc/user-guide.rst and the release notes are updated.

API review findings deferred to issues

Updating data and re-solving (editing model.parameters changes evaluate but not the model); applying a spec to an existing model and composing programs; bare relations and sets derived from data; sparse data densified; data-only named expressions not in model.expressions; lazy per-parameter reads.

Checklist

  • AI-generated content is marked (see AGENTS.md).
  • Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in doc.
  • Unit tests for new features were added (if applicable).
  • A note for the release notes doc/release_notes.rst of the upcoming release is included.
  • I consent to the release of this PR's code under the MIT license.

Keep every parameter unfilled in ModelSpec.parameters and fill on read;
drop the .spec stamp, presence dataset, add_spec, lower_text and the
file-name sniffing of str specs. Public surface is ModelSpec,
NamedExpression, SpecDataError, Declaration and attach. Notebook updated.
from_spec writes raw parameters and relations into model.parameters;
linopy.spec.evaluate and linopy.spec.parameter replace ModelSpec.
Object parameters round-trip netcdf with NaN holes; grid sources accept
NaN as a missing row; sets, extension arrays and named value columns are
accepted; str file names are refused with a Path hint. Docs: spec-data page.
@FabianHofmann FabianHofmann changed the title Simplify linopy.spec: one constructor, no stamp, no presence dataset Simplify linopy.spec: one constructor, data in model.parameters, no accessor Oct 27, 2026
A hole in a refused parameter is excused where where: masks, a masked
variable, an absent row, an unfilled shift edge or a failed lookup make
the term absent anyway. build and evaluate share one check that names
the parameter, the declaration and the missing labels.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant