A spec in several files#
Build a dispatch spec from 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, which balances every bus. It reads the injection at
a bus under given:, because each
component file adds its own share as a
term. A term is a named expression
that adds to a sum another file reads. The file also sets the objective, which
reads the total cost in the same way: each component adds its cost as a term.
description: Every bus is balanced in every snapshot.
given:
expressions:
injection:
dims: [snapshot, bus]
description: what the components put into a bus, less what they take out
total_cost:
dims: []
description: what running the system costs
dimensions:
snapshot: { dtype: int, description: dispatch periods }
bus: { description: network nodes }
constraints:
balance:
dims: [snapshot, bus]
expression: injection == 0
objective:
sense: minimize
expression: total_cost
Check the file:
The check accepts it, and notes each name it reads and does not define:
expression 'injection' is read here and declared elsewhere: the model this one is layered onto provides it. A tool refuses the spec where that model does not, over the same dims. merge() replaces this entry with the one another file declares, or builds it from the terms other files add.
expression 'total_cost' is read here and declared elsewhere: the model this one is layered onto provides it. A tool refuses the spec where that model does not, over the same dims. merge() replaces this entry with the one another file declares, or builds it from the terms other files add.
The generators#
Make a file generators.yaml. Its named expression generation is what the
fleet puts into a bus. The file reads the injection too, and
adds_to: on generation makes
generation a term of the injection. In the same way, generation_cost is a
term of the total cost. The file restates the two dimensions it shares with the
network as a dtype only, because a missing description does not conflict with
the network, and merge keeps the description from network.yaml.
description: A generator fleet, each unit on one bus.
given:
expressions:
injection: { dims: [snapshot, bus] }
total_cost: { dims: [] }
dimensions:
snapshot: { dtype: int }
bus: { dtype: str }
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 }
expressions:
generation:
description: what the fleet puts into a bus
expression: sum(dispatch, over=generator, by=gen_bus[bus])
adds_to: injection
generation_cost:
description: what running the fleet costs
expression: sum(dispatch * cost)
adds_to: total_cost
Check the file:
The check accepts it, and notes each term:
expression 'injection' is read here, and this file adds a term to it. merge() sums the term with the terms other files add under that name. Until then, the program reads it and does not build it.
expression 'total_cost' is read here, and this file adds a term to it. merge() sums the term with the terms other files add under that name. Until then, the program reads it and does not build it.
Print the math of the file on its own:
Each term prints as its own definition, and the legend, left out here, says what each term adds to. The file sets no objective, so on its own it is a feasibility problem:
Rendered output
A generator fleet, each unit on one bus.
Variable domains#
dispatch
Definitions#
generation
generation_cost
The loads#
Make a file loads.yaml. Its term, consumption, takes the demand out of the
bus:
description: The demand at every bus.
given:
expressions:
injection: { dims: [snapshot, bus] }
dimensions:
snapshot: { dtype: int }
bus: { dtype: str }
parameters:
demand: { dims: [snapshot, bus], description: demand to be met }
expressions:
consumption:
description: what the loads take out of a bus
expression: -demand
adds_to: injection
Check the file:
The check accepts it, with the same note:
expression 'injection' is read here, and this file adds a term to it. merge() sums the term with the terms other files add under that name. Until then, the program reads it and does not build it.
Merge the files#
Merge the three files in Python, and give them as a list:
spec = ms.merge(['network.yaml', 'generators.yaml', 'loads.yaml'])
print(spec.expressions['injection'].expression)
print(spec.dimensions['snapshot'].description)
merge writes the injection as the sum of the two terms by name, in the order
of the list. Each term stays a named expression of the merged spec. The
dimension keeps the one description written for it, which is the network's:
Print the math of the merged spec:
Rendered output
Variable domains#
dispatch
Definitions#
generation
generation_cost
consumption
injection
total_cost
Objective#
Subject to#
balance
Add a component#
Make a file imports.yaml. It adds a term, purchase, to the injection and a
term, import_cost, to the total cost:
description: Power bought from outside the network, at a price.
given:
expressions:
injection: { dims: [snapshot, bus] }
total_cost: { dims: [] }
dimensions:
snapshot: { dtype: int }
bus: { dtype: str }
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 }
expressions:
purchase:
description: what the imports put into a bus
expression: imported
adds_to: injection
import_cost:
description: what the imports cost
expression: sum(imported * import_price)
adds_to: total_cost
Merge the four files:
spec = ms.merge(['network.yaml', 'generators.yaml', 'loads.yaml', 'imports.yaml'])
print(spec.expressions['injection'].expression)
print(spec.expressions['total_cost'].expression)
The injection has a third term at the end, and the total cost a second one,
although network.yaml did not change:
Only network.yaml sets the objective. merge refuses a second file that
sets one, because it does not join two objectives. Add a part of the cost as a
term of total_cost instead.
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.
given:
variables:
dispatch: { dims: [snapshot, generator] }
dimensions:
snapshot: { dtype: int }
generator: { dtype: str }
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 tool refuses the spec where that model does not, over the same dims. merge() replaces this entry with the one another file declares.
Merge all five files:
spec = ms.merge(['network.yaml', 'generators.yaml', 'loads.yaml', 'imports.yaml', 'emissions.yaml'])
print(sorted(spec.constraints))
print(bool(spec.program.given))
The cap reads dispatch from generators.yaml. Because spec.program.given
is empty, nothing outside the five files has to provide a name:
Leave the network out#
Merge the generators and the loads without the network:
merge refuses it. A term adds to a sum that the rest of the spec reads, but
without the network, only the two files that add to injection read it:
fragments 'generators.yaml' and 'loads.yaml' add a term to 'injection', and no other fragment reads it. Add the fragment that reads 'injection', or fix the spelling under 'given:'.
Where to next#
- Compose a spec from several files covers
mergeandoverride, which lays a patch over a spec. givengives the rules for a file that reads what another file declares.- A component library shows larger fragments beside the math they print.