Skip to content

Example 2: Assignment Problem

Problem Description

Given a set of companies and products. Each company incurs a specific cost to produce each product.

Product AProduct BProduct CProduct D
Co. A920480650340
Co. B870510700350
Co. C880500720400
Co. D930490680410

Determine which company produce each product. The goal is to assign distinct companies to produce different products such that the total cost is minimized.

Mathematical Model

Variables

xcp :whether company c is assigned to produce product p.

Intermediate Expressions

1. Total Cost

Cost=cCpPCostcpxcp

2. Company Assignment

AssignmentcCompany=pPxcp,cC

3. Product Assignment

AssignmentpProduct=pPxcp,pP

Objective Function

1. Minimize Total Cost

minCost

Constraints

1. Company Assignment Limit

s.t.AssignmentcCompany1,cC

2. Product Assignment Limit

s.t.AssignmentpProduct=1,pP

Expected Result

Company A produces product C, company B produces product D, company C produces product A, and company D produces product B.

Code Implementation

kotlin
import fuookami.ospf.kotlin.utils.concept.*
import fuookami.ospf.kotlin.utils.functional.*
import fuookami.ospf.kotlin.utils.multi_array.*
import fuookami.ospf.kotlin.utils.math.*
import fuookami.ospf.kotlin.core.frontend.variable.*
import fuookami.ospf.kotlin.core.frontend.expression.monomial.*
import fuookami.ospf.kotlin.core.frontend.expression.polynomial.*
import fuookami.ospf.kotlin.core.frontend.expression.symbol.*
import fuookami.ospf.kotlin.core.frontend.inequality.*
import fuookami.ospf.kotlin.core.frontend.model.mechanism.*
import fuookami.ospf.kotlin.core.backend.plugins.scip.*

class Product : AutoIndexed(Product::class)

data class Company(
    val cost: Map<Product, Flt64>
) : AutoIndexed(Company::class)

private val products: List<Product> = ...  // product data
private val companies: List<Company> = ... // company data

// create a model instance
val metaModel = LinearMetaModel("demo2")

// define variables
val x = BinVariable2("x", Shape2(companies.size, products.size))
for (c in companies) {
    for (p in products) {
        x[c, p].name = "${x.name}_${c.index},${p.index}"
    }
}
metaModel.add(x)

// define intermediate expressions
val cost = LinearExpressionSymbol(flatSum(companies) { c ->
    products.map { p ->
        c.cost[p]?.let { it * x[c, p] }
    }
}, "cost")
metaModel.add(cost)

val assignmentCompany = LinearIntermediateSymbols(
    "assignment_company",
    Shape1(companies.size)
)
for (c in companies) {
    assignmentCompany[c].asMutable() += sumVars(products) { p -> 
        c.cost[p]?.let { x[c, p] } 
    }
}
metaModel.add(assignmentCompany)

val assignmentProduct = flatMap(
    "assignment_product",
    products,
    { p -> sumVars(companies) { c -> c.cost[p]?.let { x[c, p] } } }
)
metaModel.add(assignmentProduct)

// define objective function
metaModel.minimize(cost)

// define constraints
for (c in companies) {
    metaModel.addConstraint(assignmentCompany[c] leq 1)
}
for (p in products) {
    metaModel.addConstraint(assignmentProduct[p] eq 1)
}

// solve the model
val solver = ScipLinearSolver()
when (val ret = solver(metaModel)) {
    is Ok -> {
        metaModel.tokens.setSolution(ret.value.solution)
    }

    is Failed -> {}
}

// parse results
val solution = ArrayList<Pair<Company, Product>>()
for (token in metaModel.tokens.tokens) {
    if (token.result!! eq Flt64.one && token.variable.belongsTo(x)) {
        val company = companies[token.variable.vectorView[0]]
        val product = products[token.variable.vectorView[1]]
        solution.add(company to product)
    }
}

For the complete implementation, please refer to: