A spec in several files#
In this lesson you build a dispatch spec out of files that each hold one part of it, merge them into one spec, and then add a component without changing the other files. Do your first spec first.
The network#
Make a file network.yaml. It balances every bus, and it reads the injection
at a bus under given: rather
than defining it:
description: Every bus is balanced in every snapshot.
dimensions:
snapshot: { dtype: int, description: dispatch periods }
bus: { description: network nodes }
given:
expressions:
injection:
dims: [snapshot, bus]
description: what the components put into a bus, less what they take out
constraints:
balance:
dims: [snapshot, bus]
expression: injection == 0
Check the file:
The check accepts it, and notes the expression it reads:
expression 'injection' is read here and declared elsewhere: the model this one is layered onto provides it. A consumer checks that it does, on the same frame, and refuses the program where it does not. A fragment is composed instead: merge() folds this declaration into the one a sibling introduces.
The generators#
Make a file generators.yaml. It reads the injection too, and it says what it
puts in: a term:
on the entry.
description: A generator fleet, each unit on one bus.
dimensions:
snapshot: { dtype: int, description: dispatch periods }
bus: { description: network nodes }
generator: { description: generating units }
relations:
gen_bus: { key: generator, values: bus, description: the bus a generator sits on }
parameters:
capacity: { dims: [generator], description: installed capacity }
cost: { dims: [generator], description: marginal cost }
variables:
dispatch:
description: output of a generator in a snapshot
dims: [snapshot, generator]
bounds: { lower: 0, upper: capacity }
given:
expressions:
injection:
dims: [snapshot, bus]
term: sum(dispatch, by=gen_bus, over=generator, into=bus)
objective:
sense: minimize
expression: sum(dispatch * cost)
Check the file:
The check accepts it, and notes the term:
expression 'injection' is read here and declared elsewhere, and this file adds a term to it: merge() sums the term with what the other files declare under the name. Until then, the program reads it and does not build it.
Print the math of the file on its own:
The term prints after dots, which stand for what the other files put in:
Rendered output
A generator fleet, each unit on one bus.
Objective#
Definitions#
injection
Variable domains#
dispatch
The loads#
Make a file loads.yaml. Its term takes the demand out of the bus:
description: The demand at every bus.
dimensions:
snapshot: { dtype: int, description: dispatch periods }
bus: { description: network nodes }
parameters:
demand: { dims: [snapshot, bus], description: demand to be met }
given:
expressions:
injection:
dims: [snapshot, bus]
term: -demand
Check the file:
The check accepts it, with the same note:
expression 'injection' is read here and declared elsewhere, and this file adds a term to it: merge() sums the term with what the other files declare under the name. Until then, the program reads it and does not build it.
Merge the files#
Merge the three files in Python. Each name is what an error calls that file:
spec = ms.merge({'network': 'network.yaml', 'generators': 'generators.yaml', 'loads': 'loads.yaml'})
print(spec.expressions['injection'].expression)
The injection is the sum of the two terms, in the order of the file names:
Print the math of the merged spec:
Rendered output
Objective#
Subject to#
balance
Definitions#
injection
Variable domains#
dispatch
Add a component#
Make a file imports.yaml. It adds a term to the injection and a term to the
objective:
description: Power bought from outside the network, at a price.
dimensions:
snapshot: { dtype: int, description: dispatch periods }
bus: { description: network nodes }
parameters:
import_limit: { dims: [bus], description: most a bus can import }
import_price: { dims: [snapshot], description: price of imported power }
variables:
imported:
description: power a bus imports in a snapshot
dims: [snapshot, bus]
bounds: { lower: 0, upper: import_limit }
given:
expressions:
injection:
dims: [snapshot, bus]
term: imported
objective:
sense: minimize
expression: sum(imported * import_price)
Merge the four files:
spec = ms.merge(
{'network': 'network.yaml', 'generators': 'generators.yaml', 'loads': 'loads.yaml', 'imports': 'imports.yaml'}
)
print(spec.expressions['injection'].expression)
print(spec.objective.expression)
The injection has a third term, and the objective sums the two objectives.
network.yaml did not change:
(sum(dispatch, by=gen_bus, over=generator, into=bus)) + (imported) + (-demand)
(sum(dispatch * cost)) + (sum(imported * import_price))
Read what another file declares#
Make a file emissions.yaml. It caps what the fleet emits, and it reads
dispatch under given: rather than declaring it:
description: A cap on what the fleet emits over the horizon.
dimensions:
snapshot: { dtype: int, description: dispatch periods }
generator: { description: generating units }
given:
variables:
dispatch: { dims: [snapshot, generator] }
parameters:
emission_rate: { dims: [generator], description: emissions per unit of output }
emission_cap: { dims: [], description: most the fleet may emit }
constraints:
emission_limit:
dims: []
expression: sum(dispatch * emission_rate) <= emission_cap
Check the file:
The check accepts it, and notes the variable it reads:
variable 'dispatch' is read here and declared elsewhere: the model this one is layered onto provides it. A consumer checks that it does, on the same frame, and refuses the program where it does not. A fragment is composed instead: merge() folds this declaration into the one a sibling introduces.
Merge all five files:
spec = ms.merge(
{
'network': 'network.yaml',
'generators': 'generators.yaml',
'loads': 'loads.yaml',
'imports': 'imports.yaml',
'emissions': 'emissions.yaml',
}
)
print(sorted(spec.constraints))
print(bool(spec.program.given))
The cap reads the generators' dispatch, and nothing is left for anything
outside the files to provide:
Leave the network out#
Merge the generators and the loads without the network:
merge refuses it. A term adds to a name another file has, and without the
network no file defines, reads or uses injection:
fragments 'generators' and 'loads' add a term to 'injection', which no fragment defines, reads or uses. A term adds to a name another file has: define it under 'expressions:', read it under 'given: expressions:', or fix the spelling.
Where to next#
- Compose a spec from several files covers
mergeandoverride, which lays a patch over a spec. givengives every rule a file that reads another file obeys.- A component library shows larger fragments beside the math they print.