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
22 changes: 11 additions & 11 deletions docs/about/limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,17 @@ lets an engine build the model one chunk of rows at a time.
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. A clean pass would only mean that no two annotated operands disagreed. It would also need a grammar of units that the language then has to maintain ([#125](https://github.com/fluxopt/lpspec/issues/125)) | 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, whose dual is the shadow price; weight it in the objective; read it back after the solve ([#124](https://github.com/fluxopt/lpspec/issues/124)) |
| `**` with a variable in the base or the exponent | the exponent would decide the degree, and `to_spec` reads no data. `p ** n` is linear at `n = 1`, quadratic at `n = 2`, and refused at `n = 3` | `x * x` for a square. `**` over parameters and numbers is allowed ([#1175](https://github.com/fluxopt/lpspec/issues/1175)) |
| 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. A clean pass would only mean that no two annotated operands disagreed. It would also need a grammar of units that the language then has to maintain ([#125](https://github.com/fluxopt/lpspec/issues/125)). A range is different: `assumptions:` states one, and the engine checks it against every row | 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, whose dual is the shadow price; weight it in the objective; read it back after the solve ([#124](https://github.com/fluxopt/lpspec/issues/124)) |
| `**` with a variable in the base or the exponent | the exponent would decide the degree, and `to_spec` reads no data. `p ** n` is linear at `n = 1`, quadratic at `n = 2`, and refused at `n = 3` | `x * x` for a square. `**` over parameters and numbers is allowed ([#1175](https://github.com/fluxopt/lpspec/issues/1175)) |
| 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
13 changes: 13 additions & 0 deletions docs/examples/commitment.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ constraints:
p - shift(p, over=snapshot, offset=1, edge=0)
<= ramp_limit * previous_status + start_up_limit * (1 - previous_status)

assumptions:
floor_below_capacity:
description: a floor above the capacity leaves `upper` and `lower` no output to agree on
holds: "p_min <= p_max"

objective:
sense: minimize
expression: sum(p * cost)
Expand Down Expand Up @@ -182,6 +187,14 @@ p_{t,g} - p_{t \boxminus_{0} 1,g} \le \mathrm{ramp\_limit}_{g} \cdot \mathit{pre
```math
\mathit{status}_{t,g} \in \{0, 1\} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G}
```

#### Assumptions

**`floor_below_capacity`**

```math
\mathrm{p}^{\mathrm{min}}_{g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, g \in \mathcal{G}
```
<!-- gallery:end -->

Regenerate with `pixi run python -m tools.gallery`.
68 changes: 67 additions & 1 deletion docs/reference/language/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ SPDX-License-Identifier: CC-BY-4.0

# Parameters, variables, constraints and the objective

These four blocks carry the math. Each takes an optional `description:`.
These four blocks carry the math, and a fifth, `assumptions`, says what the
math takes for granted about its data. Each takes an optional `description:`.

A description is free text with no length limit. The parser throws a `#` comment
away, but keeps a description, so a renderer or a checker can print it. The
Expand Down Expand Up @@ -210,3 +211,68 @@ different models.

A second objective cannot be written, because the schema holds one block. To
pursue several goals, weight them into one expression.

## `assumptions`

An assumption is a claim about the data: a predicate that every coordinate
has to satisfy before the model is built. The language decides nothing about
the numbers, so the tool that binds the data checks each assumption and refuses
the data where one does not hold. The [typeset](../typeset.md) document prints
every assumption under its own heading, so the math a reader checks carries what
the model assumes of its inputs.

```yaml
dimensions:
generator: { dtype: str }
parameters:
p_min: { dims: [generator] }
p_max: { dims: [generator] }
efficiency: { dims: [generator] }
variables:
p: { dims: [generator], bounds: { lower: p_min, upper: p_max } }
assumptions:
efficiency_is_a_fraction: "efficiency > 0 AND efficiency <= 1"
bounds_do_not_cross:
holds: "p_min <= p_max"
where: "p_min"
description: a unit with no minimum is unconstrained below
```

```math
\mathrm{efficiency}_{g} > 0 \wedge \mathrm{efficiency}_{g} \le 1 \qquad \forall\, g \in \mathcal{G}
```

```math
\mathrm{p}^{\mathrm{min}}_{g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{p}^{\mathrm{min}}_{g} \text{ is defined}
```

| Field | | |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
| `holds` | required. A [`where` string](expressions.md#where-strings) over parameters, dimensions and lookups. A bare string is read as `holds` | |
| `where` | which coordinates are checked, in the same grammar | default `null` |
| `description` | free text | default `null` |

Three rules say what an assumption means:

- **It holds at every coordinate of its frame.** The frame is the product of
every dimension that `holds` and `where` name, so `p_min <= p_max` over one
dimension is checked once per generator, and `budget > 0` over none is
checked once. There is no `dims:` to declare, because a predicate widens
nothing.
- **A missing row reads as false**, as it does in every `where`
([absence](absence.md#what-creates-absence)). So `efficiency > 0` refuses a
generator whose row is missing. Where a parameter is supplied only for the
units it applies to, say so in `where:`, as `bounds_do_not_cross` does: the
assumption is checked where `p_min` is defined and nowhere else.
- **Two parameters may be compared.** `p_min <= p_max` reads the two coordinate
by coordinate, the narrower one at every coordinate of the wider. Both are
numbers, or both share a dtype. A number against a label is refused.

A predicate that names a variable is refused, because an assumption is about
the data and a variable is what the solver decides from it. State a rule about
a decision as a constraint. A predicate that folds to `True` or `False` is
refused too: the first assumes nothing, and the second admits no data.

A `piecewise:` block assumes things of its breakpoints that no file writes,
such as a strictly increasing x-axis. Those print under the same heading,
labelled by the block ([what a curve assumes](piecewise.md#what-a-curve-assumes)).
Loading
Loading