Quantcast
Channel: Yet Another Math Programming Consultant
Viewing all articles
Browse latest Browse all 809

Assignment problem with a wrinkle formulated as a network problem

$
0
0

In a previous post [1] I discussed a simple problem, but not so easy to solve for some larger data sets. Basically, it was an assignment problem with an extra condition. The problem was a follows:

Consider two arrays \(\color{darkblue}a_i\) (length \(\color{darkblue}m\)) and \(\color{darkblue}b_j\) (length \(\color{darkblue}n\)) with \(\color{darkblue}m \lt \color{darkblue}n\). Assign all values \(\color{darkblue}a_i\) to a \(\color{darkblue}b_j\) such that:

  • Each \(\color{darkblue}b_j\) can have 0 or 1 \(\color{darkblue}a_i\) assigned to it.
  • The assignments need to maintain the original order of \(\color{darkblue}a_i\). I.e. if \(\color{darkblue}a_i \rightarrow \color{darkblue}b_j\) then \(\color{darkblue}a_{i+1}\) must be assigned to a slot in \(\color{darkblue}b\) that is beyond slot \(j\). In the picture below that means that arrows cannot cross.
  • Do this while minimizing the sum of the products.


In [1], I attacked this as a mixed-integer programming problem. In this post, I want to see if we can solve this as a network problem. This was largely inspired by the comments in [1].

Graph 


We start with the nodes. We denote the nodes by \(\color{darkblue}n_{i,j}\) representing: \(\color{darkblue}a_i\) is assigned to \(\color{darkblue}b_j\). Not all assignments are possible. For instance, we cannot assign (\color{darkblue}a_2\) to \(\color{darkblue}b_1\). That means: node  \(\color{darkblue}n_{2,1}\)  does not exist.

We also need a source node and a sink node.

The arcs indicate: after assigning \(\color{darkblue}a_i \rightarrow \color{darkblue}b_j\) we need to assign the next item \(\color{darkblue}a_{i+1}\rightarrow \color{darkblue}b_{j+k}\) for some \(k\ge 1\). In addition we need to connect the source node to all nodes with \(i=1\) and the sink node to all node with \(i=3\) (our last \(\color{darkblue}a_i\)).  So our network looks like:

Network representation


Note how any arc not connected to the sink or source node, goes to the right and downwards.

We have costs for visiting each node: \(\color{darkblue}a_i\cdot\color{darkblue}b_j\).  As we want to formulate this as a shortest path problem, we need to allocate these costs to arcs. I used the incoming arcs for this. So any arc \(e_{i,j,i',j'}\) gets a cost \(\color{darkblue}a_{i'}\cdot\color{darkblue}b_{j'}\).  The arcs to the sink node have a zero cost. As you can see: because the nodes have two indices, the arcs have four! That will be fun.

Implementation

 
I implemented the network model in GAMS and solved it as an LP. 

The data for our tiny data set looks like:

----     15 SET i  

i1, i2, i3


---- 15 SET j

j1, j2, j3, j4, j5, j6


---- 15 PARAMETER a

i1 1.000, i2 2.000, i3 3.000


---- 15 PARAMETER b

j1 4.000, j2 9.000, j3 5.000, j4 3.000, j5 2.000, j6 10.000


From this, we generate our nodes and arcs:

----     31 SET n  nodes

j1 j2 j3 j4 j5 j6

src YES
i1 YES YES YES YES
i2 YES YES YES YES
i3 YES YES YES YES
snk YES


---- 32 PARAMETER numnodes = 14.000

---- 41 SET e arcs

src. .i1 .j1, src. .i1 .j2, src. .i1 .j3, src. .i1 .j4, i1 .j1.i2 .j2, i1 .j1.i2 .j3
i1 .j1.i2 .j4, i1 .j1.i2 .j5, i1 .j2.i2 .j3, i1 .j2.i2 .j4, i1 .j2.i2 .j5, i1 .j3.i2 .j4
i1 .j3.i2 .j5, i1 .j4.i2 .j5, i2 .j2.i3 .j3, i2 .j2.i3 .j4, i2 .j2.i3 .j5, i2 .j2.i3 .j6
i2 .j3.i3 .j4, i2 .j3.i3 .j5, i2 .j3.i3 .j6, i2 .j4.i3 .j5, i2 .j4.i3 .j6, i2 .j5.i3 .j6
i3 .j3.snk. , i3 .j4.snk. , i3 .j5.snk. , i3 .j6.snk.


