Homework 1

Yutong Dai yutongd3@illinois.edu

## 1.4

### a
Denote
$r_{11}=|x_1-3|, r_{12}=|x_1|, r_{13}=|x_1+2|, r_{14}=|x_1-1|$, $r_{21}=|x_2-1|, r_{22}=|x_2+3|, r_{23}=|x_2-2|, r_{24}=|x_2-4|$.

Note that $|r|=r^+ + r^-$, where $r^+ =\max\{x, 0\}, r^-=\max\{-x, 0\}$.

Then the problem can be formulated as

$$
\begin{align*}
    & \min \sum_{i=1}^2\sum_{j=1}^4 r_{ij}^{+} + r^{-}_{ij} \\
    & s.t. \\
    & r_{ij}^{+}, r^{-}_{ij}\geq 0, \quad i=1,2; j=1,2,3,4
 \end{align*}
$$

### b

$$
\begin{align*}
     \min\quad  & 6(r_{11}^{+} + r^{-}_{11} + r_{21}^{+} + r^{-}_{21}) + 4(r_{12}^{+} + r^{-}_{12} + r_{22}^{+} + r^{-}_{22})\quad + \\
            & 7(r_{13}^{+} + r^{-}_{13} + r_{23}^{+} + r^{-}_{23}) + 2(r_{14}^{+} + r^{-}_{14} + r_{24}^{+} + r^{-}_{24})\quad  \\
    & s.t. \\
    & r_{ij}^{+}, r^{-}_{ij}\geq 0, \quad i=1,2; j=1,2,3,4
 \end{align*}
$$

### c

Note that $r = r^+ - r^-$, then the problem can be formulated as

$$
\begin{align*}
    & \min\sum_{i=1}^2\sum_{j=1}^4 r_{ij}^{+} + r^{-}_{ij}\\
    & s.t. \\
    & -1 \leq r_{1j}^{+}+ r^{-}_{1j}\leq 2,\quad  j=1,2,3,4 \\
    & 0 \leq r_{2j}^{+}+ r^{-}_{2j}\leq 1,\quad  j=1,2,3,4\\
    & r_{ij}^{+}, r^{-}_{ij}\geq 0, \quad i=1,2; j=1,2,3,4
 \end{align*}
$$

### d

$$
\begin{align*}
    & \min \sum_{i=1}^2\sum_{j=1}^4 r_{ij}^{+} + r^{-}_{ij} \\
    & s.t. \\
    & r_{11}^{+} + r^{-}_{11} + r_{21}^{+} + r^{-}_{21} \leq 2\\
    & r_{11}^{+} - r^{-}_{11} + 3 = r_{12}^{+} - r^{-}_{12} = r_{13}^{+} + r^{-}_{13} - 2 = r_{14}^{+} + r^{-}_{14} + 1\\
    & r_{21}^{+} - r^{-}_{21} + 1 = r_{22}^{+} - r^{-}_{22} - 3 = r_{23}^{+} - r^{-}_{23} + 2  = r_{24}^{+} - r^{-}_{24} + 4\\
    & r_{ij}^{+}, r^{-}_{ij}\geq 0, \quad i=1,2; j=1,2,3,4
 \end{align*}
$$

## 1.6

**Assumptions**

* The surplus of week $i$ only accounts for newly produced cheese.
* The cheese produced at the beginning of the week $i$ is regarded as equivalent to those produced at the end of the week $i$ in terms of the time, which means they can be used to fulfill the following one week's demand.

**Model**

| Notation | Explaination |
| :--- | :--- |
|$i$|week index; $i=1,..,8$|
|$j$|job type; $j=1,2,3$ represents producing Swiss cheese, sharp cheese and training new employees respectively.|
|$k$|type of cheese; $k=1,2$ represents Swiss cheese and sharp cheese respectively.|
| $x_{ij}$ | the number of people doing task $j$ on the week $i$ |
|$y_{i}$| the number of people trained on the week $i$|
|$r_{ik}$|the surplus of type $k$  cheese on the week $i$|

**Problem Formulation:**
$$
\begin{align*}
    & \min 40 ( \sum_{i=1}^8 \sum_{j=1}^3 x_{ij} + \sum_{i=1}^6 y_{i}) \\
    & s.t. \\
    & x_{11} + x_{12} + x_{13} = 65 \\
    & 400x_{11} - r_{11} = 11000 ; 240x_{12}  - r_{12} = 8000 \\
    & x_{21} + x_{22} + x_{23} = 65\\
    & 400x_{21} + r_{11} - r_{21} = 12000 ; 240x_{22} + r_{12} - r_{22} = 8000 \\
    & x_{31} + x_{32} + x_{33} = 65 + \sum_{i=1}^1y_{i} ; y_{1} \leq 3x_{13} \\
    & 400x_{31} + r_{21} - r_{31} = 13000  ; 240x_{32} + r_{22} - r_{32}= 10000  \\
    & x_{41} + x_{42} + x_{43} = 65 + \sum_{i=1}^2y_{i} ;  y_{2} \leq 3x_{23}\\
    & 400x_{41} + r_{31}  - r_{41}= 18000 ; 240x_{42} + r_{32} - r_{42}= 8000  \\
    & x_{51} + x_{52} + x_{53} = 65 + \sum_{i=1}^3y_{i} ; y_{3} \leq 3x_{33}\\
    & 400x_{51} + r_{41} - r_{51}= 14000  ; 240x_{52} + r_{42} - r_{52}= 12000 \\
&  x_{61} + x_{62} + x_{63} = 65 + \sum_{i=1}^4y_{i} ; y_{4} \leq 3x_{43} \\
& 400x_{61} + r_{51} - r_{61}= 18000  ; 240x_{62} + r_{52}- r_{62} = 13000 \\
& x_{71} + x_{72} + x_{73} = 65 + ;  y_{5} \leq 3x_{53}\\
& 400x_{71} + r_{61}- r_{71} = 20000  ; 240x_{72} + r_{62}- r_{72}
 = 12000 \\
 &  x_{81} + x_{82} + x_{83} = 65 + \sum_{i=1}^6y_{i}; y_{6} \leq 3x_{63}\\
 & 400x_{81} + r_{71} \geq 20000  ; 240x_{82} + r_{72} \geq 12000  \\
 & \sum_{i=1}^6y_{i} = 35\\
 & x_{ij}, y_i \in Z_{+}
 \end{align*}
$$

Based on the solution given by the following code, there's no viable solution.

```python
from pulp import *
x_1 = ["x_{},1".format(i) for i in range(1,9)]
x_2 = ["x_{},2".format(i) for i in range(1,9)]
x_3 = ["x_{},3".format(i) for i in range(1,9)]
y = ["y_{}".format(i) for i in range(1,7)]
r_1 = ["r_{},1".format(i) for i in range(1,9)]
r_2 = ["r_{},2".format(i) for i in range(1,9)]
var = [x_1, x_2, x_3, y, r_1, r_2]; var = [item for sublist in var for item in sublist]
```

```python
prob = LpProblem("The Cheese and Worker Problem",LpMinimize)
Lp_vars = LpVariable.dicts("Lp",var,0, upBound=None, cat="Integer")
prob += lpSum([40 * Lp_vars[i] for i in var if "r" not in i]), "Total Cost"
counter = 0
for i in range(len(x_1)):
    counter += 1
    if counter <= 2:
        prob += lpSum([Lp_vars[name] for name in [x_1[i], x_2[i], x_3[i]]]) == int(65)

    else:
        prob += lpSum([Lp_vars[name] for name in [x_1[i], x_2[i], x_3[i]]]) == int(65) + lpSum([Lp_vars["y_{}".format(j)] for j in range(1,i)])

for i in range(1,7):
    prob += Lp_vars["y_{}".format(i)] <= 3 * Lp_vars["x_{},3".format(i)]

prob += 400 * Lp_vars["x_1,1"] - Lp_vars["r_1,1"] == 11000
prob += 400 * Lp_vars["x_2,1"] + Lp_vars["r_1,1"] - Lp_vars["r_2,1"]== 12000
prob += 400 * Lp_vars["x_3,1"] + Lp_vars["r_2,1"] - Lp_vars["r_3,1"]== 13000
prob += 400 * Lp_vars["x_4,1"] + Lp_vars["r_3,1"] - Lp_vars["r_4,1"]== 18000
prob += 400 * Lp_vars["x_5,1"] + Lp_vars["r_4,1"] - Lp_vars["r_5,1"]== 14000
prob += 400 * Lp_vars["x_6,1"] + Lp_vars["r_5,1"] - Lp_vars["r_6,1"]== 18000
prob += 400 * Lp_vars["x_7,1"] + Lp_vars["r_6,1"] - Lp_vars["r_7,1"]== 20000

prob += 240 * Lp_vars["x_1,2"] - Lp_vars["r_1,2"] == 8000
prob += 240 * Lp_vars["x_2,2"] + Lp_vars["r_1,2"] - Lp_vars["r_2,2"]== 8000
prob += 240 * Lp_vars["x_3,2"] + Lp_vars["r_2,2"] - Lp_vars["r_3,2"]== 10000
prob += 240 * Lp_vars["x_4,2"] + Lp_vars["r_3,2"] - Lp_vars["r_4,2"]== 8000
prob += 240 * Lp_vars["x_5,2"] + Lp_vars["r_4,2"] - Lp_vars["r_5,2"]== 12000
prob += 240 * Lp_vars["x_6,2"] + Lp_vars["r_5,2"] - Lp_vars["r_6,2"]== 13000
prob += 240 * Lp_vars["x_7,2"] + Lp_vars["r_6,2"] - Lp_vars["r_7,2"]== 12000

prob += 400 * Lp_vars["x_8,1"] + Lp_vars["r_7,1"] >= 20000
prob += 240 * Lp_vars["x_8,2"] + Lp_vars["r_7,2"] >= 12000
prob += lpSum([Lp_vars["y_{}".format(i)] for i in range(1,7)]) == 35
prob
prob.writeLP("Production_Inventory_Problem.lp")
prob.solve()
print("Status:", LpStatus[prob.status])
```

    Status: Undefined

```python
import re
r1 = {}; r2 = {}; x1 = {}; x2 = {}; x3 = {}
for v in prob.variables():
    if not (re.match(re.compile('Lp_r_.,1'), v.name) is None) > 0:
        r1[v.name] = v.varValue
    if not (re.match(re.compile('Lp_r_.,2'), v.name) is None) > 0:
        r2[v.name] = v.varValue
    if not (re.match(re.compile('Lp_x_.,1'), v.name) is None) > 0:
        x1[v.name] = v.varValue
    if not (re.match(re.compile('Lp_x_.,2'), v.name) is None) > 0:
        x2[v.name] = v.varValue
    if not (re.match(re.compile('Lp_x_.,3'), v.name) is None) > 0:
        x3[v.name] = v.varValue
```

```python
x1, x2, x3
```

    ({'Lp_x_1,1': 27.5,
      'Lp_x_2,1': 30.0,
      'Lp_x_3,1': 35.595238,
      'Lp_x_4,1': 43.690476,
      'Lp_x_5,1': 33.214286,
      'Lp_x_6,1': 45.0,
      'Lp_x_7,1': 50.0,
      'Lp_x_8,1': 50.0},
     {'Lp_x_1,2': 33.333333,
      'Lp_x_2,2': 33.333333,
      'Lp_x_3,2': 41.666667,
      'Lp_x_4,2': 33.333333,
      'Lp_x_5,2': 50.0,
      'Lp_x_6,2': 54.52381,
      'Lp_x_7,2': 49.642857,
      'Lp_x_8,2': 50.0},
     {'Lp_x_1,3': 4.1666667,
      'Lp_x_2,3': 1.6666667,
      'Lp_x_3,3': 0.23809524,
      'Lp_x_4,3': 5.4761905,
      'Lp_x_5,3': 0.0,
      'Lp_x_6,3': 0.11904762,
      'Lp_x_7,3': 0.0,
      'Lp_x_8,3': 0.0})

```python
r1, r2
```

    ({'Lp_r_1,1': 0.0,
      'Lp_r_2,1': 0.0,
      'Lp_r_3,1': 1238.0952,
      'Lp_r_4,1': 714.28571,
      'Lp_r_5,1': 0.0,
      'Lp_r_6,1': 0.0,
      'Lp_r_7,1': 0.0},
     {'Lp_r_1,2': 0.0,
      'Lp_r_2,2': 0.0,
      'Lp_r_3,2': 0.0,
      'Lp_r_4,2': 0.0,
      'Lp_r_5,2': 0.0,
      'Lp_r_6,2': 85.714286,
      'Lp_r_7,2': 0.0})

## 1.7

**Model**

| Notation | Explaination |
| :--- | :--- |
|$i$|month index; $i=1,..,12$|
|$j$|item type; $j=1,2$ |
| $x_{ij}$ | the number of item $j$ produced on the month $i$ |
|$y_{ij}$| the number of item $j$ stored on the month $i$|

**Problem Formulation:**

$$
\begin{align*}
    & \min \sum_{i=1}^6 (5x_{i1} +8.5 x_{i2}) + \sum_{i=7}^{12} (4.5x_{ij} +  7x_{i2}) + \sum_{i=1}^{12} (0.4 y_{i1} + 0.8y_{i2})\\
    & s.t. \\
    & x_{i1}- y_{i1}= 10^4, i=1,..,4 \\
    & x_{i1}- y_{i1} = 3*10^4, i=5,..,9 \\
    & x_{i1}- y_{i1}= 10*10^4, i=10,..,12 \\
    & x_{i2}- y_{i2} = 5*10^4, i=1,2,10,11,12\\
    & x_{i2}- y_{i2} = 1.5*10^4, i=3,...,9 \\
    & x_{i1} + x_{i2} \leq 12*10^4, i=1,..,9 \\
    & x_{i1} + x_{i2} \leq 15*10^4, i=10,..,12 \\
    & 2 * y_{i1} + 4 * y_{i2} \leq 15 * 10^4, i=1,...,12\\
    & x_{ij},y_{ij}\geq 0
 \end{align*}
