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

Change thermal house behaviour to heat till targetTemperature #1183

Draft
wants to merge 32 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
267c08d
Change thermal house behaviour to heat till targetTemperature
danielfeismann Feb 13, 2025
be2830f
use correct hp when applying for weather service within EmAgentIT
danielfeismann Feb 13, 2025
59e7bdf
fix to use correct temperature when checking if innerTemperature is t…
danielfeismann Feb 14, 2025
86e6d23
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 16, 2025
99313f0
Merge remote-tracking branch 'refs/remotes/origin/df/#1167-handle-edg…
danielfeismann Feb 16, 2025
fd4b0b3
some simplifications and scalaDoc, fmt
danielfeismann Feb 16, 2025
b4c2389
fixing HpModel to comply with integration test
danielfeismann Feb 16, 2025
30669ea
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 17, 2025
25ae76b
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 17, 2025
57ab44a
fix EmAgentIT by adding the additional activation when thermal house …
danielfeismann Feb 17, 2025
8e1a5c4
updating scalaDocs
danielfeismann Feb 17, 2025
9e4d640
renaming ThermalHouse.determineState to ThermalHouse.updateState
danielfeismann Feb 17, 2025
6ac88aa
renaming actualTargetTemperature to actualTemperatureTarget
danielfeismann Feb 17, 2025
3d80b5b
introduce useUpperTempBoundaryForFlexibility already in HpAgentFundam…
danielfeismann Feb 17, 2025
ad89d9f
simplify HpModel.calcState
danielfeismann Feb 17, 2025
f8b8f4c
update HpModel.handleControlledPowerChange to distinguish between fle…
danielfeismann Feb 17, 2025
73ffacd
update ThermalGridIT to change of heating till targetTemp
danielfeismann Feb 17, 2025
ca7ed5b
fmt
danielfeismann Feb 17, 2025
fc0a3a9
more simplification
danielfeismann Feb 17, 2025
a910efc
fix HpModelSpec after merging
danielfeismann Feb 17, 2025
bfb3386
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 18, 2025
a329407
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 19, 2025
f1ac488
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 20, 2025
65c5900
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 24, 2025
6d81f40
Merge branch 'dev' into df/#1176-heat-pump-targetTemp
danielfeismann Feb 26, 2025
da941d5
adapt ThermalGrid to new requirements
danielfeismann Feb 26, 2025
8d1bf57
include also edge case
danielfeismann Feb 26, 2025
6377f1b
more refactoring
danielfeismann Feb 26, 2025
dcb4141
fmt
danielfeismann Feb 26, 2025
97702b5
adapt EmAgentIT to change of heating house always just till target te…
danielfeismann Feb 27, 2025
efcf5b6
correctly name ThermalHouse temperature boundary
danielfeismann Feb 27, 2025
4f2ef03
correctly name ThermalHouse temperature boundary part 2
danielfeismann Feb 27, 2025
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for topologies without transformers and slack grids with multiple nodes [#1099](https://github.com/ie3-institute/simona/issues/1099)
- Checking the number of slack nodes [#1122](https://github.com/ie3-institute/simona/issues/1122)
- Enhance exception message in case of InvalidGridException [#1124](https://github.com/ie3-institute/simona/issues/1124)
- Integration test for thermal grids [#1145](https://github.com/ie3-institute/simona/issues/1145)
- Added `VoltageLimits` [#1133](https://github.com/ie3-institute/simona/issues/1133)
- Introducing new ParticipantAgent and ParticipantModel [#1134](https://github.com/ie3-institute/simona/issues/1134)
- Using new `ParticipantAgent.Request` messages everywhere [#1195](https://github.com/ie3-institute/simona/issues/1195)
Expand All @@ -57,6 +56,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Replace `PvModel` with its new implementation [#1149](https://github.com/ie3-institute/simona/issues/1149)
- Replace `WecModel` with its new implementation [#1154](https://github.com/ie3-institute/simona/issues/1154)
- Replace `StorageModel` with its new implementation [#1153](https://github.com/ie3-institute/simona/issues/1153)
- Integration test for thermal grids without Em [#1145](https://github.com/ie3-institute/simona/issues/1145)
- Change thermal house behaviour to heat till targetTemperature [#1176](https://github.com/ie3-institute/simona/issues/1176)

### Changed
- Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435)
Expand Down
17 changes: 16 additions & 1 deletion docs/readthedocs/models/thermal_house_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@ This page documents the functionality of the thermal house available in SIMONA.

## Behaviour

This house model represents the thermal behaviour of a building. This reflects a simple shoebox with a thermal capacity and with transmission losses.
This house model represents the thermal behaviour of a building. It represents a simple shoebox with thermal capacity and transmission losses.
The house can optionally be equipped with a {ref}`cts_model` as thermal storage. Both are connected by the {ref}`thermal_grid_model`.

The thermal house provides two different energy demands. The required demand indicates that the inner temperature of the house is below the lower temperature boundary and thus, requires mandatory heating. An additional demand indicates the amount of energy necessary to reach the target temperature. Additional demand not necessarily requires to be covered but could, e.g. for flexibility purposes.

There are different operating modes, depending on whether the heat source is em-controlled or not.

### Behaviour without EM control

If the heat source of this building is not under {ref}`em` control, the internal temperature of the house should remain between the target temperature and lower temperature boundary. If the temperature falls below the lower temperature limit, the available energy from the storage is used first. If the storage
is empty, the heat pump will first heat the house up to the target temperature and then refill the storage.
As the storage is initialised as empty, the heat source will start charging the storage first. Whenever the heat source is in operation (e.g. to charge the storage), it will continue to operate until the house has reached the target temperature again.

### Behaviour under EM control

Currently not fully supported

## Attributes, Units and Remarks

Expand Down
39 changes: 28 additions & 11 deletions src/main/scala/edu/ie3/simona/model/participant/HpModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,13 @@ final case class HpModel(

// Updating the HpState
val updatedHpState =
calcState(lastHpState, relevantData, turnOn, thermalDemandWrapper)
calcState(
lastHpState,
relevantData,
turnOn,
thermalDemandWrapper,
currentThermalGridState,
)
(canOperate, canBeOutOfOperation, updatedHpState)
}

Expand Down Expand Up @@ -214,6 +220,8 @@ final case class HpModel(
* determines whether the heat pump is running or not
* @param demandWrapper
* holds the thermal demands of the thermal units (house, storage)
* @param currentThermalGridState
* Current state of the thermalGrid
* @return
* next [[HpState]]
*/
Expand All @@ -222,24 +230,32 @@ final case class HpModel(
relevantData: HpRelevantData,
isRunning: Boolean,
demandWrapper: ThermalDemandWrapper,
currentThermalGridState: ThermalGridState,
): HpState = {
val lastStateStorageQDot = lastState.thermalGridState.storageState
val qDotLastHouseState = lastState.thermalGridState.houseState
.map(_.qDot)
.getOrElse(zeroKW)
val currentEnergyOfThermalStorage = currentThermalGridState.storageState
.map(_.storedEnergy)
.getOrElse(zeroKWh)
val pThermalOfStorage = thermalGrid.storage
.map(_.getChargingPower: squants.Power)
.getOrElse(zeroKW)

val (newActivePowerHp, newThermalPowerHp, qDotIntoGrid) = {
if (isRunning)
(pRated, pThermal, pThermal)
else if (lastStateStorageQDot < zeroKW)
(zeroKW, zeroKW, lastStateStorageQDot * (-1))
// If the house has req. demand and storage isn't empty, we can heat the house from storage.
else if (
lastStateStorageQDot == zeroKW && (demandWrapper.houseDemand.hasRequiredDemand || demandWrapper.heatStorageDemand.hasRequiredDemand)
currentEnergyOfThermalStorage > zeroKWh && demandWrapper.houseDemand.hasRequiredDemand
) {
(zeroKW, zeroKW, pThermalOfStorage)
// Edge case when em controlled: If the house was heated last state by Hp and surplus energy is gone now,
// but house didn't reach target temperature yet. House can be heated from storage, if this one is not empty.
} else if (
currentEnergyOfThermalStorage > zeroKWh && demandWrapper.houseDemand.hasAdditionalDemand && qDotLastHouseState > zeroKW
)
(
zeroKW,
zeroKW,
thermalGrid.storage.map(_.getChargingPower: squants.Power).get,
)
(zeroKW, zeroKW, pThermalOfStorage)
else (zeroKW, zeroKW, zeroKW)
}

Expand Down Expand Up @@ -319,7 +335,7 @@ final case class HpModel(

val (
thermalDemandWrapper,
_,
updatedThermalGridState,
) =
thermalGrid.energyDemandAndUpdatedState(
relevantData,
Expand All @@ -331,6 +347,7 @@ final case class HpModel(
relevantData,
turnOn,
thermalDemandWrapper,
updatedThermalGridState,
)

(
Expand Down
11 changes: 3 additions & 8 deletions src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ final case class ThermalGrid(
lastHpState: HpState,
): (ThermalDemandWrapper, ThermalGridState) = {
/* First get the energy demand of the houses but only if inner temperature is below target temperature */

val (houseDemand, updatedHouseState) =
house.zip(lastHpState.thermalGridState.houseState) match {
case Some((thermalHouse, lastHouseState)) =>
Expand All @@ -76,8 +75,7 @@ final case class ThermalGrid(
lastHouseState.qDot,
)
if (
updatedHouseState.innerTemperature < thermalHouse.targetTemperature |
(lastHouseState.qDot > zeroKW && updatedHouseState.innerTemperature < thermalHouse.upperBoundaryTemperature)
updatedHouseState.innerTemperature < thermalHouse.targetTemperature
) {
(
thermalHouse.energyDemand(
Expand All @@ -86,11 +84,9 @@ final case class ThermalGrid(
),
Some(updatedHouseState),
)

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

case None =>
(ThermalEnergyDemand.noDemand, None)
}
Expand Down Expand Up @@ -623,9 +619,8 @@ final case class ThermalGrid(
}

/** Check, if the storage can heat the house. This is only done, if <ul>
* <li>the house has reached it's lower temperature boundary,</li> <li>there
* is no infeed from external and</li> <li>the storage is not empty
* itself</li> </ul>
* <li>there is no infeed from external and</li> <li>the storage is not empty
* itself.</li> </ul>
*
* @param relevantData
* data of heat pump including state of the heat pump.
Expand Down
75 changes: 27 additions & 48 deletions src/main/scala/edu/ie3/simona/model/thermal/ThermalHouse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import edu.ie3.datamodel.models.input.thermal.{
import edu.ie3.simona.model.participant.HpModel.HpRelevantData
import edu.ie3.simona.model.thermal.ThermalGrid.ThermalEnergyDemand
import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{
HouseTargetTemperatureReached,
HouseTemperatureLowerBoundaryReached,
HouseTemperatureTargetOrUpperBoundaryReached,
}
import edu.ie3.simona.model.thermal.ThermalHouse.{
ThermalHouseState,
Expand Down Expand Up @@ -75,61 +75,41 @@ final case class ThermalHouse(
bus,
) {

/** Calculate the energy demand at the instance in question. If the inner
* temperature is at or above the lower boundary temperature, there is no
* demand. If it is below the target temperature, the demand is the energy
* needed to heat up the house to the maximum temperature. The current
* (external) thermal infeed is not accounted for, as we assume, that after
* determining the thermal demand, a change in external infeed will take
* place.
/** Calculate the energy demand at the instance in question by calculating the
* energy needed to reach target temperature from actual inner temperature.
* In case the inner temperature is at or below the lower boundary
* temperature, this energy demand is interpreted as required energy. Else,
* required energy will be zero. In case the inner temperature is not at or
* above the target temperature, the demand is interpreted as additional
* energy. Else, additional energy will be zero. The current (external)
* thermal infeed is not accounted for, as we assume, that after determining
* the thermal demand, a change in external infeed will take place.
*
* @param relevantData
* Data of heat pump including state of the heat pump.
* @param state
* @param currentThermalHouseState
* Most recent state, that is valid for this model.
* @return
* The needed energy in the questioned tick.
*/
def energyDemand(
relevantData: HpRelevantData,
state: ThermalHouseState,
currentThermalHouseState: ThermalHouseState,
): ThermalEnergyDemand = {
/* Calculate the inner temperature of the house, at the questioned instance in time */
val duration = Seconds(relevantData.currentTick - state.tick)
val currentInnerTemp = newInnerTemperature(
state.qDot,
duration,
state.innerTemperature,
relevantData.ambientTemperature,
)
// Since we updated the state before, we can directly take the innerTemperature
val currentInnerTemp = currentThermalHouseState.innerTemperature

/* Determine, which temperature boundary triggers a needed energy to reach the temperature constraints */
val temperatureToTriggerRequiredEnergy =
if (
currentInnerTemp <= state.innerTemperature &&
state.qDot <= zeroKW
) {
// temperature has been decreasing and heat source has been turned off
// => we have reached target temp before and are now targeting lower temp
lowerBoundaryTemperature
} else targetTemperature
val requiredEnergy =
if (
isInnerTemperatureTooLow(
currentInnerTemp,
temperatureToTriggerRequiredEnergy,
)
) energy(targetTemperature, currentInnerTemp)
else
zeroMWh
if (isInnerTemperatureTooLow(currentInnerTemp)) {
energy(targetTemperature, currentInnerTemp)
} else
zeroKWh

val possibleEnergy =
if (!isInnerTemperatureTooHigh(currentInnerTemp)) {
// if upper boundary has not been reached,
// there is an amount of optional energy that could be stored
energy(upperBoundaryTemperature, currentInnerTemp)
} else
zeroMWh
energy(targetTemperature, currentInnerTemp)
} else zeroKWh

ThermalEnergyDemand(requiredEnergy, possibleEnergy)
}

Expand Down Expand Up @@ -160,16 +140,15 @@ final case class ThermalHouse(

/** Check if inner temperature is higher than preferred maximum temperature
* @param innerTemperature
* The inner temperature of the house
* The inner temperature of the house.
* @param boundaryTemperature
* The applied boundary temperature to check against
*
* The applied boundary temperature to check against.
* @return
* True, if inner temperature is too high.
*/
def isInnerTemperatureTooHigh(
innerTemperature: Temperature,
boundaryTemperature: Temperature = upperBoundaryTemperature,
boundaryTemperature: Temperature = targetTemperature,
): Boolean =
innerTemperature > (
boundaryTemperature - temperatureTolerance
Expand Down Expand Up @@ -316,10 +295,10 @@ final case class ThermalHouse(
/* House has more gain than losses */
nextActivation(
tick,
upperBoundaryTemperature,
targetTemperature,
innerTemperature,
resultingQDot,
).map(HouseTemperatureTargetOrUpperBoundaryReached)
).map(HouseTargetTemperatureReached)
} else {
/* House is in perfect balance */
None
Expand Down Expand Up @@ -403,7 +382,7 @@ object ThermalHouse {
final case class HouseTemperatureLowerBoundaryReached(
override val tick: Long
) extends ThermalThreshold
final case class HouseTemperatureTargetOrUpperBoundaryReached(
final case class HouseTargetTemperatureReached(
override val tick: Long
) extends ThermalThreshold
}
Expand Down
Loading
Loading