Skip to content

Absence and where#

This page lists the constructs that remove a row from an entry, and how absence travels through an expression. Every entry is a table with at most one row for each combination of labels of its dimensions (a coordinate). An entry is absent at a coordinate when it has no row there. For a variable, absent means that the solver never sees it at that coordinate, and for a constraint, nothing is required there. Absent is different from zero, because zero is a value in a row.

where#

Where its condition is false, a where: on a variable leaves the variable absent, not zero.

dimensions:
  generator: { dtype: str }
parameters:
  capacity: { dims: [generator] }
variables:
  dispatch:
    dims: [generator]
    where: "capacity > 0"
A where condition on a variable removes rows The parameter capacity is a table with three rows: wind 10, gas 5 and old 0. The variable dispatch, declared with where capacity greater than zero, is a table with two rows, wind and gas. The row for old, where capacity is zero, is absent. capacity a parameter generator value wind10 gas5 old0 where: keeps a row only where it is true dispatch a variable generator wind gas no row for old: dispatch is absent condition is false absent: no row

With capacity = {wind: 10, gas: 5, old: 0}, the model has dispatch[wind] and dispatch[gas], and no dispatch[old].

Where its condition is false, a where: on a constraint removes the row, so the constraint requires nothing there:

A where condition on a constraint removes rows The parameter capacity is a table with three rows: wind 10, gas 5 and old 0. The constraint min_out, with expression x greater than or equal to 1 and where capacity greater than zero, is a table with two rows: x of wind at least 1 and x of gas at least 1. The row for old, where capacity is zero, is absent, so nothing is required there. constraints: min_out: dims: [generator] expression: x >= 1 where: "capacity > 0" capacity a parameter generator value wind10 gas5 old0 where: keeps a row only where it is true min_out a constraint generator row windx[wind] >= 1 gasx[gas] >= 1 no row for old: nothing is required condition is false absent: no row

With the same capacities, min_out has rows at wind and gas, and none at old.

The condition grammar defines what a where: may contain.

What creates absence#

Eight constructs make a coordinate absent:

Construct What is absent
where: on a variable the variable, at the coordinates the condition excludes (where)
where: on a parameter the parameter, at the coordinates the condition excludes (where on a parameter)
where: on a constraint the row (where)
shift(x, along=d, offset=n) without edge= the expression, at the edge coordinate (shift)
a key with no row in a missing: absent relation the relation, at that key (relations)
a missing row of a missing: absent parameter the parameter, at that coordinate (missing)
within= at a coordinate in no group the expression, at that coordinate (within=)
a quotient whose divisor is zero the quotient, at that coordinate (reported values)

No value shifts in at the edge, so a shift without edge= has no row there:

A shift without edge leaves the first position absent The variable x along the ordered dimension snapshot is a table with three rows: x of 1, x of 2 and x of 3. The expression shift of x along snapshot with offset 1 is a table in which snapshot 2 has x of 1 and snapshot 3 has x of 2. Snapshot 1 has no row, because nothing shifts in there, so the expression is absent at snapshot 1. dimensions: snapshot: { dtype: int, ordered: true } variables: x: { dims: [snapshot] } x a variable snapshot value 1x[1] 2x[2] 3x[3] offset=1 each value moves one position on shift(x, along=snapshot, offset=1) an expression snapshot value 1 2x[1] 3x[2] no row: nothing shifts in absent: no row

shift(x, along=snapshot, offset=1) has no row at snapshot 1, so the expression is absent there.

A relation with missing: absent is absent at a key that has no row in the data:

A relation with missing absent is absent at a key with no row The dimension line has three labels: l1, l2 and l3. The relation line_to, declared with missing absent, maps each line to its bus. Its data has a row for l1 at bus north and a row for l2 at bus south, and no row for l3. At l3 the relation line_to is absent. relations: line_to: { key: line, values: bus, missing: absent } line a dimension line l1 l2 l3 line_to maps each line to its bus line_to a relation line bus l1north l2south no row for l3: line_to is absent no row in line_to absent: no row

line_to has no row for l3, so the relation is absent at l3.

missing#

On a parameter and on a variable, the missing: key sets what a coordinate with no row means.

missing on a parameter#

The missing: key of a parameter takes one of three values:

