Skip to content

Commit

Permalink
Merge branch 'df/#878-thermalGridIT' into df/#856-tap-water
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/scala/edu/ie3/simona/model/participant/HpModel.scala
#	src/main/scala/edu/ie3/simona/model/thermal/CylindricalThermalStorage.scala
#	src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
#	src/test/scala/edu/ie3/simona/model/participant/ChpModelSpec.scala
#	src/test/scala/edu/ie3/simona/model/thermal/CylindricalThermalStorageSpec.scala
#	src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala
#	src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala
#	src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala
  • Loading branch information
danielfeismann committed Nov 26, 2024
2 parents c765a82 + 6051f39 commit 4d2213b
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 111 deletions.
20 changes: 17 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,29 @@ jobs:
with:
fetch-depth: 0

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Check Branch
run: |
if [ "${{ github.event_name }}" == "pull_request" ]; then
branchName="${{ github.head_ref }}"
else
branchName="${{ github.ref_name }}"
fi
if [[ "$branchName" == refs/heads/* ]]; then
branchName="${branchName#refs/heads/}"
fi
./gradlew checkBranchName -PbranchName="$branchName"
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Build Project
run: ./gradlew --refresh-dependencies clean assemble spotlessCheck

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add unapply method for ThermalHouseResults [#934](https://github.com/ie3-institute/simona/issues/934)
- 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)
- Integration test for thermal grids [#878](https://github.com/ie3-institute/simona/issues/878)
- Integration test for thermal grids [#878](https://github.com/ie3-institute/simona/issues/878)

Expand Down Expand Up @@ -106,6 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactor `ResultFileHierarchy` [#1031](https://github.com/ie3-institute/simona/issues/1031)
- Removing logs in `logs/simona` [#1017](https://github.com/ie3-institute/simona/issues/1017)
- Fix implausible test cases of HpModelSpec [#1042](https://github.com/ie3-institute/simona/issues/1042)
- Refactoring to only use 'lastHpState' and 'relevantData' for 'ThermalGrid' calculations [#916](https://github.com/ie3-institute/simona/issues/916)

### Fixed
- Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505)
Expand Down
41 changes: 41 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
cff-version: 1.2.0
title: SIMONA
message: "If you use this software, please cite it as below."
type: software
authors:
- family-names: Hiry
given-names: Johannes
orcid: https://orcid.org/0000-0002-1447-0607
- family-names: Kittl
given-names: Chris
orcid: https://orcid.org/0000-0002-1187-0568
- family-names: Sen Sarma
given-names: Debopama
orcid: https://orcid.org/0000-0003-3311-3020
- family-names: Oberließen
given-names: Thomas
orcid: https://orcid.org/0000-0001-5805-5408
- family-names: Peter
given-names: Sebastian
orcid: https://orcid.org/0000-0001-6311-6113
- family-names: Feismann
given-names: Daniel
orcid: https://orcid.org/0000-0002-3531-9025
- family-names: Bao
given-names: Johannes
orcid: https://orcid.org/0009-0008-3641-6469
- family-names: Hohmann
given-names: Julian
- family-names: Staudt
given-names: Marius
repository-code: https://github.com/ie3-institute/simona
url: https://simona.ie3.e-technik.tu-dortmund.de
repository-artifact: https://central.sonatype.com/artifact/com.github.ie3-institute/simona
keywords:
- agent-based
- discrete-event simulation
- powerflow
- electricity distribution grid
license: BSD-3-Clause
version: 3.0.0
date-released: 2023-08-07
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ apply from: scriptsLocation + 'scoverage.gradle' // scoverage scala code coverag
apply from: scriptsLocation + 'deploy.gradle'
apply from: scriptsLocation + 'semVer.gradle'
apply from: scriptsLocation + 'mavenCentralPublish.gradle'
apply from: scriptsLocation + 'branchName.gradle'

configurations {
scalaCompilerPlugin
Expand Down
26 changes: 26 additions & 0 deletions gradle/scripts/branchName.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
tasks.register('checkBranchName') {
doLast {
if (!project.hasProperty('branchName')) {
throw new GradleException("Error: Missing required property 'branchName'.")
}

def branchName = project.property('branchName')

def patterns = [
~/^(developer|develop|dev)$/,
~/.*rel\/.*/,
~/^dependabot\/.*$/,
~/.*hotfix\/\pL{2}\/#\d+.*/,
~/.*main/,
~/^[a-z]{2}\/#[0-9]+(?:-.+)?$/
]

def isValid = patterns.any { pattern -> branchName ==~ pattern }

if (!isValid) {
throw new GradleException("Error: Check Branch name format (e.g., ps/#1337-FeatureName).")
}

println "Branch name is $branchName"
}
}
58 changes: 15 additions & 43 deletions src/main/scala/edu/ie3/simona/model/participant/HpModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ final case class HpModel(
tick: Long,
currentState: HpState,
data: HpRelevantData,
): Power = currentState.qDot
): Power =
// only if Hp is running qDot will be from Hp, else qDot results from other source, e.g. some storage
if (currentState.isRunning)
currentState.qDot
else
zeroKW

