Skip to content
Merged
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
28 changes: 17 additions & 11 deletions docs/about/limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,28 @@ second kind. `min_up_time` is a column the model already binds, so
`sum_back(window=min_up_time)` reads the width off the column and you ship no
window mask.

Checking a column is neither. `p_min <= p_max` is a rule two consumers must not
answer differently, so the rule is
[language](../reference/language/assumptions.md) and the check is the
consumer's. The file states the predicate, and whoever binds the numbers runs
it.

## Deliberate non-primitives

What has been asked for and refused, with the reason and what to write instead.
That another tool has a feature is not by itself a reason to add it.

| Request | Why refused | Instead |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| Resampling, clustering, file IO, unit conversion | not math | do it in data preparation, and pass a parameter |
| Unit checking at load | a `unit: MW` on a parameter is a claim that nothing checks against the column, and it needs a grammar of units the language then maintains | convert to one unit system in data preparation, and name it in the `description:` |
| Array operations such as `merge` and `reindex` | there is no end to them | data preparation |
| Helpers for one domain, such as `reduce_carrier_dim` | writes one field's vocabulary into the language | a component library of macros over the operators that exist |
| A vocabulary for tracked metrics: `impacts:`, `effects:`, a `costs` axis | a named expression already does this | an `impact` dimension and one named expression. Cap it with a constraint, weight it in the objective, read it back after the solve |
| `**` with a variable in the base or the exponent | the exponent would decide the degree, and `to_spec` reads no data | `x * x` for a square. `**` over parameters and numbers is allowed |
| Normalisation, `x / sum(x)` | dividing by a variable is not a polynomial, and no solver takes it | write the ratio as a constraint, or fix the denominator |
| An `if`, a loop, or declarations that depend on the data | `to_spec` could no longer read the file without the data | `where:` masks and `dims:` dimensions. A tool may loop over models |
| A Python API for building models | the model is the file you review and diff | YAML, or a `dict` with the same keys ([below](#composition-component-libraries)) |
| Request | Why refused | Instead |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Resampling, clustering, file IO, unit conversion | not math | do it in data preparation, and pass a parameter |
| Unit checking at load | a `unit: MW` on a parameter is a claim that nothing checks against the column, and it needs a grammar of units the language then maintains | convert to one unit system in data preparation, and name it in the `description:`. A range the data has to meet is an [`assumptions:`](../reference/language/assumptions.md) entry |
| Array operations such as `merge` and `reindex` | there is no end to them | data preparation |
| Helpers for one domain, such as `reduce_carrier_dim` | writes one field's vocabulary into the language | a component library of macros over the operators that exist |
| A vocabulary for tracked metrics: `impacts:`, `effects:`, a `costs` axis | a named expression already does this | an `impact` dimension and one named expression. Cap it with a constraint, weight it in the objective, read it back after the solve |
| `**` with a variable in the base or the exponent | the exponent would decide the degree, and `to_spec` reads no data | `x * x` for a square. `**` over parameters and numbers is allowed |
| Normalisation, `x / sum(x)` | dividing by a variable is not a polynomial, and no solver takes it | write the ratio as a constraint, or fix the denominator |
| An `if`, a loop, or declarations that depend on the data | `to_spec` could no longer read the file without the data | `where:` masks and `dims:` dimensions. A tool may loop over models |
| A Python API for building models | the model is the file you review and diff | YAML, or a `dict` with the same keys ([below](#composition-component-libraries)) |

## Composition (component libraries)

Expand Down
18 changes: 18 additions & 0 deletions docs/examples/commitment.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ constraints:
dispatch - shift(dispatch, along=snapshot, offset=1, edge=0)
<= ramp_limit * previous_status + start_up_limit * (1 - previous_status)

assumptions:
output_floor_fits_under_the_cap:
holds: "min_output <= capacity"
where: "committable"
description: >-
`lower` and `upper` hold one dispatch between them, so a floor above the
cap makes a running unit infeasible rather than expensive. A unit that
cannot be switched off is held to its floor in every snapshot, so the
check is the committable ones'.

objective:
sense: minimize
expression: sum(dispatch * cost)
Expand Down Expand Up @@ -178,6 +188,14 @@ $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own ord
```math
\mathit{status}_{t,g} \in \{0, 1\} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G}
```

#### Assumptions

**`output_floor_fits_under_the_cap`**

```math
\mathrm{min\_output}_{g} \le \mathrm{capacity}_{g} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{committable}_{g}
```
<!-- gallery:end -->

Regenerate with `pixi run python -m tools.gallery`.
131 changes: 131 additions & 0 deletions docs/reference/language/assumptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<!--
SPDX-FileCopyrightText: math-spec contributors
SPDX-License-Identifier: CC-BY-4.0
-->

# Assumptions

`assumptions:` states what the model expects of the data it is bound to. The
language reads no data, so it checks nothing here. It types the predicate,
carries it on the program, and prints it in the
[typeset document](../typeset.md). The consumer that binds the numbers runs
each one, and refuses the data that fails it.

```yaml
dimensions:
generator: { dtype: str }
parameters:
p_min: { dims: [generator] }
p_max: { dims: [generator] }
variables:
p:
dims: [generator]
bounds: { lower: p_min, upper: p_max }
constraints:
cap:
dims: [generator]
expression: p <= p_max
objective:
sense: minimize
expression: sum(p, over=generator)
assumptions:
bounds_do_not_cross: "p_min <= p_max"
```

$$\mathrm{p}^{\mathrm{min}}_{g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\thinspace g \in \mathcal{G}$$

## The entry

An entry is one where string, or a mapping once it carries more than the
predicate.

| Field | | |
| ------------- | ----------------------------------------------------------------------------- | -------------- |
| `holds` | required. The predicate, in the [where grammar](expressions.md#where-strings) | |
| `where` | which coordinates it is checked at, in the same grammar | default `null` |
| `description` | why the rule is there. A refusal quotes it | default `null` |

`bounds_do_not_cross: "p_min <= p_max"` above is the short form of
`bounds_do_not_cross: { holds: "p_min <= p_max" }`.

A `description:` says why the rule is there. The sentence a consumer refuses
with quotes it, so a failure names the columns and the reason.

There is no `dims:`. The predicate holds at every coordinate of the product of
the dimensions its two masks name. A predicate narrower than that broadcasts,
as it does in any `where`.

## What a predicate may say

Everything the [where grammar](expressions.md#where-strings) admits, which
includes arithmetic on either side:

```yaml
dimensions:
snapshot: { dtype: int }
generator: { dtype: str }
parameters:
eta: { dims: [generator] }
p_max: { dims: [generator] }
peak: { dims: [] }
load: { dims: [snapshot] }
ramp_limit: { dims: [] }
variables:
p:
dims: [snapshot, generator]
bounds: { lower: 0, upper: p_max }
constraints:
meet_load:
dims: [snapshot]
expression: sum(p, over=generator) == load
objective:
sense: minimize
expression: sum(p)
assumptions:
efficiency_is_a_fraction: "eta > 0 AND eta <= 1"
peak_is_reachable: "sum(p_max, over=generator) >= peak"
ramps_are_gentle:
holds: "load - shift(load, along=snapshot, offset=1, edge=0) <= ramp_limit"
where: "position(snapshot) > 0"
description: the first snapshot has no predecessor to ramp from
```

A `where:` narrows which coordinates are checked. A parameter supplied only
where it applies takes one, so the rows it has no value at are not held to the
predicate.

## What the loader refuses

**A predicate the connectives already decide.** It reads no data, so it is
either a claim about nothing or a claim no data can meet:

> `Assumption 'sound'`: the predicate `'c > 0 OR true'` folds to true, so it
> assumes nothing of the data. Delete it, or name a parameter it constrains.

A `where:` the connectives decide is refused the same way: one that folds to
true narrows nothing, and one that folds to false checks the entry on no row.

**A variable.** An assumption is about the numbers the caller binds, and a
variable is what the solver decides from them:

> `Assumption 'sound'`: variable `'p'` stands in what the assumption assumes,
> and an assumption is about the data — a variable is what the solver decides
> from it. Name a parameter, or state the rule as a constraint.

A rule that binds a decision is a [constraint](declarations.md#constraints).
A constraint whose sides carry no variable is refused, and its message names
this section.

## What a curve assumes

A [`piecewise:`](piecewise.md) block puts its own conditions on the numbers.
Its breakpoints increase along the curve, and the shape is the one its
`method:` is exact for. The language derives both from the method, not from
anything the file writes, and carries them beside the written ones under the
name a refusal quotes. A `method: convex` block called `curve` adds
`curve increasing` and `curve curvature`.

Both kinds print under one _Assumptions_ heading, because a reader checking
the data against the document checks all of them.
[Reading a loaded model](../reading.md#what-the-data-has-to-satisfy) says how
a consumer runs them.
3 changes: 2 additions & 1 deletion docs/reference/language/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ Position decides which kinds of name are legal:
A bare word in the value of a keyword argument is a name to resolve, which is
why `wrap` is quoted. A keyword's key is never a name.

Constraints sit outside the flat namespace, so a model may name a constraint
Constraints and assumptions sit outside the flat namespace, because no
expression names either, so a model may name a constraint or an assumption
after a variable. The objective has no name at all.

## How dimensions combine
Expand Down
5 changes: 3 additions & 2 deletions docs/reference/language/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ SPDX-License-Identifier: CC-BY-4.0

# File shape

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

| Key | |
| ------------- | ------------------------------------------------------------------------------------------------- |
Expand All @@ -20,6 +20,7 @@ and `description`. Any subset of the ten is accepted.
| `macros` | templates that take arguments ([macros](named.md#macros)) |
| `piecewise` | piecewise-linear curves ([piecewise](piecewise.md)) |
| `sos` | special-ordered sets ([sos](piecewise.md#sos)) |
| `assumptions` | what the model expects of its data ([assumptions](assumptions.md)) |

A file with no `objective` is a **feasibility problem**: it asks whether the
constraints can all be met.
Expand Down
Loading
Loading