Skip to content

Example 4: Ingredient Problem

Problem Description

There are some products and some raw materials, each raw material has a given available quantity, each product has a revenue, each product has a maximum production quantity, and each product requires multiple types of raw materials.

Raw Material ARaw Material B
Availible Quantity248
Product AProduct B
Profit54
Product AProductB
Max Yield32
Raw Material ARaw Material B
Product A61
Product B42

Determine the production quantity for each product to maximize total revenue, while satisfying the following conditions:

  1. The difference in production quantities between any two products must not exceed one unit.

Mathematical Model

Variables

xp: yield of product p

Intermediate Expressions

1. Total Profit

Profit=pPProfitpxp

2. Use of Materials

Usem=pPUsepmxp,mM

Objective Function

1. Maximize Total Profit

maxProfit

Constraints

1. Yield Limit

s.t.xpYieldpMax,pP

2. Use Limit

s.t.UsemAvailablem,mM

3. Yield Difference Limit

s.t.xpxpDiffMax,(p,p)(P2ΔP)

Expected Result

Product A yields 83 units, product B yieldp 53 units.

Code Implementation

kotlin
import fuookami.ospf.kotlin.utils.math.*
import fuookami.ospf.kotlin.utils.concept.*
import fuookami.ospf.kotlin.utils.functional.*
import fuookami.ospf.kotlin.utils.multi_array.*
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.*

data class Material(
    val available: Flt64
) : AutoIndexed(Material::class)

data class Product(
     val profit: Flt64,
    val maxYield: Flt64,
    val use: Map<Material, Flt64>
) : AutoIndexed(Product::class)

val materials: List<Material> = ... // material data
val products: List<Product> = ...  // product data
val maxDiff = Int64(1)

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

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

// define intermediate expressions
val profit = LinearExpressionSymbol(sum(products) { 
    p -> p.profit * x[p] 
}, "profit")
metaModel.add(profit)

val use = LinearIntermediateSymbols1("use", Shape1(materials.size)) { m, _ ->
    val material = materials[m]
    val ps = products.filter { it.use.contains(material) }
    LinearExpressionSymbol(
        sum(ps) { p -> p.use[material]!! * x[p] },
        "use_${m}"
    )
}
metaModel.add(use)

// define objective function
metaModel.maximize(profit, "profit")

// define constraints
for (p in products) {
    x[p].range.ls(p.maxYield)
}

for (m in materials) {
    metaModel.addConstraint(use[m] leq m.available)
}

for (p1 in products) {
    for (p2 in products) {
        if (p1.index == p2.index) {
            continue
        }
        metaModel.addConstraint((x[p1] - x[p2]) leq maxDiff)
    }
}

// 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 = HashMap<Material, Flt64>()
for (token in metaModel.tokens.tokens) {
    if (token.result!! eq Flt64.one && token.variable.belongsTo(x)) {
        solution[materials[token.variable.vectorView[0]]] = token.result!!
    }
}

For the complete implementation, please refer to: