diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f489acbf..5e56ad29ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,8 +133,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed ThermalStorageResults having multiple entries [#924](https://github.com/ie3-institute/simona/issues/924) - Fix filter for thermal result checking for lastTick not for currentTick [#1008](https://github.com/ie3-institute/simona/issues/1008) - Fixed `CHANGELOG` entry for issue ([#103](https://github.com/ie3-institute/simona/issues/103)) [#941](https://github.com/ie3-institute/simona/issues/941) -- Fixed Hp results leading to overheating house and other effects [#827](https://github.com/ie3-institute/simona/issues/827) -- Fixed thermal storage getting recharged when empty [#827](https://github.com/ie3-institute/simona/issues/827) +- Refactoring of `ThermalGrid.handleInfeed` to fix thermal storage recharge correctly when empty [#930](https://github.com/ie3-institute/simona/issues/930) ## [3.0.0] - 2023-08-07 diff --git a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala index f884230853..097f5ae200 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala @@ -12,6 +12,7 @@ import edu.ie3.simona.model.SystemComponent import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState} import edu.ie3.simona.model.participant.control.QControl import edu.ie3.simona.model.thermal.ThermalGrid.{ + ThermalDemandIndicator, ThermalEnergyDemand, ThermalGridState, } @@ -150,7 +151,7 @@ final case class HpModel( ) // Determining the operation point and limitations at this tick - val (turnOn, canOperate, canBeOutOfOperation) = + val (turnOn, canOperate, canBeOutOfOperation, demandIndicator) = operatesInNextState( lastHpState, currentThermalGridState, @@ -161,7 +162,7 @@ final case class HpModel( // Updating the HpState val updatedState = - calcState(lastHpState, relevantData, turnOn) + calcState(lastHpState, relevantData, turnOn, demandIndicator) (canOperate, canBeOutOfOperation, updatedState) } @@ -183,7 +184,8 @@ final case class HpModel( * ThermalEnergyDemand of the thermal storage * @return * boolean defining if heat pump runs in next time step, if it can be in - * operation and out of operation plus the demand of house and storage + * operation and can be out of operation plus the + * [[ThermalDemandIndicator]] of the thermal units */ private def operatesInNextState( lastState: HpState, @@ -191,47 +193,10 @@ final case class HpModel( relevantData: HpRelevantData, demandHouse: ThermalEnergyDemand, demandThermalStorage: ThermalEnergyDemand, - ): (Boolean, Boolean, Boolean, Boolean, Boolean) = { - - //FIXME - /* - val (demandHouse, demandThermalStorage, updatedState) = - thermalGrid.energyDemandAndUpdatedState( - relevantData.currentTick, - state.ambientTemperature.getOrElse(relevantData.ambientTemperature), - relevantData.ambientTemperature, - state.thermalGridState, - ) - - val ( - houseDemand, - heatStorageDemand, - noThermalStorageOrThermalStorageIsEmpty, - ) = determineDemandBooleans( - state, - updatedState, - demandHouse, - demandThermalStorage, - ) - - val turnHpOn: Boolean = - houseDemand || heatStorageDemand - - val canOperate = - demandHouse.hasRequiredDemand || demandHouse.hasAdditionalDemand || - demandThermalStorage.hasRequiredDemand || demandThermalStorage.hasAdditionalDemand - val canBeOutOfOperation = - !(demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) - - (turnHpOn, canOperate, canBeOutOfOperation, houseDemand, heatStorageDemand) - } - */ - - /* + ): (Boolean, Boolean, Boolean, ThermalDemandIndicator) = { val ( - houseHasDemand, - heatStorageHasDemand, + demandIndicator, noThermalStorageOrThermalStorageIsEmpty, ) = determineDemandBooleans( lastState, @@ -240,8 +205,8 @@ final case class HpModel( demandThermalStorage, ) - val turnHpOn: Boolean = - houseHasDemand || heatStorageHasDemand + val turnHpOn = + demandIndicator.houseDemand || demandIndicator.heatStorageDemand val canOperate = demandHouse.hasRequiredDemand || demandHouse.hasAdditionalDemand || @@ -249,9 +214,12 @@ final case class HpModel( val canBeOutOfOperation = !(demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) - (turnHpOn, canOperate, canBeOutOfOperation) - */ - + ( + turnHpOn, + canOperate, + canBeOutOfOperation, + demandIndicator, + ) } /** This method will return booleans whether there is a heat demand of house @@ -273,11 +241,11 @@ final case class HpModel( */ private def determineDemandBooleans( - lastHpState: HpState, - updatedGridState: ThermalGridState, - demandHouse: ThermalEnergyDemand, - demandThermalStorage: ThermalEnergyDemand, - ): (Boolean, Boolean, Boolean) = { + lastHpState: HpState, + updatedGridState: ThermalGridState, + demandHouse: ThermalEnergyDemand, + demandThermalStorage: ThermalEnergyDemand, + ): (ThermalDemandIndicator, Boolean) = { implicit val tolerance: Energy = KilowattHours(1e-3) val noThermalStorageOrThermalStorageIsEmpty: Boolean = updatedGridState.storageState.isEmpty || updatedGridState.storageState @@ -289,7 +257,9 @@ final case class HpModel( (demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || (lastHpState.isRunning && demandHouse.hasAdditionalDemand) val heatStorageDemand = demandThermalStorage.hasRequiredDemand || (lastHpState.isRunning && demandThermalStorage.hasAdditionalDemand) - (houseDemand, heatStorageDemand, noThermalStorageOrThermalStorageIsEmpty) + + val demandIndicator = ThermalDemandIndicator(houseDemand, heatStorageDemand) + (demandIndicator, noThermalStorageOrThermalStorageIsEmpty) } /** Calculate state depending on whether heat pump is needed or not. Also @@ -302,10 +272,9 @@ final case class HpModel( * data of heat pump including state of the heat pump * @param isRunning * determines whether the heat pump is running or not - * @param houseDemand - * determines if the thermal house has heat demand - * @param storageDemand - * determines if the thermal storage has heat demand + * @param demandIndicator + * determines if the thermal units (house, storage) having some heat demand + * or not * @return * next [[HpState]] */ @@ -313,8 +282,7 @@ final case class HpModel( lastState: HpState, relevantData: HpRelevantData, isRunning: Boolean, - houseDemand: Boolean, - storageDemand: Boolean, + demandIndicator: ThermalDemandIndicator, ): HpState = { val lastStateStorageQDot = lastState.thermalGridState.storageState .map(_.qDot) @@ -336,8 +304,7 @@ final case class HpModel( relevantData.ambientTemperature, isRunning, newThermalPower, - houseDemand, - storageDemand, + demandIndicator, ) HpState( @@ -400,18 +367,9 @@ final case class HpModel( lastState: HpState, setPower: Power, ): (HpState, FlexChangeIndicator) = { - /* If the setpoint value is above 50 % of the electrical power, turn on the heat pump otherwise turn it off */ + /* If the set point value is above 50 % of the electrical power, turn on the heat pump otherwise turn it off */ val turnOn = setPower > (sRated * cosPhiRated * 0.5) - // FIXME - /* val updatedHpState = calcState( - lastState, - data, - turnOn, - ) - - */ - val ( thermalEnergyDemandHouse, thermalEnergyDemandStorage, @@ -425,9 +383,8 @@ final case class HpModel( ) val ( - houseDemand, - heatStorageDemand, - noThermalStorageOrThermalStorageIsEmpty, + demandIndicator, + _, ) = determineDemandBooleans( lastState, updatedThermalGridState, @@ -435,14 +392,12 @@ final case class HpModel( thermalEnergyDemandStorage, ) - val updatedHpState: HpState = - calcState( - lastState, - data, - turnOn, - houseDemand, - heatStorageDemand, - ) + val updatedHpState = calcState( + lastState, + data, + turnOn, + demandIndicator, + ) ( updatedHpState, 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 7ee49f7322..956af27cd4 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -16,6 +16,7 @@ import edu.ie3.datamodel.models.result.thermal.{ import edu.ie3.simona.exceptions.InvalidParameterException import edu.ie3.simona.exceptions.agent.InconsistentStateException import edu.ie3.simona.model.thermal.ThermalGrid.{ + ThermalDemandIndicator, ThermalEnergyDemand, ThermalGridState, } @@ -158,13 +159,12 @@ final case class ThermalGrid( * @param ambientTemperature * Current ambient temperature * @param isRunning - * Determines whether the heat pump is running or not + * determines whether the heat pump is running or not * @param qDot * Thermal energy balance - * @param houseDemand - * Determines if the thermal house has heat demand - * @param storageDemand - * Determines if the thermal storage has heat demand + * @param demandIndicator + * determines if the thermal units (house, storage) having some heat demand + * or not * @return * The updated state of the grid */ @@ -175,8 +175,7 @@ final case class ThermalGrid( ambientTemperature: Temperature, isRunning: Boolean, qDot: Power, - houseDemand: Boolean, - storageDemand: Boolean, + demandIndicator: ThermalDemandIndicator, ): (ThermalGridState, Option[ThermalThreshold]) = if (qDot > zeroKW) handleInfeed( tick, @@ -185,8 +184,7 @@ final case class ThermalGrid( state, isRunning, qDot, - houseDemand, - storageDemand, + demandIndicator, ) else handleConsumption( @@ -209,13 +207,12 @@ final case class ThermalGrid( * @param state * Current state of the houses * @param isRunning - * Determines whether the heat pump is running or not + * determines whether the heat pump is running or not * @param qDot * Infeed to the grid - * @param houseDemand - * Determines if the thermal house has heat demand - * @param storageDemand - * Determines if the thermal storage has heat demand + * @param demandIndicator + * determines if the thermal units (house, storage) having some heat demand + * or not * @return * Updated thermal grid state */ @@ -226,8 +223,7 @@ final case class ThermalGrid( state: ThermalGridState, isRunning: Boolean, qDot: Power, - houseDemand: Boolean, - heatStorageDemand: Boolean, + demandIndicator: ThermalDemandIndicator, ): (ThermalGridState, Option[ThermalThreshold]) = { // TODO: We would need to issue a storage result model here... @@ -245,7 +241,7 @@ final case class ThermalGrid( } if ( - (qDotHouseLastState > zeroKW && !(qDotStorageLastState < zeroKW)) | (qDotStorageLastState > zeroKW & heatStorageDemand) + (qDotHouseLastState > zeroKW && (qDotStorageLastState >= zeroKW)) | (qDotStorageLastState > zeroKW & demandIndicator.heatStorageDemand) ) { val (updatedHouseState, thermalHouseThreshold, remainingQDotHouse) = handleInfeedHouse( @@ -308,7 +304,7 @@ final case class ThermalGrid( } } else { - (houseDemand, heatStorageDemand) match { + (demandIndicator.houseDemand, demandIndicator.heatStorageDemand) match { case (true, _) => // house first then heatStorage after heating House @@ -354,14 +350,14 @@ final case class ThermalGrid( * @param tick * Current tick * @param lastAmbientTemperature - * Ambient temperature valid up until (not including) the current tick + * Ambient temperature until this tick * @param ambientTemperature - * Current ambient temperature + * actual ambient temperature * @param state * Current state of the thermal grid * @param qDotHouse * Infeed to the house - * @param qDotStorage + * @param qDotHeatStorage * Infeed to the heat storage * @return * Updated thermal grid state and the next threshold if there is one @@ -407,9 +403,9 @@ final case class ThermalGrid( * @param tick * Current tick * @param lastAmbientTemperature - * Ambient temperature valid up until (not including) the current tick + * Ambient temperature until this tick * @param ambientTemperature - * Current ambient temperature + * actual ambient temperature * @param state * Current state of the houses * @param qDot @@ -460,8 +456,6 @@ final case class ThermalGrid( * grid (negative qDot). * @param tick * Current tick - * @param ambientTemperature - * Ambient temperature * @param state * Current state of the houses * @param qDot @@ -486,6 +480,16 @@ final case class ThermalGrid( } } + /** Determines the most recent threshold of two given input thresholds + * + * @param maybeHouseThreshold + * Option of a possible next threshold of the thermal house + * @param maybeStorageThreshold + * Option of a possible next threshold of the thermal storage + * @return + * The next threshold + */ + private def determineMostRecentThreshold( maybeHouseThreshold: Option[ThermalThreshold], maybeStorageThreshold: Option[ThermalThreshold], @@ -735,6 +739,19 @@ object ThermalGrid { thermalGrid.storage.map(_.startingState), ) + /** Wraps booleans indicating the demand of thermal units (thermal house, + * thermal storage). + * + * @param houseDemand + * Boolean indicating the demand of the thermal house + * @param heatStorageDemand + * Boolean indicating the demand of the thermal heat storage + */ + final case class ThermalDemandIndicator private ( + houseDemand: Boolean, + heatStorageDemand: Boolean, + ) + /** Defines the thermal energy demand of a thermal grid. It comprises the * absolutely required energy demand to reach the target state as well as an * energy, that can be handled. The possible energy always has to be greater 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 0a016d4566..21f27b1892 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala @@ -9,6 +9,7 @@ package edu.ie3.simona.model.thermal import edu.ie3.datamodel.models.OperationTime import edu.ie3.datamodel.models.input.OperatorInput import edu.ie3.datamodel.models.input.thermal.ThermalBusInput +import edu.ie3.simona.model.thermal.ThermalGrid.ThermalDemandIndicator import squants.energy.{Kilowatts, Power} import squants.thermal.{Celsius, Temperature} @@ -25,8 +26,12 @@ trait ThermalGridTestData { protected val testGridQDotInfeed: Power = Kilowatts(15d) protected val testGridQDotConsumption: Power = Kilowatts(-42d) protected val testGridQDotConsumptionHigh: Power = Kilowatts(-200d) - protected val noThermalDemand: Boolean = false - protected val thermalDemand: Boolean = true + protected val noThermalDemand: ThermalDemandIndicator = + ThermalDemandIndicator(false, false) + protected val onlyThermalDemandOfHouse: ThermalDemandIndicator = + ThermalDemandIndicator(true, false) + protected val onlyThermalDemandOfHeatStorage: ThermalDemandIndicator = + ThermalDemandIndicator(false, true) 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 b2b732ae0e..ce58b6a52a 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala @@ -498,8 +498,7 @@ class ThermalGridWithHouseAndStorageSpec initialGridState, isNotRunning, externalQDot, - thermalDemand, - noThermalDemand, + onlyThermalDemandOfHouse, ) updatedGridState match { @@ -547,8 +546,7 @@ class ThermalGridWithHouseAndStorageSpec gridState, isNotRunning, externalQDot, - noThermalDemand, - thermalDemand, + onlyThermalDemandOfHeatStorage, ) updatedGridState match { 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 4ecbfc819b..39a614e92c 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala @@ -181,8 +181,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { gridState, isNotRunning, testGridQDotInfeed, - thermalDemand, - noThermalDemand, + onlyThermalDemandOfHouse, ) updatedGridState match { @@ -210,8 +209,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { testGridAmbientTemperature, isRunning, testGridQDotInfeed, - thermalDemand, - noThermalDemand, + onlyThermalDemandOfHouse, ) match { case ( ThermalGridState( @@ -236,8 +234,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { testGridAmbientTemperature, isNotRunning, testGridQDotConsumption, - thermalDemand, - noThermalDemand, + onlyThermalDemandOfHouse, ) match { case ( ThermalGridState( @@ -262,8 +259,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { testGridAmbientTemperature, isNotRunning, Megawatts(0d), - thermalDemand, - noThermalDemand, + onlyThermalDemandOfHouse, ) match { case ( ThermalGridState( 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 c1ded4c6dc..c2c1880fef 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala @@ -184,8 +184,7 @@ class ThermalGridWithStorageOnlySpec gridState, isNotRunning, testGridQDotInfeed, - noThermalDemand, - thermalDemand, + onlyThermalDemandOfHeatStorage, ) updatedGridState match { @@ -211,8 +210,7 @@ class ThermalGridWithStorageOnlySpec testGridAmbientTemperature, isRunning, testGridQDotInfeed, - noThermalDemand, - thermalDemand, + onlyThermalDemandOfHeatStorage, ) nextThreshold shouldBe Some(StorageFull(276000L)) @@ -247,8 +245,7 @@ class ThermalGridWithStorageOnlySpec testGridAmbientTemperature, isRunning, testGridQDotConsumptionHigh, - thermalDemand, - noThermalDemand, + onlyThermalDemandOfHouse, ) match { case ( ThermalGridState( @@ -274,7 +271,6 @@ class ThermalGridWithStorageOnlySpec isRunning, Kilowatts(0d), noThermalDemand, - noThermalDemand, ) updatedState match { case (