Piecewise curves and SOS#
piecewise: states a curve through breakpoints. sos: states a family of
variables of which only one, or only two neighbours, may be non-zero. Both are
formulations: blocks that expand into ordinary variables and constraints
before the model is built. spec.expand() writes
that expansion out.
piecewise#
A piecewise block ties two or more expressions to one piecewise-linear curve,
which is given as breakpoints: the corner values each expression takes
together.
piecewise:
chp:
over: bp # breakpoint dimension
links:
- [power, power_bp] # [expression, values-parameter]
- [fuel, fuel_bp]
- [heat, heat_bp]
method: adjacency # how the weights are restricted — below
activity: null # optional: a binary variable that the weights sum to
# a two-link block may bound one side instead of pinning it
fuel_cap:
over: bp
links:
- [power, power_bp]
- [fuel, fuel_bp, "<="]
| Part of a link | |
|---|---|
| expression | Any affine expression. The simplest is a bare variable name |
| values | A parameter that carries the over dimension, plus any dimensions the link expressions carry. A dimension the links do not carry is refused |
| sign | <= or >=. At most one per block, and only in a block with exactly two links. It bounds the link instead of pinning it |
| Key | ||
|---|---|---|
over |
required. The breakpoint dimension | |
links |
required. Two or more links | |
method |
adjacency, sos2, convex or lp: how the weights are restricted (below) |
default adjacency |
activity |
a binary variable that gates the curve (below) | default null |
points |
how far each curve runs, where the curves are not all the same length (below) | default null |
A block states one weight per breakpoint in [0, 1], a row making the weights
sum to 1, and a row per link tying its expression to the weighted breakpoints.
The breakpoint order is the order of over, which is an
ordered dimension. What a block assumes of
its numbers is on what a curve assumes.
activity#
activity: names a binary variable, and the weights then sum to that variable
instead of to 1, so 0 pins the curve off.
The gate is an ordinary variable declaration:
variables:
running:
dims: [snapshot, generator]
domain: binary
where: committable # only some units have a commitment decision
Where the gate does not exist, the curve is ungated, as under missing: 1. To
have no curve there instead, put missing: 0 on the gate.
points#
A values parameter with a missing row does not build a shorter curve, because
the assumption of the block refuses the
data. To give a curve fewer breakpoints than the dimension holds, set points:
to one of the values parameters of the block, and the curve runs as far as that
parameter has rows:
piecewise:
cost_curve:
over: bp
points: bp_x # this curve runs as far as its own breakpoints do
links:
- [p, bp_x]
- [op_cost, bp_y]
The tables stop where the curve stops, and the curve reads no row past its last
breakpoint (missing breakpoints). A row missing from
bp_y where bp_x has one is still refused. Where the length is its own data,
name a boolean parameter instead, whose marked breakpoints must be one
consecutive run, anywhere along the dimension.
Missing breakpoints#
A parameter that a block reads takes missing:
like any other parameter, and reads it the same inside and outside the curve.
Inside the curve, every reading builds the same curve: the curve reads a table
only at the breakpoints that points: marks, and there the <block>_complete
assumption refuses a missing row. The readings differ where an expression
outside the curve reads the table:
missing: of a values parameter |
A missing row read outside the curve |
|---|---|
refused, by default |
is refused |
absent |
removes the term |
a value, such as 0 |
reads that value |
Keep the default where no expression outside the curve reads the table past its
last breakpoint. Use absent where no number stands for a breakpoint the curve
does not have, and a value where one does.
method#
method says how the weights are restricted once they exist.
method |
What it adds | |
|---|---|---|
adjacency (default) |
an sos: block over the weights, written out as binaries |
the curve, built |
sos2 |
an sos: block over the weights, left as a set |
the curve, stated for a solver that branches on the set itself |
convex |
nothing | the hull, which is a pure linear program |
lp |
no weights at all: one row per segment line, plus two rows holding the domain | the curve as its own lines |
convex takes exactly two links and no activity:, and the shape it needs is
an assumption.
lp states the curve as its segment lines. It needs exactly two links, one
of them bounded with <= or >=, and no activity::
piecewise:
cost_curve:
over: bp
method: lp
links:
- [p, bp_x]
- [op_cost, bp_y, ">="] # cost bounded below by the curve
Where the number of links is data, write the formulation out (a curve by hand).
sos#
An sos block declares a special-ordered set: one dimension of one
variable, and how many members of that set may be non-zero at once.
sos:
pick_one_size:
variable: build # the variable the set is over
along: size # the dimension it runs along — one set per coordinate of the rest
type: 1 # 1: at most one non-zero; 2: at most two, and consecutive
A set is over one variable, and a variable holds one set, so a second block naming the same variable is a load error.
A member the variable's where masks out is not in the set. The order is the
order of the along dimension, which is
ordered for type: 2.
What a set is written out as#
spec.expand('sos') states the set as binaries: one per member for type: 1,
one per segment for type: 2. A member the binaries do not admit is held at
zero, from above and from below. For a set s over variable x along d, let
admitted be (s_seg) at type: 1 and (s_seg + shift(s_seg, along=d,
offset=1, edge=0)) at type: 2:
| Emitted | |
|---|---|
s_seg |
a binary over x's own dims, masked as x is |
s_pick: sum(s_seg, over=d) <= 1 |
at most one is picked |
s_nonzero (type: 1), s_adjacency (type: 2) |
x <= upper * admitted |
the same name plus _below |
x >= lower * admitted, where lower is not 0 |
upper and lower are the bounds: of the member, a number or a parameter,
and for a binary member they are 0 and 1. to_spec refuses a spec where a
member has no bounds.lower, or no bounds.upper and no domain: binary. A
name the expansion writes that the file already declares is refused at load too.
Writing a formulation out#
Spec.expand() replaces the block
with the variables and constraints it states.
See what a curve or a set expands to shows a
spec before and after.
- Every name written out starts with the name of the block, so the weights of
the curve
curvearecurve_lam. - No formulation emits a parameter, so the same data attaches to a spec and its expansion, and reads the same in both.
- The assumptions a
method:implies becomeassumptions:entries with the same names.