Demand restrictions
Demand restrictions are a common concept in water resource models. When the state of a water resource is poor (for example, lower than normal reservoir levels) demand restrictions may be imposed on customers in order to reduce the likelihood of a failure to supply.
The demand at a given timestep can be represented using the equation below,
where \(d\) is the final demand to be calculated, \(d_{mean}\) is the baseline demand, \(p\) is the annual profile and \(s\) is the demand restriction (e.g. 5%).
The code snippets that follow belong in the "parameters": {}
section of the model document (except for the node
at the end).
The baseline demand is specified as a constant (this is often the mean annual demand).
"demand_baseline": {
"type": "constant",
"values": 50
}
A daily or monthly profile can be used to vary the demand throughout the year. In the example below the demand in May - August is 1.2x the baseline demand, with the rest of the year at 0.9x the baseline, forming the common “top hat” profile (illustrated below).
"demand_profile": {
"type": "monthlyprofile",
"values": [0.9, 0.9, 0.9, 0.9, 1.2, 1.2, 1.2, 1.2, 0.9, 0.9, 0.9, 0.9]
},
(Source code
, png
, hires.png
, pdf
)
The demand restriction level describes the level of restrictions as an integer, where 0 means no demand restrictions, level 1 (L1) is some restriction (or perhaps just publicity), level 2 (L2) more severe restrictions, and so on. The specifics will depend on the system being modelled. This is represented in Pywr using the ControlCurveIndexParameter
. IndexParameter
return an integer to be used as an index, rather than a decimal value.
The demand restriction level is often related to the storage of strategic reservoirs in relation to a control curve (the expected reservoir volume at a given time of the year). The example below two demand restriction levels are defined (and implicitly a L0) based on the volume in the "Central Reservoir"
storage node. L1 is defined as 80% of full, L2 as 50% of full. Control curves are commonly more complicated, as the expected level of a reservoir is usually lower in the summer than it is in the winter.
"demand_restriction_level": {
"type": "controlcurveindex",
"storage_node": "Central Reservoir",
"control_curves": [
"level1",
"level2"
]
},
"level1": {
"type": "constant",
"values": 0.8
},
"level2": {
"type": "constant",
"values": 0.5
},
The demand restriction factor is determined by indexing an array of possible restriction profiles with the demand restriction level (i.e. index). In the example below the list of profiles ("params"
) corresponds to the L0, L1 and L2 profiles respectively. At L0 a constant factor 1.0
is used to represent no restrictions. At L1 there is a 10% reduction in demand (0.90
as a factor) during the summer months and a 5% reduction elsewhere (0.95
). At L2 there are further reductions to 75%/80%.
"demand_restriction_factor": {
"type": "indexedarray",
"index_parameter": "demand_restriction_level",
"params": [
{
"type": "constant",
"values": 1.0
},
{
"type": "monthlyprofile",
"values": [0.95, 0.95, 0.95, 0.95, 0.90, 0.90, 0.90, 0.90, 0.95, 0.95, 0.95, 0.95]
},
{
"type": "monthlyprofile",
"values": [0.8, 0.8, 0.8, 0.8, 0.75, 0.75, 0.75, 0.75, 0.8, 0.8, 0.8, 0.8]
}
]
},
To understand how the index works, the following equivalent Python code may help:
month = 5
demand_restriction_level = 1
demand_factors = [[1.0, ...], [0.95, ...], [0.8, ...]]
demand_restriction_factor = demand_factors[demand_restriction_level][month-1]
Finally the demand components can be combined as in the equation at the beginning using an AggregatedParameter
. Each timestep the value of each of the components is calculated and the values are multiplied to give the final demand value.
"demand_max_flow": {
"type": "aggregated",
"agg_func": "product",
"parameters": [
"demand_baseline",
"demand_profile",
"demand_restriction_factor"
]
},
The final profiles are illustrated in the figure below. The actual demand value will switch between the three profiles depending on the resource state of the reservoir.
(Source code
, png
, hires.png
, pdf
)
This parameter can then be applied to the max_flow
attribute of the demand node.
{
"type": "output",
"name": "Demand",
"max_flow": "demand_max_flow",
"cost": -500
},
When a model has more than one demand node it is OK to re-use the demand restriction level/factor for each demand node. Pywr will only calculate the index once for each parameter. Therefore, it is more efficient to share IndexParameter
where possible.