---- 42 PARAMETER numarcs = 28.000

---- 47 PARAMETER c cost of arcs

src. .i1.j1 4.000, src. .i1.j2 9.000, src. .i1.j3 5.000, src. .i1.j4 3.000, i1 .j1.i2.j2 18.000
i1 .j1.i2.j3 10.000, i1 .j1.i2.j4 6.000, i1 .j1.i2.j5 4.000, i1 .j2.i2.j3 10.000, i1 .j2.i2.j4 6.000
i1 .j2.i2.j5 4.000, i1 .j3.i2.j4 6.000, i1 .j3.i2.j5 4.000, i1 .j4.i2.j5 4.000, i2 .j2.i3.j3 15.000
i2 .j2.i3.j4 9.000, i2 .j2.i3.j5 6.000, i2 .j2.i3.j6 30.000, i2 .j3.i3.j4 9.000, i2 .j3.i3.j5 6.000
i2 .j3.i3.j6 30.000, i2 .j4.i3.j5 6.000, i2 .j4.i3.j6 30.000, i2 .j5.i3.j6 30.000
 

Our nodes are two-dimensional, so somewhat artificially, the source and the sink node are denoted by \(\color{darkblue}n_{'src',''}\) and \(\color{darkblue}n_{'snk',''}\). Again, the costs of visiting a node are allocated to the incoming arcs. Note that zero costs are not printed (the parameter is stored as a sparse data structure, so zero and does not exist is the same).

The LP model for this problem can look like:

Shortest Path LP Model
\[\begin{align}\min& \sum_{i,j,i',j'}\color{darkred}f_{i,j,i',j'}\cdot\color{darkblue}c_{i,j,i',j'}\\ &\sum_{i',j'|e(i',j',i,j)} \color{darkred}f_{i',j',i,j} + \color{darkblue}g_{i,j} = \sum_{i',j'|e(i,j,i',j')} \color{darkred}f_{i,j,i',j'} &&\forall \color{darkblue}n_{i,j}\\ & \color{darkred}f_{i,j,i',j'} \in [0,1] \end{align}\]


where \(\color{darkred}f\) is our flow variable and \(\color{darkblue}g\) is exogenous inflow. In our case:\[\color{darkblue}g_{i,j} = \begin{cases} 1 & \text{for the source node}\\ -1 & \text{for the sink node} \\ 0 & \text{for all other nodes}\end{cases}\]

The results are:
 
----     75 VARIABLE cost.L                =       16.000  objective

---- 75 VARIABLE f.L flow

src. .i1 .j1 1.000, i1 .j1.i2 .j4 1.000, i2 .j4.i3 .j5 1.000, i3 .j5.snk. 1.000


---- 79 SET assign assignments recovered from flows

j1 j4 j5

i1 YES
i2 YES
i3 YES


There are different ways to solve a shortest path problem. Here we look at three:
  1. Cplex default LP solver (dual simplex)
  2. Cplex network solver
  3. Sparse version of Dijkstra's algorithm from scipy.sparse.csgraph.
The \(m=100, n=1000\) problem is a bit large for Cplex to handle as an LP, but let's do the \(m=50, n=500\) data set. Here are the results:


MIP ModelNetwork LP ModelDijkstra
solverMIPLP defaultNetworkSparse
a/b length50/50050/50050/50050/500
nodes/arcs22,552/4,995,27622,552/4,995,27622,552/4,995,276
rows/columns/nz650/25,025/100,14722,553/4,995,277/14,985,37822,553/4,995,277/14,985,378
objective6466.6736466.6736466.6736466.673
time59721total:38
presolve:32
extract network:2
solve network:3
0.1
b&b nodes5,023
iterations60,19911,535100,446


This looks better. My setup is a bit slow using unoptimized Python code, but the raw solve is very fast. When trying the \(m=100,n=1000\) data set, I got:


MIP ModelDijkstra
a/b length100/1000100/1000
variables/constraints1,300/100,100
nodes/arcs90,102/40,230,551
objective14,371.45514,371.455
time1,8530.9


Note that the shortest path version would result in an LP with 90k rows and 40 million columns. That is very large. So here a specialized shortest path algorithm is far superior to more general tools.


References



Viewing all articles
Browse latest Browse all 809

Trending Articles