Skip to content

Commit 1c28c46

Browse files
committed
Merge branch 'dev' into ms/#53-upgrate-to-scala3
# Conflicts: # CHANGELOG.md # build.gradle # src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala # src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala
2 parents 4f9c4cd + 78bc19d commit 1c28c46

34 files changed

+586
-503
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ on:
2323
jobs:
2424
buildAndTest:
2525
runs-on: ubuntu-latest
26+
timeout-minutes: 30
2627

2728
steps:
2829
- name: Checkout Source

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4747
- Added support for topologies without transformers and slack grids with multiple nodes [#1099](https://github.com/ie3-institute/simona/issues/1099)
4848
- Checking the number of slack nodes [#1122](https://github.com/ie3-institute/simona/issues/1122)
4949
- Enhance exception message in case of InvalidGridException [#1124](https://github.com/ie3-institute/simona/issues/1124)
50-
- Integration test for thermal grids [#1145](https://github.com/ie3-institute/simona/issues/1145)
5150
- Added `VoltageLimits` [#1133](https://github.com/ie3-institute/simona/issues/1133)
5251
- Introducing new ParticipantAgent and ParticipantModel [#1134](https://github.com/ie3-institute/simona/issues/1134)
5352
- Using new `ParticipantAgent.Request` messages everywhere [#1195](https://github.com/ie3-institute/simona/issues/1195)
@@ -62,6 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6261
- Replace `EvcsModel` with its new implementation [#1151](https://github.com/ie3-institute/simona/issues/1151)
6362
- Fix determination of qDotIntoGrid in HpModel.calcState() in case heatStorage should feed the thermal grid [#1165](https://github.com/ie3-institute/simona/issues/1165)
6463
- Replace `BmModel` with its new implementation [#1157](https://github.com/ie3-institute/simona/issues/1157)
64+
- Integration test for thermal grids without Em [#1145](https://github.com/ie3-institute/simona/issues/1145)
65+
- Change thermal house behaviour to heat till targetTemperature [#1176](https://github.com/ie3-institute/simona/issues/1176)
6566

6667
### Changed
6768
- Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435)
@@ -160,6 +161,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
160161
- Updated Gradle to version 8.13 and removed deprecated syntax [#1286](https://github.com/ie3-institute/PowerSystemDataModel/issues/1286)
161162
- Consider inputContainer when initialize participant models [#1251](https://github.com/ie3-institute/simona/issues/1251)
162163
- Change logging level for unsupported messages from ExtDataSupport [#1286](https://github.com/ie3-institute/simona/issues/1286)
164+
- Converting `WeatherService` to pekko typed [#1216](https://github.com/ie3-institute/simona/issues/1216)
163165
- Upgraded `scala2` to `scala3` [#53](https://github.com/ie3-institute/simona/issues/53)
164166

165167
### Fixed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ dependencies {
105105

106106
/* logging */
107107
implementation "com.typesafe.scala-logging:scala-logging_${scalaVersion}:3.9.5" // pekko scala logging
108-
implementation "ch.qos.logback:logback-classic:1.5.17"
108+
implementation "ch.qos.logback:logback-classic:1.5.18"
109109

110110
/* testing */
111111
// scalatest & junit
@@ -162,7 +162,7 @@ dependencies {
162162
implementation 'javax.measure:unit-api:2.2'
163163
implementation 'tech.units:indriya:2.2.2' // quantities
164164
implementation "org.typelevel:squants_${scalaVersion}:1.8.3"
165-
implementation 'org.apache.commons:commons-csv:1.13.0'
165+
implementation 'org.apache.commons:commons-csv:1.14.0'
166166
implementation "org.scalanlp:breeze_2.13:2.1.0"
167167
//implementation "org.scalanlp:breeze_${scalaVersion}:2.1.0" // scientific calculations (http://www.scalanlp.org/)
168168
implementation 'de.lmu.ifi.dbs.elki:elki:0.7.5' // Statistics (for random load model)

docs/readthedocs/models/thermal_house_model.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,22 @@ This page documents the functionality of the thermal house available in SIMONA.
66

77
## Behaviour
88

9-
This house model represents the thermal behaviour of a building. This reflects a simple shoebox with a thermal capacity and with transmission losses.
9+
This house model represents the thermal behaviour of a building. It represents a simple shoebox with thermal capacity and transmission losses.
10+
The house can optionally be equipped with a {ref}`cts_model` as thermal storage. Both are connected by the {ref}`thermal_grid_model`.
11+
12+
The thermal house provides two different energy demands. The required demand indicates that the inner temperature of the house is below the lower temperature boundary and thus, requires mandatory heating. An additional demand indicates the amount of energy necessary to reach the target temperature. Additional demand not necessarily requires to be covered but could, e.g. for flexibility purposes.
13+
14+
There are different operating modes, depending on whether the heat source is em-controlled or not.
15+
16+
### Behaviour without EM control
17+
18+
If the heat source of this building is not under {ref}`em` control, the internal temperature of the house should remain between the target temperature and lower temperature boundary. If the temperature falls below the lower temperature limit, the available energy from the storage is used first. If the storage
19+
is empty, the heat pump will first heat the house up to the target temperature and then refill the storage.
20+
As the storage is initialised as empty, the heat source will start charging the storage first. Whenever the heat source is in operation (e.g. to charge the storage), it will continue to operate until the house has reached the target temperature again.
21+
22+
### Behaviour under EM control
23+
24+
Currently, not fully supported. Will be fixed by [PR #1159](https://github.com/ie3-institute/simona/pull/1159)
1025

1126
## Attributes, Units and Remarks
1227

src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package edu.ie3.simona.agent
88

99
import edu.ie3.simona.event.RuntimeEvent
1010
import edu.ie3.simona.ontology.messages.SchedulerMessage
11-
import edu.ie3.simona.ontology.messages.services.EvMessage
11+
import edu.ie3.simona.ontology.messages.services.{EvMessage, WeatherMessage}
1212
import org.apache.pekko.actor.typed.ActorRef
1313
import org.apache.pekko.actor.{ActorRef => ClassicRef}
1414

@@ -30,6 +30,6 @@ final case class EnvironmentRefs(
3030
scheduler: ActorRef[SchedulerMessage],
3131
runtimeEventListener: ActorRef[RuntimeEvent],
3232
primaryServiceProxy: ClassicRef,
33-
weather: ClassicRef,
33+
weather: ActorRef[WeatherMessage],
3434
evDataService: Option[ActorRef[EvMessage]],
3535
)

src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage
3838
import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation
3939
import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexResponse
4040
import edu.ie3.simona.scheduler.ScheduleLock
41+
import edu.ie3.simona.ontology.messages.services.{
42+
ServiceMessage,
43+
WeatherMessage,
44+
}
4145
import edu.ie3.simona.service.ServiceType
4246
import edu.ie3.simona.util.ConfigUtil
4347
import edu.ie3.simona.util.ConfigUtil._
@@ -337,11 +341,11 @@ class GridAgentBuilder(
337341
maybeControllingEm: Option[ActorRef[FlexResponse]],
338342
): ActorRef[ParticipantAgent.Request] = {
339343

340-
val serviceMap: Map[ServiceType, ClassicRef] =
344+
val serviceMap: Map[ServiceType, ActorRef[_ >: ServiceMessage]] =
341345
Seq(
342346
Some(ServiceType.WeatherService -> environmentRefs.weather),
343347
environmentRefs.evDataService.map(ref =>
344-
ServiceType.EvMovementService -> ref.toClassic
348+
ServiceType.EvMovementService -> ref
345349
),
346350
).flatten.toMap
347351

@@ -522,7 +526,7 @@ class GridAgentBuilder(
522526
thermalGrid: ThermalGrid,
523527
modelConfiguration: HpRuntimeConfig,
524528
primaryServiceProxy: ClassicRef,
525-
weatherService: ClassicRef,
529+
weatherService: ActorRef[WeatherMessage],
526530
requestVoltageDeviationThreshold: Double,
527531
outputConfig: NotifierConfig,
528532
maybeControllingEm: Option[ActorRef[FlexResponse]],
@@ -536,7 +540,7 @@ class GridAgentBuilder(
536540
thermalGrid,
537541
modelConfiguration,
538542
primaryServiceProxy,
539-
Iterable(ActorWeatherService(weatherService)),
543+
Iterable(ActorWeatherService(weatherService.toClassic)),
540544
simulationStartDate,
541545
simulationEndDate,
542546
resolution,

src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import edu.ie3.simona.model.participant.{
2424
SystemParticipant,
2525
}
2626
import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage
27+
import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps
2728

2829
trait ServiceRegistration[
2930
PD <: PrimaryDataWithComplexPower[PD],
@@ -117,7 +118,7 @@ trait ServiceRegistration[
117118
s"is invalid."
118119
)
119120
}
120-
serviceRef ! RegisterForWeatherMessage(participantRef, lat, lon)
121+
serviceRef ! RegisterForWeatherMessage(participantRef.toTyped, lat, lon)
121122
}
122123

123124
}

src/main/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInit.scala

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{
2020
ScheduleActivation,
2121
}
2222
import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._
23+
import edu.ie3.simona.ontology.messages.services.ServiceMessage
2324
import edu.ie3.simona.ontology.messages.services.ServiceMessage.{
2425
PrimaryServiceRegistrationMessage,
2526
RegisterForEvDataMessage,
@@ -59,7 +60,7 @@ object ParticipantAgentInit {
5960
final case class ParticipantRefs(
6061
gridAgent: ActorRef[GridAgent.Request],
6162
primaryServiceProxy: ClassicRef,
62-
services: Map[ServiceType, ClassicRef],
63+
services: Map[ServiceType, ActorRef[_ >: ServiceMessage]],
6364
resultListener: Iterable[ActorRef[ResultEvent]],
6465
)
6566

@@ -248,12 +249,14 @@ object ParticipantAgentInit {
248249
// requiring at least one secondary service, thus send out registrations and wait for replies
249250
val requiredServices = requiredServiceTypes
250251
.map(serviceType =>
251-
serviceType -> participantRefs.services.getOrElse(
252-
serviceType,
253-
throw new CriticalFailureException(
254-
s"${modelShell.identifier}: Service of type $serviceType is not available."
255-
),
256-
)
252+
serviceType -> participantRefs.services
253+
.getOrElse(
254+
serviceType,
255+
throw new CriticalFailureException(
256+
s"${modelShell.identifier}: Service of type $serviceType is not available."
257+
),
258+
)
259+
.toClassic
257260
)
258261
.toMap
259262

@@ -291,11 +294,7 @@ object ParticipantAgentInit {
291294

292295
Option(geoPosition.getY).zip(Option(geoPosition.getX)) match {
293296
case Some((lat, lon)) =>
294-
serviceRef ! RegisterForWeatherMessage(
295-
participantRef.toClassic,
296-
lat,
297-
lon,
298-
)
297+
serviceRef ! RegisterForWeatherMessage(participantRef, lat, lon)
299298
case _ =>
300299
throw new CriticalFailureException(
301300
s"${modelShell.identifier} cannot register for weather information at " +

src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ final case class ThermalGrid(
6262
lastHpState: HpState,
6363
): (ThermalDemandWrapper, ThermalGridState) = {
6464
/* First get the energy demand of the houses but only if inner temperature is below target temperature */
65-
6665
val (houseDemand, updatedHouseState) =
6766
house.zip(lastHpState.thermalGridState.houseState) match {
6867
case Some((thermalHouse, lastHouseState)) =>
@@ -76,8 +75,7 @@ final case class ThermalGrid(
7675
lastHouseState.qDot,
7776
)
7877
if (
79-
updatedHouseState.innerTemperature < thermalHouse.targetTemperature |
80-
(lastHouseState.qDot > zeroKW && updatedHouseState.innerTemperature < thermalHouse.upperBoundaryTemperature)
78+
updatedHouseState.innerTemperature < thermalHouse.targetTemperature
8179
) {
8280
(
8381
thermalHouse.energyDemand(
@@ -86,11 +84,9 @@ final case class ThermalGrid(
8684
),
8785
Some(updatedHouseState),
8886
)
89-
9087
} else {
9188
(ThermalEnergyDemand.noDemand, Some(updatedHouseState))
9289
}
93-
9490
case None =>
9591
(ThermalEnergyDemand.noDemand, None)
9692
}

src/main/scala/edu/ie3/simona/model/thermal/ThermalHouse.scala

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import edu.ie3.datamodel.models.input.thermal.{
1515
import edu.ie3.simona.model.participant.HpModel.HpRelevantData
1616
import edu.ie3.simona.model.thermal.ThermalGrid.ThermalEnergyDemand
1717
import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{
18+
HouseTargetTemperatureReached,
1819
HouseTemperatureLowerBoundaryReached,
19-
HouseTemperatureTargetOrUpperBoundaryReached,
2020
}
2121
import edu.ie3.simona.model.thermal.ThermalHouse.{
2222
ThermalHouseState,
@@ -75,61 +75,44 @@ final case class ThermalHouse(
7575
bus,
7676
) {
7777

78-
/** Calculate the energy demand at the instance in question. If the inner
79-
* temperature is at or above the lower boundary temperature, there is no
80-
* demand. If it is below the target temperature, the demand is the energy
81-
* needed to heat up the house to the maximum temperature. The current
82-
* (external) thermal infeed is not accounted for, as we assume, that after
83-
* determining the thermal demand, a change in external infeed will take
84-
* place.
78+
/** Calculates the energy demand at the instance in question by calculating
79+
* the [[ThermalEnergyDemand]] to reach target temperature from actual inner
80+
* temperature. Since [[ThermalEnergyDemand]] is two parted, requiredEnergy
81+
* and possibleEnergy, both have to be determined. RequiredEnergy: In case
82+
* the inner temperature is at or below the lower boundary temperature, the
83+
* energy demand till target temperature is interpreted as requiredEnergy.
84+
* Otherwise, the required energy will be zero. PossibleEnergy: In case the
85+
* inner temperature is not at or above the target temperature, the energy
86+
* demand to reach targetTemperature is interpreted as possible energy.
87+
* Otherwise, it will be zero. The current (external) thermal infeed is not
88+
* accounted for, as we assume, that after determining the thermal demand, a
89+
* change in external infeed will take place.
8590
*
8691
* @param relevantData
8792
* Data of heat pump including state of the heat pump.
88-
* @param state
93+
* @param currentThermalHouseState
8994
* Most recent state, that is valid for this model.
9095
* @return
9196
* The needed energy in the questioned tick.
9297
*/
9398
def energyDemand(
9499
relevantData: HpRelevantData,
95-
state: ThermalHouseState,
100+
currentThermalHouseState: ThermalHouseState,
96101
): ThermalEnergyDemand = {
97-
/* Calculate the inner temperature of the house, at the questioned instance in time */
98-
val duration = Seconds(relevantData.currentTick - state.tick)
99-
val currentInnerTemp = newInnerTemperature(
100-
state.qDot,
101-
duration,
102-
state.innerTemperature,
103-
relevantData.ambientTemperature,
104-
)
102+
// Since we updated the state before, we can directly take the innerTemperature
103+
val currentInnerTemp = currentThermalHouseState.innerTemperature
105104

106-
/* Determine, which temperature boundary triggers a needed energy to reach the temperature constraints */
107-
val temperatureToTriggerRequiredEnergy =
108-
if (
109-
currentInnerTemp <= state.innerTemperature &&
110-
state.qDot <= zeroKW
111-
) {
112-
// temperature has been decreasing and heat source has been turned off
113-
// => we have reached target temp before and are now targeting lower temp
114-
lowerBoundaryTemperature
115-
} else targetTemperature
116105
val requiredEnergy =
117-
if (
118-
isInnerTemperatureTooLow(
119-
currentInnerTemp,
120-
temperatureToTriggerRequiredEnergy,
121-
)
122-
) energy(targetTemperature, currentInnerTemp)
123-
else
124-
zeroMWh
106+
if (isInnerTemperatureTooLow(currentInnerTemp)) {
107+
energy(targetTemperature, currentInnerTemp)
108+
} else
109+
zeroKWh
125110

126111
val possibleEnergy =
127112
if (!isInnerTemperatureTooHigh(currentInnerTemp)) {
128-
// if upper boundary has not been reached,
129-
// there is an amount of optional energy that could be stored
130-
energy(upperBoundaryTemperature, currentInnerTemp)
131-
} else
132-
zeroMWh
113+
energy(targetTemperature, currentInnerTemp)
114+
} else zeroKWh
115+
133116
ThermalEnergyDemand(requiredEnergy, possibleEnergy)
134117
}
135118

@@ -158,25 +141,28 @@ final case class ThermalHouse(
158141
ethCapa * temperatureDiff
159142
}
160143

161-
/** Check if inner temperature is higher than preferred maximum temperature
144+
/** Check if inner temperature is higher than preferred maximum temperature.
162145
* @param innerTemperature
163-
* The inner temperature of the house
146+
* The inner temperature of the house.
164147
* @param boundaryTemperature
165-
* The applied boundary temperature to check against
166-
*
148+
* The applied boundary temperature to check against.
167149
* @return
168150
* True, if inner temperature is too high.
169151
*/
170152
def isInnerTemperatureTooHigh(
171153
innerTemperature: Temperature,
172-
boundaryTemperature: Temperature = upperBoundaryTemperature,
154+
boundaryTemperature: Temperature = targetTemperature,
173155
): Boolean =
174156
innerTemperature > (
175157
boundaryTemperature - temperatureTolerance
176158
)
177159

178-
/** Check if inner temperature is lower than preferred minimum temperature
160+
/** Check if inner temperature is lower than preferred minimum temperature.
179161
*
162+
* @param innerTemperature
163+
* The inner temperature of the house.
164+
* @param boundaryTemperature
165+
* The applied boundary temperature to check against.
180166
* @return
181167
* true, if inner temperature is too low
182168
*/
@@ -316,10 +302,10 @@ final case class ThermalHouse(
316302
/* House has more gain than losses */
317303
nextActivation(
318304
tick,
319-
upperBoundaryTemperature,
305+
targetTemperature,
320306
innerTemperature,
321307
resultingQDot,
322-
).map(HouseTemperatureTargetOrUpperBoundaryReached)
308+
).map(HouseTargetTemperatureReached)
323309
} else {
324310
/* House is in perfect balance */
325311
None
@@ -405,7 +391,7 @@ object ThermalHouse {
405391
final case class HouseTemperatureLowerBoundaryReached(
406392
override val tick: Long
407393
) extends ThermalThreshold
408-
final case class HouseTemperatureTargetOrUpperBoundaryReached(
394+
final case class HouseTargetTemperatureReached(
409395
override val tick: Long
410396
) extends ThermalThreshold
411397
}

0 commit comments

Comments
 (0)