diff --git a/CHANGELOG.md b/CHANGELOG.md index 169294fd29..1eda0ce2a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added option to directly zip the output files [#793](https://github.com/ie3-institute/simona/issues/793) - Added weatherData HowTo for Copernicus ERA5 data [#967](https://github.com/ie3-institute/simona/issues/967) - Add some quote to 'printGoodbye' [#997](https://github.com/ie3-institute/simona/issues/997) +- Add unapply method for ThermalHouseResults [#934](https://github.com/ie3-institute/simona/issues/934) - Integration test for thermal grids [#878](https://github.com/ie3-institute/simona/issues/878) ### Changed @@ -97,6 +98,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move compression of output files into `ResultEventListener`[#965](https://github.com/ie3-institute/simona/issues/965) - Rewrote StorageModelTest from groovy to scala [#646](https://github.com/ie3-institute/simona/issues/646) - Updated `ExtEvSimulationClasses` [#898](https://github.com/ie3-institute/simona/issues/898) +- Refactoring of `ThermalGrid.energyGrid` to distinguish between demand of house and storage [#928](https://github.com/ie3-institute/simona/issues/928) +- Refactoring to use zeroKW and zeroKWH in thermal grid unit tests [#1023](https://github.com/ie3-institute/simona/issues/1023) ### Fixed - Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala index 74cfd91d5a..a693b86ae7 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -1913,7 +1913,6 @@ protected trait ParticipantAgentFundamentals[ ): Option[ResultEvent] = result match { case thermalUnitResult: ThermalUnitResult => Some(ResultEvent.ThermalResultEvent(thermalUnitResult)) - case unsupported => log.debug( s"Results of class '${unsupported.getClass.getSimpleName}' are currently not supported." diff --git a/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala index d70cbfd9b2..365820ec3c 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala @@ -314,9 +314,7 @@ trait HpAgentFundamentals nodalVoltage: squants.Dimensionless, model: HpModel, ): HpState = { - val (_, _, state) = - model.determineState(modelState, calcRelevantData) - state + model.determineState(modelState, calcRelevantData)._3 } /** Abstract definition, individual implementations found in individual agent diff --git a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala index 2f5e1340dc..e840b0e15d 100644 --- a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala @@ -62,20 +62,15 @@ object ResultEvent { ComparableQuantity[Power], ComparableQuantity[Temperature], ) - ] = { - if (result != null) { - Some( - ( - result.getTime, - result.getInputModel, - result.getqDot, - result.getIndoorTemperature, - ) + ] = + Option(result).map { result => + ( + result.getTime, + result.getInputModel, + result.getqDot, + result.getIndoorTemperature, ) - } else { - None } - } } object CylindricalThermalStorageResult { @@ -87,17 +82,13 @@ object ResultEvent { ComparableQuantity[Energy], ) ] = { - if (result != null) { - Some( - ( - result.getTime, - result.getInputModel, - result.getqDot, - result.getEnergy, - ) + Option(result).map { result => + ( + result.getTime, + result.getInputModel, + result.getqDot, + result.getEnergy, ) - } else { - None } } } @@ -105,23 +96,19 @@ object ResultEvent { object DomesticHotWaterStorageResult { def unapply(result: DomesticHotWaterStorageResult): Option[ ( - ZonedDateTime, + ZonedDateTime, UUID, ComparableQuantity[Power], ComparableQuantity[Energy], - ) + ) ] = { - if (result != null) { - Some( - ( - result.getTime, - result.getInputModel, - result.getqDot, - result.getEnergy, - ) + Option(result).map { result => + ( + result.getTime, + result.getInputModel, + result.getqDot, + result.getEnergy, ) - } else { - None } } } 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 d64c150808..423b865482 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala @@ -82,7 +82,7 @@ final case class HpModel( * [[HpModel.determineState]]. This state then is fed into the power * calculation logic by [[HpState]]. * - * @param modelState + * @param currentState * Current state of the heat pump * @param relevantData * data of heat pump including state of the heat pump @@ -90,9 +90,9 @@ final case class HpModel( * active power */ override protected def calculateActivePower( - modelState: HpState, + currentState: HpState, relevantData: HpRelevantData, - ): Power = modelState.activePower + ): Power = currentState.activePower /** "Calculate" the heat output of the heat pump. The hp's state is already * updated, because the calculation of apparent power in @@ -101,7 +101,7 @@ final case class HpModel( * * @param tick * Current simulation time for the calculation - * @param modelState + * @param currentState * Current state of the heat pump * @param data * Relevant (external) data for calculation @@ -110,45 +110,51 @@ final case class HpModel( */ override def calculateHeat( tick: Long, - modelState: HpState, + currentState: HpState, data: HpRelevantData, - ): Power = modelState.qDot + ): Power = currentState.qDot - /** Given a [[HpRelevantData]] object and the current [[HpState]], this - * function calculates the heat pump's next state to get the actual active - * power of this state use [[calculateActivePower]] with the generated state + /** Given a [[HpRelevantData]] object and the last [[HpState]], this function + * calculates the heat pump's next state to get the actual active power of + * this state use [[calculateActivePower]] with the generated state * - * @param lastState + * @param lastHpState * Last state of the heat pump * @param relevantData * data of heat pump including * @return - * Booleans if Hp can operate and can be out of operation plus next + * Booleans if Hp can operate and can be out of operation plus the updated * [[HpState]] */ def determineState( - lastState: HpState, + lastHpState: HpState, relevantData: HpRelevantData, ): (Boolean, Boolean, HpState) = { - val ( - turnOn, - canOperate, - canBeOutOfOperation, - houseDemand, - thermalStorageDemand, - domesticHotWaterStorageDemand, - ) = - operatesInNextState(lastState, relevantData) - val updatedState = calcState( - lastState, - relevantData, - turnOn, - houseDemand, - thermalStorageDemand, - domesticHotWaterStorageDemand, - relevantData.simulationStart, - relevantData.houseInhabitants, - ) + + // Use lastHpState and relevantData to update state of thermalGrid to the current tick + val (demandHouse, demandThermalStorage, currentThermalGridState) = + thermalGrid.energyDemandAndUpdatedState( + relevantData.currentTick, + lastHpState.ambientTemperature.getOrElse( + relevantData.ambientTemperature + ), + relevantData.ambientTemperature, + lastHpState.thermalGridState, + ) + + // Determining the operation point and limitations at this tick + val (turnOn, canOperate, canBeOutOfOperation) = + operatesInNextState( + lastHpState, + currentThermalGridState, + relevantData, + demandHouse, + demandThermalStorage, + ) + + // Updating the HpState + val updatedState = + calcState(lastHpState, relevantData, turnOn) (canOperate, canBeOutOfOperation, updatedState) } @@ -158,17 +164,24 @@ final case class HpModel( * met or the heat pump currently is in operation and the grid is able to * handle additional energy * - * @param state - * Current state of the heat pump + * @param lastState + * last state of the heat pump + * @param currentThermalGridState + * to current tick updated state of the thermalGrid * @param relevantData * Relevant (external) data + * @param demandHouse + * ThermalEnergyDemand of the house + * @param demandThermalStorage + * 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 [[ThermalEnergyDemand]] of * house, heat storage, domestic hot water storage */ private def operatesInNextState( - state: HpState, + lastState: HpState, + currentThermalGridState: ThermalGridState, relevantData: HpRelevantData, ): ( Boolean, @@ -269,8 +282,8 @@ final case class HpModel( * calculate inner temperature change of thermal house and update its inner * temperature. * - * @param state - * Current state of the heat pump + * @param lastState + * state of the heat pump until this tick * @param relevantData * data of heat pump including state of the heat pump * @param isRunning @@ -289,7 +302,7 @@ final case class HpModel( * next [[HpState]] */ private def calcState( - state: HpState, + lastState: HpState, relevantData: HpRelevantData, isRunning: Boolean, houseDemand: ThermalEnergyDemand, @@ -298,23 +311,23 @@ final case class HpModel( simulationStartTime: ZonedDateTime, houseInhabitants: Double, ): HpState = { - val lastStateStorageqDot = state.thermalGridState.storageState + val lastStateStorageQDot = lastState.thermalGridState.storageState .map(_.qDot) .getOrElse(zeroKW) val (newActivePower, newThermalPower) = if (isRunning) (pRated, pThermal) - else if (lastStateStorageqDot < zeroKW) - (zeroKW, lastStateStorageqDot * (-1)) + else if (lastStateStorageQDot < zeroKW) + (zeroKW, lastStateStorageQDot * (-1)) else (zeroKW, zeroKW) /* Push thermal energy to the thermal grid and get its updated state in return */ val (thermalGridState, maybeThreshold) = thermalGrid.updateState( relevantData.currentTick, - state.thermalGridState, - state.ambientTemperature.getOrElse(relevantData.ambientTemperature), + lastState.thermalGridState, + lastState.ambientTemperature.getOrElse(relevantData.ambientTemperature), relevantData.ambientTemperature, isRunning, newThermalPower, @@ -488,7 +501,7 @@ object HpModel { * @param qDot * result heat power * @param thermalGridState - * Currently applicable state of the thermal grid + * applicable state of the thermal grid * @param maybeThermalThreshold * An optional threshold of the thermal grid, indicating the next state * change 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 295ea06268..4de7ac513a 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -56,7 +56,7 @@ final case class ThermalGrid( * @param tick * Questioned instance in time * @param lastAmbientTemperature - * Ambient temperature valid up until (not including) the current tick + * Ambient temperature until this tick * @param ambientTemperature * Current ambient temperature * @param state @@ -132,8 +132,8 @@ final case class ThermalGrid( heatStorage .zip(state.storageState) .map { case (storage, state) => - val updatedStorageState = - storage.updateState(tick, state.qDot, state)._1 + val (updatedStorageState,_) = + storage.updateState(tick, state.qDot, state) val storedEnergy = updatedStorageState.storedEnergy val soc = storedEnergy / storage.getMaxEnergyThreshold val storageRequired = { @@ -173,8 +173,8 @@ final case class ThermalGrid( ) ) .getOrElse(ThermalEnergyDemand(zeroKWH, zeroKWH)) - val applicableqDotDomesticStorage = - identifyApplicableQDot(tick, domesticHotWaterDemand)._1 + val (applicableqDotDomesticStorage,_) = + identifyApplicableQDot(tick, domesticHotWaterDemand) domesticHotWaterStorage .zip(state.domesticHotWaterStorageState) 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 d3013d9a35..8b2bd77893 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala @@ -16,6 +16,7 @@ import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{ HouseTemperatureLowerBoundaryReached, HouseTemperatureUpperBoundaryReached, } +import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWH} import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageThreshold.{ StorageEmpty, 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 eec838478f..901bebc1e1 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala @@ -18,6 +18,7 @@ import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageThreshold.{ StorageEmpty, StorageFull, } +import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWH} import edu.ie3.simona.test.common.UnitSpec import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWH} import squants.energy._ @@ -190,7 +191,7 @@ class ThermalGridWithHouseOnlySpec "deliver the house state by just letting it cool down, if just no infeed is given" in { val tick = 0L val gridState = ThermalGrid.startingState(thermalGrid) - val externalQDot = Megawatts(0d) + val externalQDot = zeroKW val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption(