Optimal management of a river system which includes water resources,#

riversys.py

"""
## LICENSETYPE: Demo
## MODELTYPE: NLP


Optimal management of a river system which includes water resources,
reservoirs, consumers and an estuary.

October 6, 2005

Adapted from:
McKinney, D.C. and Savitsky, A.G., "Basic optimization models
for water and energy management", Revision 6, February 2003.
"""

from __future__ import annotations

import os

import gamspy.math as gams_math
import pandas as pd
from gamspy import (
    Alias,
    Container,
    Equation,
    Model,
    Ord,
    Parameter,
    Set,
    Sum,
    Variable,
)


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # Source records table
    cols = [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    ]
    inds = ["Source_1", "Source_2"]
    data = [
        [98, 115, 244, 390, 641, 754, 807, 512, 367, 210, 181, 128],
        [29, 49, 78, 121, 198, 144, 105, 98, 79, 72, 45, 29],
    ]
    Source_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # Demand records table
    cols = [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    ]
    inds = ["User_1", "User_2", "Outlet"]
    data = [
        [0.0, 0.0, 10, 64.5, 189.8, 184.4, 243.7, 200.9, 99.5, 0.0, 0.0, 0.0],
        [0.0, 0.0, 10, 13.5, 15.0, 22.1, 26.0, 24.9, 13.0, 0.0, 0.0, 0.0],
        [
            500,
            500,
            500,
            100.0,
            100.0,
            100.0,
            100.0,
            500.0,
            500.0,
            500,
            500,
            500,
        ],
    ]
    Demand_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return Source_recs, Demand_recs


