Skip to content

Commit

Permalink
Merge branch 'sp/#1228-fix-state-ParticipantModelShell' into sp/#1153-…
Browse files Browse the repository at this point in the history
…new-StorageModel
  • Loading branch information
sebastian-peter committed Feb 25, 2025
2 parents 9621afd + d05a128 commit da6ad30
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,9 @@ object ParticipantAgent {
): Behavior[Request] =
Behaviors.receivePartial {
case (ctx, request: ParticipantRequest) =>
// ParticipantRequests are always directly answered
// without taking into account possible new input data
val updatedShell = modelShell
.updateModelInput(
inputHandler.getData,
gridAdapter.nodalVoltage,
request.tick,
)
.handleRequest(ctx, request)

ParticipantAgent(
Expand Down Expand Up @@ -400,7 +397,7 @@ object ParticipantAgent {

val (updatedShell, updatedGridAdapter) = Scope(modelShell)
.map(
_.updateModelInput(
_.handleInputData(
inputHandler.getData,
gridAdapter.nodalVoltage,
activation.tick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.{
}
import edu.ie3.simona.agent.participant.data.Data
import edu.ie3.simona.model.participant2.ParticipantModel.{
ModelInput,
ModelState,
OperatingPoint,
}
Expand Down Expand Up @@ -70,7 +69,7 @@ abstract class ParticipantModel[

/** Determines the initial state given an initial model input.
*/
val initialState: ModelInput => S
val initialState: (Long, ZonedDateTime) => S

/** Determines the current state given the last state and the operating point
* that has been valid from the last state up until now.
Expand All @@ -80,17 +79,38 @@ abstract class ParticipantModel[
* @param operatingPoint
* The operating point valid from the simulation time of the last state up
* until now.
* @param input
* The model input data for the current tick.
* @param tick
* The current tick
* @param simulationTime
* The current simulation time
* @return
* The current state.
*/
def determineState(
lastState: S,
operatingPoint: OP,
input: ModelInput,
tick: Long,
simulationTime: ZonedDateTime,
): S

/** Handles input data (primary or secondary) by integrating into the current
* mode state.
*
* @param state
* The current state
* @param receivedData
* The received primary or secondary data.
* @param nodalVoltage
* The voltage at the node that we're connected to.
* @return
* The current state with updated input data
*/
def handleInput(
state: S,
receivedData: Seq[Data],
nodalVoltage: Dimensionless,
): S = state

/** Returns a partial function that transfers the current nodal voltage and
* active power into reactive power based on the participants properties.
*
Expand Down Expand Up @@ -191,24 +211,6 @@ abstract class ParticipantModel[

object ParticipantModel {

/** Holds all potentially relevant input data for model calculation.
*
* @param receivedData
* The received primary or secondary data.
* @param nodalVoltage
* The voltage at the node that we're connected to.
* @param currentTick
* The current tick.
* @param currentSimulationTime
* The current simulation time (matches the tick).
*/
final case class ModelInput(
receivedData: Seq[Data],
nodalVoltage: Dimensionless,
currentTick: Long,
currentSimulationTime: ZonedDateTime,
)

trait OperatingPoint {

val activePower: Power
Expand Down Expand Up @@ -239,14 +241,15 @@ object ParticipantModel {
] {
this: ParticipantModel[OP, FixedState] =>

override val initialState: ModelInput => FixedState =
input => FixedState(input.currentTick)
override val initialState: (Long, ZonedDateTime) => FixedState =
(tick, _) => FixedState(tick)

override def determineState(
lastState: FixedState,
operatingPoint: OP,
input: ModelInput,
): FixedState = FixedState(input.currentTick)
tick: Long,
simulationTime: ZonedDateTime,
): FixedState = FixedState(tick)

}

Expand All @@ -264,15 +267,16 @@ object ParticipantModel {
] {
this: ParticipantModel[OP, DateTimeState] =>

override val initialState: ModelInput => DateTimeState = input =>
DateTimeState(input.currentTick, input.currentSimulationTime)
override val initialState: (Long, ZonedDateTime) => DateTimeState =
(tick, simulationTime) => DateTimeState(tick, simulationTime)

override def determineState(
lastState: DateTimeState,
operatingPoint: OP,
input: ModelInput,
tick: Long,
simulationTime: ZonedDateTime,
): DateTimeState =
DateTimeState(input.currentTick, input.currentSimulationTime)
DateTimeState(tick, simulationTime)

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import edu.ie3.simona.exceptions.CriticalFailureException
import edu.ie3.simona.model.SystemComponent
import edu.ie3.simona.model.em.EmTools
import edu.ie3.simona.model.participant2.ParticipantModel.{
ModelInput,
ModelState,
OperatingPoint,
OperationChangeIndicator,
Expand Down Expand Up @@ -67,9 +66,6 @@ import scala.reflect.ClassTag
* The date and time at which simulation started.
* @param _state
* The most recent model state, if one has been calculated already.
* @param _input
* The most recent model input used for calculation, if it has been received
* already.
* @param _flexOptions
* The most recent flex options, if they have been calculated already.
* @param _lastOperatingPoint
Expand All @@ -94,7 +90,6 @@ final case class ParticipantModelShell[
private val operationInterval: OperationInterval,
private val simulationStartDate: ZonedDateTime,
private val _state: Option[S] = None,
private val _input: Option[ModelInput] = None,
private val _flexOptions: Option[ProvideFlexOptions] = None,
private val _lastOperatingPoint: Option[OP] = None,
private val _operatingPoint: Option[OP] = None,
Expand Down Expand Up @@ -128,18 +123,6 @@ final case class ParticipantModelShell[
def requiredServices: Iterable[ServiceType] =
model.getRequiredSecondaryServices

/** Returns the current relevant data, if present, or throws a
* [[CriticalFailureException]]. Only call this if you are certain the model
* input data has been set.
*
* @return
* The model input data.
*/
private def modelInput: ModelInput =
_input.getOrElse(
throw new CriticalFailureException("No relevant data available!")
)

/** Returns the current operating point, if present, or throws a
* [[CriticalFailureException]]. Only call this if you are certain the
* operating point has been set.
Expand Down Expand Up @@ -177,7 +160,7 @@ final case class ParticipantModelShell[
def reactivePowerFunc: Dimensionless => Power => ReactivePower =
model.reactivePowerFunc

/** Updates the model input according to the received data, the current nodal
/** Updates the model state according to the received data, the current nodal
* voltage and the current tick.
*
* @param receivedData
Expand All @@ -189,23 +172,16 @@ final case class ParticipantModelShell[
* @return
* An updated [[ParticipantModelShell]].
*/
def updateModelInput(
def handleInputData(
receivedData: Seq[Data],
nodalVoltage: Dimensionless,
tick: Long,
): ParticipantModelShell[OP, S] = {
val currentSimulationTime = tick.toDateTime(simulationStartDate)

copy(_input =
Some(
ModelInput(
receivedData,
nodalVoltage,
tick,
currentSimulationTime,
)
)
)
val currentState = determineCurrentState(tick)
val updatedState =
model.handleInput(currentState, receivedData, nodalVoltage)

copy(_state = Some(updatedState))
}

/** Update operating point when the model is '''not''' em-controlled.
Expand Down Expand Up @@ -454,13 +430,23 @@ final case class ParticipantModelShell[
private def determineCurrentState(tick: Long): S = {
// new state is only calculated if there's an old state and an operating point
val state = _state
.zip(_operatingPoint)
.flatMap { case (st, op) =>
Option.when(st.tick < tick) {
model.determineState(st, op, modelInput)
.map { st =>
if (st.tick < tick) {
// If the state is old, an operating point needs
// to be present to determine the curren state
model.determineState(
st,
operatingPoint,
tick,
tick.toDateTime(simulationStartDate),
)
} else {
// The state is up-to-date, no need to update
st
}
}
.getOrElse(model.initialState(modelInput))
// No state present, create an initial one
.getOrElse(model.initialState(tick, tick.toDateTime(simulationStartDate)))

if (state.tick != tick)
throw new CriticalFailureException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.{
import edu.ie3.simona.exceptions.CriticalFailureException
import edu.ie3.simona.model.participant.control.QControl
import edu.ie3.simona.model.participant2.ParticipantModel.{
ModelInput,
ModelState,
OperatingPoint,
OperationChangeIndicator,
Expand All @@ -30,7 +29,7 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage
import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions
import edu.ie3.simona.service.ServiceType
import edu.ie3.util.scala.quantities.{ApparentPower, ReactivePower}
import squants.Power
import squants.{Dimensionless, Power}

import java.time.ZonedDateTime
import java.util.UUID
Expand Down Expand Up @@ -62,22 +61,27 @@ final case class PrimaryDataParticipantModel[PD <: PrimaryData: ClassTag](
PrimaryDataState[PD],
] {

override val initialState: ModelInput => PrimaryDataState[PD] = { input =>
val primaryData = getPrimaryData(input.receivedData)
PrimaryDataState(
primaryData,
input.currentTick,
)
override val initialState: (Long, ZonedDateTime) => PrimaryDataState[PD] = {
(tick, _) =>
PrimaryDataState(
primaryDataExtra.zero,
tick,
)
}

override def determineState(
lastState: PrimaryDataState[PD],
operatingPoint: PrimaryOperatingPoint[PD],
input: ModelInput,
): PrimaryDataState[PD] = initialState(input)
tick: Long,
simulationTime: ZonedDateTime,
): PrimaryDataState[PD] = lastState.copy(tick = tick)

private def getPrimaryData(receivedData: Seq[Data]): PD = {
receivedData
override def handleInput(
state: PrimaryDataState[PD],
receivedData: Seq[Data],
nodalVoltage: Dimensionless,
): PrimaryDataState[PD] = {
val primaryData = receivedData
.collectFirst { case data: PD =>
data
}
Expand All @@ -88,6 +92,8 @@ final case class PrimaryDataParticipantModel[PD <: PrimaryData: ClassTag](
s"got $receivedData"
)
}

state.copy(data = primaryData)
}

override def determineOperatingPoint(
Expand Down
38 changes: 24 additions & 14 deletions src/main/scala/edu/ie3/simona/model/participant2/PvModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import edu.ie3.simona.model.participant.control.QControl
import edu.ie3.simona.model.participant2.ParticipantFlexibility.ParticipantSimpleFlexibility
import edu.ie3.simona.model.participant2.ParticipantModel.{
ActivePowerOperatingPoint,
ModelInput,
ModelState,
}
import edu.ie3.simona.model.participant2.PvModel.PvState
import edu.ie3.simona.ontology.messages.services.WeatherMessage.WeatherData
import edu.ie3.simona.service.ServiceType
import edu.ie3.util.quantities.PowerSystemUnits
import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble
import edu.ie3.util.scala.quantities.DefaultQuantities.zeroWPerSM
import edu.ie3.util.scala.quantities._
import squants._
import squants.space.{Degrees, SquareMeters}
Expand Down Expand Up @@ -74,25 +74,30 @@ class PvModel private (
private val activationThreshold =
sRated.toActivePower(cosPhiRated) * 0.001 * -1

override val initialState: ModelInput => PvState = { input =>
val weatherData = getWeatherData(input.receivedData)

PvState(
input.currentTick,
input.currentSimulationTime,
weatherData.diffIrr,
weatherData.dirIrr,
)
override val initialState: (Long, ZonedDateTime) => PvState = {
(tick, simulationTime) =>
PvState(
tick,
simulationTime,
zeroWPerSM,
zeroWPerSM,
)
}

override def determineState(
lastState: PvState,
operatingPoint: ActivePowerOperatingPoint,
input: ModelInput,
): PvState = initialState(input)
tick: Long,
simulationTime: ZonedDateTime,
): PvState =
lastState.copy(tick = tick, dateTime = simulationTime)

private def getWeatherData(receivedData: Seq[Data]): WeatherData = {
receivedData
override def handleInput(
state: PvState,
receivedData: Seq[Data],
nodalVoltage: Dimensionless,
): PvState = {
val weatherData = receivedData
.collectFirst { case weatherData: WeatherData =>
weatherData
}
Expand All @@ -101,6 +106,11 @@ class PvModel private (
s"Expected WeatherData, got $receivedData"
)
}

state.copy(
diffIrradiance = weatherData.diffIrr,
dirIrradiance = weatherData.dirIrr,
)
}

/** Calculate the active power behaviour of the model.
Expand Down
Loading

0 comments on commit da6ad30

Please sign in to comment.