summaryrefslogtreecommitdiff
path: root/opendc-simulator/opendc-simulator-power
diff options
context:
space:
mode:
authorHongyu He <hongyuhe.cs@googlemail.com>2021-06-11 17:42:47 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-06-11 17:42:47 +0200
commit885137dd79f76a63aee1bcaecbc0c9e9dec80d3a (patch)
tree7be6cb364eabdf0efa5091b1bf0fc5b8a28e0298 /opendc-simulator/opendc-simulator-power
parent1768292251957da5ce6411ecc7d2dffebf8709c8 (diff)
simulator: Add model for UPS
This change adds a new model for the UPS to the OpenDC simulator power subsystem.
Diffstat (limited to 'opendc-simulator/opendc-simulator-power')
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt4
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt107
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt4
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt102
4 files changed, 213 insertions, 4 deletions
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt
index ed3175c7..11034a57 100644
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt
@@ -33,8 +33,8 @@ import org.opendc.simulator.resources.*
*/
public class SimPdu(
interpreter: SimResourceInterpreter,
- public val idlePower: Double = 0.0,
- public val lossCoefficient: Double = 0.0,
+ private val idlePower: Double = 0.0,
+ private val lossCoefficient: Double = 0.0,
) : SimPowerInlet() {
/**
* The [SimResourceDistributor] that distributes the electricity over the PDU outlets.
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt
new file mode 100644
index 00000000..f9431d21
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.power
+
+import org.opendc.simulator.resources.*
+
+/**
+ * A model of an Uninterruptible Power Supply (UPS).
+ *
+ * This model aggregates multiple power sources into a single source in order to ensure that power is always available.
+ *
+ * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood.
+ * @param idlePower The idle power consumption of the UPS independent of the load.
+ * @param lossCoefficient The coefficient for the power loss of the UPS proportional to the load.
+ */
+public class SimUps(
+ interpreter: SimResourceInterpreter,
+ private val idlePower: Double = 0.0,
+ private val lossCoefficient: Double = 0.0,
+) : SimPowerOutlet() {
+ /**
+ * The resource aggregator used to combine the input sources.
+ */
+ private val aggregator = SimResourceAggregatorMaxMin(interpreter)
+
+ /**
+ * Create a new UPS outlet.
+ */
+ public fun newInlet(): SimPowerInlet {
+ val forward = SimResourceForwarder(isCoupled = true)
+ aggregator.addInput(forward)
+ return Inlet(forward)
+ }
+
+ override fun onConnect(inlet: SimPowerInlet) {
+ val consumer = inlet.createConsumer()
+ aggregator.startConsumer(object : SimResourceConsumer by consumer {
+ override fun onNext(ctx: SimResourceContext): SimResourceCommand {
+ return when (val cmd = consumer.onNext(ctx)) {
+ is SimResourceCommand.Consume -> {
+ val duration = cmd.work / cmd.limit
+ val loss = computePowerLoss(cmd.limit)
+ val newLimit = cmd.limit + loss
+
+ SimResourceCommand.Consume(duration * newLimit, newLimit, cmd.deadline)
+ }
+ is SimResourceCommand.Idle -> {
+ val loss = computePowerLoss(0.0)
+ if (loss > 0.0)
+ SimResourceCommand.Consume(Double.POSITIVE_INFINITY, loss, cmd.deadline)
+ else
+ cmd
+ }
+ else -> cmd
+ }
+ }
+ })
+ }
+
+ override fun onDisconnect(inlet: SimPowerInlet) {
+ aggregator.cancel()
+ }
+
+ /**
+ * Compute the power loss that occurs in the UPS.
+ */
+ private fun computePowerLoss(load: Double): Double {
+ // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ return idlePower + lossCoefficient * load
+ }
+
+ /**
+ * A UPS inlet.
+ */
+ public inner class Inlet(private val forwarder: SimResourceTransformer) : SimPowerInlet(), AutoCloseable {
+ override fun createConsumer(): SimResourceConsumer = forwarder
+
+ /**
+ * Remove the inlet from the PSU.
+ */
+ override fun close() {
+ forwarder.close()
+ }
+
+ override fun toString(): String = "SimPsu.Inlet"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt
index b7f51ad3..17a174b7 100644
--- a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt
+++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt
@@ -94,10 +94,10 @@ internal class SimPduTest {
val interpreter = SimResourceInterpreter(coroutineContext, clock)
val source = SimPowerSource(interpreter, capacity = 100.0)
// https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
- val pdu = SimPdu(interpreter, idlePower = 0.015, lossCoefficient = 0.015)
+ val pdu = SimPdu(interpreter, idlePower = 1.5, lossCoefficient = 0.015)
source.connect(pdu)
pdu.newOutlet().connect(SimpleInlet())
- assertEquals(87.515, source.powerDraw, 0.01)
+ assertEquals(89.0, source.powerDraw, 0.01)
}
@Test
diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt
new file mode 100644
index 00000000..8d5fa857
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.power
+
+import io.mockk.spyk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertAll
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.resources.SimResourceConsumer
+import org.opendc.simulator.resources.SimResourceEvent
+import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.resources.consumer.SimWorkConsumer
+
+/**
+ * Test suite for the [SimUps] class.
+ */
+internal class SimUpsTest {
+ @Test
+ fun testSingleInlet() = runBlockingSimulation {
+ val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val source = SimPowerSource(interpreter, capacity = 100.0)
+ val ups = SimUps(interpreter)
+ source.connect(ups.newInlet())
+ ups.connect(SimpleInlet())
+
+ assertEquals(50.0, source.powerDraw)
+ }
+
+ @Test
+ fun testDoubleInlet() = runBlockingSimulation {
+ val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val source1 = SimPowerSource(interpreter, capacity = 100.0)
+ val source2 = SimPowerSource(interpreter, capacity = 100.0)
+ val ups = SimUps(interpreter)
+ source1.connect(ups.newInlet())
+ source2.connect(ups.newInlet())
+
+ ups.connect(SimpleInlet())
+
+ assertAll(
+ { assertEquals(50.0, source1.powerDraw) },
+ { assertEquals(50.0, source2.powerDraw) }
+ )
+ }
+
+ @Test
+ fun testLoss() = runBlockingSimulation {
+ val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val source = SimPowerSource(interpreter, capacity = 100.0)
+ // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ val ups = SimUps(interpreter, idlePower = 4.0, lossCoefficient = 0.05)
+ source.connect(ups.newInlet())
+ ups.connect(SimpleInlet())
+
+ assertEquals(56.5, source.powerDraw)
+ }
+
+ @Test
+ fun testDisconnect() = runBlockingSimulation {
+ val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val source1 = SimPowerSource(interpreter, capacity = 100.0)
+ val source2 = SimPowerSource(interpreter, capacity = 100.0)
+ val ups = SimUps(interpreter)
+ source1.connect(ups.newInlet())
+ source2.connect(ups.newInlet())
+ val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0))
+ val inlet = object : SimPowerInlet() {
+ override fun createConsumer(): SimResourceConsumer = consumer
+ }
+
+ ups.connect(inlet)
+ ups.disconnect()
+
+ verify { consumer.onEvent(any(), SimResourceEvent.Exit) }
+ }
+
+ class SimpleInlet : SimPowerInlet() {
+ override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 0.5)
+ }
+}