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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp

## Upcoming version

- feat(language): an expression that only other files' terms fill is declared with `expression: null`, and prints as dots ([#758](https://github.com/energy-models/mathspec/pull/758))
- feat(language): a named expression may declare the frame it is read over ([#741](https://github.com/energy-models/mathspec/pull/741))
- docs: code examples on the site are readable in light and dark mode, and a diagram shows what mathspec leaves to engines and other tools ([#730](https://github.com/energy-models/mathspec/pull/730))
- docs: the site follows the reader's light or dark setting, and a page shows where it sits in the navigation ([#727](https://github.com/energy-models/mathspec/pull/727))
Expand Down
24 changes: 12 additions & 12 deletions docs/howto/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,18 @@ compose as `override(merge({…}), {…})`.

## What a fragment may share

| The entry | What happens |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| a dimension or a relation | every fragment may declare it, and the ones that do say the same thing about it |
| a `description` on a shared dimension or relation | it is prose rather than a claim, and the first wording in fragment-name order is carried, whatever order the fragments are passed in |
| any other declaration | one fragment declares it, and a second is refused |
| an entry under `given:` | it is checked against the fragment that introduces the name, then folded into it. Its description fills the declaration where the introducer wrote none |
| a given expression | the definition's body carries no dimension the reader's `dims` do not name |
| a given entry no fragment introduces | it stays under `given:` until a host model provides it |
| a given expression with a `term` | the name is defined as the definition one fragment writes, if any, plus every term by its name, in fragment-name order. Each term stays a named expression. A term that lands on a name no fragment defines, reads or uses is refused |
| `objective` | the terms are summed in fragment-name order, each in parentheses, and the senses agree. The first description in fragment-name order is carried |
| `version` | every fragment is written against the same one |
| `description` at the top of a fragment | it is about the fragment and is not carried. Pass the composed spec's as `description=` |
| The entry | What happens |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| a dimension or a relation | every fragment may declare it, and the ones that do say the same thing about it |
| a `description` on a shared dimension or relation | it is prose rather than a claim, and the first wording in fragment-name order is carried, whatever order the fragments are passed in |
| any other declaration | one fragment declares it, and a second is refused |
| an entry under `given:` | it is checked against the fragment that introduces the name, then folded into it. Its description fills the declaration where the introducer wrote none |
| a given expression | the definition's body carries no dimension the reader's `dims` do not name |
| a given entry no fragment introduces | it stays under `given:` until a host model provides it |
| a given expression with a `term` | the name is defined as the definition one fragment writes plus every term by its name, in fragment-name order. An empty definition, `expression: null`, adds no body. Each term stays a named expression. A term that lands on a name no fragment defines is refused |
| `objective` | the terms are summed in fragment-name order, each in parentheses, and the senses agree. The first description in fragment-name order is carried |
| `version` | every fragment is written against the same one |
| `description` at the top of a fragment | it is about the fragment and is not carried. Pass the composed spec's as `description=` |

## A name two fragments declare

Expand Down
47 changes: 25 additions & 22 deletions docs/reference/language/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,15 @@ given:
```

```yaml
# balance.yaml reads the sum
# balance.yaml defines the sum as empty, and reads it
dimensions:
snapshot: { dtype: int }
bus: { dtype: str }
given:
expressions:
injection:
dims: [snapshot, bus]
description: what the components put into a bus
expressions:
injection:
dims: [snapshot, bus]
expression: null
description: what the components put into a bus
constraints:
balance:
dims: [snapshot, bus]
Expand All @@ -293,22 +293,25 @@ The typeset legend lists the entry under _Given_ and names the term, and the
math prints the term under _Definitions_ as its own line.

[`merge`](../../howto/compose.md#a-library-of-components) defines the name as
the definition one fragment writes under `expressions:`, if any, plus every
term by its name, in fragment-name order, and keeps each term as a named
expression of the composed spec. Nothing declares that the name is a sum: a
term adds to whatever the other files define, as a fragment's objective adds
to the objective, and a later merge adds to the composed definition the same
way. The file that defines the name does not opt in. It reads the name as its
own definition alone, and as the definition plus every term once composed;
whoever composes the files answers for that sum. A term has to land on a name another file
has: one that defines it, reads it with no term of its own, or uses it in its
math. Terms alone are refused, with the near miss named, since `merge` fills
a reading or extends a definition and never invents a name. A definition
written as `cases:` is refused, since it is summed as written: name the cased
body as its own expression, and define the name as that name. A cased term is
added like any other, by its name. The definition keeps its own description,
or takes the first a reader wrote. Two files that both define the name under
`expressions:` are refused as a collision, and the message names `term:`.
the definition one fragment writes under `expressions:` plus every term by its
name, in fragment-name order, and keeps each term as a named expression of the
composed spec. Nothing declares that the name is a sum: a term adds to
whatever the other files define, as a fragment's objective adds to the
objective, and a later merge adds to the composed definition the same way. The
file that defines the name does not opt in. It reads the name as its own
definition alone, and as the definition plus every term once composed;
whoever composes the files answers for that sum. A definition that is only
the terms is written as
[an empty expression](named.md#an-empty-expression), `expression: null`, and
the terms alone fill it. A term has to land on a definition. A name that
another file only reads under `given:`, or only uses in its math, is refused,
with the near miss named, since `merge` extends a definition and never invents
a name. A definition written as `cases:` is refused, since it is summed as
written: name the cased body as its own expression, and define the name as
that name. A cased term is added like any other, by its name. The definition
keeps its own frame and description, or takes the first description a reader
wrote. Two files that both define the name under `expressions:` are refused as
a collision, and the message names `term:`.

## `constraints`

Expand Down
34 changes: 34 additions & 0 deletions docs/reference/language/named.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Each case prints as one row of the definition, and `otherwise:` as the last:
$$\mathit{previous\_status}_{t,g} = \begin{cases} 1 & \text{if } \neg \mathrm{committable}_{g} \cr \mathrm{status}^{\mathrm{initial}}_{g} & \text{if } \mathrm{committable}_{g} \wedge \mathrm{pos}(t) = 0 \cr \mathit{status}_{t - 1,g} & \text{otherwise} \end{cases} \qquad \forall\thinspace t \in \mathcal{T},\enspace g \in \mathcal{G}$$

A named expression carries **exactly one** of `expression:` and `cases:`.
`expression: null` counts as an `expression:`: it is
[an empty expression](#an-empty-expression).

| Key | |
| ----------- | ---------------------------------------------------------------------------- |
Expand Down Expand Up @@ -121,6 +123,38 @@ masked variable.

`cases:` is not accepted inside a `macros:` template.

## An empty expression

`expression: null` defines a quantity with no body of its own. Other files add
[terms](declarations.md#a-term-a-file-adds) to it, and
[`merge`](../../howto/compose.md#a-library-of-components) sums them.

```yaml
dimensions:
snapshot: { dtype: int }
bus: { dtype: str }
expressions:
injection:
dims: [snapshot, bus]
expression: null
description: what the components put into a bus
constraints:
balance:
dims: [snapshot, bus]
expression: injection == 0
```

`dims:` is required, because no body gives the frame. `cases:` and
`otherwise:` are refused beside it. On its own, the file reads the name as it
reads a [given expression](declarations.md#given-expressions): a quantity over
the frame, of degree one, that a `where` does not read. The program holds it
under `given.expressions`, marked `empty`. The definition prints as dots:

$$\mathit{injection}_{t,b} = \dots \qquad \forall\thinspace t \in \mathcal{T},\enspace b \in \mathcal{B}$$

The legend lists it under _Definitions_. It draws no advice. A merge that adds
no term to it keeps it empty.

## Reported expressions

A named expression is either **in the math** or **reported**. The objective
Expand Down
16 changes: 16 additions & 0 deletions docs/reference/notation.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ parameters:
| $`\mathit{lcoe}`$ | `lcoe` (scalar) |
| $`\mathit{marginal\_price}`$ | `marginal_price` over $`\mathcal{T} \times \mathcal{B}`$ |
| $`\mathrm{startup\_cost}`$ | `startup_cost` over $`\mathcal{T} \times \mathcal{G}`$ — what starting a unit in this snapshot costs, which the horizon's edge changes |
| $`\mathit{imports}`$ | `imports` over $`\mathcal{T} \times \mathcal{B}`$, empty here: the terms other files add fill it — what neighbouring areas put into a bus |

Upright is what the data supplies — a parameter such as $`\mathrm{p}^{\mathrm{max}}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`p`$. An index is italic too, being what a quantifier chooses, and a set is script.

Expand Down Expand Up @@ -732,6 +733,21 @@ expressions:
\mathit{marginal\_price}_{t,b} = \lambda_{\mathrm{balance},t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B}
```

#### Empty expression

an empty expression: the terms other files add fill it, so its body prints as dots

```yaml
expressions:
imports:
dims: [snapshot, bus]
expression: null
```

```math
\mathit{imports}_{t,b} = \dots \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B}
```

### Shifts

#### Cyclic and acyclic shift
Expand Down
4 changes: 3 additions & 1 deletion docs/reference/reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ model this one is layered onto. An expression reads a given expression as a
A given expression with a `term` is one this file adds to:
`program.given.expressions[name].term` is the term: the `Named` node of the
entry of `program.expressions` it names. The name is still one the program
reads and does not build.
reads and does not build. An [empty expression](language/named.md#an-empty-expression)
is under `program.given.expressions` too, with `empty` set: the file defines
the name, and the terms other files add are its whole body.

```python
layer = to_spec(
Expand Down
Loading