Skip to content

Commit 3848731

Browse files
committed
feature: move examples to main repository.
1 parent fc5b6e5 commit 3848731

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2821
-0
lines changed

examples/ospf-kotlin-example/pom.xml

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<groupId>io.github.fuookami.ospf.kotlin</groupId>
7+
<artifactId>ospf-kotlin-parent</artifactId>
8+
<version>1.0.4</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>ospf-kotlin-example</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>io.github.fuookami.ospf.kotlin</groupId>
17+
<artifactId>ospf-kotlin-starter</artifactId>
18+
<version>${project.version}</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>io.github.fuookami.ospf.kotlin.core.plugin</groupId>
22+
<artifactId>ospf-kotlin-core-plugin-gurobi</artifactId>
23+
<version>${project.version}</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>io.github.fuookami.ospf.kotlin.core.plugin</groupId>
27+
<artifactId>ospf-kotlin-core-plugin-cplex</artifactId>
28+
<version>${project.version}</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>io.github.fuookami.ospf.kotlin.core.plugin</groupId>
32+
<artifactId>ospf-kotlin-core-plugin-scip</artifactId>
33+
<version>${project.version}</version>
34+
</dependency>
35+
<dependency>
36+
<groupId>io.github.fuookami.ospf.kotlin.core.plugin</groupId>
37+
<artifactId>ospf-kotlin-core-plugin-heuristic</artifactId>
38+
<version>${project.version}</version>
39+
</dependency>
40+
</dependencies>
41+
42+
<build>
43+
<sourceDirectory>src/main</sourceDirectory>
44+
<testSourceDirectory>src/test</testSourceDirectory>
45+
</build>
46+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package fuookami.ospf.kotlin.example.column_generation_demo
2+
3+
import fuookami.ospf.kotlin.utils.functional.*
4+
5+
class Demo1 {
6+
suspend operator fun invoke(): Try {
7+
val demo = fuookami.ospf.kotlin.example.column_generation_demo.demo1.CSP()
8+
return demo()
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package fuookami.ospf.kotlin.example.column_generation_demo.demo1
2+
3+
import fuookami.ospf.kotlin.utils.math.*
4+
import fuookami.ospf.kotlin.utils.concept.*
5+
import fuookami.ospf.kotlin.framework.model.*
6+
7+
data class Product(
8+
val length: UInt64,
9+
val demand: UInt64
10+
) : AutoIndexed(Product::class)
11+
12+
data class CuttingPlan(
13+
val products: Map<Product, UInt64>
14+
) : AutoIndexed(CuttingPlan::class)
15+
16+
class SPM : AbstractShadowPriceMap<Product, SPM>()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package fuookami.ospf.kotlin.example.column_generation_demo.demo1
2+
3+
import fuookami.ospf.kotlin.utils.math.*
4+
import fuookami.ospf.kotlin.utils.functional.*
5+
6+
class CSP {
7+
private val length = UInt64(1000UL)
8+
private val products: List<Product> = arrayListOf(
9+
Product(UInt64(450UL), UInt64(97UL)),
10+
Product(UInt64(360UL), UInt64(610UL)),
11+
Product(UInt64(310UL), UInt64(395UL)),
12+
Product(UInt64(140UL), UInt64(211UL)),
13+
)
14+
15+
suspend operator fun invoke(): Try {
16+
val initialCuttingPlans = InitialSolutionGenerator(length, products)
17+
when (initialCuttingPlans) {
18+
is Failed -> {
19+
return Failed(initialCuttingPlans.error)
20+
}
21+
22+
is Ok -> {}
23+
}
24+
val rmp = RMP(length, products, initialCuttingPlans.value)
25+
val sp = SP()
26+
var i = UInt64.zero
27+
while (true) {
28+
val spm = rmp(i)
29+
when (spm) {
30+
is Failed -> {
31+
return Failed(spm.error)
32+
}
33+
34+
is Ok -> {}
35+
}
36+
val newCuttingPlan = sp(i, length, products, spm.value)
37+
when (newCuttingPlan) {
38+
is Failed -> {
39+
return Failed(newCuttingPlan.error)
40+
}
41+
42+
is Ok -> {}
43+
}
44+
if (reducedCost(newCuttingPlan.value, spm.value) geq Flt64.zero
45+
|| !rmp.addColumn(newCuttingPlan.value)
46+
) {
47+
break
48+
}
49+
++i
50+
}
51+
when (val solution = rmp()) {
52+
is Failed -> {
53+
return Failed(solution.error)
54+
}
55+
56+
is Ok -> {
57+
println(
58+
solution.value.asIterable().joinToString(";") {
59+
"${
60+
it.key.products.asIterable()
61+
.joinToString(",") { product -> "${product.key.length} * ${product.value}" }
62+
}: ${it.value}"
63+
})
64+
}
65+
}
66+
return Ok(success)
67+
}
68+
69+
private fun reducedCost(cuttingPlan: CuttingPlan, shadowPrices: SPM) = Flt64.one -
70+
cuttingPlan.products.sumOf { (shadowPrices(it.key) * it.value.toFlt64()) }
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package fuookami.ospf.kotlin.example.column_generation_demo.demo1
2+
3+
import java.util.*
4+
import fuookami.ospf.kotlin.utils.math.*
5+
import fuookami.ospf.kotlin.utils.functional.*
6+
import fuookami.ospf.kotlin.utils.multi_array.*
7+
import fuookami.ospf.kotlin.core.frontend.variable.*
8+
import fuookami.ospf.kotlin.core.frontend.expression.monomial.*
9+
import fuookami.ospf.kotlin.core.frontend.expression.polynomial.*
10+
import fuookami.ospf.kotlin.core.frontend.expression.symbol.*
11+
import fuookami.ospf.kotlin.core.frontend.inequality.*
12+
import fuookami.ospf.kotlin.core.frontend.model.mechanism.*
13+
import fuookami.ospf.kotlin.core.backend.plugins.cplex.*
14+
import fuookami.ospf.kotlin.core.backend.plugins.gurobi.*
15+
import fuookami.ospf.kotlin.framework.model.*
16+
import fuookami.ospf.kotlin.framework.solver.*
17+
18+
data class ProductDemandShadowPriceKey(
19+
val product: Product
20+
) : ShadowPriceKey(ProductDemandShadowPriceKey::class)
21+
22+
class RMP(
23+
private val length: UInt64,
24+
private val products: List<Product>,
25+
initialCuttingPlans: List<CuttingPlan>
26+
) {
27+
private val cuttingPlans: MutableList<CuttingPlan> = ArrayList()
28+
private val x: MutableList<UIntVar> = ArrayList()
29+
private val rest = LinearExpressionSymbol(MutableLinearPolynomial(), "rest")
30+
private val yield = LinearExpressionSymbols1("output", Shape1(products.size)) { v ->
31+
LinearExpressionSymbol(MutableLinearPolynomial(), "output_${v.second[0]}")
32+
}
33+
private val metaModel = LinearMetaModel("demo1")
34+
private val solver: ColumnGenerationSolver =
35+
if (System.getProperty("os.name").lowercase(Locale.getDefault()).contains("win")) {
36+
CplexColumnGenerationSolver()
37+
} else {
38+
GurobiColumnGenerationSolver()
39+
}
40+
41+
init {
42+
metaModel.addSymbol(rest)
43+
metaModel.addSymbols(yield)
44+
45+
metaModel.minimize(rest)
46+
metaModel.registerConstraintGroup("product_demand")
47+
48+
for (product in products) {
49+
metaModel.addConstraint(yield[product] geq product.demand, "product_demand_${product.index}")
50+
}
51+
52+
addColumns(initialCuttingPlans)
53+
}
54+
55+
fun addColumn(cuttingPlan: CuttingPlan, flush: Boolean = true): Boolean {
56+
if (cuttingPlans.find { it.products == cuttingPlan.products } != null) {
57+
return false
58+
}
59+
60+
cuttingPlans.add(cuttingPlan)
61+
val x = UIntVar("x_${cuttingPlan.index}")
62+
x.range.leq(cuttingPlan.products.maxOf { (product, amount) -> product.demand / amount + UInt64.one })
63+
this.x.add(x)
64+
metaModel.addVar(x)
65+
66+
rest.asMutable() += (length - cuttingPlan.products.sumOf { it.key.length * it.value }) * x
67+
rest.flush()
68+
for ((product, amount) in cuttingPlan.products) {
69+
yield[product].asMutable() += amount * x
70+
yield[product].flush()
71+
}
72+
if (flush) {
73+
metaModel.flush()
74+
}
75+
return true
76+
}
77+
78+
fun addColumns(cuttingPlans: List<CuttingPlan>) {
79+
for (cuttingPlan in cuttingPlans) {
80+
addColumn(cuttingPlan, false)
81+
}
82+
metaModel.flush()
83+
}
84+
85+
// solve lp
86+
suspend operator fun invoke(iteration: UInt64): Ret<SPM> {
87+
return when (val result = solver.solveLP("demo1-rmp-$iteration", metaModel, true)) {
88+
is Ok -> {
89+
Ok(extractShadowPriceMap(result.value.dualSolution))
90+
}
91+
92+
is Failed -> {
93+
Failed(result.error)
94+
}
95+
}
96+
}
97+
98+
// solve ip
99+
suspend operator fun invoke(): Ret<Map<CuttingPlan, UInt64>> {
100+
return when (val result = solver.solveMILP("demo1-rmp-ip", metaModel)) {
101+
is Ok -> {
102+
Ok(analyzeSolution(result.value.solution))
103+
}
104+
105+
is Failed -> {
106+
Failed(result.error)
107+
}
108+
}
109+
}
110+
111+
private fun extractShadowPriceMap(dualResult: List<Flt64>): SPM {
112+
val ret = SPM()
113+
114+
for ((i, j) in metaModel.indicesOfConstraintGroup("product_demand")!!.withIndex()) {
115+
ret.put(ShadowPrice(ProductDemandShadowPriceKey(products[i]), dualResult[j]))
116+
}
117+
ret.put { map, args ->
118+
map.map[ProductDemandShadowPriceKey(args)]?.price ?: Flt64.zero
119+
}
120+
121+
return ret
122+
}
123+
124+
private fun analyzeSolution(result: List<Flt64>): Map<CuttingPlan, UInt64> {
125+
val solution = HashMap<CuttingPlan, UInt64>()
126+
for (token in metaModel.tokens.tokens) {
127+
if (result[token.solverIndex] geq Flt64.one) {
128+
for (i in cuttingPlans.indices) {
129+
if (token.variable.belongsTo(x[i])) {
130+
solution[cuttingPlans[i]] = result[token.solverIndex].toUInt64()
131+
}
132+
}
133+
}
134+
}
135+
return solution
136+
}
137+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package fuookami.ospf.kotlin.example.column_generation_demo.demo1
2+
3+
import java.util.*
4+
import fuookami.ospf.kotlin.utils.math.*
5+
import fuookami.ospf.kotlin.utils.functional.*
6+
import fuookami.ospf.kotlin.utils.multi_array.*
7+
import fuookami.ospf.kotlin.core.frontend.variable.*
8+
import fuookami.ospf.kotlin.core.frontend.expression.monomial.*
9+
import fuookami.ospf.kotlin.core.frontend.expression.polynomial.*
10+
import fuookami.ospf.kotlin.core.frontend.expression.symbol.*
11+
import fuookami.ospf.kotlin.core.frontend.inequality.*
12+
import fuookami.ospf.kotlin.core.frontend.model.mechanism.*
13+
import fuookami.ospf.kotlin.core.backend.plugins.scip.*
14+
import fuookami.ospf.kotlin.framework.solver.*
15+
16+
object InitialSolutionGenerator {
17+
operator fun invoke(length: UInt64, products: List<Product>): Ret<List<CuttingPlan>> {
18+
val solution = ArrayList<CuttingPlan>()
19+
for (product in products) {
20+
val amount = length / product.length
21+
solution.add(CuttingPlan(mapOf(Pair(product, amount))))
22+
}
23+
return Ok(solution)
24+
}
25+
}
26+
27+
class SP {
28+
private val solver: ColumnGenerationSolver = SCIPColumnGenerationSolver()
29+
30+
suspend operator fun invoke(
31+
iteration: UInt64,
32+
length: UInt64,
33+
products: List<Product>,
34+
shadowPrice: SPM
35+
): Ret<CuttingPlan> {
36+
val model = LinearMetaModel("demo1-sp-$iteration")
37+
38+
val y = UIntVariable1("y", Shape1(products.size))
39+
for (product in products) {
40+
y[product].name = "${y.name}_${product.index}"
41+
}
42+
model.addVars(y)
43+
44+
val use = LinearExpressionSymbol(sum(products) { p -> p.length * y[p] }, "use")
45+
model.addSymbol(use)
46+
47+
model.minimize(Flt64.one - sum(products) { p -> shadowPrice(p) * y[p] })
48+
model.addConstraint(use leq length, "use")
49+
50+
return when (val result = solver.solveMILP("demo1-sp-$iteration", model)) {
51+
is Failed -> {
52+
Failed(result.error)
53+
}
54+
55+
is Ok -> {
56+
Ok(analyze(model, products, result.value.solution))
57+
}
58+
}
59+
}
60+
61+
private fun analyze(model: LinearMetaModel, products: List<Product>, result: List<Flt64>): CuttingPlan {
62+
val cuttingPlan = HashMap<Product, UInt64>()
63+
for (token in model.tokens.tokens) {
64+
if (result[token.solverIndex] geq Flt64.one) {
65+
val vector = token.variable.vectorView
66+
cuttingPlan[products[vector[0]]] = result[token.solverIndex].toUInt64()
67+
}
68+
}
69+
return CuttingPlan(cuttingPlan)
70+
}
71+
}

0 commit comments

Comments
 (0)