A trading company must assign three sales managers to three sales offices. Its objective is to find the assignments that maximize the total yearly sales of all three offices. Naturally, only one person can be assigned to each sales office. The expected yearly sales (in millions of dollars) if each individual is assigned to each office are as follows:

Atlanta Boston Chicago
Tardy 21 25 17
Vincent 14 17 15
Schuldiner 21 19 13

The relocation expense budget for all three moves is $200000. The costs (in thousands of dollars) of relocating each individual to each location are as follows:

Atlanta Boston Chicago
Tardy 65 50 30
Vincent 90 65 85
Schuldiner 100 20 80

Formulate and solve an integer programming model allowing you to decide which individual should be assigned to which office.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# File: solve.py
# Author: Jakub Kozłowicz
# Copyright (c) Jakub Kozłowicz 2023
# MIT License

"""File contain code for solving assignment 16.

It uses the following libraries:
  - pulp.
"""

import enum
import itertools
import sys

from pulp import LpBinary, LpMaximize, LpProblem, LpVariable, lpSum, value

class SalesOffice(enum.StrEnum):
    """Enum for sales offices."""

    ATLANTA = "Atlanta"
    BOSTON = "Boston"
    CHICAGO = "Chicago"

class SalesManager(enum.StrEnum):
    """Enum for sales managers."""

    TARDY = "Tardy"
    VINCENT = "Vincent"
    SCHULDINER = "Schuldiner"

YEARLY_SALES = {
    SalesManager.TARDY: {
        SalesOffice.ATLANTA: 21_000_000,
        SalesOffice.BOSTON: 25_000_000,
        SalesOffice.CHICAGO: 17_000_000,
    },
    SalesManager.VINCENT: {
        SalesOffice.ATLANTA: 14_000_000,
        SalesOffice.BOSTON: 17_000_000,
        SalesOffice.CHICAGO: 15_000_000,
    },
    SalesManager.SCHULDINER: {
        SalesOffice.ATLANTA: 21_000_000,
        SalesOffice.BOSTON: 19_000_000,
        SalesOffice.CHICAGO: 13_000_000,
    },
}

RELOCATION_COSTS = {
    SalesManager.TARDY: {
        SalesOffice.ATLANTA: 65_000,
        SalesOffice.BOSTON: 50_000,
        SalesOffice.CHICAGO: 30_000,
    },
    SalesManager.VINCENT: {
        SalesOffice.ATLANTA: 90_000,
        SalesOffice.BOSTON: 65_000,
        SalesOffice.CHICAGO: 85_000,
    },
    SalesManager.SCHULDINER: {
        SalesOffice.ATLANTA: 100_000,
        SalesOffice.BOSTON: 20_000,
        SalesOffice.CHICAGO: 80_000,
    },
}

MAX_RELOCATION_COST = 200_000

def main() -> int:
    """Main entry point of the program."""
    permutations = list(itertools.product(SalesManager, SalesOffice))

    problem = LpProblem("sales_maximization", LpMaximize)

    # Decision variables
    x = LpVariable.dicts(
        "x",
        permutations,
        0,
        1,
        LpBinary,
    )

    # Objective function
    problem += lpSum(
        [
            YEARLY_SALES[manager][office] * x[(manager, office)]
            for manager, office in permutations
        ]
    )

    # Office Constraints
    for i in SalesManager:
        problem += lpSum([x[(i, j)] for j in SalesOffice]) == 1

    for j in SalesOffice:
        problem += lpSum([x[(i, j)] for i in SalesManager]) == 1

    # Relocation Constraints
    problem += (
        lpSum(
            [
                RELOCATION_COSTS[manager][office] * x[(manager, office)]
                for manager, office in permutations
            ]
        )
        <= MAX_RELOCATION_COST
    )

    # Solve the problem
    problem.solve()

    # Print the results
    print(f"Status: {problem.status}")
    for variant in permutations:
        if x[variant].value() == 1:
            manager, office = variant
            print(f"{manager} is assigned to {office}")

    print(f"Total yearly sales = {value(problem.objective):,}")

    return 0

if __name__ == "__main__":
    sys.exit(main())