Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/df/#928-Refactor-Th…
Browse files Browse the repository at this point in the history
…ermalGird-energyDemand' into df/tmpHPmergeall
  • Loading branch information
danielfeismann committed Aug 24, 2024
2 parents 0b1868d + 67e2a4e commit e2d7133
Show file tree
Hide file tree
Showing 8 changed files with 696 additions and 259 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated `Gradle` to version V8.10 [#829](https://github.com/ie3-institute/simona/issues/829)
- Updated AUTHORS.md [#905](https://github.com/ie3-institute/simona/issues/905)
- Prepare ThermalStorageTestData for Storage without storageVolumeLvlMin [#894](https://github.com/ie3-institute/simona/issues/894)
- Refactoring of `ThermalGrid.energyGrid` to distinguish between demand of house and storage [#928](https://github.com/ie3-institute/simona/issues/928)

### Fixed
- Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,11 @@ trait HpAgentFundamentals
calcRelevantData: HpRelevantData,
nodalVoltage: squants.Dimensionless,
model: HpModel,
): HpState = model.determineState(modelState, calcRelevantData)
): HpState = {
val (_, _, state) =
model.determineState(modelState, calcRelevantData)
state
}

/** Abstract definition, individual implementations found in individual agent
* fundamental classes
Expand Down
151 changes: 116 additions & 35 deletions src/main/scala/edu/ie3/simona/model/participant/HpModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPowerAndHe
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.ThermalGridState
import edu.ie3.simona.model.thermal.ThermalGrid.{
ThermalEnergyDemand,
ThermalGridState,
}
import edu.ie3.simona.model.thermal.{ThermalGrid, ThermalThreshold}
import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.ProvideFlexOptions
import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions
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.DefaultQuantities._
import squants.energy.Kilowatts
import squants.{Power, Temperature}
import squants.energy.{KilowattHours, Kilowatts}
import squants.{Energy, Power, Temperature}

import java.time.ZonedDateTime
import java.util.UUID
Expand Down Expand Up @@ -116,19 +118,23 @@ final case class HpModel(
* function calculates the heat pump's next state to get the actual active
* power of this state use [[calculateActivePower]] with the generated state
*
* @param state
* Current state of the heat pump
* @param lastState
* Last state of the heat pump
* @param relevantData
* data of heat pump including
* @return
* next [[HpState]]
* Booleans if Hp can operate and can be out of operation plus next
* [[HpState]]
*/
def determineState(
state: HpState,
lastState: HpState,
relevantData: HpRelevantData,
): HpState = {
val turnOn = operatesInNextState(state, relevantData)
calcState(state, relevantData, turnOn)
): (Boolean, Boolean, HpState) = {
val (turnOn, canOperate, canBeOutOfOperation, houseDemand, storageDemand) =
operatesInNextState(lastState, relevantData)
val updatedState =
calcState(lastState, relevantData, turnOn, houseDemand, storageDemand)
(canOperate, canBeOutOfOperation, updatedState)
}

/** Depending on the input, this function decides whether the heat pump will
Expand All @@ -142,18 +148,63 @@ final case class HpModel(
* @param relevantData
* Relevant (external) data
* @return
* boolean defining if heat pump runs in next time step
* 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
*/
private def operatesInNextState(
state: HpState,
relevantData: HpRelevantData,
): Boolean = {
val demand = thermalGrid.energyDemand(
relevantData.currentTick,
relevantData.ambientTemperature,
state.thermalGridState,
): (Boolean, Boolean, Boolean, Boolean, Boolean) = {
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,
)
demand.hasRequiredDemand || (state.isRunning && demand.hasAdditionalDemand)

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)
}

def determineDemandBooleans(
hpState: 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) || (hpState.isRunning && demandHouse.hasAdditionalDemand)
val heatStorageDemand = {
(demandThermalStorage.hasRequiredDemand) || (hpState.isRunning && demandThermalStorage.hasAdditionalDemand)
}
(houseDemand, heatStorageDemand, noThermalStorageOrThermalStorageIsEmpty)
}

/** Calculate state depending on whether heat pump is needed or not. Also
Expand All @@ -173,11 +224,19 @@ final case class HpModel(
state: HpState,
relevantData: HpRelevantData,
isRunning: Boolean,
houseDemand: Boolean,
storageDemand: Boolean,
): HpState = {
val lastStateStorageqDot = state.thermalGridState.storageState
.map(_.qDot)
.getOrElse(zeroKW)

val (newActivePower, newThermalPower) =
if (isRunning)
(pRated, pThermal)
else (DefaultQuantities.zeroKW, DefaultQuantities.zeroKW)
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) =
Expand Down Expand Up @@ -205,23 +264,14 @@ final case class HpModel(
lastState: HpState,
): ProvideFlexOptions = {
/* Determine the operating state in the given tick */
val updatedState = determineState(lastState, data)

/* Determine the options we have */
val thermalEnergyDemand = thermalGrid.energyDemand(
data.currentTick,
data.ambientTemperature,
lastState.thermalGridState,
)
val canOperate =
thermalEnergyDemand.hasRequiredDemand || thermalEnergyDemand.hasAdditionalDemand
val canBeOutOfOperation = !thermalEnergyDemand.hasRequiredDemand
val (canOperate, canBeOutOfOperation, updatedHpState)
: (Boolean, Boolean, HpState) = determineState(lastState, data)

