mathspec 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {mathspec-0.1.0 → mathspec-0.2.0}/PKG-INFO +15 -15
  2. {mathspec-0.1.0 → mathspec-0.2.0}/README.md +13 -13
  3. {mathspec-0.1.0 → mathspec-0.2.0}/pyproject.toml +1 -1
  4. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/__init__.py +5 -5
  5. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/__main__.py +11 -11
  6. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/_expression_parser.py +7 -7
  7. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/_expression_resolver.py +8 -8
  8. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/_sealed.py +1 -1
  9. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/_where_parser.py +7 -7
  10. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/_where_resolver.py +3 -3
  11. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/_yaml.py +9 -9
  12. mathspec-0.1.0/src/mathspec/advice.py → mathspec-0.2.0/src/mathspec/advising.py +15 -9
  13. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/boundedness.py +2 -2
  14. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/degree.py +4 -4
  15. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/dimensions.py +2 -2
  16. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/errors.py +5 -5
  17. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/exclusivity.py +2 -2
  18. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/expansion.py +1 -1
  19. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/lowering.py +10 -10
  20. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/operators.py +1 -1
  21. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/piecewise.py +15 -15
  22. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/program.py +78 -78
  23. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/resolution.py +12 -12
  24. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/separability.py +2 -2
  25. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/sos.py +12 -12
  26. mathspec-0.1.0/src/mathspec/model.py → mathspec-0.2.0/src/mathspec/spec.py +42 -43
  27. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/README.md +8 -8
  28. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/__init__.py +42 -42
  29. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/format.py +7 -7
  30. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/latex.py +1 -1
  31. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/legend.py +6 -6
  32. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/markdown.py +3 -3
  33. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/symbols.py +12 -12
  34. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/typst.py +1 -1
  35. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/typesetting/walk.py +5 -5
  36. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/validation.py +16 -16
  37. {mathspec-0.1.0 → mathspec-0.2.0}/.gitignore +0 -0
  38. {mathspec-0.1.0 → mathspec-0.2.0}/LICENSE +0 -0
  39. {mathspec-0.1.0 → mathspec-0.2.0}/LICENSES/Apache-2.0.txt +0 -0
  40. {mathspec-0.1.0 → mathspec-0.2.0}/LICENSES/CC-BY-4.0.txt +0 -0
  41. {mathspec-0.1.0 → mathspec-0.2.0}/LICENSES/CC0-1.0.txt +0 -0
  42. {mathspec-0.1.0 → mathspec-0.2.0}/LICENSES/MIT.txt +0 -0
  43. {mathspec-0.1.0 → mathspec-0.2.0}/src/mathspec/py.typed +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mathspec
3
- Version: 0.1.0
4
- Summary: Write an optimisation model as a YAML file. Check it and print it as math, with no data and no solver.
3
+ Version: 0.2.0
4
+ Summary: Write the specification (spec) of an optimisation model as a YAML file. Check it and print it as math, with no data and no solver.
5
5
  Project-URL: Documentation, https://mathspec.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/energy-models/mathspec
7
7
  Project-URL: Issues, https://github.com/energy-models/mathspec/issues
@@ -40,36 +40,36 @@ SPDX-License-Identifier: CC-BY-4.0
40
40
 
41
41
  <!--- --8<-- [end:badges] -->
42
42
 
43
- **Write an optimisation model as a YAML file. Check it and print it as math,
44
- with no data and no solver.**
43
+ **Write the specification (spec) of an optimisation model as a YAML file. Check it and print it
44
+ as math, with no data and no solver.**
45
45
 
