Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Mod for Network Design Problem #37

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"python.testing.unittestArgs": [
"-v",
"-s",
"./tests",
"-p",
"test_*.py"
],
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true
}
1 change: 1 addition & 0 deletions docs/source/mods/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ The Opti Mods
mwis
weighted-matching
workforce
networkdesign
182 changes: 182 additions & 0 deletions docs/source/mods/networkdesign.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
.. This template should be copied to docs/source/mods/<mod_name>.rst

Network Design
==========

A little background on the proud history of mathprog in this field.

Also data science.

Problem Specification
---------------------

Give a brief overview of the problem being solved.


.. tabs::

.. tab:: Graph Theory

For a given graph :math:`G` with set of vertices :math:`V` and potential edges
:math:`E` as well as a set of commodities :math:`K`.

Each potential edge :math:`(i,j)\in E` has the following attributes:

- flow cost: :math:`c_{ij}\in \mathbb{R}`;
- fixed cost for building the arc: :math:`f_{ij} \in \mathbb{R}`;
- and capacity: :math:`B_{ij}\in\mathbb{R}`.

Each commodity :math:`k \in K` has the following attributes:

- origin :math:`o_k \in V`;
- destination: :math:`d_k \in V`;
- and demand: :math:`D_k \in \mathbb{R}`


Also, each vertex :math:`i\in V` has a demand :math:`d_i\in\mathbb{R}`.
This value can be positive (requesting flow), negative (supplying
flow), or 0.

The problem can be stated as finding a the flow with minimal total cost
such that:

- the demand at each vertex is met;
- and, the flow is capacity feasible.

.. tab:: Optimization Model

Let us define a set of continuous variables :math:`x_{ij}` to represent
the amount of non-negative (:math:`\geq 0`) flow going through an edge
:math:`(i,j)\in E`.


The mathematical formulation can be stated as follows:

.. math::


\begin{alignat}{2}
\min \quad &\sum_k \sum_{(i,j)\in A} W^k c_{ij} x^k_{ij} + \sum_{(i,j)\in A} f_{ij} y_{ij}\\
\text{s.t.} \quad &\sum_{j\in \delta^+(i)} x^k_{ij} - \sum_{j\in \delta^-(i)} x^k_{ji} =\begin{cases}1 &\text{if }i=o_k\\-1 &\text{if }i=d_k\\0&\text{otherwise}\end{cases} \quad&\forall\ i\in N,\ k\in K\\
&\sum_{k\in K} W^k x^k_{ij} \le C_{ij} y_{ij} & \forall\ (i,j)\in A\\
& x^k_{ij}\geq 0,\ \ y_{ij}\in\{0,1\} &\forall\ (i,j)\in A,\ k\in K
\end{alignat}

Where :math:`\delta^+(\cdot)` (:math:`\delta^-(\cdot)`) denotes the
outgoing (incoming) neighours.

The objective minimises the total cost over all edges.

The first constraints ensure flow balance for all vertices. That is, for
a given node, the incoming flow (sum over all incoming edges to this
node) minus the outgoing flow (sum over all outgoing edges from this
node) is equal to the demand. Clearly, in the case when the demand is 0,
the outgoing flow must be equal to the incoming flow. When the demand is
negative, this node can supply flow to the network (outgoing term is
larger), and conversely when the demand is negative, this node can
request flow from the network (incoming term is larger).

The last constraints ensure non-negativity of the variables and that the
capacity per edge is not exceeded.
Give examples of the various input data structures. These inputs should be fixed,
so use doctests where possible.

.. testsetup:: mod

# Set pandas options for displaying dataframes, if needed
import pandas as pd
pd.options.display.max_rows = 10

.. tabs::

.. tab:: ``availability``

Give interpretation of input data.

.. doctest:: mod
:options: +NORMALIZE_WHITESPACE

>>> from gurobi_optimods import datasets
>>> data = datasets.load_workforce()
>>> data.availability
Worker Shift
0 Amy 2022-07-02
1 Amy 2022-07-03
2 Amy 2022-07-05
3 Amy 2022-07-07
4 Amy 2022-07-09
.. ... ...
67 Gu 2022-07-10
68 Gu 2022-07-11
69 Gu 2022-07-12
70 Gu 2022-07-13
71 Gu 2022-07-14
<BLANKLINE>
[72 rows x 2 columns]

