Skip to content

Commit

Permalink
Merge branch 'refs/heads/df/#827_fix_hp_storage_refill' into df/#878-…
Browse files Browse the repository at this point in the history
…thermalGridIT

# Conflicts:
#	CHANGELOG.md
#	src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala
  • Loading branch information
danielfeismann committed Aug 5, 2024
2 parents a0ecdb6 + dc8ce19 commit 79a92c3
Show file tree
Hide file tree
Showing 11 changed files with 466 additions and 184 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed result output for thermal houses and cylindrical storages [#844](https://github.com/ie3-institute/simona/issues/844)
- Fixed FixedFeedModelSpec [#861](https://github.com/ie3-institute/simona/issues/861)
- Fixing duration calculation in result events [#801](https://github.com/ie3-institute/simona/issues/801)
- Fixed Hp results leading to overheating house and other effects [#827](https://github.com/ie3-institute/simona/issues/827)
- Fixed thermal storage getting recharged when empty [#827](https://github.com/ie3-institute/simona/issues/827)

## [3.0.0] - 2023-08-07

Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/edu/ie3/simona/agent/ValueStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import scala.collection.SortedMap
*/
final case class ValueStore[+D](
maxTickSpan: Long,
private val store: SortedMap[Long, D] = SortedMap.empty[Long, D],
store: SortedMap[Long, D] = SortedMap.empty[Long, D],
) {

/** Determine the lastly known data tick, if available. Includes the given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,26 @@ trait HpAgentFundamentals
currentTick,
updatedState,
)
val updatedBaseStateData =
baseStateData.copy(stateDataStore = updatedStateDataStore)

val updatedBaseStateData = {
updatedState.maybeThermalThreshold match {
case Some(nextThreshold)
if baseStateData.foreseenDataTicks.headOption.exists {
case (_, Some(tick)) => nextThreshold.tick < tick
case _ => false
} =>
baseStateData.copy(
stateDataStore = updatedStateDataStore,
additionalActivationTicks =
baseStateData.additionalActivationTicks + nextThreshold.tick,
)
case _ =>
baseStateData.copy(
stateDataStore = updatedStateDataStore
)
}
}

updateValueStoresInformListenersAndGoToIdleWithUpdatedBaseStateData(
scheduler,
updatedBaseStateData,
Expand Down Expand Up @@ -385,25 +403,44 @@ trait HpAgentFundamentals
tick: Long,
): HpRelevantData = {
/* extract weather data from secondary data, which should have been requested and received before */
val weatherData =
baseStateData.receivedSecondaryDataStore
val weatherData = {
val currentWeatherData = baseStateData.receivedSecondaryDataStore
.last(tick)
.flatMap { case (receivedTick, receivedValues) =>
if (receivedTick != tick)
log.debug(
s"The model ${baseStateData.model.getUuid} needs to do calculations with values received " +
s"in tick $receivedTick, as no weather data has been received in tick $tick."
)
receivedValues.collectFirst {
// filter secondary data for weather data
case (_, data: WeatherData) => data
if (receivedTick == tick) {
receivedValues.collectFirst { case (_, data: WeatherData) =>
data
}
} else None
}

// If current weather data is not found, fallback to the latest entry where the map is not empty
currentWeatherData
.orElse {
val latestEntry =
baseStateData.receivedSecondaryDataStore.store.toSeq.findLast {
case (_, receivedValues) => receivedValues.nonEmpty
}

latestEntry.flatMap { case (receivedTick, receivedValues) =>
if (tick - receivedTick > 3600) {
log.warning(
s"The model ${baseStateData.model.getUuid} is using weather data from tick $receivedTick, " +
s"but there is a discrepancy of ${tick - receivedTick} seconds compared to the current tick $tick."
)
}

receivedValues.collectFirst { case (_, data: WeatherData) =>
data
}
}
}
.getOrElse(
.getOrElse {
throw new InconsistentStateException(
s"The model ${baseStateData.model} was not provided with needed weather data."
)
)
}
}

HpRelevantData(
tick,
Expand Down
73 changes: 58 additions & 15 deletions src/main/scala/edu/ie3/simona/model/participant/HpModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ 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
Expand Down Expand Up @@ -127,8 +130,9 @@ final case class HpModel(
state: HpState,
relevantData: HpRelevantData,
): HpState = {
val turnOn = operatesInNextState(state, relevantData)
calcState(state, relevantData, turnOn)
val (turnOn, houseDemand, storageDemand) =
operatesInNextState(state, relevantData)
calcState(state, relevantData, turnOn, houseDemand, storageDemand)
}

/** Depending on the input, this function decides whether the heat pump will
Expand All @@ -142,18 +146,31 @@ 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 and if house and
* storage have some demand
*/
private def operatesInNextState(
state: HpState,
relevantData: HpRelevantData,
): Boolean = {
val demand = thermalGrid.energyDemand(
): (Boolean, ThermalEnergyDemand, ThermalEnergyDemand) = {
val (demandHouse, demandStorage) = thermalGrid.energyDemand(
relevantData.currentTick,
relevantData.ambientTemperature,
state.thermalGridState,
)
demand.hasRequiredDemand || (state.isRunning && demand.hasAdditionalDemand)

val storedEnergy = state.thermalGridState.storageState
.map(storageState => storageState.storedEnergy)
.getOrElse(zeroKWH)

val turnHpOn =
(demandHouse.hasRequiredDemand && demandHouse.required > storedEnergy) || demandStorage.hasRequiredDemand || (state.isRunning && demandHouse.hasAdditionalDemand) || (state.isRunning && demandStorage.hasAdditionalDemand)

(
turnHpOn,
demandHouse,
demandStorage,
)
}

/** Calculate state depending on whether heat pump is needed or not. Also
Expand All @@ -166,13 +183,19 @@ final case class HpModel(
* data of heat pump including state of the heat pump
* @param isRunning
* determines whether the heat pump is running or not
* @param houseDemand
* determines if the thermal house has heat demand
* @param storageDemand
* determines if the thermal storage has heat demand
* @return
* next [[HpState]]
*/
private def calcState(
state: HpState,
relevantData: HpRelevantData,
isRunning: Boolean,
houseDemand: ThermalEnergyDemand,
storageDemand: ThermalEnergyDemand,
): HpState = {
val (newActivePower, newThermalPower) =
if (isRunning)
Expand All @@ -186,6 +209,8 @@ final case class HpModel(
state.thermalGridState,
state.ambientTemperature.getOrElse(relevantData.ambientTemperature),
newThermalPower,
houseDemand,
storageDemand,
)

HpState(
Expand All @@ -207,14 +232,17 @@ final case class HpModel(
val updatedState = determineState(lastState, data)

/* Determine the options we have */
val thermalEnergyDemand = thermalGrid.energyDemand(
data.currentTick,
data.ambientTemperature,
lastState.thermalGridState,
)
val (thermalEnergyDemandHouse, thermalEnergyDemandStorge) =
thermalGrid.energyDemand(
data.currentTick,
data.ambientTemperature,
lastState.thermalGridState,
)
val canOperate =
thermalEnergyDemand.hasRequiredDemand || thermalEnergyDemand.hasAdditionalDemand
val canBeOutOfOperation = !thermalEnergyDemand.hasRequiredDemand
thermalEnergyDemandHouse.hasRequiredDemand || thermalEnergyDemandHouse.hasAdditionalDemand ||
thermalEnergyDemandStorge.hasRequiredDemand || thermalEnergyDemandStorge.hasAdditionalDemand
val canBeOutOfOperation =
!thermalEnergyDemandHouse.hasRequiredDemand && !thermalEnergyDemandStorge.hasRequiredDemand

val lowerBoundary =
if (canBeOutOfOperation)
Expand Down Expand Up @@ -259,7 +287,22 @@ 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) =
thermalGrid.energyDemand(
data.currentTick,
data.ambientTemperature,
lastState.thermalGridState,
)

val updatedState =
calcState(
lastState,
data,
turnOn,
thermalEnergyDemandHouse,
thermalEnergyDemandStorage,
)

(
updatedState,
Expand Down
Loading

0 comments on commit 79a92c3

Please sign in to comment.