46
- A mathspec file declares four things: the axes the model runs over, such as
47
- `snapshot` and `generator`; the data it expects, such as `load` and `cost`; the
48
- decisions the solver makes, such as `dispatch`; and the rules those decisions
49
- obey, such as `sum(dispatch, over=generator) == load`. The file
50
- [below](#example) is a complete model.
46
+ A mathspec file states a specification, or spec. A spec declares four things: the axes it runs
47
+ over, such as `snapshot` and `generator`; the data it expects, such as `load`
48
+ and `cost`; the decisions the solver makes, such as `dispatch`; and the rules
49
+ those decisions obey, such as `sum(dispatch, over=generator) == load`. The file
50
+ [below](#example) is a complete spec.
51
51
 
52
52
  <!--- --8<-- [start:engines] -->
53
53
 
54
54
  mathspec builds nothing and solves nothing itself.
55
55
  [specsolve](https://github.com/fluxopt/specsolve) and
56
- [linopy](https://github.com/PyPSA/linopy) build and solve a mathspec model.
57
- Support in both is work in progress.
56
+ [linopy](https://github.com/PyPSA/linopy) build a model from a spec and its
57
+ data, and solve it. Support in both is work in progress.
58
58
 
59
59
  <!--- --8<-- [end:engines] -->
60
60
 
61
61
  <!--- --8<-- [start:benefits] -->
62
62
 
63
- - **Check models in CI, with no data.** A wrong name or
63
+ - **Check specs in CI, with no data.** A wrong name or
64
64
  dimension fails when the file loads, and the error names the fix.
65
65
  [Errors →](https://mathspec.readthedocs.io/en/latest/reference/language/errors/)
66
66
  - **Publish the math you solve.** The equations in the paper
67
67
  print from the file the solver reads.
68
68
  [Typeset →](https://mathspec.readthedocs.io/en/latest/reference/typeset/)
69
- - **One model, many tools.** Engines, renderers and analysers read the model
69
+ - **One spec, many tools.** Engines, renderers and analysers read the spec
70
70
  through one public API, so no two of them can read the file differently.
71
71
  [Program API →](https://mathspec.readthedocs.io/en/latest/reference/program/)
72
- - **Write full-size models.** PyPSA's `n.optimize()` model is
72
+ - **Write full-size specs.** The spec of PyPSA's `n.optimize()` model is
73
73
  one file, with stochastic, multi-period and quadratic variants.
74
74
  [PyPSA in one file →](https://mathspec.readthedocs.io/en/latest/examples/pypsa/)
75
75
 
@@ -206,7 +206,7 @@ ms.to_typst(spec)
206
206
 
207
207
  A [symbol table](https://mathspec.readthedocs.io/en/latest/reference/typeset/#symbol-tables) gives the names their
208
208
  conventional spelling, as in the folded block.
209
- [Print a model as math](https://mathspec.readthedocs.io/en/latest/howto/print/) does the same from a shell.
209
+ [Print a spec as math](https://mathspec.readthedocs.io/en/latest/howto/print/) does the same from a shell.
210
210
 
211
211
  ## Documentation
212
212
 
@@ -15,36 +15,36 @@ SPDX-License-Identifier: CC-BY-4.0
15
15
 
16
16
  <!--- --8<-- [end:badges] -->
17
17
 
18
- **Write an optimisation model as a YAML file. Check it and print it as math,
19
- with no data and no solver.**
18
+ **Write the specification (spec) of an optimisation model as a YAML file. Check it and print it
19
+ as math, with no data and no solver.**
20
20
 
21
- A mathspec file declares four things: the axes the model runs over, such as
22
- `snapshot` and `generator`; the data it expects, such as `load` and `cost`; the
23
- decisions the solver makes, such as `dispatch`; and the rules those decisions
24
- obey, such as `sum(dispatch, over=generator) == load`. The file
25
- [below](#example) is a complete model.
21
+ A mathspec file states a specification, or spec. A spec declares four things: the axes it runs
22
+ over, such as `snapshot` and `generator`; the data it expects, such as `load`
23
+ and `cost`; the decisions the solver makes, such as `dispatch`; and the rules
24
+ those decisions obey, such as `sum(dispatch, over=generator) == load`. The file
25
+ [below](#example) is a complete spec.
26
26
 
27
27
  <!--- --8<-- [start:engines] -->
28
28
 
29
29
  mathspec builds nothing and solves nothing itself.
30
30
  [specsolve](https://github.com/fluxopt/specsolve) and
31
- [linopy](https://github.com/PyPSA/linopy) build and solve a mathspec model.
32
- Support in both is work in progress.
31
+ [linopy](https://github.com/PyPSA/linopy) build a model from a spec and its
32
+ data, and solve it. Support in both is work in progress.
33
33
 
34
34
  <!--- --8<-- [end:engines] -->
35
35
 
36
36
  <!--- --8<-- [start:benefits] -->
37
37
 
38
- - **Check models in CI, with no data.** A wrong name or
38
+ - **Check specs in CI, with no data.** A wrong name or
39
39
  dimension fails when the file loads, and the error names the fix.
40
40
  [Errors →](https://mathspec.readthedocs.io/en/latest/reference/language/errors/)
41
41
  - **Publish the math you solve.** The equations in the paper
42
42
  print from the file the solver reads.
43
43
  [Typeset →](https://mathspec.readthedocs.io/en/latest/reference/typeset/)
44
- - **One model, many tools.** Engines, renderers and analysers read the model
44
+ - **One spec, many tools.** Engines, renderers and analysers read the spec
45
45
  through one public API, so no two of them can read the file differently.
46
46
  [Program API →](https://mathspec.readthedocs.io/en/latest/reference/program/)
47
- - **Write full-size models.** PyPSA's `n.optimize()` model is
47
+ - **Write full-size specs.** The spec of PyPSA's `n.optimize()` model is
48
48
  one file, with stochastic, multi-period and quadratic variants.
49
49
  [PyPSA in one file →](https://mathspec.readthedocs.io/en/latest/examples/pypsa/)
50
50
 
@@ -181,7 +181,7 @@ ms.to_typst(spec)
181
181
 
182
182
  A [symbol table](https://mathspec.readthedocs.io/en/latest/reference/typeset/#symbol-tables) gives the names their
183
183
  conventional spelling, as in the folded block.
184
- [Print a model as math](https://mathspec.readthedocs.io/en/latest/howto/print/) does the same from a shell.
184
+ [Print a spec as math](https://mathspec.readthedocs.io/en/latest/howto/print/) does the same from a shell.
185
185
 
186
186
  ## Documentation
187
187
 
@@ -20,7 +20,7 @@ only-include = ["src", "README.md", "LICENSE", "LICENSES"]
20
20
 
21
21
  [project]
22
22
  name = "mathspec"
23
- description = "Write an optimisation model as a YAML file. Check it and print it as math, with no data and no solver."
23
+ description = "Write the specification (spec) of an optimisation model as a YAML file. Check it and print it as math, with no data and no solver."
24
24
  authors = [
25
25
  { name = "Felix Bumann", email = "117816358+FBumann@users.noreply.github.com" },
26
26
  { name = "Bryn Pickering", email = "17178478+brynpickering@users.noreply.github.com" },
@@ -4,18 +4,18 @@
4
4
 
5
5
  """The language: what a YAML file may say, and what it means.
6
6
 
7
- Two public states — a :class:`~mathspec.model.Spec` is what the file *says*,
8
- and its :attr:`~mathspec.model.Spec.program` is what it *means* — and
9
- :func:`to_spec`, the one door to both. Everything between them — both grammars
7
+ Two public states — a [`Spec`][] is what the file *says*,
8
+ and its [`program`][mathspec.spec.Spec.program] is what it *means* — and
9
+ [`to_spec`][], the one door to both. Everything between them — both grammars
10
10
  and the tree they build — is package-private, because a consumer reads a
11
11
  program instead.
12
12
  """
13
13
 
14
14
  from mathspec import program
15
- from mathspec.advice import advice
15
+ from mathspec.advising import advice
16
16
  from mathspec.errors import Advice, AdviceKind, DimensionError, LanguageError, MathSpecError, SchemaError, did_you_mean
17
- from mathspec.model import Spec
18
17
  from mathspec.operators import BUILTIN_NAMES
18
+ from mathspec.spec import Spec
19
19
  from mathspec.typesetting import (
20
20
  FORMATS,
21
21
  SymbolTable,
@@ -2,13 +2,13 @@
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
- """``python -m mathspec <verb> model.yaml`` — the shell front.
5
+ """``python -m mathspec <verb> spec.yaml`` — the shell front.
6
6
 
7
7
  ``check`` loads the file and prints the language's advice; one further verb
8
- per typeset format, read off :data:`mathspec.typesetting.FORMATS`. Every verb
8
+ per typeset format, read off [`mathspec.typesetting.FORMATS`][]. Every verb
9
9
  reads the file as written, and nothing here writes a formulation out unasked.
10
10
  The typeset verbs take ``--expand``, because a shell cannot compose
11
- :meth:`~mathspec.model.Spec.expand` the way a caller does and the rows are a
11
+ [`expand`][mathspec.spec.Spec.expand] the way a caller does and the rows are a
12
12
  different document; ``check`` has no such flag, because advice reads a block
13
13
  as the rows it states.
14
14
  """
@@ -19,7 +19,7 @@ import argparse
19
19
  import sys
20
20
  from pathlib import Path
21
21
 
22
- from mathspec.advice import advice
22
+ from mathspec.advising import advice
23
23
  from mathspec.errors import MathSpecError
24
24
  from mathspec.typesetting import FORMATS, typeset
25
25
  from mathspec.validation import to_spec
@@ -30,12 +30,12 @@ def parser() -> argparse.ArgumentParser:
30
30
  front = argparse.ArgumentParser(prog='python -m mathspec')
31
31
  verbs = front.add_subparsers(dest='verb', required=True)
32
32
 
33
- check = verbs.add_parser('check', help='load a model, and print what the language advises')
34
- check.add_argument('model', help='path to a mathspec YAML model')
33
+ check = verbs.add_parser('check', help='load a spec, and print what the language advises')
34
+ check.add_argument('spec', help='path to a mathspec YAML file')
35
35
 
36
36
  for name in FORMATS:
37
- verb = verbs.add_parser(name, help=f'render a model as {name}')
38
- verb.add_argument('model', help='path to a mathspec YAML model')
37
+ verb = verbs.add_parser(name, help=f'render a spec as {name}')
38
+ verb.add_argument('spec', help='path to a mathspec YAML file')
39
39
  verb.add_argument('-o', '--out', help='write here instead of stdout')
40
40
  verb.add_argument('--symbols', help='sidecar YAML saying how names should print')
41
41
  verb.add_argument('--standalone', action='store_true', help='emit a compilable document')
@@ -60,15 +60,15 @@ def main(argv: list[str] | None = None) -> int:
60
60
  args = parser().parse_args(argv)
61
61
  if args.verb == 'check':
62
62
  try:
63
- notes = advice(args.model)
63
+ notes = advice(args.spec)
64
64
  except MathSpecError as e:
65
65
  sys.stderr.write(f'{e}\n')
66
66
  return 1
67
67
  sys.stdout.write(''.join(f'{note}\n' for note in notes))
68
68
  return 0
69
- model = to_spec(args.model).expand() if args.expand else args.model
69
+ spec = to_spec(args.spec).expand() if args.expand else args.spec
70
70
  text = typeset(
71
- model,
71
+ spec,
72
72
  args.verb,
73
73
  symbols=args.symbols,
74
74
  standalone=args.standalone,
@@ -5,7 +5,7 @@
5
5
  """The syntax tree the expression grammar builds, and the grammar — package-private.
6
6
 
7
7
  Only expansion and resolution read it: resolution rewrites it into the
8
- :mod:`mathspec.program` vocabulary, which every pass after reads. Arithmetic
8
+ [`mathspec.program`][] vocabulary, which every pass after reads. Arithmetic
9
9
  nests anywhere; a comparison appears only at the top of a parsed expression.
10
10
  """
11
11
 
@@ -31,7 +31,7 @@ ComparisonOperator = Literal['<=', '>=', '==']
31
31
  UnaryOperator = Literal['+', '-']
32
32
 
33
33
  #: The arithmetic a binary operator may spell. Closed by the grammar, and the
34
- #: vocabulary a renderer dispatching on :attr:`BinaryOperatorNode.op`
34
+ #: vocabulary a renderer dispatching on [`BinaryOperatorNode.op`][]
35
35
  #: switches over — it keeps no list of its own.
36
36
  BinaryOperator = Literal['+', '-', '*', '/', '**']
37
37
 
@@ -145,7 +145,7 @@ class ComparisonNode:
145
145
  right: ArithmeticNode
146
146
 
147
147
  def __str__(self) -> str:
148
- """Both sides bare: a comparison is not an :data:`ArithmeticNode`, so nothing can take one as an operand."""
148
+ """Both sides bare: a comparison is not an [`ArithmeticNode`][], so nothing can take one as an operand."""
149
149
  return f'{self.left} {self.op} {self.right}'
150
150
 
151
151
 
@@ -191,7 +191,7 @@ def nodes(*roots: ParsedNode) -> Iterator[ParsedNode]:
191
191
  """Every node under *roots*, each root itself included, parents first.
192
192
 
193
193
  The traversal a question about a tree is a filter of, the way
194
- :func:`mathspec.program.walk` is for a program.
194
+ [`mathspec.program.walk`][] is for a program.
195
195
  """
196
196
  for root in roots:
197
197
  yield root
@@ -199,7 +199,7 @@ def nodes(*roots: ParsedNode) -> Iterator[ParsedNode]:
199
199
 
200
200
 
201
201
  def with_children(node: ArithmeticNode, recurse: Callable[[ArithmeticNode], ArithmeticNode]) -> ArithmeticNode:
202
- """*node* rebuilt with *recurse* applied to each of its :func:`children`; a leaf comes back as is."""
202
+ """*node* rebuilt with *recurse* applied to each of its [`children`][]; a leaf comes back as is."""
203
203
  if isinstance(node, NumberNode | NameNode | NameListNode | KeywordNode):
204
204
  return node
205
205
  if isinstance(node, UnaryOperatorNode):
@@ -346,11 +346,11 @@ def parse_text[T](
346
346
  child_of: Callable[[T], tuple[T, ...]],
347
347
  deep_rewrite: str,
348
348
  ) -> T:
349
- """Parse the whole of *text* with *grammar*, or raise :class:`SchemaError` naming *what* failed to parse.
349
+ """Parse the whole of *text* with *grammar*, or raise [`SchemaError`][] naming *what* failed to parse.
350
350
 
351
351
  *rewrite* is asked for the predictable mistake at the failure position; its
352
352
  sentence, if any, precedes the grammar's own complaint. A tree nesting past
353
- :data:`MAX_DEPTH`, measured through *child_of*, is refused with
353
+ [`MAX_DEPTH`][], measured through *child_of*, is refused with
354
354
  *deep_rewrite* — and so is one the parser itself ran out of stack on. The
355
355
  node comes back as the type *child_of* walks, which is the grammar's word
356
356
  for what it builds.
@@ -4,9 +4,9 @@
4
4
 
5
5
  """The expression walk of name resolution: an arithmetic syntax tree into the program's expression nodes.
6
6
 
7
- Every operator call is read here — its shape against :data:`~mathspec.operators.BUILTINS`,
7
+ Every operator call is read here — its shape against [`BUILTINS`][],
8
8
  its dimension and relation arguments against the namespace — and the node it
9
- stands for is built. :mod:`mathspec.resolution` holds the namespace and the
9
+ stands for is built. [`mathspec.resolution`][] holds the namespace and the
10
10
  doors that call this.
11
11
  """
12
12
 
@@ -32,7 +32,6 @@ from mathspec._expression_parser import (
32
32
  )
33
33
  from mathspec.dimensions import dims_of
34
34
  from mathspec.errors import DimensionError, SchemaError, did_you_mean
35
- from mathspec.model import NUMERIC_DTYPES
36
35
  from mathspec.operators import (
37
36
  AMOUNTS,
38
37
  BUILTINS,
@@ -62,6 +61,7 @@ from mathspec.program import (
62
61
  carries_variable,
63
62
  children,
64
63
  )
64
+ from mathspec.spec import NUMERIC_DTYPES
65
65
 
66
66
  if TYPE_CHECKING:
67
67
  from collections.abc import Mapping
@@ -101,7 +101,7 @@ class ExpressionResolver:
101
101
  return isinstance(value, NameNode) and value.name in self.formals
102
102
 
103
103
  def build(self, node: ArithmeticNode) -> Expression | None:
104
- """The program tree *node* stands for, held to :data:`MAX_RESOLVED_DEPTH` before anything walks it.
104
+ """The program tree *node* stands for, held to [`MAX_RESOLVED_DEPTH`][] before anything walks it.
105
105
 
106
106
  The depth is measured with every named expression written in, since
107
107
  that is the tree every later pass recurses over, and measured with an
@@ -175,9 +175,9 @@ class ExpressionResolver:
175
175
  def _name(self, node: NameNode) -> Expression | None:
176
176
  """A bare name as the variable, parameter or named expression it declares; a dimension or relation is not a value.
177
177
 
178
- A named expression arrives as the one node :meth:`Namespace.named`
178
+ A named expression arrives as the one node [`Namespace.named`][]
179
179
  built for it; the cast is the one place a
180
- :class:`~mathspec.program.Named` enters a tree typed as a program's,
180
+ [`Named`][mathspec.program.Named] enters a tree typed as a program's,
181
181
  which lowering makes true.
182
182
  """
183
183
  if node.name in self.formals:
@@ -360,7 +360,7 @@ class ExpressionResolver:
360
360
  def _amount(self, value: ArithmeticNode, operator: str, key: str) -> int | str | None:
361
361
  """``offset=`` or ``window=``: a whole number in the operator's range, or the name of a parameter.
362
362
 
363
- Closed so that :func:`mathspec.dimensions._check_named_amount` sees
363
+ Closed so that [`mathspec.dimensions._check_named_amount`][] sees
364
364
  every parameter an amount carries, and so that a program's
365
365
  ``offset`` and ``width`` are the ``int | str`` they say.
366
366
  """
@@ -441,7 +441,7 @@ class ExpressionResolver:
441
441
  Constraints sit outside the flat namespace, so this store is consulted
442
442
  only here — a bare name in arithmetic never reaches it. A dual standing
443
443
  where the math is built is refused separately
444
- (:mod:`mathspec.degree`); this pass only types the name.
444
+ ([`mathspec.degree`][]); this pass only types the name.
445
445
  """
446
446
  (value,) = node.args
447
447
  if self._formal(value):
@@ -19,7 +19,7 @@ class Sealed[K, V](Mapping[K, V]):
19
19
  What every group a program hands out is held behind, and a node's
20
20
  keyword arguments with them: a consumer cannot rewrite what another
21
21
  consumer reads, and the whole crosses a process — which is the one thing
22
- :class:`types.MappingProxyType` cannot do. Equal to any mapping with the
22
+ [`types.MappingProxyType`][] cannot do. Equal to any mapping with the
23
23
  same items, and hashable over them where they are, so a dataclass may
24
24
  hold one as a default.
25
25
  """
@@ -8,8 +8,8 @@ A where string is a boolean algebra over comparisons, and a comparison's
8
8
  sides are the expression grammar's own arithmetic. What a side *is* — a
9
9
  parameter, a dimension, a relation column, a ``position()`` — only the schema
10
10
  knows, so the grammar hands both sides over bare and
11
- :mod:`mathspec.resolution` reads them. The resolved vocabulary lives in
12
- :mod:`mathspec.program`.
11
+ [`mathspec.resolution`][] reads them. The resolved vocabulary lives in
12
+ [`mathspec.program`][].
13
13
  """
14
14
 
15
15
  from __future__ import annotations
@@ -71,7 +71,7 @@ class UnresolvedPredicateCallNode:
71
71
  arithmetic arguments, and a predicate is not arithmetic. So the where
72
72
  grammar reads it, and resolution decides which operator the name is and
73
73
  what the kwargs mean. ``kwargs`` is held and hashed as
74
- :class:`~mathspec._expression_parser.FunctionCallNode` holds its own.
74
+ [`FunctionCallNode`][mathspec._expression_parser.FunctionCallNode] holds its own.
75
75
  """
76
76
 
77
77
  name: str
@@ -86,7 +86,7 @@ class UnresolvedPredicateCallNode:
86
86
  class UnresolvedCountNode:
87
87
  """``count(<predicate>, over=<dim>) <op> <number>`` — a count against a literal.
88
88
 
89
- Its own node rather than an :class:`UnresolvedComparisonNode` with a call
89
+ Its own node rather than an [`UnresolvedComparisonNode`][] with a call
90
90
  on the left: every other comparison has arithmetic on both sides, and
91
91
  widening that one to carry a predicate would widen every reader of a side
92
92
  with it.
@@ -103,7 +103,7 @@ class UnresolvedComparisonNode:
103
103
 
104
104
  A side is the expression grammar's arithmetic, so a name, a number and a
105
105
  ``position(...)`` call all arrive as the nodes an expression would carry
106
- them in, a quoted label as the :class:`KeywordNode` a quoted kwarg is, and a
106
+ them in, a quoted label as the [`KeywordNode`][] a quoted kwarg is, and a
107
107
  relation column in a node of its own.
108
108
  """
109
109
 
@@ -272,8 +272,8 @@ def parse_where(text: str) -> Predicate | UnresolvedWhereNode:
272
272
  """Parse a where string into an AST, its leaves still unresolved.
273
273
 
274
274
  The connectives and literals are the resolved vocabulary's own; the leaves
275
- naming declarations are a bare :class:`NameNode` or an ``Unresolved*``
276
- node, which only :func:`~mathspec.resolution.resolve_where` takes.
275
+ naming declarations are a bare [`NameNode`][] or an ``Unresolved*``
276
+ node, which only [`resolve_where`][mathspec.resolution.resolve_where] takes.
277
277
 
278
278
  Raises:
279
279
  SchemaError: If *text* is not a where string of the language. A
@@ -7,7 +7,7 @@
7
7
  A bare name, a comparison, a count and a predicate read through a relation
8
8
  or along a dimension are each typed here against the namespace, and a
9
9
  comparison of expressions hands its sides to the expression walk.
10
- :mod:`mathspec.resolution` holds the namespace and the doors that call this.
10
+ [`mathspec.resolution`][] holds the namespace and the doors that call this.
11
11
  """
12
12
 
13
13
  from __future__ import annotations
@@ -90,7 +90,7 @@ class WhereResolver:
90
90
  appended to ``errors``; every sibling is still read, so a mask with two
91
91
  faults reports both. ``self_variable`` is the variable whose own
92
92
  ``where`` is being read, which may not ask whether it exists. A side
93
- that is an expression is built by an :class:`ExpressionResolver` over
93
+ that is an expression is built by an [`ExpressionResolver`][] over
94
94
  the same namespace.
95
95
  """
96
96
 
@@ -169,7 +169,7 @@ class WhereResolver:
169
169
  """``shift(<predicate>, along=, offset=)`` or ``at(<predicate>, by=, over=, into=)`` — the two operators that read a predicate and answer one.
170
170
 
171
171
  ``count`` answers a number, so it stands on a comparison's side and
172
- :meth:`_count` reads it there. Anything else naming a predicate is
172
+ [`_count`][] reads it there. Anything else naming a predicate is
173
173
  refused here rather than resolved into arithmetic it cannot be.
174
174
 
175
175
  An operand that failed to resolve is handed straight back: resolution
@@ -94,10 +94,10 @@ def read_yaml(path: Path | str) -> dict[str, object]:
94
94
  return parse_yaml(Path(path).read_text(encoding='utf-8'), str(path))
95
95
 
96
96
 
97
- def read_model(model: str | Path) -> dict[str, object]:
98
- """A model from a file or from its text — a newline decides which a ``str`` is.
97
+ def read_spec(spec: str | Path) -> dict[str, object]:
98
+ """A spec from a file or from its text — a newline decides which a ``str`` is.
99
99
 
100
- A :class:`~pathlib.Path` names a file, and so does a ``str`` with no
100
+ A [`Path`][] names a file, and so does a ``str`` with no
101
101
  newline in it, since no path holds one; a ``str`` with a newline is the
102
102
  YAML itself, which every file and every ``to_yaml()`` ends with. Nothing
103
103
  else is read off the content and nothing is read off the disk to decide.
@@ -106,15 +106,15 @@ def read_model(model: str | Path) -> dict[str, object]:
106
106
  FileNotFoundError: A ``str`` with no newline that names no file — a
107
107
  one-line text is told to end with one.
108
108
  """
109
- if isinstance(model, str) and '\n' not in model and not Path(model).is_file():
109
+ if isinstance(spec, str) and '\n' not in spec and not Path(spec).is_file():
110
110
  msg = (
111
- f'no file named {model!r}. A str with no newline in it is read as a path, and YAML text '
111
+ f'no file named {spec!r}. A str with no newline in it is read as a path, and YAML text '
112
112
  f'is told apart by one — end the text with a newline, or pass a Path.'
113
113
  )
114
114
  raise FileNotFoundError(msg)
115
- if isinstance(model, Path) or '\n' not in model:
116
- return read_yaml(Path(model))
117
- return parse_yaml(model, 'YAML text')
115
+ if isinstance(spec, Path) or '\n' not in spec:
116
+ return read_yaml(Path(spec))
117
+ return parse_yaml(spec, 'YAML text')
118
118
 
119
119
 
120
120
  def parse_yaml(text: str, origin: str = '<string>') -> dict[str, object]:
@@ -136,6 +136,6 @@ def parse_yaml(text: str, origin: str = '<string>') -> dict[str, object]:
136
136
  if not data:
137
137
  return {}
138
138
  if not isinstance(data, dict):
139
- msg = f'{origin}: a model file must be a mapping of sections (dimensions:, variables:, …), got {type(data).__name__}.'
139
+ msg = f'{origin}: a spec file must be a mapping of sections (dimensions:, variables:, …), got {type(data).__name__}.'
140
140
  raise SchemaError(msg)
141
141
  return data
@@ -4,7 +4,7 @@
4
4
 
5
5
  """Advice — what is decidable without data and is a note rather than a refusal.
6
6
 
7
- One door, :func:`advice`, over every pass of that kind.
7
+ One door, [`advice`][], over every pass of that kind.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
@@ -20,23 +20,29 @@ if TYPE_CHECKING:
20
20
  from collections.abc import Mapping
21
21
  from pathlib import Path
22
22
 
23
- from mathspec.model import Spec
23
+ from mathspec.spec import Spec
24
24
 
25
25
 
26
- def advice(model: str | Path | Mapping[str, object] | Spec | Program) -> tuple[Advice, ...]:
27
- """Everything the language advises about *model* — never an error, decidable without data.
26
+ def advice(spec: str | Path | Mapping[str, object] | Spec | Program) -> tuple[Advice, ...]:
27
+ """Everything the language advises about *spec*, decided without data.
28
+
29
+ Advice is a note, not a refusal: a file with advice still loads.
28
30
 
29
31
  Args:
30
- model: A YAML path, a mapping, a loaded :class:`Spec`, or a
31
- :class:`Program`, read as it arrived. A ``piecewise:`` or ``sos:``
32
- block is read as the rows it states, so the answer is the one its
33
- expansion gets, with nothing expanded.
32
+ spec: Anything [`to_spec`][] accepts, or a [`Program`][], read as
33
+ it arrived. A ``piecewise:`` or ``sos:`` block is read as the rows
34
+ it states, so the answer is the one its expansion gets, with
35
+ nothing expanded.
34
36
 
35
37
  Returns:
36
38
  The never-an-axis advice in declaration order, then the unboundedness
37
39
  advice; ``str()`` of each is its sentence.
40
+
41
+ Raises:
42
+ LanguageError: *spec* does not load; [`to_spec`][] says why.
43
+ FileNotFoundError: A ``str`` with no newline that names no file.
38
44
  """
39
- program = model if isinstance(model, Program) else to_spec(model).program
45
+ program = spec if isinstance(spec, Program) else to_spec(spec).program
40
46
  return tuple(_never_an_axis(program) + unbounded_notes(program))
41
47
 
42
48
 
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
- """Provably unbounded models, named before a solver says a bare ``unbounded``.
5
+ """Provably unbounded specs, named before a solver says a bare ``unbounded``.
6
6
 
7
7
  A variable unbounded on the side its objective term improves toward, and named
8
8
  by no constraint, runs to infinity for any data. Which side is read off the
@@ -84,7 +84,7 @@ def unbounded_notes(program: Program) -> list[Advice]:
84
84
  Advice(
85
85
  'unbounded',
86
86
  vname,
87
- f"Variable '{vname}' makes this model unbounded: no constraint names it, and "
87
+ f"Variable '{vname}' makes this spec unbounded: no constraint names it, and "
88
88
  f'bounds.{side} is open, which is the direction a {sign}{vname} term '
89
89
  f'improves a {program.objective.sense} objective in. No data can change that, so '
90
90
  f'the solve would answer `unbounded` and name nothing.\n'
@@ -103,7 +103,7 @@ def _degree(node: Expression) -> int:
103
103
  """The polynomial degree *node* stands for, counted structurally.
104
104
 
105
105
  A product adds its factors' degrees and a division keeps the dividend's
106
- (:func:`check_binary` has already refused a divisor carrying a variable);
106
+ ([`check_binary`][] has already refused a divisor carrying a variable);
107
107
  everything else — a sum, a reduction, a shape operator — is the highest
108
108
  degree beneath it. No data, so this answers at ``check`` time, which is
109
109
  what stops a cubic from reaching a consumer to be refused by whichever one
@@ -184,7 +184,7 @@ def _joins_terms(node: Expression) -> bool:
184
184
 
185
185
 
186
186
  def check_expression(node: Expression, context: str, *, ceiling: int = 1) -> None:
187
- """What the math admits at one position: no dual anywhere under *node*, then :func:`check_binary` everywhere in it.
187
+ """What the math admits at one position: no dual anywhere under *node*, then [`check_binary`][] everywhere in it.
188
188
 
189
189
  Asked of the resolved tree, so a dual or a product reached through a
190
190
  macro or a named expression is caught alongside one written in place.
@@ -192,7 +192,7 @@ def check_expression(node: Expression, context: str, *, ceiling: int = 1) -> Non
192
192
 
193
193
  Raises:
194
194
  LanguageError: A dual, which exists only after a solve; or what
195
- :func:`check_binary` refuses.
195
+ [`check_binary`][] refuses.
196
196
  """
197
197
  for found in walk(node):
198
198
  if isinstance(found, Dual):
@@ -205,5 +205,5 @@ def check_expression(node: Expression, context: str, *, ceiling: int = 1) -> Non
205
205
 
206
206
 
207
207
  def calls_dual(node: Expression) -> bool:
208
- """Whether a :class:`~mathspec.program.Dual` stands anywhere under *node*."""
208
+ """Whether a [`Dual`][mathspec.program.Dual] stands anywhere under *node*."""
209
209
  return any(isinstance(found, Dual) for found in walk(node))
@@ -53,8 +53,8 @@ from mathspec.program import (
53
53
  )
54
54
 
55
55
  if TYPE_CHECKING:
56
- from mathspec.model import Spec
57
56
  from mathspec.program import Program
57
+ from mathspec.spec import Spec
58
58
 
59
59
 
60
60
  def dims_of(node: Expression, schema: Spec, context: str) -> frozenset[str]:
@@ -343,7 +343,7 @@ def _check_where_dims(
343
343
  """A predicate may only test dims the frame carries; reducing an outside dim to fit would fail open.
344
344
 
345
345
  The refusal names the leaf that left the frame, reading its dims as
346
- :attr:`~mathspec.program.Mask.dims` does.
346
+ [`dims`][mathspec.program.Mask.dims] does.
347
347
  """
348
348
  if mask is None:
349
349
  return
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
  from pydantic import ValidationError
17
17
 
18
18
 
19
- #: Which pass an :class:`Advice` comes from. Closed, like the operator set: a
19
+ #: Which pass an [`Advice`][] comes from. Closed, like the operator set: a
20
20
  #: consumer filtering on it can enumerate every value.
21
21
  AdviceKind = Literal['never-an-axis', 'unbounded']
22
22
 
@@ -25,7 +25,7 @@ AdviceKind = Literal['never-an-axis', 'unbounded']
25
25
  class Advice:
26
26
  """One thing the language advises about a file it accepts.
27
27
 
28
- Never an error: each is what a half-written model looks like too. A
28
+ Never an error: each is what a half-written spec looks like too. A
29
29
  consumer prints it, or filters on ``kind`` and ``subject``; the text is the
30
30
  language's, so no consumer writes its own.
31
31
 
@@ -48,7 +48,7 @@ class MathSpecError(ValueError):
48
48
 
49
49
 
50
50
  class LanguageError(MathSpecError):
51
- """The model is not sayable in the language, or does not obey its rules."""
51
+ """The spec is not sayable in the language, or does not obey its rules."""
52
52
 
53
53
 
54
54
  class SchemaError(LanguageError):
@@ -71,8 +71,8 @@ def did_you_mean(name: str, known: Iterable[str], *, label: str = 'Declared') ->
71
71
  def schema_error(exc: ValidationError) -> LanguageError:
72
72
  """A pydantic ``ValidationError`` as one of ours.
73
73
 
74
- Returns the original :class:`LanguageError` subclass where exactly one
75
- error carries one, and a :class:`SchemaError` otherwise.
74
+ Returns the original [`LanguageError`][] subclass where exactly one
75
+ error carries one, and a [`SchemaError`][] otherwise.
76
76
  """
77
77
  errors = exc.errors()
78
78
  lines = []