In the model, this corresponds to ...

.. tab:: ``shift_requirements``

Another bit of input data (perhaps a secondary table)

|

Code
----

Self contained code example to run the mod from an example dataset. Example
datasets should bd included in the ``gurobi_optimods.datasets`` module for
easy access by users.

.. testcode:: mod

import pandas as pd

from gurobi_optimods.datasets import load_mod_data
from gurobi_optimods.mod import solve_mod


data = load_mod_data()
solution = solve_mod(data.table1, data.table2)

.. A snippet of the Gurobi log output here won't show in the rendered page,
but serves as a doctest to make sure the code example runs. The ... lines
are meaningful here, they will match anything in the output test.

.. testoutput:: mod
:hide:

...
Optimize a model with 14 rows, 72 columns, and 72 nonzeros
...
Optimal objective
...

The model is solved as an LP/MIP/QP by Gurobi.

.. You can include the full Gurobi log output here for the curious reader.
It will be visible as a collapsible section.

.. collapse:: View Gurobi Logs

.. code-block:: text

Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (mac64[x86])
Optimize a model with ...
Best obj ... Best bound ...

|

Solution
--------

Show the solution. One way is to use doctests to display simple shell outputs
(see the workforce example). This can be done simply by pasting outputs
directly from a python shell. Another option is to include and display figures
(see the graph matching examples).

.. doctest:: mod
:options: +NORMALIZE_WHITESPACE

>>>
3 changes: 3 additions & 0 deletions src/gurobi_optimods/data/commodities.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Commodity, Origin, Destination, Demand
0, 0, 4, 10
1, 2, 4, 15
82 changes: 82 additions & 0 deletions src/gurobi_optimods/data/network_flow.gml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
graph [
directed 1
node [
id 0
label "0"
demand 20
]
node [
id 1
label "1"
demand 0
]
node [
id 2
label "2"
demand 0
]
node [
id 3
label "3"
demand -5
]
node [
id 4
label "4"
demand -15
]
edge [
source 0
target 1
capacity 15
cost 4
]
edge [
source 0
target 2
capacity 8
cost 4
]
edge [
source 1
target 3
capacity 4
cost 2
]
edge [
source 1
target 2
capacity 20
cost 2
]
edge [
source 1
target 4
capacity 10
cost 6
]
edge [
source 2
target 3
capacity 15
cost 1
]
edge [
source 2
target 4
capacity 5
cost 3
]
edge [
source 3
target 4
capacity 20
cost 2
]
edge [
source 4
target 2
capacity 4
cost 3
]
]
32 changes: 31 additions & 1 deletion src/gurobi_optimods/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"""

import pathlib

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.sparse as sp
import csv

try:
import networkx as nx
Expand All @@ -18,6 +19,8 @@
_convert_pandas_to_digraph,
_convert_pandas_to_scipy,
)
import networkx as nx


DATA_FILE_DIR = pathlib.Path(__file__).parent / "data"

Expand Down Expand Up @@ -88,3 +91,30 @@ def load_diet():
foods=pd.read_csv(DATA_FILE_DIR / "diet-foods.csv"),
nutrition_values=pd.read_csv(DATA_FILE_DIR / "diet-values.csv"),
)


def load_commodities():
return pd.read_csv(DATA_FILE_DIR / "commodities.csv")


def load_network_design():
G = nx.DiGraph()
G.add_edge(0, 1, capacity=15, fixed_cost=4, flow_cost=3)
G.add_edge(0, 2, capacity=8, fixed_cost=4, flow_cost=5)
G.add_edge(1, 3, capacity=4, fixed_cost=2, flow_cost=1)
G.add_edge(1, 2, capacity=20, fixed_cost=2, flow_cost=2)
G.add_edge(1, 4, capacity=10, fixed_cost=6, flow_cost=1)
G.add_edge(2, 3, capacity=15, fixed_cost=1, flow_cost=5)
G.add_edge(3, 4, capacity=20, fixed_cost=2, flow_cost=4)
G.add_edge(2, 4, capacity=5, fixed_cost=3, flow_cost=6)
G.add_edge(4, 2, capacity=4, fixed_cost=3, flow_cost=6)
# nx.draw(G, with_labels=True)
# plt.draw() # pyplot draw()
# plt.show()
# nx.write_gml(G, "network_design1.gml")
return G


def _create_feasible_commodities():

return
Loading