Skip to content

Commit cfebac9

Browse files
Merge pull request #1038 from ie3-institute/ms/#1034-consider-PrimaryData-that-start-before-simulation
Considering primary data that start before simulation.
2 parents aa47b9f + d47832e commit cfebac9

File tree

9 files changed

+106
-73
lines changed

9 files changed

+106
-73
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Enhancing config with more default value [#1413](https://github.com/ie3-institute/simona/issues/1413)
1515
- Updated Authors.md file [#1503](https://github.com/ie3-institute/simona/issues/1503)
1616
- Quantity to squants conversion in WeatherService [#1506](https://github.com/ie3-institute/simona/issues/1506)
17+
- Considering primary data that start before simulation [#1034](https://github.com/ie3-institute/simona/issues/1034)
1718

1819
### Changed
1920
- Upgraded `scala2` to `scala3` [#53](https://github.com/ie3-institute/simona/issues/53)

src/main/scala/edu/ie3/simona/service/SimonaService.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import org.apache.pekko.actor.typed.scaladsl.{
3232
StashBuffer,
3333
}
3434
import org.apache.pekko.actor.typed.{ActorRef, Behavior}
35+
import org.slf4j.Logger
3536

3637
import scala.util.{Failure, Success, Try}
3738

@@ -96,7 +97,7 @@ abstract class SimonaService {
9697
case (ctx, Activation(INIT_SIM_TICK)) =>
9798
// init might take some time and could go wrong if invalid initialize service data is received
9899
// execute complete and unstash only if init is carried out successfully
99-
init(initializeStateData) match {
100+
init(initializeStateData)(using ctx.log) match {
100101
case Success((serviceStateData, maybeNewTick)) =>
101102
scheduler ! Completion(
102103
ctx.self,
@@ -223,13 +224,15 @@ abstract class SimonaService {
223224
*
224225
* @param initServiceData
225226
* The data that should be used for initialization
227+
* @param log
228+
* The logger for logging.
226229
* @return
227230
* The state data of this service and optional tick that should be included
228231
* in the completion message
229232
*/
230233
def init(
231234
initServiceData: InitializeServiceStateData
232-
): Try[(S, Option[Long])]
235+
)(using log: Logger): Try[(S, Option[Long])]
233236

234237
/** Handle a request to register for information from this service
235238
*

src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import edu.ie3.simona.util.ReceiveDataMap
3434
import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK
3535
import org.apache.pekko.actor.typed.ActorRef
3636
import org.apache.pekko.actor.typed.scaladsl.ActorContext
37+
import org.slf4j.Logger
3738

3839
import java.util.UUID
3940
import scala.jdk.CollectionConverters.*
@@ -59,7 +60,7 @@ object ExtEvDataService extends SimonaService with ExtDataSupport {
5960

6061
override def init(
6162
initServiceData: ServiceStateData.InitializeServiceStateData
62-
): Try[
63+
)(using log: Logger): Try[
6364
(
6465
ExtEvStateData,
6566
Option[Long],

src/main/scala/edu/ie3/simona/service/load/LoadProfileService.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import edu.ie3.simona.util.SimonaConstants.FIRST_TICK_IN_SIMULATION
3030
import edu.ie3.simona.util.TickUtil.TickLong
3131
import org.apache.pekko.actor.typed.ActorRef
3232
import org.apache.pekko.actor.typed.scaladsl.ActorContext
33+
import org.slf4j.Logger
3334

3435
import java.time.ZonedDateTime
3536
import scala.util.{Failure, Success, Try}
@@ -78,7 +79,7 @@ object LoadProfileService extends SimonaService {
7879

7980
override def init(
8081
initServiceData: InitializeServiceStateData
81-
): Try[(LoadProfileInitializedStateData, Option[Long])] =
82+
)(using log: Logger): Try[(LoadProfileInitializedStateData, Option[Long])] =
8283
initServiceData match {
8384
case InitLoadProfileServiceStateData(
8485
dataSource,

src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,10 @@ object PrimaryServiceWorker extends SimonaService {
155155

156156
override type S = PrimaryServiceInitializedStateData[Value]
157157

158-
/** Initialize the actor with the given information. Try to figure out the
159-
* initialized state data and the next activation ticks, that will then be
160-
* sent to the scheduler
161-
*
162-
* @param initServiceData
163-
* The data that should be used for initialization
164-
* @return
165-
* The state data of this service actor and optional tick that should be
166-
* included in the completion message
167-
*/
168158
override def init(
169159
initServiceData: InitializeServiceStateData
160+
)(using
161+
log: Logger
170162
): Try[(PrimaryServiceInitializedStateData[Value], Option[Long])] = {
171163
(initServiceData match {
172164
case PrimaryServiceWorker.CsvInitPrimaryServiceStateData(
@@ -255,11 +247,13 @@ object PrimaryServiceWorker extends SimonaService {
255247
.map(_.toTick)
256248
).pop
257249

258-
(maybeNextTick, furtherActivationTicks) match {
259-
case (Some(tick), furtherActivationTicks) if tick == 0L =>
250+
val previousOption =
251+
source.getLastTimeKeyBefore(simulationStart).toScala
252+
253+
(maybeNextTick, previousOption) match {
254+
case (Some(tick), _) if tick == 0L =>
260255
/* Set up the state data and determine the next activation tick. */
261-
val initializedStateData
262-
: PrimaryServiceInitializedStateData[Value] =
256+
val initializedStateData =
263257
PrimaryServiceInitializedStateData(
264258
maybeNextTick,
265259
furtherActivationTicks,
@@ -270,14 +264,54 @@ object PrimaryServiceWorker extends SimonaService {
270264

271265
Success(initializedStateData, maybeNextTick)
272266

273-
case (Some(tick), _) if tick > 0L =>
274-
/* The start of the data needs to be at the first tick of the simulation. */
267+
case (Some(tick), None) if tick > 0L =>
268+
/* No data for the first tick or before, but the start of the data needs to be at the first tick of the simulation. */
275269
Failure(
276270
new ServiceRegistrationException(
277271
s"The data for the timeseries '${source.getTimeSeries.getUuid}' starts after the start of this simulation (tick: $tick)! This is not allowed!"
278272
)
279273
)
280274

275+
case (Some(_), Some(value)) =>
276+
/* We have data before and after the start of the simulation, but not at tick 0 */
277+
log.debug(
278+
s"No data at the start of the simulation. Use last know data for tick: ${value.toTick}"
279+
)
280+
281+
val startTick = Some(0L)
282+
283+
/* Set up the state data. */
284+
val initializedStateData =
285+
PrimaryServiceInitializedStateData(
286+
startTick,
287+
furtherActivationTicks,
288+
simulationStart,
289+
valueClass,
290+
source,
291+
)
292+
293+
Success(initializedStateData, startTick)
294+
295+
case (_, Some(value)) =>
296+
/* We have data before, but not after the start of the simulation */
297+
log.warn(
298+
s"Only found data before the start of the simulation. Tick: ${value.toTick}"
299+
)
300+
301+
val nextTick = Some(0L)
302+
303+
/* Set up the state data. */
304+
val initializedStateData =
305+
PrimaryServiceInitializedStateData(
306+
nextTick,
307+
furtherActivationTicks,
308+
simulationStart,
309+
valueClass,
310+
source,
311+
)
312+
313+
Success(initializedStateData, nextTick)
314+
281315
case _ =>
282316
/* No data for the simulation. */
283317
Failure(
@@ -316,18 +350,6 @@ object PrimaryServiceWorker extends SimonaService {
316350
)
317351
}
318352

319-
/** Send out the information to all registered recipients
320-
*
321-
* @param tick
322-
* Current tick data should be announced for
323-
* @param serviceBaseStateData
324-
* The current state data of this service
325-
* @return
326-
* The service stata data that should be used in the next state (normally
327-
* with updated values) together with the completion message that is sent
328-
* in response to the trigger that is sent to start the initialization
329-
* process
330-
*/
331353
override protected def announceInformation(
332354
tick: Long
333355
)(using
@@ -340,7 +362,7 @@ object PrimaryServiceWorker extends SimonaService {
340362
/* Get the information to distribute */
341363
val simulationTime =
342364
tick.toDateTime(using serviceBaseStateData.startDateTime)
343-
serviceBaseStateData.source.getValue(simulationTime).toScala match {
365+
serviceBaseStateData.source.getValueOrLast(simulationTime).toScala match {
344366
case Some(value) =>
345367
processDataAndAnnounce(tick, value, serviceBaseStateData)(using
346368
ctx.self,
@@ -438,17 +460,12 @@ object PrimaryServiceWorker extends SimonaService {
438460
PrimaryServiceInitializedStateData[V],
439461
Option[Long],
440462
) = {
441-
val (maybeNextTick, remainderActivationTicks) =
442-
serviceBaseStateData.activationTicks.pop
443-
val updatedStateData =
444-
serviceBaseStateData.copy(
445-
maybeNextActivationTick = maybeNextTick,
446-
activationTicks = remainderActivationTicks,
447-
)
463+
val (updatedStateData, maybeNextTick) =
464+
updateStateDataAndBuildTriggerMessages(serviceBaseStateData)
448465

449-
val provisionMessage =
450-
DataProvision(tick, self, primaryData, maybeNextTick)
466+
val provisionMessage = DataProvision(tick, self, primaryData, maybeNextTick)
451467
serviceBaseStateData.subscribers.foreach(_ ! provisionMessage)
468+
452469
(updatedStateData, maybeNextTick)
453470
}
454471
}

src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import edu.ie3.simona.util.TickUtil.RichZonedDateTime
3333
import edu.ie3.util.scala.collection.immutable.SortedDistinctSeq
3434
import org.apache.pekko.actor.typed.ActorRef
3535
import org.apache.pekko.actor.typed.scaladsl.ActorContext
36+
import org.slf4j.Logger
3637

3738
import java.time.ZonedDateTime
3839
import scala.util.{Failure, Success, Try}
@@ -88,23 +89,9 @@ object WeatherService extends SimonaService {
8889
simulationEnd: ZonedDateTime,
8990
) extends InitializeServiceStateData
9091

91-
/** Initialize the concrete service implementation using the provided
92-
* initialization data. This method should perform all heavyweight tasks
93-
* before the actor becomes ready. The return values are a) the state data of
94-
* the initialized service and b) optional triggers that should be sent to
95-
* the [[edu.ie3.simona.scheduler.Scheduler]] together with the completion
96-
* message that is sent in response to the trigger that is sent to start the
97-
* initialization process
98-
*
99-
* @param initServiceData
100-
* the data that should be used for initialization
101-
* @return
102-
* the state data of this service and optional triggers that should be
103-
* included in the completion message
104-
*/
10592
override def init(
10693
initServiceData: InitializeServiceStateData
107-
): Try[(WeatherInitializedStateData, Option[Long])] =
94+
)(using log: Logger): Try[(WeatherInitializedStateData, Option[Long])] =
10895
initServiceData match {
10996
case InitWeatherServiceStateData(
11097
sourceDefinition,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"time";"p"

src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,16 @@ class PrimaryServiceWorkerSpec
110110
}
111111
}
112112

113-
"fail to init, if time series ends with delay before simulation start" in {
113+
"fail to init, if time series has no data" in {
114114
val initData = validInitData.copy(
115-
simulationStart = validInitData.simulationStart.plusHours(1)
115+
timeSeriesUuid = uuidEmpty,
116+
filePath = Paths.get("its_p_" + uuidEmpty),
117+
simulationStart = validInitData.simulationStart.plusHours(1),
116118
)
117119

118120
PrimaryServiceWorker.init(initData) match {
119121
case Failure(exception) =>
120-
exception.getMessage shouldBe "No appropriate data found within simulation time range in timeseries '9185b8c1-86ba-4a16-8dea-5ac898e8caa5'!"
122+
exception.getMessage shouldBe "No appropriate data found within simulation time range in timeseries 'b73a7e3f-9045-40cd-b518-c11a9a6a1025'!"
121123
case Success(_) =>
122124
fail("Initialisation with unsupported init data is meant to fail.")
123125
}
@@ -136,6 +138,34 @@ class PrimaryServiceWorkerSpec
136138
}
137139
}
138140

141+
"init, if there is a value before the simulation start" in {
142+
val initData = validInitData.copy(
143+
simulationStart = validInitData.simulationStart.plusHours(1)
144+
)
145+
146+
PrimaryServiceWorker.init(initData) match {
147+
case Success((_, maybeNextTick)) =>
148+
maybeNextTick shouldBe Some(0L)
149+
150+
case Failure(_) =>
151+
fail("Initialisation with init data is meant to succeed.")
152+
}
153+
}
154+
155+
"init, if there are values before and after the simulation start" in {
156+
val initData = validInitData.copy(
157+
simulationStart = validInitData.simulationStart.plusMinutes(5)
158+
)
159+
160+
PrimaryServiceWorker.init(initData) match {
161+
case Success((_, maybeNextTick)) =>
162+
maybeNextTick shouldBe Some(0L)
163+
164+
case Failure(_) =>
165+
fail("Initialisation with init data is meant to succeed.")
166+
}
167+
}
168+
139169
"fail, if pointed to the wrong file" in {
140170
// time series exists, but is malformed
141171
val tsUuid = UUID.fromString("3fbfaa97-cff4-46d4-95ba-a95665e87c27")
@@ -377,20 +407,10 @@ class PrimaryServiceWorkerSpec
377407
)
378408
}
379409

380-
"should not announce anything, if time step is not covered in source" in {
381-
382-
serviceRef ! Activation(200)
383-
384-
val completionMsg = scheduler.expectMessageType[Completion]
385-
completionMsg.newTick shouldBe Some(1800)
386-
387-
systemParticipant.expectNoMessage()
388-
}
389-
390-
"should announce something, if the time step is covered in source" in {
410+
"should announce something" in {
391411
serviceRef ! Activation(900)
392412
val completionMsg = scheduler.expectMessageType[Completion]
393-
completionMsg.newTick shouldBe None
413+
completionMsg.newTick shouldBe Some(1800)
394414

395415
inside(
396416
systemParticipant.expectMessageType[DataProvision[PrimaryData]]
@@ -408,7 +428,7 @@ class PrimaryServiceWorkerSpec
408428
p should approximate(Kilowatts(1250.0))
409429
case _ => fail("Expected to get active power only.")
410430
}
411-
nextDataTick shouldBe None
431+
nextDataTick shouldBe Some(1800)
412432
}
413433
}
414434
}

src/test/scala/edu/ie3/simona/test/common/input/TimeSeriesTestData.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import java.util.UUID
1515
trait TimeSeriesTestData {
1616
protected val uuidP: UUID =
1717
UUID.fromString("9185b8c1-86ba-4a16-8dea-5ac898e8caa5")
18+
protected val uuidEmpty: UUID =
19+
UUID.fromString("b73a7e3f-9045-40cd-b518-c11a9a6a1025")
1820
protected val uuidPq: UUID =
1921
UUID.fromString("3fbfaa97-cff4-46d4-95ba-a95665e87c26")
2022
protected val uuidPqh: UUID =

0 commit comments

Comments
 (0)