diff --git a/CHANGELOG.md b/CHANGELOG.md index 841c859a..97957fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Bao and Staudt to the list of reviewers [#216](https://github.com/ie3-institute/simonaAPI/issues/216) - Documentation for this API [#230](https://github.com/ie3-institute/simonaAPI/issues/230) + +### Changed +- Refactoring `ExtCoSimulation` [#237](https://github.com/ie3-institute/simonaAPI/issues/237) + ## [0.6.0] - 2024-12-02 ### Added diff --git a/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnectionWithMapping.java b/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnectionWithMapping.java new file mode 100644 index 00000000..12781b0b --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnectionWithMapping.java @@ -0,0 +1,86 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data; + +import edu.ie3.datamodel.models.value.Value; +import edu.ie3.simona.api.data.ontology.DataMessageFromExt; +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; +import java.util.*; +import org.apache.pekko.actor.ActorRef; +import org.slf4j.Logger; + +public abstract class ExtInputDataConnectionWithMapping< + M extends DataMessageFromExt, V extends Value> + implements ExtInputDataConnection { + + /** Actor reference to service that handles data within SIMONA */ + private ActorRef dataService; + + /** Actor reference to adapter that handles scheduler control flow in SIMONA */ + private ActorRef extSimAdapter; + + /** Assets that provide data to SIMONA */ + private final Map extDataMapping; + + protected ExtInputDataConnectionWithMapping(Map extDataMapping) { + this.extDataMapping = extDataMapping; + } + + @Override + public void setActorRefs(ActorRef dataService, ActorRef extSimAdapter) { + this.dataService = dataService; + this.extSimAdapter = extSimAdapter; + } + + /** + * Converts the data and sends them to SIMONA. + * + * @param tick current tick + * @param data to be converted and send + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ + public abstract void convertAndSend( + long tick, Map data, Optional maybeNextTick, Logger log); + + /** Provide data from an external simulation for one tick. */ + public abstract void provideData(long tick, Map data, Optional maybeNextTick); + + /** + * Send information from the external simulation to SIMONA's external primary data service. + * Furthermore, ExtSimAdapter within SIMONA is instructed to activate the ev data service with the + * current tick. + * + * @param msg the data/information that is sent to SIMONA's external primary data service + */ + public void sendExtMsg(M msg) { + dataService.tell(msg, ActorRef.noSender()); + // we need to schedule data receiver activation with scheduler + extSimAdapter.tell(new ScheduleDataServiceMessage(dataService), ActorRef.noSender()); + } + + /** Returns a list of the uuids of the assets that expect external data */ + protected List getDataAssets() { + return extDataMapping.values().stream().toList(); + } + + /** + * Method to remap the data from the externally used {@link String} to {@link UUID} used by + * SIMONA. + * + * @param inputMap map: string to value + * @return map: uuid to value + */ + @SuppressWarnings("unchecked") + protected Map convert(Map inputMap) { + Map valueMap = new HashMap<>(); + inputMap.entrySet().stream() + .filter(e -> extDataMapping.containsKey(e.getKey())) + .forEach(e -> valueMap.put(extDataMapping.get(e.getKey()), (V) e.getValue())); + return valueMap; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java b/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java index 5a0bbaa6..ad1a801b 100644 --- a/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java @@ -8,74 +8,48 @@ import edu.ie3.datamodel.models.value.PValue; import edu.ie3.datamodel.models.value.Value; -import edu.ie3.simona.api.data.ExtInputDataConnection; +import edu.ie3.simona.api.data.ExtInputDataConnectionWithMapping; import edu.ie3.simona.api.data.em.ontology.EmDataMessageFromExt; import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData; -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; import java.util.*; -import java.util.stream.Collectors; -import org.apache.pekko.actor.ActorRef; import org.slf4j.Logger; /** Enables data connection of em data between SIMONA and SimonaAPI */ -public class ExtEmDataConnection implements ExtInputDataConnection { - - /** Actor reference to service that handles ev data within SIMONA */ - private ActorRef emDataService; - - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private ActorRef extSimAdapter; - - /** Assets that provide primary data to SIMONA */ - private final Map extEmMapping; +public class ExtEmDataConnection + extends ExtInputDataConnectionWithMapping { public ExtEmDataConnection(Map extEmMapping) { - this.extEmMapping = extEmMapping; + super(extEmMapping); } - @Override - public void setActorRefs(ActorRef emDataService, ActorRef extSimAdapter) { - this.emDataService = emDataService; - this.extSimAdapter = extSimAdapter; + /** Returns a list of the uuids of the em agents that expect external set points */ + public List getControlledEms() { + return getDataAssets(); } + /** + * Converts the data and sends them to SIMONA. + * + * @param tick current tick + * @param data to be converted and send + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ public void convertAndSend( long tick, Map data, Optional maybeNextTick, Logger log) { // filtering the data and converting the keys - Map convertedMap = - data.entrySet().stream() - .filter(e -> extEmMapping.containsKey(e.getKey())) - .collect( - Collectors.toMap(e -> extEmMapping.get(e.getKey()), e -> (PValue) e.getValue())); + Map convertedMap = convert(data); if (convertedMap.isEmpty()) { log.warn("No em data found! Sending no em data to SIMONA for tick {}.", tick); } else { log.debug("Provided SIMONA with em data."); - provideEmData(tick, convertedMap, maybeNextTick); + provideData(tick, convertedMap, maybeNextTick); } } - /** Returns a list of the uuids of the em agents that expect external set points */ - public List getControlledEms() { - return extEmMapping.values().stream().toList(); - } - /** Provide primary data from an external simulation for one tick. */ - public void provideEmData(Long tick, Map emData, Optional maybeNextTick) { + public void provideData(long tick, Map emData, Optional maybeNextTick) { sendExtMsg(new ProvideEmSetPointData(tick, emData, maybeNextTick)); } - - /** - * Send information from the external simulation to SIMONA's external primary data service. - * Furthermore, ExtSimAdapter within SIMONA is instructed to activate the ev data service with the - * current tick. - * - * @param msg the data/information that is sent to SIMONA's external primary data service - */ - public void sendExtMsg(EmDataMessageFromExt msg) { - emDataService.tell(msg, ActorRef.noSender()); - // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(emDataService), ActorRef.noSender()); - } } diff --git a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java b/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java index 10fe1842..d65eaa39 100644 --- a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java @@ -7,75 +7,59 @@ package edu.ie3.simona.api.data.primarydata; import edu.ie3.datamodel.models.value.Value; -import edu.ie3.simona.api.data.ExtInputDataConnection; -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; +import edu.ie3.simona.api.data.ExtInputDataConnectionWithMapping; import edu.ie3.simona.api.data.primarydata.ontology.PrimaryDataMessageFromExt; import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData; -import java.util.*; -import java.util.stream.Collectors; -import org.apache.pekko.actor.ActorRef; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import org.slf4j.Logger; /** Enables data connection of primary data between SIMONA and SimonaAPI */ -public class ExtPrimaryDataConnection implements ExtInputDataConnection { +public class ExtPrimaryDataConnection + extends ExtInputDataConnectionWithMapping { - /** Actor reference to service that handles primary data within SIMONA */ - private ActorRef dataService; + private Map> valueClasses; - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private ActorRef extSimAdapter; + public ExtPrimaryDataConnection(Map extPrimaryDataMapping) { + super(extPrimaryDataMapping); + } - /** Assets that provide primary data to SIMONA */ - private final Map extPrimaryDataMapping; + public void setValueClasses(Map> valueClasses) { + this.valueClasses = valueClasses; + } - public ExtPrimaryDataConnection(Map extPrimaryDataMapping) { - this.extPrimaryDataMapping = extPrimaryDataMapping; + /** Returns a list of the uuids of the system participants that expect external primary data */ + public List getPrimaryDataAssets() { + return getDataAssets(); } - @Override - public void setActorRefs(ActorRef dataService, ActorRef extSimAdapter) { - this.dataService = dataService; - this.extSimAdapter = extSimAdapter; + /** + * @param uuid of the model + * @return an option for the value class associated with the model. + */ + public Optional> getValueClass(UUID uuid) { + return Optional.ofNullable(valueClasses.get(uuid)); } + @Override public void convertAndSend( long tick, Map data, Optional maybeNextTick, Logger log) { // filtering the data and converting the keys - Map convertedMap = - data.entrySet().stream() - .filter(e -> extPrimaryDataMapping.containsKey(e.getKey())) - .collect( - Collectors.toMap(e -> extPrimaryDataMapping.get(e.getKey()), Map.Entry::getValue)); + Map convertedMap = convert(data); if (convertedMap.isEmpty()) { log.warn("No primary data found! Sending no primary data to SIMONA for tick {}.", tick); } else { log.debug("Provided SIMONA with primary data."); - providePrimaryData(tick, convertedMap, maybeNextTick); + provideData(tick, convertedMap, maybeNextTick); } } - /** Returns a list of the uuids of the system participants that expect external primary data */ - public List getPrimaryDataAssets() { - return extPrimaryDataMapping.values().stream().toList(); - } - /** Provide primary data from an external simulation in one tick. */ - public void providePrimaryData( - Long tick, Map primaryData, Optional maybeNextTick) { + @Override + public void provideData(long tick, Map primaryData, Optional maybeNextTick) { sendExtMsg(new ProvidePrimaryData(tick, primaryData, maybeNextTick)); } - - /** - * Send information from the external simulation to SIMONA's external primary data service. - * Furthermore, ExtSimAdapter within SIMONA is instructed to activate the ev data service with the - * current tick. - * - * @param msg the data/information that is sent to SIMONA's external primary data service - */ - public void sendExtMsg(PrimaryDataMessageFromExt msg) { - dataService.tell(msg, ActorRef.noSender()); - // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(dataService), ActorRef.noSender()); - } } diff --git a/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java b/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java index 531faf7c..4379905e 100644 --- a/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java +++ b/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java @@ -10,6 +10,7 @@ import edu.ie3.datamodel.models.result.ModelResultEntity; import edu.ie3.datamodel.models.result.NodeResult; +import edu.ie3.datamodel.models.result.ResultEntity; import edu.ie3.datamodel.models.result.connector.LineResult; import edu.ie3.datamodel.models.result.system.SystemParticipantResult; import edu.ie3.simona.api.data.ExtDataContainer; @@ -64,8 +65,14 @@ public Optional getNextTick() { return maybeNextTick; } + /** Returns the result for a certain asset. */ + public ResultEntity getResult(String assetId) { + return simonaResultsMap.get(assetId); + } + /** - * Returns the voltage deviation for certain asset, if this asset provided a {@link NodeResult} + * Returns the voltage deviation in pu for certain asset, if this asset provided a {@link + * NodeResult} */ public double getVoltageDeviation(String assetId) { if (simonaResultsMap.get(assetId) instanceof NodeResult nodeResult) { @@ -77,6 +84,17 @@ public double getVoltageDeviation(String assetId) { } } + /** + * Returns the voltage deviation for certain asset, if this asset provided a {@link NodeResult} + */ + public double getVoltage(String assetId) { + if (simonaResultsMap.get(assetId) instanceof NodeResult nodeResult) { + return nodeResult.getvMag().getValue().doubleValue(); + } else { + throw new IllegalArgumentException("VOLTAGE is only available for NodeResult's!"); + } + } + /** * Returns the active power in kW for certain asset, if this asset provided a {@link * SystemParticipantResult} diff --git a/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java b/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java index de90fa41..535927e1 100644 --- a/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java @@ -8,6 +8,7 @@ import edu.ie3.datamodel.models.result.ModelResultEntity; import edu.ie3.datamodel.models.result.NodeResult; +import edu.ie3.datamodel.models.result.system.FlexOptionsResult; import edu.ie3.datamodel.models.result.system.SystemParticipantResult; import edu.ie3.simona.api.data.ExtOutputDataConnection; import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; @@ -20,6 +21,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.LinkedBlockingQueue; +import java.util.stream.Stream; import org.apache.pekko.actor.ActorRef; /** Enables data connection of results between SIMONA and SimonaAPI */ @@ -44,10 +46,16 @@ public class ExtResultDataConnection implements ExtOutputDataConnection { /** Map uuid to external id of system participants */ private final Map participantResultAssetMapping; + /** Map uuid to external id of participant flex options */ + private final Map flexOptionsMapping; + public ExtResultDataConnection( - Map participantResultAssetMapping, Map gridResultAssetMapping) { + Map participantResultAssetMapping, + Map gridResultAssetMapping, + Map flexOptionsMapping) { this.participantResultAssetMapping = participantResultAssetMapping; this.gridResultAssetMapping = gridResultAssetMapping; + this.flexOptionsMapping = flexOptionsMapping; } /** @@ -73,9 +81,35 @@ public List getParticipantResultDataAssets() { return participantResultAssetMapping.keySet().stream().toList(); } + public List getFlexOptionAssets() { + return flexOptionsMapping.keySet().stream().toList(); + } + /** Method that an external simulation can request results from SIMONA as a list. */ private List requestResultList(long tick) throws InterruptedException { - sendExtMsg(new RequestResultEntities(tick)); + List allExtEntities = + Stream.concat( + Stream.concat(getFlexOptionAssets().stream(), getGridResultDataAssets().stream()), + getParticipantResultDataAssets().stream()) + .toList(); + sendExtMsg(new RequestResultEntities(tick, allExtEntities)); + return receiveWithType(ProvideResultEntities.class).results(); + } + + private List requestFlexOptionResultsList(long tick) + throws InterruptedException { + sendExtMsg(new RequestResultEntities(tick, getFlexOptionAssets())); + return receiveWithType(ProvideResultEntities.class).results(); + } + + private List requestGridResultsList(long tick) throws InterruptedException { + sendExtMsg(new RequestResultEntities(tick, getGridResultDataAssets())); + return receiveWithType(ProvideResultEntities.class).results(); + } + + private List requestParticiapntResultsList(long tick) + throws InterruptedException { + sendExtMsg(new RequestResultEntities(tick, getParticipantResultDataAssets())); return receiveWithType(ProvideResultEntities.class).results(); } @@ -86,6 +120,20 @@ public Map requestResults(long tick) throws Interrupt return createResultMap(requestResultList(tick)); } + public Map requestFlexOptionResults(long tick) + throws InterruptedException { + return createResultMap(requestFlexOptionResultsList(tick)); + } + + public Map requestGridResults(long tick) throws InterruptedException { + return createResultMap(requestGridResultsList(tick)); + } + + public Map requestParticipantResults(long tick) + throws InterruptedException { + return createResultMap(requestParticiapntResultsList(tick)); + } + protected Map createResultMap(List results) { Map resultMap = new HashMap<>(); results.forEach( @@ -96,9 +144,12 @@ protected Map createResultMap(List resultMap.put( participantResultAssetMapping.get(systemParticipantResult.getInputModel()), systemParticipantResult); + } else if (result instanceof FlexOptionsResult flexOptionsResult) { + resultMap.put( + flexOptionsMapping.get(flexOptionsResult.getInputModel()), flexOptionsResult); } else { throw new IllegalArgumentException( - "ExtResultData can only handle NodeResult's and SystemParticipantResult's!"); + "ExtResultData can only handle NodeResult's, FlexOptionResult's and SystemParticipantResult's!"); } }); return resultMap; diff --git a/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java b/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java index 8ef656b0..9f72ab7b 100644 --- a/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java +++ b/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java @@ -6,5 +6,9 @@ package edu.ie3.simona.api.data.results.ontology; +import java.util.List; +import java.util.UUID; + /** Request calculated results from SIMONA in the current tick */ -public record RequestResultEntities(Long tick) implements ResultDataMessageFromExt {} +public record RequestResultEntities(Long tick, List requestedResults) + implements ResultDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/exceptions/ExtDataConnectionException.java b/src/main/java/edu/ie3/simona/api/exceptions/ExtDataConnectionException.java new file mode 100644 index 00000000..cfa54bc1 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/exceptions/ExtDataConnectionException.java @@ -0,0 +1,23 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.exceptions; + +import edu.ie3.simona.api.data.ExtDataConnection; + +public class ExtDataConnectionException extends RuntimeException { + + public ExtDataConnectionException(Class connectionClass) { + this( + "The external data connection '" + + connectionClass.getSimpleName() + + "' could not be build!"); + } + + public ExtDataConnectionException(final String message) { + super(message); + } +} diff --git a/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java b/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java index f4a242b3..9c403556 100644 --- a/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java +++ b/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java @@ -11,7 +11,10 @@ public class NoExtSimulationException extends RuntimeException { public NoExtSimulationException(Class linkClass) { - this("No external simulation was set up in ExtLinkInterface: ." + linkClass.getSimpleName()); + this( + "No external simulation was set up in ExtLinkInterface: " + + linkClass.getSimpleName() + + "."); } public NoExtSimulationException(final String message) { diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java index f07ec9bd..15a749ef 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java @@ -9,22 +9,26 @@ import edu.ie3.datamodel.models.result.ModelResultEntity; import edu.ie3.datamodel.models.value.Value; import edu.ie3.simona.api.data.DataQueueExtSimulationExtSimulator; +import edu.ie3.simona.api.data.ExtDataConnection; import edu.ie3.simona.api.data.ExtInputDataContainer; import edu.ie3.simona.api.data.em.ExtEmDataConnection; import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection; import edu.ie3.simona.api.data.results.ExtResultContainer; import edu.ie3.simona.api.data.results.ExtResultDataConnection; +import edu.ie3.simona.api.exceptions.ExtDataConnectionException; import edu.ie3.simona.api.simulation.mapping.DataType; +import edu.ie3.simona.api.simulation.mapping.ExtEntityEntry; import edu.ie3.simona.api.simulation.mapping.ExtEntityMapping; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; import org.slf4j.Logger; /** * Abstract class for an external co-simulation with the structure: external api - ext-co-simulation - * - extsimulation - simonaAPI - simona It contains all function to transfer primary data and em - * data to SIMONA and results to the external co-simulation. + * - ext-simulation - simonaAPI - simona + * + *

It contains all function to transfer primary data and em data to SIMONA and results to the + * external co-simulation. */ public abstract class ExtCoSimulation extends ExtSimulation { @@ -46,6 +50,15 @@ protected ExtCoSimulation(String simulationName, String extSimulatorName) { this.dataQueueSimonaApiToExtCoSimulator = new DataQueueExtSimulationExtSimulator<>(); } + @SafeVarargs + protected static Set toSet( + Optional... optionals) { + return Arrays.stream(optionals) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + } + /** * Builds an {@link ExtPrimaryDataConnection}. * @@ -53,19 +66,32 @@ protected ExtCoSimulation(String simulationName, String extSimulatorName) { * @param log logger * @return an ext primary data connection */ - protected static ExtPrimaryDataConnection buildPrimaryConnection( + @SuppressWarnings("unchecked") + public static ExtPrimaryDataConnection buildPrimaryConnection( ExtEntityMapping mapping, Logger log) { - Map primaryMapping = mapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT); - ExtPrimaryDataConnection extPrimaryDataConnection = - new ExtPrimaryDataConnection(primaryMapping); + List entries = mapping.getExtEntityEntries(DataType.EXT_PRIMARY_INPUT); - if (primaryMapping.isEmpty()) { - log.warn("Primary with 0 entities created."); + if (entries.isEmpty()) { + log.warn("No primary data connection was created."); + throw new ExtDataConnectionException(ExtPrimaryDataConnection.class); } else { - log.info("Primary connection with {} entities created.", primaryMapping.size()); - } + log.info("Primary data connection with {} entities created.", entries.size()); + + Map primaryMapping = + entries.stream().collect(Collectors.toMap(ExtEntityEntry::id, ExtEntityEntry::uuid)); + Map> valueClasses = new HashMap<>(); + entries.stream() + .filter(e -> e.columnScheme().isPresent()) + .forEach( + e -> + valueClasses.put( + e.uuid(), (Class) e.columnScheme().get().getValueClass())); - return extPrimaryDataConnection; + ExtPrimaryDataConnection primaryDataConnection = new ExtPrimaryDataConnection(primaryMapping); + primaryDataConnection.setValueClasses(valueClasses); + + return primaryDataConnection; + } } /** @@ -75,17 +101,16 @@ protected static ExtPrimaryDataConnection buildPrimaryConnection( * @param log logger * @return an ext em data connection */ - protected static ExtEmDataConnection buildEmConnection(ExtEntityMapping mapping, Logger log) { + public static ExtEmDataConnection buildEmConnection(ExtEntityMapping mapping, Logger log) { Map emMapping = mapping.getExtId2UuidMapping(DataType.EXT_EM_INPUT); - ExtEmDataConnection extEmDataConnection = new ExtEmDataConnection(emMapping); if (emMapping.isEmpty()) { - log.warn("Em connection with 0 entities created."); + log.warn("No em data connection was created."); + throw new ExtDataConnectionException(ExtEmDataConnection.class); } else { - log.info("Em connection with {} entities created.", emMapping.size()); + log.info("Em data connection with {} entities created.", emMapping.size()); + return new ExtEmDataConnection(emMapping); } - - return extEmDataConnection; } /** @@ -95,28 +120,55 @@ protected static ExtEmDataConnection buildEmConnection(ExtEntityMapping mapping, * @param log logger * @return an ext result data connection */ - protected static ExtResultDataConnection buildResultConnection( + public static ExtResultDataConnection buildResultConnection( ExtEntityMapping mapping, Logger log) { Map resultParticipantMapping = mapping.getExtUuid2IdMapping(DataType.EXT_PARTICIPANT_RESULT); Map resultGridMapping = mapping.getExtUuid2IdMapping(DataType.EXT_GRID_RESULT); - ExtResultDataConnection extResultDataConnection = - new ExtResultDataConnection(resultParticipantMapping, resultGridMapping); + Map resultFlexOptionsMapping = + mapping.getExtUuid2IdMapping(DataType.EXT_FLEX_OPTIONS_RESULT); - if (resultParticipantMapping.isEmpty() && resultGridMapping.isEmpty()) { - log.warn("Result connection with 0 participants and 0 grid assets created."); + if (resultParticipantMapping.isEmpty() + && resultGridMapping.isEmpty() + && resultFlexOptionsMapping.isEmpty()) { + log.warn("No result connection was created."); + throw new ExtDataConnectionException(ExtResultDataConnection.class); } else { log.info( - "Result connection with {} participants and {} grid assets created.", + "Result connection with {} participants, {} grid assets and {} flex option mappings created.", resultParticipantMapping.size(), - resultGridMapping.size()); + resultGridMapping.size(), + resultFlexOptionsMapping.size()); + return new ExtResultDataConnection( + resultParticipantMapping, resultGridMapping, resultFlexOptionsMapping); } + } - return extResultDataConnection; + /** + * Function to send primary data to SIMONA using the given {@link ExtPrimaryDataConnection}. This + * method will take a value from the {@link #dataQueueExtCoSimulatorToSimonaApi}. + * + * @param extPrimaryDataConnection the connection to SIMONA + * @param tick for which data is sent + * @param maybeNextTick option for the next tick data is sent + * @param log logger + */ + protected void sendPrimaryDataToSimona( + ExtPrimaryDataConnection extPrimaryDataConnection, + long tick, + Optional maybeNextTick, + Logger log) + throws InterruptedException { + sendPrimaryDataToSimona( + extPrimaryDataConnection, + tick, + dataQueueExtCoSimulatorToSimonaApi.takeData().getSimonaInputMap(), + maybeNextTick, + log); } /** - * Function to send primary data to SIMONA using ExtPrimaryData + * Function to send primary data to SIMONA using the given {@link ExtPrimaryDataConnection}. * * @param extPrimaryDataConnection the connection to SIMONA * @param tick for which data is sent @@ -137,8 +189,33 @@ protected void sendPrimaryDataToSimona( } /** - * Function to send em data to SIMONA using ExtPrimaryData nextTick is necessary, because the em - * agents have an own scheduler that should know, when the next set point arrives. + * Function to send em data to SIMONA using the given {@link ExtEmDataConnection}. This method + * will take a value from the * {@link #dataQueueExtCoSimulatorToSimonaApi}. + * + *

{@code nextTick} is necessary, because the em agents have an own scheduler that should know, + * when the next set point arrives. + * + * @param extEmDataConnection the connection to SIMONA + * @param tick for which data is sent + * @param maybeNextTick option for the next tick data is sent + * @param log logger + */ + protected void sendEmDataToSimona( + ExtEmDataConnection extEmDataConnection, long tick, Optional maybeNextTick, Logger log) + throws InterruptedException { + sendEmDataToSimona( + extEmDataConnection, + tick, + dataQueueExtCoSimulatorToSimonaApi.takeData().getSimonaInputMap(), + maybeNextTick, + log); + } + + /** + * Function to send em data to SIMONA using the given {@link ExtEmDataConnection}. + * + *

{@code nextTick} is necessary, because the em agents have an own scheduler that should know, + * when the next set point arrives. * * @param extEmDataConnection the connection to SIMONA * @param tick for which data is sent @@ -158,14 +235,63 @@ protected void sendEmDataToSimona( } /** - * Function to get result data from SIMONA using the available {@link ExtResultDataConnection} + * Function to send only participant result data from SIMONA to the external simulation using the + * given {@link ExtResultDataConnection} + */ + protected void sendParticipantResultsToExt( + ExtResultDataConnection connection, long tick, Optional nextTick, Logger log) + throws InterruptedException { + sendSingleResultType( + "participant", connection.requestParticipantResults(tick), tick, nextTick, log); + } + + /** + * Function to send only grid result data from SIMONA to the external simulation using the given + * {@link ExtResultDataConnection} + */ + protected void sendGridResultsToExt( + ExtResultDataConnection connection, long tick, Optional nextTick, Logger log) + throws InterruptedException { + sendSingleResultType("grid", connection.requestGridResults(tick), tick, nextTick, log); + } + + /** + * Function to send only flex option result data from SIMONA to the external simulation using the + * given {@link ExtResultDataConnection} + */ + protected void sendFlexOptionResultsToExt( + ExtResultDataConnection connection, long tick, Optional nextTick, Logger log) + throws InterruptedException { + sendSingleResultType( + "flex option", connection.requestFlexOptionResults(tick), tick, nextTick, log); + } + + private void sendSingleResultType( + String type, + Map resultsToBeSend, + long tick, + Optional nextTick, + Logger log) + throws InterruptedException { + log.info("Request results from SIMONA for {} for tick {}!", type, tick); + + String resultString = resultMapToString(resultsToBeSend); + log.debug("[{}] Received {} results from SIMONA!\n{}", tick, type, resultString); + dataQueueSimonaApiToExtCoSimulator.queueData( + new ExtResultContainer(tick, resultsToBeSend, nextTick)); + log.info("Sent {} results for tick {} to {}", type, tick, extSimulatorName); + } + + /** + * Function to send all result data from SIMONA to the external simulation using the given {@link + * ExtResultDataConnection} * * @param connection the connection to SIMONA * @param tick for which data is received * @param maybeNextTick option for the next tick data is received * @param log logger */ - protected void sendDataToExt( + protected void sendResultToExt( ExtResultDataConnection connection, long tick, Optional maybeNextTick, Logger log) throws InterruptedException { log.debug("Request results from SIMONA!"); @@ -175,4 +301,23 @@ protected void sendDataToExt( new ExtResultContainer(tick, resultsToBeSend, maybeNextTick)); log.debug("Sent results to {}", extSimulatorName); } + + private String resultMapToString(Map results) { + StringBuilder resultString = new StringBuilder(); + + for (Map.Entry entry : results.entrySet()) { + String key = entry.getKey(); + ModelResultEntity value = entry.getValue(); + + resultString + .append("id = ") + .append(key) + .append(", time = ") + .append(value.getTime()) + .append(", result = ") + .append(value.getClass().getSimpleName()) + .append("\n"); + } + return resultString.toString(); + } } diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java index 67bfaa12..04d17497 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java @@ -12,7 +12,8 @@ public enum DataType { EXT_PRIMARY_INPUT("primary_input"), EXT_EM_INPUT("em_input"), EXT_GRID_RESULT("grid_result"), - EXT_PARTICIPANT_RESULT("participant_result"); + EXT_PARTICIPANT_RESULT("participant_result"), + EXT_FLEX_OPTIONS_RESULT("flex_options_result"); public final String type; @@ -26,6 +27,7 @@ public static DataType parse(String type) throws ParsingException { case "em_input" -> EXT_EM_INPUT; case "grid_result" -> EXT_GRID_RESULT; case "participant_result" -> EXT_PARTICIPANT_RESULT; + case "flex_options_result" -> EXT_FLEX_OPTIONS_RESULT; default -> throw new ParsingException("Data type " + type + " is not supported!"); }; } diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java index 1444c3e1..1df4987b 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java @@ -8,6 +8,7 @@ import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme; import edu.ie3.datamodel.models.input.InputEntity; +import java.util.Optional; import java.util.UUID; /** @@ -15,14 +16,11 @@ * * @param uuid SIMONA uuid * @param id external id - * @param columnScheme data types the external asset expects + * @param columnScheme option for data types the external asset expects * @param dataType data types the external asset expects */ public record ExtEntityEntry( - UUID uuid, - String id, - ColumnScheme columnScheme, // FIXME: placeholder -> ColumnScheme should handle more data types - DataType dataType) + UUID uuid, String id, Optional columnScheme, DataType dataType) implements InputEntity { public String toString() { diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java index 9a820258..49718d13 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java @@ -46,11 +46,6 @@ protected ExtEntityEntry buildModel(EntityData data) { throw new FactoryException(e); } - return new ExtEntityEntry( - simonaUuid, - extId, - columnScheme - .orElseThrow(), // FIXME: Interim version -> ColumnScheme should handle more data types - inputType); + return new ExtEntityEntry(simonaUuid, extId, columnScheme, inputType); } } diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java index 8fd033e6..9fe43371 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java @@ -19,6 +19,21 @@ public ExtEntityMapping(List extEntityEntryList) { extEntityEntryList.stream().collect(Collectors.groupingBy(ExtEntityEntry::dataType)); } + /** Returns the data types of this mapping. */ + public Set getDataTypes() { + return extEntities.keySet(); + } + + /** + * Method for getting the external entity entries for a specific data type. + * + * @param dataType for which entries should be returned + * @return a list containing all entries or an empty list + */ + public List getExtEntityEntries(DataType dataType) { + return extEntities.getOrDefault(dataType, Collections.emptyList()); + } + /** * Mapping external id to SIMONA uuid * diff --git a/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy index d8f798f5..a2d1096b 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy @@ -4,13 +4,10 @@ import edu.ie3.datamodel.models.value.PValue import edu.ie3.datamodel.models.value.Value import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData import edu.ie3.simona.api.test.common.DataServiceTestData import org.apache.pekko.actor.ActorSystem import org.apache.pekko.testkit.TestProbe import org.apache.pekko.testkit.javadsl.TestKit -import org.slf4j.Logger -import org.slf4j.LoggerFactory import spock.lang.Shared import spock.lang.Specification @@ -51,7 +48,7 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa def convertedEmData = Map.of(uuid, pValue as PValue) when: - extEmDataConnection.provideEmData(0L, convertedEmData, Optional.of(900L)) + extEmDataConnection.provideData(0L, convertedEmData, Optional.of(900L)) then: dataService.expectMsg(new ProvideEmSetPointData(0, convertedEmData, Optional.of(900L))) diff --git a/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy index 5aba77d4..93031445 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy @@ -47,7 +47,7 @@ class ExtPrimaryDataConnectionTest extends Specification implements DataServiceT def convertedPrimaryData = Map.of(uuid, pValue as Value) when: - extPrimaryDataConnection.providePrimaryData(0L, convertedPrimaryData, Optional.of(900L)) + extPrimaryDataConnection.provideData(0L, convertedPrimaryData, Optional.of(900L)) then: dataService.expectMsg(new ProvidePrimaryData(0L, convertedPrimaryData, Optional.of(900L))) diff --git a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy index 7dc0dc22..f49f99ca 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy @@ -30,6 +30,9 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe @Shared Map gridResultAssetMapping = [:] + @Shared + Map flexResultAssetMapping = [:] + class WrongResultDataResponseMessageToExt implements ResultDataResponseMessageToExt {} def setupSpec() { @@ -46,7 +49,7 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe def dataService = new TestProbe(actorSystem) def dataServiceActivation = new TestProbe(actorSystem) def extSimAdapter = new TestProbe(actorSystem) - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping, flexResultAssetMapping) extResultDataConnection.setActorRefs( dataService.ref(), dataServiceActivation.ref(), @@ -61,7 +64,7 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe def receivedResults = extResultDataConnection.requestResults(0L) then: - dataService.expectMsg(new RequestResultEntities(0L)) + dataService.expectMsg(new RequestResultEntities(0L, [inputUuid])) extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataServiceActivation.ref())) receivedResults.get("Load") == loadResult } @@ -71,7 +74,7 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe def dataService = new TestProbe(actorSystem) def dataServiceActivation = new TestProbe(actorSystem) def extSimAdapter = new TestProbe(actorSystem) - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping, flexResultAssetMapping) extResultDataConnection.setActorRefs( dataService.ref(), dataServiceActivation.ref(), @@ -86,14 +89,14 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe extResultDataConnection.requestResults(0L) then: - dataService.expectMsg(new RequestResultEntities(0L)) + dataService.expectMsg(new RequestResultEntities(0L, [inputUuid])) extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataServiceActivation.ref())) thrown RuntimeException } def "ExtResultData should convert a list of result entities correctly to a map of resultAssetMappingId to result entity"() { given: - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping, flexResultAssetMapping) when: def mapOfResults = extResultDataConnection.createResultMap([loadResult]) @@ -105,7 +108,7 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe def "ExtResultData should throw an exception, if a result with a wrong data type was provided"() { given: - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping, flexResultAssetMapping) Quantity iAMag = Quantities.getQuantity(100, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE) Quantity iAAng = Quantities.getQuantity(45, StandardUnits.ELECTRIC_CURRENT_ANGLE) Quantity iBMag = Quantities.getQuantity(150, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE) diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy index 2903384c..f12eeaab 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy @@ -20,17 +20,19 @@ class ExtCoSimulationTest extends Specification { UUID uuid2 = UUID.randomUUID() UUID uuid3 = UUID.randomUUID() + Optional columnScheme = Optional.of(ColumnScheme.ACTIVE_POWER) + ExtEntityMapping mapping = new ExtEntityMapping([ - new ExtEntityEntry(uuid1, "primary1", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), - new ExtEntityEntry(uuid2, "em1", ColumnScheme.ACTIVE_POWER, DataType.EXT_EM_INPUT), - new ExtEntityEntry(uuid3, "primary2", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), + new ExtEntityEntry(uuid1, "primary1", columnScheme, DataType.EXT_PRIMARY_INPUT), + new ExtEntityEntry(uuid2, "em1", columnScheme, DataType.EXT_EM_INPUT), + new ExtEntityEntry(uuid3, "primary2", columnScheme, DataType.EXT_PRIMARY_INPUT), ]) when: def actual = ExtCoSimulation.buildPrimaryConnection(mapping, log) then: - actual.getPrimaryDataAssets() == [uuid3, uuid1] + actual.primaryDataAssets == [uuid3, uuid1] } def "An ExtCoSimulation can build an em data connection correctly"() { @@ -39,17 +41,19 @@ class ExtCoSimulationTest extends Specification { UUID uuid2 = UUID.randomUUID() UUID uuid3 = UUID.randomUUID() + Optional columnScheme = Optional.of(ColumnScheme.ACTIVE_POWER) + ExtEntityMapping mapping = new ExtEntityMapping([ - new ExtEntityEntry(uuid1, "em1", ColumnScheme.ACTIVE_POWER, DataType.EXT_EM_INPUT), - new ExtEntityEntry(uuid2, "em2", ColumnScheme.ACTIVE_POWER, DataType.EXT_EM_INPUT), - new ExtEntityEntry(uuid3, "primary1", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), + new ExtEntityEntry(uuid1, "em1", columnScheme, DataType.EXT_EM_INPUT), + new ExtEntityEntry(uuid2, "em2", columnScheme, DataType.EXT_EM_INPUT), + new ExtEntityEntry(uuid3, "primary1", columnScheme, DataType.EXT_PRIMARY_INPUT), ]) when: def actual = ExtCoSimulation.buildEmConnection(mapping, log) then: - actual.getControlledEms() == [uuid1, uuid2] + actual.controlledEms == [uuid1, uuid2] } def "An ExtCoSimulation can build a result data connection correctly"() { @@ -58,17 +62,20 @@ class ExtCoSimulationTest extends Specification { UUID uuid2 = UUID.randomUUID() UUID uuid3 = UUID.randomUUID() + Optional columnScheme = Optional.of(ColumnScheme.ACTIVE_POWER) + ExtEntityMapping mapping = new ExtEntityMapping([ - new ExtEntityEntry(uuid1, "grid_result", ColumnScheme.ACTIVE_POWER, DataType.EXT_GRID_RESULT), - new ExtEntityEntry(uuid2, "participant_result", ColumnScheme.ACTIVE_POWER, DataType.EXT_PARTICIPANT_RESULT), - new ExtEntityEntry(uuid3, "primary1", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), + new ExtEntityEntry(uuid1, "grid_result", columnScheme, DataType.EXT_GRID_RESULT), + new ExtEntityEntry(uuid2, "participant_result", columnScheme, DataType.EXT_PARTICIPANT_RESULT), + new ExtEntityEntry(uuid3, "primary1", columnScheme, DataType.EXT_PRIMARY_INPUT), ]) when: def actual = ExtCoSimulation.buildResultConnection(mapping, log) then: - actual.getGridResultDataAssets() == [uuid1] - actual.getParticipantResultDataAssets() == [uuid2] + actual.gridResultDataAssets == [uuid1] + actual.participantResultDataAssets == [uuid2] + actual.flexOptionAssets == [] } } diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy index 9f49f614..87818c68 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy @@ -12,7 +12,7 @@ class ExtEntityMappingTest extends Specification { ExtEntityEntry extResultEntry = new ExtEntityEntry( loadUuid, "Load", - ColumnScheme.parse("p").get(), + ColumnScheme.parse("p"), DataType.EXT_PARTICIPANT_RESULT ) @@ -20,7 +20,7 @@ class ExtEntityMappingTest extends Specification { ExtEntityEntry extInputEntry = new ExtEntityEntry( loadUuid, "Load", - ColumnScheme.parse("p").get(), + ColumnScheme.parse("p"), DataType.EXT_PRIMARY_INPUT )