val lowerBoundary =
if (canBeOutOfOperation)
zeroKW
else
updatedState.activePower
updatedHpState.activePower
val upperBoundary =
if (canOperate)
sRated * cosPhiRated
Expand All @@ -230,7 +280,7 @@ final case class HpModel(

ProvideMinMaxFlexOptions(
uuid,
updatedState.activePower,
updatedHpState.activePower,
lowerBoundary,
upperBoundary,
)
Expand Down Expand Up @@ -260,13 +310,44 @@ final case class HpModel(
): (HpState, FlexChangeIndicator) = {
/* If the setpoint value is above 50 % of the electrical power, turn on the heat pump otherwise turn it off */
val turnOn = setPower > (sRated * cosPhiRated * 0.5)
val updatedState = calcState(lastState, data, turnOn)

val (
thermalEnergyDemandHouse,
thermalEnergyDemandStorage,
updatedThermalGridState,
) =
thermalGrid.energyDemandAndUpdatedState(
data.currentTick,
lastState.ambientTemperature.getOrElse(data.ambientTemperature),
data.ambientTemperature,
lastState.thermalGridState,
)

val (
houseDemand,
heatStorageDemand,
noThermalStorageOrThermalStorageIsEmpty,
) = determineDemandBooleans(
lastState,
updatedThermalGridState,
thermalEnergyDemandHouse,
thermalEnergyDemandStorage,
)

val updatedHpState: HpState =
calcState(
lastState,
data,
turnOn,
houseDemand,
heatStorageDemand,
)

(
updatedState,
updatedHpState,
FlexChangeIndicator(
changesAtNextActivation = true,
updatedState.maybeThermalThreshold.map(_.tick),
updatedHpState.maybeThermalThreshold.map(_.tick),
),
)
}
Expand Down
105 changes: 75 additions & 30 deletions src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,60 +43,105 @@ final case class ThermalGrid(
) extends LazyLogging {

/** Determine the energy demand of the total grid at the given instance in
* time
* time and returns it including the updatedState
*
* @param tick
* Questioned instance in time
* @param lastAmbientTemperature
* Ambient temperature until this tick
* @param ambientTemperature
* Ambient temperature in the instance in question
* @param state
* Currently applicable state of the thermal grid
* @return
* The total energy demand of the grid
* The total energy demand of the house and the storage and an updated
* [[ThermalGridState]]
*/
def energyDemand(
def energyDemandAndUpdatedState(
tick: Long,
// FIXME this is also in state
lastAmbientTemperature: Temperature,
ambientTemperature: Temperature,
state: ThermalGridState,
): ThermalEnergyDemand = {
/* First get the energy demand of the houses */
val houseDemand = house
.zip(state.houseState)
.map { case (house, state) =>
house.energyDemand(
tick,
ambientTemperature,
state,
)
): (ThermalEnergyDemand, ThermalEnergyDemand, ThermalGridState) = {
/* First get the energy demand of the houses but only if inner temperature is below target temperature */

val (houseDemand, updatedHouseState) =
house.zip(state.houseState).headOption match {
case Some((thermalHouse, lastHouseState)) =>
val (updatedHouseState, updatedStorageState) =
thermalHouse.determineState(
tick,
lastHouseState,
lastAmbientTemperature,
ambientTemperature,
lastHouseState.qDot,
)
if (
updatedHouseState.innerTemperature < thermalHouse.targetTemperature | (lastHouseState.qDot > zeroKW && updatedHouseState.innerTemperature < thermalHouse.upperBoundaryTemperature)
) {
(
thermalHouse.energyDemand(
tick,
ambientTemperature,
updatedHouseState,
),
Some(updatedHouseState),
)

} else {
(ThermalEnergyDemand.noDemand, Some(updatedHouseState))
}

case None =>
(ThermalEnergyDemand.noDemand, None)
}
.getOrElse(ThermalEnergyDemand.noDemand)

/* Then go over the storages, see what they can provide and what they might be able to charge */
val (storedEnergy, remainingCapacity) = {
val (storageDemand, updatedStorageState) = {

storage
.zip(state.storageState)
.map { case (storage, state) =>
val usableEnergy = state.storedEnergy
val remaining = storage.getMaxEnergyThreshold - usableEnergy
val (updatedStorageState, _) =
storage.updateState(tick, state.qDot, state)
val storedEnergy = updatedStorageState.storedEnergy
val soc = storedEnergy / storage.getMaxEnergyThreshold
val storageRequired = {
if (soc == 0d) {
storage.getMaxEnergyThreshold - storedEnergy

} else {
zeroMWH
}
}

val storagePossible = storage.getMaxEnergyThreshold - storedEnergy
(
usableEnergy,
remaining,
ThermalEnergyDemand(
storageRequired,
storagePossible,
),
Some(updatedStorageState),
)

}
.getOrElse(
(zeroMWH, zeroMWH)
ThermalEnergyDemand(zeroMWH, zeroMWH),
None,
)
}

val usedEnergy =
if (storedEnergy >= houseDemand.required)
houseDemand.required
else
storedEnergy
val finallyRemaining = remainingCapacity + usedEnergy

ThermalEnergyDemand(
houseDemand.required - usedEnergy,
houseDemand.possible + finallyRemaining,
(
ThermalEnergyDemand(
houseDemand.required,
houseDemand.possible,
),
ThermalEnergyDemand(
storageDemand.required,
storageDemand.possible,
),
ThermalGridState(updatedHouseState, updatedStorageState),
)
}

Expand Down
Loading

0 comments on commit e2d7133

Please sign in to comment.