diff --git a/CHANGELOG.md b/CHANGELOG.md index 89fb07c8..2fbe0dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp ## Upcoming version +- fix(language): dual(c) is the rate at which the optimal objective rises with the right side of c, so an equality has a sign too ([#751](https://github.com/energy-models/mathspec/pull/751)) - fix(language): a macro formal written inside a list takes the name the call binds to it ([#779](https://github.com/energy-models/mathspec/pull/779)) - feat(language): a sum names several dimensions in one over= list ([#778](https://github.com/energy-models/mathspec/pull/778)) - feat(language): a divisor or a power over parameters may add, so a discount factor is written where it is used ([#757](https://github.com/energy-models/mathspec/pull/757)) diff --git a/docs/reference/language/named.md b/docs/reference/language/named.md index 41f08da8..aae1743d 100644 --- a/docs/reference/language/named.md +++ b/docs/reference/language/named.md @@ -176,9 +176,18 @@ Constraint 'd': a dual exists only after a solve; the math cannot read one — keep the entry that carries it out of constraints, the objective, bounds and where. ``` -`dual(c)` is the rate at which the optimal objective improves as `c` is relaxed -in the direction its comparator points, under the spec's own `minimize` or -`maximize`. +`dual(c)` is the rate at which the optimal objective rises as the right side +of `c` rises. Read `lhs <= rhs` as `lhs <= rhs + d`: the dual is the rate in `d` +at `d = 0`. The rule is the same for `<=`, `>=` and `==`, and under `minimize` +and `maximize`, so an equality has a dual with a sign too. Which side a term is +written on decides the sign: `p <= cap` and `-p >= -cap` state one row, and their +duals are opposite. + +| Under `minimize`, a binding row | Its dual | +| ------------------------------- | ------------------------------------ | +| `p <= cap` | at most 0 | +| `p >= load` | at least 0 | +| `sum(p, over=g) == load` | the price of one more unit of `load` | A row that `c`'s `where:` deletes has no dual. diff --git a/src/mathspec/program.py b/src/mathspec/program.py index d1430e1e..ad882ec9 100644 --- a/src/mathspec/program.py +++ b/src/mathspec/program.py @@ -189,12 +189,14 @@ class Variable: @dataclass(frozen=True) class Dual: - """A constraint's dual — its shadow price, read after the solve. - - Stands only under an [`ExpressionDeclaration`][] the math never reads: - the loader refuses ``dual()`` anywhere a solver ingests. One value per - coordinate of the named constraint's own ``dims`` frame: the leaf reshapes - nothing, like a parameter. + """A constraint's dual — the rate the optimal objective rises with the constraint's right side, read after the solve. + + Of ``lhs <= rhs`` it is the rate in ``d`` of ``lhs <= rhs + d``, for every + comparator and under either sense. Stands only under an + [`ExpressionDeclaration`][] the math never reads: the loader refuses + ``dual()`` anywhere a solver ingests. One value per coordinate of the named + constraint's own ``dims`` frame: the leaf reshapes nothing, like a + parameter. """ constraint: str