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 3b4af9501d..7f07635456 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 @@ -311,7 +311,11 @@ trait HpAgentFundamentals calcRelevantData: HpRelevantData, nodalVoltage: squants.Dimensionless, model: HpModel, - ): HpState = model.determineState(modelState, calcRelevantData) + ): HpState = { + val (canOperate, canBeOutOfOperation, state) = + model.determineState(modelState, calcRelevantData) + state + } /** Abstract definition, individual implementations found in individual agent * fundamental classes 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 d78d3ffa07..499cec7fb2 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala @@ -11,7 +11,7 @@ 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 @@ -115,26 +115,29 @@ final case class HpModel( * function calculates the heat pump's next state to get the actual active * power of this state use [[calculateActivePower]] with the generated state * - * @param state - * Current state of the heat pump + * @param lastState + * Last state of the heat pump * @param relevantData * data of heat pump including * @return - * next [[HpState]] + * Booleans if Hp can operate and can be out of operation plus next + * [[HpState]] */ def determineState( - state: HpState, + lastState: HpState, relevantData: HpRelevantData, ): HpState = { val ( turnOn, + canOperate, + canBeOutOfOperation, houseDemand, thermalStorageDemand, domesticHotWaterStorageDemand, ) = operatesInNextState(state, relevantData) - calcState( - state, + val updatedState=calcState( + lastState, relevantData, turnOn, houseDemand, @@ -143,6 +146,7 @@ final case class HpModel( relevantData.simulationStart, relevantData.houseInhabitants, ) + (canOperate, canBeOutOfOperation, updatedState) } /** Depending on the input, this function decides whether the heat pump will @@ -156,19 +160,14 @@ final case class HpModel( * @param relevantData * Relevant (external) data * @return - * boolean defining if heat pump runs in next time step and if house and - * storages have some demand + * boolean defining if heat pump runs in next time step, if it can be in + * operation and out of operation plus the demand of house and storage */ private def operatesInNextState( state: HpState, relevantData: HpRelevantData, - ): (Boolean, Boolean, Boolean, Boolean) = { - val ( - demandHouse, - demandThermalStorage, - demandDomesticHotWaterStorage, - updatedState, - ) = + ): (Boolean, Boolean, Boolean, Boolean, Boolean) = { + val (demandHouse, demandThermalStorage, demandDomesticHotWaterStorage, updatedState) = thermalGrid.energyDemandAndUpdatedState( relevantData.currentTick, state.ambientTemperature.getOrElse(relevantData.ambientTemperature), @@ -177,28 +176,45 @@ final case class HpModel( relevantData.simulationStart, relevantData.houseInhabitants, ) + + + val (houseDemand, heatStorageDemand, domesticHotWaterStorageDemand, noThermalStorageOrThermalStorageIsEmpty) = determineDemandBooleans(state, updatedState, demandHouse, demandThermalStorage) + + val turnHpOn: Boolean = + houseDemand || heatStorageDemand || domesticHotWaterStorageDemand + + val canOperate = + demandHouse.hasRequiredDemand || demandHouse.hasAdditionalDemand || + demandThermalStorage.hasRequiredDemand || demandThermalStorage.hasAdditionalDemand + demandDomesticHotWaterStorage.hasRequiredDemand || demandDomesticHotWaterStorage.hasAdditionalDemand + // FIXME: does demandDomesticHotWaterStorage need to be considered here? + val canBeOutOfOperation = + !(demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) + + ( + turnHpOn, + canOperate, + canBeOutOfOperation, + houseDemand, + heatStorageDemand) + } + + def determineDemandBooleans(hpState: HpState, updatedGridState:ThermalGridState, demandHouse:ThermalEnergyDemand, demandThermalStorage: ThermalEnergyDemand, demandDomesticHotWaterStorage:ThermalEnergyDemand):(Boolean, Boolean, Boolean, Boolean)={ implicit val tolerance: Energy = KilowattHours(1e-3) val noThermalStorageOrThermalStorageIsEmpty: Boolean = - updatedState.storageState.isEmpty || updatedState.storageState.exists( + updatedGridState.storageState.isEmpty || updatedGridState.storageState.exists( _.storedEnergy =~ zeroKWH ) val houseDemand = - (demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || (state.isRunning && demandHouse.hasAdditionalDemand) - val heatStorageDemand = - (demandThermalStorage.hasRequiredDemand) || (state.isRunning && demandThermalStorage.hasAdditionalDemand) + (demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || (hpState.isRunning && demandHouse.hasAdditionalDemand) + val heatStorageDemand = { + (demandThermalStorage.hasRequiredDemand) || (hpState.isRunning && demandThermalStorage.hasAdditionalDemand) + } val domesticHotWaterStorageDemand = (demandDomesticHotWaterStorage.hasRequiredDemand) || (state.isRunning && demandDomesticHotWaterStorage.hasAdditionalDemand) - val turnHpOn: Boolean = - houseDemand || heatStorageDemand || domesticHotWaterStorageDemand - - ( - turnHpOn, - houseDemand, - heatStorageDemand, - domesticHotWaterStorageDemand, - ) + (houseDemand, heatStorageDemand,domesticHotWaterStorageDemand, noThermalStorageOrThermalStorageIsEmpty) } /** Calculate state depending on whether heat pump is needed or not. Also @@ -277,33 +293,8 @@ final case class HpModel( lastState: HpState, ): ProvideFlexOptions = { /* Determine the operating state in the given tick */ - val updatedHpState: HpState = determineState(lastState, data) - - /* Determine the options we have */ - val ( - thermalEnergyDemandHouse, - thermalEnergyDemandHeatStorage, - thermalEnergyDemandDomesticHotWaterStorage, - updatedThermalGridState, - ) = - thermalGrid.energyDemandAndUpdatedState( - data.currentTick, - lastState.ambientTemperature.getOrElse(data.ambientTemperature), - data.ambientTemperature, - lastState.thermalGridState, - data.simulationStart, - data.houseInhabitants, - ) - - val canOperate = - thermalEnergyDemandHouse.hasRequiredDemand || thermalEnergyDemandHouse.hasAdditionalDemand || - thermalEnergyDemandHeatStorage.hasRequiredDemand || thermalEnergyDemandHeatStorage.hasAdditionalDemand || - thermalEnergyDemandDomesticHotWaterStorage.hasRequiredDemand || thermalEnergyDemandDomesticHotWaterStorage.hasAdditionalDemand - - val canBeOutOfOperation = - !thermalEnergyDemandHouse.hasRequiredDemand && - !thermalEnergyDemandHeatStorage.hasRequiredDemand && - !thermalEnergyDemandDomesticHotWaterStorage.hasRequiredDemand + val (canOperate, canBeOutOfOperation, updatedHpState) + : (Boolean, Boolean, HpState) = determineState(lastState, data) val lowerBoundary = if (canBeOutOfOperation) @@ -364,20 +355,7 @@ final case class HpModel( data.houseInhabitants, ) - implicit val tolerance: Energy = KilowattHours(1e-3) - val noThermalStorageOrThermalStorageIsEmpty: Boolean = - updatedThermalGridState.storageState.isEmpty.||( - updatedThermalGridState.storageState.exists( - _.storedEnergy =~ zeroKWH - ) - ) - - val houseDemand = - (thermalEnergyDemandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || (lastState.isRunning && thermalEnergyDemandHouse.hasAdditionalDemand) - val heatStorageDemand = - (thermalEnergyDemandStorage.hasRequiredDemand) || (lastState.isRunning && thermalEnergyDemandStorage.hasAdditionalDemand) - val domesticHotWaterStorageDemand = - (thermalEnergyDemandDomesticHotWaterStorage.hasRequiredDemand) || (lastState.isRunning && thermalEnergyDemandDomesticHotWaterStorage.hasAdditionalDemand) + val (houseDemand, heatStorageDemand, domesticHotWaterStorageDemand, noThermalStorageOrThermalStorageIsEmpty) = determineDemandBooleans(lastState, updatedThermalGridState, thermalEnergyDemandHouse, thermalEnergyDemandStorage, thermalEnergyDemandDomesticHotWaterStorage) val updatedHpState: HpState = 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 8a66917f43..26aa041b7f 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -16,6 +16,7 @@ import edu.ie3.datamodel.models.result.thermal.{ CylindricalStorageResult, ThermalHouseResult, } +import edu.ie3.simona.exceptions.InvalidParameterException import edu.ie3.simona.exceptions.agent.InconsistentStateException import edu.ie3.simona.model.thermal.ThermalGrid.{ ThermalEnergyDemand, @@ -1062,13 +1063,12 @@ object ThermalGrid { def hasRequiredDemand: Boolean = required > zeroMWH - def hasAdditionalDemand: Boolean = possible > required + def hasAdditionalDemand: Boolean = possible > zeroMWH } object ThermalEnergyDemand { /** Builds a new instance of [[ThermalEnergyDemand]]. If the possible energy - * is less than the required energy, this is considered to be a bad state - * and the required energy is curtailed to the possible energy. + * is less than the required energy, this is considered to be a bad state. * @param required * The absolutely required energy to reach target state * @param possible @@ -1080,8 +1080,12 @@ object ThermalGrid { required: Energy, possible: Energy, ): ThermalEnergyDemand = { - if (possible < required) - new ThermalEnergyDemand(possible, possible) + if ( + math.abs(possible.toKilowattHours) < math.abs(required.toKilowattHours) + ) + throw new InvalidParameterException( + s"The possible amount of energy {$possible} is smaller than the required amount of energy {$required}. This is not supported." + ) else new ThermalEnergyDemand(required, possible) } diff --git a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala index 87fcb245d6..101ca7a739 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala @@ -208,14 +208,18 @@ class HpModelSpec val hp = hpModel(grid) hp.determineState(state, data) match { - case HpState( - isRunning, + case ( _, _, - activePower, - _, - ThermalGridState(Some(thermalHouseState), _, _), - maybeThreshold, + HpState( + isRunning, + _, + _, + activePower, + _, + ThermalGridState(Some(thermalHouseState), _, _), + maybeThreshold, + ), ) => isRunning shouldBe expectedRunningState activePower should approximate(Kilowatts(expectedActivePower)) @@ -231,24 +235,45 @@ class HpModelSpec } } } - +//FIXME "determining the flexibility options for different states" should { "deliver correct flexibility options" in { val testCases : TableFor3[ThermalGridState, HpState, (Double, Double, Double)] = Table( ("thermalState", "lastState", "expectedValues"), - // Hp actually not running + // 1. Hp actually not running // House is below lower temperature boundary // Heat storage is empty - // Domestic hot water storage is full - // should not be possible to turn hp off + // hp must be turned on ( ThermalGridState( Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(0.0), + Kilowatts(0.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + (95.0, 95.0, 95.0), + ), + // 2. Same as before but heat storage is NOT empty + // should be possible to keep hp off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), HpState( isRunning = false, 0, @@ -260,19 +285,91 @@ class HpModelSpec Some( ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + ), + (0.0, 0.0, 95.0), + ), + // 3. Hp actually running + // House is below lower temperature boundary + // Heat storage is empty + // Hp must run because of house and storage + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(0.0), + Kilowatts(0.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), Some( - ThermalStorageState(0L, KilowattHours(250), Kilowatts(0)) + ThermalStorageState(0L, KilowattHours(0), Kilowatts(0)) ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), None, ), (95.0, 95.0, 95.0), ), - // Hp actually running + // 4. Same as before but heat storage is NOT empty + // Hp should not run because of storage but can be turned on + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(0.0), + Kilowatts(0.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), + Some( + ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) + ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + None, + ), + (0.0, 0.0, 95.0), + ), + // 5. Hp actually running // House is between target temperature and lower temperature boundary - // Heat storage has capacity - // Domestic hot water storage is full - // Should be possible to turn off, since storage can be used to cover heat demand of house + // Heat storage is empty + // Hp runs but can be turned off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), + Some( + ThermalStorageState(0L, KilowattHours(0), Kilowatts(0)) + ), + ), + None, + ), + (95.0, 0.0, 95.0), + ), + // 6. Same as before but heat storage is NOT empty + // should be possible to keep hp off ( ThermalGridState( Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), @@ -286,20 +383,45 @@ class HpModelSpec Kilowatts(95.0), Kilowatts(80.0), ThermalGridState( - Some(ThermalHouseState(0L, Celsius(19), Kilowatts(80))), + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), Some( ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + None, + ), + (95.0, 0.0, 95.0), + ), + // 7. Hp actually NOT running + // House is between target temperature and lower temperature boundary + // Heat storage is empty + // Hp should run because of storage but can be turned off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), Some( - ThermalStorageState(0L, KilowattHours(250), Kilowatts(0)) + ThermalStorageState(0L, KilowattHours(0), Kilowatts(0)) ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), None, ), (95.0, 0.0, 95.0), ), - - // House is between target temperature and lower temperature boundary, Hp actually not running + // 8. Same as before but heat storage is NOT empty + // Hp should be off but able to turn on ( ThermalGridState( Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), @@ -310,25 +432,49 @@ class HpModelSpec isRunning = false, 0, Some(hpData.ambientTemperature), - Kilowatts(0.0), - Kilowatts(0.0), + Kilowatts(95.0), + Kilowatts(80.0), ThermalGridState( Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), Some( ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) ), - Some( - ThermalStorageState(0L, KilowattHours(250), Kilowatts(0)) - ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), None, ), (0.0, 0.0, 95.0), ), - // Storage and house have remaining capacity + // 9. Hp actually running + // House is between target temperature and upper temperature boundary + // Heat storage is empty + // Hp will run because of storage but can be turned off ( ThermalGridState( - Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + Some(HouseTemperatureUpperBoundaryReached(0L)), + ), + (95.0, 0.0, 95.0), + ), + // 10. Same as before but storage is NOT empty + // Hp should run but can be turned off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), @@ -339,23 +485,165 @@ class HpModelSpec Kilowatts(95.0), Kilowatts(80.0), ThermalGridState( - Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), - Some( - ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) - ), - Some( - ThermalStorageState(0L, KilowattHours(250), Kilowatts(0)) - ), + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + Some(HouseTemperatureUpperBoundaryReached(0L)), + ), + (95.0, 0.0, 95.0), + ), + // 11. Hp actually not running + // House is between target temperature and upper temperature boundary + // Heat storage is empty + // Hp should run because of storage but can be turned off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + Some(HouseTemperatureUpperBoundaryReached(0L)), + ), + (95.0, 0.0, 95.0), + ), + // 12. Same as before but storage is NOT empty + // Hp should not run but can be turned on for storage or house + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + Some(HouseTemperatureUpperBoundaryReached(0L)), + ), + (0.0, 0.0, 95.0), + ), + + // 13. Hp actually running + // House is at upper temperature boundary + // Heat storage is empty + // Hp should run because of storage but can be turned off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(22), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + Some(HouseTemperatureUpperBoundaryReached(0L)), + ), + (95.0, 0.0, 95.0), + ), + // 14. Same as before but storage is NOT empty + // Hp should run but can be turned off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(22), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + Some(HouseTemperatureUpperBoundaryReached(0L)), + ), + (95.0, 0.0, 95.0), + ), + // 15. Hp actually not running + // House is at upper temperature boundary + // Heat storage is empty + // Hp should run because of storage but can be turned off + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(22), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(0), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), Some(HouseTemperatureUpperBoundaryReached(0L)), ), (95.0, 0.0, 95.0), ), + // 16. Same as before but storage is NOT empty + // Hp should not run but can be turned on for storage + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(22), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), + ), + Some(HouseTemperatureUpperBoundaryReached(0L)), + ), + (0.0, 0.0, 95.0), + ), + // Storage is full, House has capacity till upper boundary ( ThermalGridState( - Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), @@ -366,24 +654,21 @@ class HpModelSpec Kilowatts(0.0), Kilowatts(0.0), ThermalGridState( - Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), Some( ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) ), - Some( - ThermalStorageState(0L, KilowattHours(250), Kilowatts(0)) - ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), Some(HouseTemperatureUpperBoundaryReached(0L)), ), (0.0, 0.0, 95.0), ), - // No capacity for flexibility at all because house is // at upperTempBoundary and storage is at max capacity ( ThermalGridState( - Some(ThermalHouseState(0L, Celsius(22), Kilowatts(80))), + Some(ThermalHouseState(0L, Celsius(22), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), @@ -394,13 +679,11 @@ class HpModelSpec Kilowatts(95.0), Kilowatts(80.0), ThermalGridState( - Some(ThermalHouseState(0L, Celsius(22), Kilowatts(80))), + Some(ThermalHouseState(0L, Celsius(22), Kilowatts(0))), Some( ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) ), - Some( - ThermalStorageState(0L, KilowattHours(250), Kilowatts(0)) - ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), Some(HouseTemperatureUpperBoundaryReached(0L)), ), @@ -425,9 +708,7 @@ class HpModelSpec Some( ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) ), - Some( - ThermalStorageState(0L, KilowattHours(250), Kilowatts(0)) - ), + Some(ThermalStorageState(0L, KilowattHours(250), Kilowatts(0))), ), None, ), @@ -435,6 +716,7 @@ class HpModelSpec ), ) + // Run the test cases forAll(testCases) { ( @@ -448,11 +730,7 @@ class HpModelSpec // Initialize the house and grid models val house = thermalHouse(18, 22).copy(ethLosses = WattsPerKelvin(200)) - val grid = thermalGrid( - house, - Some(thermalStorage), - Some(domesticHotWaterStorage), - ) + val grid = thermalGrid(house, Some(thermalStorage)) val hp = hpModel(grid) // Create relevant data for the current test @@ -464,7 +742,6 @@ class HpModelSpec thermalState.houseState.map(_.tick).getOrElse(0L) ) - // Invoke determineFlexOptions and match the results hp.determineFlexOptions(relevantData, lastState) match { case ProvideMinMaxFlexOptions( modelUuid, 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..9d279ede07 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridSpec.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.model.thermal +import edu.ie3.simona.exceptions.InvalidParameterException import edu.ie3.simona.model.thermal.ThermalGrid.ThermalEnergyDemand import edu.ie3.simona.test.common.UnitSpec import squants.energy.{MegawattHours, WattHours, Watts} @@ -20,14 +21,22 @@ class ThermalGridSpec extends UnitSpec { "Testing the thermal energy demand" when { "instantiating it from given values" should { - "correct non-sensible input" in { + "throw exception for non-sensible input (positive)" in { val possible = MegawattHours(40d) val required = MegawattHours(42d) - val energyDemand = ThermalEnergyDemand(required, possible) + intercept[InvalidParameterException] { + ThermalEnergyDemand(required, possible) + }.getMessage shouldBe s"The possible amount of energy {$possible} is smaller than the required amount of energy {$required}. This is not supported." + } - energyDemand.required should approximate(possible) - energyDemand.possible should approximate(possible) + "throw exception for non-sensible input (negative)" in { + val possible = MegawattHours(-40d) + val required = MegawattHours(-42d) + + intercept[InvalidParameterException] { + ThermalEnergyDemand(required, possible) + }.getMessage shouldBe s"The possible amount of energy {$possible} is smaller than the required amount of energy {$required}. This is not supported." } "set the correct values, if they are sensible" in { @@ -51,22 +60,30 @@ class ThermalGridSpec extends UnitSpec { } "checking for required and additional demand" should { - "return proper information, if no required but additional demand is apparent" in { + "return proper information, if no required and no additional demand is apparent" in { val required = MegawattHours(0d) - val possible = MegawattHours(45d) + val possible = MegawattHours(0d) val energyDemand = ThermalEnergyDemand(required, possible) energyDemand.hasRequiredDemand shouldBe false - energyDemand.hasAdditionalDemand shouldBe true + energyDemand.hasAdditionalDemand shouldBe false } - "return proper information, if required but no additional demand is apparent" in { - val required = MegawattHours(45d) + "return proper information, if no required but additional demand is apparent" in { + val required = MegawattHours(0d) val possible = MegawattHours(45d) val energyDemand = ThermalEnergyDemand(required, possible) - energyDemand.hasRequiredDemand shouldBe true - energyDemand.hasAdditionalDemand shouldBe false + energyDemand.hasRequiredDemand shouldBe false + energyDemand.hasAdditionalDemand shouldBe true + } + + "throw exception, if required demand is higher than possible demand" in { + val required = MegawattHours(1d) + val possible = MegawattHours(0d) + intercept[InvalidParameterException] { + ThermalEnergyDemand(required, possible) + }.getMessage shouldBe s"The possible amount of energy {$possible} is smaller than the required amount of energy {$required}. This is not supported." } "return proper information, if required and additional demand is apparent" in { 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 8515aad93f..2677456c76 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala @@ -373,6 +373,7 @@ class ThermalGridWithHouseAndStorageSpec tick, testGridAmbientTemperature, testGridAmbientTemperature, + testGridAmbientTemperature, gridState, externalQDot, defaultSimulationStart, @@ -448,6 +449,7 @@ class ThermalGridWithHouseAndStorageSpec None, testGridAmbientTemperature, testGridAmbientTemperature, + testGridAmbientTemperature, testGridQDotConsumption, ) match { case (maybeRevisedHouseState, maybeRevisedStorageState) => @@ -639,6 +641,7 @@ class ThermalGridWithHouseAndStorageSpec formerStorageState, ambientTemperature, ambientTemperature, + ambientTemperature, zeroInflux, ) match { case ( @@ -830,6 +833,7 @@ class ThermalGridWithHouseAndStorageSpec tick, testGridAmbientTemperature, testGridAmbientTemperature, + testGridAmbientTemperature, gridState, false, 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 bcba09f89d..f9b1b9a775 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala @@ -277,6 +277,7 @@ class ThermalGridWithHouseOnlySpec tick, testGridAmbientTemperature, testGridAmbientTemperature, + testGridAmbientTemperature, gridState, testGridQDotConsumption, defaultSimulationStart,