diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d58167b6c..d11126c218 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a0624832..84f8eae170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add some quote to 'printGoodbye' [#997](https://github.com/ie3-institute/simona/issues/997) - 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) ### Changed - Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435) @@ -103,6 +106,8 @@ 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) +- Refactor thermal calcRelevantData [#1051](https://github.com/ie3-institute/simona/issues/1051) ### Fixed - Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505) @@ -142,6 +147,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix some minor issues and findings from inspections [#1019](https://github.com/ie3-institute/simona/issues/1019) - Fix initialisation freezing on empty primary data [#981](https://github.com/ie3-institute/simona/issues/981) - Shallow fetch in CI [#1041](https://github.com/ie3-institute/simona/issues/1041) +- Correct wrong use of term "wall clock time" [#727](https://github.com/ie3-institute/simona/issues/727) ## [3.0.0] - 2023-08-07 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..0008300e9f --- /dev/null +++ b/CITATION.cff @@ -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 diff --git a/build.gradle b/build.gradle index a69e01e63d..3125cfe07e 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/docs/readthedocs/config.md b/docs/readthedocs/config.md index 45deee0778..f6e968659a 100644 --- a/docs/readthedocs/config.md +++ b/docs/readthedocs/config.md @@ -12,15 +12,13 @@ To create the output directory name, the name of the simulation is used as a str `simona.simulationName = "vn_simona"` ### Time parameters -Starting date and time of the simulation - - Format: "YYYY-MM-DD HH:MM:SS" +Starting date and time of the simulation in ISO-8601 date and time format with offset - `simona.time.startDateTime = "2011-01-01 00:00:00"` + `simona.time.startDateTime = "2011-01-01T00:00:00Z"` -Ending date and time of the simulation - - Format: "YYYY-MM-DD HH:MM:SS" +Ending date and time of the simulation in ISO-8601 date and time format with offset - `simona.time.endDateTime = "2011-01-01 02:00:00"` + `simona.time.endDateTime = "2011-01-01T02:00:00Z"` The preset ReadyCheckWindow should be maintained @@ -39,7 +37,9 @@ Setting of the data source `simona.input.grid.datasource.id = "csv"` -Specify the folder path containing the csv data of the grid components and the csv separator (e.g. "," or ";") +Specify the folder path containing the csv data of the grid components and the csv separator (e.g. "," or ";"). +The directory structure is determined by the boolean `isHierarchic`. +If files are placed within [a specific set of subdirectories](https://powersystemdatamodel.readthedocs.io/en/latest/io/csvfiles.html#default-directory-hierarchy), `isHierarchic: true` needs to be set. ``` simona.input.primary.csvParams = { diff --git a/gradle/scripts/branchName.gradle b/gradle/scripts/branchName.gradle new file mode 100644 index 0000000000..b1357b16f1 --- /dev/null +++ b/gradle/scripts/branchName.gradle @@ -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" + } +} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala index ad0d6c3b07..8d615ec3af 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala @@ -455,9 +455,9 @@ class GridAgentController( * @param primaryServiceProxy * Reference to the primary data service proxy * @param simulationStartDate - * First wall clock time in simulation + * The simulation time at which the simulation starts * @param simulationEndDate - * Last wall clock time in simulation + * The simulation time at which the simulation ends * @param resolution * Frequency of power flow calculations * @param requestVoltageDeviationThreshold @@ -512,9 +512,9 @@ class GridAgentController( * @param primaryServiceProxy * Reference to the primary data service proxy * @param simulationStartDate - * First wall clock time in simulation + * The simulation time at which the simulation starts * @param simulationEndDate - * Last wall clock time in simulation + * The simulation time at which the simulation ends * @param resolution * Frequency of power flow calculations * @param requestVoltageDeviationThreshold @@ -571,9 +571,9 @@ class GridAgentController( * @param weatherService * Reference to the weather service actor * @param simulationStartDate - * First wall clock time in simulation + * The simulation time at which the simulation starts * @param simulationEndDate - * Last wall clock time in simulation + * The simulation time at which the simulation ends * @param resolution * Frequency of power flow calculations * @param requestVoltageDeviationThreshold @@ -631,9 +631,9 @@ class GridAgentController( * @param evMovementsService * Reference to the ev movements service actor * @param simulationStartDate - * First wall clock time in simulation + * The simulation time at which the simulation starts * @param simulationEndDate - * Last wall clock time in simulation + * The simulation time at which the simulation ends * @param resolution * Frequency of power flow calculations * @param requestVoltageDeviationThreshold @@ -749,9 +749,9 @@ class GridAgentController( * @param weatherService * Reference to the weather service actor * @param simulationStartDate - * First wall clock time in simulation + * The simulation time at which the simulation starts * @param simulationEndDate - * Last wall clock time in simulation + * The simulation time at which the simulation ends * @param resolution * Frequency of power flow calculations * @param requestVoltageDeviationThreshold @@ -807,9 +807,9 @@ class GridAgentController( * @param primaryServiceProxy * Reference to the primary data service proxy * @param simulationStartDate - * First wall clock time in simulation + * The simulation time at which the simulation starts * @param simulationEndDate - * Last wall clock time in simulation + * The simulation time at which the simulation ends * @param resolution * Frequency of power flow calculations * @param requestVoltageDeviationThreshold diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala index 59f0248f81..6b8b6520ff 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala @@ -617,7 +617,7 @@ object GridResultsSupport { /** Partial result for the port at the high voltage side * * @param time - * Wall clock time, the result does belong to + * Simulation time of the result * @param input * Unique identifier of the input model * @param currentMagnitude @@ -638,7 +638,7 @@ object GridResultsSupport { /** Partial result for the port at the medium voltage side * * @param time - * Wall clock time, the result does belong to + * Simulation time of the result * @param input * Unique identifier of the input model * @param currentMagnitude @@ -656,7 +656,7 @@ object GridResultsSupport { /** Partial result for the port at the low voltage side * * @param time - * Wall clock time, the result does belong to + * Simulation time of the result * @param input * Unique identifier of the input model * @param currentMagnitude diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala index 12b0e9657c..a505696943 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -214,9 +214,9 @@ protected trait ParticipantAgentFundamentals[ * @param modelConfig * Configuration for the model * @param simulationStartDate - * Wall clock time of first instant in simulation + * Simulation time of first instant in simulation * @param simulationEndDate - * Wall clock time of last instant in simulation + * Simulation time of last instant in simulation * @return */ def buildModel( diff --git a/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala index 6a9d80cf58..534126b986 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/hp/HpAgentFundamentals.scala @@ -440,9 +440,9 @@ trait HpAgentFundamentals * @param modelConfig * Configuration for the model * @param simulationStartDate - * Wall clock time of first instant in simulation + * Simulation time of first instant in simulation * @param simulationEndDate - * Wall clock time of last instant in simulation + * Simulation time of last instant in simulation * @return */ override def buildModel( diff --git a/src/main/scala/edu/ie3/simona/agent/participant/statedata/BaseStateData.scala b/src/main/scala/edu/ie3/simona/agent/participant/statedata/BaseStateData.scala index b6ebd99841..04650f4763 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/statedata/BaseStateData.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/statedata/BaseStateData.scala @@ -43,7 +43,7 @@ trait BaseStateData[+PD <: PrimaryDataWithApparentPower[PD]] */ val startDate: ZonedDateTime - /** The wall clock date, at which the simulation ends + /** The simulation time at which the simulation ends */ val endDate: ZonedDateTime @@ -132,7 +132,7 @@ object BaseStateData { * @param startDate * The date, that fits the tick 0 * @param endDate - * The wall clock date, at which the simulation ends + * The simulation time at which the simulation ends * @param outputConfig * Determines the output behaviour of this model * @param additionalActivationTicks @@ -179,7 +179,7 @@ object BaseStateData { * @param startDate * The date, that fits the tick 0 * @param endDate - * The wall clock date, at which the simulation ends + * The simulation time at which the simulation ends * @param model * Physical model of the load * @param services diff --git a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala index 98d18d9ce3..25d8e1d92a 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala @@ -12,7 +12,7 @@ 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.{ - ThermalEnergyDemand, + ThermalDemandWrapper, ThermalGridState, } import edu.ie3.simona.model.thermal.{ThermalGrid, ThermalThreshold} @@ -22,8 +22,8 @@ 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.{ApparentPower, Kilovoltamperes} -import squants.energy.{KilowattHours, Kilowatts} -import squants.{Energy, Power, Temperature} +import squants.energy.Kilowatts +import squants.{Power, Temperature} import java.time.ZonedDateTime import java.util.UUID @@ -132,14 +132,10 @@ final case class HpModel( ): (Boolean, Boolean, HpState) = { // Use lastHpState and relevantData to update state of thermalGrid to the current tick - val (demandHouse, demandThermalStorage, currentThermalGridState) = + val (thermalDemandWrapper, currentThermalGridState) = thermalGrid.energyDemandAndUpdatedState( - relevantData.currentTick, - lastHpState.ambientTemperature.getOrElse( - relevantData.ambientTemperature - ), - relevantData.ambientTemperature, - lastHpState.thermalGridState, + relevantData, + lastHpState, ) // Determining the operation point and limitations at this tick @@ -148,8 +144,7 @@ final case class HpModel( lastHpState, currentThermalGridState, relevantData, - demandHouse, - demandThermalStorage, + thermalDemandWrapper, ) // Updating the HpState @@ -170,10 +165,8 @@ final case class HpModel( * 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 + * @param thermalDemands + * ThermalEnergyDemand of the house and the thermal storage * @return * boolean defining if heat pump runs in next time step, if it can be in * operation and can be out of operation @@ -182,23 +175,19 @@ final case class HpModel( lastState: HpState, currentThermalGridState: ThermalGridState, relevantData: HpRelevantData, - demandHouse: ThermalEnergyDemand, - demandThermalStorage: ThermalEnergyDemand, + thermalDemands: ThermalDemandWrapper, ): (Boolean, Boolean, Boolean) = { - val ( - houseHasDemand, - heatStorageHasDemand, - noThermalStorageOrThermalStorageIsEmpty, - ) = determineDemandBooleans( - lastState, - currentThermalGridState, - demandHouse, - demandThermalStorage, - ) + val demandHouse = thermalDemands.houseDemand + val demandThermalStorage = thermalDemands.heatStorageDemand + val noThermalStorageOrThermalStorageIsEmpty = + currentThermalGridState.isThermalStorageEmpty - val turnHpOn: Boolean = - houseHasDemand || heatStorageHasDemand + val turnHpOn = + (demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || + (demandHouse.hasAdditionalDemand && lastState.isRunning) || + demandThermalStorage.hasRequiredDemand || + (demandThermalStorage.hasAdditionalDemand && lastState.isRunning) val canOperate = demandHouse.hasRequiredDemand || demandHouse.hasAdditionalDemand || @@ -206,45 +195,11 @@ final case class HpModel( 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) + ( + turnHpOn, + canOperate, + canBeOutOfOperation, + ) } /** Calculate state depending on whether heat pump is needed or not. Also @@ -279,10 +234,9 @@ final case class HpModel( /* Push thermal energy to the thermal grid and get its updated state in return */ val (thermalGridState, maybeThreshold) = thermalGrid.updateState( - relevantData.currentTick, + relevantData, lastState.thermalGridState, lastState.ambientTemperature.getOrElse(relevantData.ambientTemperature), - relevantData.ambientTemperature, newThermalPower, ) @@ -346,7 +300,7 @@ final case class HpModel( lastState: HpState, setPower: Power, ): (HpState, FlexChangeIndicator) = { - /* If the setpoint value is above 50 % of the electrical power, turn on the heat pump otherwise turn it off */ + /* If the set point value is above 50 % of the electrical power, turn on the heat pump otherwise turn it off */ val turnOn = setPower > (sRated.toActivePower(cosPhiRated) * 0.5) val updatedHpState = calcState( diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala index 50f56e1849..4a118c3481 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -14,7 +14,9 @@ import edu.ie3.datamodel.models.result.thermal.{ ThermalHouseResult, } import edu.ie3.simona.exceptions.agent.InconsistentStateException +import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState} import edu.ie3.simona.model.thermal.ThermalGrid.{ + ThermalDemandWrapper, ThermalEnergyDemand, ThermalGridState, } @@ -23,7 +25,7 @@ import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.DefaultQuantities._ -import squants.energy.Kilowatts +import squants.energy.{KilowattHours, Kilowatts} import squants.{Energy, Power, Temperature} import java.time.ZonedDateTime @@ -45,36 +47,30 @@ 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 - * Ambient temperature in the instance in question - * @param state - * Currently applicable state of the thermal grid + * @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, - ): (ThermalEnergyDemand, ThermalEnergyDemand, ThermalGridState) = { + 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) = - house.zip(state.houseState) match { + house.zip(lastHpState.thermalGridState.houseState) match { case Some((thermalHouse, lastHouseState)) => val (updatedHouseState, _) = thermalHouse.determineState( - tick, + relevantData, lastHouseState, - lastAmbientTemperature, - ambientTemperature, + lastHpState.ambientTemperature.getOrElse( + relevantData.ambientTemperature + ), lastHouseState.qDot, ) if ( @@ -83,8 +79,7 @@ final case class ThermalGrid( ) { ( thermalHouse.energyDemand( - tick, - ambientTemperature, + relevantData, updatedHouseState, ), Some(updatedHouseState), @@ -102,10 +97,10 @@ final case class ThermalGrid( val (storageDemand, updatedStorageState) = { storage - .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 = { @@ -134,13 +129,15 @@ final case class ThermalGrid( } ( - ThermalEnergyDemand( - houseDemand.required, - houseDemand.possible, - ), - ThermalEnergyDemand( - storageDemand.required, - storageDemand.possible, + ThermalDemandWrapper( + ThermalEnergyDemand( + houseDemand.required, + houseDemand.possible, + ), + ThermalEnergyDemand( + storageDemand.required, + storageDemand.possible, + ), ), ThermalGridState(updatedHouseState, updatedStorageState), ) @@ -148,32 +145,28 @@ final case class ThermalGrid( /** Update the current state of the grid * - * @param tick - * Instance in time + * @param relevantData + * data of heat pump including state of the heat pump * @param state * Currently applicable state * @param lastAmbientTemperature * Ambient temperature valid up until (not including) the current tick - * @param ambientTemperature - * Current ambient temperature * @param qDot * Thermal energy balance * @return * The updated state of the grid */ def updateState( - tick: Long, + relevantData: HpRelevantData, state: ThermalGridState, lastAmbientTemperature: Temperature, - ambientTemperature: Temperature, qDot: Power, ): (ThermalGridState, Option[ThermalThreshold]) = if (qDot > zeroKW) - handleInfeed(tick, lastAmbientTemperature, ambientTemperature, state, qDot) + handleInfeed(relevantData, lastAmbientTemperature, state, qDot) else handleConsumption( - tick, + relevantData, lastAmbientTemperature, - ambientTemperature, state, qDot, ) @@ -181,12 +174,10 @@ final case class ThermalGrid( /** Handles the case, when a grid has infeed. First, heat up all the houses to * their maximum temperature, then fill up the storages * - * @param tick - * Current tick + * @param relevantData + * data of heat pump including state of the heat pump * @param lastAmbientTemperature * Ambient temperature valid up until (not including) the current tick - * @param ambientTemperature - * Current ambient temperature * @param state * Current state of the houses * @param qDot @@ -195,9 +186,8 @@ final case class ThermalGrid( * Updated thermal grid state */ private def handleInfeed( - tick: Long, + relevantData: HpRelevantData, lastAmbientTemperature: Temperature, - ambientTemperature: Temperature, state: ThermalGridState, qDot: Power, ): (ThermalGridState, Option[ThermalThreshold]) = @@ -210,7 +200,7 @@ final case class ThermalGrid( Some( thermalStorage .updateState( - tick, + relevantData.currentTick, zeroKW, storageState, ) @@ -221,10 +211,9 @@ final case class ThermalGrid( val (updatedHouseState, maybeHouseThreshold) = thermalHouse.determineState( - tick, + relevantData, lastHouseState, lastAmbientTemperature, - ambientTemperature, qDot, ) @@ -236,16 +225,19 @@ final case class ThermalGrid( /* The house is already heated up fully, set back the infeed and put it into storage, if available */ val (fullHouseState, maybeFullHouseThreshold) = thermalHouse.determineState( - tick, + relevantData, lastHouseState, lastAmbientTemperature, - ambientTemperature, zeroKW, ) storage.zip(updatedStorageState) match { case Some((thermalStorage, storageState)) => val (updatedStorageState, maybeStorageThreshold) = - thermalStorage.updateState(tick, qDot, storageState) + thermalStorage.updateState( + relevantData.currentTick, + qDot, + storageState, + ) /* Both house and storage are updated. Determine what reaches the next threshold */ val nextThreshold = determineMostRecentThreshold( @@ -279,7 +271,11 @@ final case class ThermalGrid( storage.zip(state.storageState) match { case Some((thermalStorage, storageState)) => val (updatedStorageState, maybeStorageThreshold) = - thermalStorage.updateState(tick, qDot, storageState) + thermalStorage.updateState( + relevantData.currentTick, + qDot, + storageState, + ) ( state.copy(storageState = Some(updatedStorageState)), maybeStorageThreshold, @@ -308,12 +304,10 @@ final case class ThermalGrid( /** Handle consumption (or no infeed) from thermal grid * - * @param tick - * Current tick + * @param relevantData + * data of heat pump including state of the heat pump * @param lastAmbientTemperature * Ambient temperature valid up until (not including) the current tick - * @param ambientTemperature - * Current ambient temperature * @param state * Current state of the houses * @param qDot @@ -322,9 +316,8 @@ final case class ThermalGrid( * Updated thermal grid state */ private def handleConsumption( - tick: Long, + relevantData: HpRelevantData, lastAmbientTemperature: Temperature, - ambientTemperature: Temperature, state: ThermalGridState, qDot: Power, ): (ThermalGridState, Option[ThermalThreshold]) = { @@ -332,10 +325,9 @@ final case class ThermalGrid( val maybeUpdatedHouseState = house.zip(state.houseState).map { case (house, houseState) => house.determineState( - tick, + relevantData, houseState, lastAmbientTemperature, - ambientTemperature, zeroMW, ) } @@ -343,18 +335,17 @@ final case class ThermalGrid( /* Update the state of the storage */ val maybeUpdatedStorageState = storage.zip(state.storageState).map { case (storage, storageState) => - storage.updateState(tick, qDot, storageState) + storage.updateState(relevantData.currentTick, qDot, storageState) } val (revisedHouseState, revisedStorageState) = reviseInfeedFromStorage( - tick, + relevantData, maybeUpdatedHouseState, maybeUpdatedStorageState, state.houseState, state.storageState, lastAmbientTemperature, - ambientTemperature, qDot, ) @@ -377,8 +368,8 @@ final case class ThermalGrid( * is no infeed from external and
  • the storage is not empty * itself
  • * - * @param tick - * The current tick + * @param relevantData + * data of heat pump including state of the heat pump * @param maybeHouseState * Optional thermal house state * @param maybeStorageState @@ -389,15 +380,13 @@ final case class ThermalGrid( * Previous thermal storage state before a first update was performed * @param lastAmbientTemperature * Ambient temperature valid up until (not including) the current tick - * @param ambientTemperature - * Current ambient temperature * @param qDot * Thermal influx * @return * Options to revised thermal house and storage state */ def reviseInfeedFromStorage( - tick: Long, + relevantData: HpRelevantData, maybeHouseState: Option[(ThermalHouseState, Option[ThermalThreshold])], maybeStorageState: Option[ (ThermalStorageState, Option[ThermalThreshold]) @@ -405,7 +394,6 @@ final case class ThermalGrid( formerHouseState: Option[ThermalHouseState], formerStorageState: Option[ThermalStorageState], lastAmbientTemperature: Temperature, - ambientTemperature: Temperature, qDot: Power, ): ( Option[(ThermalHouseState, Option[ThermalThreshold])], @@ -423,7 +411,7 @@ final case class ThermalGrid( ) && !thermalStorage.isEmpty(storageState.storedEnergy) => /* Storage is meant to heat the house only, if there is no infeed from external (+/- 10 W) and the house is cold */ val revisedStorageState = thermalStorage.updateState( - tick, + relevantData.currentTick, thermalStorage.getChargingPower * -1, formerStorageState.getOrElse( throw new InconsistentStateException( @@ -432,14 +420,13 @@ final case class ThermalGrid( ), ) val revisedHouseState = thermalHouse.determineState( - tick, + relevantData, formerHouseState.getOrElse( throw new InconsistentStateException( "Impossible to find no house state" ) ), lastAmbientTemperature, - ambientTemperature, thermalStorage.getChargingPower, ) (Some(revisedHouseState), Some(revisedStorageState)) @@ -532,7 +519,23 @@ object ThermalGrid { final case class ThermalGridState( houseState: Option[ThermalHouseState], storageState: 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( @@ -540,6 +543,18 @@ object ThermalGrid { thermalGrid.storage.map(_.startingState), ) + /** Wraps the demand of thermal units (thermal house, thermal storage). + * + * @param houseDemand + * the demand of the thermal house + * @param heatStorageDemand + * the demand of the thermal heat storage + */ + final case class ThermalDemandWrapper private ( + houseDemand: ThermalEnergyDemand, + heatStorageDemand: ThermalEnergyDemand, + ) + /** Defines the thermal energy demand of a thermal grid. It comprises the * absolutely required energy demand to reach the target state as well as an * energy, that can be handled. The possible energy always has to be greater diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalHouse.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalHouse.scala index e0fd7024f7..4296efa1ae 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalHouse.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalHouse.scala @@ -12,6 +12,7 @@ import edu.ie3.datamodel.models.input.thermal.{ ThermalBusInput, ThermalHouseInput, } +import edu.ie3.simona.model.participant.HpModel.HpRelevantData import edu.ie3.simona.model.thermal.ThermalGrid.ThermalEnergyDemand import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{ HouseTemperatureLowerBoundaryReached, @@ -82,27 +83,24 @@ final case class ThermalHouse( * determining the thermal demand, a change in external infeed will take * place. * - * @param tick - * Questionable tick - * @param ambientTemperature - * Ambient temperature in the instance in question + * @param relevantData + * data of heat pump including state of the heat pump * @param state * most recent state, that is valid for this model * @return * the needed energy in the questioned tick */ def energyDemand( - tick: Long, - ambientTemperature: Temperature, + relevantData: HpRelevantData, state: ThermalHouseState, ): ThermalEnergyDemand = { /* Calculate the inner temperature of the house, at the questioned instance in time */ - val duration = Seconds(tick - state.tick) + val duration = Seconds(relevantData.currentTick - state.tick) val currentInnerTemp = newInnerTemperature( state.qDot, duration, state.innerTemperature, - ambientTemperature, + relevantData.ambientTemperature, ) /* Determine, which temperature boundary triggers a needed energy to reach the temperature constraints */ @@ -219,27 +217,24 @@ final case class ThermalHouse( /** Update the current state of the house * - * @param tick - * Current instance in time + * @param relevantData + * data of heat pump including state of the heat pump * @param state * Currently applicable state * @param lastAmbientTemperature * Ambient temperature valid up until (not including) the current tick - * @param ambientTemperature - * Current ambient temperature * @param qDot * New thermal influx * @return * Updated state and the tick in which the next threshold is reached */ def determineState( - tick: Long, + relevantData: HpRelevantData, state: ThermalHouseState, lastAmbientTemperature: Temperature, - ambientTemperature: Temperature, qDot: Power, ): (ThermalHouseState, Option[ThermalThreshold]) = { - val duration = Seconds(tick - state.tick) + val duration = Seconds(relevantData.currentTick - state.tick) val updatedInnerTemperature = newInnerTemperature( state.qDot, duration, @@ -249,11 +244,16 @@ final case class ThermalHouse( /* Calculate the next given threshold */ val threshold = - nextThreshold(tick, qDot, updatedInnerTemperature, ambientTemperature) + nextThreshold( + relevantData.currentTick, + qDot, + updatedInnerTemperature, + relevantData.ambientTemperature, + ) ( state.copy( - tick = tick, + tick = relevantData.currentTick, innerTemperature = updatedInnerTemperature, qDot = qDot, ), diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index 838608a514..45de3bae63 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -79,7 +79,7 @@ import scala.util.{Failure, Success, Try} * @param scheduler * Reference to the scheduler of the simulation * @param startDateTime - * Wall clock time of the first instant in simulation + * Simulation time of the first instant in simulation */ case class PrimaryServiceProxy( scheduler: ActorRef, @@ -132,7 +132,7 @@ case class PrimaryServiceProxy( * @param primaryConfig * Configuration for the primary source * @param simulationStart - * Wall clock time of first instant in simulation + * Simulation time of first instant in simulation * @return * State data, containing the known model and time series identifiers */ @@ -505,7 +505,7 @@ object PrimaryServiceProxy { * @param primaryConfig * Configuration for the primary source * @param simulationStart - * Wall clock time of the first instant in simulation + * Simulation time of the first instant in simulation */ final case class InitPrimaryServiceProxyStateData( primaryConfig: PrimaryConfig, @@ -519,7 +519,7 @@ object PrimaryServiceProxy { * @param timeSeriesToSourceRef * Mapping from time series identifier to [[SourceRef]] * @param simulationStart - * Wall clock time of the first instant in simulation + * Simulation time of the first instant in simulation * @param primaryConfig * The configuration for the sources * @param mappingSource diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala index b20fdd834c..f61974392d 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala @@ -228,8 +228,8 @@ final case class PrimaryServiceWorker[V <: Value]( Option[Long], ) = { /* Get the information to distribute */ - val wallClockTime = tick.toDateTime(serviceBaseStateData.startDateTime) - serviceBaseStateData.source.getValue(wallClockTime).toScala match { + val simulationTime = tick.toDateTime(serviceBaseStateData.startDateTime) + serviceBaseStateData.source.getValue(simulationTime).toScala match { case Some(value) => processDataAndAnnounce(tick, value, serviceBaseStateData) case None => @@ -237,7 +237,7 @@ final case class PrimaryServiceWorker[V <: Value]( log.warning( s"I expected to get data for tick '{}' ({}), but data is not available", tick, - wallClockTime, + simulationTime, ) updateStateDataAndBuildTriggerMessages(serviceBaseStateData) } @@ -371,7 +371,7 @@ object PrimaryServiceWorker { * @param timeSeriesUuid * Unique identifier of the time series to read * @param simulationStart - * Wall clock time of the beginning of simulation time + * Simulation time of the beginning of simulation time * @param csvSep * Column separation character of the csv files * @param directoryPath @@ -400,7 +400,7 @@ object PrimaryServiceWorker { * @param timeSeriesUuid * Unique identifier of the time series to read * @param simulationStart - * Wall clock time of the beginning of simulation time + * Simulation time of the beginning of simulation time * @param sqlParams * Parameters regarding SQL connection and table selection * @param databaseNamingStrategy @@ -420,7 +420,7 @@ object PrimaryServiceWorker { * @param activationTicks * Linked collection of ticks, in which data is available * @param startDateTime - * Wall clock time of the first instant in simulation + * Simulation time of the first instant in simulation * @param source * Implementation of [[TimeSeriesSource]] to use for actual acquisition of * data diff --git a/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala b/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala index 0db3ded44c..7f3a7de525 100644 --- a/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala +++ b/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala @@ -65,15 +65,15 @@ final class SampleWeatherSource( private def getWeather( tick: Long ): WeatherData = { - val wallClockTime = tick.toDateTime - val month = wallClockTime.get(MONTH_OF_YEAR) - 1 - val hour = wallClockTime.get(HOUR_OF_DAY) + val simulationTime = tick.toDateTime + val month = simulationTime.get(MONTH_OF_YEAR) - 1 + val hour = simulationTime.get(HOUR_OF_DAY) val year = if ( - wallClockTime.get(YEAR) != 2011 && !(wallClockTime + simulationTime.get(YEAR) != 2011 && !(simulationTime .get(YEAR) == 2012 && month == 0) ) 2011 - else wallClockTime.get(YEAR) + else simulationTime.get(YEAR) val index = (((year - 2011) * 288) + (month * 24) + hour) + 1 WeatherData( WattsPerSquareMeter( diff --git a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentMock.scala b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentMock.scala index c3c77e2292..3e17e76ccb 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentMock.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentMock.scala @@ -260,9 +260,9 @@ class ParticipantAgentMock( * @param modelConfig * Configuration for the model * @param simulationStartDate - * Wall clock time of first instant in simulation + * The simulation time at which the simulation starts * @param simulationEndDate - * Wall clock time of last instant in simulation + * The simulation time at which the simulation ends * @return */ override def buildModel( diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridSpec.scala index e4c1c14c70..bb34a88682 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridSpec.scala @@ -6,13 +6,19 @@ package edu.ie3.simona.model.thermal +import edu.ie3.datamodel.models.input.thermal.ThermalStorageInput import edu.ie3.simona.model.thermal.ThermalGrid.ThermalEnergyDemand import edu.ie3.simona.test.common.UnitSpec -import squants.energy.{MegawattHours, WattHours, Watts} +import squants.energy.{KilowattHours, MegawattHours, WattHours, Watts} import squants.thermal.Celsius import squants.{Energy, Power, Temperature} -class ThermalGridSpec extends UnitSpec { +import scala.jdk.CollectionConverters._ + +class ThermalGridSpec + extends UnitSpec + with ThermalHouseTestData + with ThermalStorageTestData { implicit val tempTolerance: Temperature = Celsius(1e-3) implicit val powerTolerance: Power = Watts(1e-3) @@ -97,4 +103,44 @@ class ThermalGridSpec extends UnitSpec { } } } + "ThermalGridState" should { + val thermalGridOnlyHouse = ThermalGrid( + new edu.ie3.datamodel.models.input.container.ThermalGrid( + thermalBusInput, + Set(thermalHouseInput).asJava, + Set.empty[ThermalStorageInput].asJava, + ) + ) + + "return true when there is no storage" in { + val initialState = ThermalGrid.startingState(thermalGridOnlyHouse) + val result = initialState.isThermalStorageEmpty + result shouldBe true + } + + val thermalGrid = ThermalGrid( + new edu.ie3.datamodel.models.input.container.ThermalGrid( + thermalBusInput, + Set(thermalHouseInput).asJava, + Set[ThermalStorageInput](thermalStorageInput).asJava, + ) + ) + + "return true when all stored energy is effectively zero" in { + val initialState = ThermalGrid.startingState(thermalGrid) + val result = initialState.isThermalStorageEmpty + result shouldBe true + } + + "return false when storage is not empty" in { + val initialState = ThermalGrid.startingState(thermalGrid) + val gridState = initialState.copy(storageState = + initialState.storageState.map(storageState => + storageState.copy(storedEnergy = KilowattHours(1)) + ) + ) + val result = gridState.isThermalStorageEmpty + result shouldBe false + } + } } diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala index 6011c6caf2..df410786c5 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala @@ -9,7 +9,12 @@ package edu.ie3.simona.model.thermal import edu.ie3.datamodel.models.OperationTime import edu.ie3.datamodel.models.input.OperatorInput import edu.ie3.datamodel.models.input.thermal.ThermalBusInput -import squants.energy.{Kilowatts, Power} +import edu.ie3.simona.model.thermal.ThermalGrid.{ + ThermalDemandWrapper, + ThermalEnergyDemand, +} +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKWh +import squants.energy.{KilowattHours, Kilowatts, Power} import squants.thermal.{Celsius, Temperature} import java.util.UUID @@ -25,4 +30,21 @@ trait ThermalGridTestData { protected val testGridQDotInfeed: Power = Kilowatts(15d) protected val testGridQDotConsumption: Power = Kilowatts(-42d) protected val testGridQDotConsumptionHigh: Power = Kilowatts(-200d) + protected val noThermalDemand: ThermalDemandWrapper = + ThermalDemandWrapper( + ThermalEnergyDemand(zeroKWh, zeroKWh), + ThermalEnergyDemand(zeroKWh, zeroKWh), + ) + protected val onlyThermalDemandOfHouse: ThermalDemandWrapper = + ThermalDemandWrapper( + ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)), + ThermalEnergyDemand(zeroKWh, zeroKWh), + ) + protected val onlyThermalDemandOfHeatStorage: ThermalDemandWrapper = + ThermalDemandWrapper( + ThermalEnergyDemand(zeroKWh, zeroKWh), + ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)), + ) + protected val isRunning: Boolean = true + protected val isNotRunning: Boolean = false } diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala index 7d8d2c27f1..5b57075ed6 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala @@ -7,19 +7,20 @@ package edu.ie3.simona.model.thermal import edu.ie3.datamodel.models.input.thermal.ThermalStorageInput +import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState} import edu.ie3.simona.model.thermal.ThermalGrid.ThermalGridState import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseState import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{ HouseTemperatureLowerBoundaryReached, HouseTemperatureUpperBoundaryReached, } -import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageThreshold.{ StorageEmpty, StorageFull, } import edu.ie3.simona.test.common.UnitSpec +import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import squants.energy._ import squants.thermal.Celsius import squants.{Energy, Kelvin, Power, Temperature} @@ -95,15 +96,27 @@ class ThermalGridWithHouseAndStorageSpec "determining the energy demand" should { "deliver the house demand (no demand) with added flexibility by storage" in { - val tick = 10800 // after three hours - - val (houseDemand, storageDemand, updatedThermalGridState) = + val relevantData = HpRelevantData( + 10800, // after three hours + testGridAmbientTemperature, + ) + val lastHpState = HpState( + true, + relevantData.currentTick, + Some(testGridAmbientTemperature), + Kilowatts(42), + Kilowatts(42), + ThermalGrid.startingState(thermalGrid), + None, + ) + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( - tick, - testGridAmbientTemperature, - testGridAmbientTemperature, - ThermalGrid.startingState(thermalGrid), + relevantData, + lastHpState, ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand + houseDemand.required should approximate(zeroKWh) houseDemand.possible should approximate(KilowattHours(31.05009722d)) storageDemand.required should approximate(KilowattHours(1150d)) @@ -117,20 +130,32 @@ class ThermalGridWithHouseAndStorageSpec } "deliver the correct house and storage demand" in { - val tick = 10800 // after three hours - + val relevantData = HpRelevantData( + 10800, // after three hours + testGridAmbientTemperature, + ) val startingState = ThermalGrid.startingState(thermalGrid) - val (houseDemand, storageDemand, updatedThermalGridState) = + val lastHpState = HpState( + true, + relevantData.currentTick, + Some(testGridAmbientTemperature), + Kilowatts(42), + Kilowatts(42), + startingState.copy(houseState = + startingState.houseState.map( + _.copy(innerTemperature = Celsius(16d)) + ) + ), + None, + ) + + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( - tick, - testGridAmbientTemperature, - testGridAmbientTemperature, - startingState.copy(houseState = - startingState.houseState.map( - _.copy(innerTemperature = Celsius(16d)) - ) - ), + relevantData, + lastHpState, ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand houseDemand.required should approximate(KilowattHours(45.6000555)) houseDemand.possible should approximate(KilowattHours(75.600055555)) @@ -152,7 +177,10 @@ class ThermalGridWithHouseAndStorageSpec ) "return house threshold, if storage is in balance" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val initialGridState = ThermalGrid.startingState(thermalGrid) val initialLoading = KilowattHours(430d) val gridState = initialGridState.copy(storageState = @@ -164,8 +192,7 @@ class ThermalGridWithHouseAndStorageSpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, externalQDot, @@ -189,7 +216,10 @@ class ThermalGridWithHouseAndStorageSpec } "take energy from storage, if there is actual consumption" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val initialGridState = ThermalGrid.startingState(thermalGrid) val initialLoading = KilowattHours(200d) val gridState = initialGridState.copy(storageState = @@ -201,8 +231,7 @@ class ThermalGridWithHouseAndStorageSpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, externalQDot, @@ -230,13 +259,16 @@ class ThermalGridWithHouseAndStorageSpec "revising infeed from storage to house" should { val zeroInflux = zeroKW - val tick = 3600L + val relevantData = HpRelevantData( + 3600, + testGridAmbientTemperature, + ) val ambientTemperature = Celsius(14d) "hand back unaltered information if needed information is missing" in { val maybeHouseState = Some( ( ThermalHouseState( - tick, + relevantData.currentTick, Celsius( thermalHouseInput.getTargetTemperature .to(Units.CELSIUS) @@ -251,13 +283,12 @@ class ThermalGridWithHouseAndStorageSpec val maybeStorageState = None thermalGrid.reviseInfeedFromStorage( - tick, + relevantData, maybeHouseState, maybeStorageState, maybeHouseState.map(_._1), None, testGridAmbientTemperature, - testGridAmbientTemperature, testGridQDotConsumption, ) match { case (maybeRevisedHouseState, maybeRevisedStorageState) => @@ -270,7 +301,7 @@ class ThermalGridWithHouseAndStorageSpec val maybeHouseState = Some( ( ThermalHouseState( - tick, + relevantData.currentTick, Celsius( thermalHouseInput.getTargetTemperature .to(Units.CELSIUS) @@ -285,7 +316,7 @@ class ThermalGridWithHouseAndStorageSpec val maybeStorageState = Some( ( ThermalStorageState( - tick, + relevantData.currentTick, KilowattHours(50d), zeroInflux, ), @@ -294,13 +325,12 @@ class ThermalGridWithHouseAndStorageSpec ) thermalGrid.reviseInfeedFromStorage( - tick, + relevantData, maybeHouseState, maybeStorageState, maybeHouseState.map(_._1), maybeStorageState.map(_._1), ambientTemperature, - ambientTemperature, zeroInflux, ) match { case (maybeRevisedHouseState, maybeRevisedStorageState) => @@ -313,7 +343,7 @@ class ThermalGridWithHouseAndStorageSpec val maybeHouseState = Some( ( ThermalHouseState( - tick, + relevantData.currentTick, Celsius( thermalHouseInput.getTargetTemperature .to(Units.CELSIUS) @@ -328,7 +358,7 @@ class ThermalGridWithHouseAndStorageSpec val maybeStorageState = Some( ( ThermalStorageState( - tick, + relevantData.currentTick, KilowattHours(50d), zeroInflux, ), @@ -337,13 +367,12 @@ class ThermalGridWithHouseAndStorageSpec ) thermalGrid.reviseInfeedFromStorage( - tick, + relevantData, maybeHouseState, maybeStorageState, maybeHouseState.map(_._1), maybeStorageState.map(_._1), ambientTemperature, - ambientTemperature, testGridQDotInfeed, ) match { case (maybeRevisedHouseState, maybeRevisedStorageState) => @@ -356,7 +385,7 @@ class ThermalGridWithHouseAndStorageSpec val maybeHouseState = Some( ( ThermalHouseState( - tick, + relevantData.currentTick, Celsius( thermalHouseInput.getLowerTemperatureLimit .to(Units.CELSIUS) @@ -365,28 +394,29 @@ class ThermalGridWithHouseAndStorageSpec ), zeroInflux, ), - Some(HouseTemperatureLowerBoundaryReached(tick)), + Some( + HouseTemperatureLowerBoundaryReached(relevantData.currentTick) + ), ) ) val maybeStorageState = Some( ( ThermalStorageState( - tick, + relevantData.currentTick, zeroKWh, testGridQDotInfeed, ), - Some(StorageEmpty(tick)), + Some(StorageEmpty(relevantData.currentTick)), ) ) thermalGrid.reviseInfeedFromStorage( - tick, + relevantData, maybeHouseState, maybeStorageState, maybeHouseState.map(_._1), maybeStorageState.map(_._1), ambientTemperature, - ambientTemperature, zeroInflux, ) match { case (maybeRevisedHouseState, maybeRevisedStorageState) => @@ -399,7 +429,7 @@ class ThermalGridWithHouseAndStorageSpec val maybeHouseState = Some( ( ThermalHouseState( - tick, + relevantData.currentTick, Celsius( thermalHouseInput.getLowerTemperatureLimit .to(Units.CELSIUS) @@ -408,13 +438,15 @@ class ThermalGridWithHouseAndStorageSpec ), zeroInflux, ), - Some(HouseTemperatureLowerBoundaryReached(tick)), + Some( + HouseTemperatureLowerBoundaryReached(relevantData.currentTick) + ), ) ) val maybeStorageState = Some( ( ThermalStorageState( - tick, + relevantData.currentTick, KilowattHours(20d), testGridQDotInfeed, ), @@ -442,13 +474,12 @@ class ThermalGridWithHouseAndStorageSpec ) thermalGrid.reviseInfeedFromStorage( - tick, + relevantData, maybeHouseState, maybeStorageState, formerHouseState, formerStorageState, ambientTemperature, - ambientTemperature, zeroInflux, ) match { case ( @@ -465,8 +496,8 @@ class ThermalGridWithHouseAndStorageSpec ) ), ) => - houseTick shouldBe tick - storageTick shouldBe tick + houseTick shouldBe relevantData.currentTick + storageTick shouldBe relevantData.currentTick revisedQDotHouse should approximate(thermalStorage.chargingPower) revisedQDotStorage should approximate( @@ -487,14 +518,16 @@ class ThermalGridWithHouseAndStorageSpec ) "heat the house, if the upper temperature in the house is not reached" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val initialGridState = ThermalGrid.startingState(thermalGrid) val externalQDot = testGridQDotInfeed val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, initialGridState, externalQDot, @@ -528,7 +561,10 @@ class ThermalGridWithHouseAndStorageSpec } "load the storage, if the upper temperature in the house is reached" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val initialGridState = ThermalGrid.startingState(thermalGrid) val gridState = initialGridState.copy(houseState = initialGridState.houseState.map( @@ -539,8 +575,7 @@ class ThermalGridWithHouseAndStorageSpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, externalQDot, diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala index 6b14ee3780..46ad85a3f0 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala @@ -7,14 +7,15 @@ package edu.ie3.simona.model.thermal import edu.ie3.datamodel.models.input.thermal.ThermalStorageInput +import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState} import edu.ie3.simona.model.thermal.ThermalGrid.ThermalGridState import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseState import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{ HouseTemperatureLowerBoundaryReached, HouseTemperatureUpperBoundaryReached, } -import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import edu.ie3.simona.test.common.UnitSpec +import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import squants.energy._ import squants.thermal.Celsius import squants.{Energy, Kelvin, Power, Temperature} @@ -74,21 +75,34 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { "determining the energy demand" should { "exactly be the demand of the house" in { - val tick = 10800 // after three hours - val expectedHouseDemand = thermalHouse.energyDemand( - tick, + val relevantData = HpRelevantData( + 10800, // after three hours testGridAmbientTemperature, + ) + val lastHpState = HpState( + true, + relevantData.currentTick, + Some(testGridAmbientTemperature), + Kilowatts(42), + Kilowatts(42), + ThermalGrid.startingState(thermalGrid), + None, + ) + + val expectedHouseDemand = thermalHouse.energyDemand( + relevantData, expectedHouseStartingState, ) - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( - tick, - testGridAmbientTemperature, - testGridAmbientTemperature, - ThermalGrid.startingState(thermalGrid), + relevantData, + lastHpState, ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand + houseDemand.required should approximate(expectedHouseDemand.required) houseDemand.possible should approximate(expectedHouseDemand.possible) storageDemand.required should approximate(zeroKWh) @@ -107,14 +121,16 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { ) "deliver the house state by just letting it cool down, if just no infeed is given" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val gridState = ThermalGrid.startingState(thermalGrid) val externalQDot = zeroKW val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, externalQDot, @@ -136,13 +152,15 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { } "not withdraw energy from the house, if actual consumption is given" in { - val tick = 0L // after three hours + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val gridState = ThermalGrid.startingState(thermalGrid) val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, testGridQDotConsumption, @@ -171,13 +189,15 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { ) "solely heat up the house" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val gridState = ThermalGrid.startingState(thermalGrid) val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, testGridQDotInfeed, @@ -200,12 +220,13 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { } "updating the grid state dependent on the given thermal infeed" should { + val relevantData = HpRelevantData(0, testGridAmbientTemperature) "deliver proper result, if energy is fed into the grid" in { + thermalGrid.updateState( - 0L, + relevantData, ThermalGrid.startingState(thermalGrid), testGridAmbientTemperature, - testGridAmbientTemperature, testGridQDotInfeed, ) match { case ( @@ -225,10 +246,9 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { "deliver proper result, if energy is consumed from the grid" in { thermalGrid.updateState( - 0L, + relevantData, ThermalGrid.startingState(thermalGrid), testGridAmbientTemperature, - testGridAmbientTemperature, testGridQDotConsumption, ) match { case ( @@ -248,10 +268,9 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { "deliver proper result, if energy is neither consumed from nor fed into the grid" in { thermalGrid.updateState( - 0L, + relevantData, ThermalGrid.startingState(thermalGrid), testGridAmbientTemperature, - testGridAmbientTemperature, zeroKW, ) match { case ( diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala index ead1ff6b03..ddff53ff39 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala @@ -10,6 +10,7 @@ import edu.ie3.datamodel.models.input.thermal.{ ThermalHouseInput, ThermalStorageInput, } +import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState} import edu.ie3.simona.model.thermal.ThermalGrid.ThermalGridState import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageThreshold.{ @@ -79,15 +80,27 @@ class ThermalGridWithStorageOnlySpec "determining the energy demand" should { "deliver the capabilities of the storage" in { - val tick = 10800 // after three hours + val relevantData = HpRelevantData( + 10800, // after three hours + testGridAmbientTemperature, + ) + val lastHpState = HpState( + true, + relevantData.currentTick, + Some(testGridAmbientTemperature), + Kilowatts(42), + Kilowatts(42), + ThermalGrid.startingState(thermalGrid), + None, + ) - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( - tick, - testGridAmbientTemperature, - testGridAmbientTemperature, - ThermalGrid.startingState(thermalGrid), + relevantData, + lastHpState, ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand houseDemand.required should approximate(zeroKWh) houseDemand.possible should approximate(zeroKWh) @@ -100,18 +113,30 @@ class ThermalGridWithStorageOnlySpec } "deliver the capabilities of a half full storage" in { - val tick = 10800 // after three hours + val relevantData = HpRelevantData( + 10800, // after three hours + testGridAmbientTemperature, + ) + val lastHpState = HpState( + true, + relevantData.currentTick, + Some(testGridAmbientTemperature), + Kilowatts(42), + Kilowatts(42), + ThermalGridState( + None, + Some(ThermalStorageState(0L, KilowattHours(575d), zeroKW)), + ), + None, + ) - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( - tick, - testGridAmbientTemperature, - testGridAmbientTemperature, - ThermalGridState( - None, - Some(ThermalStorageState(0L, KilowattHours(575d), zeroKW)), - ), + relevantData, + lastHpState, ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand houseDemand.required should approximate(zeroKWh) houseDemand.possible should approximate(zeroKWh) @@ -131,7 +156,10 @@ class ThermalGridWithStorageOnlySpec ) "properly take the energy from storage" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val gridState = ThermalGrid .startingState(thermalGrid) .copy(storageState = @@ -146,8 +174,7 @@ class ThermalGridWithStorageOnlySpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, testGridQDotConsumptionHigh, @@ -174,13 +201,15 @@ class ThermalGridWithStorageOnlySpec ) "properly put energy to storage" in { - val tick = 0L + val relevantData = HpRelevantData( + 0L, + testGridAmbientTemperature, + ) val gridState = ThermalGrid.startingState(thermalGrid) val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( - tick, - testGridAmbientTemperature, + relevantData, testGridAmbientTemperature, gridState, testGridQDotInfeed, @@ -201,12 +230,12 @@ class ThermalGridWithStorageOnlySpec } "updating the grid state dependent on the given thermal infeed" should { + val relevantData = HpRelevantData(0, testGridAmbientTemperature) "deliver proper result, if energy is fed into the grid" in { val (updatedState, nextThreshold) = thermalGrid.updateState( - 0L, + relevantData, ThermalGrid.startingState(thermalGrid), testGridAmbientTemperature, - testGridAmbientTemperature, testGridQDotInfeed, ) @@ -226,7 +255,7 @@ class ThermalGridWithStorageOnlySpec "deliver proper result, if energy is consumed from the grid" in { thermalGrid.updateState( - 0L, + relevantData, ThermalGrid .startingState(thermalGrid) .copy(storageState = @@ -239,7 +268,6 @@ class ThermalGridWithStorageOnlySpec ) ), testGridAmbientTemperature, - testGridAmbientTemperature, testGridQDotConsumptionHigh, ) match { case ( @@ -259,10 +287,9 @@ class ThermalGridWithStorageOnlySpec "deliver proper result, if energy is neither consumed from nor fed into the grid" in { val updatedState = thermalGrid.updateState( - 0L, + relevantData, ThermalGrid.startingState(thermalGrid), testGridAmbientTemperature, - testGridAmbientTemperature, zeroKW, ) updatedState match { diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala index f4a7c97574..4438036f06 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.model.thermal +import edu.ie3.simona.model.participant.HpModel.HpRelevantData import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.HouseTemperatureLowerBoundaryReached import edu.ie3.simona.model.thermal.ThermalHouse.{ ThermalHouseState, @@ -71,16 +72,16 @@ class ThermalHouseSpec extends UnitSpec with HpInputTestData { } "Check for the correct state of house when ambient temperature changes" in { + val ambientTemperature = Temperature(-20, Celsius) + val relevantData = HpRelevantData(3600, ambientTemperature) val house = thermalHouse(18, 22) val initialHousestate = startingState(house) val lastAmbientTemperature = Temperature(15, Celsius) - val ambientTemperature = Temperature(-20, Celsius) val (thermalHouseState, threshold) = house.determineState( - 3600L, + relevantData, initialHousestate, lastAmbientTemperature, - ambientTemperature, zeroKW, )