/** Given a [[HpRelevantData]] object and the last [[HpState]], this function
* calculates the heat pump's next state to get the actual active power of
Expand All @@ -134,12 +139,8 @@ final case class HpModel(
// Use lastHpState and relevantData to update state of thermalGrid to the current tick
val (thermalDemandWrapper, currentThermalGridState) =
thermalGrid.energyDemandAndUpdatedState(
relevantData.currentTick,
lastHpState.ambientTemperature.getOrElse(
relevantData.ambientTemperature
),
relevantData.ambientTemperature,
lastHpState.thermalGridState,
relevantData,
lastHpState,
relevantData.simulationStart,
relevantData.houseInhabitants,
)
Expand Down Expand Up @@ -190,10 +191,8 @@ final case class HpModel(
val demandDomesticHotWaterStorage =
thermalDemands.domesticHotWaterStorageDemand

val noThermalStorageOrThermalStorageIsEmpty = determineThermalStorageStatus(
lastState,
currentThermalGridState,
)
val noThermalStorageOrThermalStorageIsEmpty =
currentThermalGridState.isThermalStorageEmpty

val turnHpOn =
(demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) ||
Expand All @@ -217,31 +216,6 @@ final case class HpModel(
)
}

/** Determines the actual status of heat pump and the status of the heat
* storage if there is one
*
* @param hpState
* Current state of the heat pump
* @param updatedGridState
* The updated state of the [[ThermalGrid]]
* @return
* boolean which is true, if there is no thermalStorage, or it's empty.
*/

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

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.
Expand Down Expand Up @@ -339,7 +313,7 @@ final case class HpModel(
* operating state and give back the next tick in which something will
* change.
*
* @param data
* @param relevantData
* Relevant data for model calculation
* @param lastState
* The last known model state
Expand All @@ -350,7 +324,7 @@ final case class HpModel(
* options will change next
*/
override def handleControlledPowerChange(
data: HpRelevantData,
relevantData: HpRelevantData,
lastState: HpState,
setPower: Power,
): (HpState, FlexChangeIndicator) = {
Expand All @@ -362,17 +336,15 @@ final case class HpModel(
_,
) =
thermalGrid.energyDemandAndUpdatedState(
data.currentTick,
lastState.ambientTemperature.getOrElse(data.ambientTemperature),
data.ambientTemperature,
lastState.thermalGridState,
relevantData,
lastState,
data.simulationStart,
data.houseInhabitants,
)

val updatedHpState = calcState(
lastState,
data,
relevantData,
turnOn,
thermalDemands,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ object CylindricalThermalStorage extends ThermalStorageCalculations {
input: CylindricalStorageInput,
initialStoredEnergy: Energy = zeroKWh,
): CylindricalThermalStorage = {

val maxEnergyThreshold = volumeToEnergy(
CubicMeters(
input.getStorageVolumeLvl.to(Units.CUBIC_METRE).getValue.doubleValue
Expand Down
71 changes: 38 additions & 33 deletions src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import edu.ie3.datamodel.models.result.thermal.{
}
import edu.ie3.simona.exceptions.InvalidParameterException
import edu.ie3.simona.exceptions.agent.InconsistentStateException
import edu.ie3.simona.model.participant.HpModel.HpRelevantData
import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState}
import edu.ie3.simona.model.thermal.ThermalGrid.{
ThermalDemandWrapper,
ThermalEnergyDemand,
Expand Down Expand Up @@ -55,51 +55,40 @@ final case class ThermalGrid(
/** Determine the energy demand of the total grid at the given instance in
* time and returns it including the updatedState
*
* @param tick
* Questioned instance in time
* @param lastAmbientTemperature
* Ambient temperature until this tick
* @param ambientTemperature
* Current ambient temperature in the instance in question
* @param state
* Currently applicable state of the thermal grid
* @param simulationStartTime
* simulationStartDate as ZonedDateTime
* @param houseInhabitants
* number of people living in the building
* @param lastHpState
* Last state of the heat pump
* @param relevantData
* data of heat pump including
* @return
* The total energy demand of the house and the storage and an updated
* [[ThermalGridState]]
*/
def energyDemandAndUpdatedState(
tick: Long,
// FIXME this is also in state
lastAmbientTemperature: Temperature,
ambientTemperature: Temperature,
state: ThermalGridState,
simulationStartTime: ZonedDateTime,
houseInhabitants: Double,
relevantData: HpRelevantData,
lastHpState: HpState,
): (ThermalDemandWrapper, ThermalGridState) = {
/* First get the energy demand of the houses but only if inner temperature is below target temperature */

val (houseDemand, updatedHouseState, demandHotDomesticWater) =
house.zip(state.houseState) match {
case Some((thermalHouse, lastHouseState)) => {
val (updatedHouseState, updatedStorageState) =
val (houseDemand, updatedHouseState) =
house.zip(lastHpState.thermalGridState.houseState) match {
case Some((thermalHouse, lastHouseState)) =>
val (updatedHouseState, _) =
thermalHouse.determineState(
tick,
relevantData.currentTick,
lastHouseState,
lastAmbientTemperature,
ambientTemperature,
lastHpState.ambientTemperature.getOrElse(
relevantData.ambientTemperature
),
relevantData.ambientTemperature,
lastHouseState.qDot,
)
val (heatDemand, newHouseState) = if (
updatedHouseState.innerTemperature < thermalHouse.targetTemperature | (lastHouseState.qDot > zeroKW && updatedHouseState.innerTemperature < thermalHouse.upperBoundaryTemperature)
) {
(
thermalHouse.energyDemandHeating(
tick,
ambientTemperature,
relevantData.currentTick,
relevantData.ambientTemperature,
updatedHouseState,
),
Some(updatedHouseState),
Expand Down Expand Up @@ -127,10 +116,10 @@ final case class ThermalGrid(
val (storageDemand, updatedStorageState) = {

heatStorage
.zip(state.storageState)
.zip(lastHpState.thermalGridState.storageState)
.map { case (storage, state) =>
val (updatedStorageState, _) =
storage.updateState(tick, state.qDot, state)
storage.updateState(relevantData.currentTick, state.qDot, state)
val storedEnergy = updatedStorageState.storedEnergy
val soc = storedEnergy / storage.getMaxEnergyThreshold
val storageRequired = {
Expand Down Expand Up @@ -1484,8 +1473,24 @@ object ThermalGrid {
final case class ThermalGridState(
houseState: Option[ThermalHouseState],
storageState: Option[ThermalStorageState],
domesticHotWaterStorageState: Option[ThermalStorageState],
)
domesticHotWaterStorageState: 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(
Expand Down
Loading

0 comments on commit 4d2213b

Please sign in to comment.