"""
Aircraft Allocation under uncertain Demand (AIRCRAF)

The objective of this model is to allocate aircrafts to routes to maximize
the expected profit when traffic demand is uncertain. Two different
formulations are used, the delta and the lambda formulation.


Dantzig, G B, Chapter 28. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

Keywords: linear programming, aircraft managing, allocation problem
"""
from __future__ import annotations

import os

import numpy as np

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


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

    # Sets
    i = Set(m, name="i", records=["a", "b", "c", "d"])
    j = Set(m, name="j", records=[f"route-{i}" for i in range(1, 6)])
    h = Set(m, name="h", records=list(range(1, 6)))

    # Alias
    hp = Alias(m, name="hp", alias_with=h)

    # Parameters
    dd = Parameter(
        m,
        name="dd",
        domain=[j, h],
        records=np.array(
            [
                [200, 220, 250, 270, 300],
                [50, 150, 0, 0, 0],
                [140, 160, 180, 200, 220],
                [10, 50, 80, 100, 340],
                [580, 600, 620, 0, 0],
            ]
        ),
    )

    lamda = Parameter(
        m,
        name="lamda",
        domain=[j, h],
        records=np.array(
            [
                [0.2, 0.05, 0.35, 0.2, 0.2],
                [0.3, 0.7, 0, 0, 0],
                [0.1, 0.2, 0.4, 0.2, 0.1],
                [0.2, 0.2, 0.3, 0.2, 0.1],
                [0.1, 0.8, 0.1, 0, 0],
            ]
        ),
    )

    c = Parameter(
        m,
        name="c",
        domain=[i, j],
        records=np.array(
            [
                [18, 21, 18, 16, 10],
                [0, 15, 16, 14, 9],
                [0, 10, 0, 9, 6],
                [17, 16, 17, 15, 10],
            ]
        ),
    )

    p = Parameter(
        m,
        name="p",
        domain=[i, j],
        records=np.array(
            [
                [16, 15, 28, 23, 81],
                [0, 10, 14, 15, 57],
                [0, 5, 0, 7, 29],
                [9, 11, 22, 17, 55],
            ]
        ),
    )

    aa = Parameter(
        m, name="aa", domain=[i], records=np.array([10, 19, 25, 15])
    )
    k = Parameter(m, name="k", domain=[j], records=np.array([13, 13, 7, 7, 1]))
    ed = Parameter(m, name="ed", domain=[j])
    gamma = Parameter(m, name="gamma", domain=[j, h])
    deltb = Parameter(m, name="deltb", domain=[j, h])

    ed[j][...] = Sum(h, lamda[j, h] * dd[j, h])
    gamma[j, h] = Sum(hp.where[(Ord(hp) >= Ord(h))], lamda[j, hp])
    deltb[j, h] = dd[j, h] - dd[j, h.lag(1, "linear")].where[dd[j, h]]

    # Variables
    x = Variable(m, name="x", domain=[i, j], type="positive")
    y = Variable(m, name="y", domain=[j, h], type="positive")
    b = Variable(m, name="b", domain=[j, h], type="positive")
    oc = Variable(m, name="oc", type="positive")
    bc = Variable(m, name="bc", type="positive")
    phi = Variable(m, name="phi")

    # Equations
    ab = Equation(m, name="ab", domain=[i])
    db = Equation(m, name="db", domain=[j])
    yd = Equation(m, name="yd", domain=[j, h])
    bd = Equation(m, name="bd", domain=[j, h])
    ocd = Equation(m, name="ocd")
    bcd1 = Equation(m, name="bcd1")
    bcd2 = Equation(m, name="bcd2")
    obj = Equation(m, name="obj")

    ab[i] = Sum(j, x[i, j]) <= aa[i]
    db[j] = Sum(i, p[i, j] * x[i, j]) >= Sum(h.where[deltb[j, h]], y[j, h])
    yd[j, h] = y[j, h] <= Sum(i, p[i, j] * x[i, j])
    bd[j, h] = b[j, h] == dd[j, h] - y[j, h]
    ocd[...] = oc == Sum([i, j], c[i, j] * x[i, j])
    bcd1[...] = bc == Sum(j, k[j] * (ed[j] - Sum(h, gamma[j, h] * y[j, h])))
    bcd2[...] = bc == Sum([j, h], k[j] * lamda[j, h] * b[j, h])
    obj[...] = phi == oc + bc

    alloc1 = Model(
        m,
        name="alloc1",
        equations=[ab, db, ocd, bcd1, obj],
        problem="LP",
        sense=Sense.MIN,
        objective=phi,
    )
    alloc2 = Model(
        m,
        name="alloc2",
        equations=[ab, yd, bd, ocd, bcd2, obj],
        problem="LP",
        sense=Sense.MIN,
        objective=phi,
    )

    y.up[j, h] = deltb[j, h]
    alloc1.solve()

    print("Number of passengers carried with limit:")
    print(y.pivot())

    y.up[j, h] = np.inf

    alloc2.solve()
    print()
    print("Number of passengers carried without limit:")
    print(y.pivot())

    import math

    assert math.isclose(alloc2.objective_value, 1566.042189, rel_tol=0.001)


if __name__ == "__main__":
    main()