missing: Result
refused, by default a tool refuses the parameter if any row of its data is missing, and names the coordinate
absent a missing row is absent: a term that uses the row is absent, and a sum leaves the term out
a value a missing row takes that value, wherever an expression uses the parameter
The four meanings of a missing parameter row The parameter rel_max has a row for wind at 0.9 and a row for gas at 0.6, and no row for old. Under missing refused, the data is refused when it is attached. Under missing absent, rel_max has no row for old. Under missing 0, old takes the value 0. Under missing 1, old takes the value 1. parameters: rel_max: dims: [generator] missing: ... rel_max a parameter generator value wind0.9 gas0.6 no row for old missing: refused missing: absent missing: 0 missing: 1 rel_max, a parameter rel_max, a parameter rel_max, a parameter rel_max, a parameter refused when the data is attached no row for old generatorvalue wind0.9 gas0.6 generatorvalue wind0.9 gas0.6 old0 / false generatorvalue wind0.9 gas0.6 old1 absent: no row

At old, where rel_max has no row, a tool refuses the data, or rel_max is absent, 0 or 1.

A value is one number in every position where an expression uses the parameter. A cost that is zero takes missing: 0, an efficiency of one takes missing: 1, and a flag that is off takes missing: false.

Under refused, a tool refuses the data before any expression uses a missing row. Under absent and under a value, the result depends on the position:

Position Under absent Under a value
a coefficient outside a sum, or a constant on either side of a comparison the row is absent a tool uses the value
a coefficient inside a sum the sum leaves the term out, and the row stays a tool uses the value
a divisor the row is absent a tool uses the value. A 0 needs a guard
a bounds: entry that side of the variable is open the value is the bound
the amount of an offset= or a window= the operator is absent at that coordinate the value is the amount
a comparison in a where: the comparison is false a tool compares the value
a bare bool name in a where: the condition is false the condition is the value
a bare number or label in a where: the condition is false the condition is false

Under missing: 0, a shift(x, โ€ฆ) with a missing offset= amount is x itself. A sum_back with a missing window= amount is a sum of no terms.

Three declarations set a bound only where the data has one: missing: absent, missing: .inf or -.inf on the parameter, or a where: on the variable. Elsewhere, that side of the variable is open.

A curve requires a row at each breakpoint within its where:, and missing breakpoints describes a missing row in its table.

Required rows in an absent table#

Under missing: absent, an engine accepts data with a missing row at any coordinate. An assumption whose holds: is the bare name of the parameter requires a row wherever its where: is true:

dimensions:
  g: { dtype: str }
parameters:
  capacity: { dims: [g] }
  p_max: { dims: [g], missing: absent }
variables:
  dispatch:
    dims: [g]
    where: "capacity > 0"
    bounds: { lower: 0, upper: p_max }
objective:
  sense: maximize
  expression: sum(dispatch, over=g)
assumptions:
  p_max_where_dispatch_exists:
    holds: "p_max"
    where: "capacity > 0"
An absent table needs a row only where the condition is true The parameter capacity is a table with three rows: wind 10, gas 5 and old 0. The parameter p_max, declared missing: absent, has rows wind 8 and gas 4, and no row for old, which the data may leave out, because the assumption needs a row only where capacity is greater than zero. The variable dispatch, with where capacity greater than zero and upper bound p_max, has rows at wind with upper bound 8 and at gas with upper bound 4. The row for old is absent. capacity a parameter p_max a parameter, missing: absent dispatch a variable g value wind10 gas5 old0 g value wind8 gas4 g upper wind8 gas4 no row for old: allowed no row for old: dispatch is absent where: capacity > 0 bounds: upper: p_max the assumption needs a row only where capacity > 0 condition is false absent: no row

With capacity = {wind: 10, gas: 5, old: 0}, an engine accepts p_max data with rows for wind and gas only, because capacity > 0 is false at old. Data with no row for gas fails the assumption. Without the assumption, a missing p_max[gas] leaves the upper bound of dispatch[gas] open.

A guarded divisor#

A guard is a condition that is false at every missing row of a parameter, such as c > 0, c != 0 or the bare name c. At load, mathspec refuses a divisor that a missing: 0 parameter can make 0, unless a guard covers the quotient. A guard covers the quotient when it is one of the conditions joined by AND in one of these places:

  • the where: of the constraint
  • the when: of the case that contains the quotient
  • the where: of a missing: absent variable that multiplies the quotient, because the product is absent where that variable is absent
dimensions:
  g: { dtype: str }
parameters:
  c: { dims: [g], missing: 0 }
  d: { dims: [g] }
variables:
  y: { dims: [g] }
constraints:
  per_unit:
    dims: [g]
    where: "c > 0"
    expression: y <= d / c
A guard leaves out the row whose divisor is 0 The parameter c, declared missing: 0, is a table with rows wind 4 and gas 2. It has no data for old, so c is 0 there. The constraint per_unit, with where c greater than zero, has two rows: y at wind is at most d at wind divided by 4, and y at gas is at most d at gas divided by 2. The row for old is absent, so nothing divides by 0. c a parameter, missing: 0 g value wind4 gas2 old0 where: c > 0 keeps a row only where it is true per_unit a constraint g row windy[wind] <= d[wind] / 4 gasy[gas] <= d[gas] / 2 no row for old: no division by 0 no data, so 0 absent: no row

