# Hydrothermal Scheduling Problem with Conic Programming¶

Description: Hydrothermal Scheduling Problem using Second-Order Cones

Tags: amplpy, conic, second-order cone, quadratic cone, nonlinear programming, scheduling, engineering, power generation, geothermal energy, hydropower

Notebook author: Gleb Belov <gleb@ampl.com>

References: Wood, A J, and Wollenberg, B F, Example Problem 7b. In Power Generation, Operation and Control. John Wiley and Sons, 1984, p. 202.

```# Install dependencies
%pip install -q amplpy pandas
```
```# Google Colab & Kaggle integration
from amplpy import AMPL, ampl_notebook

ampl = ampl_notebook(
modules=["coin", "mosek"],  # modules to install
)  # instantiate AMPL object and register magics
```

Hydrothermal scheduling problem involves allocating the total power demand and losses among the hydro and thermal generators in a least-cost way. The scheduling period is typically a few days long. The hydraulic flow constraints and the limits on generator outputs have to be observed in the scheduling problem.

## AMPL model¶

```%%writefile hydrothermal.mod

param nperiods;        ## Number of periods (12 hours long)

set TT := 0..nperiods;
set T  := 1..nperiods;

param load {T};        ## load (MW) for the t-th period
param losscof;         ## loss coeff for hydro generation / 0.00008 /
param nhours           ## number of hours in each period  / 12      /
default 12;

param vLU {1..2};      ## storage volume range
param thermalLU {1..2};## steam output range
param hydroUB;         ## hydro output upper bound

var cost_sqr{T}>=0;    ## quadratic term of the costs
var thermal{T}         ## output from the steam thermal plant (MW)
>=thermalLU[1], <=thermalLU[2];
var hydro{T}           ## output from the hydro plant         (MW)
>=0, <=hydroUB;
var loss{T}    >=0;    ## total loss                          (MW)
var q{TT}      >=0;    ## hydro flow rate in acre-ft per hour
var v{TT}              ## reservoir storage volume at the end of t
>=vLU[1], <=vLU[2];

minimize Cost:
1.15*nhours*nperiods * sum {t in T} (500 + 8*thermal[t] + cost_sqr[t]);

s.t. CostPerPeriodSqr {t in T}:  ## Extract quadratic terms of the objective
cost_sqr[t] >= 0.0016*(thermal[t] ** 2); ## into a second-order cone

s.t. Loss {t in T}:    ## loss calculated as function of hydro output
loss[t] >= losscof*(hydro[t] ** 2);      ## another cone

s.t. Demand {t in T}:  ## demand plus loss must be met from hydro and thermal
thermal[t] + hydro[t] == load[t] + loss[t];

s.t. Flow {t in T}:    ## hydraulic continuity equation
v[t] == v[t-1] + (2000 - q[t]) * nhours;

s.t. Dischar {t in T}: ## calculation of hydro discharge
q[t] == 330 +4.97*hydro[t];
```
```Overwriting hydrothermal.mod
```

## Load data directly from Python data structures using amplpy¶

```m = AMPL()

m.param["nperiods"] = 6

m.param["load"] = [1200, 1500, 1100, 1800, 950, 1300]
m.param["losscof"] = 0.00008

m.param["vLU"] = [60e3, 120e3]
m.param["thermalLU"] = [150, 1500]
m.param["hydroUB"] = 1000

m.eval("fix v[0] := 100e3;")  ## initial storage volume
```

## Solve with Ipopt¶

```m.option["solver"] = "ipopt"
m.get_output("solve;")
m.obj["Cost"].value()
```
```4366944.151328332
```

## Retrieve solution as a pandas dataframe¶

```m.var["thermal"].to_pandas().T
```
1 2 3 4 5 6
thermal.val 839.196498 955.803132 801.935007 1079.933203 734.194 864.004863
```m.var["hydro"].to_pandas().T
```
1 2 3 4 5 6
hydro.val 371.866263 570.207825 305.533028 767.148093 219.666261 452.365932

## Solve with Mosek as a Conic Program¶

```m.option["solver"] = "mosek"
# m.option["mosek_options"] = "outlev=1"
m.solve()
```
```MOSEK 10.0.43: MOSEK 10.0.43: optimal; objective 4366944.122
0 simplex iterations
17 barrier iterations
```

## Solve with Mosek as a Quadratic Program (QCP)¶

```m.option["solver"] = "mosek"
m.option["mosek_options"] = "cvt:socp=0"
m.solve()
```
```MOSEK 10.0.43: cvt:socp=0
MOSEK 10.0.43: optimal; objective 4366944.115
0 simplex iterations
17 barrier iterations
```