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
23 changes: 12 additions & 11 deletions docs/howto/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +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 fragment's wording is carried |
| 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 |
| 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 |
| `objective` | the terms are summed in fragment-name order, each in parentheses, and the senses agree |
| `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 model'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 fragment's wording is carried |
| 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 |
| 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 name a `given:` entry marks `additive` | every fragment's declaration of it is a term: the terms are summed in fragment-name order, each in parentheses, and the marked entry is kept |
| `objective` | the terms are summed in fragment-name order, each in parentheses, and the senses agree. The first description 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 model's as `description=` |

## A name two fragments declare

Expand Down
70 changes: 64 additions & 6 deletions docs/reference/language/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,17 @@ constraints:
expression: injection == 0
```

| Field | | |
| ------------- | ------------------------------------------------- | -------------- |
| `dims` | required. The dimensions the expression runs over | |
| `description` | free text | default `null` |
| Field | | |
| ------------- | -------------------------------------------------------------------------------------------- | --------------- |
| `dims` | required. The dimensions the expression runs over | |
| `additive` | `true` where the name is a sum other files add terms to ([a sum](#a-sum-other-files-add-to)) | default `false` |
| `description` | free text | default `null` |

There is no body. This file reads the name as it reads a given variable: a
quantity over the frame, of degree one. A `where` does not read it, because a
mask is built before any variable exists. A name declared under both
`expressions:` and `given: expressions:` is refused. The typeset legend lists a
given expression under _Given_.
`expressions:` and `given: expressions:` is refused, unless the entry is
marked `additive`. The typeset legend lists a given expression under _Given_.

[`merge`](../../howto/compose.md#a-library-of-components) folds a given
expression into the definition of another fragment. The `dims` are an upper
Expand All @@ -239,6 +240,63 @@ term carries it. The composed model holds the body to the rules of every place
this file reads it: a square of a given expression that is quadratic is
refused once folded.

#### A sum other files add to

`additive: true` says the name is a sum other files add terms to. One file
says it, on its `given:` entry, with the frame. Each file that adds a term
declares it as an ordinary named expression under that name.

```yaml
# balance.yaml reads the sum
dimensions:
snapshot: { dtype: int }
bus: { dtype: str }
given:
expressions:
injection:
dims: [snapshot, bus]
additive: true
description: what the components put into a bus
variables:
slack: { dims: [snapshot, bus] }
constraints:
balance:
dims: [snapshot, bus]
expression: injection + slack == 0
```

```yaml
# fleet.yaml adds a term
dimensions:
snapshot: { dtype: int }
bus: { dtype: str }
generator: { dtype: str }
relations:
gen_bus: { key: generator, values: bus }
variables:
gen_p: { dims: [snapshot, generator], bounds: { lower: 0 } }
expressions:
injection: sum(gen_p, by=gen_bus, over=generator, into=bus)
```

Each file loads alone: the reader over a sum it does not build, and the
contributor over its own term. Other readers state the frame and nothing more.

A file may carry the marked entry and declare a term too. Then it reads the
sum so far, which alone is its own term. The term is one `expression:`, and
carries no dimension the entry does not state; both are checked at load. The
entry then folds into the definition, and the typeset legend lists it under
_Definitions_, as a sum other files add terms to.

[`merge`](../../howto/compose.md#a-library-of-components) sums every term of a
marked name, each in parentheses, in fragment-name order, and keeps the marked
entry, so a later merge adds more. The entry's description is the sum's. It
refuses a term written as `cases:`, a term over a dimension the entry does not
state, and a file that declares a term and reads the name without carrying the
marked entry: on its own that file reads its term, and composed it would read
the sum. Two terms of a name no entry marks are refused as a collision. A
marked name no file adds to stays under `given:`.

## `constraints`

One block is one rule. The name of the block is the name of the constraint.
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ reads and does not build ([given](language/declarations.md#given)). Every
other group is a build instruction. These four are names to look up in the
model this one is layered onto. An expression reads a given expression as a
`Variable` of that name, over the frame under `program.given.expressions`.
A given expression with `additive` set is a sum other files add terms to.
Where the file also declares a term of it, the name is a definition with
`additive` set under `program.expressions`, and not a given name.

```python
layer = to_spec(
Expand Down
7 changes: 6 additions & 1 deletion schema/math-spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,13 @@
},
"GivenExpressionBlock": {
"additionalProperties": false,
"description": "A named expression this file reads and another file defines.\n\nThe frame is all this file states. This file reads the name as a quantity\nover that frame, affine in the columns, as it reads a given variable: the\nbody is the definer's, and the composed model holds the body to the rules\nof every place this file reads it.",
"description": "A named expression this file reads and another file defines.\n\nThe frame is all this file states. This file reads the name as a quantity\nover that frame, affine in the columns, as it reads a given variable: the\nbody is the definer's, and the composed model holds the body to the rules\nof every place this file reads it.\n\n``additive: true`` says the name is a sum other files add terms to. Each\nof them declares its term as an ordinary named expression under the name,\nand :func:`~math_spec.composition.merge` sums the terms. A file that marks\nthe name may declare a term of its own too, and then reads the sum so far.",
"properties": {
"additive": {
"default": false,
"title": "Additive",
"type": "boolean"
},
"description": {
"anyOf": [
{
Expand Down
Loading