Skip to content
Closed
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
26 changes: 13 additions & 13 deletions docs/about/limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ costs to add.
`variables:` or `given_variables:`. One enters where it says something no
section already says, where a file decides it without data, and where the
typesetter prints it. `given_variables:` entered on all three: nothing else
states that a column belongs to another file, which is what lets a template
load and print on its own.
states that a column belongs to another file, which is what lets a component
file load and print on its own.

A request that is none of the three is refused, and the
[table of refusals](#deliberate-non-primitives) records it with what to write
Expand Down Expand Up @@ -174,10 +174,10 @@ That another tool has a feature is not by itself a reason to add it.

## Composition (component libraries)

A component library is a set of templates, such as a boiler, a battery and a
line, that agree on how ports and flows are named. You merge the templates you
need into one file, wire the components together with a connectivity table in
the data, and close the system with one `sum(by=)` balance.
A component library is a set of files, one per component type — a boiler, a
battery, a line — that agree on how ports and flows are named. You merge the
files you need into one, wire the components together with a connectivity table
in the data, and close the system with one `sum(by=)` balance.

The topology is data. Adding a second battery is a row in a table, not a second
block of YAML, so the file grows with the number of component _types_ and not
Expand All @@ -189,16 +189,16 @@ a path, so a model assembled in Python is checked exactly as a file is, and
file may hold. There is no Python API that builds models any other way.

Two verbs do this, and they answer different questions. `merge` composes
peers, so a name two templates declare is a collision and the order they are
peers, so a name two fragments declare is a collision and the order they are
given in means nothing. `override` lays patches over a base, which is what a
framework ships and a project extends, a field at a time.
[Compose a model from several files](../howto/compose.md) is the recipe for
both.

A template reads the coupling surface it is written against, and declares that
column under `given_variables:`. So a template loads on its own, and prints as
math on its own, which is what it could not do while a fragment was a file the
loader had to refuse. `merge` folds each given declaration into the one that
A component file reads the coupling surface it is written against, and declares
that column under `given_variables:`. So it loads on its own, and prints as math
on its own, which is what it could not do while a fragment was a file the loader
had to refuse. `merge` folds each given declaration into the one that
introduces it, so a composed library carries none.

A layer over a model this language never sees — one built through linopy, say —
Expand All @@ -219,8 +219,8 @@ exactly, never changed, because the expressions written over an axis are
already in the base.

Two requests were closed against this design. A built-in merge (#30) and
namespaces so that two templates can each declare a `p` (#29) are both things a
namespaces so that two fragments can each declare a `p` (#29) are both things a
library does before it hands over a `dict`. Arithmetic in `bounds:`, which signed
and bidirectional flows need, is still open as #31. A component whose number of
ports is only known at run time belongs in the library, which emits more rows or
more templates, and never one block of YAML per component.
more files, and never one block of YAML per component.
4 changes: 2 additions & 2 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ or starts printing different math fails CI.
declaration at a time. PyPSA's name for each row sits beside the YAML and the
equation.
- [A component library](library/index.md) is several files that compose into
one model. Each template reads the coupling surface and prints on its own,
and the composed page shows what `merge` returns.
one model. Each file reads the coupling surface and prints on its own, and the
composed page shows what `merge` returns.

The math on these pages is printed by the typesetter from the file above it. See
[Typeset the math](../reference/typeset.md) to print your own.
10 changes: 5 additions & 5 deletions docs/howto/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ SPDX-License-Identifier: CC-BY-4.0
# Compose a model from several files

Build one model out of files that each say part of it. `merge` composes
**peers** — the templates of a component library, where a name two of them
declare is a collision. `override` lays **patches** over a base — what a
**peers** — the files of a component library, where a name two of them declare
is a collision. `override` lays **patches** over a base — what a
framework ships and a project extends. Both hand back one mapping, which
[`to_spec`](../reference/language/reading.md) loads like any file, and they
compose: `override(merge({…}), {…})`.

## A library of templates
## A library of components

1. **Write the coupling surface as a model.** One flow per port, one balance
per bus. Nothing in it knows which components exist.
Expand Down Expand Up @@ -131,12 +131,12 @@ compose: `override(merge({…}), {…})`.
Fragments own their math, so a name two of them declare is refused, both named. Here two files each say what a generator fleet is:

```text
fragments 'gas' and 'coal' both declare the parameter 'Generator_p_nom'. Two of the same kind of thing are two rows of a dimension rather than two fragments: merge the template once, and let the data carry both. Different math under one spelling is a rename — call one of them something else.
fragments 'gas' and 'coal' both declare the parameter 'Generator_p_nom'. Two of the same kind of thing are two rows of a dimension rather than two fragments: merge the fragment once, and let the data carry both. Different math under one spelling is a rename — call one of them something else.
```

## A column read one way and introduced another

What a template states about a column it reads has to agree with the file that
What a fragment states about a column it reads has to agree with the file that
owns it:

```text
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/language/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ stand in another variable's `bounds`.
## `given_variables`

A given variable is a column this file reads and another file introduces. It is
what lets a template stand on its own: the file loads, and it prints as math,
what lets a fragment stand on its own: the file loads, and it prints as math,
without the file that owns the column.

```yaml
Expand Down
4 changes: 2 additions & 2 deletions src/math_spec/advice.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def _given(program: Program) -> list[Advice]:
"""One note per declaration the program reads and does not build.

A note rather than a refusal, because both readings are a model somebody
meant: a template is composed with the file that introduces the column, and
meant: a fragment is composed with the file that introduces the column, and
a layer is bound to the model it is laid onto. What neither is, is a model
a consumer can build alone, and the consumer is the one that can tell which
it is holding.
Expand All @@ -55,7 +55,7 @@ def _given(program: Program) -> list[Advice]:
'given',
name,
f"{kind} '{name}' is read here and built elsewhere: a consumer binds it to the model this "
f'one is layered onto, and refuses where it cannot. A template is composed instead, and '
f'one is layered onto, and refuses where it cannot. A fragment is composed instead, and '
f'merge() folds it into the file that introduces it.',
)
for kind, group in (('variable', program.given_variables), ('row family', program.given_constraints))
Expand Down
18 changes: 9 additions & 9 deletions src/math_spec/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""Several files into one model, before any of them is validated.

Two verbs, and they answer different questions. :func:`merge` composes
**peers**: templates that each own part of the math, where a name two of them
**peers**: fragments that each own part of the math, where a name two of them
declare is a collision and the order they are given in means nothing.
:func:`override` layers a **base and its patches**: what a framework ships and a
project extends, where a name the patch declares is the point. Neither is a
Expand Down Expand Up @@ -95,14 +95,14 @@ def merge(
) -> dict[str, Any]:
"""*fragments* composed as peers, each owning the math it declares.

A component library is a set of templates that agree on a coupling
surface — one flow per port, one balance per bus — and wiring a system is
rows in a table rather than generated YAML. This is what takes the
templates and hands back one model.
A component library is a set of files that agree on a coupling surface —
one flow per port, one balance per bus — and wiring a system is rows in a
table rather than generated YAML. This is what takes those files and hands
back one model.

Args:
fragments: What each fragment is called, to the fragment. The name is
what an error calls it, so it is the template's name rather than a
what an error calls it, so it is the fragment's name rather than a
path. The order they are given in does not reach the result.
description: What the *composed* model is. A fragment's own
``description`` is about the fragment, so it is neither carried nor
Expand Down Expand Up @@ -142,7 +142,7 @@ def _one_version(read: Mapping[str, dict[str, Any]]) -> int:
"""The language version every fragment is written against.

A fragment saying nothing is version 0 like any file, so a library pinning
one and a template pinning none is the disagreement it looks like rather
one and a fragment pinning none is the disagreement it looks like rather
than a default quietly winning.
"""
declared = {name: sections.get('version', 0) for name, sections in read.items()}
Expand Down Expand Up @@ -198,7 +198,7 @@ def _claimed(read: Mapping[str, dict[str, Any]], section: str) -> dict[str, Any]
raise LanguageError(
f"fragments '{author[key]}' and '{name}' both declare the {_singular(section)} "
f'{key!r}. Two of the same kind of thing are two rows of a dimension rather than '
f'two fragments: merge the template once, and let the data carry both. Different '
f'two fragments: merge the fragment once, and let the data carry both. Different '
f'math under one spelling is a rename — call one of them something else.'
)
merged[key] = block
Expand Down Expand Up @@ -239,7 +239,7 @@ def _author_of(read: Mapping[str, dict[str, Any]], section: str, key: str) -> st
def _summed_objective(read: Mapping[str, dict[str, Any]]) -> dict[str, Any] | None:
"""Every fragment's objective, summed, or ``None`` where none declares one.

Summing is what composing costs: each template prices what it owns, and the
Summing is what composing costs: each fragment prices what it owns, and the
system pays for all of it. The senses must agree, because a sum of two
objectives has one sense and nothing in the files says which — negating the
minority would be this function deciding what a model means.
Expand Down
4 changes: 2 additions & 2 deletions tests/test_given.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

"""What a file reads and does not build: a column, and a row family.

Two readings, and each is a model somebody meant. A **template** reads a column
Two readings, and each is a model somebody meant. A **fragment** reads a column
the file beside it introduces, and `merge` folds the two together, so the
composed model carries neither the declaration nor any trace of it. A **layer**
reads a column, or the dual of a row family, that a model outside the language
Expand All @@ -21,7 +21,7 @@

from math_spec import FORMATS, LanguageError, advice, merge, to_markdown, to_program, to_spec, typeset

#: One component template: it pins the flow at its own port, and the column it
#: One component file: it pins the flow at its own port, and the column it
#: pins belongs to the surface fragment below.
SUPPLY = {
'description': 'A fleet of generators, each on one port.',
Expand Down
4 changes: 2 additions & 2 deletions tests/test_library_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_every_fragment_loads_and_prints_on_its_own(name):


@pytest.mark.parametrize('name', ['generator', 'load'])
def test_a_component_template_reads_the_surface_and_introduces_no_flow(name):
def test_a_component_file_reads_the_surface_and_introduces_no_flow(name):
spec = to_spec(FRAGMENTS[name])
assert sorted(spec.given_variables) == ['Port_p']
assert 'Port_p' not in spec.variables, 'the surface introduces the column, and a component file only writes into it'
Expand All @@ -48,7 +48,7 @@ def test_the_library_composes_into_one_model():
)


def test_the_balance_is_written_once_however_many_templates_are_merged():
def test_the_balance_is_written_once_however_many_fragments_are_merged():
one = to_spec(merge({'surface': FRAGMENTS['surface'], 'load': FRAGMENTS['load']}))
both = to_spec(merge(FRAGMENTS))
assert one.constraints['Bus_nodal_balance'].expression == both.constraints['Bus_nodal_balance'].expression
Expand Down
Loading