With c = {wind: 4, gas: 2} and no row for old, c is 0 at old. The guard c > 0 is false there, so per_unit has no row at old, and a tool never evaluates d / c at old.

A guard covers a divisor only at the coordinate where a tool evaluates the guard. So a guard does not cover a divisor in three cases:

  • The variable that multiplies the quotient has a missing: number. It takes that number outside its where:, so the product is never absent.
  • The divisor is inside a sum, sum_back, shift or at, and the guard is outside it. These operators use other coordinates than the guard does.
  • The divisor is inside a named expression. A tool evaluates a named expression over its own dimensions, so the guard goes in its own cases:.

Zero divisors in the data#

At load, mathspec checks only the form of a divisor. So a divisor in a file that loads can still be 0 once data is attached:

  • x / (1 - c) with c: { missing: 1 }, at a missing row of c
  • x / ((c + 1) - 1) with c: { missing: 0 }, at a missing row of c
  • a case that contains 1 - 1, where its when: holds
  • x / c, at a row of c whose value is 0

With data attached, an engine refuses each of these where it builds the row, as missing rows describes.

missing on a variable#

Outside its where:, a variable is absent by default. Its missing: key can set a number there instead, such as 0. Under absent, a row that needs that coordinate is absent. A file declares missing: 0 for a quantity that is zero outside its condition:

dimensions:
  storage: { dtype: str }
parameters:
  inflow: { dims: [storage] }
  has_inflow: { dims: [storage], dtype: bool }
  has_store: { dims: [storage], dtype: bool }
variables:
  spill:
    dims: [storage]
    where: has_inflow
    missing: 0 # outside the condition spill is 0, and the row exists
  soc:
    dims: [storage]
    where: has_store # the default, missing: absent, so no row
constraints:
  balance:
    dims: [storage]
    expression: inflow - spill - soc == 0
A variable with missing 0 is zero outside its condition, an absent one has no row Three storages: dam has inflow and a store, tank has a store and no inflow, river has inflow and no store. The variable spill, with where has_inflow and missing 0, has a row at every storage, and its row at tank is 0. The variable soc, with where has_store, has rows at dam and tank and no row at river. The constraint balance has a row at dam, inflow minus spill minus soc equals 0, and a row at tank, inflow minus soc equals 0. It has no row at river, because soc is absent there. spill a variable, missing: 0 storage value damspill[dam] tank0 riverspill[river] outside the condition soc a variable, missing: absent storage value damsoc[dam] tanksoc[tank] no row for river: soc is absent balance a constraint storage row daminflow[dam] - spill[dam] - soc[dam] == 0 tankinflow[tank] - soc[tank] == 0 no row for river: soc[river] is absent absent: no row

At a storage with a store and no inflow, balance is inflow - soc == 0, and at a storage with inflow and no store balance has no row.

A variable has no data for a tool to refuse, so its missing: takes only absent or a finite number. A number requires a where:, because a variable with no condition exists at every coordinate. At load, mathspec refuses the variable without one:

variables.p: missing: 0 needs a `where:`. A variable with no `where:` exists at every coordinate of its dimensions, so no coordinate is missing. Add a `where:`, or delete `missing:`.

mathspec suggests two rewrites: a where:, or no missing:.

The number must lie in the domain: of the variable, so a binary variable takes 0 or 1, and an integer variable takes an integer. The bounds: apply only where the where: holds, so the number may lie outside them. A unit with lower: 1 where it is built can have missing: 0 where it is not.

Inside a sum, missing: 0 has no effect, because an absent term already adds nothing there. With any other number, the sum gains that number once for each coordinate that the where: excludes.

In the legend, beside the variable, the typesetter prints the number, such as `0` where the mask leaves it out.

How absence travels#

In arithmetic, an absent term makes the whole row absent, but inside a sum, an absent term is left out and the row stays.

dimensions:
  generator: { dtype: str }
parameters:
  capacity: { dims: [generator] }
variables:
  x: { dims: [generator] }
  y: { dims: [generator], where: "capacity > 0" } # no y[old]
constraints:
  each:
    dims: [generator]
    expression: x + y >= 1 # rows at wind and gas, no row at old
  total:
    dims: []
    expression: sum(x + y, over=generator) >= 1 # x[wind] + y[wind] + x[gas] + y[gas] >= 1
  split:
    dims: []
    expression: sum(x, over=generator) + sum(y, over=generator) >= 1 # x[old] is back in
