diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c0bd375d7..06a13beece 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added basic external em service [#1566](https://github.com/ie3-institute/simona/issues/1566)
- Implement energy limit flex options and adapt optimization [#1572](https://github.com/ie3-institute/simona/issues/1572)
- Introducing `onePU` as default quantity [#1607](https://github.com/ie3-institute/simona/issues/1607)
+- Introducing energy demand for warm water heating [#856](https://github.com/ie3-institute/simona/issues/856)
### Changed
- Upgraded `scala2` to `scala3` [#53](https://github.com/ie3-institute/simona/issues/53)
diff --git a/docs/readthedocs/models/dhws_model.md b/docs/readthedocs/models/dhws_model.md
index da3fdc1bba..7c192ce0bd 100644
--- a/docs/readthedocs/models/dhws_model.md
+++ b/docs/readthedocs/models/dhws_model.md
@@ -3,7 +3,7 @@
This page documents the functionality of the domestic hot water storage available in SIMONA.
## Behaviour
-This storage model operates on volumes, although the functions it provides for other models all operate with energy. Internally the storage model converts energy to volume and vice versa with formulas specified below. Furthermore, it is assumed that the storage medium is water. At initialisation the storage will be full. Domestic hot water storage will be charged when the state of charge is be below 20 % or when the required heat demand for hot tap water is higher than the stored energy in the storage.
+This storage model operates on volumes, although the functions it provides for other models all operate with energy. Internally the storage model converts energy to volume and vice versa with formulas specified below. Furthermore, it is assumed that the storage medium is water. At initialisation the storage will be full. Domestic hot water storage will be charged when the state of charge is be below 20 % or when the required heat demand for hot tap water is higher than the stored energy in the storage. Furthermore the domestic hot water storage is not considered in any flexibility measures of the heat source (e.g. heat pump).
## Attributes, Units and Remarks
diff --git a/docs/readthedocs/models/thermal_house_model.md b/docs/readthedocs/models/thermal_house_model.md
index 7f8d871d80..603f26dd09 100644
--- a/docs/readthedocs/models/thermal_house_model.md
+++ b/docs/readthedocs/models/thermal_house_model.md
@@ -7,8 +7,7 @@ This page documents the functionality of the thermal house available in SIMONA.
## Behaviour
-This house model represents the thermal behaviour of a building. It represents a simple shoebox with thermal capacity and transmission losses.
-The house can optionally be equipped with a {ref}`cts_model` as thermal storage. Both are connected by the {ref}`thermal_grid_model`.
+This house model represents the thermal behaviour of a building. It represents a simple shoebox with thermal capacity and transmission losses for its heating demand. As well the hot water demand (tap water) based on the number of inhabitants and the housing type on hourly basis is considered. The house can optionally be equipped with a {ref}`cts_model` as thermal storage. Both are connected by the {ref}`thermal_grid_model`.
The thermal house provides two different energy demands. The required demand indicates that the inner temperature of the house is below the lower temperature boundary and thus, requires mandatory heating. An additional demand indicates the amount of energy necessary to reach the target temperature. Additional demand not necessarily requires to be covered but could, e.g. for flexibility purposes.
diff --git a/src/main/scala/edu/ie3/simona/model/participant/hp/HpModel.scala b/src/main/scala/edu/ie3/simona/model/participant/hp/HpModel.scala
index 6b07e7307c..444251d995 100644
--- a/src/main/scala/edu/ie3/simona/model/participant/hp/HpModel.scala
+++ b/src/main/scala/edu/ie3/simona/model/participant/hp/HpModel.scala
@@ -24,7 +24,10 @@ import edu.ie3.simona.model.participant.control.QControl
import edu.ie3.simona.model.participant.hp.HpModel.{HpOperatingPoint, HpState}
import edu.ie3.simona.model.participant.{ParticipantFlexModel, ParticipantModel}
import edu.ie3.simona.model.thermal.ThermalGrid
-import edu.ie3.simona.model.thermal.ThermalGrid.*
+import edu.ie3.simona.model.thermal.ThermalGrid.{
+ ThermalDemandWrapper,
+ ThermalGridState,
+}
import edu.ie3.simona.ontology.messages.flex.FlexType
import edu.ie3.simona.service.Data.PrimaryData.{
ComplexPower,
@@ -78,7 +81,19 @@ class HpModel private (
operatingPoint,
)
- val thermalDemands = thermalGrid.determineEnergyDemand(thermalGridState)
+ val hoursWaterDemandToDetermine = thermalGrid.house.flatMap(
+ _.checkIfNeedToDetermineDomesticHotWaterDemand(
+ tick,
+ simulationTime,
+ lastState,
+ )
+ )
+
+ val thermalDemands =
+ thermalGrid.determineEnergyDemand(
+ thermalGridState,
+ hoursWaterDemandToDetermine,
+ )
lastState.copy(
tick = tick,
@@ -231,7 +246,7 @@ class HpModel private (
* @param thermalGridState
* State of the thermalGrid.
* @param thermalDemands
- * ThermalEnergyDemand of the house and the thermal storage.
+ * ThermalEnergyDemand of the house and the thermal storages.
* @param wasRunningLastPeriod
* Indicates if the Hp was running till this tick.
* @return
@@ -246,19 +261,23 @@ class HpModel private (
val demandHouse = thermalDemands.houseDemand
val demandHeatStorage = thermalDemands.heatStorageDemand
+ val demandDomesticHotWaterStorage =
+ thermalDemands.domesticHotWaterStorageDemand
val noHeatStorageOrEmpty = thermalGridState.isHeatStorageEmpty
val turnHpOn =
(demandHouse.hasRequiredDemand && noHeatStorageOrEmpty) ||
- (demandHouse.hasPossibleDemand && wasRunningLastPeriod ||
- demandHeatStorage.hasRequiredDemand ||
- (demandHeatStorage.hasPossibleDemand && wasRunningLastPeriod))
+ (demandHouse.hasPossibleDemand && wasRunningLastPeriod) ||
+ demandHeatStorage.hasRequiredDemand ||
+ (demandHeatStorage.hasPossibleDemand && wasRunningLastPeriod) ||
+ demandDomesticHotWaterStorage.hasRequiredDemand
val canOperate =
demandHouse.hasRequiredDemand || demandHouse.hasPossibleDemand ||
- demandHeatStorage.hasRequiredDemand || demandHeatStorage.hasPossibleDemand
+ demandHeatStorage.hasRequiredDemand || demandHeatStorage.hasPossibleDemand ||
+ demandDomesticHotWaterStorage.hasRequiredDemand
val canBeOutOfOperation =
- !(demandHouse.hasRequiredDemand && noHeatStorageOrEmpty)
+ !(demandHouse.hasRequiredDemand && noHeatStorageOrEmpty) && !demandDomesticHotWaterStorage.hasRequiredDemand
(
turnHpOn,
@@ -333,16 +352,21 @@ object HpModel {
* [[edu.ie3.simona.model.thermal.ThermalHouse]] used for space heating.
* @param qDotHeatStorage
* The thermal power input of the
- * [[edu.ie3.simona.model.thermal.ThermalStorage]].
+ * [[edu.ie3.simona.model.thermal.ThermalStorage]] used for heat storage.
+ * @param qDotDomesticHotWaterStorage
+ * The thermal power input of the
+ * [[edu.ie3.simona.model.thermal.DomesticHotWaterStorage]] used for
+ * domestic hot water / tap water.
*/
final case class ThermalGridOperatingPoint(
qDotHp: Power,
qDotHouse: Power,
qDotHeatStorage: Power,
+ qDotDomesticHotWaterStorage: Power,
)
object ThermalGridOperatingPoint {
def zero: ThermalGridOperatingPoint =
- ThermalGridOperatingPoint(zeroKW, zeroKW, zeroKW)
+ ThermalGridOperatingPoint(zeroKW, zeroKW, zeroKW, zeroKW)
}
/** Holds all relevant data for a hp model calculation.
@@ -381,7 +405,11 @@ object HpModel {
): HpState = {
val therGrid = ThermalGrid(thermalGrid)
val initialState = ThermalGrid.startingState(therGrid, zeroCelsius)
- val thermalDemand = therGrid.determineEnergyDemand(initialState)
+ val thermalDemand =
+ therGrid.determineEnergyDemand(
+ initialState,
+ Some(Seq(simulationTime.getHour)),
+ )
HpState(
tick,
diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalDemandConditions.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalDemandConditions.scala
index 4317ed3a11..15a757253a 100644
--- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalDemandConditions.scala
+++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalDemandConditions.scala
@@ -12,8 +12,10 @@ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW
private case class ThermalDemandConditions(
shouldContinueHouseHeating: Boolean,
houseDemand: Boolean,
+ waterStorageDemand: Boolean,
heatStorageDemand: Boolean,
housePossible: Boolean,
+ waterStoragePossible: Boolean,
heatStoragePossible: Boolean,
houseHeatedLastState: Boolean,
)
@@ -38,14 +40,19 @@ private object ThermalDemandConditions {
ThermalDemandConditions(
/* Consider the action in the last state
* We can continue using the qDots from last operating point to keep continuity.
- * If the house was heated in lastState and has still some demand.
- */
+ * If the house was heated in lastState and has still some demand and the domestic
+ * hot water storage has no demand. */
shouldContinueHouseHeating =
- lastOperatingPoint.qDotHouse > zeroKW && houseDemand.hasPossibleDemand,
+ lastOperatingPoint.qDotHouse > zeroKW && houseDemand.hasPossibleDemand &&
+ !state.thermalDemands.domesticHotWaterStorageDemand.hasRequiredDemand,
houseDemand = houseDemand.hasRequiredDemand,
+ waterStorageDemand =
+ state.thermalDemands.domesticHotWaterStorageDemand.hasRequiredDemand,
heatStorageDemand =
heatStorageDemand.hasRequiredDemand || heatStorageDemand.hasPossibleDemand,
housePossible = houseDemand.hasPossibleDemand,
+ waterStoragePossible =
+ state.thermalDemands.domesticHotWaterStorageDemand.hasPossibleDemand,
heatStoragePossible = heatStorageDemand.hasPossibleDemand,
houseHeatedLastState = isHouseHeatedLastState,
)
diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
index c865580df4..5f1114f350 100644
--- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
+++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
@@ -7,7 +7,10 @@
package edu.ie3.simona.model.thermal
import com.typesafe.scalalogging.LazyLogging
-import edu.ie3.datamodel.models.input.thermal.CylindricalStorageInput
+import edu.ie3.datamodel.models.input.thermal.{
+ CylindricalStorageInput,
+ DomesticHotWaterStorageInput,
+}
import edu.ie3.datamodel.models.result.ResultEntity
import edu.ie3.datamodel.models.result.thermal.{
CylindricalStorageResult,
@@ -27,6 +30,7 @@ import edu.ie3.simona.model.thermal.ThermalGrid.{
}
import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseState
import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState
+import edu.ie3.simona.util.TickUtil.RichZonedDateTime
import edu.ie3.util.quantities.QuantityUtils.{
asKelvin,
asMegaWatt,
@@ -34,8 +38,9 @@ import edu.ie3.util.quantities.QuantityUtils.{
asPu,
}
import edu.ie3.util.scala.quantities.DefaultQuantities.*
+import edu.ie3.util.scala.quantities.QuantityUtil.*
import squants.energy.KilowattHours
-import squants.{Energy, Power, Temperature}
+import squants.{Energy, Power, Seconds, Temperature}
import java.time.ZonedDateTime
import scala.jdk.CollectionConverters.SetHasAsScala
@@ -48,10 +53,13 @@ import scala.language.postfixOps
* Thermal houses connected to the bus.
* @param heatStorage
* Thermal storages connected to the bus.
+ * @param domesticHotWaterStorage
+ * Storages for domestic hot water / tap water connected to the bus.
*/
final case class ThermalGrid(
house: Option[ThermalHouse],
heatStorage: Option[CylindricalThermalStorage],
+ domesticHotWaterStorage: Option[DomesticHotWaterStorage],
) extends LazyLogging {
/** Determines the state of the ThermalGrid by using the HpOperatingPoint.
@@ -72,6 +80,7 @@ final case class ThermalGrid(
): ThermalGridState = {
val houseQDot = operatingPoint.thermalOps.qDotHouse
val heatStorageQDot = operatingPoint.thermalOps.qDotHeatStorage
+ val waterStorageQDot = operatingPoint.thermalOps.qDotDomesticHotWaterStorage
val updatedHouseState = house.zip(lastState.houseState) match {
case Some((thermalHouse, houseState)) =>
@@ -99,65 +108,158 @@ final case class ThermalGrid(
case _ => None
}
- ThermalGridState(updatedHouseState, updatedHeatStorageState)
+ val updatedDomesticHotWaterStorageState = domesticHotWaterStorage
+ .zip(lastState.domesticHotWaterStorageState)
+ .map {
+ case (
+ storage: DomesticHotWaterStorage,
+ waterStorageState: ThermalStorageState,
+ ) =>
+ storage.determineState(
+ tick,
+ waterStorageState,
+ waterStorageQDot,
+ )
+ case _ =>
+ throw new IllegalStateException(
+ "Could not find state of domestic hot water storage."
+ )
+ }
+
+ ThermalGridState(
+ updatedHouseState,
+ updatedHeatStorageState,
+ updatedDomesticHotWaterStorageState,
+ )
}
/** Determine the energy demand of the thermalGrid.
*
* @param thermalGridState
* Last state of the thermal grid.
+ * @param hoursWaterDemandToDetermine
+ * The hours of which the energy demand for domestic hot water will have to
+ * be determined.
* @return
* The energy demand of elements of thermalGrid.
*/
def determineEnergyDemand(
- thermalGridState: ThermalGridState
+ thermalGridState: ThermalGridState,
+ hoursWaterDemandToDetermine: Option[Seq[Int]],
): ThermalDemandWrapper = {
- val houseDemand = house.zip(thermalGridState.houseState) match {
+ val (houseDemandHeating, houseDemandWater) =
+ calculateHouseDemand(thermalGridState, hoursWaterDemandToDetermine)
+ val heatStorageDemand = calculateHeatStorageDemand(thermalGridState)
+ val domesticHotWaterStorageDemand = calculateDomesticStorageDemand(
+ thermalGridState
+ )
+
+ ThermalDemandWrapper(
+ houseDemandHeating,
+ heatStorageDemand,
+ houseDemandWater,
+ domesticHotWaterStorageDemand,
+ )
+ }
+
+ /** Determine the energy demand for heating and the water demand of the house.
+ *
+ * @param thermalGridState
+ * Last state of the thermal grid.
+ * @param hoursWaterDemandToDetermine
+ * The hours of which the energy demand for domestic hot water will have to
+ * be determined.
+ * @return
+ * The energy and water demand of the house.
+ */
+ private def calculateHouseDemand(
+ thermalGridState: ThermalGridState,
+ hoursWaterDemandToDetermine: Option[Seq[Int]],
+ ): (ThermalEnergyDemand, ThermalEnergyDemand) = {
+ house.zip(thermalGridState.houseState) match {
case Some((thermalHouse, houseState)) =>
- if houseState.innerTemperature < thermalHouse.targetTemperature then {
- thermalHouse.energyDemandHeating(houseState)
- } else {
- ThermalEnergyDemand.noDemand
+ // Calculate domestic hot water demand
+ val domesticHotWaterDemand =
+ thermalHouse.energyDemandDomesticHotWater(
+ hoursWaterDemandToDetermine
+ )
+ // Calculate heating demand of house
+ val heatingDemand = {
+ if houseState.innerTemperature < thermalHouse.targetTemperature
+ then {
+ thermalHouse.energyDemandHeating(houseState)
+ } else {
+ ThermalEnergyDemand.noDemand
+ }
}
- case None => ThermalEnergyDemand.noDemand
+ (heatingDemand, domesticHotWaterDemand)
+
+ case None =>
+ (ThermalEnergyDemand.noDemand, ThermalEnergyDemand.noDemand)
}
+ }
- val heatStorageDemand =
- heatStorage.zip(thermalGridState.heatStorageState) match {
- case Some((storage, storageState)) =>
- val storedEnergy = storageState.storedEnergy
- val storageRequired = {
- if storedEnergy == zeroKWh then storage.getMaxEnergyThreshold
- else zeroMWh
- }
+ /** Determine the energy demand of the HeatStorage.
+ *
+ * @param thermalGridState
+ * Last state of the thermal grid.
+ * @return
+ * The energy demand of the HeatStorage.
+ */
+ private def calculateHeatStorageDemand(
+ thermalGridState: ThermalGridState
+ ): ThermalEnergyDemand = {
+ heatStorage.zip(thermalGridState.heatStorageState) match {
+ case Some((storage, storageState)) =>
+ val storedEnergy = storageState.storedEnergy
+ val storageRequired = {
+ if storedEnergy == zeroKWh then storage.getMaxEnergyThreshold
+ else zeroMWh
+ }
- val storagePossible = storage.getMaxEnergyThreshold - storedEnergy
- ThermalEnergyDemand(
- storageRequired,
- storagePossible,
- )
- case None => ThermalEnergyDemand.noDemand
- }
+ val storagePossible = storage.getMaxEnergyThreshold - storedEnergy
+ ThermalEnergyDemand(
+ storageRequired,
+ storagePossible,
+ )
+ case None => ThermalEnergyDemand.noDemand
+ }
+ }
- ThermalDemandWrapper(
- ThermalEnergyDemand(
- houseDemand.required,
- houseDemand.possible,
- ),
- ThermalEnergyDemand(
- heatStorageDemand.required,
- heatStorageDemand.possible,
- ),
- )
+ /** Determine the energy demand of the DomesticHotWaterStorage.
+ *
+ * @param thermalGridState
+ * Last state of the thermal grid.
+ * @return
+ * The energy demand of the domestic hot water storage.
+ */
+ private def calculateDomesticStorageDemand(
+ thermalGridState: ThermalGridState
+ ): ThermalEnergyDemand = {
+ domesticHotWaterStorage.zip(
+ thermalGridState.domesticHotWaterStorageState
+ ) match {
+ case Some((storage, storageState)) =>
+ val storedEnergy = storageState.storedEnergy
+ val storageRequired = {
+ if storedEnergy == zeroKWh then storage.getMaxEnergyThreshold
+ else zeroMWh
+ }
+
+ val storagePossible = storage.getMaxEnergyThreshold - storedEnergy
+ ThermalEnergyDemand(
+ storageRequired,
+ storagePossible,
+ )
+ case None => ThermalEnergyDemand.noDemand
+ }
}
- /** Handles the case, when a grid has feed in. Depending on which entity has
- * some heat demand the house or the storage will be heated up / filled up.
- * First the actions from last operating point will be considered and checked
- * if the behaviour should be continued. This might be the case, if we got
- * activated by updated weather data. If this is not the case, all other
- * cases will be handled by [[ThermalGrid.handleFinalFeedInCases]].
+ /** Handles the case, when a grid has feed in. To do so, first the conditions
+ * of all grid elements are evaluated if there is demand for heating. Based
+ * on these, the distribution strategy for the heating power (qDot) is
+ * chosen.
*
* @param state
* State of the heat pump.
@@ -175,41 +277,24 @@ final case class ThermalGrid(
// TODO: We would need to issue a storage result model here...
val conditions = ThermalDemandConditions.from(state)
val strategy = selectFeedInStrategy(conditions)
- val (qDotHouse, qDotHeatStorage) = strategy(qDot, heatStorage)
-
- handleCase(state, qDotHouse, qDotHeatStorage)
+ val (qDotHouse, qDotHeatStorage, qDotWaterStorage) =
+ strategy(qDot, heatStorage, domesticHotWaterStorage)
+ handleCase(state, qDotHouse, qDotHeatStorage, qDotWaterStorage)
}
- /** Handles the last cases of [[ThermalGrid.handleFeedIn]], where the thermal
- * feed in should be determined.
- *
- * | house req. demand | house add. demand | storage req. demand | storage add. demand | qDot to house | qDot to storage |
- * |:------------------|:------------------|:--------------------|:--------------------|:--------------|:----------------|
- * | true | true | true | true | true | false |
- * | true | true | true | false | true | false |
- * | true | true | false | true | true | false |
- * | true | true | false | false | true | false |
- * | true | false | true | true | true | false |
- * | true | false | true | false | true | false |
- * | true | false | false | true | true | false |
- * | true | false | false | false | true | false |
- * | false | true | true | true | false | true |
- * | false | true | true | false | false | true |
- * | false | true | false | true | false | true |
- * | false | true | false | false | true | false |
- * | false | false | true | true | false | true |
- * | false | false | true | false | false | true |
- * | false | false | false | true | false | true |
- * | false | false | false | false | false | false |
+ /** Selects the strategy how to distribute the thermal power (qDot) from the
+ * heat source to the elements within this ThermalGrid.
*
- * This can be simplified to four cases
- * | No | Conditions | Result |
- * |:---|:-------------------------------------|:----------|
- * | 1 | if house.reqD | house |
- * | 2 | else if storage.reqD OR storage.addD | storage |
- * | 3 | else if house.addD | house |
- * | 4 | else | no output |
+ * | No | Conditions | Result |
+ * |:---|:---------------------------------------------|:--------------------------------|
+ * | 1 | if house.reqD AND waterStorage.reqD | split to house and waterStorage |
+ * | 2 | else if house.reqD | house |
+ * | 3 | else if waterStorage.reqD | waterStorage |
+ * | 4 | else if heatStorage.reqD OR heatStorage.posD | heatStorage |
+ * | 5 | else if waterStorage.posD | waterStorage |
+ * | 6 | else if house.posD | house |
+ * | 7 | else | no output |
*
* @param conditions
* The ThermalDemandConditions, describing the current status of heat
@@ -222,8 +307,14 @@ final case class ThermalGrid(
): FeedInStrategy = {
if conditions.shouldContinueHouseHeating then {
HouseOnlyStrategy
+ } else if conditions.waterStorageDemand &&
+ (conditions.houseDemand || conditions.houseHeatedLastState)
+ then {
+ SplitHouseWaterStrategy
} else if conditions.houseDemand then {
HouseOnlyStrategy
+ } else if conditions.waterStorageDemand then {
+ WaterStorageFirstStrategy
} else if conditions.heatStorageDemand then {
HeatStorageFirstStrategy
} else if conditions.housePossible then {
@@ -242,6 +333,8 @@ final case class ThermalGrid(
* @param qDotHeatStorage
* Feed in to the heat storage (positive: Storage is charging, negative:
* Storage is discharging).
+ * @param qDotDomesticHotWaterStorage
+ * In-feed to the domestic hot water storage.
* @return
* The operating point of the thermal grid and the next threshold if there
* is one.
@@ -250,25 +343,38 @@ final case class ThermalGrid(
state: HpState,
qDotHouse: Power,
qDotHeatStorage: Power,
+ qDotDomesticHotWaterStorage: Power,
): (ThermalGridOperatingPoint, Option[ThermalThreshold]) = {
- val (_, thermalHouseThreshold) =
+ val (_, thresholdThermalHouse) =
handleFeedInHouse(state, qDotHouse)
- val heatStorageThreshold =
- handleFeedInStorages(state, qDotHeatStorage)
+ val thresholdHeatStorage = handleFeedInHeatStorage(state, qDotHeatStorage)
+
+ // Handle domestic hot water demand
+ val (resultingQDotHotWaterStorage, thresholdHotWaterStorage) =
+ // There only can be consumption, if there isn't feed in into the storage.
+ if qDotDomesticHotWaterStorage == zeroKW then
+ handleHotWaterConsumption(state)
+ else {
+ val threshold =
+ handleFeedInHotWaterStorage(state, qDotDomesticHotWaterStorage)
+ (qDotDomesticHotWaterStorage, threshold)
+ }
val nextThreshold = determineNextThreshold(
Seq(
- thermalHouseThreshold,
- heatStorageThreshold,
+ thresholdThermalHouse,
+ thresholdHeatStorage,
+ thresholdHotWaterStorage,
)
)
(
ThermalGridOperatingPoint(
- qDotHouse + qDotHeatStorage,
+ qDotHouse + qDotHeatStorage + qDotDomesticHotWaterStorage,
qDotHouse,
qDotHeatStorage,
+ resultingQDotHotWaterStorage,
),
nextThreshold,
)
@@ -306,16 +412,28 @@ final case class ThermalGrid(
* @param qDotHeatStorage
* Feed in to the heat storage (positive: Storage is charging, negative:
* Storage is discharging).
+ * @param qDotHotWaterStorage
+ * Feed in to the water storage (positive: Storage is charging, negative:
+ * Storage is discharging).
* @return
* The ThermalThreshold if there is one.
*/
private def handleFeedInStorages(
state: HpState,
qDotHeatStorage: Power,
- ): Option[ThermalThreshold] = {
- if qDotHeatStorage != zeroKW then
- handleFeedInHeatStorage(state, qDotHeatStorage)
- else None
+ qDotHotWaterStorage: Power,
+ ): (Option[ThermalThreshold], Option[ThermalThreshold]) = {
+ val heatStorageThreshold =
+ if qDotHeatStorage != zeroKW then
+ handleFeedInHeatStorage(state, qDotHeatStorage)
+ else None
+
+ val hotWaterStorageThreshold =
+ if qDotHotWaterStorage != zeroKW then
+ handleFeedInHotWaterStorage(state, qDotHotWaterStorage)
+ else None
+
+ (heatStorageThreshold, hotWaterStorageThreshold)
}
private def handleFeedInHeatStorage(
@@ -328,6 +446,18 @@ final case class ThermalGrid(
} yield storage.determineNextThreshold(storageState, qDotStorage)
}.flatten
+ private def handleFeedInHotWaterStorage(
+ state: HpState,
+ qDotStorage: Power,
+ ): Option[ThermalThreshold] = {
+ for {
+ storage <- domesticHotWaterStorage.collect {
+ case s: DomesticHotWaterStorage => s
+ }
+ storageState <- state.thermalGridState.domesticHotWaterStorageState
+ } yield storage.determineNextThreshold(storageState, qDotStorage)
+ }.flatten
+
/** Determines the next threshold of a given input sequence of thresholds.
*
* @param thresholds
@@ -360,6 +490,9 @@ final case class ThermalGrid(
def handleConsumption(
state: HpState
): (ThermalGridOperatingPoint, Option[ThermalThreshold]) = {
+ // handle hot water demand
+ val (qDotHotWaterStorage, thresholdWaterStorage) =
+ handleHotWaterConsumption(state)
/* House will be left with no influx in all cases. Determine if and when a threshold is reached */
val houseThreshold = house.zip(state.thermalGridState.houseState) match {
case Some((thermalHouse, houseState)) =>
@@ -368,7 +501,106 @@ final case class ThermalGrid(
}
/* Check if house can be heated from storage */
- reviseFeedInFromStorage(state, houseThreshold)
+ val (revisedOp, revisedThreshold) =
+ reviseFeedInFromStorage(state, houseThreshold)
+
+ val operatingPoint =
+ revisedOp.copy(qDotDomesticHotWaterStorage = qDotHotWaterStorage)
+ val nextThreshold = determineNextThreshold(
+ Seq(revisedThreshold, thresholdWaterStorage)
+ )
+
+ (operatingPoint, nextThreshold)
+ }
+
+ private def handleHotWaterConsumption(
+ state: HpState
+ ): (Power, Option[ThermalThreshold]) = {
+ val domesticHotWaterDemand =
+ state.thermalDemands.domesticWaterDemandOfHouse
+
+ val (qDot, threshold) = domesticHotWaterStorage.zip(
+ state.thermalGridState.domesticHotWaterStorageState
+ ) match {
+ case Some((_, storageState)) =>
+ // Check if storage can handle the demand
+ if storageState.storedEnergy < domesticHotWaterDemand.required then {
+ // if it can't, take max qDot that empties the storage asap, return the according threshold
+ {
+ identifyApplicableQDot(
+ state,
+ ThermalEnergyDemand(
+ storageState.storedEnergy,
+ storageState.storedEnergy,
+ ),
+ )
+ }
+ } else {
+ // else, choose qDot to fit demand and return the according threshold
+ identifyApplicableQDot(state, domesticHotWaterDemand)
+ }
+ case _ => (zeroKW, None)
+ }
+ (qDot, threshold)
+ }
+
+ private def identifyApplicableQDot(
+ state: HpState,
+ domesticHotWaterDemand: ThermalEnergyDemand,
+ ): (Power, Option[ThermalThreshold]) = {
+ val minimumOperationDuration = Seconds(1)
+ if domesticHotWaterDemand.required > zeroKWh then {
+ val chargingPower = domesticHotWaterStorage
+ .map(_.getpThermalMax)
+ .getOrElse(
+ throw new RuntimeException(
+ s"Trying to get the chargingPower of domesticHotWaterStorage was not possible"
+ )
+ )
+
+ val approxDurationAtFullPower =
+ domesticHotWaterDemand.required / chargingPower
+
+ if approxDurationAtFullPower > minimumOperationDuration then {
+ val preciseChargingPower =
+ -1 * domesticHotWaterDemand.required / Seconds(
+ approxDurationAtFullPower.toSeconds.toLong + 1
+ )
+ val threshold =
+ -1 * domesticHotWaterDemand.required / preciseChargingPower
+
+ (
+ preciseChargingPower,
+ Some(
+ SimpleThermalThreshold(state.tick + math.round(threshold.toSeconds))
+ ),
+ )
+ } else {
+ (
+ -1 * domesticHotWaterDemand.required / minimumOperationDuration,
+ Some(SimpleThermalThreshold(state.tick + 1)),
+ )
+ }
+ } else {
+
+ val nextThreshold = calculateNextHourThreshold(state)
+ (zeroKW, Some(SimpleThermalThreshold(nextThreshold)))
+ }
+ }
+
+ /** Calculates the tick value for the next full hour threshold based on the
+ * current simulation state.
+ * @param state
+ * State of the heat pump.
+ * @return
+ * The tick of the next full hour.
+ */
+ private def calculateNextHourThreshold(state: HpState): Long = {
+ val time = state.simulationTime
+ val nextFullHour: ZonedDateTime =
+ time.plusHours(1).withMinute(0).withSecond(0).withNano(0)
+ val simulationStartTime = time.minusSeconds(state.tick)
+ nextFullHour.toTick(simulationStartTime)
}
/** Check, if the storage can heat the house. This is only done, if
@@ -424,6 +656,7 @@ final case class ThermalGrid(
zeroKW,
thermalStorage.getpThermalMax,
thermalStorage.getpThermalMax * -1,
+ zeroKW,
),
nextThreshold,
)
@@ -496,13 +729,13 @@ final case class ThermalGrid(
def createDomesticHotWaterStorageResult(
storage: DomesticHotWaterStorage
): Option[DomesticHotWaterStorageResult] = {
- state.thermalGridState.heatStorageState // TODO Dummy
+ state.thermalGridState.domesticHotWaterStorageState
.collectFirst { case ThermalStorageState(_, storedEnergy) =>
new DomesticHotWaterStorageResult(
dateTime,
storage.uuid,
storedEnergy.toMegawattHours.asMegaWattHour,
- currentOpThermals.qDotHeatStorage.toMegawatts.asMegaWatt, // TODO Dummy
+ currentOpThermals.qDotDomesticHotWaterStorage.toMegawatts.asMegaWatt,
(storedEnergy / storage.maxEnergyThreshold).asPu,
)
}
@@ -541,9 +774,24 @@ final case class ThermalGrid(
}
}
+ // We always want the results if there are changes, or it's the first tick
+ val maybeDomesticHotWaterStorageResult = {
+ (
+ domesticHotWaterStorage,
+ lastOpThermals.forall(
+ _.qDotDomesticHotWaterStorage != currentOpThermals.qDotDomesticHotWaterStorage
+ ) || state.tick == 0,
+ ) match {
+ case (Some(storage: DomesticHotWaterStorage), true) =>
+ createDomesticHotWaterStorageResult(storage)
+ case _ => None
+ }
+ }
+
Seq(
maybeHouseResult,
maybeHeatStorageResult,
+ maybeDomesticHotWaterStorageResult,
).flatten
}
}
@@ -557,14 +805,26 @@ object ThermalGrid {
.heatStorages()
.asScala
.flatMap {
+ case _: DomesticHotWaterStorageInput =>
+ None
case cylindricalInput: CylindricalStorageInput =>
Some(CylindricalThermalStorage(cylindricalInput))
case _ => None
}
.toSet
+ val domesticHotWaterStorage = input
+ .domesticHotWaterStorages()
+ .asScala
+ .flatMap {
+ case domesticHotWaterInput: DomesticHotWaterStorageInput =>
+ Some(DomesticHotWaterStorage(domesticHotWaterInput))
+ case _ => None
+ }
+ .toSet
new ThermalGrid(
houses.headOption,
heatStorages.headOption,
+ domesticHotWaterStorage.headOption,
)
}
@@ -574,15 +834,17 @@ object ThermalGrid {
* State of the thermal house.
* @param heatStorageState
* State of the thermal heat storage.
+ * @param domesticHotWaterStorageState
+ * State of the domestic hot water storage.
*/
final case class ThermalGridState(
houseState: Option[ThermalHouseState],
heatStorageState: Option[ThermalStorageState],
+ domesticHotWaterStorageState: Option[ThermalStorageState],
) {
- /** This method will return booleans whether there is a heat demand of house
- * or thermal storage as well as a boolean indicating if there is no heat
- * storage, or it is empty.
+ /** This method will return boolean indicating if there is no heat storage,
+ * or it is empty.
*
* @return
* boolean which is true, if there is no heat storage, or it's empty.
@@ -605,6 +867,7 @@ object ThermalGrid {
ThermalHouse.startingState(house, ambientTemperature)
),
thermalGrid.heatStorage.map(_.startingState),
+ thermalGrid.domesticHotWaterStorage.map(_.startingState),
)
/** Wraps the demand of thermal units (thermal house, thermal storage).
@@ -613,10 +876,17 @@ object ThermalGrid {
* The demand of the thermal house.
* @param heatStorageDemand
* The demand of the thermal heat storage.
+ * @param domesticWaterDemandOfHouse
+ * The actual demand for domestic hot water by the house that needs to get
+ * covered from domesticHotWaterStorage.
+ * @param domesticHotWaterStorageDemand
+ * The demand of the domestic hot water storage.
*/
final case class ThermalDemandWrapper(
houseDemand: ThermalEnergyDemand,
heatStorageDemand: ThermalEnergyDemand,
+ domesticWaterDemandOfHouse: ThermalEnergyDemand,
+ domesticHotWaterStorageDemand: ThermalEnergyDemand,
)
/** Defines the thermal energy demand of a thermal grid. It comprises the
diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalStrategyPatterns.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalStrategyPatterns.scala
index 28eeccc285..c745b0f3ba 100644
--- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalStrategyPatterns.scala
+++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalStrategyPatterns.scala
@@ -18,7 +18,8 @@ private sealed trait FeedInStrategy {
def apply(
qDot: Power,
heatStorage: Option[CylindricalThermalStorage],
- ): (Power, Power) // (house, heatStorage)
+ waterStorage: Option[DomesticHotWaterStorage],
+ ): (Power, Power, Power) // (house, heatStorage, waterStorage)
/** Get maximum thermal power of a storage.
*
@@ -55,22 +56,53 @@ private sealed trait FeedInStrategy {
else (zeroKW, qDot)
}
+private object SplitHouseWaterStrategy extends FeedInStrategy {
+ override def apply(
+ qDot: Power,
+ heatStorage: Option[CylindricalThermalStorage],
+ waterStorage: Option[DomesticHotWaterStorage],
+ ): (Power, Power, Power) = {
+ val maxWater = getMaxPower(waterStorage)
+ val halfQDot = qDot / 2
+
+ // Check if water storage can handle half of qDot
+ if halfQDot > maxWater then {
+ val remaining = qDot - maxWater
+ (remaining, zeroKW, maxWater)
+ } else (halfQDot, zeroKW, halfQDot)
+ }
+}
+
private object HouseOnlyStrategy extends FeedInStrategy {
override def apply(
qDot: Power,
heatStorage: Option[CylindricalThermalStorage],
- ): (Power, Power) =
- (qDot, zeroKW)
+ waterStorage: Option[DomesticHotWaterStorage],
+ ): (Power, Power, Power) =
+ (qDot, zeroKW, zeroKW)
+}
+
+private object WaterStorageFirstStrategy extends FeedInStrategy {
+ override def apply(
+ qDot: Power,
+ heatStorage: Option[CylindricalThermalStorage],
+ waterStorage: Option[DomesticHotWaterStorage],
+ ): (Power, Power, Power) = {
+ val maxWater = getMaxPower(waterStorage)
+ val (remaining, toWater) = distribute(qDot, maxWater)
+ (remaining, zeroKW, toWater)
+ }
}
private object HeatStorageFirstStrategy extends FeedInStrategy {
override def apply(
qDot: Power,
heatStorage: Option[CylindricalThermalStorage],
- ): (Power, Power) = {
+ waterStorage: Option[DomesticHotWaterStorage],
+ ): (Power, Power, Power) = {
val maxHeat = getMaxPower(heatStorage)
val (remaining, toHeat) = distribute(qDot, maxHeat)
- (remaining, toHeat)
+ (remaining, toHeat, zeroKW)
}
}
@@ -78,6 +110,7 @@ private object NoOperationStrategy extends FeedInStrategy {
override def apply(
qDot: Power,
heatStorage: Option[CylindricalThermalStorage],
- ): (Power, Power) =
- (zeroKW, zeroKW)
+ waterStorage: Option[DomesticHotWaterStorage],
+ ): (Power, Power, Power) =
+ (zeroKW, zeroKW, zeroKW)
}
diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalThreshold.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalThreshold.scala
index 932c6724c6..3aa75e7b3f 100644
--- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalThreshold.scala
+++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalThreshold.scala
@@ -9,3 +9,4 @@ package edu.ie3.simona.model.thermal
trait ThermalThreshold {
val tick: Long
}
+case class SimpleThermalThreshold(tick: Long) extends ThermalThreshold
diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala
index 23c1522744..b2f487fb06 100644
--- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala
+++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala
@@ -510,6 +510,90 @@ class EmAgentIT
emResult.getQ should equalWithTolerance(-0.0018318880807.asMegaVar)
}
resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(75)))
+
+ /* TICK 75
+ DomesticHotWaterStorage stopped discharging. Expect same behaviour as before
+ LOAD: 0.269 kW (unchanged)
+ PV: -5.842 kW
+ Heat pump: running (turned on from last request), can also be turned off
+ -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state
+ -> remaining -0.723 kW
+ */
+ emAgentActivation ! Activation(75)
+
+ resultListener.expectMessageType[ParticipantResultEvent] match {
+ case ParticipantResultEvent(emResult: EmResult) =>
+ emResult.getInputModel shouldBe emInput.getUuid
+ emResult.getTime shouldBe 75.toDateTime
+ emResult.getP should equalWithTolerance(-0.0055734002706.asMegaWatt)
+ emResult.getQ should equalWithTolerance(-0.00183188808074.asMegaVar)
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(3600)))
+
+ /* TICK 3600
+ DomesticHotWaterStorage stopped discharging. Expect same behaviour as before
+ LOAD: 0.269 kW (unchanged)
+ PV: -3.715 kW
+ Heat pump: running (turned on from last request), can also be turned off
+ -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state
+ -> remaining ~0.0 kW
+ */
+ emAgentActivation ! Activation(3600)
+
+ resultListener.expectMessageType[ParticipantResultEvent] match {
+ case ParticipantResultEvent(emResult: EmResult) =>
+ emResult.getInputModel shouldBe emInput.getUuid
+ emResult.getTime shouldBe 3600.toDateTime
+ emResult.getP should equalWithTolerance(-0.00072340027.asMegaWatt)
+ emResult.getQ should equalWithTolerance(-0.00084705357667.asMegaVar)
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(3675)))
+
+ /* TICK 3675
+ DomesticHotWaterStorage stopped discharging. Expect same behaviour as before
+ LOAD: 0.269 kW (unchanged)
+ PV: -5.842 kW
+ Heat pump: running (turned on from last request), can also be turned off
+ -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state
+ -> remaining -0.723 kW
+ */
+ emAgentActivation ! Activation(3675)
+
+ resultListener.expectMessageType[ParticipantResultEvent] match {
+ case ParticipantResultEvent(emResult: EmResult) =>
+ emResult.getInputModel shouldBe emInput.getUuid
+ emResult.getTime shouldBe 3675.toDateTime
+ emResult.getP should equalWithTolerance(
+ -0.00072340027059.asMegaWatt
+ )
+ emResult.getQ should equalWithTolerance(
+ -0.00084705357666777.asMegaVar
+ )
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(6056)))
+
+ /* TICK 6056
+ DomesticHotWaterStorage stopped discharging. Expect same behaviour as before
+ LOAD: 0.269 kW (unchanged)
+ PV: -5.842 kW
+ Heat pump: running (turned on from last request), can also be turned off
+ -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state
+ -> remaining -0.723 kW
+ */
+ emAgentActivation ! Activation(6056)
+
+ resultListener.expectMessageType[ParticipantResultEvent] match {
+ case ParticipantResultEvent(emResult: EmResult) =>
+ emResult.getInputModel shouldBe emInput.getUuid
+ emResult.getTime shouldBe 6056.toDateTime
+ emResult.getP should equalWithTolerance(-0.00557340027.asMegaWatt)
+ emResult.getQ should equalWithTolerance(-0.00183188808074.asMegaVar)
+ }
+ resultListener.expectNoMessage()
scheduler.expectMessage(Completion(emAgentActivation, Some(7200)))
/* TICK 7200
@@ -540,8 +624,48 @@ class EmAgentIT
emResult.getInputModel shouldBe emInput.getUuid
emResult.getTime shouldBe 7200.toDateTime
emResult.getP should equalWithTolerance(0.001403143271.asMegaWatt)
+ emResult.getQ should equalWithTolerance(-0.0001480925156.asMegaVar)
+ }
+
+ scheduler.expectMessage(Completion(emAgentActivation, Some(7278)))
+
+ /* TICK 7278
+ DomesticHotWaterStorage stopped discharging. Expect same behaviour as before
+ LOAD: 0.269 kW (unchanged)
+ PV: -3.791 kW
+ Heat pump: running (turned on from last request), can also be turned off
+ -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state
+ -> remaining 0 MW
+ */
+ emAgentActivation ! Activation(7278)
+
+ resultListener.expectMessageType[ParticipantResultEvent] match {
+ case ParticipantResultEvent(emResult: EmResult) =>
+ emResult.getInputModel shouldBe emInput.getUuid
+ emResult.getTime shouldBe 7278.toDateTime
+ emResult.getP should equalWithTolerance(0.00140314327091.asMegaWatt)
emResult.getQ should equalWithTolerance(-0.00014809252.asMegaVar)
}
+
+ scheduler.expectMessage(Completion(emAgentActivation, Some(7981)))
+
+ /* TICK 7981
+ DomesticHotWaterStorage stopped discharging. Expect same behaviour as before
+ LOAD: 0.269 kW (unchanged)
+ PV: -3.791 kW
+ Heat pump: running (turned on from last request), can also be turned off
+ -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state
+ -> remaining 0 MW
+ */
+ emAgentActivation ! Activation(7981)
+
+ resultListener.expectMessageType[ParticipantResultEvent] match {
+ case ParticipantResultEvent(emResult: EmResult) =>
+ emResult.getInputModel shouldBe emInput.getUuid
+ emResult.getTime shouldBe 7981.toDateTime
+ emResult.getP should equalWithTolerance(-0.003446856729.asMegaWatt)
+ emResult.getQ should equalWithTolerance(-0.00113292702.asMegaVar)
+ }
resultListener.expectNoMessage()
scheduler.expectMessage(Completion(emAgentActivation, Some(10800)))
@@ -576,6 +700,26 @@ class EmAgentIT
emResult.getQ should equalWithTolerance(-0.000244490516.asMegaVar)
}
resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(10879)))
+
+ /* TICK 10879
+ DomesticHotWaterStorage stopped discharging. Expect same behaviour as before
+ LOAD: 0.269 kW (unchanged)
+ PV: -4.008 kW
+ Heat pump: running (turned on from last request), can also be turned off
+ -> set point ~3.7 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state
+ -> remaining 1.111 kW
+ */
+ emAgentActivation ! Activation(10879)
+
+ resultListener.expectMessageType[ParticipantResultEvent] match {
+ case ParticipantResultEvent(emResult: EmResult) =>
+ emResult.getInputModel shouldBe emInput.getUuid
+ emResult.getTime shouldBe 10879.toDateTime
+ emResult.getP should equalWithTolerance(0.0011098586291.asMegaWatt)
+ emResult.getQ should equalWithTolerance(-0.000244490516.asMegaVar)
+ }
+ resultListener.expectNoMessage()
scheduler.expectMessage(Completion(emAgentActivation, Some(11000)))
/* TICK 11000
@@ -642,11 +786,11 @@ class EmAgentIT
case ParticipantResultEvent(emResult: EmResult) =>
emResult.getInputModel shouldBe emInput.getUuid
emResult.getTime shouldBe 11500.toDateTime
- emResult.getP should equalWithTolerance(0.00013505248.asMegaWatt)
- emResult.getQ should equalWithTolerance(0.000044389603878.asMegaVar)
+ emResult.getP should equalWithTolerance(0.0049850525.asMegaWatt)
+ emResult.getQ should equalWithTolerance(0.0010292241.asMegaVar)
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(28800)))
+ scheduler.expectMessage(Completion(emAgentActivation, Some(12725)))
}
}
diff --git a/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala b/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala
index bc0e214e8b..a641804e06 100644
--- a/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala
+++ b/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala
@@ -57,6 +57,7 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{
ScalaTestWithActorTestKit,
TestProbe,
}
+import org.apache.pekko.actor.typed.ActorRef
import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps
import org.scalatest.OptionValues.convertOptionToValuable
import org.scalatest.matchers.should
@@ -136,7 +137,7 @@ class ThermalGridIT
val hpAgent = spawn(
ParticipantAgentInit(
- typicalHpInputContainer,
+ hpInputContainerLittleWaterStorage,
HpRuntimeConfig(),
outputConfigOn,
Left(scheduler.ref),
@@ -152,6 +153,26 @@ class ThermalGridIT
hpInitSchedule.tick shouldBe INIT_SIM_TICK
val heatPumpAgent = hpInitSchedule.actor
+ /** Helper Method * */
+ def performMultipleActivations(
+ activationActor: ActorRef[Activation],
+ tickPairs: Seq[(Long, Long)],
+ ): Unit = {
+ tickPairs.foreach { case (currentTick, nextTick) =>
+ activationActor ! Activation(currentTick)
+
+ Range(0, 2)
+ .map { _ => resultListener.expectMessageType[ResultEvent] }
+ .foreach {
+ case ParticipantResultEvent(_) =>
+ case ThermalResultEvent(_) =>
+ }
+
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(activationActor, Some(nextTick)))
+ }
+ }
+
/* INIT */
heatPumpAgent ! Activation(INIT_SIM_TICK)
@@ -191,8 +212,10 @@ class ThermalGridIT
/* TICK 0
Start of Simulation
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand ~ 0.0674 kWh, possibleDemand ~ 0.067 kWh
HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
- Heat pump: turned on - to serve the storage demand
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ Heat pump: turned on - to serve the heat storage demand
*/
heatPumpAgent ! Activation(0)
@@ -210,7 +233,7 @@ class ThermalGridIT
)
}
- Range(0, 3)
+ Range(0, 4)
.map { _ =>
resultListener.expectMessageType[ResultEvent]
}
@@ -244,6 +267,54 @@ class ThermalGridIT
time shouldBe 0.toDateTime
qDot should equalWithTolerance(0.011.asMegaWatt)
energy should equalWithTolerance(0.asMegaWattHour)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 0.toDateTime
+ qDot should equalWithTolerance(-0.005405957260274.asMegaWatt)
+ energy should equalWithTolerance(0.000522.asMegaWattHour)
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(45)))
+
+ /* TICK 45
+ Domestic hot water storage stops discharging
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 10.3 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.067 kWh
+ Heat pump: stays on to serve the heat storage demand
+ */
+ heatPumpAgent ! Activation(45)
+
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(hpResult) =>
+ hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
+ hpResult.getTime shouldBe 45.toDateTime
+ hpResult.getP should equalWithTolerance(pRunningHp)
+ hpResult.getQ should equalWithTolerance(qRunningHp)
+ case ResultEvent.ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 45.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.0004544255342.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
@@ -252,7 +323,9 @@ class ThermalGridIT
/* TICK 3416
Heat storage is fully heated up
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 2.36 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.067 kWh
Heat pump: stays on since it was on and the house has possible demand
*/
heatPumpAgent ! Activation(3416)
@@ -298,9 +371,11 @@ class ThermalGridIT
scheduler.expectMessage(Completion(heatPumpAgent, Some(3600)))
/* TICK 3600
- New weather data (unchanged) incoming
+ New weather data (unchanged) incoming + Domestic hot water storage will cover hot water demand
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.92 kWh
+ House demand water : requiredDemand = 0.037 kWh, possibleDemand = 0.037 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.067 kWh
Heat pump: stays on, we got triggered by incoming weather data. So we continue with same behaviour as before
*/
heatPumpAgent ! Activation(3600)
@@ -319,26 +394,84 @@ class ThermalGridIT
)
}
- resultListener.expectMessageType[ParticipantResultEvent] match {
- case ParticipantResultEvent(hpResult) =>
- hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
- hpResult.getTime shouldBe 3600.toDateTime
- hpResult.getP should equalWithTolerance(pRunningHp)
- hpResult.getQ should equalWithTolerance(
- qRunningHp
- )
- }
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(hpResult) =>
+ hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
+ hpResult.getTime shouldBe 3600.toDateTime
+ hpResult.getP should equalWithTolerance(pRunningHp)
+ hpResult.getQ should equalWithTolerance(
+ qRunningHp
+ )
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 3600.toDateTime
+ qDot should equalWithTolerance(-0.005405957260273974.asMegaWatt)
+ energy should equalWithTolerance(
+ 0.00045442553424658.asMegaWattHour
+ )
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(3625)))
- // Since this activation is caused by new weather data, we don't expect any
- // message for house or heat storage since there is no change of their operating
- // point nor one of it reached any boundary.
+ /* TICK 3625
+ Domestic hot water storage will stop discharging.
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.86 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.1 kWh
+ Heat pump: stays on - continue with same behaviour as before
+ */
+ heatPumpAgent ! Activation(3625)
+
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(hpResult) =>
+ hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
+ hpResult.getTime shouldBe 3625.toDateTime
+ hpResult.getP should equalWithTolerance(pRunningHp)
+ hpResult.getQ should equalWithTolerance(
+ qRunningHp
+ )
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 3625.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.00041688416.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
resultListener.expectNoMessage()
scheduler.expectMessage(Completion(heatPumpAgent, Some(4412)))
/* TICK 4412
House reaches target temperature boundary
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.1 kWh
Heat pump: turned off
*/
heatPumpAgent ! Activation(4412)
@@ -371,13 +504,31 @@ class ThermalGridIT
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(heatPumpAgent, Some(21600)))
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(7200)))
+
+ /* We'll jump through a bunch of activations caused from DomesticHotWaterStorage being active.
+ The results are checked implicitly through the state of stored energy at the next result check.
+ */
+ val activationTicksBlock =
+ Seq(7200L, 7215L, 10800L, 10808L, 14400L, 14410L, 18000L, 18015L,
+ 21600L)
+ val tickPairs = activationTicksBlock.zipWithIndex.collect {
+ case (tick, index) if index < activationTicksBlock.length - 1 =>
+ (tick, activationTicksBlock(index + 1))
+ }
+
+ performMultipleActivations(
+ heatPumpAgent,
+ tickPairs,
+ )
/* TICK 21600
House would reach lowerTempBoundary at tick 50797.
But now it's getting colder which should decrease inner temp of house faster.
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 11.57 kWh
+ House demand water : requiredDemand = 0.09 kWh, possibleDemand = 0.09 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.18 kWh
Heat pump: stays off
*/
heatPumpAgent ! Activation(21600)
@@ -396,24 +547,80 @@ class ThermalGridIT
)
}
- resultListener.expectMessageType[ParticipantResultEvent] match {
- case ParticipantResultEvent(hpResult) =>
- hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
- hpResult.getTime shouldBe 21600.toDateTime
- hpResult.getP should equalWithTolerance(0.asMegaWatt)
- hpResult.getQ should equalWithTolerance(0.asMegaVar)
- }
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(hpResult) =>
+ hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
+ hpResult.getTime shouldBe 21600.toDateTime
+ hpResult.getP should equalWithTolerance(0.asMegaWatt)
+ hpResult.getQ should equalWithTolerance(0.asMegaVar)
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 21600.toDateTime
+ qDot should equalWithTolerance(-0.005497583655.asMegaWatt)
+ energy should equalWithTolerance(0.00034555556.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(21659)))
+
+ /* TICK 21659
+ Domestic storage stops discharging
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 11.7 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.27 kWh
+ Heat pump: stays off
+ */
+ heatPumpAgent ! Activation(21659)
- // Since this activation is caused by new weather data, we don't expect any
- // message for house or heat storage since there is no change of their operating
- // point nor one of it reached any boundary.
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(hpResult) =>
+ hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
+ hpResult.getTime shouldBe 21659.toDateTime
+ hpResult.getP should equalWithTolerance(0.asMegaWatt)
+ hpResult.getQ should equalWithTolerance(0.asMegaVar)
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 21659.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(
+ 0.00025545627397260273.asMegaWattHour
+ )
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
resultListener.expectNoMessage()
scheduler.expectMessage(Completion(heatPumpAgent, Some(23288)))
/* TICK 23288
House reach lowerTemperatureBoundary
House demand heating : requiredDemand = 15.0 kWh, possibleDemand = 15.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.27 kWh
Heat pump: stays off, demand should be covered by storage
*/
heatPumpAgent ! Activation(23288)
@@ -459,11 +666,12 @@ class ThermalGridIT
scheduler.expectMessage(Completion(heatPumpAgent, Some(25000)))
/* TICK 25000
- Heat storage will be empty at tick 26704
- Additional trigger caused by (unchanged) weather data should not change this
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 13.24 kWh
- HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 5.23 kWh
- Heat pump: stays off
+ Additional trigger caused by (unchanged) weather data should not change this
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 13.24 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 5.23 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.27 kWh
+ Heat pump: stays off, demand should be covered by storage
*/
heatPumpAgent ! Activation(25000)
@@ -480,25 +688,105 @@ class ThermalGridIT
Some(28000),
)
}
- resultListener.expectMessageType[ParticipantResultEvent] match {
+ resultListener.expectMessageType[ResultEvent] match {
case ParticipantResultEvent(hpResult) =>
hpResult.getInputModel shouldBe typicalHpInputModel.getUuid
hpResult.getTime shouldBe 25000.toDateTime
hpResult.getP should equalWithTolerance(0.asMegaWatt)
hpResult.getQ should equalWithTolerance(0.asMegaVar)
}
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(25200)))
- // Since this activation is caused by new weather data, we don't expect any
- // message for house or heat storage since there is no change of their operating
- // point nor one of it reached any boundary.
+ /* TICK 25200
+ DomesticHotWaterStorage
+ House demand heating : requiredDemand = 0.0kWh, possibleDemand = 13.04 kWh
+ House demand water : requiredDemand = 0.18 kWh, possibleDemand = 0.18 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 5.84 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.27 kWh
+ Heat pump: stays off
+ */
+ heatPumpAgent ! Activation(25200)
+
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 25200.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 25200.toDateTime
+ qDot should equalWithTolerance(-0.00547586188.asMegaWatt)
+ energy should equalWithTolerance(0.000255456274.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(25316)))
+
+ /* TICK 25316
+ DomesticHotWaterStorage stops discharging.
+ House demand heating : requiredDemand = 0.0kWh, possibleDemand = 12.92 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 6.2 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.44 kWh
+ Heat pump: stays off
+ */
+ heatPumpAgent ! Activation(25316)
+
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 25316.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 25316.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.000079011836.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
resultListener.expectNoMessage()
scheduler.expectMessage(Completion(heatPumpAgent, Some(26704)))
/* TICK 26704
- Heat storage is empty
- House demand heating : requiredDemand = 0.0kWh, possibleDemand = 11.51 kWh
- HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
- Heat pump: will be turned on - to serve the remaining heat demand of house (and refill storage later)
+ Heat storage is empty
+ House demand heating : requiredDemand = 0.0kWh, possibleDemand = 11.51 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.44 kWh
+ Heat pump: turned on - to serve the remaining heat demand of house (and refill storage later)
*/
heatPumpAgent ! Activation(26704)
@@ -535,7 +823,9 @@ class ThermalGridIT
/* TICK 28000
New weather data: it's getting warmer again
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 10.19 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.44 kWh
Heat pump: stays on
*/
heatPumpAgent ! Activation(28000)
@@ -565,15 +855,60 @@ class ThermalGridIT
// message for house or storage since there is no change of their operating
// point nor one of it reached any boundary.
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(heatPumpAgent, Some(31837)))
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(28800)))
- /* TICK 31837
- House will reach the upperTemperatureBoundary
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
- HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
- Heat pump: stays on to recharge the storage now
+ /* TICK 28800
+ DomesticHotWaterStorage
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 8.06 kWh
+ House demand water : requiredDemand = 0.25 kWh, possibleDemand = 0.25 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.44 kWh
+ Heat pump: stays on to recharge the HeatStorage now
+ */
+ heatPumpAgent ! Activation(28800)
+
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 28800.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 28800.toDateTime
+ qDot should equalWithTolerance(-0.0054700501581.asMegaWatt)
+ energy should equalWithTolerance(
+ 0.00007901183561643826.asMegaWattHour
+ )
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(28852)))
+
+ /* TICK 28852
+ DomesticHotWaterStorage is empty
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 7.92 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.522 kWh, possibleDemand = 0.522 kWh
+ Heat pump: stays on, qDot should be split between DomesticHotWaterStorage and House
*/
- heatPumpAgent ! Activation(31837)
+ heatPumpAgent ! Activation(28852)
Range(0, 3)
.map { _ =>
@@ -584,7 +919,7 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 31837.toDateTime
+ hpResult._1 shouldBe 28852.toDateTime
hpResult._3 should equalWithTolerance(pRunningHp)
hpResult._4 should equalWithTolerance(qRunningHp)
}
@@ -597,31 +932,34 @@ class ThermalGridIT
indoorTemp,
) =>
inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 31837.toDateTime
- qDot should equalWithTolerance(0.asMegaWatt)
- indoorTemp should equalWithTolerance(19.99.asDegreeCelsius)
+ time shouldBe 28852.toDateTime
+ qDot should equalWithTolerance(0.0055.asMegaWatt)
+ indoorTemp should equalWithTolerance(18.94.asDegreeCelsius)
case AbstractThermalStorageResult(
time,
inputModel,
qDot,
energy,
- ) if inputModel == typicalHeatStorage.getUuid =>
- time shouldBe 31837.toDateTime
- qDot should equalWithTolerance(0.011.asMegaWatt)
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 28852.toDateTime
+ qDot should equalWithTolerance(0.0055.asMegaWatt)
energy should equalWithTolerance(0.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(heatPumpAgent, Some(35253)))
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(29193)))
- /* TICK 35253
- Storage will be fully charged, but meanwhile the house cooled a bit
- House demand heating : requiredDemand = 0.00 kWh, possibleDemand = 1.41 kWh
- HeatStorage : requiredDemand = 0.00 kWh, possibleDemand = 0.00 kWh
- Heat pump: stays on
+ /* TICK 29193
+ DomesticWaterStorage is fully charged
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 7.53 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ Heat pump: stays on to heat the house alone now
*/
- heatPumpAgent ! Activation(35253)
+ heatPumpAgent ! Activation(29193)
Range(0, 3)
.map { _ =>
@@ -632,7 +970,7 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 35253.toDateTime
+ hpResult._1 shouldBe 29193.toDateTime
hpResult._3 should equalWithTolerance(pRunningHp)
hpResult._4 should equalWithTolerance(qRunningHp)
}
@@ -645,33 +983,36 @@ class ThermalGridIT
indoorTemp,
) =>
inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 35253.toDateTime
+ time shouldBe 29193.toDateTime
qDot should equalWithTolerance(0.011.asMegaWatt)
- indoorTemp should equalWithTolerance(19.81.asDegreeCelsius)
+ indoorTemp should equalWithTolerance(19.asDegreeCelsius)
case AbstractThermalStorageResult(
time,
inputModel,
qDot,
energy,
- ) if inputModel == typicalHeatStorage.getUuid =>
- time shouldBe 35253.toDateTime
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 29193.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
- energy should equalWithTolerance(0.01044.asMegaWattHour)
+ energy should equalWithTolerance(0.000522.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(heatPumpAgent, Some(35788)))
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(32032)))
- /* TICK 35788
- Neither house nor heat storage have any demand
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
- HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
- Heat pump: turned off
+ /* TICK 32032
+ House will reach the upperTemperatureBoundary
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ Heat pump: stays on to recharge the HeatStorage now
*/
- heatPumpAgent ! Activation(35788)
+ heatPumpAgent ! Activation(32032)
- Range(0, 2)
+ Range(0, 3)
.map { _ =>
resultListener.expectMessageType[ResultEvent]
}
@@ -680,9 +1021,9 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 35788.toDateTime
- hpResult._3 should equalWithTolerance(0.asMegaWatt)
- hpResult._4 should equalWithTolerance(0.asMegaVar)
+ hpResult._1 shouldBe 32032.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
}
case ThermalResultEvent(thermalUnitResult) =>
thermalUnitResult match {
@@ -693,22 +1034,204 @@ class ThermalGridIT
indoorTemp,
) =>
inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 35788.toDateTime
+ time shouldBe 32032.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
- indoorTemp should equalWithTolerance(20.asDegreeCelsius)
+ indoorTemp should equalWithTolerance(19.99.asDegreeCelsius)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ ) if inputModel == typicalHeatStorage.getUuid =>
+ time shouldBe 32032.toDateTime
+ qDot should equalWithTolerance(0.011.asMegaWatt)
+ energy should equalWithTolerance(0.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(heatPumpAgent, Some(74421)))
- }
- }
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(32400)))
+
+ /* TICK 32400
+ DomesticHotWaterStorage will cover demand
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.15 kWh
+ House demand water : requiredDemand = 0.21 kWh, possibleDemand = 0.21 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 9.32 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ Heat pump: stays on
+ */
+ heatPumpAgent ! Activation(32400)
- "A Thermal Grid with thermal house, thermal storage and heat pump that is controlled by an energy management" should {
- "be initialized correctly and run through some activations" in {
- implicit val simulationStartWithPv: ZonedDateTime =
- TimeUtil.withDefaults.toZonedDateTime("2020-06-01T10:00:00Z")
- val simulationEndWithPv: ZonedDateTime =
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 32400.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 32400.toDateTime
+ qDot should equalWithTolerance(-0.005463467444.asMegaWatt)
+ energy should equalWithTolerance(0.000522.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(32541)))
+
+ /* TICK 32541
+ DomesticHotWaterStorage stops discharging.
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.21 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 8.9 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.21 kWh
+ Heat pump: stays on
+ */
+ heatPumpAgent ! Activation(32541)
+
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 32541.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == littleDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 32541.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.0003080141917.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(35448)))
+
+ /* TICK 35448
+ Storage will be fully charged, but meanwhile the house cooled a bit
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.4 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.21 kWh
+ Heat pump: stays on to again heat the house
+ */
+ heatPumpAgent ! Activation(35448)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 35448.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case ThermalHouseResult(
+ time,
+ inputModel,
+ qDot,
+ indoorTemp,
+ ) =>
+ inputModel shouldBe typicalThermalHouse.getUuid
+ time shouldBe 35448.toDateTime
+ qDot should equalWithTolerance(0.011.asMegaWatt)
+ indoorTemp should equalWithTolerance(19.81.asDegreeCelsius)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ ) if inputModel == typicalHeatStorage.getUuid =>
+ time shouldBe 35448.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.01044.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(35983)))
+
+ /* TICK 35983
+ Thermal House reaches target temperature
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.21 kWh
+ Heat pump: turned off - since neither house nor any storage have any demand
+ */
+ heatPumpAgent ! Activation(35983)
+
+ Range(0, 2)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 35983.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case ThermalHouseResult(
+ time,
+ inputModel,
+ qDot,
+ indoorTemp,
+ ) =>
+ inputModel shouldBe typicalThermalHouse.getUuid
+ time shouldBe 35983.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ indoorTemp should equalWithTolerance(20.asDegreeCelsius)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(heatPumpAgent, Some(36000)))
+ }
+ }
+
+ "A Thermal Grid with thermal house, thermal storage and heat pump that is controlled by an energy management" should {
+ "be initialized correctly and run through some activations" in {
+ implicit val simulationStartWithPv: ZonedDateTime =
+ TimeUtil.withDefaults.toZonedDateTime("2020-06-01T10:00:00Z")
+ val simulationEndWithPv: ZonedDateTime =
TimeUtil.withDefaults.toZonedDateTime("2020-06-12T10:00:00Z")
given SimulationParameters = SimulationParameters(
@@ -765,7 +1288,7 @@ class ThermalGridIT
val hpAgent = spawn(
ParticipantAgentInit(
- typicalHpInputContainer,
+ hpInputContainerSmallWaterStorage,
HpRuntimeConfig(),
outputConfigOn,
Right(emAgent),
@@ -781,6 +1304,26 @@ class ThermalGridIT
emInitSchedule.tick shouldBe INIT_SIM_TICK
val emAgentActivation = emInitSchedule.actor
+ /** Helper Method * */
+ def performMultipleActivations(
+ activationActor: ActorRef[Activation],
+ tickPairs: Seq[(Long, Long)],
+ ): Unit = {
+ tickPairs.foreach { case (currentTick, nextTick) =>
+ activationActor ! Activation(currentTick)
+
+ Range(0, 3)
+ .map { _ => resultListener.expectMessageType[ResultEvent] }
+ .foreach {
+ case ParticipantResultEvent(_) =>
+ case ThermalResultEvent(_) =>
+ }
+
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(activationActor, Some(nextTick)))
+ }
+ }
+
scheduler.expectNoMessage()
emInitSchedule.unlockKey.value.unlock()
@@ -852,9 +1395,11 @@ class ThermalGridIT
/* TICK 0
Start of Simulation, No sun at the moment.
PV: 0.0 kW
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand 0.0 kWh
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.23 kWh, possibleDemand = 0.23 kWh
HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
- Heat pump: stays out - since requiredDemand of heat storage not necessarily demand hp operation.
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ Heat pump: stays out - since requiredDemand of HeatStorage not necessarily demand hp operation.
*/
emAgentActivation ! Activation(0)
@@ -872,7 +1417,7 @@ class ThermalGridIT
)
}
- Range(0, 4)
+ Range(0, 5)
.map { _ =>
resultListener.expectMessageType[ResultEvent]
}
@@ -911,6 +1456,64 @@ class ThermalGridIT
time shouldBe 0.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
energy should equalWithTolerance(0.asMegaWattHour)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 0.toDateTime
+ qDot should equalWithTolerance(-0.005496056547945205.asMegaWatt)
+ energy should equalWithTolerance(0.00149814.asMegaWattHour)
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(150)))
+
+ /* TICK 150
+ Domestic hot water storage stops discharging.
+ PV: 0.0 kW
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.1 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.23 kWh
+ Heat pump: stays out - since requiredDemand of HeatStorage not necessarily demand hp operation.
+ */
+ emAgentActivation ! Activation(150)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 150.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 150.toDateTime
+ emResult._3 should equalWithTolerance(0.asMegaWatt)
+ emResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 150.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.0012691376438.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
@@ -920,8 +1523,11 @@ class ThermalGridIT
New Weather: The sun comes out, PV will produce.
PV: -6.3 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.25 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
- Heat pump: turns on - since now we have flexibility potential available which can be used by hp to serve the reqDemand of ThermalStorage
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.23 kWh
+ Heat pump: turns on - since now we have flexibility potential available which can be
+ used by hp to serve the reqDemand of ThermalStorage
*/
emAgentActivation ! Activation(1800)
@@ -973,6 +1579,108 @@ class ThermalGridIT
time shouldBe 1800.toDateTime
qDot should equalWithTolerance(0.011.asMegaWatt)
energy should equalWithTolerance(0.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(3600)))
+
+ /* TICK 3600
+ DomesticHotWaterStorage will serve the water demand of the house
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 2.5 kWh
+ House demand water : requiredDemand = 0.23 kWh, possibleDemand = 0.23 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 4.9 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.23 kWh
+ Heat pump: stays on
+ */
+ emAgentActivation ! Activation(3600)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 3600.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 3600.toDateTime
+ emResult._3 should equalWithTolerance(
+ -0.002517561515.asMegaWatt
+ )
+ emResult._4 should equalWithTolerance(
+ -0.00082748245392177.asMegaVar
+ )
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 3600.toDateTime
+ qDot should equalWithTolerance(-0.005496056547945205.asMegaWatt)
+ energy should equalWithTolerance(
+ 0.0012691376438356.asMegaWattHour
+ )
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(3750)))
+
+ /* TICK 3750
+ DomesticHotWaterStorage stops discharging.
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 2.6 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.44 kWh, possibleDemand = 4.4 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.46 kWh
+ Heat pump: stays on - since now we have flexibility potential available which
+ can be used by hp to serve the reqDemand of ThermalStorage
+ */
+ emAgentActivation ! Activation(3750)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 3750.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 3750.toDateTime
+ emResult._3 should equalWithTolerance(-0.00251756152.asMegaWatt)
+ emResult._4 should equalWithTolerance(-0.00082748245.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 3750.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.001040135288.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
@@ -982,7 +1690,9 @@ class ThermalGridIT
Storage is fully heated up, meanwhile house cooled a bit.
PV: -6.3 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 3.59 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
Heat pump: stays on since it was on and the house has possible demand
*/
emAgentActivation ! Activation(5216)
@@ -1027,6 +1737,16 @@ class ThermalGridIT
time shouldBe 5216.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
energy should equalWithTolerance(0.01044.asMegaWattHour)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 5216.toDateTime
+ qDot should equalWithTolerance(-0.010971095671.asMegaWatt)
+ energy should equalWithTolerance(0.001269575507.asMegaWattHour)
}
}
resultListener.expectNoMessage()
@@ -1036,7 +1756,9 @@ class ThermalGridIT
PV: 0.0 kW
New weather data, sun is gone again, thus we should now heat the house by storage.
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 3.15 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.45 kWh
Heat pump: turns off
*/
emAgentActivation ! Activation(5400)
@@ -1094,7 +1816,9 @@ class ThermalGridIT
The house reaches target temperature
PV: 0.0 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 4.07 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.45 kWh
Heat pump: stays off
*/
emAgentActivation ! Activation(6731)
@@ -1142,14 +1866,108 @@ class ThermalGridIT
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(9200)))
+ scheduler.expectMessage(Completion(emAgentActivation, Some(7200)))
- /* TICK 9200
- The sun is back again, storage first.
- PV: -5.2 kW
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.7 kWh
+ /* TICK 7200
+ DomesticHotWaterStorage will serve the water demand of the house
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.32 kWh
+ House demand water : requiredDemand = 0.24 kWh, possibleDemand = 0.24 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 4.07 kWh
- Heat pump: turned on
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.45 kWh
+ Heat pump: stays off
+ */
+ emAgentActivation ! Activation(7200)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 7200.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 7200.toDateTime
+ emResult._3 should equalWithTolerance(0.asMegaWatt)
+ emResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 7200.toDateTime
+ qDot should equalWithTolerance(-0.00549315011931065.asMegaWatt)
+ energy should equalWithTolerance(0.001040135288.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(7355)))
+
+ /* TICK 7355
+ DomesticHotWaterStorage stops discharging
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.43 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 4.07 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.69 kWh
+ Heat pump: stays off
+ */
+ emAgentActivation ! Activation(7355)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 7355.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 7355.toDateTime
+ emResult._3 should equalWithTolerance(0.asMegaWatt)
+ emResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 7355.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.0008036246575.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(9200)))
+
+ /* TICK 9200
+ The sun is back again, storage first.
+ PV: -5.2 kW
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.7 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 4.07 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.69 kWh
+ Heat pump: turned on
*/
emAgentActivation ! Activation(9200)
@@ -1206,7 +2024,9 @@ class ThermalGridIT
Storage is full, now heating the house till target temperature.
PV: -5.2 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 2.62 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
Heat pump: stays on
*/
emAgentActivation ! Activation(10531)
@@ -1254,13 +2074,115 @@ class ThermalGridIT
}
}
resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(10800)))
+
+ /* TICK 10800
+ House has water demand
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.98 kWh
+ House demand water : requiredDemand = 0.24 kWh, possibleDemand = 0.24 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.69 kWh
+ Heat pump: stays on
+ */
+ emAgentActivation ! Activation(10800)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 10800.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 10800.toDateTime
+ emResult._3 should equalWithTolerance(
+ -0.0013527980811.asMegaWatt
+ )
+ emResult._4 should equalWithTolerance(
+ -0.0004446432268.asMegaVar
+ )
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 10800.toDateTime
+ qDot should equalWithTolerance(-0.005474387099011617.asMegaWatt)
+ energy should equalWithTolerance(0.000803624658.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(10958)))
+
+ /* TICK 10958
+ DomesticHotWaterStorage stops discharging
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.61 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.93 kWh
+ Heat pump: stays on
+ */
+ emAgentActivation ! Activation(10958)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 10958.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 10958.toDateTime
+ emResult._3 should equalWithTolerance(
+ -0.0013527980811294546.asMegaWatt
+ )
+ emResult._4 should equalWithTolerance(
+ -0.0004446432267837181.asMegaVar
+ )
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 10958.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.00056335989.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
scheduler.expectMessage(Completion(emAgentActivation, Some(11638)))
/* TICK 11638
House reaches target temperature boundary.
PV: -5.2 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.93 kWh
Heat pump: turned off
*/
emAgentActivation ! Activation(11638)
@@ -1305,7 +2227,9 @@ class ThermalGridIT
but now it's getting colder which should decrease inner temp of house faster, but the sun is still there.
PV: -5.2 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.25 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.93 kWh
Heat pump: turned on, since there is possibleDemand and setPower is 3800 W which is > 0.5 sRated of Hp
*/
emAgentActivation ! Activation(12000)
@@ -1363,7 +2287,9 @@ class ThermalGridIT
PV: 0.0 kW
House reaches the target temperature.
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.93 kWh
Heat pump: turned off
*/
emAgentActivation ! Activation(12139)
@@ -1408,7 +2334,9 @@ class ThermalGridIT
Inner temperature of the house is decreasing but above the lower boundary.
Thus, updated weather data (sun is gone) should not change behaviour.
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.45 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.93 kWh
Heat pump: stays off
*/
emAgentActivation ! Activation(12500)
@@ -1446,13 +2374,31 @@ class ThermalGridIT
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(24412)))
+ scheduler.expectMessage(Completion(emAgentActivation, Some(14400)))
+
+ /* We'll jump through a bunch of activations caused from DomesticHotWaterStorage being active.
+ The results are checked implicitly through the state of stored energy at the next result check.
+ */
+ val firstActivationTicksBlock =
+ Seq(14400L, 14526L, 18000L, 18109L, 21600L, 21706L, 24412L)
+
+ val firstTickPairs = firstActivationTicksBlock.zipWithIndex.collect {
+ case (tick, index) if index < firstActivationTicksBlock.length - 1 =>
+ (tick, firstActivationTicksBlock(index + 1))
+ }
+
+ performMultipleActivations(
+ emAgentActivation,
+ firstTickPairs,
+ )
/* TICK 24412
House reaches lower boundary, since we don't have surplus energy from pv, we would use the energy from storage to heat the house.
PV: 0.0 kW
House demand heating : requiredDemand = 15.0 kWh, possibleDemand = 15.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 1.45 kWh
Heat pump: stays off
*/
emAgentActivation ! Activation(24412)
@@ -1506,7 +2452,9 @@ class ThermalGridIT
The sun comes out and it's getting warmer.
PV: -4.4 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 13.53 kWh
+ House demand water : requiredDemand = 0.18 kWh, possibleDemand = 0.18 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.4 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 1.45 kWh
Heat pump: will be turned on and will continue heating the house
*/
emAgentActivation ! Activation(25200)
@@ -1525,7 +2473,7 @@ class ThermalGridIT
)
}
- Range(0, 3)
+ Range(0, 4)
.map { _ =>
resultListener.expectMessageType[ResultEvent]
}
@@ -1554,6 +2502,127 @@ class ThermalGridIT
time shouldBe 25200.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
energy should equalWithTolerance(0.0080322222222.asMegaWattHour)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 25200.toDateTime
+ qDot should equalWithTolerance(-0.00543467835616.asMegaWatt)
+ energy should equalWithTolerance(0.000045288986.asMegaWattHour)
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(25230)))
+
+ /* TICK 25230
+ DomesticHotWaterStorage is empty.
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 13.46 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.41 kWh
+ DomesticWaterStorage : requiredDemand = 1.5 kWh, possibleDemand = 1.5 kWh
+ Heat pump: Stays on, but qDot will be split between house and hot water storage
+ */
+ emAgentActivation ! Activation(25230)
+
+ Range(0, 4)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 25230.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 25230.toDateTime
+ emResult._3 should equalWithTolerance(-0.00055721828.asMegaWatt)
+ emResult._4 should equalWithTolerance(-0.000183148792.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case ThermalHouseResult(
+ time,
+ inputModel,
+ qDot,
+ indoorTemp,
+ ) =>
+ inputModel shouldBe typicalThermalHouse.getUuid
+ time shouldBe 25230.toDateTime
+ qDot should equalWithTolerance(0.0055.asMegaWatt)
+ indoorTemp should equalWithTolerance(18.20.asDegreeCelsius)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 25230.toDateTime
+ qDot should equalWithTolerance(0.0055.asMegaWatt)
+ energy should equalWithTolerance(0.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(26210)))
+
+ /* TICK 26210
+ DomesticHotWaterStorage is full
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 12.6 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.41 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ Heat pump: stays on - will continue heating the house only
+ */
+ emAgentActivation ! Activation(26210)
+
+ Range(0, 4)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 26210.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 26210.toDateTime
+ emResult._3 should equalWithTolerance(-0.00055721828.asMegaWatt)
+ emResult._4 should equalWithTolerance(-0.00018314879.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case ThermalHouseResult(
+ time,
+ inputModel,
+ qDot,
+ indoorTemp,
+ ) =>
+ inputModel shouldBe typicalThermalHouse.getUuid
+ time shouldBe 26210.toDateTime
+ qDot should equalWithTolerance(0.011.asMegaWatt)
+ indoorTemp should equalWithTolerance(18.32.asDegreeCelsius)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 26210.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.00149814.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
@@ -1564,7 +2633,9 @@ class ThermalGridIT
Additional trigger caused by (unchanged) weather data should not change this.
PV: -3.9 kW
House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 9.5 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.41 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
Heat pump: stays on
*/
emAgentActivation ! Activation(27500)
@@ -1602,18 +2673,19 @@ class ThermalGridIT
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(30872)))
-
- /* TICK 30872
- House reaches target temperature, since Hp is running we now charge the storage.
- PV: -3.9 kW
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
- HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.41 kWh
- Heat pump: stays on - to serve the remaining heat demand of the heat storage.
+ scheduler.expectMessage(Completion(emAgentActivation, Some(28800)))
+
+ /* TICK 28800
+ DomesticHotWaterStorage discharges
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 6.39 kWh
+ House demand water : requiredDemand = 0.21 kWh, possibleDemand = 0.21 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.41 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ Heat pump: stays on
*/
- emAgentActivation ! Activation(30872)
+ emAgentActivation ! Activation(28800)
- Range(0, 4)
+ Range(0, 3)
.map { _ =>
resultListener.expectMessageType[ResultEvent]
}
@@ -1622,38 +2694,73 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 30872.toDateTime
+ hpResult._1 shouldBe 28800.toDateTime
hpResult._3 should equalWithTolerance(pRunningHp)
hpResult._4 should equalWithTolerance(qRunningHp)
case EmResult(emResult) =>
emResult._2 shouldBe emInput.getUuid
- emResult._1 shouldBe 30872.toDateTime
- emResult._3 should equalWithTolerance(
- -0.000063896497.asMegaWatt
- )
+ emResult._1 shouldBe 28800.toDateTime
+ emResult._3 should equalWithTolerance(-0.0000638965.asMegaWatt)
emResult._4 should equalWithTolerance(-0.000021001763.asMegaVar)
}
case ThermalResultEvent(thermalUnitResult) =>
thermalUnitResult match {
- case ThermalHouseResult(
+ case AbstractThermalStorageResult(
time,
inputModel,
qDot,
- indoorTemp,
- ) =>
- inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 30872.toDateTime
- qDot should equalWithTolerance(0.asMegaWatt)
- indoorTemp should equalWithTolerance(20.asDegreeCelsius)
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 28800.toDateTime
+ qDot should equalWithTolerance(-0.0054634674439.asMegaWatt)
+ energy should equalWithTolerance(0.00149814.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(28941)))
+
+ /* TICK 28941
+ DomesticHotWaterStorage stops discharging.
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 6.06 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.41 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.21 kWh
+ Heat pump: stays on
+ */
+ emAgentActivation ! Activation(28941)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 28941.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 28941.toDateTime
+ emResult._3 should equalWithTolerance(-0.0000638965.asMegaWatt)
+ emResult._4 should equalWithTolerance(-0.000021001763.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
case AbstractThermalStorageResult(
time,
inputModel,
qDot,
energy,
- ) if inputModel == typicalHeatStorage.getUuid =>
- time shouldBe 30872.toDateTime
- qDot should equalWithTolerance(0.011.asMegaWatt)
- energy should equalWithTolerance(0.008032222222.asMegaWattHour)
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 28941.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.001284154192.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
@@ -1661,11 +2768,14 @@ class ThermalGridIT
scheduler.expectMessage(Completion(emAgentActivation, Some(31000)))
/* TICK 31000
- The sun is gone again, it's getting colder as well.
- PV: 0.0 kW
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.1 kWh
- HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.02 kWh
- Heat pump: Will be turned off since no required demand need to be covered.
+ The sun is gone again, it's getting colder as well.
+ PV: 0.0 kW
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 1.17 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 2.41 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.21 kWh
+ Heat pump: Will be turned off since setPower of EM is zero and
+ the heating of the house can be continued from storage.
*/
emAgentActivation ! Activation(31000)
@@ -1679,7 +2789,7 @@ class ThermalGridIT
Celsius(-35d),
MetersPerSecond(0d),
),
- Some(145000),
+ Some(46800),
)
}
@@ -1710,22 +2820,98 @@ class ThermalGridIT
energy,
) if inputModel == typicalHeatStorage.getUuid =>
time shouldBe 31000.toDateTime
+ qDot should equalWithTolerance(-0.011.asMegaWatt)
+ energy should equalWithTolerance(0.00803222222.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(31762)))
+
+ /* TICK 31762
+ House reaches target temperature.
+ PV: 0.0 kW
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 4.7 kW
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.21 kWh
+ Heat pump: stays off.
+ */
+ emAgentActivation ! Activation(31762)
+
+ Range(0, 4)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 31762.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 31762.toDateTime
+ emResult._3 should equalWithTolerance(0.asMegaWatt)
+ emResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case ThermalHouseResult(
+ time,
+ inputModel,
+ qDot,
+ indoorTemp,
+ ) =>
+ inputModel shouldBe typicalThermalHouse.getUuid
+ time shouldBe 31762.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
- energy should equalWithTolerance(0.0084233333333.asMegaWattHour)
+ indoorTemp should equalWithTolerance(19.99.asDegreeCelsius)
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ ) if inputModel == typicalHeatStorage.getUuid =>
+ time shouldBe 31762.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.0057038888889.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(40942)))
+ scheduler.expectMessage(Completion(emAgentActivation, Some(32400)))
- /* TICK 40942
- House reach lower temperature boundary
- PV: 0.0 kW
- House demand heating : requiredDemand = 15.00 kWh, possibleDemand = 15.00 kWh
- HeatStorage : requiredDemand = 0.00 kWh, possibleDemand = 2.02 kWh
- Heat pump: stays off - demand will be covered by storage.
+ /* We'll jump through a bunch of activations caused from DomesticHotWaterStorage being active.
+ The results are checked implicitly through the state of stored energy at the next result check.
*/
- emAgentActivation ! Activation(40942)
+
+ val secondActivationTicksBlock =
+ Seq(32400L, 32560L, 36000L, 36163L, 39600L, 39743L, 41762L)
+
+ val secondTickPairs = secondActivationTicksBlock.zipWithIndex.collect {
+ case (tick, index) if index < secondActivationTicksBlock.length - 1 =>
+ (tick, secondActivationTicksBlock(index + 1))
+ }
+
+ performMultipleActivations(
+ emAgentActivation,
+ secondTickPairs,
+ )
+
+ /* TICK 41762
+ House reaches lower temperature.
+ PV: 0.0 kW
+ House demand heating : requiredDemand = 15.0 kWh, possibleDemand = 15.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 4.7 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 0.92 kWh
+ Heat pump: stays off - demand will be served by storage.
+ */
+ emAgentActivation ! Activation(41762)
Range(0, 4)
.map { _ =>
@@ -1736,12 +2922,12 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 40942.toDateTime
+ hpResult._1 shouldBe 41762.toDateTime
hpResult._3 should equalWithTolerance(0.asMegaWatt)
hpResult._4 should equalWithTolerance(0.asMegaVar)
case EmResult(emResult) =>
emResult._2 shouldBe emInput.getUuid
- emResult._1 shouldBe 40942.toDateTime
+ emResult._1 shouldBe 41762.toDateTime
emResult._3 should equalWithTolerance(0.asMegaWatt)
emResult._4 should equalWithTolerance(0.asMegaVar)
}
@@ -1754,7 +2940,7 @@ class ThermalGridIT
indoorTemp,
) =>
inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 40942.toDateTime
+ time shouldBe 41762.toDateTime
qDot should equalWithTolerance(0.011.asMegaWatt)
indoorTemp should equalWithTolerance(18.asDegreeCelsius)
case AbstractThermalStorageResult(
@@ -1763,27 +2949,89 @@ class ThermalGridIT
qDot,
energy,
) if inputModel == typicalHeatStorage.getUuid =>
- time shouldBe 40942.toDateTime
+ time shouldBe 41762.toDateTime
qDot should equalWithTolerance(-0.011.asMegaWatt)
- energy should equalWithTolerance(0.008423333333.asMegaWattHour)
+ energy should equalWithTolerance(0.005703888889.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(43698)))
-
- /* TICK 43698
- Storage is empty now.
- Note: One could argue, that new weather should not change the operation of an agent (at least,
- if the weather did not change the flexOptions), but so far we don't check for this.
- Thus, the Hp will stop operation since it can be turned off
- (lower Temp < innerTemp < targetTemp && storage must not directly recharged).
- PV: 0.0 kW
- House demand heating : requiredDemand = 0.00 kWh, possibleDemand = 10.65 kWh
- HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ scheduler.expectMessage(Completion(emAgentActivation, Some(43200)))
+
+ /* We'll jump through a bunch of activations caused from DomesticHotWaterStorage being active.
+The results are checked implicitly through the state of stored energy at the next result check.
+ */
+ val thirdActivationTicksBlock =
+ Seq(43200L, 43311L)
+
+ val thirdTickPairs = thirdActivationTicksBlock.zipWithIndex.collect {
+ case (tick, index) if index < thirdActivationTicksBlock.length - 1 =>
+ (tick, thirdActivationTicksBlock(index + 1))
+ }
+
+ performMultipleActivations(
+ emAgentActivation,
+ thirdTickPairs,
+ )
+
+ /* TICK 43311
+ Domestic hot water storage stops discharging
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 12.55 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 0.0 kWh, possibleDemand = 9.46 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 1.09 kWh
Heat pump: stays off
*/
- emAgentActivation ! Activation(43698)
+ emAgentActivation ! Activation(43311)
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 43311.toDateTime
+ hpResult._3 should equalWithTolerance(0.asMegaWatt)
+ hpResult._4 should equalWithTolerance(0.asMegaVar)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 43311.toDateTime
+ emResult._3 should equalWithTolerance(0.asMegaWatt)
+ emResult._4 should equalWithTolerance(0.asMegaVar)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 43311.toDateTime
+ qDot should equalWithTolerance(0.asMegaWatt)
+ energy should equalWithTolerance(0.0004056861.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(43628)))
+
+ /* TICK 43628
+ Storage is empty now.
+ Note: One could argue, that the Hp now should be started to continue heating of the house,
+ but actually we don't support this. So the house is cooling down now.
+ PV: 0.0 kW
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 12.05 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 1.09 kWh
+ Heat pump: stays off.
+ */
+ emAgentActivation ! Activation(43628)
Range(0, 4)
.map { _ =>
@@ -1794,12 +3042,12 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 43698.toDateTime
+ hpResult._1 shouldBe 43628.toDateTime
hpResult._3 should equalWithTolerance(0.asMegaWatt)
hpResult._4 should equalWithTolerance(0.asMegaVar)
case EmResult(emResult) =>
emResult._2 shouldBe emInput.getUuid
- emResult._1 shouldBe 43698.toDateTime
+ emResult._1 shouldBe 43628.toDateTime
emResult._3 should equalWithTolerance(0.asMegaWatt)
emResult._4 should equalWithTolerance(0.asMegaVar)
}
@@ -1812,32 +3060,34 @@ class ThermalGridIT
indoorTemp,
) =>
inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 43698.toDateTime
+ time shouldBe 43628.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
- indoorTemp should equalWithTolerance(18.58.asDegreeCelsius)
+ indoorTemp should equalWithTolerance(18.39.asDegreeCelsius)
case AbstractThermalStorageResult(
time,
inputModel,
qDot,
energy,
) if inputModel == typicalHeatStorage.getUuid =>
- time shouldBe 43698.toDateTime
+ time shouldBe 43628.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
energy should equalWithTolerance(0.asMegaWattHour)
case _ => fail("Unexpected thermal unit result")
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(46631)))
+ scheduler.expectMessage(Completion(emAgentActivation, Some(45620)))
- /* TICK 46631
- House will reach the lower temperature boundary
+ /* TICK 45620
+ House reaches lower temperature.
PV: 0.0 kW
- House demand heating : requiredDemand = 15.00 kWh, possibleDemand = 15.00 kWh
+ House demand heating : requiredDemand = 15.0 kWh, possibleDemand = 15.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 1.09 kWh
Heat pump: turned on to heat the house
*/
- emAgentActivation ! Activation(46631)
+ emAgentActivation ! Activation(45620)
Range(0, 3)
.map { _ =>
@@ -1848,12 +3098,12 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 46631.toDateTime
+ hpResult._1 shouldBe 45620.toDateTime
hpResult._3 should equalWithTolerance(pRunningHp)
hpResult._4 should equalWithTolerance(qRunningHp)
case EmResult(emResult) =>
emResult._2 shouldBe emInput.getUuid
- emResult._1 shouldBe 46631.toDateTime
+ emResult._1 shouldBe 45620.toDateTime
emResult._3 should equalWithTolerance(pRunningHp)
emResult._4 should equalWithTolerance(qRunningHp)
}
@@ -1866,22 +3116,101 @@ class ThermalGridIT
indoorTemp,
) =>
inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 46631.toDateTime
+ time shouldBe 45620.toDateTime
qDot should equalWithTolerance(0.011.asMegaWatt)
indoorTemp should equalWithTolerance(18.asDegreeCelsius)
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(56274)))
+ scheduler.expectMessage(Completion(emAgentActivation, Some(46800)))
- /* TICK 56274
- House will reach target temperature
+ /* TICK 46800
+ New weather data should not change behaviour.
PV: 0.0 kW
- House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 13.13 kWh
+ House demand water : requiredDemand = 0.12 kWh, possibleDemand = 0.12 kWh
HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
- Heat pump: turned off - no surplus energy to recharge the storage now
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 1.09 kWh
+ Heat pump: stays on
+ */
+ emAgentActivation ! Activation(46800)
+
+ weatherDependentAgents.foreach {
+ _ ! DataProvision(
+ 46800,
+ weatherService.ref,
+ WeatherData(
+ WattsPerSquareMeter(0),
+ WattsPerSquareMeter(0),
+ Celsius(-35d),
+ MetersPerSecond(0d),
+ ),
+ Some(57600),
+ )
+ }
+
+ Range(0, 3)
+ .map { _ =>
+ resultListener.expectMessageType[ResultEvent]
+ }
+ .foreach {
+ case ParticipantResultEvent(participantResult) =>
+ participantResult match {
+ case HpResult(hpResult) =>
+ hpResult._2 shouldBe typicalHpInputModel.getUuid
+ hpResult._1 shouldBe 46800.toDateTime
+ hpResult._3 should equalWithTolerance(pRunningHp)
+ hpResult._4 should equalWithTolerance(qRunningHp)
+ case EmResult(emResult) =>
+ emResult._2 shouldBe emInput.getUuid
+ emResult._1 shouldBe 46800.toDateTime
+ emResult._3 should equalWithTolerance(pRunningHp)
+ emResult._4 should equalWithTolerance(qRunningHp)
+ }
+ case ThermalResultEvent(thermalUnitResult) =>
+ thermalUnitResult match {
+ case AbstractThermalStorageResult(
+ time,
+ inputModel,
+ qDot,
+ energy,
+ )
+ if inputModel == smallDomesticHotWaterStorageInput.getUuid =>
+ time shouldBe 46800.toDateTime
+ qDot should equalWithTolerance(-0.005474387099.asMegaWatt)
+ energy should equalWithTolerance(0.000405686137.asMegaWattHour)
+ case _ => fail("Unexpected thermal unit result")
+ }
+ }
+ resultListener.expectNoMessage()
+ scheduler.expectMessage(Completion(emAgentActivation, Some(46879)))
+
+ /* We'll jump through a bunch of activations caused from DomesticHotWaterStorage being active.
+ The results are checked implicitly through the state of stored energy at the next result check.
*/
- emAgentActivation ! Activation(56274)
+ val fourthActivationTicksBlock =
+ Seq(46879L, 50400L, 50445L, 54000L, 54025L, 55263L)
+
+ val fourthTickPairs = fourthActivationTicksBlock.zipWithIndex.collect {
+ case (tick, index) if index < fourthActivationTicksBlock.length - 1 =>
+ (tick, fourthActivationTicksBlock(index + 1))
+ }
+
+ performMultipleActivations(
+ emAgentActivation,
+ fourthTickPairs,
+ )
+
+ /* TICK 55263
+ House reaches target temperature.
+ House demand heating : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ House demand water : requiredDemand = 0.0 kWh, possibleDemand = 0.0 kWh
+ HeatStorage : requiredDemand = 10.44 kWh, possibleDemand = 10.44 kWh
+ DomesticWaterStorage : requiredDemand = 0.0 kWh, possibleDemand = 1.32 kWh
+ Heat pump: turned off, storage won't be recharged since EM setPower is zero.
+ */
+
+ emAgentActivation ! Activation(55263)
Range(0, 3)
.map { _ =>
@@ -1892,12 +3221,12 @@ class ThermalGridIT
participantResult match {
case HpResult(hpResult) =>
hpResult._2 shouldBe typicalHpInputModel.getUuid
- hpResult._1 shouldBe 56274.toDateTime
+ hpResult._1 shouldBe 55263.toDateTime
hpResult._3 should equalWithTolerance(0.asMegaWatt)
hpResult._4 should equalWithTolerance(0.asMegaVar)
case EmResult(emResult) =>
emResult._2 shouldBe emInput.getUuid
- emResult._1 shouldBe 56274.toDateTime
+ emResult._1 shouldBe 55263.toDateTime
emResult._3 should equalWithTolerance(0.asMegaWatt)
emResult._4 should equalWithTolerance(0.asMegaVar)
}
@@ -1910,13 +3239,13 @@ class ThermalGridIT
indoorTemp,
) =>
inputModel shouldBe typicalThermalHouse.getUuid
- time shouldBe 56274.toDateTime
+ time shouldBe 55263.toDateTime
qDot should equalWithTolerance(0.asMegaWatt)
indoorTemp should equalWithTolerance(20.asDegreeCelsius)
}
}
resultListener.expectNoMessage()
- scheduler.expectMessage(Completion(emAgentActivation, Some(66274)))
+ scheduler.expectMessage(Completion(emAgentActivation, Some(57600)))
}
}
}
diff --git a/src/test/scala/edu/ie3/simona/model/participant/hp/HpModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/hp/HpModelSpec.scala
index 929a0a3bd9..6cbc550fc3 100644
--- a/src/test/scala/edu/ie3/simona/model/participant/hp/HpModelSpec.scala
+++ b/src/test/scala/edu/ie3/simona/model/participant/hp/HpModelSpec.scala
@@ -105,7 +105,12 @@ class HpModelSpec
thermalGridState = thermalState(Celsius(0), ambientTemperature),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(80),
- ThermalGridOperatingPoint(Kilowatts(80), Kilowatts(80), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(80),
+ Kilowatts(80),
+ zeroKW,
+ zeroKW,
+ ),
),
),
16.3142322,
@@ -117,7 +122,12 @@ class HpModelSpec
thermalGridState = thermalState(Celsius(2), ambientTemperature),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(80),
- ThermalGridOperatingPoint(Kilowatts(80), Kilowatts(80), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(80),
+ Kilowatts(80),
+ zeroKW,
+ zeroKW,
+ ),
),
),
17.9516937,
@@ -129,7 +139,12 @@ class HpModelSpec
thermalGridState = thermalState(Celsius(17), ambientTemperature),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(80),
- ThermalGridOperatingPoint(Kilowatts(80), Kilowatts(80), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(80),
+ Kilowatts(80),
+ zeroKW,
+ zeroKW,
+ ),
),
),
30.232655,
@@ -152,6 +167,7 @@ class HpModelSpec
zeroKW,
state.lastHpOperatingPoint.thermalOps.qDotHouse,
zeroKW,
+ zeroKW,
)
)
val expectedDemand = ThermalDemandWrapper(
@@ -163,6 +179,8 @@ class HpModelSpec
KilowattHours(exptHeatStorageDemand._1),
KilowattHours(exptHeatStorageDemand._2),
),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand(zeroKWh, zeroKWh),
)
val updatedState = hpModel.determineState(
@@ -176,7 +194,7 @@ class HpModelSpec
case HpState(
tick,
_,
- ThermalGridState(Some(thermalHouseState), _),
+ ThermalGridState(Some(thermalHouseState), _, _),
_,
thermalDemands,
) =>
@@ -233,6 +251,7 @@ class HpModelSpec
ThermalGridState(
Some(ThermalHouseState(tick, ambientTemperature, Celsius(19))),
None,
+ None,
),
HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
ThermalDemandWrapper(
@@ -240,7 +259,9 @@ class HpModelSpec
KilowattHours(requiredDemandHouse),
KilowattHours(requiredDemandHouse),
),
- ThermalEnergyDemand(zeroKWh, zeroKWh),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
),
)
@@ -283,6 +304,7 @@ class HpModelSpec
ThermalGridState(
Some(ThermalHouseState(tick, ambientTemperature, Celsius(19))),
None,
+ None,
),
HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
ThermalDemandWrapper(
@@ -290,7 +312,9 @@ class HpModelSpec
KilowattHours(requiredDemandHouse),
KilowattHours(requiredDemandHouse),
),
- ThermalEnergyDemand(zeroKWh, zeroKWh),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
),
)
val setPower = Kilowatts(setPwr)
diff --git a/src/test/scala/edu/ie3/simona/model/participant/hp/HpPowerLimitFlexModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/hp/HpPowerLimitFlexModelSpec.scala
index 43aa070715..4111259de4 100644
--- a/src/test/scala/edu/ie3/simona/model/participant/hp/HpPowerLimitFlexModelSpec.scala
+++ b/src/test/scala/edu/ie3/simona/model/participant/hp/HpPowerLimitFlexModelSpec.scala
@@ -79,8 +79,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(demand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(demand, demand, demand, noDemand),
),
(95.0, 95.0, 95.0),
),
@@ -102,8 +104,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(demand, onlyAddDemand),
+ thermalDemands =
+ ThermalDemandWrapper(demand, onlyAddDemand, demand, noDemand),
),
(0.0, 0.0, 95.0),
),
@@ -128,12 +132,19 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
),
- thermalDemands = ThermalDemandWrapper(demand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(demand, demand, demand, noDemand),
),
(95.0, 95.0, 95.0),
),
@@ -155,12 +166,19 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
),
- thermalDemands = ThermalDemandWrapper(demand, onlyAddDemand),
+ thermalDemands =
+ ThermalDemandWrapper(demand, onlyAddDemand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -184,6 +202,7 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
@@ -191,9 +210,11 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
Kilowatts(1),
Kilowatts(1),
zeroKW,
+ zeroKW,
),
),
- thermalDemands = ThermalDemandWrapper(onlyAddDemand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(onlyAddDemand, demand, noDemand, noDemand),
),
(95.0, 95.0, 95.0),
),
@@ -215,12 +236,14 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
zeroKW,
- ThermalGridOperatingPoint(zeroKW, zeroKW, zeroKW),
+ ThermalGridOperatingPoint.zero,
),
- thermalDemands = ThermalDemandWrapper(onlyAddDemand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(onlyAddDemand, demand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -242,13 +265,23 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
+ ),
+ thermalDemands = ThermalDemandWrapper(
+ onlyAddDemand,
+ onlyAddDemand,
+ demand,
+ noDemand,
),
- thermalDemands =
- ThermalDemandWrapper(onlyAddDemand, onlyAddDemand),
),
(95.0, 0.0, 95.0),
),
@@ -272,8 +305,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(onlyAddDemand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(onlyAddDemand, demand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -295,9 +330,14 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
+ ),
+ thermalDemands = ThermalDemandWrapper(
+ onlyAddDemand,
+ onlyAddDemand,
+ demand,
+ noDemand,
),
- thermalDemands =
- ThermalDemandWrapper(onlyAddDemand, onlyAddDemand),
),
(0.0, 0.0, 95.0),
),
@@ -321,12 +361,19 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
),
- thermalDemands = ThermalDemandWrapper(noDemand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, demand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -348,12 +395,19 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
),
- thermalDemands = ThermalDemandWrapper(noDemand, onlyAddDemand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, onlyAddDemand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -377,8 +431,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(noDemand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, demand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -400,8 +456,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(noDemand, onlyAddDemand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, onlyAddDemand, demand, noDemand),
),
(0.0, 0.0, 95.0),
),
@@ -425,12 +483,19 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
),
- thermalDemands = ThermalDemandWrapper(noDemand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, demand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -452,12 +517,23 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
+ ),
+ thermalDemands = ThermalDemandWrapper(
+ noDemand,
+ onlyAddDemand,
+ noDemand,
+ noDemand,
),
- thermalDemands = ThermalDemandWrapper(noDemand, onlyAddDemand),
),
(95.0, 0.0, 95.0),
),
@@ -481,8 +557,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
zeroKWh,
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(noDemand, demand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, demand, demand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -504,8 +582,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(20),
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(noDemand, onlyAddDemand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, onlyAddDemand, demand, noDemand),
),
(0.0, 0.0, 95.0),
),
@@ -526,8 +606,14 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(500),
)
),
+ None,
+ ),
+ thermalDemands = ThermalDemandWrapper(
+ onlyAddDemand,
+ noDemand,
+ noDemand,
+ noDemand,
),
- thermalDemands = ThermalDemandWrapper(onlyAddDemand, noDemand),
),
(0.0, 0.0, 95.0),
),
@@ -548,12 +634,23 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(500),
)
),
+ None,
),
lastHpOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(
+ Kilowatts(1),
+ Kilowatts(1),
+ zeroKW,
+ zeroKW,
+ ),
+ ),
+ thermalDemands = ThermalDemandWrapper(
+ onlyAddDemand,
+ noDemand,
+ noDemand,
+ noDemand,
),
- thermalDemands = ThermalDemandWrapper(onlyAddDemand, noDemand),
),
(95.0, 0.0, 95.0),
),
@@ -575,8 +672,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(500),
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(noDemand, noDemand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, noDemand, noDemand, noDemand),
),
(0.0, 0.0, 0.0),
),
@@ -597,8 +696,10 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
KilowattHours(500),
)
),
+ None,
),
- thermalDemands = ThermalDemandWrapper(noDemand, noDemand),
+ thermalDemands =
+ ThermalDemandWrapper(noDemand, noDemand, noDemand, noDemand),
),
(0.0, 0.0, 0.0),
),
@@ -634,7 +735,5 @@ class HpPowerLimitFlexModelSpec extends UnitSpec with HpInputTestData {
}
}
}
-
}
-
}
diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala
index bfa0af857e..07bb599bde 100644
--- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala
+++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala
@@ -30,28 +30,62 @@ trait ThermalGridTestData {
protected val testGridQDotInfeed: Power = Kilowatts(15d)
protected val noThermalDemand: ThermalDemandWrapper =
ThermalDemandWrapper(
- ThermalEnergyDemand(zeroKWh, zeroKWh),
- ThermalEnergyDemand(zeroKWh, zeroKWh),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
)
protected val onlyThermalDemandOfHouse: ThermalDemandWrapper =
ThermalDemandWrapper(
ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
- ThermalEnergyDemand(zeroKWh, zeroKWh),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
)
protected val onlyThermalDemandOfHeatStorage: ThermalDemandWrapper =
ThermalDemandWrapper(
- ThermalEnergyDemand(zeroKWh, zeroKWh),
+ ThermalEnergyDemand.noDemand,
ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
)
protected val onlyPossibleDemandOfHeatStorage: ThermalDemandWrapper =
ThermalDemandWrapper(
ThermalEnergyDemand.noDemand,
ThermalEnergyDemand(zeroKWh, KilowattHours(2)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ )
+
+ protected val onlyThermalDemandOfHotWaterStorage: ThermalDemandWrapper =
+ ThermalDemandWrapper(
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
+ )
+ protected val onlyPossibleDemandOfHotWaterStorage: ThermalDemandWrapper =
+ ThermalDemandWrapper(
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand(zeroKWh, KilowattHours(2)),
)
+
+ protected val thermalDemandOfHouseAndWaterStorage: ThermalDemandWrapper =
+ ThermalDemandWrapper(
+ ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand(KilowattHours(2), KilowattHours(2)),
+ )
+
protected val thermalDemandOfHouseAndHeatStorage: ThermalDemandWrapper =
ThermalDemandWrapper(
ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
)
protected val isRunning: Boolean = true
protected val isNotRunning: Boolean = false
diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala
index 15981292bd..a405124e13 100644
--- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala
+++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala
@@ -51,7 +51,7 @@ class ThermalGridWithHouseAndStorageSpec
thermalBusInput,
Set(thermalHouseInput).asJava,
Set[ThermalStorageInput](heatStorageInput).asJava,
- Set.empty[ThermalStorageInput].asJava,
+ Set[ThermalStorageInput](domesticHotWaterStorageInput).asJava,
)
)
@@ -73,16 +73,18 @@ class ThermalGridWithHouseAndStorageSpec
thermalBusInput,
Set(thermalHouseInput).asJava,
Set[ThermalStorageInput](heatStorageInput).asJava,
- Set.empty[ThermalStorageInput].asJava,
+ Set[ThermalStorageInput](domesticHotWaterStorageInput).asJava,
)
ThermalGrid(thermalGridInput) match {
case ThermalGrid(
Some(thermalHouseGenerated),
Some(thermalHeatStorageGenerated),
+ Some(domesticHotWaterStorageGenerated),
) =>
thermalHouseGenerated shouldBe thermalHouse
thermalHeatStorageGenerated shouldBe heatStorage
+ domesticHotWaterStorageGenerated shouldBe domesticHotWaterStorage
case _ =>
fail("Generation of thermal grid from thermal input grid failed.")
}
@@ -108,9 +110,16 @@ class ThermalGridWithHouseAndStorageSpec
storedEnergyHeatStorage,
)
),
+ Some(
+ ThermalStorageState(
+ tickWaterStorage,
+ storedEnergyWaterStorage,
+ )
+ ),
) =>
houseTick shouldBe expectedHouseStartingState.tick
tickHeatStorage shouldBe expectedHeatStorageStartingState.tick
+ tickWaterStorage shouldBe expectedDomesticHotWaterStorageStartingState.tick
innerTemperature should approximate(
expectedHouseStartingState.innerTemperature
@@ -118,6 +127,10 @@ class ThermalGridWithHouseAndStorageSpec
storedEnergyHeatStorage should approximate(
expectedHeatStorageStartingState.storedEnergy
)
+
+ storedEnergyWaterStorage should approximate(
+ expectedDomesticHotWaterStorageStartingState.storedEnergy
+ )
case _ => fail("Determination of starting state failed")
}
}
@@ -130,7 +143,7 @@ class ThermalGridWithHouseAndStorageSpec
val updatedThermalGridState =
thermalGrid.determineState(
tick,
- initialHpState.thermalGridState,
+ initialGridState,
HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
)
@@ -140,11 +153,19 @@ class ThermalGridWithHouseAndStorageSpec
Some(
ThermalStorageState(heatStorageTick, heatStorageStoredEnergy)
),
+ Some(
+ ThermalStorageState(
+ waterStorageTick,
+ waterStorageStoredEnergy,
+ )
+ ),
) =>
houseTick shouldBe 10800
heatStorageTick shouldBe houseTick
+ waterStorageTick shouldBe houseTick
innerTemperature should approximate(Celsius(18.93))
heatStorageStoredEnergy shouldBe zeroKWh
+ waterStorageStoredEnergy should approximate(KilowattHours(12.18))
case _ => fail("Thermal grid state couldn't be matched.")
}
}
@@ -154,23 +175,36 @@ class ThermalGridWithHouseAndStorageSpec
"deliver the heat demand of the house (no demand) with added flexibility by storage" in {
val tick = 10800L // after three hours
+ val hoursToDetermine =
+ thermalHouse.checkIfNeedToDetermineDomesticHotWaterDemand(
+ tick,
+ defaultSimulationStart,
+ initialHpState,
+ )
+
val updatedThermalGridState =
thermalGrid.determineState(
tick,
- initialHpState.thermalGridState,
+ initialGridState,
HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
)
val thermalDemands =
- thermalGrid.determineEnergyDemand(updatedThermalGridState)
+ thermalGrid.determineEnergyDemand(
+ updatedThermalGridState,
+ hoursToDetermine,
+ )
val houseDemand = thermalDemands.houseDemand
val storageDemand = thermalDemands.heatStorageDemand
+ val waterStorageDemand = thermalDemands.domesticHotWaterStorageDemand
houseDemand.required should approximate(zeroKWh)
houseDemand.possible should approximate(KilowattHours(1.04476746))
storageDemand.required should approximate(KilowattHours(1150d))
storageDemand.possible should approximate(KilowattHours(1150d))
+ waterStorageDemand.required should approximate(KilowattHours(0d))
+ waterStorageDemand.possible should approximate(KilowattHours(0d))
}
"updatedThermalGridState" should {
@@ -198,14 +232,29 @@ class ThermalGridWithHouseAndStorageSpec
heatStorageStoredEnergy,
)
),
+ Some(
+ ThermalStorageState(
+ waterStorageTick,
+ waterStorageStoredEnergy,
+ )
+ ),
) =>
houseTick shouldBe 10800
heatStorageTick shouldBe houseTick
innerTemperature should approximate(Celsius(15.96))
heatStorageStoredEnergy shouldBe zeroKWh
+ waterStorageTick shouldBe houseTick
+ waterStorageStoredEnergy should approximate(KilowattHours(12.18))
case _ => fail("Thermal grid state couldn't be matched.")
}
+ // OperatingPoint zero for waterStorage
+ updatedThermalGridState.domesticHotWaterStorageState shouldBe Some(
+ ThermalStorageState(
+ 10800,
+ expectedDomesticHotWaterStorageStartingState.storedEnergy,
+ )
+ )
}
"exactly calculate the state of the thermalGrid with non-zero OperatingPoint" in {
@@ -220,6 +269,7 @@ class ThermalGridWithHouseAndStorageSpec
testGridQDotInfeed,
zeroKW,
testGridQDotInfeed,
+ Kilowatts(-1d),
)
val updatedThermalGridState =
@@ -233,18 +283,63 @@ class ThermalGridWithHouseAndStorageSpec
case ThermalGridState(
Some(ThermalHouseState(houseTick, _, innerTemperature)),
Some(ThermalStorageState(heatStorageTick, heatStoredEnergy)),
+ Some(ThermalStorageState(waterStorageTick, waterStoredEnergy)),
) =>
houseTick shouldBe 10800
heatStorageTick shouldBe houseTick
+ waterStorageTick shouldBe houseTick
innerTemperature should approximate(Celsius(15.9602))
heatStoredEnergy shouldBe KilowattHours(45)
+ waterStoredEnergy should approximate(KilowattHours(9.18))
case _ => fail("Thermal grid state couldn't be matched.")
}
}
}
+ "determine the hot water demand" in {
+ val tick = 0
+ val startingState =
+ ThermalGrid.startingState(thermalGrid, testGridAmbientTemperature)
+
+ val hoursToDetermine =
+ thermalHouse.checkIfNeedToDetermineDomesticHotWaterDemand(
+ tick,
+ defaultSimulationStart,
+ initialHpState,
+ )
+
+ val thermalDemands =
+ thermalGrid.determineEnergyDemand(
+ startingState,
+ hoursToDetermine,
+ )
+
+ thermalDemands.houseDemand.required should approximate(zeroKWh)
+ thermalDemands.houseDemand.possible should approximate(zeroKWh)
+ thermalDemands.heatStorageDemand.required should approximate(
+ KilowattHours(1150d)
+ )
+ thermalDemands.heatStorageDemand.possible should approximate(
+ KilowattHours(1150d)
+ )
+ thermalDemands.domesticHotWaterStorageDemand.required should approximate(
+ KilowattHours(0d)
+ )
+ thermalDemands.domesticHotWaterStorageDemand.possible should approximate(
+ KilowattHours(0d)
+ )
+ }
+
"deliver the correct house and heat storage demand" in {
val tick = 10800
+
+ val hoursToDetermine =
+ thermalHouse.checkIfNeedToDetermineDomesticHotWaterDemand(
+ tick,
+ defaultSimulationStart,
+ initialHpState,
+ )
+
val gridState = initialGridState.copy(houseState =
initialGridState.houseState.map(
_.copy(innerTemperature = Celsius(16d))
@@ -259,15 +354,21 @@ class ThermalGridWithHouseAndStorageSpec
)
val thermalDemands =
- thermalGrid.determineEnergyDemand(updatedThermalGridState)
+ thermalGrid.determineEnergyDemand(
+ updatedThermalGridState,
+ hoursToDetermine,
+ )
val houseDemand = thermalDemands.houseDemand
val heatStorageDemand = thermalDemands.heatStorageDemand
+ val waterStorageDemand = thermalDemands.domesticHotWaterStorageDemand
houseDemand.required should approximate(KilowattHours(45.59701))
houseDemand.possible should approximate(KilowattHours(45.59701))
heatStorageDemand.required should approximate(KilowattHours(1150d))
heatStorageDemand.possible should approximate(KilowattHours(1150d))
+ waterStorageDemand.required should approximate(KilowattHours(0d))
+ waterStorageDemand.possible should approximate(KilowattHours(0d))
}
}
@@ -289,7 +390,7 @@ class ThermalGridWithHouseAndStorageSpec
thermalGrid.handleConsumption(state)
reachedThreshold shouldBe Some(
- HouseTemperatureLowerBoundaryReached(166482L)
+ SimpleThermalThreshold(3600L)
)
thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint.zero
}
@@ -312,6 +413,8 @@ class ThermalGridWithHouseAndStorageSpec
thermalDemands = ThermalDemandWrapper(
ThermalEnergyDemand(KilowattHours(1), KilowattHours(1)),
ThermalEnergyDemand(KilowattHours(1), KilowattHours(1)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
),
)
@@ -323,6 +426,7 @@ class ThermalGridWithHouseAndStorageSpec
zeroKW,
heatStorage.pThermalMax,
heatStorage.pThermalMax * -1,
+ zeroKW,
)
}
@@ -341,7 +445,7 @@ class ThermalGridWithHouseAndStorageSpec
)
val lastOperatingPoint = HpOperatingPoint(
Kilowatts(1),
- ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW),
+ ThermalGridOperatingPoint(Kilowatts(1), Kilowatts(1), zeroKW, zeroKW),
)
val state = initialHpState.copy(
@@ -351,6 +455,8 @@ class ThermalGridWithHouseAndStorageSpec
thermalDemands = ThermalDemandWrapper(
ThermalEnergyDemand(zeroKWh, KilowattHours(1)),
ThermalEnergyDemand(KilowattHours(1), KilowattHours(1)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
),
)
@@ -361,6 +467,7 @@ class ThermalGridWithHouseAndStorageSpec
zeroKW,
heatStorage.pThermalMax,
heatStorage.pThermalMax * -1,
+ zeroKW,
)
reachedThreshold shouldBe Some(StorageEmpty(900))
@@ -373,20 +480,78 @@ class ThermalGridWithHouseAndStorageSpec
thermalDemands = onlyThermalDemandOfHeatStorage,
)
"hand back unaltered information if needed information is missing" in {
- val houseState = state.thermalGridState.houseState.getOrElse(
- throw new IllegalStateException(
- "Could not get state of thermal house."
+ val maybeHouseState = Some(
+ ThermalHouseState(
+ state.tick,
+ testGridAmbientTemperature,
+ Celsius(
+ thermalHouseInput.getTargetTemperature
+ .to(Units.CELSIUS)
+ .getValue
+ .doubleValue
+ ),
)
)
+ val maybeStorageState = None
+ val maybeWaterStorageState =
+ Some(ThermalStorageState(0L, KilowattHours(2d)))
+
+ val maybeThermalGridState =
+ ThermalGridState(
+ maybeHouseState,
+ maybeStorageState,
+ maybeWaterStorageState,
+ )
- val maybeHouseThreshold = thermalHouse.determineNextThreshold(
- houseState,
- zeroKW,
- )
+ val maybeThreshold = None
+
+ val hpState = state.copy(thermalGridState = maybeThermalGridState)
thermalGrid.reviseFeedInFromStorage(
- state,
- maybeHouseThreshold,
+ hpState,
+ maybeThreshold,
+ ) match {
+ case (thermalGridOperatingPoint, nextThreshold) =>
+ thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint.zero
+ nextThreshold shouldBe None
+ }
+ }
+
+ "hand back unaltered information if house temperature is above lower boundary temperature" in {
+ val maybeHouseState =
+ ThermalHouseState(
+ state.tick,
+ testGridAmbientTemperature,
+ Celsius(
+ thermalHouseInput.getTargetTemperature
+ .to(Units.CELSIUS)
+ .getValue
+ .doubleValue
+ ),
+ )
+ val maybeStorageState =
+ ThermalStorageState(
+ state.tick,
+ KilowattHours(50d),
+ )
+
+ val maybeWaterStorageState = None
+
+ val maybeThermalGridState =
+ ThermalGridState(
+ Some(maybeHouseState),
+ Some(maybeStorageState),
+ maybeWaterStorageState,
+ )
+
+ val hpState = state.copy(thermalGridState = maybeThermalGridState)
+
+ val maybeThreshold =
+ thermalHouse.determineNextThreshold(maybeHouseState, zeroKW)
+
+ thermalGrid.reviseFeedInFromStorage(
+ hpState,
+ maybeThreshold,
) match {
case (
thermalGridOperatingPoint,
@@ -394,41 +559,44 @@ class ThermalGridWithHouseAndStorageSpec
) =>
thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint.zero
nextThreshold shouldBe Some(
- HouseTemperatureLowerBoundaryReached(166482L)
+ HouseTemperatureLowerBoundaryReached(170082L)
)
}
}
"heat house from storage if house temperature is at lower boundary temperature" in {
- val houseState = ThermalHouseState(
- state.tick,
- testGridAmbientTemperature,
- Celsius(
- thermalHouseInput.getLowerTemperatureLimit
- .to(Units.CELSIUS)
- .getValue
- .doubleValue
- ),
- )
+ val maybeHouseState =
+ ThermalHouseState(
+ state.tick,
+ testGridAmbientTemperature,
+ Celsius(
+ thermalHouseInput.getLowerTemperatureLimit
+ .to(Units.CELSIUS)
+ .getValue
+ .doubleValue
+ ),
+ )
val maybeHouseThreshold =
- thermalHouse.determineNextThreshold(
- houseState,
- zeroKW,
- )
+ thermalHouse.determineNextThreshold(maybeHouseState, zeroKW)
+
+ val maybeStorageState =
+ Some(ThermalStorageState(state.tick, KilowattHours(10)))
- val heatStorageState =
- ThermalStorageState(state.tick, KilowattHours(10))
+ val maybeWaterStorageState = None
val hpState = state.copy(
thermalGridState = state.thermalGridState.copy(
- houseState = Some(houseState),
- heatStorageState = Some(heatStorageState),
+ houseState = Some(maybeHouseState),
+ heatStorageState = maybeStorageState,
+ domesticHotWaterStorageState = maybeWaterStorageState,
),
// The exact amount doesn't matter
thermalDemands = ThermalDemandWrapper(
ThermalEnergyDemand(KilowattHours(1), KilowattHours(1)),
ThermalEnergyDemand(zeroKWh, KilowattHours(1)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
),
)
@@ -444,6 +612,7 @@ class ThermalGridWithHouseAndStorageSpec
zeroKW,
heatStorage.pThermalMax,
heatStorage.getpThermalMax * -1,
+ zeroKW,
)
nextThreshold shouldBe Some(StorageEmpty(5400))
}
@@ -483,6 +652,8 @@ class ThermalGridWithHouseAndStorageSpec
thermalDemands = ThermalDemandWrapper(
ThermalEnergyDemand(KilowattHours(1), KilowattHours(1)),
ThermalEnergyDemand(zeroKWh, KilowattHours(1)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
),
)
@@ -495,6 +666,7 @@ class ThermalGridWithHouseAndStorageSpec
zeroKW,
heatStorage.pThermalMax,
heatStorage.pThermalMax * -1,
+ zeroKW,
)
threshold shouldBe Some(HouseTargetTemperatureReached(6344L))
@@ -513,6 +685,8 @@ class ThermalGridWithHouseAndStorageSpec
)
),
heatStorageState = Some(expectedHeatStorageStartingState),
+ domesticHotWaterStorageState =
+ Some(expectedDomesticHotWaterStorageStartingState),
)
val state = initialHpState.copy(
@@ -521,6 +695,8 @@ class ThermalGridWithHouseAndStorageSpec
thermalDemands = ThermalDemandWrapper(
ThermalEnergyDemand(KilowattHours(1), KilowattHours(1)),
ThermalEnergyDemand(KilowattHours(1), KilowattHours(1)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand.noDemand,
),
)
@@ -532,76 +708,156 @@ class ThermalGridWithHouseAndStorageSpec
externalQDot,
)
- reachedThreshold shouldBe Some(HouseTargetTemperatureReached(7345L))
+ reachedThreshold shouldBe Some(SimpleThermalThreshold(3600L))
thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
externalQDot,
externalQDot,
zeroKW,
+ zeroKW,
)
}
- "load the storage, if the target temperature in the house is reached" in {
- val externalQDot = testGridQDotInfeed * 1.3
-
+ "heat the house and recharge the domestic hot water storage, if the house has required heat demand and domestic hot water storage is empty" in {
val gridState = initialGridState.copy(
- houseState = Some(
- ThermalHouseState(
- -1,
- testGridAmbientTemperature,
- thermalHouse.upperBoundaryTemperature,
- )
+ houseState = initialGridState.houseState.map(
+ _.copy(innerTemperature = thermalHouse.lowerBoundaryTemperature)
),
- heatStorageState = Some(expectedHeatStorageStartingState),
+ domesticHotWaterStorageState =
+ initialGridState.domesticHotWaterStorageState.map(
+ _.copy(storedEnergy = zeroKWh)
+ ),
)
- val state = initialHpState.copy(
- thermalGridState = gridState
- )
+ val state =
+ HpState(
+ 0,
+ defaultSimulationStart,
+ gridState,
+ HpOperatingPoint.zero,
+ thermalDemandOfHouseAndWaterStorage,
+ )
- val (thermalGridOperatingPoint, reachedThreshold) =
+ val (operatingPoint, reachedThreshold) =
thermalGrid.handleFeedIn(
state,
- externalQDot,
+ testGridQDotInfeed,
)
- reachedThreshold shouldBe Some(StorageFull(212307))
- thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
- externalQDot,
+ reachedThreshold shouldBe Some(StorageFull(5846))
+ operatingPoint shouldBe ThermalGridOperatingPoint(
+ testGridQDotInfeed,
+ testGridQDotInfeed / 2,
zeroKW,
- externalQDot,
+ testGridQDotInfeed / 2,
)
}
+ }
- "load the storage, if the temperature in the house is sufficient and overheat the house with remaining qDot" in {
- val externalQDot = testGridQDotInfeed * 10
+ "load the storage, if the target temperature in the house is reached" in {
+ val externalQDot = testGridQDotInfeed * 1.3
- val gridState = initialGridState.copy(
- houseState = Some(
- ThermalHouseState(
- -1,
- testGridAmbientTemperature,
- thermalHouse.upperBoundaryTemperature,
- )
- ),
- heatStorageState = Some(expectedHeatStorageStartingState),
- )
+ val gridState = initialGridState.copy(
+ houseState = Some(
+ ThermalHouseState(
+ -1,
+ testGridAmbientTemperature,
+ thermalHouse.upperBoundaryTemperature,
+ )
+ ),
+ heatStorageState = Some(expectedHeatStorageStartingState),
+ )
- val state = initialHpState.copy(
- thermalGridState = gridState
+ val state = initialHpState.copy(
+ thermalGridState = gridState
+ )
+
+ val (thermalGridOperatingPoint, reachedThreshold) =
+ thermalGrid.handleFeedIn(
+ state,
+ externalQDot,
)
- val (thermalGridOperatingPoint, reachedThreshold) =
- thermalGrid.handleFeedIn(
- state,
- externalQDot,
+ reachedThreshold shouldBe Some(SimpleThermalThreshold(3600))
+ thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
+ externalQDot,
+ zeroKW,
+ externalQDot,
+ zeroKW,
+ )
+ }
+ "load the storage, if the temperature in the house is sufficient and overheat the house with remaining qDot" in {
+ val externalQDot = testGridQDotInfeed * 10
+
+ val gridState = initialGridState.copy(
+ houseState = Some(
+ ThermalHouseState(
+ -1,
+ testGridAmbientTemperature,
+ thermalHouse.upperBoundaryTemperature,
)
+ ),
+ heatStorageState = Some(expectedHeatStorageStartingState),
+ )
- reachedThreshold shouldBe Some(StorageFull(207000))
- thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
+ val state = initialHpState.copy(
+ thermalGridState = gridState
+ )
+
+ val (thermalGridOperatingPoint, reachedThreshold) =
+ thermalGrid.handleFeedIn(
+ state,
externalQDot,
- Kilowatts(130d),
- heatStorage.pThermalMax,
)
- }
+
+ reachedThreshold shouldBe Some(SimpleThermalThreshold(3600))
+ thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
+ externalQDot,
+ Kilowatts(130d),
+ heatStorage.pThermalMax,
+ zeroKW,
+ )
+ }
+
+ "don't load the heat storage, use qDot directly to cover hot water demand before recharge domestic hot water storage if the upper temperature in the house is reached and the domestic hot water storage is empty" in {
+ val firstThermalDemands = ThermalDemandWrapper(
+ ThermalEnergyDemand(zeroKWh, zeroKWh),
+ ThermalEnergyDemand(KilowattHours(1150), KilowattHours(1150)),
+ ThermalEnergyDemand.noDemand,
+ ThermalEnergyDemand(KilowattHours(12.18), KilowattHours(12.18)),
+ )
+
+ val gridState = initialGridState.copy(
+ houseState = initialGridState.houseState.map(
+ _.copy(innerTemperature = thermalHouse.upperBoundaryTemperature)
+ ),
+ domesticHotWaterStorageState =
+ initialGridState.domesticHotWaterStorageState.map(
+ _.copy(storedEnergy = zeroKWh)
+ ),
+ )
+ val externalQDot = testGridQDotInfeed
+
+ val state =
+ HpState(
+ 0,
+ defaultSimulationStart,
+ gridState,
+ HpOperatingPoint.zero,
+ firstThermalDemands,
+ )
+
+ val (firstOperatingPoint, firstReachedThreshold) =
+ thermalGrid.handleFeedIn(
+ state,
+ externalQDot,
+ )
+
+ firstOperatingPoint shouldBe ThermalGridOperatingPoint(
+ testGridQDotInfeed,
+ Kilowatts(4), // remaining qDot into house
+ zeroKW,
+ domesticHotWaterStorage.pThermalMax,
+ )
+ firstReachedThreshold shouldBe Some(StorageFull(3986))
}
}
diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala
index 8649fa0829..b5e26fb8d9 100644
--- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala
+++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala
@@ -12,12 +12,13 @@ import edu.ie3.simona.model.participant.hp.HpModel.{
HpState,
ThermalGridOperatingPoint,
}
-import edu.ie3.simona.model.thermal.ThermalGrid.ThermalGridState
-import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseState
-import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{
- HouseTargetTemperatureReached,
- HouseTemperatureLowerBoundaryReached,
+import edu.ie3.simona.model.thermal.ThermalGrid.{
+ ThermalEnergyDemand,
+ ThermalGridState,
}
+import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseState
+import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState
+import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageThreshold.StorageFull
import edu.ie3.simona.test.common.{DefaultTestData, UnitSpec}
import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh}
import squants.energy.*
@@ -43,25 +44,30 @@ class ThermalGridWithHouseOnlySpec
thermalBusInput,
Set(thermalHouseInput).asJava,
Set.empty[ThermalStorageInput].asJava,
- Set.empty[ThermalStorageInput].asJava,
+ Set[ThermalStorageInput](domesticHotWaterStorageInput).asJava,
)
ThermalGrid(thermalGridInput) match {
- case ThermalGrid(Some(thermalHouseGenerated), None) =>
+ case ThermalGrid(
+ Some(thermalHouseGenerated),
+ None,
+ Some(domesticHotWaterStorageGenerated),
+ ) =>
thermalHouseGenerated shouldBe thermalHouse
+ domesticHotWaterStorageGenerated shouldBe domesticHotWaterStorage
case _ =>
fail("Generation of thermal grid from thermal input grid failed.")
}
}
}
- "Testing a thermal grid with only a house" when {
+ "Testing a thermal grid with only a house and a domestic hot water storage" when {
val thermalGrid: ThermalGrid = ThermalGrid(
new edu.ie3.datamodel.models.input.container.ThermalGrid(
thermalBusInput,
Set(thermalHouseInput).asJava,
Set.empty[ThermalStorageInput].asJava,
- Set.empty[ThermalStorageInput].asJava,
+ Set[ThermalStorageInput](domesticHotWaterStorageInput).asJava,
)
)
val initialGridState: ThermalGridState =
@@ -81,17 +87,27 @@ class ThermalGridWithHouseOnlySpec
case ThermalGridState(
Some(
ThermalHouseState(
- tick,
+ houseTick,
_,
innerTemperature,
)
),
None,
+ Some(
+ ThermalStorageState(
+ waterDomesticHotStorageTick,
+ storedEnergyDomesticHotWaterStorage,
+ )
+ ),
) =>
- tick shouldBe expectedHouseStartingState.tick
+ houseTick shouldBe expectedHouseStartingState.tick
+ waterDomesticHotStorageTick shouldBe expectedDomesticHotWaterStorageStartingState.tick
innerTemperature should approximate(
expectedHouseStartingState.innerTemperature
)
+ storedEnergyDomesticHotWaterStorage should approximate(
+ expectedDomesticHotWaterStorageStartingState.storedEnergy
+ )
case _ => fail("Determination of starting state failed")
}
@@ -102,19 +118,29 @@ class ThermalGridWithHouseOnlySpec
"exactly calculate the state of the thermalGrid" in {
val tick = 10800L // after three hours
- val updatedThermalGridState = thermalGrid.determineState(
- tick,
- initialHpState.thermalGridState,
- HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
- )
+ val updatedThermalGridState =
+ thermalGrid.determineState(
+ tick,
+ initialGridState,
+ HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
+ )
updatedThermalGridState match {
case ThermalGridState(
Some(ThermalHouseState(houseTick, _, innerTemperature)),
None,
+ Some(
+ ThermalStorageState(
+ waterStorageTick,
+ waterStorageStoredEnergy,
+ )
+ ),
) =>
- houseTick shouldBe 10800L
+ houseTick shouldBe 10800
+ waterStorageTick shouldBe 10800
innerTemperature should approximate(Celsius(18.93))
+ waterStorageStoredEnergy should approximate(KilowattHours(12.18))
+
case _ => fail("Thermal grid state couldn't be matched.")
}
}
@@ -124,48 +150,102 @@ class ThermalGridWithHouseOnlySpec
"exactly be the demand of the house" in {
val tick = 10800L // after three hours
- val updatedThermalGridState = thermalGrid.determineState(
- tick,
- initialHpState.thermalGridState,
- HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
- )
+ val hoursToDetermine =
+ thermalHouse.checkIfNeedToDetermineDomesticHotWaterDemand(
+ tick,
+ defaultSimulationStart,
+ initialHpState,
+ )
+
+ val updatedThermalGridState =
+ thermalGrid.determineState(
+ tick,
+ initialGridState,
+ HpOperatingPoint.zero,
+ )
val thermalDemands =
- thermalGrid.determineEnergyDemand(updatedThermalGridState)
+ thermalGrid.determineEnergyDemand(
+ updatedThermalGridState,
+ hoursToDetermine,
+ )
val houseDemand = thermalDemands.houseDemand
val storageDemand = thermalDemands.heatStorageDemand
+ val domesticHotWaterDemand =
+ thermalDemands.domesticHotWaterStorageDemand
houseDemand.required should approximate(zeroKWh)
houseDemand.possible should approximate(KilowattHours(1.04476746))
storageDemand.required should approximate(zeroKWh)
storageDemand.possible should approximate(zeroKWh)
+ domesticHotWaterDemand.required should approximate(zeroKWh)
+ domesticHotWaterDemand.possible should approximate(zeroKWh)
+ // houseState and heatStorageState are already tested sufficiently
+ updatedThermalGridState.domesticHotWaterStorageState shouldBe Some(
+ ThermalStorageState(
+ 10800,
+ // when simulating from tick 0 - 10800 the hourly demand
+ // for hot water would normally be taken from domestic
+ // hot water storage, resulting in a lower storedEnergy here
+ expectedDomesticHotWaterStorageStartingState.storedEnergy,
+ )
+ )
+
+ }
+ }
+
+ "determining the energy demand for domestic warm water supply" should {
+ "exactly be the thermal demand for domestic water supply of the house" in {
+ val tick = 86399 // heat demand for one day
+ val expectedEnergyDemandWater =
+ ThermalEnergyDemand(
+ KilowattHours(3.7541369),
+ KilowattHours(3.7541369),
+ )
+
+ val hoursToDetermine =
+ thermalHouse.checkIfNeedToDetermineDomesticHotWaterDemand(
+ tick,
+ defaultSimulationStart,
+ initialHpState,
+ )
+
+ val energyDemandDomesticHotWater =
+ thermalHouse.energyDemandDomesticHotWater(hoursToDetermine)
+
+ energyDemandDomesticHotWater.required should approximate(
+ expectedEnergyDemandWater.required
+ )
+ energyDemandDomesticHotWater.possible should approximate(
+ expectedEnergyDemandWater.possible
+ )
}
}
"handling thermal energy consumption from grid" should {
+
"deliver the house state by just letting it cool down, if just no feed in is given" in {
val (thermalGridOperatingPoint, reachedThreshold) =
thermalGrid.handleConsumption(initialHpState)
reachedThreshold shouldBe Some(
- HouseTemperatureLowerBoundaryReached(166482L)
+ SimpleThermalThreshold(3600)
)
thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint.zero
}
}
"handling thermal feed in into the grid" should {
- "solely heat up the house" in {
- val gridState = ThermalGridState(
- Some(
- ThermalHouseState(
- -1,
- testGridAmbientTemperature,
- Celsius(17),
- )
+ "heat up the house and domestic hot water storage parallel" in {
+ val gridState = initialGridState.copy(
+ houseState = initialGridState.houseState.map(
+ _.copy(innerTemperature = thermalHouse.lowerBoundaryTemperature)
),
- None,
+ domesticHotWaterStorageState =
+ initialGridState.domesticHotWaterStorageState.map(
+ _.copy(storedEnergy = zeroKWh)
+ ),
)
val state = HpState(
@@ -173,7 +253,7 @@ class ThermalGridWithHouseOnlySpec
defaultSimulationStart,
gridState,
HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
- onlyThermalDemandOfHouse,
+ thermalDemandOfHouseAndWaterStorage,
)
val (thermalGridOperatingPoint, reachedThreshold) =
@@ -182,11 +262,47 @@ class ThermalGridWithHouseOnlySpec
testGridQDotInfeed,
)
- reachedThreshold shouldBe Some(HouseTargetTemperatureReached(7345L))
+ reachedThreshold shouldBe Some(StorageFull(5846L))
thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
testGridQDotInfeed,
- testGridQDotInfeed,
+ testGridQDotInfeed / 2,
+ zeroKW,
+ testGridQDotInfeed / 2,
+ )
+ }
+
+ "heat up the house and domestic hot water storage parallel, but split qDot would exceed pThermalMax of storage" in {
+ val qDotInfeed = testGridQDotInfeed * 2
+ val gridState = initialGridState.copy(
+ houseState = initialGridState.houseState.map(
+ _.copy(innerTemperature = Celsius(10))
+ ),
+ domesticHotWaterStorageState =
+ initialGridState.domesticHotWaterStorageState.map(
+ _.copy(storedEnergy = zeroKWh)
+ ),
+ )
+
+ val state = HpState(
+ 0,
+ defaultSimulationStart,
+ gridState,
+ HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
+ thermalDemandOfHouseAndWaterStorage,
+ )
+
+ val (thermalGridOperatingPoint, reachedThreshold) =
+ thermalGrid.handleFeedIn(
+ state,
+ qDotInfeed,
+ )
+
+ reachedThreshold shouldBe Some(StorageFull(3986L))
+ thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
+ qDotInfeed,
+ Kilowatts(19d),
zeroKW,
+ domesticHotWaterStorage.pThermalMax,
)
}
}
@@ -198,10 +314,16 @@ class ThermalGridWithHouseOnlySpec
ThermalHouseState(
-1,
testGridAmbientTemperature,
- Celsius(17),
+ Celsius(18),
)
),
None,
+ Some(
+ ThermalStorageState(
+ -1,
+ KilowattHours(42),
+ )
+ ),
)
val initState = initialHpState.copy(
thermalGridState = gridState,
@@ -214,13 +336,14 @@ class ThermalGridWithHouseOnlySpec
) match {
case (
thermalGridOperatingPoint,
- Some(HouseTargetTemperatureReached(thresholdTick)),
+ Some(SimpleThermalThreshold(thresholdTick)),
) =>
- thresholdTick shouldBe 7345L
+ thresholdTick shouldBe 3600
thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint(
testGridQDotInfeed,
testGridQDotInfeed,
zeroKW,
+ zeroKW,
)
case _ => fail("Thermal grid state updated failed")
}
@@ -230,9 +353,9 @@ class ThermalGridWithHouseOnlySpec
thermalGrid.handleConsumption(initialHpState) match {
case (
thermalGridOperatingPoint,
- Some(HouseTemperatureLowerBoundaryReached(thresholdTick)),
+ Some(SimpleThermalThreshold(thresholdTick)),
) =>
- thresholdTick shouldBe 166482L
+ thresholdTick shouldBe 3600L
thermalGridOperatingPoint shouldBe ThermalGridOperatingPoint.zero
case _ => fail("Thermal grid state updated failed")
}
diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala
index 40ff8d4f84..1e18931208 100644
--- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala
+++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala
@@ -45,7 +45,7 @@ class ThermalGridWithStorageOnlySpec
)
ThermalGrid(thermalGridInput) match {
- case ThermalGrid(None, Some(thermalStorageGenerated)) =>
+ case ThermalGrid(None, Some(thermalStorageGenerated), None) =>
thermalStorageGenerated shouldBe heatStorage
case _ =>
fail("Generation of thermal grid from thermal input grid failed.")
@@ -83,6 +83,7 @@ class ThermalGridWithStorageOnlySpec
storedEnergy,
)
),
+ None,
) =>
tick shouldBe expectedHeatStorageStartingState.tick
storedEnergy should approximate(
@@ -98,14 +99,18 @@ class ThermalGridWithStorageOnlySpec
"deliver the capabilities of the storage" in {
val tick = 10800L // after three hours
- val updatedThermalGridState = thermalGrid.determineState(
- tick,
- initialHpState.thermalGridState,
- HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
- )
+ val updatedThermalGridState =
+ thermalGrid.determineState(
+ tick,
+ initialGridState,
+ HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
+ )
val thermalDemands =
- thermalGrid.determineEnergyDemand(updatedThermalGridState)
+ thermalGrid.determineEnergyDemand(
+ updatedThermalGridState,
+ None,
+ )
val houseDemand = thermalDemands.houseDemand
val storageDemand = thermalDemands.heatStorageDemand
@@ -124,14 +129,27 @@ class ThermalGridWithStorageOnlySpec
}
"deliver the capabilities of a half full storage" in {
+ val tick = 0
val initialLoading = KilowattHours(575d)
val gridState = initialGridState.copy(heatStorageState =
initialGridState.heatStorageState.map(storageState =>
- storageState.copy(tick = 10800, storedEnergy = initialLoading)
+ storageState.copy(storedEnergy = initialLoading)
)
)
- val thermalDemands = thermalGrid.determineEnergyDemand(gridState)
+ val updatedThermalGridState =
+ thermalGrid.determineState(
+ tick,
+ gridState,
+ HpOperatingPoint(zeroKW, ThermalGridOperatingPoint.zero),
+ )
+
+ val thermalDemands =
+ thermalGrid.determineEnergyDemand(
+ updatedThermalGridState,
+ None,
+ )
+
val houseDemand = thermalDemands.houseDemand
val storageDemand = thermalDemands.heatStorageDemand
@@ -139,6 +157,10 @@ class ThermalGridWithStorageOnlySpec
houseDemand.possible should approximate(zeroKWh)
storageDemand.required should approximate(zeroKWh)
storageDemand.possible should approximate(KilowattHours(575d))
+ updatedThermalGridState.houseState shouldBe None
+ updatedThermalGridState.heatStorageState shouldBe Some(
+ ThermalStorageState(0, KilowattHours(575d))
+ )
}
}
@@ -155,6 +177,7 @@ class ThermalGridWithStorageOnlySpec
testGridQDotInfeed,
zeroKW,
testGridQDotInfeed,
+ zeroKW,
)
}
}
@@ -171,6 +194,7 @@ class ThermalGridWithStorageOnlySpec
testGridQDotInfeed,
zeroKW,
testGridQDotInfeed,
+ zeroKW,
)
nextThreshold shouldBe Some(StorageFull(276000))
}
diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala
index daa49b711b..db65236005 100644
--- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala
+++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala
@@ -6,9 +6,9 @@
package edu.ie3.simona.model.thermal
-import edu.ie3.datamodel.models.{OperationTime, StandardUnits}
import edu.ie3.datamodel.models.input.OperatorInput
import edu.ie3.datamodel.models.input.thermal.ThermalHouseInput
+import edu.ie3.datamodel.models.{OperationTime, StandardUnits}
import edu.ie3.simona.model.participant.hp.HpModel.{HpOperatingPoint, HpState}
import edu.ie3.simona.model.thermal.ThermalGrid.ThermalGridState
import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{
@@ -19,9 +19,11 @@ import edu.ie3.simona.model.thermal.ThermalHouse.{
ThermalHouseState,
startingState,
}
+import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState
import edu.ie3.simona.test.common.input.HpInputTestData
import edu.ie3.simona.test.common.{DefaultTestData, UnitSpec}
import edu.ie3.simona.util.TickUtil.TickLong
+import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKWh
import edu.ie3.util.scala.quantities.WattsPerKelvin
import org.scalatest.prop.{TableFor2, TableFor3, TableFor4, TableFor7}
import squants.energy.*
@@ -523,6 +525,7 @@ class ThermalHouseSpec
)
),
None,
+ Some(ThermalStorageState(lastTick, zeroKWh)),
)
val state = HpState(
diff --git a/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala
index e02487675f..2321a206ae 100644
--- a/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala
+++ b/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala
@@ -129,7 +129,7 @@ trait EmInputTestData
thermalBusInput,
Seq(adaptedThermalHouse).asJava,
Seq.empty[ThermalStorageInput].asJava,
- Seq.empty[ThermalStorageInput].asJava,
+ Seq[ThermalStorageInput](defaultDomesticHotWaterStorageInput).asJava,
)
protected val adaptedWithHeatContainer =
diff --git a/src/test/scala/edu/ie3/simona/test/common/input/HpInputTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/HpInputTestData.scala
index deee89a287..fe66bb9d8d 100644
--- a/src/test/scala/edu/ie3/simona/test/common/input/HpInputTestData.scala
+++ b/src/test/scala/edu/ie3/simona/test/common/input/HpInputTestData.scala
@@ -17,7 +17,6 @@ import edu.ie3.datamodel.models.input.thermal.{
}
import edu.ie3.datamodel.models.input.{OperatorInput, container}
import edu.ie3.datamodel.models.{OperationTime, StandardUnits}
-import edu.ie3.simona.model.InputModelContainer.WithHeatInputContainer
import edu.ie3.simona.model.thermal.*
import edu.ie3.simona.model.thermal.ThermalGrid.ThermalGridState
import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseState
@@ -87,7 +86,9 @@ trait HpInputTestData extends NodeInputTestData with ThermalGridTestData {
thermalBusInput,
Seq(thermalHouseInput(18, 22)).asJava,
Seq.empty[ThermalStorageInput].asJava,
- Seq.empty[ThermalStorageInput].asJava,
+ Seq(
+ defaultDomesticHotWaterStorageInput.asInstanceOf[ThermalStorageInput]
+ ).asJava,
)
protected val typicalThermalHouse = new ThermalHouseInput(
@@ -119,7 +120,7 @@ trait HpInputTestData extends NodeInputTestData with ThermalGridTestData {
thermalBusInput,
Seq(typicalThermalHouse).asJava,
Set[ThermalStorageInput](typicalHeatStorage).asJava,
- Set.empty[ThermalStorageInput].asJava,
+ Seq[ThermalStorageInput](defaultDomesticHotWaterStorageInput).asJava,
)
protected val typicalHpTypeInput = new HpTypeInput(
@@ -144,16 +145,15 @@ trait HpInputTestData extends NodeInputTestData with ThermalGridTestData {
typicalHpTypeInput,
)
- protected val typicalHpInputContainer =
- WithHeatInputContainer(typicalHpInputModel, typicalThermalGrid)
-
protected def thermalGrid(
thermalHouse: ThermalHouse,
thermalStorage: Option[CylindricalThermalStorage] = None,
+ domesticWaterStorage: Option[DomesticHotWaterStorage] = None,
): ThermalGrid =
ThermalGrid(
Some(thermalHouse),
thermalStorage,
+ domesticWaterStorage,
)
protected def thermalHouse(
@@ -218,5 +218,6 @@ trait HpInputTestData extends NodeInputTestData with ThermalGridTestData {
)
),
None,
+ None,
)
}