Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Solve Assignment Problem With Constraints?

Tags:

algorithm

Assume there are N people and M tasks are there and there is a cost matrix which tells when a task is assigned to a person how much it cost.

Assume we can assign more than one task to a person.

It means we can assign all of the tasks to a person if it leads to minimum cost. I know this problem can be solved using various techniques. Some of them are below.

  • Bit Masking
  • Hungarian Algorithm
  • Min Cost Max Flow
  • Brute Force( All permutations M!)

Question: But what if we put a constraint like only consecutive tasks can be assigned to a person. 

    T1   T2   T3
P1   2    2    2
P2   3    1    4

Answer: 6 rather than 5

Explanation:

We might think that , P1->T1, P2->T2, P1->T3 = 2+1+2 =5 can be answer but it is not because (T1 and T3 are consecutive so can not be assigned to P1)

P1->T1, P1->T2, P1-T3 = 2+2+2 = 6

How to approach solving this problem?

like image 662
chindirala sampath kumar Avatar asked Jun 29 '20 17:06

chindirala sampath kumar


2 Answers

You can solve this problem using ILP.

Here is an OPL-like pseudo-code:

**input: 
two integers N, M        // N persons, M tasks
a cost matrix C[N][M] 

**decision variables: 
X[N][M][M]     // An array with values in {0, 1}
               // X[i][j][k] = 1 <=> the person i performs the tasks j to k 
                              

**constraints:
// one person can perform at most 1 sequence of consecutive tasks
for all i in {1, N}, sum(j in {1, ..., M}, k in {1, ..., M}) X[i][j][k] <= 1

// each task is performed exactly once
for all t in {1, M}, sum(i in {1, ..., N}, j in {1, ..., t}, k in {t, ..., M}) X[i][j][k] = 1

// impossible tasks sequences are discarded
for all i in {1, ..., N}, for all j in {1, ..., M}, sum(k in {1, ..., j-1}) X[i][j][k] = 0

**objective function:
minimize sum(i, j, k) X[i][j][k] * (sum(t in {j, ..., k}) C[t])

I think that ILP could be the tool of choice here, since more often that not scheduling and production-planning problems are solved using it.
If you do not have experience coding LP programs, don't worry, it is much easier than it looks like, and this problem is rather easy and nice to get started.

There also exists a stackexchange dedicated to this kind of problems and solutions, the OR stack exchange.

like image 148
m.raynal Avatar answered Sep 29 '22 06:09

m.raynal


This looks np-complete to me. If I am correct, there is not going to be a universally quick solution, and the best one can do is approach this problem using the best possible heuristics.

One approach you did not mention is a constructive approach using A* search. In this case, the search in would move along the matrix from left to right, adding candidate items to a priority queue with every step. Each item in the queue would consist of the current column index, the total cost expended so far, and the list of people who have acted so far. The remaining-cost heuristic for any given state would be the sum of the columnar minima for all remaining columns.

I'm certain that this can find a solution, I'm just not sure it is the best approach. Some quick Googling shows that A* has been applied to several types of scheduling problems though.

Edit: Here is an implementation.

public class OrderedTasks {

private class State {
    private final State prev;
    private final int position;
    private final int costSoFar;
    private final int lastActed;
    
    public State(int position, int costSoFar, int lastActed, State prev) {
        super();
        this.prev = prev;
        this.lastActed = lastActed;
        this.position = position;
        this.costSoFar = costSoFar;
    }

    public void getNextSteps(int[] task, Consumer<State> consumer) {
        Set<Integer> actedSoFar = new HashSet<>();
        State prev = this.prev;
        if (prev != null) {
            for (; prev!=null; prev=prev.prev) {
                actedSoFar.add(prev.lastActed);
            }
        }
        for (int person=0; person<task.length; ++person) {
            if (actedSoFar.contains(person) && this.lastActed!=person) {
                continue;
            }
            consumer.accept(new State(position+1,task[person]+this.costSoFar,
                    person, this));
        }
    }
}

public int minCost(int[][] tasksByPeople) {
    int[] cumulativeMinCost = getCumulativeMinCostPerTask(tasksByPeople);
    Function<State, Integer> totalCost = state->state.costSoFar+(state.position<cumulativeMinCost.length? cumulativeMinCost[state.position]: 0);
    PriorityQueue<State> pq = new PriorityQueue<>((s1,s2)->{
        return Integer.compare(totalCost.apply(s1), totalCost.apply(s2));
    });
    State state = new State(0, 0, -1, null);
    for (; state.position<tasksByPeople.length; state = pq.poll()) {
        state.getNextSteps(tasksByPeople[state.position], pq::add);
    }
    return state.costSoFar;
}

private int[] getCumulativeMinCostPerTask(int[][] tasksByPeople) {
    int[] result = new int[tasksByPeople.length];
    int cumulative = 0;
    for (int i=tasksByPeople.length-1; i>=0; --i) {
        cumulative += minimum(tasksByPeople[i]);
        result[i] = cumulative;
    }
    return result;
}

private int minimum(int[] arr) {
    if (arr.length==0) {
        throw new RuntimeException("Not valid for empty arrays.");
    }
    int min = arr[0];
    for (int i=1; i<arr.length; ++i) {
        min = Math.min(min, arr[i]);
    }
    return min;
}

public static void main(String[] args) {
    OrderedTasks ot = new OrderedTasks();
    System.out.println(ot.minCost(new int[][]{{2, 3},{2,1},{2,4},{2,2}}));
}
}
like image 44
John Angland Avatar answered Sep 29 '22 07:09

John Angland