Absence travels through arithmetic and is left out of a sum The variable x has rows for wind, gas and old. The variable y, declared with where capacity greater than zero, has rows for wind and gas, and no row for old. The constraint each, x plus y at least 1, has rows at wind and gas and no row at old. The constraint total has one row, x of wind plus y of wind plus x of gas plus y of gas at least 1: the term x of old plus y of old is left out. The constraint split has one row, x of wind plus x of gas plus x of old plus y of wind plus y of gas at least 1: x of old is in the first sum, and y of old is left out. x a variable y a variable generator wind gas old generator wind gas no row for old x, y in three constraints each a constraint total a constraint split a constraint generator row windx[wind] + y[wind] >= 1 gasx[gas] + y[gas] >= 1 row x[wind] + y[wind] + x[gas] + y[gas] >= 1 row x[wind] + x[gas] + x[old] + y[wind] + y[gas] >= 1 no row for old: y[old] is absent the term x[old] + y[old] is absent and left out x[old] is in the first sum; y[old] is left out absent: no row

With the capacities above, y[old] is absent, and because x + y is arithmetic, each has no row at old. In total, the term x + y is absent at old, so the sum leaves out both x[old] and y[old]. In split, each sum runs over the coordinates of its own operand, so x[old] is in the first sum, and total and split differ.

A parameter with a missing row and an absent variable have different effects:

dimensions:
  generator: { dtype: str }
parameters:
  capacity: { dims: [generator] }
  rel_max: { dims: [generator], missing: 0 }
variables:
  x: { dims: [generator] }
  y: { dims: [generator], where: "capacity > 0" }
constraints:
  cap:
    dims: [generator]
    expression: x - rel_max * y <= 0
A missing parameter row and an absent variable affect a row differently The variable y has rows for wind and gas, and no row for old, where capacity is 0. The parameter rel_max, with missing 0, is 0.5 for wind and 0.9 for old, and has no row for gas. The constraint cap has a row for wind, x of wind minus 0.5 times y of wind at most 0, and a row for gas, x of gas at most 0. It has no row for old, because y is absent there. y a variable generator wind gas no row for old: y is absent rel_max a parameter generator value wind0.5 gasโ€” old0.9 cap rel_max is 0 where it has no row cap a constraint generator row windx[wind] โˆ’ 0.5 ยท y[wind] <= 0 gasx[gas] <= 0 no row for old: cap is absent no data absent: no row

With data attached, y is absent at old, so cap has no row there. At gas, where rel_max has no row, the row is x[gas] <= 0. With missing: absent on rel_max, or with where: rel_max on the constraint, cap has no row there either.

An operator has one of two results for an absent input:

Operator Inputs to one output At an absent input
sum(x, over=d) every position along d the sum leaves it out, and the row stays
sum(x, over=d, by=relation[c]) every member of the group the sum leaves it out, and the row stays
sum_back(x, along=d, window=w) the positions in the window the sum leaves it out, and the row stays
shift(x, along=d, offset=n) one position, n back that input is the only one, so the row is absent
at(x, by=relation[c]) one position, through the map that input is the only one, so the row is absent

With data attached, a missing row that takes the value 0 can leave a constraint row with no variable in it. In a constraint rel_max * y <= capacity, the row at a generator with no rel_max data is 0 <= capacity. Before any data is attached, mathspec refuses only a constraint whose expression has no variable. So that constraint loads, because its expression has the variable y, and the row 0 <= capacity exists only once the data is attached.

Reported values#

A reported expression is computed from solved values, and is absent wherever a value that it uses is absent.

A quotient is also absent where its divisor is zero, and the zero may come from the data or from a solved value. Here lcoe is a reported expression over generator:

dimensions:
  snapshot: { dtype: int }
  generator: { dtype: str }
parameters:
  marginal_cost: { dims: [generator] }
variables:
  p: { dims: [snapshot, generator], bounds: { lower: 0 } }
expressions:
  cost: sum(p * marginal_cost, over=snapshot)
  delivered: sum(p, over=snapshot)
  lcoe: cost / delivered
objective:
  sense: minimize
  expression: sum(cost)
A quotient is absent where its divisor is zero The key is lcoe: cost / delivered. The solved expression delivered has three rows: wind 50, gas 20 and old 0. The reported quotient lcoe has rows for wind and gas. The row for old, where the divisor is zero, is absent. expressions: lcoe: cost / delivered delivered an expression generator value wind50 gas20 old0 cost / delivered absent where the divisor is zero lcoe a reported expression generator wind gas no row for old: lcoe is absent divisor is zero absent: no row

With delivered zero at old, lcoe has rows at wind and gas, and none at old.

A divisor at a missing row of an absent parameter makes the quotient absent, and a missing: 0 divisor needs a guard.

See also

  • dual for the dual of an absent constraint row.
  • Operators and conditions for the operators and the where: grammar that this page uses.