Reservoirs store water between timesteps. They are nodes in the water network, and receive upstream flows and precipitation, and provide withdrawals to canals. However, their outflows are determined by an optimization.

Selecting Reservoirs

The reservoirs that should be modeled as storing water across period, rather than acting like stream nodes, differ depending on the timestep. The function that provides a list of reservoirs is getreservoirs(config).


In [2]:
include("../src/lib/readconfig.jl")
include("../src/lib/reservoirs.jl")

config = readconfig("../configs/standard.yml")


Out[2]:
Dict{Any,Any} with 6 entries:
  "startweather" => 1
  "timestep"     => 6
  "netset"       => "usa"
  "filterstate"  => nothing
  "endmonth"     => "9/2010"
  "startmonth"   => "10/2000"

In [4]:
@doc getreservoirs


Out[4]:

Return a DataFrame containing collection and colid fields matching those in the Water Network.

Any additional columns can be provided, to be used by other components.

Rows may be excluded, to represent that a given reservoir should be modeled as a stream at the specified timestep (in months).


In [3]:
getreservoirs(config)


Out[3]:
collectioncolidarealatlonelev
1reservoir1NA48.8733-122.688NA
2reservoir2145.0394448.7583-122.422NA
3reservoir3556.8478548.6583-121.687NA
4reservoir4556.8478548.6483-121.69NA
5reservoir52587.4000148.7317-121.067NA
6reservoir62913.7387548.7133-121.13NA
7reservoir73001.7984148.6983-121.207NA
8reservoir8769.2270348.5483-121.74NA
9reservoir9NA48.9317-119.418NA
10reservoir10797.7169248.095-123.555NA
11reservoir11NA48.8116-119.533NA
12reservoir12634.5475548.0017-123.598NA
13reservoir13NA48.9866-117.347NA
14reservoir14NA48.5583-119.745NA
15reservoir15NA48.4683-120.25NA
16reservoir16313.3887948.5416-119.747NA
17reservoir17132.60748848.84-117.288NA
18reservoir18NA48.78-117.41NA
19reservoir19NA48.3616-119.697NA
20reservoir20176.1193247.975-121.687NA
21reservoir21NA47.945-121.83NA
22reservoir22NA47.665-122.397NA
23reservoir23106.1895947.385-123.605NA
24reservoir2433.6698748.2217-118.893NA
25reservoir25243.4590647.4216-123.222NA
26reservoir26NA47.3983-123.2NA
27reservoir27NA47.6933-121.688NA
28reservoir2895.8296348.275-118.375NA
29reservoir291481.4742848.49-116.902NA
30reservoir30NA47.925-120.177NA
&vellip&vellip&vellip&vellip&vellip&vellip&vellip

Optimization example

To understand the functioning of reservoirs, consider how they work in a very simple three gauge example. The three guages example has three counties, with a river running through them. The middle county has a reservoir. Water is supplied only upstream (in counties 1 and 2) and consumed only downstream (in counties 2 and 3).

Note that the reservoir appears to be outside of the river system. While it is spatially synonymous with the middle gauge, reservoirs get all of their inflows from "captured" water. Any water that is not captured is allowed to run through the reservoir just like a stream. Reservoir captures can also be negative, providing releases.

The optimize-surface.jl script models the constraints to satisfy surface water demands, using reservoirs to store water between periods.

In the three counties example, the first period has more rainfall than the second period, so that storage is optimal.


In [1]:
include("../src/optimize-surface.jl")


Loading from saved region network...
WARNING: imported binding for edges overwritten in module Main
Loading from saved water network...
Optimize a model with 22 rows, 20 columns and 50 nonzeros
Coefficient statistics:
  Matrix range    [1e+00, 1e+00]
  Objective range [1e-02, 1e+03]
  Bounds range    [0e+00, 0e+00]
  RHS range       [1e+00, 1e+09]
Presolve removed 17 rows and 12 columns
Presolve time: 0.27s
Presolved: 5 rows, 8 columns, 12 nonzeros

Iteration    Objective       Primal Inf.    Dual Inf.      Time
       0    1.0000000e-02   3.000000e+00   0.000000e+00      0s
       3    4.0000000e-02   0.000000e+00   0.000000e+00      0s

Solved in 3 iterations and 0.39 seconds
Optimal objective  4.000000000e-02
  5.969384 seconds (3.02 M allocations: 120.035 MB, 2.18% gc time)
waterfromsupersource
All zero.
withdrawals
[0.0,1.0,1.0,0.0,1.0,1.0]
Sum: 4.0
returns
All zero.
captures
[1.0,-1.0]
Sum: 0.0
Ignore:

Out[1]:
48

First, look at the runoff values, with rows for the three gauges and columns for the two time periods.


In [2]:
runoff


Out[2]:
3x2 Array{Float64,2}:
 2.0  1.0
 1.0  0.0
 0.0  0.0

The requirements are for one unit of water each period for each of the lower two gauges.


In [5]:
reshape(constraintoffset_allocation_recordedbalance(m).f, 3, 2)


Out[5]:
3x2 Array{Float64,2}:
 0.0  0.0
 1.0  1.0
 1.0  1.0

The order of the parameters in the LP problem is:


In [6]:
parameters


Out[6]:
4-element Array{Symbol,1}:
 :waterfromsupersource
 :withdrawals         
 :returns             
 :captures            

And the order of the constraint variables is:


In [8]:
constraints


Out[8]:
5-element Array{Symbol,1}:
 :outflows     
 :balance      
 :returnbalance
 :storagemin   
 :storagemax   

Consider the constraint matrix one parameter at a time. The first parameter is the water drawn from the supersource, which only affects the second constraint, :balance, the difference between water demand and water supply. The objective function is such that supersource withdrawals are avoided.


In [3]:
full(house.A)[:, 1:6]


Out[3]:
22x6 Array{Float64,2}:
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
 -1.0   0.0   0.0   0.0   0.0   0.0
  0.0  -1.0   0.0   0.0   0.0   0.0
  0.0   0.0  -1.0   0.0   0.0   0.0
  0.0   0.0   0.0  -1.0   0.0   0.0
  0.0   0.0   0.0   0.0  -1.0   0.0
  0.0   0.0   0.0   0.0   0.0  -1.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0

Normal withdrawals affect :balance as well, but they also affect the level of streamflow, which is constrained to be above 0. See the top six rows in two blocks, in the upper left and lower right, corresponding to the two periods. The gauges are ordered, by chance, such that the most upstream gauge is second, so a withdrawal from that gauge causes all three gauges to inch closer to empty.


In [4]:
full(house.A)[:, 7:12]


Out[4]:
22x6 Array{Float64,2}:
  0.0   1.0   0.0   0.0   0.0   0.0
  1.0   1.0   0.0   0.0   0.0   0.0
  1.0   1.0   1.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   1.0   0.0
  0.0   0.0   0.0   1.0   1.0   0.0
  0.0   0.0   0.0   1.0   1.0   1.0
 -1.0   0.0   0.0   0.0   0.0   0.0
  0.0  -1.0   0.0   0.0   0.0   0.0
  0.0   0.0  -1.0   0.0   0.0   0.0
  0.0   0.0   0.0  -1.0   0.0   0.0
  0.0   0.0   0.0   0.0  -1.0   0.0
  0.0   0.0   0.0   0.0   0.0  -1.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0
  0.0   0.0   0.0   0.0   0.0   0.0

We'll ignore the :returns constraint. The last is the reservoir in the two periods. Reservoir captures have the same affect on streamflows, at least downstream. They have no direct effect on :balance. Finally, they affect the last two constraints on the upper and lower bounds of the reservoir, where capture (or release) in period 1 affects both periods, while capture (or release) in period 2 only affects one.


In [6]:
full(house.A)[:, end-1:end]


Out[6]:
22x2 Array{Float64,2}:
  0.0   0.0
  0.0   0.0
  1.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   1.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
  0.0   0.0
 -1.0   0.0
 -1.0  -1.0
  1.0   0.0
  1.0   1.0

The result is that it is optimal to store one unit of water and release it in the second period.


In [7]:
constraining(house, sol.sol)


Ignore:

Out[7]:
solutioncomponentparameterabovefailbelowfail
10.0Allocationwaterfromsupersourcebalance.1
20.0Allocationwaterfromsupersourcebalance.2
30.0Allocationwaterfromsupersourcebalance.3
40.0Allocationwaterfromsupersourcebalance.4
50.0Allocationwaterfromsupersourcebalance.5
60.0Allocationwaterfromsupersourcebalance.6
70.0Allocationwithdrawalsoutflows.3balance.1
81.0Allocationwithdrawalsoutflows.3balance.2
91.0Allocationwithdrawalsoutflows.3balance.3
100.0Allocationwithdrawalsoutflows.5, outflows.6balance.4
111.0Allocationwithdrawalsoutflows.4, outflows.5, outflows.6balance.5
121.0Allocationwithdrawalsoutflows.6balance.6
130.0Allocationreturnsreturnbalance.1outflows.3
140.0Allocationreturnsreturnbalance.2outflows.3
150.0Allocationreturnsreturnbalance.3outflows.3
160.0Allocationreturnsreturnbalance.4outflows.5, outflows.6
170.0Allocationreturnsreturnbalance.5outflows.4, outflows.5, outflows.6
180.0Allocationreturnsreturnbalance.6outflows.6
191.0Reservoircapturesoutflows.3storagemin.2
20-1.0Reservoircapturesoutflows.6storagemin.2

Future work:

  • The optimization does not include evaporation, since this would make the numbers for this toy example inconvenient. Evaporation needs to be added to both the optimization and the example.