Skip to content

Example 13: Two-Stage Transportation Problem

Problem Description

Transport goods from three distribution centers to five dealers. Transportation costs are determined based on the distance from origin to destination, independent of truck load capacity. The summarized distances between distribution centers and dealers, along with corresponding monthly supply and demand, are shown in the following table:

Distribution CenterDealer ADealer BDealer CDealer DDealer ESupply
1100mile150mile200mile140mile35mile400t
250mile70mile60mile65mile80mile200t
340mile90mile100mile150mile130mile150t
Demand100t200t150t160t140t--

The transportation cost per truck per mile is fixed. Determine the optimal transportation plan while satisfying the following conditions:

  1. Transportation volume from distribution centers does not exceed their supply;
  2. Each dealer's demand must be met;
  3. Truck load does not exceed the upper limit of 18t.

Mathematical Model

Variables

xij: Transportation volume from distribution center i to dealer j.

yij: Number of trucks used for transportation from distribution center i to dealer j.

Intermediate Values

1. Total Transportation Volume from Distribution Center

Transi=jDxij,iDC

2. Total Volume Received by Dealer

Receivej=iDCxij,jD

3. Total Transportation Cost

Cost=iDC,jDdijyij

Objective Function

1. Minimize Transportation Cost

minCost

Constraints

1. Transportation Volume Does Not Exceed Distribution Center Supply

s.t.TransiSupplyi,iDC

2. Meet Dealer Demand

s.t.ReceivejDemandj,jD

3. Truck Load Does Not Exceed Upper Limit

s.t.yijCapacityxij,iDC,jD

Expected Results

Distribution CenterDealer ADealer BDealer CDealer DDealer E
1100t160t140t
250t150t
3150t

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.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 Dealer(
    val demand: UInt64
) : AutoIndexed(Dealer::class)

data class DistributionCenter(
    val supply: UInt64,
    val distance: Map<Dealer, UInt64>
) : AutoIndexed(DistributionCenter::class)

val carCapacity = UInt64(18)
val dealers: List<Dealer> = ... // Dealer list
val distributionCenters: List<DistributionCenter> = ... // Distribution center list

// Create model instance
val metaModel = LinearMetaModel("demo13")

// Define variables
val x = UIntVariable2("x", Shape2(dealers.size, distributionCenters.size))
metaModel.add(x)

val y = UIntVariable2("y", Shape2(dealers.size, distributionCenters.size))
metaModel.add(y)

// Define intermediate values
val trans = LinearIntermediateSymbols1("trans", Shape1(distributionCenters.size)) { i, _ ->
    val distributionCenter = distributionCenters[i]
    LinearExpressionSymbol(
        sum(x[_a, distributionCenter]),
        "trans_${distributionCenter.index}"
    )
}
metaModel.add(trans)

val receive = LinearIntermediateSymbols1("receive", Shape1(dealers.size)) { i, _ ->
    val dealer = dealers[i]
    LinearExpressionSymbol(
        sum(x[dealer, _a]),
        "receive_${dealer.index}"
    )
}
metaModel.add(receive)

val cost = LinearExpressionSymbol(
    sum(dealers.flatMap { dealer ->
        distributionCenters.mapNotNull { distributionCenter ->
            val distance = distributionCenter.distance[dealer] ?: return@mapNotNull null
            distance * y[dealer, distributionCenter]
        }
    }),
    "cost"
)
metaModel.add(cost)

// Define objective function
metaModel.minimize(cost, "cost")

// Define constraints
for (distributionCenter in distributionCenters) {
    metaModel.addConstraint(
        trans[distributionCenter] leq distributionCenter.supply,
        "supply_${distributionCenter.index}"
    )
}

for (dealer in dealers) {
    metaModel.addConstraint(
        receive[dealer] geq dealer.demand,
        "demand_${dealer.index}"
    )
}

for (dealer in dealers) {
    for (distributionCenter in distributionCenters) {
        metaModel.addConstraint(
            x[dealer, distributionCenter] leq carCapacity * y[dealer, distributionCenter],
            "car_limit_${dealer.index}_${distributionCenter.index}"
        )
    }
}

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

    is Failed -> {}
}

// Parse results
val solution: MutableMap<DistributionCenter, MutableMap<Dealer, UInt64>> = hashMapOf()
for (token in metaModel.tokens.tokens) {
    if (token.result!! geq Flt64.one && token.variable belongsTo x) {
        val vector = token.variable.vectorView
        val dealer = dealers[vector[0]]
        val distributionCenter = distributionCenters[vector[1]]
        solution.getOrPut(distributionCenter) { hashMapOf() }[dealer] = token.result!!.round().toUInt64()
    }
}

Complete Implementation Reference: