diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e41c82..5ebe65ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Added +- Added support for external em communication [#304](https://github.com/ie3-institute/simonaAPI/issues/304) + ### Changed - Removed Jenkinsfile [#290](https://github.com/ie3-institute/simonaAPI/issues/290) - Adapted dependabot workflow and added CODEOWNERS [#294](https://github.com/ie3-institute/simonaAPI/issues/294) diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java index ba5d3fe9..f6c48b3b 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java @@ -7,9 +7,10 @@ package edu.ie3.simona.api.data.connection; import edu.ie3.simona.api.data.model.em.EmSetPoint; -import edu.ie3.simona.api.ontology.em.EmDataMessageFromExt; -import edu.ie3.simona.api.ontology.em.EmDataResponseMessageToExt; -import edu.ie3.simona.api.ontology.em.ProvideEmSetPointData; +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult; +import edu.ie3.simona.api.data.model.em.FlexOptionRequest; +import edu.ie3.simona.api.data.model.em.FlexOptions; +import edu.ie3.simona.api.ontology.em.*; import java.util.*; import org.slf4j.Logger; @@ -34,11 +35,47 @@ public List getControlledEms() { return new ArrayList<>(controlled); } + /** + * Sends the em flex requests to SIMONA. + * + * @param tick current tick + * @param data receiver to flex request, that should be sent to SIMONA + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ + public void sendFlexRequests( + long tick, Map data, Optional maybeNextTick, Logger log) { + if (data.isEmpty()) { + log.debug("No em flex requests found! Sending no em data to SIMONA for tick {}.", tick); + } else { + log.debug("Provided SIMONA with em flex requests."); + sendExtMsg(new ProvideFlexRequest(tick, data, maybeNextTick)); + } + } + + /** + * Sends the em flex options to SIMONA. + * + * @param tick current tick + * @param data receiver to flex options, that should be sent to SIMONA + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ + public void sendFlexOptions( + long tick, Map> data, Optional maybeNextTick, Logger log) { + if (data.isEmpty()) { + log.debug("No em flex options found! Sending no em data to SIMONA for tick {}.", tick); + } else { + log.debug("Provided SIMONA with em flex options."); + sendExtMsg(new ProvideEmFlexOption(tick, data, maybeNextTick)); + } + } + /** * Sends the em set points to SIMONA. * * @param tick current tick - * @param setPoints to be sent + * @param setPoints receiver to set point, that should be sent to SIMONA * @param maybeNextTick option for the next tick in the simulation * @param log logger */ @@ -48,10 +85,35 @@ public void sendSetPoints( log.debug("No em set points found! Sending no em data to SIMONA for tick {}.", tick); } else { log.debug("Provided SIMONA with em set points."); - sendExtMsg(new ProvideEmSetPointData(tick, setPoints, maybeNextTick)); + sendExtMsg(new ProvideEmSetPoint(tick, setPoints, maybeNextTick)); } } + /** + * Method to request em flexibility options from SIMONA. + * + * @param tick for which set points are requested + * @param emEntities for which set points are requested + * @return an {@link FlexOptionsResponse} message + * @throws InterruptedException - on interruptions + */ + public Map requestEmFlexResults( + long tick, List emEntities, boolean disaggregated) throws InterruptedException { + sendExtMsg(new RequestEmFlexResults(tick, emEntities, disaggregated)); + return receiveWithType(FlexOptionsResponse.class).receiverToFlexOptions(); + } + + /** + * Method to request the completion of the em service in SIMONA for the given tick. + * + * @param tick for which the em service should stop + * @return an option for the next tick in SIMONA + */ + public Optional requestCompletion(long tick) throws InterruptedException { + sendExtMsg(new RequestEmCompletion(tick)); + return receiveWithType(EmCompletion.class).maybeNextTick(); + } + /** Mode of the em connection */ public enum EmMode { BASE, diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java index e75a978f..23f4425f 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java @@ -19,9 +19,9 @@ public final class ExtPrimaryDataConnection extends ExtInputDataConnection { - private final Map> valueClasses; + private final Map> valueClasses; - public ExtPrimaryDataConnection(Map> valueClasses) { + public ExtPrimaryDataConnection(Map> valueClasses) { this.valueClasses = valueClasses; } @@ -34,7 +34,7 @@ public List getPrimaryDataAssets() { * @param uuid of the model * @return an option for the value class associated with the model. */ - public Optional> getValueClass(UUID uuid) { + public Optional> getValueClass(UUID uuid) { return Optional.ofNullable(valueClasses.get(uuid)); } diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java index 96a18846..615b3386 100644 --- a/src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java @@ -10,7 +10,7 @@ import java.util.Map; /** Interface for data that are exchanged between an external simulation and SimonaAPI */ -public sealed interface ExtDataContainer permits ExtInputContainer, ExtResultContainer { +public sealed interface ExtDataContainer permits ExtInputContainer, ExtOutputContainer { /** Returns true, if the container is empty. */ boolean isEmpty(); diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java index c58f8221..ca983b91 100644 --- a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java @@ -88,10 +88,14 @@ public void addPrimaryValue(UUID asset, Value value) { * @param receiver the uuid of the agent, that will receive the request * @param sender option for the uuid of the sender */ - public void addRequest(UUID receiver, Optional sender) { + public void addRequest(UUID receiver, UUID sender) { flexRequests.put(receiver, new FlexOptionRequest(receiver, sender)); } + public void addRequest(UUID receiver, FlexOptionRequest request) { + flexRequests.put(receiver, request); + } + /** * Method for adding flex options to a given receiver. * @@ -113,8 +117,8 @@ public void addFlexOptions(UUID receiver, List flexOption) { * @param asset that will receive the set point * @param power of the set point */ - public void addSetPoint(UUID asset, PValue power) { - setPoints.put(asset, new EmSetPoint(asset, power)); + public void addSetPoint(UUID asset, UUID sender, PValue power) { + setPoints.put(asset, new EmSetPoint(asset, sender, power)); } /** @@ -123,7 +127,7 @@ public void addSetPoint(UUID asset, PValue power) { * @param setPoint given set point */ public void addSetPoint(EmSetPoint setPoint) { - setPoints.put(setPoint.receiver, setPoint); + setPoints.put(setPoint.receiver(), setPoint); } /** diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtOutputContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtOutputContainer.java new file mode 100644 index 00000000..572b6408 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtOutputContainer.java @@ -0,0 +1,121 @@ +/* + * © 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.data.container; + +import edu.ie3.datamodel.models.result.ResultEntity; +import edu.ie3.simona.api.data.model.em.EmData; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** Contains all SIMONA results for a certain tick. */ +public final class ExtOutputContainer implements ExtDataContainer { + + /** Tick for which the results are meant for. */ + private final long tick; + + /** Tick when the external simulation can expect the next results from SIMONA. */ + private final Optional maybeNextTick; + + /** + * Map: receiver uuid to result from SIMONA. + * + *

ATTENTION: The time stamp of the result entities is not necessarily corresponding to the + * tick + */ + private final Map resultMap; + + /** Map: receiver uuid to {@link EmData} from SIMONA. */ + private final Map emDataMap; + + /** + * Container class for result data from SIMONA. + * + * @param tick current tick + * @param nextTick tick the external simulation can expect the next results + */ + public ExtOutputContainer(long tick, Optional nextTick) { + this.tick = tick; + this.resultMap = new HashMap<>(); + this.emDataMap = new HashMap<>(); + this.maybeNextTick = nextTick; + } + + public ExtOutputContainer(long tick) { + this(tick, Optional.empty()); + } + + @Override + public boolean isEmpty() { + return resultMap.isEmpty() && emDataMap.isEmpty(); + } + + public void addResult(UUID receiver, ResultEntity result) { + resultMap.put(receiver, result); + } + + public void addResults(Map result) { + this.resultMap.putAll(result); + } + + public void addEmData(UUID receiver, EmData emData) { + emDataMap.put(receiver, emData); + } + + public void addEmData(Map emData) { + this.emDataMap.putAll(emData); + } + + /** Returns a map: uuid to result. */ + public Map getResults() { + return resultMap; + } + + public Map getEmData() { + return emDataMap; + } + + /** + * Method to extract results of a specific type. + * + * @param clazz of the results + * @return a map: uuid to requested result, or an empty map, if no results for the requested type + * are present + * @param result type + */ + @SuppressWarnings("unchecked") + public Map getResults(Class clazz) { + Map result = new HashMap<>(); + + for (Map.Entry entry : resultMap.entrySet()) { + ResultEntity resultEntity = entry.getValue(); + + if (entry.getValue().getClass().equals(clazz)) { + // add the result, if the found result is of the requested type + result.put(entry.getKey(), (R) resultEntity); + } + } + + return result; + } + + /** Returns the tick the data is provided for. */ + public long getTick() { + return tick; + } + + /** Returns an option for the next tick, when data will be provided. */ + public Optional getMaybeNextTick() { + return maybeNextTick; + } + + /** Returns the result for a certain asset. */ + public ResultEntity getResult(UUID assetId) { + return resultMap.get(assetId); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java deleted file mode 100644 index 6f0077ee..00000000 --- a/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * © 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.data.container; - -import static edu.ie3.util.quantities.PowerSystemUnits.PU; - -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.util.quantities.PowerSystemUnits; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import javax.measure.quantity.Dimensionless; -import tech.units.indriya.ComparableQuantity; -import tech.units.indriya.quantity.Quantities; - -/** Contains all SIMONA results for a certain tick. */ -public final class ExtResultContainer implements ExtDataContainer { - - /** Tick for which the results are meant for. */ - private final long tick; - - /** Tick when the external simulation can expect the next results from SIMONA. */ - private final Optional maybeNextTick; - - /** - * Map uuid to result from SIMONA. - * - *

ATTENTION: The time stamp of the result entities is not necessarily corresponding to the - * tick - */ - private final Map resultMap; - - /** - * Container class for result data from SIMONA. - * - * @param tick current tick - * @param resultMap results from SIMONA with external id as key - * @param nextTick tick the external simulation can expect the next results - */ - public ExtResultContainer(long tick, Map resultMap, Optional nextTick) { - this.tick = tick; - this.resultMap = resultMap; - this.maybeNextTick = nextTick; - } - - public ExtResultContainer(long tick, Map resultMap) { - this(tick, resultMap, Optional.empty()); - } - - @Override - public boolean isEmpty() { - return resultMap.isEmpty(); - } - - /** Returns a map: uuid to result. */ - public Map getResults() { - return resultMap; - } - - /** - * Method to extract results of a specific type. - * - * @param clazz of the results - * @return a map: uuid to requested result, or an empty map, if no results for the requested type - * are present - * @param result type - */ - @SuppressWarnings("unchecked") - public Map getResults(Class clazz) { - Map result = new HashMap<>(); - - for (Map.Entry entry : resultMap.entrySet()) { - ResultEntity resultEntity = entry.getValue(); - - if (entry.getValue().getClass().equals(clazz)) { - // add the result, if the found result is of the requested type - result.put(entry.getKey(), (R) resultEntity); - } - } - - return result; - } - - /** Returns the tick the data is provided for. */ - public long getTick() { - return tick; - } - - /** Returns an option for the next tick, when data will be provided. */ - public Optional getMaybeNextTick() { - return maybeNextTick; - } - - /** Returns the result for a certain asset. */ - public ResultEntity getResult(UUID assetId) { - return resultMap.get(assetId); - } - - /** - * Returns the voltage deviation in pu for a certain asset, if this asset provided a {@link - * NodeResult} - */ - public double getVoltageDeviation(UUID assetId) { - if (resultMap.get(assetId) instanceof NodeResult nodeResult) { - ComparableQuantity vMagDev = - Quantities.getQuantity(-1.0, PU).add(nodeResult.getvMag()); - return vMagDev.getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "Voltage deviation is only available for NodeResult's! AssetId: " + assetId); - } - } - - /** - * Returns the voltage deviation for certain asset, if this asset provided a {@link NodeResult} - */ - public double getVoltage(UUID assetId) { - if (resultMap.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 MW for certain asset, if this asset provided a {@link - * SystemParticipantResult} - */ - public double getActivePower(UUID assetId) { - if (resultMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { - return systemParticipantResult.getP().to(PowerSystemUnits.MEGAWATT).getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "Active power is only available for SystemParticipantResult's!"); - } - } - - /** - * Returns the reactive power in MVAr for certain asset, if this asset provided a {@link - * SystemParticipantResult} - */ - public double getReactivePower(UUID assetId) { - if (resultMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { - return systemParticipantResult.getQ().to(PowerSystemUnits.MEGAVAR).getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "Reactive power is only available for SystemParticipantResult's!"); - } - } - - /** Returns the line loading for certain asset, if this asset provided a {@link LineResult} */ - public double getLineLoading(UUID assetId) { - throw new IllegalArgumentException("Line loading is not implemented yet!"); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/EmData.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmData.java new file mode 100644 index 00000000..35d50162 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmData.java @@ -0,0 +1,16 @@ +/* + * © 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.model.em; + +import java.util.UUID; + +public interface EmData { + + UUID getReceiver(); + + UUID getSender(); +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java index 1093dda1..329cae36 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java @@ -7,46 +7,60 @@ package edu.ie3.simona.api.data.model.em; import edu.ie3.datamodel.models.value.PValue; -import java.util.Objects; import java.util.Optional; import java.util.UUID; import javax.measure.quantity.Power; import tech.units.indriya.ComparableQuantity; -/** Energy management set point that will be sent to SIMONA. */ -public final class EmSetPoint { - public final UUID receiver; - public final Optional power; - - public EmSetPoint(UUID receiver) { - this.receiver = receiver; - this.power = Optional.empty(); - } - - public EmSetPoint(UUID receiver, ComparableQuantity p) { - this.receiver = receiver; - this.power = Optional.of(new PValue(p)); +/** + * Energy management set point that will be sent to SIMONA. + * + * @param receiver The receiver of the set point. + * @param sender The sender of the set point. + * @param power An option for the em set point. + */ +public record EmSetPoint(UUID receiver, UUID sender, Optional power) implements EmData { + /** + * Constructor for {@link EmSetPoint}. + * + *

Note: Using this constructor will signal SIMONA, that the current set point should be kept. + * + * @param receiver The receiver of the set point. + * @param sender The sender of the set point. + */ + public EmSetPoint(UUID receiver, UUID sender) { + this(receiver, sender, Optional.empty()); } - public EmSetPoint(UUID receiver, PValue power) { - this.receiver = receiver; - this.power = Optional.of(power); + /** + * Constructor for {@link EmSetPoint}. + * + * @param receiver The receiver of the set point. + * @param sender The sender of the set point. + * @param p Power value of the set point. + */ + public EmSetPoint(UUID receiver, UUID sender, ComparableQuantity p) { + this(receiver, sender, Optional.of(new PValue(p))); } - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - EmSetPoint that = (EmSetPoint) o; - return Objects.equals(receiver, that.receiver) && Objects.equals(power, that.power); + /** + * Constructor for {@link EmSetPoint}. + * + * @param receiver The receiver of the set point. + * @param sender The sender of the set point. + * @param power value of the set point. + */ + public EmSetPoint(UUID receiver, UUID sender, PValue power) { + this(receiver, sender, Optional.ofNullable(power)); } @Override - public int hashCode() { - return Objects.hash(receiver, power); + public UUID getReceiver() { + return receiver; } @Override - public String toString() { - return "EmSetPoint{" + "receiver=" + receiver + ", power=" + power + '}'; + public UUID getSender() { + return sender; } } diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResult.java b/src/main/java/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResult.java new file mode 100644 index 00000000..af9f4387 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResult.java @@ -0,0 +1,207 @@ +/* + * © 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.model.em; + +import edu.ie3.datamodel.models.result.system.FlexOptionsResult; +import java.time.ZonedDateTime; +import java.util.*; +import javax.measure.quantity.Power; +import org.slf4j.Logger; +import tech.units.indriya.ComparableQuantity; + +/** + * Extended {@link FlexOptionsResult}, that contains the receiver of the flex options. This models + * may also contain a disaggregation of the total flex options. + */ +public final class ExtendedFlexOptionsResult extends FlexOptionsResult { + + /** The receiver of the message. */ + private final UUID receiver; + + /** The disaggregated flex option results. */ + private final Map disaggregated; + + /** + * Standard constructor for {@link ExtendedFlexOptionsResult}. + * + * @param time date and time when the result is produced + * @param sender uuid of the input model that produces the result + * @param receiver uuid of the receiver that will receive this result + * @param pRef active power that was suggested for regular usage by the system participant + * @param pMin active minimal power that was determined by the system participant + * @param pMax active maximum power that was determined by the system participant + */ + public ExtendedFlexOptionsResult( + ZonedDateTime time, + UUID sender, + UUID receiver, + ComparableQuantity pRef, + ComparableQuantity pMin, + ComparableQuantity pMax) { + super(time, sender, pRef, pMin, pMax); + this.receiver = receiver; + this.disaggregated = new HashMap<>(); + } + + /** + * Constructor for {@link ExtendedFlexOptionsResult} with disaggregated flex options. + * + * @param time date and time when the result is produced + * @param sender uuid of the input model that produces the result + * @param receiver uuid of the receiver that will receive this result + * @param pRef active power that was suggested for regular usage by the system participant + * @param pMin active minimal power that was determined by the system participant + * @param pMax active maximum power that was determined by the system participant + */ + public ExtendedFlexOptionsResult( + ZonedDateTime time, + UUID sender, + UUID receiver, + ComparableQuantity pRef, + ComparableQuantity pMin, + ComparableQuantity pMax, + Map disaggregated) { + super(time, sender, pRef, pMin, pMax); + this.receiver = receiver; + this.disaggregated = disaggregated; + } + + /** + * Method for adding disaggregated flex option results to this object. + * + *

Note: This method does not check, if the disaggregated flex options match the total flex + * options. To do this, please use the method {@link #checkFlexOptions(Logger)}. + * + * @param uuid of the inferior model + * @param flexOptionsResult the flex options of the inferior model + */ + public void addDisaggregated(UUID uuid, FlexOptionsResult flexOptionsResult) { + this.disaggregated.put(uuid, flexOptionsResult); + } + + /** Returns the uuid of the sender ({@link #getInputModel()}) of the results. */ + public UUID getSender() { + return getInputModel(); + } + + /** Returns the uuid of the receiver. */ + public UUID getReceiver() { + return receiver; + } + + /** Returns {@code true}, if disaggregated flex option are available. */ + public boolean hasDisaggregated() { + return !disaggregated.isEmpty(); + } + + /** + * Returns a map: uuid to disaggregated flex options. + * + *

Note: If no disaggregated flex options are present (see: {@link #hasDisaggregated()}), the + * map will be empty. + */ + public Map getDisaggregated() { + return Collections.unmodifiableMap(disaggregated); + } + + /** + * Method for checking if the disaggregated flex options match the total flex options. + * + * @param log used for logging + * @return {@code true} if the flex options match, else {@code false} + */ + public boolean checkFlexOptions(Logger log) { + List> refs = new ArrayList<>(); + List> mins = new ArrayList<>(); + List> maxs = new ArrayList<>(); + + disaggregated.forEach( + (uuid, flexOptionsResult) -> { + refs.add(flexOptionsResult.getpRef()); + mins.add(flexOptionsResult.getpMin()); + maxs.add(flexOptionsResult.getpMax()); + }); + + ComparableQuantity ref = getpRef(); + ComparableQuantity min = getpMin(); + ComparableQuantity max = getpMax(); + + Optional> refSum = refs.stream().reduce(ComparableQuantity::add); + Optional> minSum = mins.stream().reduce(ComparableQuantity::add); + Optional> maxSum = maxs.stream().reduce(ComparableQuantity::add); + + boolean isRefValid = false; + boolean isMinValid = false; + boolean isMaxValid = false; + + if (refSum.isPresent()) { + isRefValid = refSum.get().isEquivalentTo(ref); + + if (!isRefValid) { + log.warn("Disaggregated reference power does not match total reference power."); + } + } else { + log.warn("Cannot check disaggregated reference power."); + } + + if (minSum.isPresent()) { + isMinValid = minSum.get().isEquivalentTo(min); + + if (!isMinValid) { + log.warn("Disaggregated minimum power does not match total minimum power."); + } + } else { + log.warn("Cannot check disaggregated minimum power."); + } + + if (maxSum.isPresent()) { + isMaxValid = maxSum.get().isEquivalentTo(max); + + if (!isMaxValid) { + log.warn("Disaggregated maximum power does not match total maximum power."); + } + } else { + log.warn("Cannot check disaggregated maximum power."); + } + + return isRefValid && isMinValid && isMaxValid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtendedFlexOptionsResult that = (ExtendedFlexOptionsResult) o; + return Objects.equals(receiver, that.receiver) + && Objects.equals(disaggregated, that.disaggregated); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), receiver, disaggregated); + } + + @Override + public String toString() { + return "ExtendedFlexOptionsResult{" + + "time=" + + getTime() + + ", sender=" + + getSender() + + ", receiver=" + + receiver + + ", pRef=" + + getpRef() + + ", pMin=" + + getpMin() + + ", pMax=" + + getpMax() + + ", disaggregated=" + + disaggregated + + '}'; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java index 492838d6..14691ae1 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java @@ -6,13 +6,23 @@ package edu.ie3.simona.api.data.model.em; -import java.util.Optional; import java.util.UUID; /** - * Flex option request that will be sent to SIMONA. + * Energy management flex option request that will be sent to SIMONA. * - * @param receiver uuid of the agent, that will receive the request - * @param sender option for the uuid of the agent, that sent the request + * @param receiver The receiver of the request. + * @param sender The sender of the request. */ -public record FlexOptionRequest(UUID receiver, Optional sender) {} +public record FlexOptionRequest(UUID receiver, UUID sender) implements EmData { + + @Override + public UUID getReceiver() { + return receiver; + } + + @Override + public UUID getSender() { + return sender; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java index c10b6481..fe50763d 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java @@ -6,6 +6,9 @@ package edu.ie3.simona.api.data.model.em; +import edu.ie3.datamodel.models.result.system.FlexOptionsResult; +import java.util.Collections; +import java.util.Map; import java.util.UUID; import javax.measure.quantity.Power; import tech.units.indriya.ComparableQuantity; @@ -13,15 +16,51 @@ /** * Flex option that will be sent to SIMONA. * - * @param receiver uuid of the flex options - * @param sender uuid of the flex options - * @param pMin minimal active power - * @param pRef current active power - * @param pMax maximal active power + * @param receiver The receiver of the flex options. + * @param sender The sender of the flex options. + * @param pRef Active power (might be negative, thus feed-in) that was suggested for regular usage. + * @param pMin Minimal active power to which the sender can be reduced (might be negative, thus + * feed-in), that was determined by the system. Therefore, equates to lower bound of possible + * flexibility provision. + * @param pMax Maximum active power to which the sender can be increased (might be negative, thus + * feed-in), that was determined by the system. Therefore, equates to upper bound of possible + * flexibility provision. */ public record FlexOptions( UUID receiver, UUID sender, - ComparableQuantity pMin, ComparableQuantity pRef, - ComparableQuantity pMax) {} + ComparableQuantity pMin, + ComparableQuantity pMax, + Map disaggregated) + implements EmData { + + public FlexOptions( + UUID receiver, + UUID sender, + ComparableQuantity pRef, + ComparableQuantity pMin, + ComparableQuantity pMax) { + this(receiver, sender, pRef, pMin, pMax, Collections.emptyMap()); + } + + /** + * Method for adding disaggregated flex option results to this object. + * + * @param uuid of the inferior model + * @param flexOptionsResult the flex options of the inferior model + */ + public void addDisaggregated(UUID uuid, FlexOptionsResult flexOptionsResult) { + this.disaggregated.put(uuid, flexOptionsResult); + } + + @Override + public UUID getReceiver() { + return receiver; + } + + @Override + public UUID getSender() { + return sender; + } +} diff --git a/src/main/java/edu/ie3/simona/api/mapping/DataType.java b/src/main/java/edu/ie3/simona/api/mapping/DataType.java index ea6d2d4a..45f1cbbd 100644 --- a/src/main/java/edu/ie3/simona/api/mapping/DataType.java +++ b/src/main/java/edu/ie3/simona/api/mapping/DataType.java @@ -11,6 +11,7 @@ public enum DataType { EXT_PRIMARY_INPUT("primary_input"), EXT_EM_INPUT("em_input"), + EXT_EM_COMMUNICATION("em_communication"), EXT_GRID_RESULT("grid_result"), EXT_PARTICIPANT_RESULT("participant_result"), EXT_FLEX_OPTIONS_RESULT("flex_options_result"); @@ -25,6 +26,7 @@ public static DataType parse(String type) throws ParsingException { return switch (type) { case "primary_input" -> EXT_PRIMARY_INPUT; case "em_input" -> EXT_EM_INPUT; + case "em_communication" -> EXT_EM_COMMUNICATION; case "grid_result" -> EXT_GRID_RESULT; case "participant_result" -> EXT_PARTICIPANT_RESULT; case "flex_options_result" -> EXT_FLEX_OPTIONS_RESULT; diff --git a/src/main/java/edu/ie3/simona/api/mapping/ExtEntityMapping.java b/src/main/java/edu/ie3/simona/api/mapping/ExtEntityMapping.java new file mode 100644 index 00000000..301dd994 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/mapping/ExtEntityMapping.java @@ -0,0 +1,104 @@ +/* + * © 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.mapping; + +import edu.ie3.simona.api.simulation.mapping.ExtEntityEntry; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** Contains the mapping between SIMONA uuid, the external id and the data type the assets hold */ +public class ExtEntityMapping { + + private final Map> extEntities; + + public ExtEntityMapping(List extEntityEntryList) { + this.extEntities = + 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 getEntries(DataType dataType) { + return extEntities.getOrDefault(dataType, Collections.emptyList()); + } + + /** + * Returns the full mapping external id to SIMONA uuid. Equals {@code + * getExtId2UuidMapping(DataType.values())}. + */ + public Map getExtId2UuidMapping() { + return extEntities.values().stream() + .flatMap(Collection::stream) + .collect(Collectors.toMap(ExtEntityEntry::id, ExtEntityEntry::uuid)); + } + + /** + * Returns the full mapping SIMONA uuid to external id. Equals {@code + * getExtUuid2IdMapping(DataType.values())}. + */ + public Map getExtUuid2IdMapping() { + return extEntities.values().stream() + .flatMap(Collection::stream) + .collect(Collectors.toMap(ExtEntityEntry::uuid, ExtEntityEntry::id)); + } + + /** + * Mapping external id to SIMONA uuid. + * + * @param dataType data type the external asset expects + * @return mapping external id to SIMONA uuid + */ + public Map getExtId2UuidMapping(DataType dataType) { + return extEntities.getOrDefault(dataType, Collections.emptyList()).stream() + .collect(Collectors.toMap(ExtEntityEntry::id, ExtEntityEntry::uuid)); + } + + /** + * Mapping external id to SIMONA uuid. + * + * @param dataTypes the external asset expects + * @return mapping external id to SIMONA uuid + */ + public Map getExtId2UuidMapping(DataType... dataTypes) { + return Stream.of(dataTypes) + .flatMap(type -> extEntities.getOrDefault(type, Collections.emptyList()).stream()) + .collect(Collectors.toMap(ExtEntityEntry::id, ExtEntityEntry::uuid)); + } + + /** + * Mapping SIMONA uuid to external id. + * + * @param dataType data type the external asset expects + * @return mapping SIMONA uuid to external id + */ + public Map getExtUuid2IdMapping(DataType dataType) { + return extEntities.getOrDefault(dataType, Collections.emptyList()).stream() + .collect(Collectors.toMap(ExtEntityEntry::uuid, ExtEntityEntry::id)); + } + + /** + * Mapping SIMONA uuid to external id. + * + * @param dataTypes data types the external asset expects + * @return mapping SIMONA uuid to external id + */ + public Map getExtUuid2IdMapping(DataType... dataTypes) { + return Stream.of(dataTypes) + .flatMap(type -> extEntities.getOrDefault(type, Collections.emptyList()).stream()) + .collect(Collectors.toMap(ExtEntityEntry::uuid, ExtEntityEntry::id)); + } +} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/EmCompletion.java b/src/main/java/edu/ie3/simona/api/ontology/em/EmCompletion.java new file mode 100644 index 00000000..12bc1709 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/EmCompletion.java @@ -0,0 +1,16 @@ +/* + * © 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.ontology.em; + +import java.util.Optional; + +/** + * Response send from SIMONA after the em service is finished. + * + * @param maybeNextTick option for the next tick in SIMONA + */ +public record EmCompletion(Optional maybeNextTick) implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/EmSetPointResponse.java b/src/main/java/edu/ie3/simona/api/ontology/em/EmSetPointResponse.java new file mode 100644 index 00000000..7888da5f --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/EmSetPointResponse.java @@ -0,0 +1,19 @@ +/* + * © 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.ontology.em; + +import edu.ie3.simona.api.data.model.em.EmSetPoint; +import java.util.Map; +import java.util.UUID; + +/** + * Message that provides em data (set points) to an external simulation. + * + * @param emData map:receiver to em set point + */ +public record EmSetPointResponse(Map emData) + implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/FlexOptionsResponse.java b/src/main/java/edu/ie3/simona/api/ontology/em/FlexOptionsResponse.java new file mode 100644 index 00000000..d196fa07 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/FlexOptionsResponse.java @@ -0,0 +1,19 @@ +/* + * © 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.ontology.em; + +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult; +import java.util.Map; +import java.util.UUID; + +/** + * Message that provides em data (flexibility options) to an external simulation. + * + * @param receiverToFlexOptions map: receiver to {@link ExtendedFlexOptionsResult} + */ +public record FlexOptionsResponse(Map receiverToFlexOptions) + implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/FlexRequestResponse.java b/src/main/java/edu/ie3/simona/api/ontology/em/FlexRequestResponse.java new file mode 100644 index 00000000..dc3efb1d --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/FlexRequestResponse.java @@ -0,0 +1,19 @@ +/* + * © 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.ontology.em; + +import edu.ie3.simona.api.data.model.em.FlexOptionRequest; +import java.util.Map; +import java.util.UUID; + +/** + * Message that provides em data (flexibility requests) to an external simulation. + * + * @param flexRequests map: receiver to {@link FlexOptionRequest} + */ +public record FlexRequestResponse(Map flexRequests) + implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmFlexOption.java b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmFlexOption.java new file mode 100644 index 00000000..79b048cc --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmFlexOption.java @@ -0,0 +1,18 @@ +/* + * © 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.ontology.em; + +import edu.ie3.simona.api.data.model.em.FlexOptions; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** Message that provides em data (flexibility options) from an external simulation. */ +public record ProvideEmFlexOption( + long tick, Map> flexOptions, Optional maybeNextTick) + implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmSetPointData.java b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmSetPoint.java similarity index 93% rename from src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmSetPointData.java rename to src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmSetPoint.java index 330da4bf..1e8bf821 100644 --- a/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmSetPointData.java +++ b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmSetPoint.java @@ -12,6 +12,6 @@ import java.util.UUID; /** Message that provides em data (set points) from an external simulation. */ -public record ProvideEmSetPointData( +public record ProvideEmSetPoint( long tick, Map emSetPoints, Optional maybeNextTick) implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/ProvideFlexRequest.java b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideFlexRequest.java new file mode 100644 index 00000000..b933114c --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideFlexRequest.java @@ -0,0 +1,17 @@ +/* + * © 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.ontology.em; + +import edu.ie3.simona.api.data.model.em.FlexOptionRequest; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** Message that provides em data (flex requests) from an external simulation. */ +public record ProvideFlexRequest( + long tick, Map flexRequests, Optional maybeNextTick) + implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmCompletion.java b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmCompletion.java new file mode 100644 index 00000000..fc6f5b18 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmCompletion.java @@ -0,0 +1,14 @@ +/* + * © 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.ontology.em; + +/** + * Request send to SIMONA to finish the em service. + * + * @param tick for which the em service should be finished + */ +public record RequestEmCompletion(long tick) implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmFlexResults.java b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmFlexResults.java new file mode 100644 index 00000000..47d41f92 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmFlexResults.java @@ -0,0 +1,14 @@ +/* + * © 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.ontology.em; + +import java.util.List; +import java.util.UUID; + +/** Request em set points from SIMONA in the given tick. */ +public record RequestEmFlexResults(long tick, List emEntities, boolean disaggregated) + implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/results/ProvideResultEntities.java b/src/main/java/edu/ie3/simona/api/ontology/results/ProvideResultEntities.java index 722b4972..27f891fb 100644 --- a/src/main/java/edu/ie3/simona/api/ontology/results/ProvideResultEntities.java +++ b/src/main/java/edu/ie3/simona/api/ontology/results/ProvideResultEntities.java @@ -17,4 +17,8 @@ public record ProvideResultEntities(List results) public ProvideResultEntities(Map resultMap) { this(resultMap.values().stream().toList()); } + + public ProvideResultEntities(ResultEntity result) { + this(List.of(result)); + } } 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 445c0e9a..bfbb6cfb 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java @@ -15,14 +15,11 @@ import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection; import edu.ie3.simona.api.data.connection.ExtResultDataConnection; import edu.ie3.simona.api.data.container.ExtInputContainer; -import edu.ie3.simona.api.data.container.ExtResultContainer; +import edu.ie3.simona.api.data.container.ExtOutputContainer; import edu.ie3.simona.api.data.model.em.EmSetPoint; import edu.ie3.simona.api.exceptions.ExtDataConnectionException; import edu.ie3.simona.api.mapping.DataType; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import org.slf4j.Logger; /** @@ -37,7 +34,7 @@ public abstract class ExtCoSimulation extends ExtSimulation { protected final ExtDataContainerQueue queueToSimona; /** Queue for the data connection from SimonaAPI to the external co-simulation */ - protected final ExtDataContainerQueue queueToExt; + protected final ExtDataContainerQueue queueToExt; /** Name of the external co-simulation */ protected final String extSimulatorName; @@ -59,7 +56,7 @@ protected ExtCoSimulation(String simulationName, String extSimulatorName) { * @return an ext primary data connection */ public static ExtPrimaryDataConnection buildPrimaryConnection( - Map> assetToValueClasses, Logger log) { + Map> assetToValueClasses, Logger log) { if (assetToValueClasses.isEmpty()) { log.warn("No primary data connection was created."); @@ -120,6 +117,8 @@ public static ExtResultDataConnection buildResultConnection( } } + // primary data methods + /** * Function to send primary data to SIMONA using ExtPrimaryData * @@ -141,6 +140,8 @@ protected void sendPrimaryDataToSimona( log.debug("Provided Primary Data to SIMONA!"); } + // energy management data methods + /** * 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. @@ -162,6 +163,8 @@ protected void sendEmSetPointsToSimona( log.debug("Provided em set points to SIMONA!"); } + // result data methods + /** * Function to get result data from SIMONA using the available {@link ExtResultDataConnection} * @@ -177,7 +180,9 @@ protected void sendResultToExt( log.debug("Request results from SIMONA!"); Map resultsToBeSend = connection.requestResults(tick); log.debug("Received results from SIMONA!"); - queueToExt.queueData(new ExtResultContainer(tick, resultsToBeSend, maybeNextTick)); + ExtOutputContainer container = new ExtOutputContainer(tick, maybeNextTick); + container.addResults(resultsToBeSend); + queueToExt.queueData(container); log.debug("Sent results to {}", extSimulatorName); } } 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 deleted file mode 100644 index 6976be08..00000000 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * © 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.simulation.mapping; - -import edu.ie3.simona.api.mapping.DataType; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - -/** Contains the mapping between SIMONA uuid, the external id and the data type the assets hold */ -public class ExtEntityMapping { - - private final Map> extEntities; - - public ExtEntityMapping(List extEntityEntryList) { - this.extEntities = - extEntityEntryList.stream().collect(Collectors.groupingBy(ExtEntityEntry::dataType)); - } - - /** - * Mapping external id to SIMONA uuid - * - * @param dataType data type the external asset expects - * @return Mapping external id to SIMONA uuid - */ - public Map getExtId2UuidMapping(DataType dataType) { - return extEntities.getOrDefault(dataType, Collections.emptyList()).stream() - .collect(Collectors.toMap(ExtEntityEntry::id, ExtEntityEntry::uuid)); - } - - /** - * Mapping SIMONA uuid to external id - * - * @param dataType data type the external asset expects - * @return Mapping SIMONA uuid to external id - */ - public Map getExtUuid2IdMapping(DataType dataType) { - return extEntities.getOrDefault(dataType, Collections.emptyList()).stream() - .collect(Collectors.toMap(ExtEntityEntry::uuid, ExtEntityEntry::id)); - } -} diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java index 1d24d7ff..db03893d 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java @@ -13,6 +13,7 @@ import edu.ie3.datamodel.io.source.DataSource; import edu.ie3.datamodel.io.source.csv.CsvDataSource; import edu.ie3.datamodel.models.Entity; +import edu.ie3.simona.api.mapping.ExtEntityMapping; import java.nio.file.Path; import java.util.Map; import java.util.Optional; diff --git a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy index b9e9251a..e99ca73b 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy @@ -1,22 +1,29 @@ package edu.ie3.simona.api.data.connection import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode -import edu.ie3.simona.api.data.model.em.EmSetPoint +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult +import edu.ie3.simona.api.data.model.em.FlexOptionRequest +import edu.ie3.simona.api.data.model.em.FlexOptions import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.ontology.em.ProvideEmSetPointData +import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.api.test.common.DataServiceTestData import org.apache.pekko.actor.testkit.typed.javadsl.ActorTestKit import spock.lang.Shared import spock.lang.Specification +import java.time.ZonedDateTime + class ExtEmDataConnectionTest extends Specification implements DataServiceTestData { @Shared ActorTestKit testKit @Shared - List controlled = [inputUuid] + private UUID sender = UUID.randomUUID() + + @Shared + private List controlled = [inputUuid] def setupSpec() { testKit = ActorTestKit.create() @@ -27,7 +34,7 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa testKit = null } - def "ExtEmDataConnection should provide em data correctly"() { + def "ExtEmDataConnection should provide em flex requests correctly"() { given: def dataService = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) @@ -37,16 +44,72 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa extSimAdapter.ref() ) - def emData = Map.of(inputUuid, new EmSetPoint(inputUuid, pValue)) + def emData = Map.of(inputUuid, new FlexOptionRequest(inputUuid, sender)) when: - extEmDataConnection.sendSetPoints(0L, emData, Optional.of(900L), log) + extEmDataConnection.sendFlexRequests(0L, emData, Optional.of(900L), log) then: - dataService.expectMessage(new ProvideEmSetPointData(0, emData, Optional.of(900L))) + dataService.expectMessage(new ProvideFlexRequest(0, emData, Optional.of(900L))) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) } + def "ExtEmDataConnection should send no message, if no flex requests are given"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = [:] as Map + + when: + extEmDataConnection.sendFlexRequests(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectNoMessage() + } + + def "ExtEmDataConnection should provide em flex options correctly"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def emData = Map.of(inputUuid, [new FlexOptions(inputUuid, UUID.randomUUID(), power, power, power)]) + + when: + extEmDataConnection.sendFlexOptions(0L, emData, Optional.of(900L), log) + + then: + dataService.expectMessage(new ProvideEmFlexOption(0, emData, Optional.of(900L))) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + } + + def "ExtEmDataConnection should send no message, if no flex options are given"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = [:] as Map + + when: + extEmDataConnection.sendFlexRequests(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectNoMessage() + } + def "ExtEmDataConnection should send no message, if input data is empty"() { given: def dataService = testKit.createTestProbe(DataMessageFromExt) @@ -65,4 +128,70 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa dataService.expectNoMessage() } + def "ExtEmDataConnection should send no message, if no em set points are given"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = [:] as Map + + when: + extEmDataConnection.sendSetPoints(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectNoMessage() + } + + def "ExtEmDataConnection should request and receive flex options correctly"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def sendMsg = new FlexOptionsResponse([(inputUuid): new ExtendedFlexOptionsResult(ZonedDateTime.now(), inputUuid, UUID.randomUUID(), power, power, power)]) + + when: + // we need to queue the msg beforehand because the receive method is blocking + extEmDataConnection.queueExtResponseMsg(sendMsg) + + def response = extEmDataConnection.requestEmFlexResults(0L, [inputUuid], false) + + then: + dataService.expectMessage(new RequestEmFlexResults(0L, [inputUuid], false)) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + response == sendMsg.receiverToFlexOptions() + } + + def "ExtEmDataConnection should request and receive flex completion correctly"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def sendMsg = new EmCompletion(Optional.of(900L)) + + when: + // we need to queue the msg beforehand because the receive method is blocking + extEmDataConnection.queueExtResponseMsg(sendMsg) + + def response = extEmDataConnection.requestCompletion(0L) + + then: + dataService.expectMessage(new RequestEmCompletion(0L)) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + response == sendMsg.maybeNextTick() + } + } diff --git a/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy index c84a5ff3..2b85d1ed 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy @@ -4,6 +4,7 @@ import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.api.data.model.em.EmSetPoint import edu.ie3.simona.api.data.model.em.FlexOptionRequest import edu.ie3.simona.api.data.model.em.FlexOptions +import spock.lang.Shared import spock.lang.Specification import tech.units.indriya.quantity.Quantities @@ -11,6 +12,9 @@ import static edu.ie3.util.quantities.PowerSystemUnits.KILOWATT class ExtInputContainerTest extends Specification { + @Shared + private UUID sender = UUID.randomUUID() + def "An ExtInputContainer should add primary data correctly"() { given: UUID uuid = UUID.randomUUID() @@ -32,10 +36,10 @@ class ExtInputContainerTest extends Specification { def container = new ExtInputContainer(0L) when: - container.addRequest(requester, Optional.empty()) + container.addRequest(requester, sender) then: - container.flexRequests == [(requester): new FlexOptionRequest(requester, Optional.empty())] + container.flexRequests == [(requester): new FlexOptionRequest(requester, sender)] } def "An ExtInputContainer should add flex option data correctly"() { @@ -61,10 +65,10 @@ class ExtInputContainerTest extends Specification { def container = new ExtInputContainer(0L) when: - container.addSetPoint(uuid, power) + container.addSetPoint(uuid, sender, power) then: - container.setPoints == [(uuid): new EmSetPoint(uuid, power)] + container.setPoints == [(uuid): new EmSetPoint(uuid, sender, power)] } def "An ExtInputContainer should extract primary data correctly"() { @@ -76,7 +80,7 @@ class ExtInputContainerTest extends Specification { container.addPrimaryValue(primaryUuid, primaryValue) UUID requestReceiver = UUID.randomUUID() - container.addRequest(requestReceiver, Optional.empty()) + container.addRequest(requestReceiver, sender) UUID receiver = UUID.randomUUID() def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) @@ -84,7 +88,7 @@ class ExtInputContainerTest extends Specification { UUID emAsset = UUID.randomUUID() def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) - container.addSetPoint(emAsset, setPoint) + container.addSetPoint(emAsset, sender, setPoint) when: def extracted = container.extractPrimaryData() @@ -108,7 +112,7 @@ class ExtInputContainerTest extends Specification { container.addPrimaryValue(primaryUuid, primaryValue) UUID requestReceiver = UUID.randomUUID() - container.addRequest(requestReceiver, Optional.empty()) + container.addRequest(requestReceiver, sender) UUID receiver = UUID.randomUUID() def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) @@ -116,14 +120,14 @@ class ExtInputContainerTest extends Specification { UUID emAsset = UUID.randomUUID() def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) - container.addSetPoint(emAsset, setPoint) + container.addSetPoint(emAsset, sender, setPoint) when: def extracted = container.extractFlexRequests() then: extracted.size() == 1 - extracted == [(requestReceiver): new FlexOptionRequest(requestReceiver, Optional.empty())] + extracted == [(requestReceiver): new FlexOptionRequest(requestReceiver, sender)] container.primaryData.size() == 1 container.flexRequests.size() == 0 @@ -140,7 +144,7 @@ class ExtInputContainerTest extends Specification { container.addPrimaryValue(primaryUuid, primaryValue) UUID requestReceiver = UUID.randomUUID() - container.addRequest(requestReceiver, Optional.empty()) + container.addRequest(requestReceiver, sender) UUID receiver = UUID.randomUUID() def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) @@ -148,7 +152,7 @@ class ExtInputContainerTest extends Specification { UUID emAsset = UUID.randomUUID() def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) - container.addSetPoint(emAsset, setPoint) + container.addSetPoint(emAsset, sender, setPoint) when: def extracted = container.extractFlexOptions() @@ -172,7 +176,7 @@ class ExtInputContainerTest extends Specification { container.addPrimaryValue(primaryUuid, primaryValue) UUID requestReceiver = UUID.randomUUID() - container.addRequest(requestReceiver, Optional.empty()) + container.addRequest(requestReceiver, sender) UUID receiver = UUID.randomUUID() def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) @@ -180,14 +184,14 @@ class ExtInputContainerTest extends Specification { UUID emAsset = UUID.randomUUID() def power = new PValue(Quantities.getQuantity(5d, KILOWATT)) - container.addSetPoint(emAsset, power) + container.addSetPoint(emAsset, sender, power) when: def extracted = container.extractSetPoints() then: extracted.size() == 1 - extracted == [(emAsset): new EmSetPoint(emAsset, power)] + extracted == [(emAsset): new EmSetPoint(emAsset, sender, power)] container.primaryData.size() == 1 container.flexRequests.size() == 1 diff --git a/src/test/groovy/edu/ie3/simona/api/data/container/ExtOutputContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtOutputContainerTest.groovy new file mode 100644 index 00000000..6519093a --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtOutputContainerTest.groovy @@ -0,0 +1,62 @@ +package edu.ie3.simona.api.data.container + +import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.datamodel.models.result.system.LoadResult +import edu.ie3.simona.api.test.common.DataServiceTestData +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.quantity.Quantities + +import java.time.ZonedDateTime + +class ExtOutputContainerTest extends Specification implements DataServiceTestData { + + @Shared + UUID nodeUuid = UUID.fromString("55b97041-64be-4e6b-983a-72dbde6eddf4") + + @Shared + NodeResult nodeResult = new NodeResult( + ZonedDateTime.parse("2020-01-30T17:26:44Z"), + nodeUuid, + Quantities.getQuantity(0.95, PowerSystemUnits.PU), + Quantities.getQuantity(45, StandardUnits.VOLTAGE_ANGLE) + ) + + def "ExtResultContainer should return all results correctly"() { + given: + def expected = [ + (nodeUuid): nodeResult, + (inputUuid): loadResult + ] + + def container = new ExtOutputContainer(0L) + container.addResults(expected) + + expect: + container.getResults() == expected + } + + def "ExtResultContainer should return specific results correctly"() { + given: + def expected = [ + (nodeUuid): nodeResult, + (inputUuid): loadResult + ] + + def container = new ExtOutputContainer(0L) + container.addResults(expected) + + when: + def nodeResults = container.getResults(NodeResult) + def loadResults = container.getResults(LoadResult) + def flexOptionsResults = container.getResults(FlexOptionsResult) + + then: + nodeResults == [(nodeUuid): nodeResult] + loadResults == [(inputUuid): loadResult] + flexOptionsResults == [:] + } +} \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy deleted file mode 100644 index 5838da4d..00000000 --- a/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy +++ /dev/null @@ -1,122 +0,0 @@ -package edu.ie3.simona.api.data.container - -import edu.ie3.datamodel.models.StandardUnits -import edu.ie3.datamodel.models.result.NodeResult -import edu.ie3.datamodel.models.result.system.FlexOptionsResult -import edu.ie3.datamodel.models.result.system.LoadResult -import edu.ie3.simona.api.test.common.DataServiceTestData -import edu.ie3.util.quantities.PowerSystemUnits -import spock.lang.Shared -import spock.lang.Specification -import tech.units.indriya.quantity.Quantities - -import java.time.ZonedDateTime - -class ExtResultContainerTest extends Specification implements DataServiceTestData { - - @Shared - UUID nodeUuid = UUID.fromString("55b97041-64be-4e6b-983a-72dbde6eddf4") - - @Shared - NodeResult nodeResult = new NodeResult( - ZonedDateTime.parse("2020-01-30T17:26:44Z"), - nodeUuid, - Quantities.getQuantity(0.95, PowerSystemUnits.PU), - Quantities.getQuantity(45, StandardUnits.VOLTAGE_ANGLE) - ) - - def "ExtResultContainer should return all results correctly"() { - given: - def expected = [ - (nodeUuid): nodeResult, - (inputUuid): loadResult - ] - - def container = new ExtResultContainer(0L, expected) - - expect: - container.getResults() == expected - } - - def "ExtResultContainer should return specific results correctly"() { - given: - def expected = [ - (nodeUuid): nodeResult, - (inputUuid): loadResult - ] - - def container = new ExtResultContainer(0L, expected) - - when: - def nodeResults = container.getResults(NodeResult) - def loadResults = container.getResults(LoadResult) - def flexOptionsResults = container.getResults(FlexOptionsResult) - - then: - nodeResults == [(nodeUuid): nodeResult] - loadResults == [(inputUuid): loadResult] - flexOptionsResults == [:] - } - - def "ExtResultContainer should return voltage deviation correctly"() { - given: - def resultMap = Map.of(nodeUuid, nodeResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - def calculatedVoltageDeviation = extResultContainer.getVoltageDeviation(nodeUuid) - - then: - calculatedVoltageDeviation == -0.05d - } - - def "ExtResultContainer should throw an exception, if voltage deviation was requested for a not NodeResult"() { - given: - def resultMap = Map.of(inputUuid, loadResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - extResultContainer.getVoltageDeviation(inputUuid) - - then: - thrown IllegalArgumentException - } - - def "ExtResultContainer should return active power correctly"() { - given: - def resultMap = Map.of(inputUuid, loadResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - def returnedActivePower = extResultContainer.getActivePower(inputUuid) - - then: - returnedActivePower == 10d - } - - def "ExtResultContainer should return reactive power correctly"() { - given: - def resultMap = Map.of(inputUuid, loadResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - def returnedReactivePower = extResultContainer.getReactivePower(inputUuid) - - then: - returnedReactivePower == 5d - } - - def "ExtResultContainer should throw an exception, if active power was requested for a not SystemParticipantResult"() { - given: - def resultMap = Map.of( - nodeUuid, nodeResult - ) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - extResultContainer.getActivePower(nodeUuid) - - then: - thrown IllegalArgumentException - } -} \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointTest.groovy new file mode 100644 index 00000000..3c68a76d --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointTest.groovy @@ -0,0 +1,52 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.ComparableQuantity +import tech.units.indriya.quantity.Quantities + +import javax.measure.quantity.Power + +class EmSetPointTest extends Specification { + + @Shared + private UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + private UUID sender = UUID.randomUUID() + + @Shared + private ComparableQuantity power = Quantities.getQuantity(10, PowerSystemUnits.KILOWATT) + + + def "An empty EmSetPoint can be constructed correctly"() { + when: + def setPoint = new EmSetPoint(receiverUuid, sender) + + then: + setPoint.receiver == receiverUuid + setPoint.sender == sender + setPoint.power == Optional.empty() + } + + def "An EmSetPoint can be constructed correctly"() { + given: + def pValue = new PValue(power) + + when: + def setPoint1 = new EmSetPoint(receiverUuid, sender, power) + def setPoint2 = new EmSetPoint(receiverUuid, sender, pValue) + + then: + setPoint1.receiver == receiverUuid + setPoint1.sender == sender + setPoint1.power == Optional.of(pValue) + + setPoint2.receiver == receiverUuid + setPoint2.sender == sender + setPoint2.power == Optional.of(pValue) + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResultTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResultTest.groovy new file mode 100644 index 00000000..3f359f71 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResultTest.groovy @@ -0,0 +1,140 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.util.quantities.PowerSystemUnits +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.ComparableQuantity +import tech.units.indriya.quantity.Quantities + +import javax.measure.quantity.Power +import java.time.ZonedDateTime + +class ExtendedFlexOptionsResultTest extends Specification { + + @Shared + Logger logger = LoggerFactory.getLogger(ExtendedFlexOptionsResultTest) + + @Shared + ZonedDateTime time = ZonedDateTime.now() + + @Shared + UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + ComparableQuantity pRef = Quantities.getQuantity(7, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMin = Quantities.getQuantity(0, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMax = Quantities.getQuantity(10, PowerSystemUnits.KILOWATT) + + + def "The ExtendedFlexOptionsResult can be constructed without disaggregated flex options"() { + when: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax, [:]) + + then: + result.time == time + result.inputModel == senderUuid + result.sender == senderUuid + result.receiver == receiverUuid + result.pRef == pRef + result.pMin == pMin + result.pMax == pMax + result.disaggregated == [:] + } + + def "The ExtendedFlexOptionsResult can be constructed with disaggregated flex options"() { + given: + def dis1 = UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65") + def dis2 = UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed") + + def disaggregated = [ + (dis1): new FlexOptionsResult(time, dis1, pRef, pMin, pMax), + (dis2): new FlexOptionsResult(time, dis2, pMin, pMin, pMin) + ] + + when: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax, disaggregated) + + then: + result.time == time + result.inputModel == senderUuid + result.sender == senderUuid + result.receiver == receiverUuid + result.pRef == pRef + result.pMin == pMin + result.pMax == pMax + result.disaggregated == disaggregated + } + + def "The ExtendedFlexOptionsResult should specify if there are disaggregated flex options correctly"() { + when: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax, diagregatedMap) + + then: + result.hasDisaggregated() == expectedResult + + where: + diagregatedMap | expectedResult + [:] as Map | false + [ + (UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65")): new FlexOptionsResult(time, UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65"), pRef, pMin, pMax), + (UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed")): new FlexOptionsResult(time, UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed"), pMin, pMin, pMin) + ] | true + } + + def "The ExtendedFlexOptionsResult should add disaggregated flex options correctly"() { + given: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax) + def inferiorUuid1 = UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65") + def inferiorUuid2 = UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed") + + def inferiorOptions1 = new FlexOptionsResult(time, inferiorUuid1, pRef, pMin, pMax) + def inferiorOptions2 = new FlexOptionsResult(time, inferiorUuid2, pMin, pMin, pMin) + + when: + result.addDisaggregated(inferiorUuid1, inferiorOptions1) + result.addDisaggregated(inferiorUuid2, inferiorOptions2) + + then: + result.hasDisaggregated() + result.disaggregated.size() == 2 + result.disaggregated.get(inferiorUuid1) == inferiorOptions1 + result.disaggregated.get(inferiorUuid2) == inferiorOptions2 + } + + def "The ExtendedFlexOptionsResult should check if disaggregated flex options match total flex options correctly"() { + given: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax) + def inferiorUuid1 = UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65") + def inferiorUuid2 = UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed") + def inferiorUuid3 = UUID.randomUUID() + + def inferiorOptions1 = new FlexOptionsResult(time, inferiorUuid1, pRef, pMin, pMin) + def inferiorOptions2 = new FlexOptionsResult(time, inferiorUuid2, pMin, pMin, pMax) + def inferiorOptions3 = new FlexOptionsResult(time, inferiorUuid3, pRef, pRef, pMax) + + when: + result.addDisaggregated(inferiorUuid1, inferiorOptions1) + boolean withOnly1 = result.checkFlexOptions(logger) + + result.addDisaggregated(inferiorUuid2, inferiorOptions2) + boolean with1And2 = result.checkFlexOptions(logger) + + result.addDisaggregated(inferiorUuid3, inferiorOptions3) + boolean with1And2And3 = result.checkFlexOptions(logger) + + then: + !withOnly1 + with1And2 + !with1And2And3 + } +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestTest.groovy new file mode 100644 index 00000000..de607e36 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestTest.groovy @@ -0,0 +1,27 @@ +package edu.ie3.simona.api.data.model.em + + +import spock.lang.Shared +import spock.lang.Specification + +class FlexOptionRequestTest extends Specification { + + @Shared + private UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + private UUID sender = UUID.randomUUID() + + @Shared + private UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + def "The FlexOptionRequest can be constructed with sender correctly"() { + when: + def request = new FlexOptionRequest(receiverUuid, senderUuid) + + then: + request.receiver == receiverUuid + request.sender == senderUuid + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionsTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionsTest.groovy new file mode 100644 index 00000000..f88d9453 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionsTest.groovy @@ -0,0 +1,40 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.ComparableQuantity +import tech.units.indriya.quantity.Quantities + +import javax.measure.quantity.Power + +class FlexOptionsTest extends Specification { + + @Shared + UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + ComparableQuantity pRef = Quantities.getQuantity(7, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMin = Quantities.getQuantity(0, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMax = Quantities.getQuantity(10, PowerSystemUnits.KILOWATT) + + def "FlexOptions can be constructed without delay correctly"() { + when: + def flexOptions = new FlexOptions(receiverUuid, senderUuid, pRef, pMin, pMax) + + then: + flexOptions.receiver == receiverUuid + flexOptions.sender == senderUuid + flexOptions.pRef == pRef + flexOptions.pMin == pMin + flexOptions.pMax == pMax + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/mapping/ExtEntityMappingTest.groovy b/src/test/groovy/edu/ie3/simona/api/mapping/ExtEntityMappingTest.groovy new file mode 100644 index 00000000..c4b4f3bc --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/mapping/ExtEntityMappingTest.groovy @@ -0,0 +1,158 @@ +package edu.ie3.simona.api.mapping + +import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme +import edu.ie3.simona.api.simulation.mapping.ExtEntityEntry +import spock.lang.Shared +import spock.lang.Specification + +class ExtEntityMappingTest extends Specification { + @Shared + UUID loadUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") + + @Shared + UUID pvUuid = UUID.fromString("12f5e864-2464-4e43-9a38-5753a439d45f") + + @Shared + UUID emUuid = UUID.fromString("60dbc7e4-9718-4bbd-913a-dd26925e68a3") + + @Shared + ExtEntityEntry extResultEntry = new ExtEntityEntry( + loadUuid, + "Load", + Optional.empty(), + DataType.EXT_PARTICIPANT_RESULT + ) + + @Shared + ExtEntityEntry extInputEntry = new ExtEntityEntry( + pvUuid, + "PV", + ColumnScheme.parse("p"), + DataType.EXT_PRIMARY_INPUT + ) + + @Shared + ExtEntityEntry extEmInputEntry = new ExtEntityEntry( + emUuid, + "Em", + Optional.empty(), + DataType.EXT_EM_INPUT + ) + + def "ExtEntityMapping should return the data types correctly"() { + when: + def extEntryMapping = new ExtEntityMapping(assets) + def types = extEntryMapping.dataTypes + + then: + types == expectedTypes as Set + + where: + assets | expectedTypes + [extResultEntry] | [DataType.EXT_PARTICIPANT_RESULT] + [extInputEntry] | [DataType.EXT_PRIMARY_INPUT] + [extEmInputEntry] | [DataType.EXT_EM_INPUT] + [extResultEntry, extInputEntry] | [DataType.EXT_PARTICIPANT_RESULT, DataType.EXT_PRIMARY_INPUT] + [extInputEntry, extEmInputEntry] | [DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT] + [extResultEntry, extInputEntry, extEmInputEntry] | [DataType.EXT_PARTICIPANT_RESULT, DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT] + } + + def "ExtEntityMapping should return the entries correctly"() { + when: + def extEntryMapping = new ExtEntityMapping(assets) + def types = extEntryMapping.getEntries(dataType) + + then: + types == expectedEntries + + where: + assets | dataType | expectedEntries + [extResultEntry, extInputEntry, extEmInputEntry] | DataType.EXT_PARTICIPANT_RESULT | [extResultEntry] + [extResultEntry, extInputEntry, extEmInputEntry] | DataType.EXT_PRIMARY_INPUT | [extInputEntry] + [extResultEntry, extInputEntry, extEmInputEntry] | DataType.EXT_EM_INPUT | [extEmInputEntry] + } + + def "ExtEntityMapping should return all SIMONA uuid mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtId2UuidMapping() + + then: + inputMap.size() == 3 + inputMap.get("Load") == loadUuid + inputMap.get("PV") == pvUuid + inputMap.get("Em") == emUuid + } + + def "ExtEntityMapping should return SIMONA uuid mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT) + + then: + inputMap.size() == 1 + inputMap.get("PV") == pvUuid + } + + def "ExtEntityMapping should return multiple SIMONA uuid mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT) + + then: + inputMap.size() == 2 + inputMap.get("PV") == pvUuid + inputMap.get("Em") == emUuid + } + + def "ExtEntityMapping should return all external id mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtUuid2IdMapping() + + then: + inputMap.size() == 3 + inputMap.get(loadUuid) == "Load" + inputMap.get(pvUuid) == "PV" + inputMap.get(emUuid) == "Em" + } + + def "ExtEntityMapping should return external id mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtUuid2IdMapping(DataType.EXT_PRIMARY_INPUT) + + then: + inputMap.size() == 1 + inputMap.get(pvUuid) == "PV" + } + + def "ExtEntityMapping should return multiple external id mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtUuid2IdMapping(DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT) + + then: + inputMap.size() == 2 + inputMap.get(pvUuid) == "PV" + inputMap.get(emUuid) == "Em" + } +} \ No newline at end of file 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 deleted file mode 100644 index dea3928e..00000000 --- a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy +++ /dev/null @@ -1,53 +0,0 @@ -package edu.ie3.simona.api.simulation.mapping - -import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme -import edu.ie3.simona.api.mapping.DataType -import spock.lang.Shared -import spock.lang.Specification - -class ExtEntityMappingTest extends Specification { - @Shared - UUID loadUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") - - @Shared - ExtEntityEntry extResultEntry = new ExtEntityEntry( - loadUuid, - "Load", - ColumnScheme.parse("p"), - DataType.EXT_PARTICIPANT_RESULT - ) - - @Shared - ExtEntityEntry extInputEntry = new ExtEntityEntry( - loadUuid, - "Load", - ColumnScheme.parse("p"), - DataType.EXT_PRIMARY_INPUT - ) - - def "ExtEntityMapping should return SIMONA uuid mapping correctly"() { - given: - def extAssetList = List.of(extResultEntry, extInputEntry) - def extEntryMapping = new ExtEntityMapping(extAssetList) - - when: - def inputMap = extEntryMapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT) - - then: - inputMap.size() == 1 - inputMap.get("Load") == loadUuid - } - - def "ExtEntityMapping should return external id mapping correctly"() { - given: - def extAssetList = List.of(extResultEntry, extInputEntry) - def extEntryMapping = new ExtEntityMapping(extAssetList) - - when: - def inputMap = extEntryMapping.getExtUuid2IdMapping(DataType.EXT_PRIMARY_INPUT) - - then: - inputMap.size() == 1 - inputMap.get(loadUuid) == "Load" - } -} \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy b/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy index 88a7af56..748b95ef 100644 --- a/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy +++ b/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy @@ -5,15 +5,20 @@ import edu.ie3.datamodel.models.result.system.LoadResult import edu.ie3.datamodel.models.value.PValue import org.slf4j.Logger import org.slf4j.LoggerFactory +import tech.units.indriya.ComparableQuantity import tech.units.indriya.quantity.Quantities +import javax.measure.quantity.Power import java.time.ZonedDateTime trait DataServiceTestData { Logger log = LoggerFactory.getLogger(DataServiceTestData) UUID inputUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") - PValue pValue = new PValue(Quantities.getQuantity(500.0, StandardUnits.ACTIVE_POWER_IN)) + + ComparableQuantity power = Quantities.getQuantity(500.0, StandardUnits.ACTIVE_POWER_IN) + + PValue pValue = new PValue(power) LoadResult loadResult = new LoadResult( ZonedDateTime.parse("2020-01-30T17:26:44Z[UTC]"),