Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor thermal grid energy demand #929

Merged
merged 37 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2cf55aa
Merge remote-tracking branch 'refs/remotes/origin/df/#894-storage-zer…
danielfeismann Aug 24, 2024
6e9b40f
Refactoring of ThermalGrid.energyGrid to distinguish between demand o…
danielfeismann Aug 24, 2024
67e2a4e
fmt
danielfeismann Aug 24, 2024
01e4265
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Aug 26, 2024
17911b6
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Aug 29, 2024
07a80ec
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Aug 29, 2024
1b7e1cc
Merge branch 'refs/heads/dev' into df/#928-Refactor-ThermalGird-energ…
danielfeismann Sep 16, 2024
15169b7
Merge branch 'refs/heads/dev' into df/#928-Refactor-ThermalGird-energ…
danielfeismann Sep 17, 2024
f2e8f1d
Merge branch 'refs/heads/dev' into df/#928-Refactor-ThermalGird-energ…
danielfeismann Sep 17, 2024
476686c
Merge branch 'refs/heads/dev' into df/#928-Refactor-ThermalGird-energ…
danielfeismann Oct 1, 2024
2965580
Merge branch 'refs/heads/dev' into df/#928-Refactor-ThermalGird-energ…
danielfeismann Oct 14, 2024
e32dd2b
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Oct 15, 2024
fbafbec
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Oct 22, 2024
a8e0c23
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
sebastian-peter Oct 25, 2024
32dea34
remove unused parameter
danielfeismann Oct 26, 2024
e886345
remove unused parameter
danielfeismann Oct 28, 2024
4f39b9b
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Oct 28, 2024
b893c08
fix scapegoat code smell because of unnecessary store before return
danielfeismann Oct 28, 2024
23b2582
Proposal for separating state and operation point calculation
danielfeismann Oct 28, 2024
ebe9f0c
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Oct 28, 2024
6be8e36
aligning states of HpModel
danielfeismann Oct 29, 2024
286f0eb
add scala doc
danielfeismann Oct 29, 2024
8a31d88
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Nov 1, 2024
f44dddf
merging dev
danielfeismann Nov 1, 2024
80fbc09
remove not necessary headOption
danielfeismann Nov 5, 2024
1beb478
break long line
danielfeismann Nov 5, 2024
07a65f1
use wildcard for not used val
danielfeismann Nov 5, 2024
79e91c2
rollback currentThermalGridState to thermalGridState
danielfeismann Nov 5, 2024
71c9852
scala doc format
danielfeismann Nov 5, 2024
d6e907c
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Nov 7, 2024
67202a8
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Nov 13, 2024
f819146
Tiny bit of code cleanup
sebastian-peter Nov 13, 2024
b1fc76c
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Nov 14, 2024
4e8d348
Merge remote-tracking branch 'origin/df/#928-Refactor-ThermalGird-ene…
danielfeismann Nov 14, 2024
61acfb6
grammar
danielfeismann Nov 14, 2024
5790cff
update scalaDoc
danielfeismann Nov 14, 2024
08055f2
Merge branch 'dev' into df/#928-Refactor-ThermalGird-energyDemand
danielfeismann Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ 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)

### Fixed
- Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@ trait HpAgentFundamentals
calcRelevantData: HpRelevantData,
nodalVoltage: squants.Dimensionless,
model: HpModel,
): HpState = model.determineState(modelState, calcRelevantData)
): HpState = {
model.determineState(modelState, calcRelevantData)._3
}

/** Abstract definition, individual implementations found in individual agent
* fundamental classes
Expand Down
199 changes: 146 additions & 53 deletions src/main/scala/edu/ie3/simona/model/participant/HpModel.scala
sebastian-peter marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -80,17 +82,17 @@ 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
* @return
* 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
Expand All @@ -99,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
Expand All @@ -108,27 +110,52 @@ 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 state
* Current state of the heat pump
* @param lastHpState
* 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 the updated
* [[HpState]]
*/
def determineState(
state: HpState,
lastHpState: HpState,
relevantData: HpRelevantData,
): HpState = {
val turnOn = operatesInNextState(state, relevantData)
calcState(state, relevantData, turnOn)
): (Boolean, Boolean, HpState) = {
danielfeismann marked this conversation as resolved.
Show resolved Hide resolved

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

/** Depending on the input, this function decides whether the heat pump will
Expand All @@ -137,31 +164,95 @@ 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
* boolean defining if heat pump runs in next time step, if it can be in
* operation and can be out of operation
*/
private def operatesInNextState(
state: HpState,
lastState: HpState,
currentThermalGridState: ThermalGridState,
relevantData: HpRelevantData,
): Boolean = {
val demand = thermalGrid.energyDemand(
relevantData.currentTick,
relevantData.ambientTemperature,
state.thermalGridState,
demandHouse: ThermalEnergyDemand,
demandThermalStorage: ThermalEnergyDemand,
): (Boolean, Boolean, Boolean) = {

val (
houseHasDemand,
heatStorageHasDemand,
noThermalStorageOrThermalStorageIsEmpty,
) = determineDemandBooleans(
lastState,
currentThermalGridState,
demandHouse,
demandThermalStorage,
)
demand.hasRequiredDemand || (state.isRunning && demand.hasAdditionalDemand)

val turnHpOn: Boolean =
houseHasDemand || heatStorageHasDemand

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

/** Calculate state depending on whether heat pump is needed or not. Also
* 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
Expand All @@ -170,21 +261,27 @@ final case class HpModel(
* next [[HpState]]
*/
private def calcState(
state: HpState,
lastState: HpState,
relevantData: HpRelevantData,
isRunning: Boolean,
): HpState = {
val lastStateStorageQDot = lastState.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) =
thermalGrid.updateState(
relevantData.currentTick,
state.thermalGridState,
state.ambientTemperature.getOrElse(relevantData.ambientTemperature),
lastState.thermalGridState,
lastState.ambientTemperature.getOrElse(relevantData.ambientTemperature),
relevantData.ambientTemperature,
newThermalPower,
)
Expand All @@ -205,23 +302,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 +318,7 @@ final case class HpModel(

ProvideMinMaxFlexOptions(
uuid,
updatedState.activePower,
updatedHpState.activePower,
lowerBoundary,
upperBoundary,
)
Expand Down Expand Up @@ -260,13 +348,18 @@ 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 updatedHpState = calcState(
lastState,
data,
turnOn,
)

(
updatedState,
updatedHpState,
FlexChangeIndicator(
changesAtNextActivation = true,
updatedState.maybeThermalThreshold.map(_.tick),
updatedHpState.maybeThermalThreshold.map(_.tick),
),
)
}
Expand Down Expand Up @@ -335,7 +428,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
Expand Down
Loading