$$

```python
from pulp import *
x_1 = ["x_{},1".format(i) for i in range(1,13)]
x_2 = ["x_{},2".format(i) for i in range(1,13)]
y_1 = ["y_{},1".format(i) for i in range(1,13)]
y_2 = ["y_{},2".format(i) for i in range(1,13)]
var = [x_1, x_2, y_1, y_2]; var = [item for sublist in var for item in sublist]
costs = {}
counter = 0
for i in x_1:
    counter += 1
    if counter <= 5:
        costs[i] = 5
    else:
        costs[i] = 4.5
counter = 0
for i in x_2:
    counter += 1
    if counter <= 5:
        costs[i] = 8.5
    else:
        costs[i] = 7
for i in y_1:
    costs[i] = 0.4
for i in y_2:
    costs[i] = 0.8
prob = LpProblem("The Production and Inventory Problem",LpMinimize)
Lp_vars = LpVariable.dicts("Lp",var,0, upBound=None, cat="Integer")
prob += lpSum([costs[i]*Lp_vars[i] for i in var]), "Total Cost"
counter = 0
for i, j in zip(x_1, y_1):
    counter += 1
    if counter <= 4:
        prob += Lp_vars[i] - Lp_vars[j] == int(1e4)
    elif counter <= 9:
        prob += Lp_vars[i] - Lp_vars[j] == 3 * int(1e4)
    else:
        prob += Lp_vars[i] - Lp_vars[j] == 10 * int(1e4)
counter = 0
for i, j in zip(x_2, y_2):
    counter += 1
    if counter in [1,2,10,11,12]:
        prob += Lp_vars[i] - Lp_vars[j] == 5 * int(1e4)
    else:
        prob += Lp_vars[i]  - Lp_vars[j] == 1.5 * int(1e4)
for i in range(1,13):
    if i <= 9:
        prob += Lp_vars["x_{},1".format(i)] + Lp_vars["x_{},2".format(i)] <= 12 * int(1e4)
    else:
        prob += Lp_vars["x_{},1".format(i)] + Lp_vars["x_{},2".format(i)] <= 15 * int(1e4)
for i in range(1,13):
    prob += 2 * Lp_vars["y_{},1".format(i)] + 4 * Lp_vars["y_{},2".format(i)] <= 15 * int(1e4)
prob.writeLP("Production_Inventory_Problem.lp")
prob.solve()
print("Status:", LpStatus[prob.status])
```

    Status: Optimal

```python
import re
x1= {}; x2={}; y1 = {}; y2 = {}
for v in prob.variables():
    if not (re.match(re.compile('Lp_x_.,1'), v.name) is None) > 0:
        x1[v.name] = v.varValue
    if not (re.match(re.compile('Lp_x_.,2'), v.name) is None) > 0:
        x2[v.name] = v.varValue
    if not (re.match(re.compile('Lp_y_.,1'), v.name) is None) > 0:
        y1[v.name] = v.varValue
    if not (re.match(re.compile('Lp_y_.,2'), v.name) is None) > 0:
            y2[v.name] = v.varValue
```

```python
x1,x2
```

    ({'Lp_x_1,1': 10000.0,
      'Lp_x_2,1': 10000.0,
      'Lp_x_3,1': 10000.0,
      'Lp_x_4,1': 10000.0,
      'Lp_x_5,1': 30000.0,
      'Lp_x_6,1': 30000.0,
      'Lp_x_7,1': 30000.0,
      'Lp_x_8,1': 30000.0,
      'Lp_x_9,1': 30000.0},
     {'Lp_x_1,2': 50000.0,
      'Lp_x_2,2': 50000.0,
      'Lp_x_3,2': 15000.0,
      'Lp_x_4,2': 15000.0,
      'Lp_x_5,2': 15000.0,
      'Lp_x_6,2': 15000.0,
      'Lp_x_7,2': 15000.0,
      'Lp_x_8,2': 15000.0,
      'Lp_x_9,2': 15000.0})

```python
y1, y2
```

    ({'Lp_y_1,1': 0.0,
      'Lp_y_2,1': 0.0,
      'Lp_y_3,1': 0.0,
      'Lp_y_4,1': 0.0,
      'Lp_y_5,1': 0.0,
      'Lp_y_6,1': 0.0,
      'Lp_y_7,1': 0.0,
      'Lp_y_8,1': 0.0,
      'Lp_y_9,1': 0.0},
     {'Lp_y_1,2': 0.0,
      'Lp_y_2,2': 0.0,
      'Lp_y_3,2': 0.0,
      'Lp_y_4,2': 0.0,
      'Lp_y_5,2': 0.0,
      'Lp_y_6,2': 0.0,
      'Lp_y_7,2': 0.0,
      'Lp_y_8,2': 0.0,
      'Lp_y_9,2': 0.0})

## 1.26

### a
**Model**

| Notation | Explaination |
| :--- | :--- |
|$i$|bidder; $i=1,..,m$|
|$j$|depot type; $j=1,2,...,n$ |
|$x_{ij}$ | the gallon of fuels transport from the bidder $i$ to the depot $j$ |
|$c_{ij}$| the cost per gallon of from the bidder $i$ to the depot $j$|

**Problem Formulation:**

$$
\begin{align*}
    & \min \sum_{i=1}^n\sum_{j=1}^m c_{ij}x_{ij} \\
    & s.t. \\
    & \sum_{j=1}^m x_{ij}\leq a_i, i=1,..,m\\
    & \sum_{i=1}^n x_{ij}\geq b_j, i=1,..,m\\
    & x_{ij} \geq 0
 \end{align*}
$$
### b

Actually, there are two different ways to interpret the so-called discount.

**(I)** Once the $x_{ij}\geq \alpha_i$, then unit price discount applies to all $x_{ij}$.
**(II)** If the $x_{ij}\geq \alpha_i$, then unit price discount applies only to  $x_{ij} - \alpha_i$ parts.

According to the TA, the interpretation **(II)** is chosen here.

we need introduce a sets of binary variables $z_{ij}$, where
$$
z_{ij} =
\begin{cases}
1, & x_{ij} \geq \alpha_i \\
0, & o.w.
\end{cases}.
$$

Also we introduce another sets of variable $d_{i}$, the discount amount for bidder $i$ if the demand exceeds $\alpha_i$. Then the problem is formulated as

$$
\begin{align*}
    & \min \sum_{i=1}^n\sum_{j=1}^m (c_{ij}- d_jz_{ij})x_{ij} \\
    & s.t. \\
    & \sum_{j=1}^m x_{ij}\leq a_i, i=1,..,m\\
    & \sum_{i=1}^n x_{ij}\geq b_j, i=1,..,m\\
    & x_{ij}\leq M{z_{ij}} \\
    & M >0; z_{ij} \in \{0,1\}
    & x_{ij} \geq 0
 \end{align*}
$$

Of course this is not a linear programming problem.

## 1.28

The problem can be formulated as

$$
\begin{align*}
    & \max_{t,Q} \sum_{i=1}^t w_iH_i + (Q - \sum_{i=1}^t)H_{t+1} - Z \\
    & s.t. \\
    & Q \geq \sum_{i=1}^t w_i \\
    & Q \leq \sum_{i=1}^{t+1} w_i \\
    & Z \leq cy \\
    & Ay = b\\
    & \alpha_ u = Q\\
    & y \geq 0
 \end{align*}
$$

## 1.30
### a
The problem is equivalent to

$$
\begin{align*}
    & \min x_1 -2x_2 - 3x_3\\
    & s.t. \\
    & -x_1 + 3x_2 + x_3 + s_1 = 13 \\
    & x_1 + 2x_2 + 3x_3 - s_2 = 12 \\
    & 2x_1 - x_2 + x_3 = 4 \\
    & x_1 = y_1 - y'_1\\
    & x_2 = y_2 - y'_2 \\
    & x_3 = -3-s_3 \\
    & s_1,s_2,s_3 \geq 0 \\
    & y_i, y'_i \geq 0, \quad i=1,2
 \end{align*}
$$

And we plug the expressions of  $x_1,x_2,x_3$ into the problem above, then the standard form is given below.

$$
\begin{align*}
    & \min (y_1 - y'_1) -2(y_2 - y'_2) - 3(-3-s_3)\\
    & s.t. \\
    & -(y_1 - y'_1) + 3(y_2 - y'_2) + (-3-s_3) + s_1 = 13 \\
    & (y_1 - y'_1) + 2(y_2 - y'_2) + 3(-3-s_3) - s_2 = 12 \\
    & 2(y_1 - y'_1) - (y_2 - y'_2) + (-3-s_3) = 4 \\
    & s_1,s_2,s_3 \geq 0 \\
    & y_i, y'_i \geq 0, \quad i=1,2
 \end{align*}
$$

### b
The problem is equivalent to

$$
\begin{align*}
    & \min x_1 -2x_2 - 3x_3\\
    & s.t. \\
    & x_1 - 3x_2 - x_3 \geq 13 \\
    & x_1 + 2x_2 + 3x_3 \geq = 12 \\
    & 2x_1 - x_2 + x_3  \geq 4 \\
    & -(2x_1 - x_2 + x_3) \geq -4 \\
    & x_1 \geq y_1 - y'_1\\
    & -x_1 \geq -(y_1 - y'_1)\\
    & x_2 \geq y_2 - y'_2 \\
    & -x_2 \geq -(y_2 - y'_2) \\
    & -x_3 \geq 3 \\
    & s_1,s_2,s_3 \geq 0 \\
    & y_i, y'_i \geq 0, \quad i=1,2
 \end{align*}
$$

And we plug the expressions of  $x_1,x_2,x_3$ into the problem above, then the standard form is given below.

$$
\begin{align*}
    & \min (y_1 - y'_1) -2(y_2 - y'_2) - 3(-3-s_3)\\
    & s.t. \\
    & (y_1 - y'_1) - 3(y_2 - y'_2) - (-3-s_3)  \geq 13 \\
    & x_1 + 2x_2 + 3x_3 \geq 12 \\
    & 2(y_1 - y'_1) - (y_2 - y'_2) + (-3-s_3)  \geq 4 \\
    & -(2(y_1 - y'_1) - (y_2 - y'_2) + (-3-s_3)) \geq -4 \\
    & -x_3 \geq 3 \\
    & s_1,s_2,s_3 \geq 0 \\
    & y_i, y'_i \geq 0, \quad i=1,2
 \end{align*}
$$

### c

The problem is equivalent to
$$
\begin{align*}
    & \max -x_1 + 2x_2 + 3x_3\\
    & s.t. \\
    & -x_1 + 3x_2 + x_3 \leq 13 \\
    & x_1 + 2x_2 + 3x_3 \geq 12 \\
    & 2x_1 - x_2 + x_3 = 4 \\
    & x_1 = y_1 - y'_1\\
    & x_2 = y_2 - y'_2 \\
    & x_3 \leq -3 \\
    & y_i, y'_i \geq 0, \quad i=1,2
 \end{align*}
$$

And we plug the expressions of  $x_1,x_2,x_3$ into the problem above, then the maximization form is given below.

$$
\begin{align*}
    & \max -(y_1 - y'_1) + 2(y_2 - y'_2) + 3x_3\\
    & s.t. \\
    & -(y_1 - y'_1) + 3(y_2 - y'_2) + x_3 \leq 13 \\
    & (y_1 - y'_1) + 2(y_2 - y'_2) + 3x_3 \geq 12 \\
    & 2(y_1 - y'_1) - (y_2 - y'_2) + x_3 = 4 \\
    & x_3 \leq -3 \\
    & y_i, y'_i \geq 0, \quad i=1,2
 \end{align*}
$$

## 1.37

### a
As shown in the graph below, the red line indicates the optimal solution set with the natural $x_1, x_2 \geq 0$ restrictions added. Mathematically, the  optimal solution set is:

$$S=\{(x_1, x_2); x_1+2x_2=4, x_1, x_2 \geq 0\}$$

```python
import numpy as np
import matplotlib.pyplot as plt
from sympy.solvers import solve
from sympy import Symbol

def f1(x):
    return 1 + 0.5 * x
def f2(x):
    return 2 * x
def f3(x):
    return -0.5 * x + 2

x = Symbol('x')
x1, =  solve(f1(x)-f2(x))
x2, =  solve(f1(x)-f3(x))
x3 = 4
x4 =0
y1 = f1(x1)
y2 = f1(x2)
y3 = 0
y4 =0

plt.plot(x1,y1,'go',markersize=10)
plt.plot(x2,y2,'go',markersize=10)
plt.plot(x3,y3,'go',markersize=10)
plt.plot(x4,y4,'go',markersize=10)

plt.fill([x1,x2,x3,x4],[y1,y2,y3,y4],'green',alpha=0.5)

xr = np.linspace(0,4,100)
y1r = f1(xr)
y2r = f2(xr)
y3r = f3(xr)
y4r = 0 * xr
plt.plot(xr,y1r,'k-',label=r"$x_2=1+0.5x_1$")
plt.plot(xr,y2r,'b--', label=r"$x_2=2x_1$")
plt.plot(xr,y4r,'k-')
plt.plot(xr,y3r,'r*-', label=r"$x_2=2-0.5x_1$")
plt.legend()
#pplt.plot(xr,-0.5*xr + 0.8,'c--')

plt.show()
```

### b
It's straightforward to see from the following graph that, the maximum is attained at $(0,2)$.

```python
plt.figure()
xr = np.linspace(0,4,100)
y3r = f3(xr)
plt.plot(xr,y3r,'k-',label=r"$x_2=2-0.5x_1$")
plt.plot(xr, 3*xr+2,'r-')
#plt.plot(xr, 3*xr,'c--')
plt.plot(0,2,'ko',markersize=10)
plt.plot(4,0,'ko',markersize=10)
plt.xlim(-1,5)
plt.legend()
plt.show()
```

### c
From the graph below, we know the optimal solution for problem
$$
\max -3x_1 + x_2
$$
over the same feasible set, is unique. That is,
$(0, 0) = \arg\max_{x_1,x_2} -3x_1 + x_2$

Since the feasible set for problem
$$
\max 3x_1 + 6x_2 \quad (**)
$$
only contains 1 element. So $\bf (0, 0)$ is also the solution for (**)

```python
plt.figure()
plt.fill([x1,x2,x3,x4],[y1,y2,y3,y4],'green',alpha=0.5)

xr = np.linspace(0,4,100)
y1r = f1(xr)
y2r = f2(xr)
y3r = f3(xr)
y4r = 0 * xr
plt.plot(xr,y1r,'k-',label=r"$x_2=1+0.5x_1$")
plt.plot(xr,y2r,'b--', label=r"$x_2=2x_1$")
plt.plot(xr,y4r,'k-')
plt.plot(xr,y3r,'m-.', label=r"$x_2=2-0.5x_1$")
plt.legend()
#plt.plot(xr, 3*xr - 5,'c--')
plt.plot(xr, 3*xr,'r-')
plt.show()
```

## (BT) 1.5
### a

**Formulation 1:**
$$
\begin{align*}
    & \min \quad c^Tx + d^Ty \\
    & s.t. \\
    & Ax + By \leq b\\
    & x_i \leq y_i \\
    & - x_i \leq y_i
 \end{align*}
$$

**Formulation 2:**
Note that $|x| = x^{+} + x^{-}$ and $x = x^{+} - x^{-}$, where the operation is element-wise, we have

$$
\begin{align*}
    & \min \quad c^T(x^{+} - x^{-}) + d^T(x^{+} + x^{-}) \\
    & s.t. \\
    & A(x^{+} - x^{-}) + B(x^{+} + x^{-}) \leq b\\
    & x^{+}  \geq 0 \\
    & x^{-} \geq 0
 \end{align*}
$$

### b
Since both **Formulation 1**  and **Formulation 2** are one-to-one transformations, so if we can obtain a solution from any of three formulation, the rest two can be uniquely determined. If there is no feasible solution in any of the three formulation, the rest two are not solvable. Therefore, three formulations are equivalent.

I use two simple example to illustrate the statement above.

1. Suppose for the **Formulation 2**, the optimal solution is
$(x^{+}, x^{-}) = (a,b)$, then

    * For the original formulation, $x = a - b$;
    * For the **Formulation 2**, $x=a - b, y = |a-b|$, where $|.|$ is a element-wise operation.

2. Suppose for the **Formulation 1**, the optimal solution is
$(x, y) = (a,|a|)$, then

    * For the original formulation, $x = a$;
    * For the **Formulation 2**, the optimal solution is $(x^{+}, x^{-}) = \big(0.5(|a| + a),0.5(|a| - a)\big)$.

### c
Consider the following example,

$$
\begin{align*}
    & \min x \\
    & s.t. \\
    & -0.5x - y\leq -3\\
    & y = |x|
 \end{align*}
$$
The feasible set for this problem is $\{x| x\leq -1.5, x\geq 2\}$. The local minimal attains at $x=2$, while the global minimal attains at $x=-\infty$.

 ## (BT) 1.11

**Assumptions**

* All the currency conversions can be done at given time point, say $k$. Throughout the day, conversions only take place $K$ times in total.

**Model**

| Notation | Explaination |
| :--- | :--- |
|$i,j$|currency index; $i,j = 1,2....n$|
|$k$|time point; $k=0,1,...,K$|
|$x_{ik}$|the amount of currency $i$ held at time point $k$|
|$t_{ijk}$|the amount of currency $i$ converted to currency $j$ at time point $k$|

**Note:** By definition, $t_{jik}$ represents the amount of currency $j$ converted to currency $i$ at time point $k$

**Problem Formulation:**
$$
\begin{align*}
    & \max \quad x_{NK} \\
    & s.t. \\
    & x_{j(k+1)} = \sum_{i=1}^Nr_{ij}t_{ijk} + x_{jk}\\
    & x_{j(k+1)} = x_{jk} - \sum_{i=1}^Nr_{ij}t_{jik} \\
    & t_{ijk} \geq 0, \quad k=1,2,...,K-1\\
    & t_{ijk} \leq x_{ik}\\
    & \sum_{j}^N\sum_{k=1}^K t_{jik} \leq u_j\\
    & x_{10} = B\\
    & x_{j0} = 0, \quad j=2,...,N
 \end{align*}
$$

## Assortment Optimization

Define the $w_i$ as the probability of buying product $i$. Especially, $w_0$ denotes buying nothing. Also denote $c$ as the number of items in the assortment. Then the object function is

$$
\begin{align*}
    & \max\sum_{i=1}^4r_iw_i \\
    & s.t. \\
    &w_0 + \sum_{i=1}w_i = 1\\
    &0 \leq w_i \leq \frac{v_i}{v_0}w_0, \quad i=1,..,4\\
    & \sum_{i}^4 \frac{w_i}{v_i} \leq c \frac{w_0}{v_0}
 \end{align*}
$$

From the code given below, the best assortment for $c=1$ is **(item1, item2, item3, item4)** while for $c=2,3,4$ is **(item1, item2, item3)**

```python
from pulp import *
w = ["w_{}".format(i) for i in range(5)]
var = w
revenue = {"w_0":0, "w_1":9.5, "w_2":9.0, "w_3":7.0, "w_4":4.5}
counter = 0
prob = LpProblem("The Assortment Problem",LpMaximize)
Lp_vars = LpVariable.dicts("Lp",var,0, upBound=None)
prob += lpSum([revenue[i]*Lp_vars[i] for i in var])
prob += lpSum(Lp_vars[i] for i in var) == 1
prob += Lp_vars["w_1"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_2"] <= 0.6 * Lp_vars["w_0"]
prob += Lp_vars["w_3"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_4"] <= 5.2 * Lp_vars["w_0"]
prob += (1 / 0.3) * Lp_vars["w_1"]  + (1 / 0.6) * Lp_vars["w_2"]  + (1 / 0.3) * Lp_vars["w_3"]  + (1/ 5.2) * Lp_vars["w_4"] <= 1
prob.writeLP("The_Assortment_Problem.lp")
prob.solve()
print("Status:", LpStatus[prob.status])
for v in prob.variables():
    print(v.name, "=", v.varValue)
```

    Status: Optimal
    Lp_w_0 = 0.31343284
    Lp_w_1 = 0.094029851
    Lp_w_2 = 0.1880597
    Lp_w_3 = 0.094029851
    Lp_w_4 = 0.31044776

```python
from pulp import *
w = ["w_{}".format(i) for i in range(5)]
var = w
revenue = {"w_0":0, "w_1":9.5, "w_2":9.0, "w_3":7.0, "w_4":4.5}
counter = 0
prob = LpProblem("The Assortment Problem",LpMaximize)
Lp_vars = LpVariable.dicts("Lp",var,0, upBound=None)
prob += lpSum([revenue[i]*Lp_vars[i] for i in var])
prob += lpSum(Lp_vars[i] for i in var) == 1
prob += Lp_vars["w_1"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_2"] <= 0.6 * Lp_vars["w_0"]
prob += Lp_vars["w_3"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_4"] <= 5.2 * Lp_vars["w_0"]
prob += (1 / 0.3) * Lp_vars["w_1"]  + (1 / 0.6) * Lp_vars["w_2"]  + (1 / 0.3) * Lp_vars["w_3"]  + (1/ 5.2) * Lp_vars["w_4"] <= 2
prob.writeLP("The_Assortment_Problem.lp")
prob.solve()
print("Status:", LpStatus[prob.status])
for v in prob.variables():
    print(v.name, "=", v.varValue)
```

    Status: Optimal
    Lp_w_0 = 0.45454545
    Lp_w_1 = 0.13636364
    Lp_w_2 = 0.27272727
    Lp_w_3 = 0.13636364
    Lp_w_4 = 0.0

```python
from pulp import *
w = ["w_{}".format(i) for i in range(5)]
var = w
revenue = {"w_0":0, "w_1":9.5, "w_2":9.0, "w_3":7.0, "w_4":4.5}
counter = 0
prob = LpProblem("The Assortment Problem",LpMaximize)
Lp_vars = LpVariable.dicts("Lp",var,0, upBound=None)
prob += lpSum([revenue[i]*Lp_vars[i] for i in var])
prob += lpSum(Lp_vars[i] for i in var) == 1
prob += Lp_vars["w_1"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_2"] <= 0.6 * Lp_vars["w_0"]
prob += Lp_vars["w_3"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_4"] <= 5.2 * Lp_vars["w_0"]
prob += (1 / 0.3) * Lp_vars["w_1"]  + (1 / 0.6) * Lp_vars["w_2"]  + (1 / 0.3) * Lp_vars["w_3"]  + (1/ 5.2) * Lp_vars["w_4"] <= 3
prob.writeLP("The_Assortment_Problem.lp")
prob.solve()
print("Status:", LpStatus[prob.status])
for v in prob.variables():
    print(v.name, "=", v.varValue)
```

    Status: Optimal
    Lp_w_0 = 0.45454545
    Lp_w_1 = 0.13636364
    Lp_w_2 = 0.27272727
    Lp_w_3 = 0.13636364
    Lp_w_4 = 0.0

```python
from pulp import *
w = ["w_{}".format(i) for i in range(5)]
var = w
revenue = {"w_0":0, "w_1":9.5, "w_2":9.0, "w_3":7.0, "w_4":4.5}
counter = 0
prob = LpProblem("The Assortment Problem",LpMaximize)
Lp_vars = LpVariable.dicts("Lp",var,0, upBound=None)
prob += lpSum([revenue[i]*Lp_vars[i] for i in var])
prob += lpSum(Lp_vars[i] for i in var) == 1
prob += Lp_vars["w_1"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_2"] <= 0.6 * Lp_vars["w_0"]
prob += Lp_vars["w_3"] <= 0.3 * Lp_vars["w_0"]
prob += Lp_vars["w_4"] <= 5.2 * Lp_vars["w_0"]
prob += (1 / 0.3) * Lp_vars["w_1"]  + (1 / 0.6) * Lp_vars["w_2"]  + (1 / 0.3) * Lp_vars["w_3"]  + (1/ 5.2) * Lp_vars["w_4"] <= 4
prob.writeLP("The_Assortment_Problem.lp")
prob.solve()
print("Status:", LpStatus[prob.status])
for v in prob.variables():
    print(v.name, "=", v.varValue)
```

    Status: Optimal
    Lp_w_0 = 0.45454545
    Lp_w_1 = 0.13636364
    Lp_w_2 = 0.27272727
    Lp_w_3 = 0.13636364
    Lp_w_4 = 0.0

## (Two-Person Zero-Sum Game)
The problem can be formulated as

$$
\begin{align*}
    & (I) \max \sum_{i=1}^3\sum_{j=1}^3 a_{ij}x_{i}y^*_{j}\qquad  (II) \max -\sum_{i=1}^3\sum_{j=1}^3 a_{ij}x^*_{i}y_{j}\\
    & s.t.  \qquad\qquad\qquad\qquad s.t\\
    &\sum_{i=1}^3 x_i = 1  \qquad\qquad\qquad \sum_{j=1}^3 y_j = 1 \\
    & x_i \geq 0, i=1,2,3\qquad\qquad y_j \geq 0, j=1,2,3
 \end{align*}
$$

Given $x^*=y^*= (1/3, 1/3, 1/3)^T$ and corresponding $a_{ij}$, the problems above become

$$
\begin{align*}
    & (I)\max 0\qquad (II) \max 0\\
    & s.t.  \qquad\qquad s.t\\
    &\sum_{i=1}^3 x_i = 1  \qquad\qquad\sum_{j=1}^3 y_j = 1 \\
    & x_i \geq 0, i=1,2,3\qquad y_j \geq 0, j=1,2,3
 \end{align*}
$$

Therefore $x^*$ is the optimal solution for (I) while $y^*$ is the optimal solution for (II).

So $x^*=y^*= (1/3, 1/3, 1/3)^T$ is an equilibrium.


## Linear Regression 의 cost 최소화의 TensorFlow 구현(new)

### Simplified hypothesis

\begin{align}
H(x) = Wx \\
\end{align}

\begin{align}
cost(W) = \frac{1}{m}\sum_{i=1}^m(Wx^{(i)} - y^{(i)})^2 \\
\end{align}

```python
import tensorflow as tf
import matplotlib.pyplot as plt

X = [1, 2, 3]
Y = [1, 2, 3]

W = tf.placeholder(tf.float32)

# Our hypothesis for linear model X * W
hypothesis = X * W

# cost/loss function
cost = tf.reduce_mean(tf.square(hypothesis - Y))

# Launch the graph in a session
sess = tf.Session()

# Variables for plotting cost function
W_history = []
cost_history = []

for i in range(-30, 50):
    curr_W = i * 0.1
    curr_cost = sess.run(cost, feed_dict={W: curr_W})
    W_history.append(curr_W)
    cost_history.append(curr_cost)

# Show the cost function
plt.plot(W_history, cost_history)
plt.show()
```

### Gradient descent

\begin{align}
W := W - a\frac{1}{m}\sum_{i=1}^m(Wx^{(i)} - y^{(i)})x^{(i)} \\
\end{align}

```python
import tensorflow as tf

x_data = [1, 2, 3]
y_data = [1, 2, 3]

W = tf.Variable(tf.random_normal([1]), name='weight')

X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)

# Our hypothesis for linear model X * W
hypothesis = X * W

# cost/loss function
cost = tf.reduce_mean(tf.square(hypothesis - Y))

# Minimize : Gradient Descent using derivative
# W -= learning_rate * derivative
learning_rate = 0.1
gradient = tf.reduce_mean((W * X - Y) * X)
descent = W - learning_rate * gradient
update = W.assign(descent)

# Launch the graph in a session.
sess = tf.Session()

# Initializes global variables in the graph.
sess.run(tf.global_variables_initializer())

for step in range(21):
    sess.run(update, feed_dict={X: x_data, Y: y_data})
    print(step, sess.run(cost, feed_dict={X: x_data, Y: y_data}), sess.run(W))
```

    0 12.285609 [-0.6225382]
    1 3.4945729 [0.13464624]
    2 0.99401206 [0.53847796]
    3 0.28274122 [0.75385493]
    4 0.08042419 [0.8687226]
    5 0.02287622 [0.9299854]
    6 0.0065070093 [0.9626589]
    7 0.0018508838 [0.9800847]
    8 0.00052647345 [0.9893785]
    9 0.00014975447 [0.9943352]
    10 4.2596832e-05 [0.99697876]
    11 1.2117175e-05 [0.99838865]
    12 3.4466948e-06 [0.9991406]
    13 9.802366e-07 [0.9995417]
    14 2.7886367e-07 [0.99975556]
    15 7.9282884e-08 [0.99986964]
    16 2.2523963e-08 [0.9999305]
    17 6.4054433e-09 [0.9999629]
    18 1.8274401e-09 [0.9999802]
    19 5.2067267e-10 [0.99998945]
    20 1.4516388e-10 [0.9999944]

### Output when W = 5
- **경사하강법**이 잘 되는지 테스트
- 오른쪽에서 하강

```python
import tensorflow as tf

# tf Graph Input
X = [1, 2, 3]
Y = [1, 2, 3]

# Set wrong model weights
W = tf.Variable(5.0)

# Linear model
hypothesis = X * W

# cost/Loss function
cost = tf.reduce_mean(tf.square(hypothesis - Y))

# Minimize : Gradient Descent Magic
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1)
train = optimizer.minimize(cost)

# Launch the graph in a session
sess = tf.Session()

# Initializes global variables int he graph
sess.run(tf.global_variables_initializer())

for step in range(100):
    print(step, sess.run(W))
    sess.run(train)
```

    0 5.0
    1 1.2666664
    2 1.0177778
    3 1.0011852
    4 1.000079
    5 1.0000052
    6 1.0000004
    7 1.0
    8 1.0
    9 1.0
    10 1.0
    11 1.0
    12 1.0
    13 1.0
    14 1.0
    15 1.0
    16 1.0
    17 1.0
    18 1.0
    19 1.0
    20 1.0
    21 1.0
    22 1.0
    23 1.0
    24 1.0
    25 1.0
    26 1.0
    27 1.0
    28 1.0
    29 1.0
    30 1.0
    31 1.0
    32 1.0
    33 1.0
    34 1.0
    35 1.0
    36 1.0
    37 1.0
    38 1.0
    39 1.0
    40 1.0
    41 1.0
    42 1.0
    43 1.0
    44 1.0
    45 1.0
    46 1.0
    47 1.0
    48 1.0
    49 1.0
    50 1.0
    51 1.0
    52 1.0
    53 1.0
    54 1.0
    55 1.0
    56 1.0
    57 1.0
    58 1.0
    59 1.0
    60 1.0
    61 1.0
    62 1.0
    63 1.0
    64 1.0
    65 1.0
    66 1.0
    67 1.0
    68 1.0
    69 1.0
    70 1.0
    71 1.0
    72 1.0
    73 1.0
    74 1.0
    75 1.0
    76 1.0
    77 1.0
    78 1.0
    79 1.0
    80 1.0
    81 1.0
    82 1.0
    83 1.0
    84 1.0
    85 1.0
    86 1.0
    87 1.0
    88 1.0
    89 1.0
    90 1.0
    91 1.0
    92 1.0
    93 1.0
    94 1.0
    95 1.0
    96 1.0
    97 1.0
    98 1.0
    99 1.0

### Output when W = -3
- **경사하강법**이 잘 되는지 테스트
- 왼쪽에서 하강

```python
import tensorflow as tf

# tf Graph Input
X = [1, 2, 3]
Y = [1, 2, 3]

# Set wrong model weights
W = tf.Variable(-3.0)

# Linear model
hypothesis = X * W

# cost/Loss function
cost = tf.reduce_mean(tf.square(hypothesis - Y))

# Minimize : Gradient Descent Magic
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1)
train = optimizer.minimize(cost)

# Launch the graph in a session
sess = tf.Session()

# Initializes global variables int he graph
sess.run(tf.global_variables_initializer())

for step in range(100):
    print(step, sess.run(W))
    sess.run(train)
```

    0 -3.0
    1 0.7333336
    2 0.98222226
    3 0.9988148
    4 0.99992096
    5 0.9999947
    6 0.99999964
    7 0.99999994
    8 1.0
    9 1.0
    10 1.0
    11 1.0
    12 1.0
    13 1.0
    14 1.0
    15 1.0
    16 1.0
    17 1.0
    18 1.0
    19 1.0
    20 1.0
    21 1.0
    22 1.0
    23 1.0
    24 1.0
    25 1.0
    26 1.0
    27 1.0
    28 1.0
    29 1.0
    30 1.0
    31 1.0
    32 1.0
    33 1.0
    34 1.0
    35 1.0
    36 1.0
    37 1.0
    38 1.0
    39 1.0
    40 1.0
    41 1.0
    42 1.0
    43 1.0
    44 1.0
    45 1.0
    46 1.0
    47 1.0
    48 1.0
    49 1.0
    50 1.0
    51 1.0
    52 1.0
    53 1.0
    54 1.0
    55 1.0
    56 1.0
    57 1.0
    58 1.0
    59 1.0
    60 1.0
    61 1.0
    62 1.0
    63 1.0
    64 1.0
    65 1.0
    66 1.0
    67 1.0
    68 1.0
    69 1.0
    70 1.0
    71 1.0
    72 1.0
    73 1.0
    74 1.0
    75 1.0
    76 1.0
    77 1.0
    78 1.0
    79 1.0
    80 1.0
    81 1.0
    82 1.0
    83 1.0
    84 1.0
    85 1.0
    86 1.0
    87 1.0
    88 1.0
    89 1.0
    90 1.0
    91 1.0
    92 1.0
    93 1.0
    94 1.0
    95 1.0
    96 1.0
    97 1.0
    98 1.0
    99 1.0

### Optional : compute_gradient and apply_gradient
- Gradient를 수정하고 싶을 때 사용
- `compute_gradients()`함수를 사용해 `cost`에 알맞는 `gradient` 계산
- 계산한 `gradient`를 `apply_gradients()`함수 사용으로 `gradient` 적용

```python
import tensorflow as tf

# tf Graph Input
X = [1, 2, 3]
Y = [1, 2, 3]

# Set wrong model weights
W = tf.Variable(5.)

# Linear model
hypothesis = X * W

# Manual gradient
gradient = tf.reduce_mean((W * X - Y) * X) * 2

# cost/Loss function
cost = tf.reduce_mean(tf.square(hypothesis - Y))
# Minimize : Gradient Descent Magic
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1)

# Get gradients
gvs = optimizer.compute_gradients(cost, [W])
# Apply gradients
apply_gradients = optimizer.apply_gradients(gvs)

# Launch the graph in a session
sess = tf.Session()

# Initializes global variables int he graph
sess.run(tf.global_variables_initializer())

for step in range(100):
    print(step, sess.run([gradient, W, gvs]))
    sess.run(apply_gradients)
```

    0 [37.333332, 5.0, [(37.333336, 5.0)]]
    1 [2.4888866, 1.2666664, [(2.4888866, 1.2666664)]]
    2 [0.1659259, 1.0177778, [(0.1659259, 1.0177778)]]
    3 [0.011061668, 1.0011852, [(0.011061668, 1.0011852)]]
    4 [0.00073742867, 1.000079, [(0.00073742867, 1.000079)]]
    5 [4.895528e-05, 1.0000052, [(4.8955284e-05, 1.0000052)]]
    6 [3.0994415e-06, 1.0000004, [(3.0994415e-06, 1.0000004)]]
    7 [0.0, 1.0, [(0.0, 1.0)]]
    8 [0.0, 1.0, [(0.0, 1.0)]]
    9 [0.0, 1.0, [(0.0, 1.0)]]
    10 [0.0, 1.0, [(0.0, 1.0)]]
    11 [0.0, 1.0, [(0.0, 1.0)]]
    12 [0.0, 1.0, [(0.0, 1.0)]]
    13 [0.0, 1.0, [(0.0, 1.0)]]
    14 [0.0, 1.0, [(0.0, 1.0)]]
    15 [0.0, 1.0, [(0.0, 1.0)]]
    16 [0.0, 1.0, [(0.0, 1.0)]]
    17 [0.0, 1.0, [(0.0, 1.0)]]
    18 [0.0, 1.0, [(0.0, 1.0)]]
    19 [0.0, 1.0, [(0.0, 1.0)]]
    20 [0.0, 1.0, [(0.0, 1.0)]]
    21 [0.0, 1.0, [(0.0, 1.0)]]
    22 [0.0, 1.0, [(0.0, 1.0)]]
    23 [0.0, 1.0, [(0.0, 1.0)]]
    24 [0.0, 1.0, [(0.0, 1.0)]]
    25 [0.0, 1.0, [(0.0, 1.0)]]
    26 [0.0, 1.0, [(0.0, 1.0)]]
    27 [0.0, 1.0, [(0.0, 1.0)]]
    28 [0.0, 1.0, [(0.0, 1.0)]]
    29 [0.0, 1.0, [(0.0, 1.0)]]
    30 [0.0, 1.0, [(0.0, 1.0)]]
    31 [0.0, 1.0, [(0.0, 1.0)]]
    32 [0.0, 1.0, [(0.0, 1.0)]]
    33 [0.0, 1.0, [(0.0, 1.0)]]
    34 [0.0, 1.0, [(0.0, 1.0)]]
    35 [0.0, 1.0, [(0.0, 1.0)]]
    36 [0.0, 1.0, [(0.0, 1.0)]]
    37 [0.0, 1.0, [(0.0, 1.0)]]
    38 [0.0, 1.0, [(0.0, 1.0)]]
    39 [0.0, 1.0, [(0.0, 1.0)]]
    40 [0.0, 1.0, [(0.0, 1.0)]]
    41 [0.0, 1.0, [(0.0, 1.0)]]
    42 [0.0, 1.0, [(0.0, 1.0)]]
    43 [0.0, 1.0, [(0.0, 1.0)]]
    44 [0.0, 1.0, [(0.0, 1.0)]]
    45 [0.0, 1.0, [(0.0, 1.0)]]
    46 [0.0, 1.0, [(0.0, 1.0)]]
    47 [0.0, 1.0, [(0.0, 1.0)]]
    48 [0.0, 1.0, [(0.0, 1.0)]]
    49 [0.0, 1.0, [(0.0, 1.0)]]
    50 [0.0, 1.0, [(0.0, 1.0)]]
    51 [0.0, 1.0, [(0.0, 1.0)]]
    52 [0.0, 1.0, [(0.0, 1.0)]]
    53 [0.0, 1.0, [(0.0, 1.0)]]
    54 [0.0, 1.0, [(0.0, 1.0)]]
    55 [0.0, 1.0, [(0.0, 1.0)]]
    56 [0.0, 1.0, [(0.0, 1.0)]]
    57 [0.0, 1.0, [(0.0, 1.0)]]
    58 [0.0, 1.0, [(0.0, 1.0)]]
    59 [0.0, 1.0, [(0.0, 1.0)]]
    60 [0.0, 1.0, [(0.0, 1.0)]]
    61 [0.0, 1.0, [(0.0, 1.0)]]
    62 [0.0, 1.0, [(0.0, 1.0)]]
    63 [0.0, 1.0, [(0.0, 1.0)]]
    64 [0.0, 1.0, [(0.0, 1.0)]]
    65 [0.0, 1.0, [(0.0, 1.0)]]
    66 [0.0, 1.0, [(0.0, 1.0)]]
    67 [0.0, 1.0, [(0.0, 1.0)]]
    68 [0.0, 1.0, [(0.0, 1.0)]]
    69 [0.0, 1.0, [(0.0, 1.0)]]
    70 [0.0, 1.0, [(0.0, 1.0)]]
    71 [0.0, 1.0, [(0.0, 1.0)]]
    72 [0.0, 1.0, [(0.0, 1.0)]]
    73 [0.0, 1.0, [(0.0, 1.0)]]
    74 [0.0, 1.0, [(0.0, 1.0)]]
    75 [0.0, 1.0, [(0.0, 1.0)]]
    76 [0.0, 1.0, [(0.0, 1.0)]]
    77 [0.0, 1.0, [(0.0, 1.0)]]
    78 [0.0, 1.0, [(0.0, 1.0)]]
    79 [0.0, 1.0, [(0.0, 1.0)]]
    80 [0.0, 1.0, [(0.0, 1.0)]]
    81 [0.0, 1.0, [(0.0, 1.0)]]
    82 [0.0, 1.0, [(0.0, 1.0)]]
    83 [0.0, 1.0, [(0.0, 1.0)]]
    84 [0.0, 1.0, [(0.0, 1.0)]]
    85 [0.0, 1.0, [(0.0, 1.0)]]
    86 [0.0, 1.0, [(0.0, 1.0)]]
    87 [0.0, 1.0, [(0.0, 1.0)]]
    88 [0.0, 1.0, [(0.0, 1.0)]]
    89 [0.0, 1.0, [(0.0, 1.0)]]
    90 [0.0, 1.0, [(0.0, 1.0)]]
    91 [0.0, 1.0, [(0.0, 1.0)]]
    92 [0.0, 1.0, [(0.0, 1.0)]]
    93 [0.0, 1.0, [(0.0, 1.0)]]
    94 [0.0, 1.0, [(0.0, 1.0)]]
    95 [0.0, 1.0, [(0.0, 1.0)]]
    96 [0.0, 1.0, [(0.0, 1.0)]]
    97 [0.0, 1.0, [(0.0, 1.0)]]
    98 [0.0, 1.0, [(0.0, 1.0)]]
    99 [0.0, 1.0, [(0.0, 1.0)]]


We would like to use SymPy methods to analyze course prerequisites. For this, every course will be defined as a variable (Symbol) and the prerequisite strings will be converted into SymPy expressions.

```python
from sympy import *
from sympy.logic.boolalg import BooleanFunction

import json
import re
import time

chrono1 = time.perf_counter()

def parse_course_name(full_name):
    return "".join(full_name.split(" - ")[0].split(" "))

with open("CoursePrerequisites.json", "r") as f:
    courses = json.loads(f.read())

# https://stackoverflow.com/a/25244576
def strike(text):
    result = ''
    for c in text:
        result = result + c + '\u0336'
    return result
```

```python
fullnames_dict = {} # full course name -> course name
courses_dict = {} # course name -> course symbol
prereq_strings_dict = {} # course name -> prereq string

for (course_name, prerequisites) in courses.items():
    name = parse_course_name(course_name)
    fullnames_dict[course_name] = name
    courses_dict[name] = Symbol(name)
    prereq_strings_dict[name] = prerequisites
```

```python
# prereqs_dict: course name -> prerequisite expression or none
prereqs_dict = {}
# "CS515" -> 𝐴𝐶𝐶201∨𝑀𝐺𝑀𝑇202

for (name, prerequisites_string) in prereq_strings_dict.items():
    if not prerequisites_string:
        continue
    else:
        try:
            prereqs_dict[name] = eval(prerequisites_string, courses_dict)
        except NameError:
            # there are some required classes that are not in the symbols dictionary yet (were not in the catalog)
            # TODO: these should be marked in some way

            # find all course names
            for new_course_name in re.findall("\w+", prerequisites_string):
                # create a symbol for the course if doesnt exist
                if new_course_name not in courses_dict:
                    courses_dict[new_course_name] = Symbol(new_course_name)
        prereqs_dict[name] = eval(prerequisites_string, courses_dict)

```

```python
# check results
prereqs_dict
```

    {'ANTH214': CULT201,
     'ARA302': ARA301,
     'ARA303': ARA301,
     'ARA304': ARA303,
     'ARA502': ARA501,
     'ARA503': ARA502,
     'ARA504': ARA503,
     'BIO301': NS202,
     'BIO303': NS202,
     'BIO304': NS202,
     'BIO305': NS202,
     'BIO306': NS202,
     'BIO308': NS202,
     'CIP102': CIP101 | TDP101,
     'CIP201': CIP102 | TDP102,
     'CIP202': CIP201 | TDP201,
     'CIP301': CIP202 | TDP202,
     'CIP302': CIP301,
     'CS202': CS201,
     'CS204': CS201,
     'CS301': CS202 & MATH204,
     'CS305': CS202,
     'CS306': CS202,
     'CS307': CS202,
     'CS308': CS202,
     'CS401': CS303,
     'CS402': CS202 & CS305,
     'CS403': CS307,
     'CS404': CS201,
     'CS405': CS202,
     'CS406': CS301,
     'CS407': CS302,
     'CS408': CS307,
     'CS409': CS201,
     'CULT212': SPS101 & SPS102,
     'CULT401': CULT201,
     'ECON301': ECON204 & MATH306,
     'ECON310': ECON204,
     'ECON320': ECON204,
     'ECON330': ECON204,
     'ECON340': ECON202 & ECON204,
     'ECON341': ECON201 & ECON204,
     'ECON350': ECON202 & ECON204,
     'ECON391': ECON202,
     'ECON400': ECON202 & ECON204,
     'ECON401': ECON301,
     'ECON402': ECON202,
     'ECON404': ECON204,
     'ECON405': ECON201 & ECON204,
     'ECON412': ECON301 & ECON330,
     'ECON414': ECON202 & ECON301,
     'ECON420': ECON202 & ECON204,
     'ECON430': ECON202 & ECON204,
     'ECON440': ECON340,
     'ECON491': ECON204,
     'ECON492': ECON202 & ECON204,
     'ECON502': ECON501,
     'ECON504': ECON503,
     'ECON506': ECON505,
     'EL202': ENS203,
     'EL204': ENS203,
     'EL302': EL202,
     'EL303': EL202,
     'EL304': EL202 & EL204,
     'EL305': NS101,
     'EL306': EL303 & ENS201,
     'EL308': CS303,
     'EL401': CS303 & EL302,
     'EL402': EL401,
     'EL405': EL303,
     'EL407': EL204,
     'ENG102': ENG101,
     'ENS201': NS101 & (MATH101 | MATH102),
     'ENS202': MATH101 & MATH102 & NS101 & NS102,
     'ENS203': MATH101 & MATH102,
     'ENS204': MATH101 & MATH102 & NS101,
     'ENS205': MATH101 & MATH102 & NS102,
     'ENS206': MATH101 & MATH102 & MATH201,
     'ENS210': CS201 & MATH101,
     'ENS211': MATH101 & MATH102,
     'ENS301': NS101 & NS102,
     'ENS491': PROJ302,
     'GREK102': GREK101,
     'HART323': HUM202 & SPS101,
     'HIST191': TLL101,
     'HIST192': HIST191 & TLL102,
     'HIST316': HIST315,
     'HIST345': SPS101 & SPS102,
     'HIST363': SPS101 & SPS102,
     'HIST502': HIST501,
     'HIST511': HIST501 & HIST502,
     'HIST512': HIST501 & HIST502,
     'HIST601': TLL501 & TLL502,
     'LIT212': CULT201,
     'MAT202': ENS202,
     'MATH202': MATH101 & MATH102,
     'MATH203': MATH101 & MATH102,
     'MATH305': MATH102,
     'MATH306': MATH203,
     'MATH317': MATH102 & MATH201,
     'MATH322': MATH202,
     'ME303': ENS206,
     'ME307': MATH101 & MATH102 & NS101,
     'ME403': ENS206,
     'MS301': MATH201,
     'MS302': MATH203,
     'MS304': ENS208,
     'MS305': MATH306,
     'MS308': MS303,
     'MS309': ENS205,
     'MS401': MS304,
     'MS403': MATH306,
     'MS405': MATH203,
     'MS408': MATH306,
     'MS411': MS301,
     'MS412': MS303,
     'MS417': MS301,
     'MS418': MS309,
     'MS451': MATH306,
     'NS202': MATH101 & MATH102 & NS102,
     'NS203': MATH101 & MATH102 & NS101 & NS102,
     'NS205': MATH101 & MATH102 & NS102,
     'NS206': NS101,
     'NS207': MATH101 & MATH102 & NS102,
     'PERS502': PERS501,
     'PERS504': PERS503,
     'PHYS302': PHYS303,
     'PHYS303': MATH101 & MATH102 & NS101,
     'POLS301': SPS101 & SPS102,
     'POLS302': POLS301,
     'PROJ302': PROJ102,
     'SOC201': SPS101 & SPS102,
     'SPS102': SPS101,
     'TE202': ENS201,
     'TE302': ENS211,
     'TE303': ENS211 & MATH203,
     'TE304': TE303,
     'TE401': ENS201,
     'TE402': TE202,
     'TE404': CS201,
     'TE405': TE302,
     'TE407': CS201,
     'TE409': TE302,
     'TE410': MATH201 & MATH203,
     'TE412': TE304,
     'TLL101': HIST191,
     'TLL102': HIST192 & TLL101,
     'TLL302': TLL301,
     'TLL502': TLL501,
     'VA202': VA201,
     'VA204': VA203,
     'VA301': VA201 & VA202 & VA203 & VA204,
     'VA302': VA201 & VA202 & VA203 & VA204,
     'VA321': VA201 & VA202 & VA203 & VA204,
     'VA322': VA201 & VA202 & VA203 & VA204,
     'VA323': VA201 & VA202 & VA203 & VA204,
     'VA324': VA201 & VA202 & VA203 & VA204,
     'VA331': VA201 & VA202 & VA203 & VA204,
     'VA332': VA201 & VA202 & VA203 & VA204,
     'VA334': VA201 & VA202 & VA203 & VA204,
     'VA336': VA335,
     'VA401': VA201 & VA202 & VA203 & VA204,
     'VA402': VA201 & VA202 & VA203 & VA204,
     'VA431': VA201 & VA202 & VA203 & VA204}

```python
def is_it_cyclic(expr, search, hist=set()):
    ret = False
    if search in hist:
        return True

    # get sub-prerequisites for every course in the prerequisites
    for sub_course_symbol in expr.atoms():
        hist.add(sub_course_symbol)
        if isinstance(sub_course_symbol, Symbol):
            if search is sub_course_symbol:
                return True
            else:
                if sub_course_symbol.name in prereqs_dict:
                    ret = ret or is_it_cyclic(prereqs_dict[sub_course_symbol.name], sub_course_symbol)
    return ret
```

```python
is_it_cyclic(prereqs_dict["HIST192"], courses_dict["HIST192"])
```

    True

```python
# returns set of of (course >> prerequisites) for all needed prerequisites for a given course
# "set of prerequisite expressions of all courses a given course depends on in any way"
def get_all_prereqs(prerequisites, course=None, ret=set()):
    if not prerequisites:
        return ret

    if course:
        ret.add(course >> prerequisites)

    # get sub-prerequisites for every course in the prerequisites
    for sub_course_symbol in prerequisites.atoms():
        if isinstance(sub_course_symbol, Symbol) and sub_course_symbol.name in prereqs_dict:
            get_all_prereqs(prereqs_dict[sub_course_symbol.name], sub_course_symbol, ret)
    return ret
```

```python
# test recursion with a 2 level deep example
get_all_prereqs(prereqs_dict["HIST192"], courses_dict["HIST192"])
```

```python
# checks if a given expression is satisfiable with given assumptions
# givens should be a set of SymPy boolean expressions
def satisfiable_with_givens(expr, givens):
    return satisfiable(And(expr, *givens));
```

```python
# checks if an arbitrary prerequisite expression (eg. "course & course & course") implies another
# true if a >> b
def prereq_implies_prereq(a, b):
    # a does not imply b if it is possible to have a=true b=false
    # check if (a&!b) is satisfiable
    # if it is satisfiable, implies is false. if it is not satisfiable, implies is true.
    res = satisfiable_with_givens(a&~b, get_all_prereqs(a) | get_all_prereqs(b))

    return not bool(res)
```

```python
def a_is_redundant(func, a, b):
    if func is Or:
        return prereq_implies_prereq(a, b)
    elif func is And:
        return prereq_implies_prereq(b, a)
```

```python
# returns A simplified prerequisite expression
def simplify_prereqs(course_prerequisites, verbose=True):
    if not isinstance(course_prerequisites, BooleanFunction):
        return course_prerequisites;

    parts = course_prerequisites.args

    # simplify all components of this prereq string before simplifying the whole thing
    parts = [simplify_prereqs(component, verbose) for component in parts]
    if verbose:
        print("analysing:", course_prerequisites.args, [(p, type(p)) for p in parts])

    for i in range(len(parts)):
        A = parts[i]
        B = course_prerequisites.func(*parts[0:i], *parts[i+1:len(parts)])
        if verbose:
            print(course_prerequisites.func, A, ",", B)

        # check if A is needed for this prerequisite
        p = a_is_redundant(course_prerequisites.func, A, B)

        # if A is redundant
        if p:
            if verbose:
                print(strike("("+str(A)+")"), sep="", end=" ")
            # remove A from the prereq string by making it the identity for this operation
            parts[i] = course_prerequisites.func.identity
    return course_prerequisites.func(*parts)
```

```python
def check(course):
    print("checking", course+":\ncurrent prereq is:")
    print(prereqs_dict[course])
    print("processed prereq is:")
    print("\n"+str(simplify_prereqs(prereqs_dict[course])))
    print("\n")

check("CS406")
check("ECON400")
```

    checking CS406:
    current prereq is:
    CS301
    processed prereq is:

    CS301

    checking ECON400:
    current prereq is:
    ECON202 & ECON204
    processed prereq is:
    analysing: (ECON202, ECON204) [(ECON202, <class 'sympy.core.symbol.Symbol'>), (ECON204, <class 'sympy.core.symbol.Symbol'>)]
    And ECON202 , ECON204
    And ECON204 , ECON202

    ECON202 & ECON204

```python
# use our methods to analyze all courses that have prerequisites, then save the simplified prerequisites
changes = {}
new_prereqs_dict = {}
c = 0
chrono2 = time.perf_counter()

for (full_name, name) in fullnames_dict.items():
    print("doing", name)
    if name in prereqs_dict:
        prereqs = prereqs_dict[name]
        new_prereqs = simplify_prereqs(prereqs, False)
        new_prereqs_dict[full_name] = new_prereqs
        if not prereqs.equals(new_prereqs):
            changes[full_name] = {"old": str(prereqs), "new": str(new_prereqs)}
        c+=1

    else:
        new_prereqs_dict[full_name] = ""

end = time.perf_counter()
chrono1 = end - chrono1
chrono2 = end - chrono2
print("Analysis complete,", c, "prerequisite expressions in", chrono2, "seconds.")
print("Average of", chrono2/c, "seconds per expression.")
print("Totals:")
print("\tcourses:", len(new_prereqs_dict))
print("\tchanges:", len(changes))
print("\ttime:", chrono1, "seconds")

# save outputs
with open('updatedValues.json', 'w') as fp:
    json.dump({n: str(p) for (n, p) in new_prereqs_dict.items()}, fp)

with open('changelist.json', 'w') as fp:
    json.dump(changes, fp)
```

    doing ANTH214
    doing ANTH255
    doing ARA301
    doing ARA302
    doing ARA303
    doing ARA304
    doing ARA501
    doing ARA502
    doing ARA503
    doing ARA504
    doing BIO301
    doing BIO301L
    doing BIO303
    doing BIO303L
    doing BIO304
    doing BIO305
    doing BIO306
    doing BIO306L
    doing BIO308
    doing BIO308L
    doing BIO310
    doing BIO332
    doing BIO332R
    doing BIO401
    doing BIO403
    doing BIO404
    doing BIO405
    doing BIO406
    doing BIO407
    doing BIO409
    doing BIO410
    doing BIO432
    doing BIO432L
    doing BIO480
    doing BIO501
    doing BIO504
    doing BIO504L
    doing BIO511
    doing BIO512
    doing BIO532
    doing BIO541
    doing BIO543
    doing BIO543L
    doing BIO551
    doing BIO552
    doing BIO580
    doing BIO581
    doing BIO590
    doing BIO601
    doing BIO604
    doing BIO610
    doing BIO631
    doing BIO641
    doing BIO642
    doing BIO643
    doing BIO643L
    doing BIO680
    doing BIO681
    doing BIO790
    doing CHEM301
    doing CHEM301L
    doing CHEM302
    doing CHEM302L
    doing CIP101
    doing CIP102
    doing CIP201
    doing CIP202
    doing CIP301
    doing CIP302
    doing CONF601
    doing CONF602
    doing CONF604
    doing CONF608
    doing CONF611
    doing CONF612
    doing CONF623
    doing CONF624
    doing CONF631
    doing CONF636
    doing CONF641
    doing CONF690
    doing CONF693
    doing CONF697
    doing CONF699
    doing CS201
    doing CS201L
    doing CS201R
    doing CS202
    doing CS202R
    doing CS204
    doing CS204L
    doing CS301
    doing CS302
    doing CS302R
    doing CS303
    doing CS303L
    doing CS303R
    doing CS305
    doing CS306
    doing CS306R
    doing CS307
    doing CS307R
    doing CS308
    doing CS308L
    doing CS401
    doing CS402
    doing CS403
    doing CS404
    doing CS405
    doing CS405L
    doing CS406
    doing CS407
    doing CS407R
    doing CS408
    doing CS409
    doing CS480
    doing CS501
    doing CS503
    doing CS505
    doing CS507
    doing CS512
    doing CS513
    doing CS513R
    doing CS515
    doing CS518
    doing CS522
    doing CS523
    doing CS525
    doing CS531
    doing CS532
    doing CS533
    doing CS533L
    doing CS534
    doing CS538
    doing CS541
    doing CS543
    doing CS545
    doing CS551
    doing CS552
    doing CS580
    doing CS581
    doing CS601
    doing CS611
    doing CS680
    doing CS681
    doing CULT201
    doing CULT212
    doing CULT222
    doing CULT224
    doing CULT225
    doing CULT231
    doing CULT232
    doing CULT242
    doing CULT250
    doing CULT251
    doing CULT291
    doing CULT322
    doing CULT323
    doing CULT326
    doing CULT331
    doing CULT342
    doing CULT344
    doing CULT362
    doing CULT399
    doing CULT401
    doing CULT423
    doing CULT433
    doing CULT443
    doing CULT444
    doing CULT643
    doing ECON201
    doing ECON201R
    doing ECON202
    doing ECON202R
    doing ECON204
    doing ECON204R
    doing ECON301
    doing ECON301R
    doing ECON310
    doing ECON320
    doing ECON330
    doing ECON330R
    doing ECON340
    doing ECON341
    doing ECON350
    doing ECON391
    doing ECON391R
    doing ECON399
    doing ECON400
    doing ECON401
    doing ECON402
    doing ECON403
    doing ECON404
    doing ECON405
    doing ECON405R
    doing ECON412
    doing ECON414
    doing ECON420
    doing ECON430
    doing ECON440
    doing ECON491
    doing ECON492
    doing ECON501
    doing ECON501R
    doing ECON502
    doing ECON503
    doing ECON503R
    doing ECON504
    doing ECON505
    doing ECON505R
    doing ECON506
    doing ECON601
    doing ECON602
    doing ECON603
    doing ECON604
    doing ECON605
    doing ECON606
    doing ECON607
    doing ECON608
    doing ECON609
    doing ECON610
    doing ECON611
    doing ECON697
    doing ECON699
    doing EE520
    doing EE520R
    doing EE521
    doing EE522
    doing EE523
    doing EE524
    doing EE524L
    doing EE525
    doing EE526
    doing EE528
    doing EE528R
    doing EE530
    doing EE531
    doing EE532
    doing EE533
    doing EE534
    doing EE535
    doing EE536
    doing EE537
    doing EE539
    doing EE550
    doing EE551
    doing EE552
    doing EE553
    doing EE554
    doing EE555
    doing EE556
    doing EE557
    doing EE560
    doing EE561
    doing EE562
    doing EE563
    doing EE565
    doing EE566
    doing EE580
    doing EE581
    doing EE581L
    doing EE582
    doing EE583
    doing EE584
    doing EE585
    doing EE620
    doing EE622
    doing EE623
    doing EE624
    doing EE626
    doing EE630
    doing EE631
    doing EE632
    doing EE633
    doing EE634
    doing EE635
    doing EE636
    doing EE637
    doing EE638
    doing EE651
    doing EE652
    doing EE653
    doing EE654
    doing EE661
    doing EE662
    doing EE665
    doing EE680
    doing EE681
    doing EE682
    doing EE683
    doing EE684
    doing EE685
    doing EECS590
    doing EECS592
    doing EECS790
    doing EL202
    doing EL202L
    doing EL202R
    doing EL204
    doing EL204R
    doing EL302
    doing EL302L
    doing EL302R
    doing EL303
    doing EL303R
    doing EL304
    doing EL304R
    doing EL305
    doing EL306
    doing EL306R
    doing EL308
    doing EL308L
    doing EL308R
    doing EL401
    doing EL401L
    doing EL401R
    doing EL402
    doing EL402R
    doing EL404
    doing EL405
    doing EL405L
    doing EL407
    doing EL407R
    doing EL480
    doing ENG001
    doing ENG002
    doing ENG003
    doing ENG101
    doing ENG102
    doing ENS201
    doing ENS201R
    doing ENS202
    doing ENS202R
    doing ENS203
    doing ENS203L
    doing ENS203R
    doing ENS204
    doing ENS204R
    doing ENS205
    doing ENS205R
    doing ENS206
    doing ENS206L
    doing ENS206R
    doing ENS208
    doing ENS208R
    doing ENS210
    doing ENS210L
    doing ENS211
    doing ENS211R
    doing ENS301
    doing ENS491
    doing ENS491R
    doing ENS492
    doing ENS502
    doing ENS596
    doing FRE101
    doing FRE102
    doing FRE201
    doing FRE202
    doing FRE301
    doing FRE302
    doing GER101
    doing GER102
    doing GER201
    doing GER202
    doing GER301
    doing GER302
    doing GER401
    doing GER402
    doing GREK101
    doing GREK102
    doing GSM5001
    doing GSM5003
    doing GSM5004
    doing GSM501
    doing GSM5013
    doing GSM502
    doing GSM503
    doing GSM504
    doing GSM505
    doing GSM509
    doing GSM510
    doing GSM5108
    doing GSM511
    doing GSM512
    doing GSM513
    doing GSM514
    doing GSM5203
    doing GSM5207
    doing GSM5209
    doing GSM5211
    doing GSM5213
    doing GSM523
    doing GSM525
    doing GSM527
    doing GSM528
    doing GSM529
    doing GSM530
    doing GSM5309
    doing GSM531
    doing GSM5311
    doing GSM5313
    doing GSM532
    doing GSM541
    doing GSM543
    doing GSM545
    doing GSM547
    doing GSM549
    doing GSM5501
    doing GSM5502
    doing GSM5503
    doing GSM5505
    doing GSM551
    doing GSM553
    doing GSM581
    doing GSM582
    doing GSM583
    doing GSM585
    doing GSM587
    doing GSM590
    doing GSM591
    doing GSM592
    doing GSM593
    doing GSM594
    doing GSM595
    doing GSM596
    doing GSM599
    doing GSM601
    doing GSM602
    doing GSM604
    doing GSM605
    doing GSM606
    doing GSM608
    doing GSM609
    doing GSM610
    doing GSM612
    doing GSM613
    doing GSM617
    doing GSM618
    doing GSM619
    doing GSM621
    doing GSM622
    doing GSM629
    doing GSM655
    doing GSM656
    doing GSM671
    doing GSM681
    doing GSM691
    doing GSM700
    doing HART213
    doing HART292
    doing HART311
    doing HART323
    doing HART392
    doing HART411
    doing HART413
    doing HART430
    doing HART432
    doing HART523
    doing HART624
    doing HIST191
    doing HIST192
    doing HIST221
    doing HIST223
    doing HIST227
    doing HIST242
    doing HIST285
    doing HIST311
    doing HIST315
    doing HIST316
    doing HIST323
    doing HIST331
    doing HIST341
    doing HIST345
    doing HIST363
    doing HIST397
    doing HIST436
    doing HIST450
    doing HIST455
    doing HIST461
    doing HIST462
    doing HIST489
    doing HIST492
    doing HIST494
    doing HIST497
    doing HIST500
    doing HIST501
    doing HIST502
    doing HIST511
    doing HIST512
    doing HIST513
    doing HIST514
    doing HIST519
    doing HIST521
    doing HIST525
    doing HIST531
    doing HIST547
    doing HIST549
    doing HIST555
    doing HIST556
    doing HIST561
    doing HIST572
    doing HIST581
    doing HIST591
    doing HIST592
    doing HIST596
    doing HIST599
    doing HIST600
    doing HIST601
    doing HIST602
    doing HIST609
    doing HIST611
    doing HIST613
    doing HIST621
    doing HIST622
    doing HIST623
    doing HIST631
    doing HIST632
    doing HIST633
    doing HIST634
    doing HIST647
    doing HIST650
    doing HIST652
    doing HIST671
    doing HIST682
    doing HIST689
    doing HIST692
    doing HIST694
    doing HIST695
    doing HIST696
    doing HIST699
    doing HIST702
    doing HIST712
    doing HIST714
    doing HIST715
    doing HIST742
    doing HIST751
    doing HIST762
    doing HIST771
    doing HIST781
    doing HIST791
    doing HIST799
    doing HUM201
    doing HUM201D
    doing HUM202
    doing HUM202D
    doing IE501
    doing IE503
    doing IE512
    doing IE514
    doing IE518
    doing IE519
    doing IE522
    doing IE524
    doing IE550
    doing IE551
    doing IE552
    doing IE553
    doing IE554
    doing IE556
    doing IE563
    doing IE563L
    doing IE564
    doing IE564L
    doing IE565
    doing IE567
    doing IE580
    doing IE581
    doing IE590
    doing IE592
    doing IE601
    doing IE605
    doing IE606
    doing IE609
    doing IE632
    doing IE638
    doing IE680
    doing IE681
    doing IE790
    doing IR201
    doing IR291
    doing IR292
    doing IR310
    doing IR341
    doing IR392
    doing IR394
    doing IT511
    doing IT512
    doing IT520
    doing IT521
    doing IT522
    doing IT523
    doing IT524
    doing IT525
    doing IT526
    doing IT531
    doing IT532
    doing IT533
    doing IT534
    doing IT553
    doing IT560
    doing IT592
    doing LIT212
    doing LIT324
    doing LIT334
    doing LIT343
    doing LIT354
    doing LIT394
    doing LIT431
    doing LIT452
    doing LIT624
    doing MAN350
    doing MAN370
    doing MAN380
    doing MAN390
    doing MAN410
    doing MAT202
    doing MAT202L
    doing MAT204
    doing MAT204R
    doing MAT301
    doing MAT302
    doing MAT302L
    doing MAT304
    doing MAT305
    doing MAT306
    doing MAT307
    doing MAT307R
    doing MAT308
    doing MAT310
    doing MAT312
    doing MAT312L
    doing MAT401
    doing MAT402
    doing MAT403
    doing MAT404
    doing MAT404D
    doing MAT406
    doing MAT408
    doing MAT416
    doing MAT480
    doing MAT501
    doing MAT502
    doing MAT503
    doing MAT504
    doing MAT505
    doing MAT506
    doing MAT507
    doing MAT508
    doing MAT509
    doing MAT509L
    doing MAT510
    doing MAT511
    doing MAT511L
    doing MAT514
    doing MAT514D
    doing MAT516
    doing MAT520
    doing MAT521
    doing MAT522
    doing MAT551
    doing MAT552
    doing MAT560
    doing MAT563
    doing MAT570
    doing MAT571
    doing MAT571L
    doing MAT580
    doing MAT581
    doing MAT590
    doing MAT591
    doing MAT592
    doing MAT601
    doing MAT602
    doing MAT611
    doing MAT620
    doing MAT621
    doing MAT622
    doing MAT630
    doing MAT632
    doing MAT672
    doing MAT680
    doing MAT681
    doing MAT790
    doing MATH101
    doing MATH101R
    doing MATH102
    doing MATH102R
    doing MATH201
    doing MATH201R
    doing MATH202
    doing MATH202R
    doing MATH203
    doing MATH203R
    doing MATH204
    doing MATH204R
    doing MATH301
    doing MATH302
    doing MATH305
    doing MATH306
    doing MATH306R
    doing MATH311
    doing MATH317
    doing MATH322
    doing MATH405
    doing MATH414
    doing MATH417
    doing MATH480
    doing MATH501
    doing MATH502
    doing MATH503
    doing MATH504
    doing MATH505
    doing MATH511
    doing MATH512
    doing MATH514
    doing MATH515
    doing MATH517
    doing MATH521
    doing MATH522
    doing MATH531
    doing MATH532
    doing MATH533
    doing MATH541
    doing MATH542
    doing MATH544
    doing MATH551
    doing MATH552
    doing MATH590
    doing MATH601
    doing MATH602
    doing MATH612
    doing MATH615
    doing MATH616
    doing MATH617
    doing MATH618
    doing MATH621
    doing MATH622
    doing MATH631
    doing MATH632
    doing MATH636
    doing MATH637
    doing MATH638
    doing MATH790
    doing ME301
    doing ME301R
    doing ME302
    doing ME302L
    doing ME302R
    doing ME303
    doing ME303R
    doing ME304
    doing ME304R
    doing ME305
    doing ME305R
    doing ME307
    doing ME307R
    doing ME308
    doing ME308R
    doing ME309
    doing ME309R
    doing ME403
    doing ME403R
    doing ME404
    doing ME404R
    doing ME405
    doing ME405R
    doing ME406
    doing ME406R
    doing ME407
    doing ME407L
    doing ME410
    doing ME412
    doing ME412R
    doing ME480
    doing MS301
    doing MS301R
    doing MS302
    doing MS302R
    doing MS303
    doing MS303R
    doing MS304
    doing MS305
    doing MS305R
    doing MS306
    doing MS307
    doing MS308
    doing MS309
    doing MS309R
    doing MS401
    doing MS402
    doing MS402L
    doing MS403
    doing MS405
    doing MS407
    doing MS408
    doing MS409
    doing MS411
    doing MS412
    doing MS413
    doing MS414
    doing MS417
    doing MS418
    doing MS418L
    doing MS419
    doing MS420
    doing MS451
    doing MS480
    doing NS101
    doing NS101L
    doing NS101R
    doing NS102
    doing NS102L
    doing NS102R
    doing NS202
    doing NS202L
    doing NS203
    doing NS203L
    doing NS203R
    doing NS205
    doing NS206
    doing NS207
    doing NS207L
    doing NS207R
    doing NS501
    doing NS502
    doing OPIM302
    doing PERS501
    doing PERS502
    doing PERS503
    doing PERS504
    doing PHIL433
    doing PHYS302
    doing PHYS302R
    doing PHYS303
    doing PHYS304
    doing PHYS412
    doing POLS222
    doing POLS250
    doing POLS254
    doing POLS271
    doing POLS301
    doing POLS302
    doing POLS303
    doing POLS304
    doing POLS351
    doing POLS352
    doing POLS364
    doing POLS372
    doing POLS383
    doing POLS384
    doing POLS392
    doing POLS393
    doing POLS421
    doing POLS441
    doing POLS453
    doing POLS455
    doing POLS473
    doing POLS491
    doing POLS492
    doing POLS495
    doing POLS501
    doing POLS503
    doing POLS512
    doing POLS521
    doing POLS583
    doing POLS584
    doing POLS593
    doing POLS594
    doing POLS595
    doing POLS610
    doing POLS700A
    doing POLS700B
    doing POLS790A
    doing POLS790B
    doing POLS799
    doing PROJ102
    doing PROJ300
    doing PROJ302
    doing SOC201
    doing SOC313
    doing SOC321
    doing SOC444
    doing SPA101
    doing SPA102
    doing SPA201
    doing SPA202
    doing SPA301
    doing SPA302
    doing SPA401
    doing SPA402
    doing SPS101
    doing SPS101D
    doing SPS102
    doing SPS102D
    doing SPS211
    doing SPS303
    doing SPS303D
    doing SPS311
    doing SPS311D
    doing SPS312
    doing SPS399
    doing SPS442
    doing TE202
    doing TE202R
    doing TE302
    doing TE302R
    doing TE303
    doing TE303R
    doing TE304
    doing TE304L
    doing TE304R
    doing TE401
    doing TE402
    doing TE404
    doing TE404R
    doing TE405
    doing TE407
    doing TE407L
    doing TE409
    doing TE409L
    doing TE410
    doing TE412
    doing TE480
    doing TLL001
    doing TLL101
    doing TLL102
    doing TLL301
    doing TLL302
    doing TLL501
    doing TLL502
    doing VA201
    doing VA202
    doing VA203
    doing VA204
    doing VA210
    doing VA212
    doing VA215
    doing VA301
    doing VA302
    doing VA321
    doing VA322
    doing VA323
    doing VA324
    doing VA329
    doing VA331
    doing VA332
    doing VA333
    doing VA334
    doing VA335
    doing VA336
    doing VA344
    doing VA399
    doing VA401
    doing VA402
    doing VA431
    doing VA432
    doing VA433
    doing VA434
    doing VA440
    doing VA441
    doing VA442
    doing VA501
    doing VA502
    doing VA503
    doing VA504
    doing VA510
    doing VA511
    doing VA512
    doing VA513
    doing VA514
    doing VA515
    doing VA520
    doing VA521
    doing VA522
    doing VA523
    doing VA524
    doing VA525
    doing VA526
    doing VA527
    doing VA528
    doing VA530
    doing VA531
    doing VA532
    doing VA533
    doing VA540
    doing VA541
    doing VA542
    doing VA543
    doing VA544
    doing VA590
    doing VA599
    Analysis complete, 162 prerequisite expressions in 0.8045804000003045 seconds.
    Average of 0.004966545679014225 seconds per expression.
    Totals:
    	courses: 992
    	changes: 21
    	time: 1.1465924000003724 seconds


# PharmSci 175/275 (UCI)
## What is this??
The material below is Lecture 2 (on Energy Minimization) from Drug Discovery Computing Techniques, PharmSci 175/275 at UC Irvine.
Extensive materials for this course, as well as extensive background and related materials, are available on the course GitHub repository: [github.com/mobleylab/drug-computing](https://github.com/mobleylab/drug-computing)

This material is a set of slides intended for presentation with RISE as detailed [in the course materials on GitHub](https://github.com/MobleyLab/drug-computing/tree/master/uci-pharmsci/lectures/energy_minimization). While it may be useful without RISE, it will also likely appear somewhat less verbose than it would if it were intended for use in written form.

# Energy landscapes and energy minimization

Today: Energy landscapes, energy functions and energy mimimization

### Instructor: David L. Mobley

### Contributors to today's materials:
- David L. Mobley (UCI)
- David Wych (Mobley lab, UCI)
- [Previous contributions](https://engineering.ucsb.edu/~shell/che210d/) from M. Scott Shell (UCSB); some images used here are drawn from his materials.

## Energy landscapes provide a useful conceptual device

### Energy landscapes govern conformations and flexibility

### Chemistry takes place on energy landscapes

- Reactions and barriers
- Conformational change
- Binding/association

### Energy landscapes govern dynamics and thermodynamics

- Reaction rates
- Equilibrium properties

## Often, we are interested in exploring the energy landscape

A potential, $U(\bf{r^N})$ describes the energy as a function of the coordinates of the particles; here ${\bf r}$ is the coordinates, **boldface** denotes it is a vector, and the superscript denotes it is coordinates of all $N$ particles in the system.

### Landscapes have many features, including
- Global minimum, the most stable state (caveat: entropy)
- Local minima: other stable/metastable states

### We can't visualize 3N dimensions, so we often project onto fewer dimensions

### Background: Vector notation

- For a single particle, we have coordinates $x$, $y$, and $z$, or $x_1$, $y_1$, and $z_1$ if it is particle 1
- We might write these as $(-1, 3, 2)$ for example, if $x=-1$, $y=3$, $z=2$.
- For evem two particles, we have $x_1$ and $x_2$, $y_1$ and $y_2$, etc.
- Writing out names of coordinates becomes slow, e.g. $f(x_1, y_1, z_1, x_2, y_2, z_2, ... z_N)$
- We simplify by writing $f({\bf r}^N)$ and remember that ${\bf r}^N$ really means:
\begin{equation}
{\bf r}^N=
\begin{bmatrix}
x_1 & y_1 & z_1 \\
x_2 & y_2 & z_2 \\
... & ... & ... \\
x_N & y_N & z_N \\
\end{bmatrix}
\end{equation}

### A concrete example
Imagine we have two particles with particle 1 having coordinates $x_1 = 1$, $y_1 = 3$, and $z_1 = 2$, and particle 2 having coordinates $x_2 = -1$, $y_2 = 3$, $z_2 = -1$. That would give us an array like this:
\begin{equation}
{\bf r}^N=
\begin{bmatrix}
1 & 3 & 2 \\
-1 & 3 & -1 \\
\end{bmatrix}
\end{equation}

### In Python, we'd store that as a numpy array

```python
import numpy as np
r_N = np.array( [[1, 3, 2], [-1, 3, -1]], float)
print('y coordinate of first particle is %.2f' % r_N[0,1])
```

    y coordinate of first particle is 3.00

We could compute the distance between particles as
$d = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2 + (z_1-z_2)^2}$

You could code that up directly in Python, or you can use array operations:

```python
d = np.sqrt( ((r_N[0,:]-r_N[1,:])**2).sum() )
print(d)
```

    3.60555127546

## Forces are properties of energy landscapes, too

The force is the slope (technically, gradient):

$f_{x,i} = -\frac{\partial U({\bf r^N})}{\partial x_i}$, $f_{y,i} = -\frac{\partial U({\bf r^N})}{\partial y_i}$, $f_{z,i} = -\frac{\partial U({\bf r^N})}{\partial z_i}$

As shorthand, this may be written ${\bf f}^N = -\frac{\partial U({\bf r^N})}{\partial {\bf r^N}}$ or ${\bf f}^N = -\nabla \cdot U({\bf r^N})$ where the result, ${\bf f}^N$, is an Nx3 array (matrix, if you prefer)

If energy function is pairwise additive, can evaluate via summing individual interactions -- force on atom k is
\begin{equation}
{\bf f}_k = \sum_{j\neq k} \frac{ {\bf r}_{kj}}{r_{kj}}  \frac{\partial}{\partial r_{kj}} U(r_{kj})
\end{equation} where ${\bf r_{kj}} = {\bf r}_j - {\bf r_k}$. Note not all force calculations are necessary: ${\bf f}_{kj} = -{\bf f}_{jk}$

## The matrix of second derivatives is called the Hessian and distinguishes minima from saddle points

\begin{equation}
{\bf H}({\bf r}^N)=
\begin{bmatrix}
\frac{d^2 U({\bf r^N}) }{ d x_1^2} & \frac{d^2 U({\bf r^N}) }{ dx_1 d y_1} & ... & \frac{d^2 U({\bf r^N}) }{ dx_1 d z_1} \\
\frac{d^2 U({\bf r^N}) }{ d y_1 d x_1} & \frac{d^2 U({\bf r^N}) }{ d y_1^2} & ... & \frac{d^2 U({\bf r^N}) }{ dy_1 d z_N}\\
... & ... & ... & ... \\
\frac{d^2 U({\bf r^N}) }{ d z_N d x_1} & \frac{d^2 U({\bf r^N}) }{ d z_N d y_1} & ... & \frac{d^2 U({\bf r^N}) }{ dz_N^2}\\
\end{bmatrix}
\end{equation}

## Types of stationary points can be distiguished from derivatives

- Stationary points have zero force on each particle: $ \nabla \cdot U({\bf r^N}) = {\bf 0}$
- These can be minima or maxima
- Minima have negative curvature in all directions (restoring force is towards the minimum)

## Energy landscapes have *lots* of minima

For an Lennard-Jones 38 cluster:

See also Doye, Miller, and Wales, J. Chem. Phys. 111: 8417 (1999)

- They have a disconnectivity graph shows minima for 13 LJ atoms
- To move between two minima, have to go to point where lines from two minima reach same energy
- 1467 distinct minima for 13 atoms!
- This is a different system, more atoms, far more minima

Note related ["Python energy landscape explorer" (PELE)](https://github.com/pele-python/pele)

(Image source https://pele-python.github.io/pele/disconnectivity_graph.html, CC-BY license, by the Pele authors: https://github.com/pele-python/pele/graphs/contributors)

```python
Bonus disconnectivity graph

<div style="float: right">

</div>

(Image source https://commons.wikimedia.org/wiki/File:Fedg.png#filelinks, by Snd0, CC-BY-SA 4.0)
```

## We care a lot about finding minima

- As noted, gives a first guess about most stable states
- Minima are stable structures, point of contact with experiment
- Initial structures need to be relaxed so forces are not too large
  - Remove strained bonds, atom overlaps
- Minimization is really optimization:
  - If you can find the minimum of $U$, you can find the minimum of $-U$
  - Same techniques apply to other things, i.e. finding set of parameters that minimizes an error, etc.

### Findining minima often becomes a numerical task, because analytical solutions become impractical very quickly

Consider $U(x,y) = x^2 + (y-1)^2$ for a single particle in a two dimensional potential. Finding $\nabla\cdot U = 0$ yields:

$2x = 0$ and $2(y-1)=0$ or $x=0$, $y=1$

simple enough

But in general, N dimensions means N coupled, potentially nonlinear equations. Consider
\begin{equation}
U = x^2 z^2 +x (y-1)^2 + xyz + 14y + z^3
\end{equation}
Setting the derivatives to zero yields:

$0 = 2xz^2 + (y-1)^2 +yz$

$0 = 2x(y-1) + xz + 14$

$0 = 2x^2z + xy + 3z^2$

**Volunteers??** It can be solved, but not fun.

**And this is just for a single particle in a 3D potential**, so we are typically forced to numerical minimizations, even when potential is analytic

### Energy minimization is a sub-class of the more general problem of finding roots

Common problem: For some $f(x)$, find values of $x$ for which $f(x)=0$

Many equations can be re-cast this way. *i.e.*, if you need to solve $g(x) = 3$, define $f(x) = g(x)-3$ and find $x$ such that $f(x)=0$

If $f(x)$ happens to be the force, this maps to energy minimization

As a consequence: Algorithms used for energy minimization typically have broader application to finding roots

### Let's check out a toy minimization problem to see how this would work
Here we'll set up a simple function to represent an energy landscape in 1D, and play with energy minimizing on that landscape.

```python
#Import pylab library we'll use
import scipy.optimize
#Get pylab ready for plotting in this notebook
%pylab inline

#Define a range of x values to look at in your plot
xlower = -5 #Start search at xlower
xupper = 5 #End search at xupper
#Define a starting guess for the location of the minimum
xstart = 0.01
#Create an array of x values for our plot, starting with xlower
#and running to xupper with step size 0.01
xvals = np.arange( xlower, xupper, 0.01)
```

    Populating the interactive namespace from numpy and matplotlib

    /Users/dmobley/anaconda3/envs/workshop_test/lib/python3.6/site-packages/IPython/core/magics/pylab.py:160: UserWarning: pylab import has clobbered these variables: ['f']
    `%matplotlib` prevents importing * from pylab and numpy
      "\n`%matplotlib` prevents importing * from pylab and numpy"

```python
#Define and the function f we want to minimize
def f(x):
    return 10*np.cos(x)+x**2-3.
#Store function values at those x values for our plot
fvals = f(xvals)

#Do our minimization ("line search" of sorts), store results to 'res'
res = scipy.optimize.minimize(f, xstart) # Apply canned minimization algorithm from scipy
```

```python
#Make a plot of our function over the specified range
plot(xvals, fvals, 'k-') #Use a black line to show the function
plot(res.x, f(res.x), 'bo') #Add the identified minimum to the plot as a blue circle
plot(xstart, f(xstart), 'ro') #Add starting point as red circle

#Add axis labels
xlabel('x')
ylabel('f(x)')
```

### Sandbox section

Try adjusting the above to explore what happens if you alter the starting conditions or the energy landscape or both. You might try:
- Change the starting point (`xstart`) so it is slightly to the left or slightly to the right
- Change the starting point so it is far up the wall to the left or the right
- Change the energy landscape to alter its shape, perhaps adding a term proportional to `+x`. Can you make it so one of the wells is a local minimum, perhaps by altering the coefficient of this term?
- If you adjust the starting point further, can you make the blue ball get stuck in a local minimum? Can you make it so it still finds the global minimum?

## Steepest descents is a simple minimization algorithm that always steps as far as possible along the direction of the force
Take ${\bf f}^N = -\frac{\partial U({\bf r}^N)}{\partial {\bf r}^N}$, then:
1. Move in direction of last force until the minimum *in that direction* is found
2. Compute new ${\bf f}_i^N $ for iteration $i$, perpendicular to previous force

Repeat until minimum is found

**Limitations**:

Oscillates in narrow valleys; slow near minimum.

### Steepest descents oscillates in narrow valleys and is slow near the minimum

(Illustration, P.A. Simonescu, [Wikipedia](https://en.wikipedia.org/wiki/Gradient_descent#/media/File:Banana-SteepDesc.gif), [CC-BY-SA](https://creativecommons.org/licenses/by-sa/3.0/))

### Another illustration further highlights this

(In this case, steepest *ascents*, but it's just the negative...)

(Images public domain: [source](https://upload.wikimedia.org/wikipedia/commons/d/db/Gradient_ascent_%28contour%29.png), [source](https://upload.wikimedia.org/wikipedia/commons/6/68/Gradient_ascent_%28surface%29.png))

## A line search can make many minimization methods more efficient

A line search is an efficient way to find a minimum along a particular direction
- Line search: Bracket minimum
  - Start with initial set of coordinates ${\bf r}$ and search direction ${\bf v}$ that is downhill
  - Generate pairs of points a distance $d$ and $2d$ along the line (${\bf r} + d{\bf v}, {\bf r}+2d{\bf v}$)
     1. If the energy at the further point is higher than the energy at the nearer point, stop
     2. Otherwise, move the pair of points $d$ further along the line and go back to 1.

### To finish a line search, identify the minimum precisely

- Fit a quadratic to our 3 points (initial, and two bracket points)
- Guess that minimum is at the zero of the quadratic, call this point 4.
- Fit a new quadratic using points 2, 3, and 4, and move to the zero.
- Repeat until the energy stops changing within a given tolerance

## To do better than steepest descents, let's consider its pros and cons

- Good to go in steepest direction initially
- It’s a good idea to move downhill
- It is initially very fast (see Leach Table 5.1)
- But steepest descent overcorrects

## SciPy does't implement steepest descents because of these issues
SciPy has lots of functions and tools for [optimization](https://docs.scipy.org/doc/scipy/reference/optimize.html), but it doesn't even implement steepest descents because of poor reliability.
However, the `Nelder-Mead` minimization method applies a downhill simplex method which also is less than ideal, so let's play around with that a bit. First, let's make a suitable landscape:

```python
#Define and the function f we want to minimize
def f(arr):
    return 10*np.cos(arr[0])+arr[0]**2-3.+arr[1]**2

#Define a range of x, y values to look at in your plot
# NOTE IF YOU WANT TO ADJUST THESE YOU NEED TO RE-RUN ALL THREE CELLS (shift-enter)
xlower, ylower = -5, -5 #Start search at xlower and yupper
xupper, yupper = 5, 5 #End search at xupper and yupper
#Define a starting guess for the location of the minimum
xstart, ystart = -1.0, 1.0
#Create an array of coordinates for our plot
xvals = np.arange( xlower, xupper, 0.01)
yvals = np.arange( ylower, yupper, 0.01)

# Make a grid of x and y values
xx, yy = np.meshgrid(xvals, yvals)
```

```python
#Store function values at those x and y values for our plot
fvals = f(np.array([xx,yy]))

colors = np.linspace(0,1,len(xx))
#Create 9''x7'' figure
plt.figure(figsize=(9, 7))
#Plot the Energy Landscape with a colorbar to the side
plt.contourf(xvals, yvals, fvals, 10)
plt.colorbar()
plt.show()
```

```python
res = scipy.optimize.minimize(f, np.array((xstart,ystart)), method='Nelder-Mead', options={'return_all':True})

plt.figure(figsize=(9, 7))
plt.contourf(xvals, yvals, fvals, 10)
plt.colorbar()

# Plot path of minimization
xvals = [ entry[0] for entry in res.allvecs ]
yvals = [ entry[1] for entry in res.allvecs ]

colors = np.linspace(0,1,len(xvals))
plt.scatter(xvals, yvals, c=colors, cmap='spring', s=5, marker = "o")
plt.plot(xvals, yvals, 'm-')
```

## Conjugate gradient (CG) works like steepest descent but chooses a different direction

- Start with an initial direction ${\bf v}$ that is downhill, move in that direction until a minimum is reached.

- Compute a new direction using ${\bf v}_i = {\bf f}^N_i + \gamma_i {\bf v}_{i-1}^N$ where $\gamma_i = \frac{({\bf f}^N_i-{\bf f}^N_{i-1}){\bf f}^N_i}{{\bf f}^N_{i-1} {\bf f}^N_{i-1}}$; note $\gamma_i$ is a scalar.

Note that by ${\bf f}^N {\bf f}^N$ we mean vector multiplication, not a matrix multiplication; that is:
\begin{equation}
{\bf f}^N {\bf f}^N = f^2_{x,1} + f^2_{y,1} + f^2_{z,1} + f^2_{x,2} + ... + f^2_{z, N}
\end{equation}

(In Python, this can be coded by multiplying ${\bf f}\times {\bf f}$, where ${\bf f}$ are arrays, and taking the sum of the result; for normal matrix multiplication of arrays one would use a dot product)

$\gamma_i$ is designed so that the new direction is *conjugate* to the old direction so that the new step does not undo any of the work done by the old step (causing oscillation)

### Conjugate gradient (CG) is more efficient

- Green: Steepest descent (with optimal step size)
- Red: Conjugate gradient

Ideally takes at most $M$ steps, where $M$ is the number of degrees of freedom, often $3N$, though in practice even small precision errors make it take longer than this.

(Image: [Wikipedia](https://en.wikipedia.org/wiki/Conjugate_gradient_method#/media/File:Conjugate_gradient_illustration.svg), public domain. Oleg Alexandrov.)

## Let's look at a CG example

```python
#Define and the function f we want to minimize
def f(arr):
    return 10*np.cos(arr[0])+arr[0]**2-3.+arr[1]**2

#Define a range of x, y values to look at in your plot
# NOTE IF YOU WANT TO ADJUST THESE YOU NEED TO RE-RUN ALL THREE CELLS (shift-enter)
xlower, ylower = -5, -5 #Start search at xlower and yupper
xupper, yupper = 5, 5 #End search at xupper and yupper
#Define a starting guess for the location of the minimum
xstart, ystart = -1.0, 1.0
#Create an array of coordinates for our plot
xvals = np.arange( xlower, xupper, 0.01)
yvals = np.arange( ylower, yupper, 0.01)

# Make a grid of x and y values
xx, yy = np.meshgrid(xvals, yvals)

#Store function values at those x and y values for our plot
fvals = f(np.array([xx,yy]))

colors = np.linspace(0,1,len(xx))
```

```python
res = scipy.optimize.minimize(f, np.array((xstart,ystart)), method='CG', options={'return_all':True})
```

```python
plt.figure(figsize=(9, 7))
plt.contourf(xvals, yvals, fvals, 10)
plt.colorbar()

# Plot path of minimization
xvals = [ entry[0] for entry in res.allvecs ]
yvals = [ entry[1] for entry in res.allvecs ]

colors = np.linspace(0,1,len(xvals))
plt.scatter(xvals, yvals, c=colors, cmap='spring', s=5, marker = "o")
plt.plot(xvals, yvals, 'm-')
```

## More advanced minimization methods can have considerable advantages

- Newton-Raphson: Based on Taylor expansion of potential around zero
  - reaches minimum in one step for quadratic potentials
  - converges to minima and saddle points, so requires initial moves to reach minima
  - slow because requires Hessian at every step
- Quasi-Newton methods approximate the Hessian to go faster
  - L-BFGS is a popular quasi-Newton method

```python
#Define and the function f we want to minimize
def f(arr):
    return 10*np.cos(arr[0])+arr[0]**2-3.+arr[1]**2

#Define a range of x, y values to look at in your plot
xlower, ylower = -5, -5 #Start search at xlower and yupper
xupper, yupper = 5, 5 #End search at xupper and yupper
#Define a starting guess for the location of the minimum
xstart, ystart = -0.1, 1.0
#Create an array of coordinates for our plot
xvals = np.arange( xlower, xupper, 0.01)
yvals = np.arange( ylower, yupper, 0.01)

xx, yy = np.meshgrid(xvals, yvals)

#Store function values at those x and y values for our plot
fvals = f(np.array([xx,yy]))
```

```python
res = scipy.optimize.minimize(f, np.array((xstart,ystart)), method='BFGS', options={'return_all':True})

plt.figure(figsize=(9, 7))
plt.contourf(xvals, yvals, fvals, 10)
plt.colorbar()

xvals = [ entry[0] for entry in res.allvecs ]
yvals = [ entry[1] for entry in res.allvecs ]

colors = np.linspace(0,1,len(xvals))
plt.scatter(xvals, yvals, c=colors, cmap='spring', s=5, marker = "o")
plt.plot(xvals, yvals, 'm-')
```

## Locating global minima is challenging and often involves combination of techniques

- Need some way to cross barriers, in addition to minimization
- Often combine techniques
  - i.e. Molecular Dynamics or Monte Carlo + minimization
- Normal mode analysis can be used to identify collective motions
  - Once identified, system can be moved in those directions

### When the number of dimensions is small, global minima can be found by repeated trials

One can simply do lots of minimizations from random starting points and find the global minimum sometimes, if there are not too many minima in total. See e.g. this [local and global minima](http://people.duke.edu/~ccc14/sta-663-2016/13_Optimization.html#Local-and-global-minima) discussion.

As an exercise, you might try to find the global minimum of this function by picking random starting points and minimizing many times (for solutions, see that link):

```python
def f(x, offset):
    return -np.sinc(x-offset)
x = np.linspace(-20, 20, 100)
plt.plot(x, f(x, 5));
```