示例 17:车辆路径问题
问题描述
车辆路径问题( vehicle routing problem,
对于
数学模型
集合
参数
变量
中间值
中间值
1. 从配送中心出发(隐含车辆是否使用)
2. 返回配送中心
3. 车辆进入节点的流量
4. 车辆流出节点的流量
5. 节点得到的服务
6. 车辆载重量限制
目标
1. 车辆使用成本最少
2. 运输成本最低
约束
1. 表示车辆从配送中心出发只能到达一个节点(车辆必须从配送中心出发或者不使用)
2. 表示流量平衡约束,即车辆到达一个节点,则必须从该节点出来
3. 表示如果车辆被使用,则必须回到配送中心
4. 表示每个节点必须得到服务
5. 表示每两个节点访问之间的到达时间关系(该约束同时起到了去除回路的作用,消除了单车辆行驶中生成的不可行回路)
6. 表示节点接收服务的时间窗约束
7. 表示不能违反的车辆载重约束
代码实现
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.*
sealed interface Node : Indexed {
val demand: UInt64 get() = UInt64.zero
val position: Point2
val timeWindow: ValueRange<UInt64>
fun distance(other: Node): Flt64 {
return position.distance(other.position)
}
fun cost(other: Node): Flt64 {
return distance(other)
}
fun time(other: Node): Flt64 {
return distance(other)
}
}
data class OriginNode(
override val position: Point2,
override val timeWindow: ValueRange<UInt64>
) : Node, AutoIndexed(Node::class)
data class EndNode(
override val position: Point2,
override val timeWindow: ValueRange<UInt64>
) : Node, AutoIndexed(Node::class)
data class DemandNode(
override val position: Point2,
override val timeWindow: ValueRange<UInt64>,
override val demand: UInt64,
val serviceTime: UInt64
) : Node, AutoIndexed(Node::class)
data class Vehicle(
val capacity: UInt64,
val fixedUsedCost: UInt64,
) : AutoIndexed(Vehicle::class)
val nodes: List<Node> = ... // 节点列表
val vehicles: List<Vehicle> = ... // 车辆列表
// 创建模型实例
val metaModel = LinearMetaModel("demo17")
// 定义变量
val x = BinVariable3(
"x",
Shape3(nodes.size, nodes.size, vehicles.size)
)
for (n1 in nodes) {
for (n2 in nodes) {
for (v in vehicles) {
val xi = x[n1, n2, v]
if (n1 !is EndNode && n2 !is OriginNode && n1 != n2) {
xi.name = "${x.name}_(${n1.index},${n2.index},${v.index})"
metaModel.add(xi)
} else {
xi.range.eq(false)
}
}
}
}
val s = URealVariable2(
"s",
Shape2(nodes.size, vehicles.size)
)
for (n in nodes) {
for (v in vehicles) {
val si = s[n, v]
si.name = "${s.name}_(${n.index}_${v.index})"
}
}
metaModel.add(s)
// 定义中间值
val origin = LinearIntermediateSymbols1(
"origin",
Shape1(vehicles.size)
) { i, _ ->
val v = vehicles[i]
LinearExpressionSymbol(
sum(nodes.filterIsInstance<OriginNode>().flatMap { n1 -> x[n1, _a, v] }),
"origin_${v.index}"
)
}
metaModel.add(origin)
val destination = LinearIntermediateSymbols1(
"destination",
Shape1(vehicles.size)
) { i, _ ->
val v = vehicles[i]
LinearExpressionSymbol(
sum(nodes.filterIsInstance<EndNode>().flatMap { n2 -> x[_a, n2, v] }),
"destination_${v.index}"
)
}
metaModel.add(destination)
val inFlow = LinearIntermediateSymbols2(
"in",
Shape2(nodes.size, vehicles.size)
) { _, vec ->
val n2 = nodes[vec[0]]
val v = vehicles[vec[1]]
if (n2 is OriginNode) {
LinearExpressionSymbol(
LinearPolynomial(),
"in_(${n2.index},${v.index})"
)
} else {
LinearExpressionSymbol(
sum(nodes.filterIsNotInstance<EndNode, Node>().map { n1 -> x[n1, n2, v] }),
"in_(${n2.index},${v.index})"
)
}
}
metaModel.add(inFlow)
val outFlow = LinearIntermediateSymbols2(
"out",
Shape2(nodes.size, vehicles.size)
) { _, vec ->
val n1 = nodes[vec[0]]
val v = vehicles[vec[1]]
if (n1 is EndNode) {
LinearExpressionSymbol(
LinearPolynomial(),
"out_(${n1.index},${v.index})"
)
} else {
LinearExpressionSymbol(
sum(nodes.filterIsNotInstance<OriginNode, Node>().map { n2 -> x[n1, n2, v] }),
"out_(${n1.index},${v.index})"
)
}
}
metaModel.add(outFlow)
val service = LinearIntermediateSymbols1(
"service",
Shape1(nodes.size)
) { i, _ ->
val n1 = nodes[i]
if (n1 is OriginNode || n1 is EndNode) {
LinearExpressionSymbol(
LinearPolynomial(),
"service_(${n1.index})"
)
} else {
LinearExpressionSymbol(
sum(nodes.filterIsNotInstance<OriginNode, Node>().flatMap { n2 -> x[n1, n2, _a] }),
"service_(${n1.index})"
)
}
}
metaModel.add(service)
val capacity = LinearIntermediateSymbols1(
"capacity",
Shape1(vehicles.size)
) { i, _ ->
val v = vehicles[i]
LinearExpressionSymbol(
sum(nodes.flatMap { n1 ->
nodes.mapNotNull { n2 ->
(n2 as? DemandNode)?.demand?.let { it * x[n1, n2, v] }
}
}),
"capacity_${v.index}"
)
}
metaModel.add(capacity)
// 定义目标函数
metaModel.minimize(
sum(vehicles.map { v -> v.fixedUsedCost * origin[v] }),
"used cost"
)
metaModel.minimize(
sum(nodes.flatMap { n1 ->
nodes.map { n2 ->
n1.cost(n2) * sum(x[n1, n2, _a])
}
}),
"trans cost"
)
// 定义约束
for (v in vehicles) {
metaModel.addConstraint(
origin[v] leq 1,
"origin_${v.index}"
)
}
for (n in nodes.filterIsInstance<DemandNode>()) {
for (v in vehicles) {
metaModel.addConstraint(
inFlow[n, v] eq outFlow[n, v],
"balance_${n.index}_${v.index}",
)
}
}
for (v in vehicles) {
metaModel.addConstraint(
destination[v] leq 1,
"destination_${v.index}"
)
}
for (n in nodes.filterIsInstance<DemandNode>()) {
metaModel.addConstraint(
service[n] eq 1,
"service_${n.index}"
)
}
val m = nodes.filterIsInstance<EndNode>().maxOf { it.timeWindow.upperBound.value.unwrap() }
for (n1 in nodes) {
for (n2 in nodes) {
for (v in vehicles) {
metaModel.addConstraint(
s[n1, v] + ((n1 as? DemandNode)?.serviceTime ?: UInt64.zero) + n1.time(n2) - m * (1 - x[n1, n2, v]) leq s[n2, v],
"time_window_${n1.index}_${n2.index}_${v.index}"
)
}
}
}
for (n in nodes) {
for (v in vehicles) {
metaModel.addConstraint(
s[n, v] geq n.timeWindow.lowerBound.value.unwrap(),
"time_window_lb_${n.index}_${v.index}"
)
metaModel.addConstraint(
s[n, v] leq n.timeWindow.upperBound.value.unwrap(),
"time_window_ub_${n.index}_${v.index}"
)
}
}
for (v in vehicles) {
metaModel.addConstraint(
capacity[v] leq v.capacity,
"capacity_${v.index}"
)
}
// 调用求解器求解
val solver = ScipLinearSolver()
when (val ret = solver(metaModel)) {
is Ok -> {
metaModel.tokens.setSolution(ret.value.solution)
}
is Failed -> {
return Failed(ret.error)
}
}
// 解析结果
val route: MutableMap<Vehicle, MutableList<Pair<Node, Node>>> = HashMap()
val time: MutableMap<Vehicle, MutableMap<Node, UInt64>> = HashMap()
for (token in metaModel.tokens.tokens) {
if (token.result!! geq Flt64.one && token.variable belongsTo x) {
val vector = token.variable.vectorView
val n1 = nodes[vector[0]]
val n2 = nodes[vector[1]]
val v = vehicles[vector[2]]
route.getOrPut(v) { ArrayList() }.add(n1 to n2)
}
}
for (token in metaModel.tokens.tokens) {
if (token.result!! geq Flt64.one && token.variable belongsTo s) {
val vector = token.variable.vectorView
val n = nodes[vector[0]]
val v = vehicles[vector[1]]
time.getOrPut(v) { HashMap() }[n] = token.result!!.round().toUInt64()
}
}
完整实现请参考: