Skip to content

Commit

Permalink
Merge branch 'dev' into df/#1051-Refactor-thermal-calcRelevantData
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfeismann committed Nov 25, 2024
2 parents 511add9 + 3b5a72f commit f3ca7a0
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 83 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `ApparentPower` to differentiate between different power types [#794](https://github.com/ie3-institute/simona/issues/794)
- Update/enhance config documentation [#1013](https://github.com/ie3-institute/simona/issues/1013)
- Create `CITATION.cff` [#1035](https://github.com/ie3-institute/simona/issues/1035)
- Introduce ThermalDemandWrapper [#1049](https://github.com/ie3-institute/simona/issues/1049)

### Changed
- Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435)
Expand Down
87 changes: 23 additions & 64 deletions src/main/scala/edu/ie3/simona/model/participant/HpModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +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.{
ThermalEnergyDemand,
ThermalDemandWrapper,
ThermalGridState,
}
import edu.ie3.simona.model.thermal.{ThermalGrid, ThermalThreshold}
Expand All @@ -22,8 +22,8 @@ import edu.ie3.util.quantities.PowerSystemUnits
import edu.ie3.util.scala.OperationInterval
import edu.ie3.util.scala.quantities.DefaultQuantities._
import edu.ie3.util.scala.quantities.{ApparentPower, Kilovoltamperes}
import squants.energy.{KilowattHours, Kilowatts}
import squants.{Energy, Power, Temperature}
import squants.energy.Kilowatts
import squants.{Power, Temperature}

import java.time.ZonedDateTime
import java.util.UUID
Expand Down Expand Up @@ -132,7 +132,7 @@ final case class HpModel(
): (Boolean, Boolean, HpState) = {

// Use lastHpState and relevantData to update state of thermalGrid to the current tick
val (demandHouse, demandThermalStorage, currentThermalGridState) =
val (thermalDemandWrapper, currentThermalGridState) =
thermalGrid.energyDemandAndUpdatedState(
relevantData,
lastHpState,
Expand All @@ -144,8 +144,7 @@ final case class HpModel(
lastHpState,
currentThermalGridState,
relevantData,
demandHouse,
demandThermalStorage,
thermalDemandWrapper,
)

// Updating the HpState
Expand All @@ -166,10 +165,8 @@ final case class HpModel(
* 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
* @param thermalDemands
* ThermalEnergyDemand of the house and the thermal storage
* @return
* boolean defining if heat pump runs in next time step, if it can be in
* operation and can be out of operation
Expand All @@ -178,69 +175,31 @@ final case class HpModel(
lastState: HpState,
currentThermalGridState: ThermalGridState,
relevantData: HpRelevantData,
demandHouse: ThermalEnergyDemand,
demandThermalStorage: ThermalEnergyDemand,
thermalDemands: ThermalDemandWrapper,
): (Boolean, Boolean, Boolean) = {

val (
houseHasDemand,
heatStorageHasDemand,
noThermalStorageOrThermalStorageIsEmpty,
) = determineDemandBooleans(
lastState,
currentThermalGridState,
demandHouse,
demandThermalStorage,
)
val demandHouse = thermalDemands.houseDemand
val demandThermalStorage = thermalDemands.heatStorageDemand
val noThermalStorageOrThermalStorageIsEmpty =
currentThermalGridState.isThermalStorageEmpty

val turnHpOn: Boolean =
houseHasDemand || heatStorageHasDemand
val turnHpOn =
(demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) ||
(demandHouse.hasAdditionalDemand && lastState.isRunning) ||
demandThermalStorage.hasRequiredDemand ||
(demandThermalStorage.hasAdditionalDemand && lastState.isRunning)

val canOperate =
demandHouse.hasRequiredDemand || demandHouse.hasAdditionalDemand ||
demandThermalStorage.hasRequiredDemand || demandThermalStorage.hasAdditionalDemand
val canBeOutOfOperation =
!(demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty)

(turnHpOn, canOperate, canBeOutOfOperation)
}

/** 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 thermal
* storage, or it is empty.
*
* @param lastHpState
* Current state of the heat pump
* @param updatedGridState
* The updated state of the [[ThermalGrid]]
* @param demandHouse
* heat demand of the thermal house
* @param demandThermalStorage
* heat demand of the thermal storage
* @return
* First boolean is true, if house has heat demand. Second boolean is true,
* if thermalStorage has heat demand. Third boolean is true, if there is no
* thermalStorage, or it's empty.
*/

private def determineDemandBooleans(
lastHpState: HpState,
updatedGridState: ThermalGridState,
demandHouse: ThermalEnergyDemand,
demandThermalStorage: ThermalEnergyDemand,
): (Boolean, Boolean, Boolean) = {
implicit val tolerance: Energy = KilowattHours(1e-3)
val noThermalStorageOrThermalStorageIsEmpty: Boolean =
updatedGridState.storageState.isEmpty || updatedGridState.storageState
.exists(
_.storedEnergy =~ zeroKWh
)

val houseDemand =
(demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || (lastHpState.isRunning && demandHouse.hasAdditionalDemand)
val heatStorageDemand =
demandThermalStorage.hasRequiredDemand || (lastHpState.isRunning && demandThermalStorage.hasAdditionalDemand)
(houseDemand, heatStorageDemand, noThermalStorageOrThermalStorageIsEmpty)
(
turnHpOn,
canOperate,
canBeOutOfOperation,
)
}

/** Calculate state depending on whether heat pump is needed or not. Also
Expand Down Expand Up @@ -341,7 +300,7 @@ 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.toActivePower(cosPhiRated) * 0.5)

val updatedHpState = calcState(
Expand Down
51 changes: 41 additions & 10 deletions src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import edu.ie3.datamodel.models.result.thermal.{
import edu.ie3.simona.exceptions.agent.InconsistentStateException
import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState}
import edu.ie3.simona.model.thermal.ThermalGrid.{
ThermalDemandWrapper,
ThermalEnergyDemand,
ThermalGridState,
}
Expand All @@ -24,7 +25,7 @@ import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState
import edu.ie3.simona.util.TickUtil.TickLong
import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble
import edu.ie3.util.scala.quantities.DefaultQuantities._
import squants.energy.Kilowatts
import squants.energy.{KilowattHours, Kilowatts}
import squants.{Energy, Power, Temperature}

import java.time.ZonedDateTime
Expand Down Expand Up @@ -57,7 +58,7 @@ final case class ThermalGrid(
def energyDemandAndUpdatedState(
relevantData: HpRelevantData,
lastHpState: HpState,
): (ThermalEnergyDemand, ThermalEnergyDemand, ThermalGridState) = {
): (ThermalDemandWrapper, ThermalGridState) = {
/* First get the energy demand of the houses but only if inner temperature is below target temperature */

val (houseDemand, updatedHouseState) =
Expand Down Expand Up @@ -128,13 +129,15 @@ final case class ThermalGrid(
}

(
ThermalEnergyDemand(
houseDemand.required,
houseDemand.possible,
),
ThermalEnergyDemand(
storageDemand.required,
storageDemand.possible,
ThermalDemandWrapper(
ThermalEnergyDemand(
houseDemand.required,
houseDemand.possible,
),
ThermalEnergyDemand(
storageDemand.required,
storageDemand.possible,
),
),
ThermalGridState(updatedHouseState, updatedStorageState),
)
Expand Down Expand Up @@ -516,14 +519,42 @@ object ThermalGrid {
final case class ThermalGridState(
houseState: Option[ThermalHouseState],
storageState: 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
* thermal storage, or it is empty.
*
* @return
* boolean which is true, if there is no thermalStorage, or it's empty.
*/
def isThermalStorageEmpty: Boolean = {
implicit val tolerance: Energy = KilowattHours(1e-3)
storageState.isEmpty || storageState
.exists(
_.storedEnergy =~ zeroKWh
)
}
}

def startingState(thermalGrid: ThermalGrid): ThermalGridState =
ThermalGridState(
thermalGrid.house.map(house => ThermalHouse.startingState(house)),
thermalGrid.storage.map(_.startingState),
)

/** Wraps the demand of thermal units (thermal house, thermal storage).
*
* @param houseDemand
* the demand of the thermal house
* @param heatStorageDemand
* the demand of the thermal heat storage
*/
final case class ThermalDemandWrapper private (
houseDemand: ThermalEnergyDemand,
heatStorageDemand: ThermalEnergyDemand,
)

/** 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
Expand Down
50 changes: 48 additions & 2 deletions src/test/scala/edu/ie3/simona/model/thermal/ThermalGridSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@

package edu.ie3.simona.model.thermal

import edu.ie3.datamodel.models.input.thermal.ThermalStorageInput
import edu.ie3.simona.model.thermal.ThermalGrid.ThermalEnergyDemand
import edu.ie3.simona.test.common.UnitSpec
import squants.energy.{MegawattHours, WattHours, Watts}
import squants.energy.{KilowattHours, MegawattHours, WattHours, Watts}
import squants.thermal.Celsius
import squants.{Energy, Power, Temperature}

class ThermalGridSpec extends UnitSpec {
import scala.jdk.CollectionConverters._

class ThermalGridSpec
extends UnitSpec
with ThermalHouseTestData
with ThermalStorageTestData {

implicit val tempTolerance: Temperature = Celsius(1e-3)
implicit val powerTolerance: Power = Watts(1e-3)
Expand Down Expand Up @@ -97,4 +103,44 @@ class ThermalGridSpec extends UnitSpec {
}
}
}
"ThermalGridState" should {
val thermalGridOnlyHouse = ThermalGrid(
new edu.ie3.datamodel.models.input.container.ThermalGrid(
thermalBusInput,
Set(thermalHouseInput).asJava,
Set.empty[ThermalStorageInput].asJava,
)
)

"return true when there is no storage" in {
val initialState = ThermalGrid.startingState(thermalGridOnlyHouse)
val result = initialState.isThermalStorageEmpty
result shouldBe true
}

val thermalGrid = ThermalGrid(
new edu.ie3.datamodel.models.input.container.ThermalGrid(
thermalBusInput,
Set(thermalHouseInput).asJava,
Set[ThermalStorageInput](thermalStorageInput).asJava,
)
)

"return true when all stored energy is effectively zero" in {
val initialState = ThermalGrid.startingState(thermalGrid)
val result = initialState.isThermalStorageEmpty
result shouldBe true
}

"return false when storage is not empty" in {
val initialState = ThermalGrid.startingState(thermalGrid)
val gridState = initialState.copy(storageState =
initialState.storageState.map(storageState =>
storageState.copy(storedEnergy = KilowattHours(1))
)
)
val result = gridState.isThermalStorageEmpty
result shouldBe false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ 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 squants.energy.{Kilowatts, Power}
import edu.ie3.simona.model.thermal.ThermalGrid.{
ThermalDemandWrapper,
ThermalEnergyDemand,
}
import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKWh
import squants.energy.{KilowattHours, Kilowatts, Power}
import squants.thermal.{Celsius, Temperature}

import java.util.UUID
Expand All @@ -25,4 +30,21 @@ trait ThermalGridTestData {
protected val testGridQDotInfeed: Power = Kilowatts(15d)
protected val testGridQDotConsumption: Power = Kilowatts(-42d)
protected val testGridQDotConsumptionHigh: Power = Kilowatts(-200d)
protected val noThermalDemand: ThermalDemandWrapper =
ThermalDemandWrapper(
ThermalEnergyDemand(zeroKWh, zeroKWh),
ThermalEnergyDemand(zeroKWh, zeroKWh),
)
protected val onlyThermalDemandOfHouse: ThermalDemandWrapper =
ThermalDemandWrapper(
ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
ThermalEnergyDemand(zeroKWh, zeroKWh),
)
protected val onlyThermalDemandOfHeatStorage: ThermalDemandWrapper =
ThermalDemandWrapper(
ThermalEnergyDemand(zeroKWh, zeroKWh),
ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)),
)
protected val isRunning: Boolean = true
protected val isNotRunning: Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ 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,
StorageFull,
}
import edu.ie3.simona.test.common.UnitSpec
import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh}
import squants.energy._
import squants.thermal.Celsius
import squants.{Energy, Kelvin, Power, Temperature}
Expand Down Expand Up @@ -109,11 +109,14 @@ class ThermalGridWithHouseAndStorageSpec
ThermalGrid.startingState(thermalGrid),
None,
)
val (houseDemand, storageDemand, updatedThermalGridState) =
val (thermalDemands, updatedThermalGridState) =
thermalGrid.energyDemandAndUpdatedState(
relevantData,
lastHpState,
)
val houseDemand = thermalDemands.houseDemand
val storageDemand = thermalDemands.heatStorageDemand

houseDemand.required should approximate(zeroKWh)
houseDemand.possible should approximate(KilowattHours(31.05009722d))
storageDemand.required should approximate(KilowattHours(1150d))
Expand Down Expand Up @@ -146,11 +149,13 @@ class ThermalGridWithHouseAndStorageSpec
None,
)

val (houseDemand, storageDemand, updatedThermalGridState) =
val (thermalDemands, updatedThermalGridState) =
thermalGrid.energyDemandAndUpdatedState(
relevantData,
lastHpState,
)
val houseDemand = thermalDemands.houseDemand
val storageDemand = thermalDemands.heatStorageDemand

houseDemand.required should approximate(KilowattHours(45.6000555))
houseDemand.possible should approximate(KilowattHours(75.600055555))
Expand Down
Loading

0 comments on commit f3ca7a0

Please sign in to comment.