Head coefficients#
When running Prodrisk for the first time on a system/dataset, Prodrisk will run twice:
Initial run: The purpose is to find reasonable head coefficients and expected reservoir trajectories for the system. These are stored on the files HKORR.SDDP and MAGVOL.SDDP.
Final run: Uses the head coefficients and expected reservoir levels available in the files HKORR.SDDP and MAGVOL.SDDP, as computed in the initial run. Stores results and updated values for head coefficients and expected reservoir levels at NY_HKORR.SDDP and NY_MAGVOL.SDDP.
The unit for MAGVOL is Mm3. The unit for HKORR is 10^6 øre/Mm3 if the price is given in øre/kWh, and NOK/Mm3 if the price is given in NOK/MWh.
It is possible to run Prodrisk without head coefficients and without the initial run, if the option -NOHEAD is used. If one wants to use the files NY_HKORR.SDDP and NY_MAGVOL.SDDP in a new Prodrisk run, the option –NEWHEAD should be used.
Small reservoirs that are not regulated should not include nominal head nor submersion, to avoid getting head coefficients calculated for this reservoir.
Head coefficients#
Head coefficients represent the expected added value of storing water in the reservoirs due to head. The head coefficients enter the optimization problem formulated for each week, defining an additional reward for storing water. These coefficients are based on pervious Prodrisk computations and vary between weeks and reservoirs. For a detailed description of the mathematical formulation, see [3].
The head coefficients are stored as follows:
If the coefficients are not present from a previous Prodrisk run, Prodrisk starts by setting head coefficients to zero. Once a Prodrisk initial run is completed, estimated coefficients are store in a file named HKORR.SDDP.
In the final run, head coefficients per week and reservoir are read from HKORR.SDDP and used in the computation of new cuts. Updated head coefficients are stored in the file NY_HKORR.SDDP.
The coefficients in files HKORR.SDDP or NY_KORR.SDDP (if -newhead is used) are always used (except if -nohead is used) by Prodrisk if these files exist and are consistent. These coefficients are used even if the starting reservoir or computation period are changed, as it is believed that previously computed coefficients provide a better estimate than running without coefficients.
The head coefficients are updated as follows:
HKORR.SDDP is updated if an initial Prodrisk run is performed or if the period of analysis (week numbers) is changed compared to a current file.
NY_HKORR.SDDP is updated if one uses the options -nohead, –newhead or –forts, or if a consistent HKORR.SDDP file exists and the period of analysis is the same as specified in this file.
Expected reservoir trajectories#
For valid cut computations, Prodrisk must use a fixed and state-independent head in each week. The following approximate scheme is used to find the expected head:
In the initial run, Prodrisk assumes that the reservoir trajectories follows the rule curves. The rule curves are either explicitly defined by the user or automatically computed by the EOPS model. The simulated mean reservoir trajectories from the initial run are stored in the file MAGVOL.SDDP.
In the final run, the mean reservoir level per week is read from the file MAGVOL.SDDP, and these are used when computing new cuts. Updated mean reservoir trajectories are stored in the file NY_MAGVOL.SDDP.
If one wants to use the new mean reservoir trajectories in a later final run, Prodrisk should be run with the option -newhead.
The trajectories in files MAGVOL.SDDP or NY_MAGVOL.SDDP (if -newhead is used) are always used by Prodrisk if these files exist and are consistent. These coefficients are used even though one changes the staring reservoir or computation period, as it is believed that previously computed coefficients provide a better estimate than running without coefficients. These coefficients are used even though one changes the starting reservoir or computation period, as it is believed that previously computed trajectories provide a better estimate than the rule curves. To use rule curves instead of the computed trajectories, the MAGVOL.SDDP file must be deleted.
The files files MAGVOL.SDDP or NY_MAGVOL.SDDP are updated similarly as the files HKORR.SDDP or NY_HKORR.SDDP.
Command line interface#
If the command line option -nohead is used, Prodrisk will run without head coefficients and run only.
If the command line option -newhead is used, Prodrisk will use the head coefficients from the file NY_HKORR.SDDP.
API#
Running the API without head coefficients can be done by setting
prodrisk-command_line_option=”-NOHEAD”.
Running the API with head coefficients from the file NY_HKORR.SDDP can be done by setting
prodrisk-command_line_option=”-NEWHEAD”.
Example of use with head coefficients#
This example illustrates how the head coefficients and mean reservoir trajectories may be set as input to a Prodrisk API run based on a previous run.
Each prodrisk session in this example is a copy of the session created in the basic example.
import pandas as pd
import numpy as np
import plotly.express as px
from pyprodrisk import ProdriskSession
from head_coefficients import build_model, set_all_head_coeffs_and_mean_reservoir_trajectories
First session#
The first session runs two main iterations, as no head coefficients are set, and command line option -NOHEAD is not used.
The F- and K-costs from the two main iterations are added to a dictionary.
prodrisk = ProdriskSession(license_path='/prodrisk', solver_path='/pyprodrisk', silent=False, log_file='')
build_model(prodrisk)
# --- run prodrisk session ---
status = prodrisk.run()
my_area = prodrisk.model.area["my_area"]
expected_objective_val_kkr = my_area.expected_objective_value.get()
print(f"Expected objective value: {expected_objective_val_kkr} kkr")
#fcost_first_run = my_area.forward_cost_first_run.get()
#kcost_first_run = my_area.backward_cost_first_run.get()
#iteration_numbers_first_run = range(1, len(fcost_first_run)+1)
#fcost = my_area.forward_cost.get()
#kcost = my_area.backward_cost.get()
#iteration_numbers = range(1, len(fcost)+1)
#cost_dict = { "F-cost, first session": pd.Series(data=fcost, index=iteration_numbers),
# "K-cost, first session": pd.Series(data=kcost, index=iteration_numbers),
# "F-cost first run, first session": pd.Series(data=fcost_first_run, index=iteration_numbers_first_run),
# "K-cost first run, first session": pd.Series(data=kcost_first_run, index=iteration_numbers_first_run),
# }
Expected objective value: 21009.885 kkr
Second session#
The second session is a copy of the first one, but the head coefficients and mean reservoir trajectories are set equal to those used in the second main iteration of the first session.
This run should thus give identical results as the first session, but take approximately half the time to run, as the head coefficients are set as input, so only one main iteration is run.
The F- and K-costs from this run are also added to the cost dictionary.
prodrisk_second_run = ProdriskSession(license_path='/prodrisk', solver_path='/pyprodrisk', silent=False, log_file='')
build_model(prodrisk_second_run)
prodrisk_second_run.command_line_option = ""
#set_all_head_coeffs_and_mean_reservoir_trajectories(prodrisk, prodrisk_second_run, new_head=False)
status = prodrisk_second_run.run()
my_area = prodrisk_second_run.model.area["my_area"]
expected_objective_val_kkr = my_area.expected_objective_value.get()
print(f"Expected objective value second run: {expected_objective_val_kkr} kkr")
my_area = prodrisk_second_run.model.area["my_area"]
#fcost = my_area.forward_cost.get()
#kcost = my_area.backward_cost.get()
#iteration_numbers = range(1, len(fcost)+1)
#cost_dict["F-cost, second session"] = pd.Series(data=fcost, index=iteration_numbers)
#cost_dict["K-cost, second session"] = pd.Series(data=kcost, index=iteration_numbers)
Expected objective value second run: 21009.885 kkr
Third session#
The third session is a copy of the first one, but the head coefficients and mean reservoir trajectories are set equal to those created by the second main iteration of the first session. This would correspond to running with option -NEWHEAD in “manual” Prodrisk runs.
This run should thus not give identical results as the first and second session, as new head coefficients are set as input, so only one main iteration is run.
prodrisk_third_run = ProdriskSession(license_path='/prodrisk', solver_path='/pyprodrisk', silent=False, log_file='')
build_model(prodrisk_third_run)
prodrisk_third_run.command_line_option = ""
#set_all_head_coeffs_and_mean_reservoir_trajectories(prodrisk, prodrisk_third_run, new_head=True)
status = prodrisk_third_run.run()
my_area = prodrisk_second_run.model.area["my_area"]
expected_objective_val_kkr = my_area.expected_objective_value.get()
print(f"Expected objective value second run: {expected_objective_val_kkr} kkr")
my_area = prodrisk_third_run.model.area["my_area"]
#fcost = my_area.forward_cost.get()
#kcost = my_area.backward_cost.get()
#iteration_numbers = range(1, len(fcost)+1)
#cost_dict["F-cost, third session"] = pd.Series(data=fcost, index=iteration_numbers)
#cost_dict["K-cost, third session"] = pd.Series(data=kcost, index=iteration_numbers)
Expected objective value second run: 21009.885 kkr
Plot F/K-costs from the different runs.#
#df = pd.DataFrame(cost_dict)
#fig = px.line(df, labels={
# "index": "Iteration number",
# "value": "Cost"
# })
#fig.show()
Example of use with head dependent maximal discharge#
This example illustrate how the attribute headDependentQmax may be used to reduce the maximal discharge capacity from a module based on the reservoir head.
Note that the head dependency of the discharge capacity is only active in the forward iterations in Prodrisk, where the maximal discharge for a given week is set based on the head at the beginning of the week. The strategy (cuts) is therefore only affected by this functionality by the fact that they are calculated based on simulated reservoir volumes based on the head dependent discharge.
Using the command line interface, this input is given as option 23 when setting up a module using the program med.exe.
import pandas as pd
import numpy as np
import plotly.express as px
import ltm_results as ltm_res
from pyprodrisk import ProdriskSession
from head_dependent_qmax import build_model
Build model#
prodrisk = ProdriskSession(license_path='/prodrisk', solver_path='/pyprodrisk', silent=False, log_file='')
build_model(prodrisk)
Add head dependent maximal discharge#
# Make a list of at most 10 xy points, describing the curve qmax_head(hrel), where hrel = head/nominal_head
# The first point MUST be (0.0, 0.0)
mod = prodrisk.model.module["ModuleA"]
qmax = mod.maxDischargeConst.get()
hrel = [0.0, 600.0/700.0, 900.0/800.0, 1000.0/700.0]
qmax_head = [0.0, 0.0, 0.8*qmax, qmax]
mod.headDependentQMax.set(pd.Series(name=0.0, index=hrel, data=qmax_head))
Plot this relation between maximal discharge and head#
ltm_res.plot_xy(mod.headDependentQMax.get(), x_axis="Relative head: (head/nominal head)", y_axis="Discharge capacity [m3/s]", plot_title="Maximal discharge as function of head")
Maximal discharge as function of head:
Run Prodrisk and inspect the results#
# --- run prodrisk session ---
status = prodrisk.run()
my_area = prodrisk.model.area["my_area"]
expected_objective_val_kkr = my_area.expected_objective_value.get()
print(f"Expected objective value: {expected_objective_val_kkr} kkr")
rsv_vols = mod.reservoirVolume.get()
ltm_res.plot_percentiles(rsv_vols, "Volume [Mm3]", "", percentiles_limits=[0, 25, 50, 75, 100])
Expected objective value: 20436.436 kkr
Files#
head_coefficients.py#
%pycat head_coefficients.py
head_dependent_qmax.py#
%pycat head_dependent_qmax.py