diff --git a/CHANGELOG.md b/CHANGELOG.md index a07dfaffc8..847e46164e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove type parameters from data and message classes [#1524](https://github.com/ie3-institute/simona/issues/1524) - Adapt ThermalHouse and HP flexibility behaviour [#1391](https://github.com/ie3-institute/simona/issues/1391) - Use ThermalStorageTypes for type safety [#1556](https://github.com/ie3-institute/simona/issues/1556) +- Refactored method to handle feed in within `ThermalGrid` [#1554](https://github.com/ie3-institute/simona/issues/1554) ### Fixed - Fixes in Documentation, ScalaDocs, Code Style and more [#1397](https://github.com/ie3-institute/simona/issues/1397) diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalDemandConditions.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalDemandConditions.scala new file mode 100644 index 0000000000..4317ed3a11 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalDemandConditions.scala @@ -0,0 +1,53 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.model.thermal + +import edu.ie3.simona.model.participant.hp.HpModel.HpState +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW + +private case class ThermalDemandConditions( + shouldContinueHouseHeating: Boolean, + houseDemand: Boolean, + heatStorageDemand: Boolean, + housePossible: Boolean, + heatStoragePossible: Boolean, + houseHeatedLastState: Boolean, +) + +private object ThermalDemandConditions { + + /** 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. + */ + def from(state: HpState): ThermalDemandConditions = { + val lastOperatingPoint = state.lastHpOperatingPoint.thermalOps + val houseDemand = state.thermalDemands.houseDemand + val heatStorageDemand = state.thermalDemands.heatStorageDemand + + val isHouseHeatedLastState = + lastOperatingPoint.qDotHouse > zeroKW && lastOperatingPoint.qDotHp > zeroKW + + 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. + */ + shouldContinueHouseHeating = + lastOperatingPoint.qDotHouse > zeroKW && houseDemand.hasPossibleDemand, + houseDemand = houseDemand.hasRequiredDemand, + heatStorageDemand = + heatStorageDemand.hasRequiredDemand || heatStorageDemand.hasPossibleDemand, + housePossible = houseDemand.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 2414d01e47..4f8ad6e4d0 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -171,14 +171,12 @@ final case class ThermalGrid( qDot: Power, ): (ThermalGridOperatingPoint, Option[ThermalThreshold]) = { // 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) + + handleCase(state, qDotHouse, qDotHeatStorage) - /* 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 state.lastHpOperatingPoint.thermalOps.qDotHouse > zeroKW && state.thermalDemands.houseDemand.hasPossibleDemand - then handleCase(state, qDot, zeroKW) - // or finally check for all other cases. - else handleFinalFeedInCases(state, qDot) } /** Handles the last cases of [[ThermalGrid.handleFeedIn]], where the thermal @@ -211,27 +209,26 @@ final case class ThermalGrid( * | 3 | else if house.addD | house | * | 4 | else | no output | * - * @param state - * State of the heat pump. - * @param qDot - * Feed in to the grid from thermal generation (e.g. heat pump) or thermal - * storages. + * @param conditions + * The ThermalDemandConditions, describing the current status of heat + * demand of the grid elements. * @return - * The operating point of the thermal grid and the thermalThreshold if - * there is one. + * The FeedInStrategy how to distribute the qDot from the heat source. */ - private def handleFinalFeedInCases( - state: HpState, - qDot: Power, - ): (ThermalGridOperatingPoint, Option[ThermalThreshold]) = { - - if state.thermalDemands.houseDemand.hasRequiredDemand then - handleCase(state, qDot, zeroKW) - else if state.thermalDemands.heatStorageDemand.hasRequiredDemand || state.thermalDemands.heatStorageDemand.hasPossibleDemand - then handleCase(state, zeroKW, qDot) - else if state.thermalDemands.houseDemand.hasPossibleDemand then - handleCase(state, qDot, zeroKW) - else handleCase(state, zeroKW, zeroKW) + private def selectFeedInStrategy( + conditions: ThermalDemandConditions + ): FeedInStrategy = { + if conditions.shouldContinueHouseHeating then { + HouseOnlyStrategy + } else if conditions.houseDemand then { + HouseOnlyStrategy + } else if conditions.heatStorageDemand then { + HeatStorageOnlyStrategy + } else if conditions.housePossible then { + HouseOnlyStrategy + } else { + NoOperationStrategy + } } /** Handles the different thermal flows from and into the thermal grid. diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalStrategyPatterns.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalStrategyPatterns.scala new file mode 100644 index 0000000000..8a4069a7ff --- /dev/null +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalStrategyPatterns.scala @@ -0,0 +1,33 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.model.thermal + +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW +import squants.Power + +/** Trait to provide a feed-in strategy for handling thermal infeed (qDot). + */ +private sealed trait FeedInStrategy { + def apply( + qDot: Power + ): (Power, Power) // (house, heatStorage) +} + +private object HouseOnlyStrategy extends FeedInStrategy { + override def apply(qDot: Power): (Power, Power) = + (qDot, zeroKW) +} + +private object HeatStorageOnlyStrategy extends FeedInStrategy { + override def apply(qDot: Power): (Power, Power) = + (zeroKW, qDot) +} + +private object NoOperationStrategy extends FeedInStrategy { + override def apply(qDot: Power): (Power, Power) = + (zeroKW, zeroKW) +}