~dricottone/xpress-demo

6ec5ee1b9d4fb789035f62c64612475ada105c06 — Dominic Ricottone 1 year, 1 month ago
Initial commit
5 files changed, 264 insertions(+), 0 deletions(-)

A .gitignore
A Makefile
A README.md
A choropleth_map.png
A main.py
A  => .gitignore +5 -0
@@ 1,5 @@
.venv
__pycache__

knapsack_example.geojson


A  => Makefile +16 -0
@@ 1,16 @@
clean:
	rm -rf .venv
	rm -rf **/__pycache__ **/*.pyc
	rm -rf *.png

.venv:
	python -m venv .venv
	(. .venv/bin/activate; pip install --upgrade pip)
	(. .venv/bin/activate; pip install numpy xpress geopandas matplotlib folium mapclassify)

knapsack_example.geojson:
	wget https://raw.githubusercontent.com/sburtner/SDSS21/main/knapsack_example.geojson

run: knapsack_example.geojson
	(. .venv/bin/activate; python -m main)


A  => README.md +186 -0
@@ 1,186 @@
# Xpress Demo

This is a demo for the FICO Xpress module for linear programming optimization.
Everything shown here is a 'normal' Python conversion of the
[Jupyter notebook](https://gist.github.com/sburtner/3c058b06fc56bf5293c9851dd645939a)
from the 2021 Spatial Data Science Symposium session
"Spatial Optimization for Planning and Decision-Making".
All credit and ownership belongs to Susan Burtner, Dr. Jing Xu, Jiwon Baik,
Seonga Cho, Vanessa Echeverri Figueroa, Evgeny Noi, B. Amelia Pludow, and
Enbo Zhou.

## Relevant Notebook Quotes

> Here, we are going to work on the [well-known] Knapsack problem, apply it to
> forest treatment, and explore some extensions.

> The context that we are interested in is to select land management units for
> monitoring and evaluation in Stanislaus National Forest, in order to maximize
> the total benefit from treatment while not exceeding the capacity of
> resources and labor. The study area is part of Stanislaus National Forest
> which is located northern California.

> Mathematical programming usually has the following form:
>
> Maximize/Minimize f(X_1_, X_2_, ... x_n_)
>
> Subject to g_1_(X_1_, X_2_, ... x_n_) <= b_1_; g_2_(X_1_, X_2_, ... x_n_) <= b_2_; ... g_m_(X_1_, X_2_, ... x_n_) <= b_m_
>
> Where X_i_, i = 1, 2, ... n, are decision variables,
> f is the objective function measuring soluton quality,
> and we optimize f to acquire the best solution.
> g_i_, i = 1, 2, ... m, are mathmatical [functions] to help formalize
> constraints.
> b_i_, i = 1, 2, ... m are constants to define constraints.

> [L]et us assume that that the US Forest Service is planning to treat 100
> acres (threshold capacity) of the Stanislaus National Forest to decrease
> wildfire risk.
> The selected units should be those that maximize the benefit or resilience
> to wildfire while the threshold capacity is not surpassed.

> Most of the real-world problems are based on multiple objectives.
> ...
> For example, (1) protecting property and (2) protecting people can be two
> major objectives (Church and Murray, 2018) in fire protection services.
> And most real-world problem contains a complicated relationship between
> multiple objectives like the example.

> The result of the multi-objective optimization problem is usually visualized
> as a Pareto frontier.
> The graph below shows that there is a trade-off relationship between
> (1) covered property and (2) covered people.

*Adjustments noted with brackets. Omissions noted with elipses.*


## Results

```
$ make run
(. .venv/bin/activate; python -m main)

:: This is the data
(16, 8)
   CSO_depart  CSOterr_ID  CNSM_NoMGT      Acres  LMU_ID  res_d_STND  res_d_SPM                                           geometry
0    0.833334         164    172765.0   5.337479   35564    0.921725  14.163780  MULTIPOLYGON (((757755.000 4243875.000, 757725...
1    0.565610         164   1757300.0  27.354568   36492    1.304450  20.044963  MULTIPOLYGON (((756915.000 4243875.000, 756975...
2    0.585455         164   3106670.0  48.926867   36493    1.220160  18.749712  MULTIPOLYGON (((756735.000 4243545.000, 756765...
3    0.590000         164   1042850.0  15.790036   36494    0.687985  10.571991  MULTIPOLYGON (((756465.000 4243695.000, 756435...
4    0.652973         164    435944.0   8.228611   36964    1.695560  26.054994  MULTIPOLYGON (((757425.000 4243665.000, 757425...


:: This is some licensing stuff, can be ignored
Using the Community license in this session. If you have a full Xpress license, pass the full path to your license file to xpress.init(). If you want to use the FICO Community license and no longer want to see this message, use the following code before using the xpress module:
  xpress.init('/home/al_dente/dev/xpress-demo/.venv/lib/python3.11/site-packages/xpress/license/community-xpauth.xpr')


:: This is the solution
FICO Xpress v9.2.5, Community, solve started 22:49:07, Nov 16, 2023
Heap usage: 390KB (peak 390KB, 86KB system)
Maximizing MILP noname using up to 4 threads and up to 7897MB memory, with these control settings:
OUTPUTLOG = 1
Original problem has:
         1 rows           16 cols           16 elements        16 entities
Presolved problem has:
         1 rows           16 cols           16 elements        16 entities
Presolve finished in 0 seconds
Heap usage: 421KB (peak 433KB, 86KB system)

Coefficient range                    original                 solved        
  Coefficients   [min,max] : [ 2.67e+00,  4.89e+01] / [ 8.34e-02,  1.53e+00]
  RHS and bounds [min,max] : [ 1.00e+00,  1.00e+02] / [ 1.00e+00,  3.12e+00]
  Objective      [min,max] : [ 1.06e+01,  2.67e+01] / [ 1.06e+01,  2.67e+01]
Autoscaling applied standard scaling

Will try to keep branch and bound tree memory usage below 7.0GB
 *** Solution found:      .000000   Time:   0.00    Heuristic: T ***
 *** Solution found:   104.739705   Time:   0.00    Heuristic: e ***
Starting concurrent solve with dual (1 thread)

 Concurrent-Solve,   0s
            Dual        
    objective   dual inf
 D  176.96135   .0000000
------- optimal --------
Concurrent statistics:
      Dual: 1 simplex iterations, 0.00s
Optimal solution found

   Its         Obj Value      S   Ninf  Nneg   Sum Dual Inf  Time
     1        176.961353      D      0     0        .000000     0
Dual solved problem
  1 simplex iterations in 0.00 seconds at time 0

Final objective                       : 1.769613529197185e+02
  Max primal violation      (abs/rel) :       0.0 /       0.0
  Max dual violation        (abs/rel) :       0.0 /       0.0
  Max complementarity viol. (abs/rel) :       0.0 /       0.0

Starting root cutting & heuristics
Deterministic mode with up to 1 additional thread

 Its Type    BestSoln    BestBound   Sols    Add    Del     Gap     GInf   Time
a          166.838660   176.961353      3                  5.72%       0      0

Performing root presolve...

Reduced problem has:       1 rows      11 columns        11 elements
Presolve dropped   :       0 rows       5 columns         5 elements
Will try to keep branch and bound tree memory usage below 6.9GB

   Its         Obj Value      S   Ninf  Nneg   Sum Dual Inf  Time
     2        176.810476      D      0     0        .000000     0
Optimal solution found
Dual solved problem
  2 simplex iterations in 0.00 seconds at time 0

Final objective                       : 1.768104758324006e+02
  Max primal violation      (abs/rel) :       0.0 /       0.0
  Max dual violation        (abs/rel) :       0.0 /       0.0
  Max complementarity viol. (abs/rel) :       0.0 /       0.0

Starting root cutting & heuristics
Deterministic mode with up to 1 additional thread

 Its Type    BestSoln    BestBound   Sols    Add    Del     Gap     GInf   Time
   1  K    166.838660   169.804364      3      1      0    1.75%       1      0

Cuts in the matrix         : 1
Cut elements in the matrix : 11

Performing root presolve...

Reduced problem has:       2 rows       5 columns        10 elements
Presolve dropped   :       0 rows       6 columns        12 elements
Presolve tightened :         5 elements
Will try to keep branch and bound tree memory usage below 6.9GB

   Its         Obj Value      S   Ninf  Nneg   Sum Dual Inf  Time
     4        170.072327      D      0     0        .000000     0
Optimal solution found
Dual solved problem
  4 simplex iterations in 0.00 seconds at time 0

Final objective                       : 1.700723269425466e+02
  Max primal violation      (abs/rel) :       0.0 /       0.0
  Max dual violation        (abs/rel) :       0.0 /       0.0
  Max complementarity viol. (abs/rel) :       0.0 /       0.0

Starting root cutting & heuristics
Deterministic mode with up to 1 additional thread

 Its Type    BestSoln    BestBound   Sols    Add    Del     Gap     GInf   Time
 *** Search completed ***
Uncrunching matrix
Final MIP objective                   : 1.668386599999999e+02
Final MIP bound                       : 1.668388369613529e+02
  Solution time / primaldual integral :      0.01s/ 24.267905%
  Number of solutions found / nodes   :         3 /         1
  Max primal violation      (abs/rel) :       0.0 /       0.0
  Max integer violation     (abs    ) :       0.0
166.83865999999995
```

![choropleth map](choropleth_map.png)


A  => choropleth_map.png +0 -0
A  => main.py +57 -0
@@ 1,57 @@
#!/usr/bin/python3

import xpress as xp
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
import folium

def main():
    gdf = gpd.read_file('knapsack_example.geojson')

    print("\n:: This is the data")
    print(gdf.shape)
    print(gdf.head())

    # decision variables from each Landscape Management Units (LMU)
    x_i = [xp.var(i, vartype=xp.binary) for i in gdf['LMU_ID'].astype(str)]

    # objective to maximize
    Beta_i = gdf['res_d_SPM']
    Z = xp.Sum(Beta_i * x_i)

    # constraint (i.e. LMUs selected are constrained by number of acres)
    alpha_i = gdf['Acres']
    c1 = xp.Sum(alpha_i * x_i) <= 100  #try changing me to 80

    # linear program
    print("\n\n:: This is some licensing stuff, can be ignored")
    p = xp.problem()
    p.addVariable(x_i)
    p.addConstraint(c1)
    p.setObjective(Z, sense=xp.maximize)

    print("\n\n:: This is the solution")
    p.solve()
    print(p.getObjVal())

    # solution
    var_solutions = [int(x.name) for x in x_i if p.getSolution(x) == 1]

    # plot all LMUs and the objective variable
    fig, ax = plt.subplots(figsize=(16,10))
    gdf.plot(color='None', edgecolor='k', ax=ax)
    gdf.plot(column='res_d_SPM', ax=ax, legend=True, scheme='quantiles', legend_kwds={'loc': 'lower right'})
    ax.set_title('res_d_SPM', fontsize=18)

    # highlight solution
    outline_solutions = gdf.loc[gdf['LMU_ID'].isin(var_solutions)]
    outline_solutions.plot(color='None', edgecolor='r', linewidth=5, ax=ax, hatch='///')

    fig.savefig('choropleth_map.png')

if __name__ == '__main__':
    main()