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
8 changes: 4 additions & 4 deletions docs/about/limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ costs to add.
as much as a primitive to build, but composes as freely as a macro, because
the rest of the model only sees the variables and constraints it emitted.
- **A declaration section** is a block of declarations of one kind, such as
`variables:` or `given_variables:`. One enters where it says something no
`variables:` or `given:`. 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
typesetter prints it. `given:` entered on all three: nothing else
states that a column belongs to another file, which is what lets a component
file load and print on its own.

Expand Down Expand Up @@ -196,7 +196,7 @@ framework ships and a project extends, a field at a time.
both.

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
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.
Expand All @@ -205,7 +205,7 @@ A layer over a model this language never sees — one built through linopy, say
has nothing to fold into. There the declaration stays, and the program carries
the name and the frame for a consumer to bind, under
[what a program does not build](../reference/language/reading.md#what-a-program-does-not-build).
`given_constraints:` is the same fact about a row family: `dual(balance)` prices
`given: constraints:` is the same fact about a row family: `dual(balance)` prices
what the base model settles, and the file says how many duals there are and what
indexes them.

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/library/composed.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ spec = ms.to_spec(model)

The file below is `spec.to_yaml()` — no fragment holds it, and nothing in the
repository commits it. `Port_p` is one declaration here: each fragment read it
under `given_variables`, and merging folded those into the surface's own.
under `given:`, and merging folded those into the surface's own.

The objective is the generator's, carried as it was written, because it is the
only fragment that priced anything. A second priced fragment would have its
Expand Down
11 changes: 6 additions & 5 deletions docs/examples/library/generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SPDX-License-Identifier: CC-BY-4.0
PyPSA's `Generator`, as one fragment. It owns its dimension, its relation into
`port`, its parameters, its column and its cost, and it reads `Port_p` from
[the surface](surface.md) under
[`given_variables`](../../reference/language/declarations.md#given_variables).
[`given`](../../reference/language/declarations.md#given).
`Generator_port` stands where PyPSA writes `Generator_bus`.

The constraint is what makes the library composable:
Expand All @@ -35,10 +35,11 @@ dimensions:
generator: { dtype: str, description: "generating units, each on one port" }
relations:
Generator_port: { key: generator, value: port }
given_variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
given:
variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
parameters:
Generator_p_nom: { dims: [generator], description: nominal power }
Generator_marginal_cost: { dims: [generator], description: cost of one unit of output }
Expand Down
9 changes: 5 additions & 4 deletions docs/examples/library/load.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ dimensions:
load: { dtype: str, description: "demands, each on one port" }
relations:
Load_port: { key: load, value: port }
given_variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
given:
variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
parameters:
Load_p_set: { dims: [snapshot, load], description: "`Load-p_set` — what a load takes in a snapshot" }
constraints:
Expand Down
9 changes: 5 additions & 4 deletions docs/howto/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ compose: `override(merge({…}), {…})`.
2. **Write each component file against that surface.** It declares its own
entities, its own math, and one relation into `port`. It names `Port_p`
under
[`given_variables`](../reference/language/declarations.md#given_variables),
[`given`](../reference/language/declarations.md#given),
because the surface introduces that column and this file only reads it.

```yaml title="generator.yaml"
Expand All @@ -47,8 +47,9 @@ compose: `override(merge({…}), {…})`.
generator: { dtype: str }
relations:
Generator_port: { key: generator, value: port }
given_variables:
Port_p: { dims: [snapshot, port] }
given:
variables:
Port_p: { dims: [snapshot, port] }
parameters:
Generator_p_nom: { dims: [generator] }
Generator_marginal_cost: { dims: [generator] }
Expand Down Expand Up @@ -78,7 +79,7 @@ compose: `override(merge({…}), {…})`.
```

`merge` folds each given declaration into the one that introduces it, so the
composed model declares `Port_p` once and carries no `given_variables`. It
composed model declares `Port_p` once and carries no `given:`. It
lowers and solves like any model.

4. **Add a component type without touching the balance.** A component file pins
Expand Down
28 changes: 18 additions & 10 deletions docs/reference/language/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@ equation whether `size` is chosen or given. A pinned variable is still a
variable, so `size * on` is `variable * variable`, and a pinned variable cannot
stand in another variable's `bounds`.

## `given_variables`
## `given`

`given:` holds what this file reads and does not build: columns under
`variables:`, row families under `constraints:`. It takes those two keys and
nothing else.

### `given: variables`

A given variable is a column this file reads and another file introduces. It is
what lets a fragment stand on its own: the file loads, and it prints as math,
Expand All @@ -127,10 +133,11 @@ dimensions:
generator: { dtype: str }
relations:
gen_port: { key: generator, value: port }
given_variables:
flow:
dims: [snapshot, port]
description: what a port puts into its bus
given:
variables:
flow:
dims: [snapshot, port]
description: what a port puts into its bus
variables:
gen_p: { dims: [snapshot, generator], bounds: { lower: 0 } }
constraints:
Expand Down Expand Up @@ -162,16 +169,17 @@ built in Python — the declaration stays, and the program carries it for a
consumer to bind. See
[what a program does not build](reading.md#what-a-program-does-not-build).

## `given_constraints`
### `given: constraints`

A given constraint is a row family this file reads the dual of and another
model builds. It is what lets a layer price something the base model settles.

```yaml
given_constraints:
balance:
dims: [snapshot, bus]
description: the host model clears each bus
given:
constraints:
balance:
dims: [snapshot, bus]
description: the host model clears each bus
expressions:
price:
expression: dual(balance)
Expand Down
32 changes: 16 additions & 16 deletions docs/reference/language/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ SPDX-License-Identifier: CC-BY-4.0

# File shape

A model file is a YAML mapping with **twelve declaration keys**, plus
`version` and `description`. Any subset of the twelve is accepted.

| Key | |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| `dimensions` | the axes ([dimensions](dimensions.md)) |
| `relations` | named relations between dimensions ([relations](dimensions.md#relations)) |
| `parameters` | the data the model expects ([declarations](declarations.md)) |
| `variables` | what the solver decides |
| `given_variables` | columns this file reads and another introduces ([given variables](declarations.md#given_variables)) |
| `constraints` | the rules those decisions obey |
| `objective` | what is minimised or maximised |
| `expressions` | named quantities, reusable in the math and readable after a solve ([expressions](expressions.md#named-expressions)) |
| `macros` | templates that take arguments ([macros](expressions.md#macros)) |
| `piecewise` | piecewise-linear curves ([piecewise](piecewise.md)) |
| `sos` | special-ordered sets ([sos](piecewise.md#sos)) |
A model file is a YAML mapping with **eleven declaration keys**, plus
`version` and `description`. Any subset of the eleven is accepted.

| Key | |
| ------------- | ------------------------------------------------------------------------------------------------------------------- |
| `dimensions` | the axes ([dimensions](dimensions.md)) |
| `relations` | named relations between dimensions ([relations](dimensions.md#relations)) |
| `parameters` | the data the model expects ([declarations](declarations.md)) |
| `variables` | what the solver decides |
| `given` | what this file reads and another file builds ([given](declarations.md#given)) |
| `constraints` | the rules those decisions obey |
| `objective` | what is minimised or maximised |
| `expressions` | named quantities, reusable in the math and readable after a solve ([expressions](expressions.md#named-expressions)) |
| `macros` | templates that take arguments ([macros](expressions.md#macros)) |
| `piecewise` | piecewise-linear curves ([piecewise](piecewise.md)) |
| `sos` | special-ordered sets ([sos](piecewise.md#sos)) |

A file with no `objective` is a **feasibility problem**: it asks whether the
constraints can all be met. It loads and solves like any other model, and the
Expand Down
12 changes: 7 additions & 5 deletions docs/reference/language/reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ classes live in `math_spec.program`.

## What a program does not build

`program.given_variables` and `program.given_constraints` name what the model
`program.given.variables` and `program.given.constraints` name what the model
reads and does not build. Every other group is a build instruction — a column
for each entry of `variables`, a row family for each entry of `constraints`.
These two are the opposite: a name to look up in the model this one is layered
Expand All @@ -128,17 +128,19 @@ onto.
layer = to_program(
{
'dimensions': {'snapshot': {'dtype': 'int'}, 'bus': {'dtype': 'str'}},
'given_variables': {'p': {'dims': ['snapshot', 'bus']}},
'given_constraints': {'balance': {'dims': ['snapshot', 'bus']}},
'given': {
'variables': {'p': {'dims': ['snapshot', 'bus']}},
'constraints': {'balance': {'dims': ['snapshot', 'bus']}},
},
'parameters': {'rate': {'dims': ['bus']}},
'constraints': {'cap': {'dims': [], 'expression': 'sum(p * rate) <= 100'}},
'expressions': {'price': {'expression': 'dual(balance)'}},
}
)

sorted(layer.variables) # []
sorted(layer.given_variables) # ['p']
layer.given_constraints['balance'].dims # ('snapshot', 'bus')
sorted(layer.given.variables) # ['p']
layer.given.constraints['balance'].dims # ('snapshot', 'bus')
```

A consumer that builds a program does three things with them:
Expand Down
9 changes: 5 additions & 4 deletions examples/library/generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ dimensions:
generator: { dtype: str, description: "generating units, each on one port" }
relations:
Generator_port: { key: generator, value: port }
given_variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
given:
variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
parameters:
Generator_p_nom: { dims: [generator], description: nominal power }
Generator_marginal_cost: { dims: [generator], description: cost of one unit of output }
Expand Down
9 changes: 5 additions & 4 deletions examples/library/load.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ dimensions:
load: { dtype: str, description: "demands, each on one port" }
relations:
Load_port: { key: load, value: port }
given_variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
given:
variables:
Port_p:
dims: [snapshot, port]
description: the surface introduces this column, and this file only writes into it
parameters:
Load_p_set: { dims: [snapshot, load], description: "`Load-p_set` — what a load takes in a snapshot" }
constraints:
Expand Down
45 changes: 30 additions & 15 deletions schema/math-spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,30 @@
"title": "ExpressionCase",
"type": "object"
},
"GivenBlock": {
"additionalProperties": false,
"description": "What this file reads and does not build, by kind.\n\nOne key per kind of declaration, and the section is closed at the two:\na third kind enters the day something reads one, and the schema's own\nerror names what is valid until then.",
"properties": {
"constraints": {
"additionalProperties": {
"$ref": "#/$defs/GivenConstraintBlock"
},
"default": {},
"title": "Constraints",
"type": "object"
},
"variables": {
"additionalProperties": {
"$ref": "#/$defs/GivenVariableBlock"
},
"default": {},
"title": "Variables",
"type": "object"
}
},
"title": "GivenBlock",
"type": "object"
},
"GivenConstraintBlock": {
"additionalProperties": false,
"description": "A row family this file reads the dual of and does not build.\n\nThe frame says how many duals there are and what indexes them, which is\nwhat ``dual()`` needs and all this file can answer. There is no\n``expression:``: the body is the owner's, and nothing here builds a row.",
Expand Down Expand Up @@ -744,21 +768,12 @@
"title": "Expressions",
"type": "object"
},
"given_constraints": {
"additionalProperties": {
"$ref": "#/$defs/GivenConstraintBlock"
},
"default": {},
"title": "Given Constraints",
"type": "object"
},
"given_variables": {
"additionalProperties": {
"$ref": "#/$defs/GivenVariableBlock"
},
"default": {},
"title": "Given Variables",
"type": "object"
"given": {
"$ref": "#/$defs/GivenBlock",
"default": {
"constraints": {},
"variables": {}
}
},
"macros": {
"additionalProperties": {
Expand Down
6 changes: 3 additions & 3 deletions src/math_spec/advice.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def _given(program: Program) -> list[Advice]:
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))
for kind, group in (('variable', program.given.variables), ('row family', program.given.constraints))
for name in group
]

Expand All @@ -73,8 +73,8 @@ def _never_an_axis(program: Program) -> list[Advice]:
reached: set[str] = set()
for declaration in (*program.parameters.values(), *program.variables.values(), *program.constraints.values()):
reached.update(declaration.dims)
reached.update(dim for given in program.given_variables.values() for dim in given.dims)
reached.update(dim for given in program.given_constraints.values() for dim in given.dims)
for group in (program.given.variables, program.given.constraints):
reached.update(dim for declaration in group.values() for dim in declaration.dims)
reached |= _produced_axes(program)
reached |= {dim for lk in program.relations.values() for dim in lk.dims}

Expand Down
Loading