def main():
    m = Container(
        system_directory=os.getenv("SYSTEM_DIRECTORY", None),
    )

    # SETS #
    n = Set(
        m,
        name="n",
        records=[
            "Source_1",
            "Source_2",
            *[f"Node_{n}" for n in range(1, 6)],
            "User_1",
            "User_2",
            "Res_1",
            "Res_2",
            "Outlet",
        ],
        description="nodes",
    )
    nn = Set(
        m,
        name="nn",
        domain=n,
        records=[f"Node_{n}" for n in range(1, 6)],
        description="Nodes",
    )
    ns = Set(
        m,
        name="ns",
        domain=n,
        records=["Source_1", "Source_2"],
        description="Sources nodes",
    )
    nr = Set(
        m,
        name="nr",
        domain=n,
        records=["User_1", "User_2", "Outlet"],
        description="Users nodes",
    )
    nl = Set(
        m,
        name="nl",
        domain=n,
        records=["Res_1", "Res_2"],
        description="Reservoir nodes",
    )
    n_from_n = Set(
        m,
        name="n_from_n",
        domain=[n, n],
        records=[
            ("Res_1", "Source_1"),
            ("Res_2", "Source_2"),
            ("Node_1", "Res_1"),
            ("Node_1", "Res_2"),
            ("Node_2", "Node_1"),
            ("User_1", "Node_2"),
            ("Node_3", "Node_2"),
            ("Node_3", "User_1"),
            ("Node_4", "Node_3"),
            ("User_2", "Node_4"),
            ("Node_5", "Node_4"),
            ("Node_5", "User_2"),
            ("outlet", "Node_5"),
        ],
        description="Topology of the network",
    )
    n_to_nr = Set(
        m,
        name="n_to_nr",
        domain=[n, n],
        records=[
            ("Node_2", "User_1"),
            ("Node_4", "User_2"),
            ("Node_5", "Outlet"),
        ],
        description="Topology of the network",
    )
    t = Set(
        m,
        name="t",
        records=[
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "May",
            "Jun",
            "Jul",
            "Aug",
            "Sep",
            "Oct",
            "Nov",
            "Dec",
        ],
        description="months",
    )

    # ALIAS #
    n1 = Alias(m, name="n1", alias_with=n)

    # PARAMETERS #
    Ini_S = Parameter(
        m,
        name="Ini_S",
        domain=n,
        records=[("Res_1", 1000), ("Res_2", 300)],
        description="Initial storage in reservoirs. (m3)",
    )
    ret = Parameter(
        m,
        name="ret",
        domain=n,
        records=[("User_1", 0.5), ("User_2", 0.5), ("Outlet", 0.0)],
        description="Return flow coefficients",
    )
    Source = Parameter(
        m,
        name="Source",
        domain=[n, t],
        records=data_records()[0],
        description="Flow of water (m3 per sec)",
    )
    Demand = Parameter(
        m,
        name="Demand",
        domain=[n, t],
        records=data_records()[1],
        description="Water demands (m3 per sec)",
    )

    # VARIABLES #
    U = Variable(
        m,
        name="U",
        type="positive",
        domain=[n, t],
        description="Diversions of water from node n in period t (m3)",
    )
    q = Variable(
        m,
        name="q",
        type="positive",
        domain=[n, t],
        description="Inflows in nodde n in period t (m3)",
    )
    r = Variable(
        m,
        name="r",
        type="positive",
        domain=[n, t],
        description="Releases from node n in period t (m3)",
    )
    s = Variable(
        m,
        name="s",
        type="positive",
        domain=[n, t],
        description="Storages of water in node n in period t (m3)",
    )

    # Upper bounds on users.
    U.up[n, t] = Demand[n, t]

    # Upper bounds on reservoirs.
    s.up["Res_1", t] = 1000
    s.up["Res_2", t] = 300

    # Lower storage in reservoirs (December).
    s.lo["Res_1", "Dec"] = 1000
    s.lo["Res_2", "Dec"] = 300

    # EQUATIONS #
    R_no = Equation(
        m, name="R_no", type="regular", domain=[n, t], description="Node"
    )
    R_ns = Equation(
        m, name="R_ns", type="regular", domain=[n, t], description="Source"
    )
    R_nr = Equation(
        m,
        name="R_nr",
        type="regular",
        domain=[n, t],
        description="Irrigation node",
    )
    R_nl = Equation(
        m,
        name="R_nl",
        type="regular",
        domain=[n, t],
        description="Reservoirs node",
    )
    R_nn = Equation(
        m, name="R_nn", type="regular", domain=[n, t], description="Node"
    )

    R_no[n, t].where[nn[n]] = r[n, t] == q[n, t]

    R_ns[n, t].where[ns[n]] = r[n, t] == Source[n, t]

    R_nr[n, t].where[nr[n]] = r[n, t] == ret[n] * U[n, t]

    R_nl[n, t].where[nl[n]] = (
        s[n, t]
        == Ini_S[n].where[Ord(t) == 1]
        + s[n, t.lag(1)].where[Ord(t) >= 1]
        + q[n, t]
        - r[n, t]
    )

    R_nn[n, t] = q[n, t] == Sum(n1.where[n_from_n[n, n1]], r[n1, t]) - Sum(
        n1.where[n_to_nr[n, n1]], U[n1, t]
    )

    # Objective Function
    Objective = Sum(
        t, Sum(n.where[nr[n]], gams_math.power((U[n, t] - Demand[n, t]), 2))
    )

    riversys = Model(
        m,
        name="riversys",
        equations=m.getEquations(),
        problem="nlp",
        sense="min",
        objective=Objective,
    )

    riversys.solve()

    print("Objective Function Value:  ", round(riversys.objective_value, 4))

    # Show the solution

    rep = Parameter(m, name="rep", domain=[t, "*"])

    rep[t, "User_1"] = U.l["User_1", t]
    rep[t, "Demand_1"] = Demand["User_1", t]
    rep[t, "User_2"] = U.l["User_2", t]
    rep[t, "Demand_2"] = Demand["User_2", t]
    rep[t, "Outlet"] = U.l["Outlet", t]
    rep[t, "Demand"] = Demand["Outlet", t]

    print("Solution:\n", rep.pivot().round(3))

    # End riversys


if __name__ == "__main__":
    main()