diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo10.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo10.kt new file mode 100644 index 0000000..57e545f --- /dev/null +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo10.kt @@ -0,0 +1,189 @@ +package fuookami.ospf.kotlin.example.core_demo + +import fuookami.ospf.kotlin.utils.math.* +import fuookami.ospf.kotlin.utils.math.value_range.* +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 object Demo10 { + data class City( + val name: String + ) : AutoIndexed(City::class) + + val beginCity = "北京" + + val cities = listOf( + City("上海"), + City("合肥"), + City("广州"), + City("成都"), + City("北京") + ) + + val distances = mapOf( + Pair(cities[0], cities[1]) to Flt64(472.0), + Pair(cities[0], cities[2]) to Flt64(1520.0), + Pair(cities[0], cities[3]) to Flt64(2095.0), + Pair(cities[0], cities[4]) to Flt64(1244.0), + + Pair(cities[1], cities[0]) to Flt64(472.0), + Pair(cities[1], cities[2]) to Flt64(1257.0), + Pair(cities[1], cities[3]) to Flt64(1615.0), + Pair(cities[1], cities[4]) to Flt64(1044.0), + + Pair(cities[2], cities[0]) to Flt64(1529.0), + Pair(cities[2], cities[1]) to Flt64(1257.0), + Pair(cities[2], cities[3]) to Flt64(1954.0), + Pair(cities[2], cities[4]) to Flt64(2174.0), + + Pair(cities[3], cities[0]) to Flt64(2095.0), + Pair(cities[3], cities[1]) to Flt64(1615.0), + Pair(cities[3], cities[2]) to Flt64(1954.0), + Pair(cities[3], cities[4]) to Flt64(1854.0), + + Pair(cities[4], cities[0]) to Flt64(1244.0), + Pair(cities[4], cities[1]) to Flt64(1044.0), + Pair(cities[4], cities[2]) to Flt64(2174.0), + Pair(cities[4], cities[3]) to Flt64(1854.0) + ) + + lateinit var x: BinVariable2 + lateinit var u: IntVariable1 + + lateinit var distance: LinearSymbol + lateinit var depart: LinearSymbols1 + lateinit var reached: LinearSymbols1 + + private val metaModel: LinearMetaModel = LinearMetaModel("demo10") + + private val subProcesses = listOf( + Demo10::initVariable, + Demo10::initSymbol, + Demo10::initObject, + Demo10::initConstraint, + Demo10::solve, + Demo10::analyzeSolution + ) + + suspend operator fun invoke(): Try { + for (process in subProcesses) { + when (val result = process()) { + is Ok -> {} + + is Failed -> { + return Failed(result.error) + } + } + } + return ok + } + + private suspend fun initVariable(): Try { + x = BinVariable2("x", Shape2(cities.size, cities.size)) + for (city1 in cities) { + for (city2 in cities) { + val xi = x[city1, city2] + xi.name = "${x.name}_(${city1.name},${city2.name})" + if (city1 != city2) { + metaModel.add(xi) + } else { + xi.range.eq(UInt8.zero) + } + } + } + u = IntVariable1("u", Shape1(cities.size)) + for (city in cities) { + val ui = u[city] + ui.name = "${u.name}_${city.name}" + if (city.name != beginCity) { + ui.range.set(ValueRange(Int64(-cities.size.toLong()), Int64(cities.size.toLong())).value!!) + metaModel.add(ui) + } else { + ui.range.eq(Int64.zero) + } + } + return ok + } + + private suspend fun initSymbol(): Try { + distance = LinearExpressionSymbol(sum(cities.flatMap { city1 -> + cities.mapNotNull { city2 -> + if (city1 == city2) { + null + } else { + distances[city1 to city2]?.let { it * x[city1, city2] } + } + } + }), "distance") + depart = LinearSymbols1("depart", Shape1(cities.size)) { i, _ -> + val city = cities[i] + LinearExpressionSymbol(sum(x[city, _a]), "depart_${city.name}") + } + reached = LinearSymbols1("reached", Shape1(cities.size)) { i, _ -> + val city = cities[i] + LinearExpressionSymbol(sum(x[_a, city]), "reached_${city.name}") + } + return ok + } + + private suspend fun initObject(): Try { + metaModel.minimize(distance, "distance") + return ok + } + + private suspend fun initConstraint(): Try { + for (city in cities) { + metaModel.addConstraint(depart[city] eq Flt64.one, "depart_${city.name}") + } + for (city in cities) { + metaModel.addConstraint(reached[city] eq Flt64.one, "reached_${city.name}") + } + val notBeginCities = cities.filter { it.name != beginCity } + for (city1 in notBeginCities) { + for (city2 in notBeginCities) { + if (city1 != city2) { + metaModel.addConstraint( + u[city1] - u[city2] + cities.size * x[city1, city2] leq cities.size - 1, + "child_route_(${city1.name},${city2.name})" + ) + } + } + } + return ok + } + + private suspend fun solve(): Try { + val solver = ScipLinearSolver() + when (val ret = solver(metaModel)) { + is Ok -> { + metaModel.tokens.setSolution(ret.value.solution) + } + + is Failed -> { + return Failed(ret.error) + } + } + return ok + } + + private suspend fun analyzeSolution(): Try { + val route: MutableMap = hashMapOf() + for (token in metaModel.tokens.tokens) { + if (token.result!! eq Flt64.one && token.variable.belongsTo(x)) { + val vector = token.variable.vectorView + val city1 = cities[vector[0]] + val city2 = cities[vector[1]] + route[city1] = city2 + } + } + return ok + } +} diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo11.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo11.kt new file mode 100644 index 0000000..96bd708 --- /dev/null +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo11.kt @@ -0,0 +1,169 @@ +package fuookami.ospf.kotlin.example.core_demo + +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 object Demo11 { + sealed class Node : AutoIndexed(Node::class) + class RootNode : Node() + class EndNode : Node() + class NormalNode : Node() + + val nodes: List = listOf( + listOf(RootNode()), + (1..7).map { NormalNode() }, + listOf(EndNode()) + ).flatten() + + val capacities = mapOf( + nodes[0] to mapOf( + nodes[1] to UInt64(15), + nodes[2] to UInt64(10), + nodes[3] to UInt64(40) + ), + nodes[1] to mapOf( + nodes[4] to UInt64(15) + ), + nodes[2] to mapOf( + nodes[5] to UInt64(10), + nodes[6] to UInt64(35) + ), + nodes[3] to mapOf( + nodes[6] to UInt64(30), + nodes[7] to UInt64(20) + ), + nodes[4] to mapOf( + nodes[6] to UInt64(10) + ), + nodes[5] to mapOf( + nodes[8] to UInt64(10) + ), + nodes[6] to mapOf( + nodes[7] to UInt64(10) + ), + nodes[7] to mapOf( + nodes[8] to UInt64(45) + ) + ) + + lateinit var x: UIntVariable2 + lateinit var flow: UIntVar + + lateinit var flowIn: LinearSymbols1 + lateinit var flowOut: LinearSymbols1 + + val metaModel: LinearMetaModel = LinearMetaModel("demo11") + + private val subProcesses = listOf( + Demo11::initVariable, + Demo11::initSymbol, + Demo11::initObject, + Demo11::initConstraint, + Demo11::solve, + Demo11::analyzeSolution + ) + + suspend operator fun invoke(): Try { + for (process in subProcesses) { + when (val result = process()) { + is Ok -> {} + + is Failed -> { + return Failed(result.error) + } + } + } + return ok + } + + private suspend fun initVariable(): Try { + x = UIntVariable2("x", Shape2(nodes.size, nodes.size)) + for (node1 in nodes) { + for (node2 in nodes) { + if (node1 == node2) { + continue + } + capacities[node1]?.get(node2)?.let { + val xi = x[node1, node2] + xi.range.leq(it) + metaModel.add(xi) + } + } + } + flow = UIntVar("flow") + metaModel.add(flow) + return ok + } + + private suspend fun initSymbol(): Try { + flowIn = LinearSymbols1("flow_in", Shape1(nodes.size)) { i, _ -> + LinearExpressionSymbol(sum(x[_a, i]), "flow_in_$i") + } + metaModel.add(flowIn) + flowOut = LinearSymbols1("flow_out", Shape1(nodes.size)) { i, _ -> + LinearExpressionSymbol(sum(x[i, _a]), "flow_out_$i") + } + metaModel.add(flowOut) + return ok + } + + private suspend fun initObject(): Try { + metaModel.maximize(flow, "flow") + return ok + } + + private suspend fun initConstraint(): Try { + val rootNode = nodes.first { it is RootNode } + metaModel.addConstraint( + flowOut[rootNode] - flowIn[rootNode] eq flow, + "flow_${rootNode.index}" + ) + val endNode = nodes.first { it is EndNode } + metaModel.addConstraint( + flowIn[endNode] - flowOut[endNode] eq flow, + "flow_${endNode.index}" + ) + for (node in nodes.filterIsInstance()) { + metaModel.addConstraint( + flowOut[node] eq flowIn[node], + "flow_${node.index}" + ) + } + return ok + } + + private suspend fun solve(): Try { + val solver = ScipLinearSolver() + when (val ret = solver(metaModel)) { + is Ok -> { + metaModel.tokens.setSolution(ret.value.solution) + } + + is Failed -> { + return Failed(ret.error) + } + } + return ok + } + + private suspend fun analyzeSolution(): Try { + val flow: MutableMap> = hashMapOf() + for (token in metaModel.tokens.tokens) { + if (token.result!! geq Flt64.one && token.variable belongsTo x) { + val vector = token.variable.vectorView + val node1 = nodes[vector[0]] + val node2 = nodes[vector[1]] + flow.getOrPut(node1) { hashMapOf() }[node2] = token.result!!.round().toUInt64() + } + } + return ok + } +} diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo12.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo12.kt new file mode 100644 index 0000000..403ef3c --- /dev/null +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo12.kt @@ -0,0 +1,126 @@ +package fuookami.ospf.kotlin.example.core_demo + +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.expression.symbol.linear_function.* +import fuookami.ospf.kotlin.core.frontend.inequality.* +import fuookami.ospf.kotlin.core.frontend.model.mechanism.* +import fuookami.ospf.kotlin.core.backend.plugins.scip.* + +data object Demo12 { + data class Product( + val yield: Flt64, + val risk: Flt64, + val premium: Flt64, + val minPremium: Flt64 + ) : AutoIndexed(Product::class) + + val products = listOf( + Product(Flt64(0.28), Flt64(0.04), Flt64(0.08), Flt64(103.0)), + Product(Flt64(0.21), Flt64(0.015), Flt64(0.02), Flt64(198.0)), + Product(Flt64(0.23), Flt64(0.05), Flt64(0.045), Flt64(52.0)), + Product(Flt64(0.25), Flt64(0.026), Flt64(0.04), Flt64(40.0)), + Product(Flt64(0.05), Flt64(0.0), Flt64(0.0), Flt64(0.0)) + ) + val funds = Flt64(1000000.0) + + lateinit var x: UIntVariable1 + + lateinit var premium: LinearSymbols1 + lateinit var yield: LinearSymbol + + val metaModel: LinearMetaModel = LinearMetaModel("demo12") + + private val subProcesses = listOf( + Demo12::initVariable, + Demo12::initSymbol, + Demo12::initObject, + Demo12::initConstraint, + Demo12::solve, + Demo12::analyzeSolution + ) + + suspend operator fun invoke(): Try { + for (process in subProcesses) { + when (val result = process()) { + is Ok -> {} + + is Failed -> { + return Failed(result.error) + } + } + } + return ok + } + + private suspend fun initVariable(): Try { + x = UIntVariable1("x", Shape1(products.size)) + metaModel.add(x) + return ok + } + + private suspend fun initSymbol(): Try { + premium = LinearSymbols1("premium", Shape1(products.size)) { i, _ -> + val product = products[i] + MaxFunction( + listOf( + LinearPolynomial(product.premium * x[i]), + LinearPolynomial(product.minPremium) + ), + "premium_$i" + ) + } + metaModel.add(premium) + yield = LinearExpressionSymbol( + sum(products.map { p -> (p.yield - p.risk) * x[p] - premium[p] }), + "yield" + ) + metaModel.add(yield) + return ok + } + + private suspend fun initObject(): Try { + metaModel.maximize(yield, "yield") + return ok + } + + private suspend fun initConstraint(): Try { + metaModel.addConstraint( + sum(products.map { p -> x[p] + premium[p] }) eq funds, + "pay" + ) + return ok + } + + private suspend fun solve(): Try { + val solver = ScipLinearSolver() + when (val ret = solver(metaModel)) { + is Ok -> { + metaModel.tokens.setSolution(ret.value.solution) + } + + is Failed -> { + return Failed(ret.error) + } + } + return ok + } + + private suspend fun analyzeSolution(): Try { + val ret = HashMap() + for (token in metaModel.tokens.tokens) { + if (token.result!! geq Flt64.one && token.variable.belongsTo(x)) { + val vector = token.variable.vectorView + val product = products[vector[0]] + ret[product] = token.result!!.round().toUInt64() + } + } + return ok + } +} diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo3.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo3.kt index db9b6a0..61b5619 100644 --- a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo3.kt +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo3.kt @@ -111,7 +111,7 @@ data object Demo3 { } private suspend fun initObject(): Try { - metaModel.minimize(LinearPolynomial(cost)) + metaModel.minimize(cost) return ok } diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo4.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo4.kt index 4f7525d..11705b9 100644 --- a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo4.kt +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo4.kt @@ -97,7 +97,7 @@ data object Demo4 { private suspend fun initObject(): Try { - metaModel.maximize(LinearPolynomial(profit), "maxProfit") + metaModel.maximize(profit, "maxProfit") return ok } diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo5.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo5.kt index be12b42..fdf675b 100644 --- a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo5.kt +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo5.kt @@ -74,7 +74,7 @@ data object Demo5 { } private suspend fun initObject(): Try { - metaModel.maximize(LinearPolynomial(cargoValue),"value") + metaModel.maximize(cargoValue,"value") return ok } diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo6.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo6.kt index 4b498fe..58cf0ef 100644 --- a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo6.kt +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo6.kt @@ -75,7 +75,7 @@ data object Demo6 { } private suspend fun initObject(): Try { - metaModel.maximize(LinearPolynomial(cargoValue),"value") + metaModel.maximize(cargoValue,"value") return ok } @@ -107,12 +107,10 @@ data object Demo6 { private suspend fun analyzeSolution(): Try { val ret = HashMap() for (token in metaModel.tokens.tokens) { - if (token.result!! neq Flt64.one - && token.variable.belongsTo(x) - ) { + if (token.result!! geq Flt64.one && token.variable.belongsTo(x)) { ret[cargos[token.variable.vectorView[0]]] = token.result!!.round().toUInt64() } } return ok } -} \ No newline at end of file +} diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo7.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo7.kt index d0a9f81..175599c 100644 --- a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo7.kt +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo7.kt @@ -131,7 +131,7 @@ data object Demo7 { } private suspend fun initObject(): Try { - metaModel.minimize(LinearPolynomial(cost),"cost") + metaModel.minimize(cost,"cost") return ok } diff --git a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo9.kt b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo9.kt index 4a10408..e45717d 100644 --- a/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo9.kt +++ b/examples/ospf-kotlin-example/src/main/fuookami/ospf/kotlin/example/core_demo/Demo9.kt @@ -5,12 +5,122 @@ 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.expression.symbol.linear_function.* import fuookami.ospf.kotlin.core.frontend.model.mechanism.* import fuookami.ospf.kotlin.core.backend.plugins.scip.* data object Demo9 { -} \ No newline at end of file + data class Settlement( + val x: Flt64, + val y: Flt64 + ) : AutoIndexed(Settlement::class) + + val settlements = listOf( + Settlement(Flt64(9.0), Flt64(2.0)), + Settlement(Flt64(2.0), Flt64(1.0)), + Settlement(Flt64(3.0), Flt64(8.0)), + Settlement(Flt64(3.0), Flt64(-2.0)), + Settlement(Flt64(5.0), Flt64(9.0)), + Settlement(Flt64(4.0), Flt64(-2.0)) + ) + + lateinit var x: IntVar + lateinit var y: IntVar + lateinit var dX: LinearSymbols1 + lateinit var dY: LinearSymbols1 + + private val metaModel: LinearMetaModel = LinearMetaModel("demo9") + + private val subProcesses = listOf( + Demo9::initVariable, + Demo9::initSymbol, + Demo9::initObject, + Demo9::initConstraint, + Demo9::solve, + Demo9::analyzeSolution + ) + + suspend operator fun invoke(): Try { + for (process in subProcesses) { + when (val result = process()) { + is Ok -> {} + + is Failed -> { + return Failed(result.error) + } + } + } + return ok + } + + private suspend fun initVariable(): Try { + x = IntVar("x") + y = IntVar("y") + metaModel.add(x) + metaModel.add(y) + return ok + } + + private suspend fun initSymbol(): Try { + dX = LinearSymbols1("dx", Shape1(settlements.size)) { i, _ -> + SlackFunction( + type = UInteger, + x = LinearPolynomial(x), + y = LinearPolynomial(settlements[i].x), + name = "dx_$i" + ) + } + metaModel.add(dX) + + dY = LinearSymbols1("dy", Shape1(settlements.size)) { i, _ -> + SlackFunction( + type = UInteger, + x = LinearPolynomial(y), + y = LinearPolynomial(settlements[i].y), + name = "dy_$i" + ) + } + metaModel.add(dY) + return ok + } + + private suspend fun initObject(): Try { + metaModel.minimize(sum(dX[_a]) + sum(dY[_a])) + return ok + } + + private suspend fun initConstraint(): Try { + return ok + } + + private suspend fun solve(): Try { + val solver = ScipLinearSolver() + when (val ret = solver(metaModel)) { + is Ok -> { + metaModel.tokens.setSolution(ret.value.solution) + } + + is Failed -> { + return Failed(ret.error) + } + } + return ok + } + + private suspend fun analyzeSolution(): Try { + val position = ArrayList() + for (token in metaModel.tokens.tokens) { + if (token.variable.belongsTo(x)) { + position.add(token.result!!) + } + } + for (token in metaModel.tokens.tokens) { + if (token.variable.belongsTo(y)) { + position.add(token.result!!) + } + } + return ok + } +} diff --git a/examples/ospf-kotlin-example/src/test/fuookami/ospf/kotlin/example/CoreDemoTest.kt b/examples/ospf-kotlin-example/src/test/fuookami/ospf/kotlin/example/CoreDemoTest.kt index 9a95313..3f129ef 100644 --- a/examples/ospf-kotlin-example/src/test/fuookami/ospf/kotlin/example/CoreDemoTest.kt +++ b/examples/ospf-kotlin-example/src/test/fuookami/ospf/kotlin/example/CoreDemoTest.kt @@ -44,4 +44,24 @@ class CoreDemoTest { fun runDemo8() { assert(runBlocking { Demo8().ok }) } + + @Test + fun runDemo9() { + assert(runBlocking { Demo9().ok }) + } + + @Test + fun runDemo10() { + assert(runBlocking { Demo10().ok }) + } + + @Test + fun runDemo11() { + assert(runBlocking { Demo11().ok }) + } + + @Test + fun runDemo12() { + assert(runBlocking { Demo12().ok }) + } }