diff --git a/CHANGELOG.md b/CHANGELOG.md index e76f38732..d75c2fe08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added weathersource documentation [#1390](https://github.com/ie3-institute/PowerSystemDataModel/issues/1390) - Added standard asset parameter for `3wTransformer` in `ReadTheDocs` [#1417](https://github.com/ie3-institute/PowerSystemDataModel/issues/1417) +- Added abstraction for power value sources [#1438](https://github.com/ie3-institute/PowerSystemDataModel/issues/1438) ### Fixed - Fixed small issues in tests [#1400](https://github.com/ie3-institute/PowerSystemDataModel/issues/1400) diff --git a/src/main/java/edu/ie3/datamodel/io/csv/CsvLoadProfileMetaInformation.java b/src/main/java/edu/ie3/datamodel/io/csv/CsvLoadProfileMetaInformation.java index 905f1ee3f..8f5aeae13 100644 --- a/src/main/java/edu/ie3/datamodel/io/csv/CsvLoadProfileMetaInformation.java +++ b/src/main/java/edu/ie3/datamodel/io/csv/CsvLoadProfileMetaInformation.java @@ -42,13 +42,9 @@ public int hashCode() { @Override public String toString() { return "CsvLoadProfileMetaInformation{" - + "uuid='" - + getUuid() - + '\'' - + ", profile='" + + "profile='" + getProfile() - + '\'' - + "fullFilePath=" + + ", fullFilePath=" + fullFilePath + '}'; } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactory.java index 8c5343260..67451014e 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactory.java @@ -9,7 +9,6 @@ import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.exceptions.ParsingException; -import edu.ie3.datamodel.io.naming.timeseries.LoadProfileMetaInformation; import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile; import edu.ie3.datamodel.models.timeseries.TimeSeriesEntry; import edu.ie3.datamodel.models.timeseries.repetitive.BdewLoadProfileTimeSeries; @@ -70,9 +69,7 @@ protected List> getFields(Class entityClass) { @Override public BdewLoadProfileTimeSeries build( - LoadProfileMetaInformation metaInformation, Set> entries) { - - BdewStandardLoadProfile profile = parseProfile(metaInformation.getProfile()); + BdewStandardLoadProfile profile, Set> entries) { ComparableQuantity maxPower = calculateMaxPower(profile, entries); ComparableQuantity profileEnergyScaling = getLoadProfileEnergyScaling(profile); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/LoadProfileFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/LoadProfileFactory.java index e927adaed..bb0f4cd6e 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/LoadProfileFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/LoadProfileFactory.java @@ -6,7 +6,6 @@ package edu.ie3.datamodel.io.factory.timeseries; import edu.ie3.datamodel.io.factory.Factory; -import edu.ie3.datamodel.io.naming.timeseries.LoadProfileMetaInformation; import edu.ie3.datamodel.models.profile.LoadProfile; import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileEntry; import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileTimeSeries; @@ -37,8 +36,7 @@ protected LoadProfileFactory(Class... valueClass) { super(valueClass); } - public abstract LoadProfileTimeSeries build( - LoadProfileMetaInformation metaInformation, Set> entries); + public abstract LoadProfileTimeSeries build(P profile, Set> entries); public abstract P parseProfile(String profile); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactory.java index f3a88199f..2addfa46a 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactory.java @@ -8,7 +8,6 @@ import static edu.ie3.datamodel.models.profile.LoadProfile.RandomLoadProfile.RANDOM_LOAD_PROFILE; import static tech.units.indriya.unit.Units.WATT; -import edu.ie3.datamodel.io.naming.timeseries.LoadProfileMetaInformation; import edu.ie3.datamodel.models.profile.LoadProfile.RandomLoadProfile; import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileEntry; import edu.ie3.datamodel.models.timeseries.repetitive.RandomLoadProfileTimeSeries; @@ -73,9 +72,7 @@ protected List> getFields(Class entityClass) { @Override public RandomLoadProfileTimeSeries build( - LoadProfileMetaInformation metaInformation, Set> entries) { - RandomLoadProfile profile = RANDOM_LOAD_PROFILE; - + RandomLoadProfile profile, Set> entries) { ComparableQuantity maxPower = calculateMaxPower(profile, entries); ComparableQuantity profileEnergyScaling = getLoadProfileEnergyScaling(profile); diff --git a/src/main/java/edu/ie3/datamodel/io/naming/timeseries/LoadProfileMetaInformation.java b/src/main/java/edu/ie3/datamodel/io/naming/timeseries/LoadProfileMetaInformation.java index 875c881e6..9190be187 100644 --- a/src/main/java/edu/ie3/datamodel/io/naming/timeseries/LoadProfileMetaInformation.java +++ b/src/main/java/edu/ie3/datamodel/io/naming/timeseries/LoadProfileMetaInformation.java @@ -18,11 +18,6 @@ public LoadProfileMetaInformation(String profile) { this.profile = profile; } - public LoadProfileMetaInformation(UUID uuid, String profile) { - super(uuid); - this.profile = profile; - } - public String getProfile() { return profile; } @@ -37,18 +32,11 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(super.hashCode(), profile); + return Objects.hash(profile); } @Override public String toString() { - return "LoadProfileTimeSeriesMetaInformation{" - + "uuid='" - + getUuid() - + '\'' - + ", profile='" - + profile - + '\'' - + '}'; + return "LoadProfileTimeSeriesMetaInformation{profile='" + profile + '}'; } } diff --git a/src/main/java/edu/ie3/datamodel/io/sink/SqlSink.java b/src/main/java/edu/ie3/datamodel/io/sink/SqlSink.java index e476a9ebd..eb007b99c 100644 --- a/src/main/java/edu/ie3/datamodel/io/sink/SqlSink.java +++ b/src/main/java/edu/ie3/datamodel/io/sink/SqlSink.java @@ -190,7 +190,7 @@ private void persistMixedList(List entities, DbGridMetadat void persistList(List entities, Class cls, DbGridMetadata identifier) throws SQLException { // Check if there are only elements of the same class - Class firstClass = entities.get(0).getClass(); + Class firstClass = entities.getFirst().getClass(); boolean allSameClass = entities.stream().allMatch(e -> e.getClass() == firstClass); if (allSameClass) { diff --git a/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java b/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java index 238300c70..57dbc1613 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java @@ -14,35 +14,36 @@ import edu.ie3.datamodel.io.factory.timeseries.LoadProfileData; import edu.ie3.datamodel.io.factory.timeseries.LoadProfileFactory; import edu.ie3.datamodel.io.factory.timeseries.RandomLoadProfileFactory; +import edu.ie3.datamodel.io.naming.timeseries.LoadProfileMetaInformation; import edu.ie3.datamodel.io.source.csv.CsvDataSource; import edu.ie3.datamodel.io.source.csv.CsvLoadProfileSource; import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile; import edu.ie3.datamodel.models.profile.LoadProfile; import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileEntry; -import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileTimeSeries; import edu.ie3.datamodel.models.timeseries.repetitive.RandomLoadProfileTimeSeries; -import edu.ie3.datamodel.models.value.PValue; import edu.ie3.datamodel.models.value.Value; import edu.ie3.datamodel.models.value.load.BdewLoadValues; import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.datamodel.models.value.load.RandomLoadValues; import edu.ie3.datamodel.utils.Try; import java.time.ZonedDateTime; -import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import javax.measure.quantity.Energy; -import javax.measure.quantity.Power; -import tech.units.indriya.ComparableQuantity; public abstract class LoadProfileSource

> - extends EntitySource { + extends EntitySource implements PowerValueSource.TimeSeriesBased { + protected final P profile; protected final Class entryClass; protected final LoadProfileFactory entryFactory; - protected LoadProfileSource(Class entryClass, LoadProfileFactory entryFactory) { + protected LoadProfileSource( + LoadProfileMetaInformation metaInformation, + Class entryClass, + LoadProfileFactory entryFactory) { + this.profile = entryFactory.parseProfile(metaInformation.getProfile()); this.entryClass = entryClass; this.entryFactory = entryFactory; } @@ -60,33 +61,18 @@ protected Try, FactoryException> createEntries( return entryFactory.get(factoryData); } - public abstract LoadProfileTimeSeries getTimeSeries(); + /** Returns the load profile entries as a set. */ + public abstract Set> getEntries(); - /** - * Method to return all time keys after a given timestamp. - * - * @param time given time - * @return a list of time keys - */ - public abstract List getTimeKeysAfter(ZonedDateTime time); - - /** - * Method to get the value for a given time. - * - * @param time for which a value is needed - * @return an optional - * @throws SourceException if an exception occurred - */ - public abstract Optional getValue(ZonedDateTime time) throws SourceException; - - /** Returns the load profile of this source. */ - public abstract P getLoadProfile(); - - /** Returns the maximal power value of the time series */ - public abstract Optional> getMaxPower(); + @Override + public P getProfile() { + return profile; + } - /** Returns the load profile energy scaling for this load profile time series. */ - public abstract Optional> getLoadProfileEnergyScaling(); + @Override + public Optional getNextTimeKey(ZonedDateTime time) { + return Optional.of(time.plusSeconds(getResolution(getProfile()))); + } /** * Returns the resolution for the given {@link LoadProfile}. @@ -125,7 +111,7 @@ public static long getResolution(LoadProfile loadProfile) { metaInformation -> new CsvLoadProfileSource<>( buildInSource, metaInformation, BdewLoadValues.class, factory)) - .collect(Collectors.toMap(CsvLoadProfileSource::getLoadProfile, Function.identity())); + .collect(Collectors.toMap(CsvLoadProfileSource::getProfile, Function.identity())); } /** diff --git a/src/main/java/edu/ie3/datamodel/io/source/PowerValueSource.java b/src/main/java/edu/ie3/datamodel/io/source/PowerValueSource.java new file mode 100644 index 000000000..88674ee50 --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/io/source/PowerValueSource.java @@ -0,0 +1,91 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.io.source; + +import edu.ie3.datamodel.models.profile.LoadProfile; +import edu.ie3.datamodel.models.profile.PowerProfile; +import edu.ie3.datamodel.models.value.PValue; +import java.time.ZonedDateTime; +import java.util.Optional; +import java.util.function.Supplier; +import javax.measure.quantity.Energy; +import javax.measure.quantity.Power; +import tech.units.indriya.ComparableQuantity; + +/** Interface defining base functionality for power value sources. */ +public sealed interface PowerValueSource< + P extends PowerProfile, ID extends PowerValueSource.InputData> + permits PowerValueSource.MarkovBased, PowerValueSource.TimeSeriesBased { + + /** Returns the profile of this source. */ + P getProfile(); + + /** + * Method to get the next power value based on the provided input data. + * + * @param data input data that is used to calculate the next power value. + * @return an option for the power value. + */ + Optional getValue(ID data); + + /** + * Method to get a supplier for the next power value based on the provided input data. Depending + * on the implementation the supplier will either always return the same value or each time a + * random value. To return one constant value please use {@link #getValue(InputData)}. + * + * @param data input data that is used to calculate the next power value. + * @return A supplier for an option on the value at the given time step. + */ + Supplier> getValueSupplier(ID data); + + /** + * Method to determine the next timestamp for which data is present. + * + * @param time current time + * @return an option for the next timestamp or {@link Optional#empty()} if no timestamp was found. + */ + Optional getNextTimeKey(ZonedDateTime time); + + /** Returns the maximal power that can be returned by this source. */ + Optional> getMaxPower(); + + /** Returns the energy scaling of this power source. */ + Optional> getProfileEnergyScaling(); + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // non-sealed implementations + + /** Interface for time-series-based power value sources. */ + non-sealed interface TimeSeriesBased + extends PowerValueSource {} + + /** Interface for markov-chain-based power value sources. */ + non-sealed interface MarkovBased extends PowerValueSource {} + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // input data + + /** + * Interface for the input data of {@link #getValue(InputData)}. The data is used to determine the + * next power. + */ + sealed interface InputData permits PowerValueSource.TimeSeriesInputValue { + /** Returns the timestamp for which a power value is needed. */ + ZonedDateTime getTime(); + } + + /** + * Input data for time-series-based power value sources. + * + * @param time + */ + record TimeSeriesInputValue(ZonedDateTime time) implements InputData { + @Override + public ZonedDateTime getTime() { + return time; + } + } +} diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java index c785599b6..87e67339f 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java @@ -18,9 +18,9 @@ import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.datamodel.utils.Try; import java.nio.file.Path; -import java.time.ZonedDateTime; import java.util.*; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.measure.quantity.Energy; import javax.measure.quantity.Power; @@ -40,13 +40,13 @@ public CsvLoadProfileSource( CsvLoadProfileMetaInformation metaInformation, Class entryClass, LoadProfileFactory entryFactory) { - super(entryClass, entryFactory); + super(metaInformation, entryClass, entryFactory); this.dataSource = source; this.filePath = metaInformation.getFullFilePath(); /* Read in the full time series */ try { - this.loadProfileTimeSeries = buildLoadProfileTimeSeries(metaInformation, this::createEntries); + this.loadProfileTimeSeries = buildLoadProfileTimeSeries(this::createEntries); } catch (SourceException e) { throw new IllegalArgumentException( "Unable to obtain load profile time series with profile '" @@ -61,24 +61,28 @@ public void validate() throws ValidationException { validate(entryClass, () -> dataSource.getSourceFields(filePath), entryFactory); } - @Override public LoadProfileTimeSeries getTimeSeries() { return loadProfileTimeSeries; } @Override - public List getTimeKeysAfter(ZonedDateTime time) { - return loadProfileTimeSeries.getTimeKeysAfter(time); + public Set> getEntries() { + return loadProfileTimeSeries.getEntries(); } @Override - public Optional getValue(ZonedDateTime time) throws SourceException { - return loadProfileTimeSeries.getValue(time); + public Optional getValue(TimeSeriesInputValue data) { + return loadProfileTimeSeries.getValue(data.time()); } @Override - public P getLoadProfile() { - return getTimeSeries().getLoadProfile(); + public Supplier> getValueSupplier(TimeSeriesInputValue data) { + return loadProfileTimeSeries.supplyValue(data.time()); + } + + @Override + public P getProfile() { + return loadProfileTimeSeries.getLoadProfile(); } @Override @@ -87,7 +91,7 @@ public Optional> getMaxPower() { } @Override - public Optional> getLoadProfileEnergyScaling() { + public Optional> getProfileEnergyScaling() { return loadProfileTimeSeries.loadProfileScaling(); } @@ -98,15 +102,12 @@ public Optional> getLoadProfileEnergyScaling() { * entries are obtained entries with the help of {@code fieldToValueFunction}. If the file does * not exist, an empty Stream is returned. * - * @param metaInformation containing an unique identifier of the time series, a path to the file - * to read as well as the profile * @param fieldToValueFunction function, that is able to transfer a mapping (from field to value) * onto a specific instance of the targeted entry class * @throws SourceException If the file cannot be read properly * @return an individual time series */ protected LoadProfileTimeSeries buildLoadProfileTimeSeries( - CsvLoadProfileMetaInformation metaInformation, Function, Try, FactoryException>> fieldToValueFunction) throws SourceException { @@ -120,6 +121,6 @@ protected LoadProfileTimeSeries buildLoadProfileTimeSeries( .getOrThrow() .collect(Collectors.toSet()); - return entryFactory.build(metaInformation, entries); + return entryFactory.build(profile, entries); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSource.java b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSource.java index 81bf4745d..51ae171d4 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSource.java @@ -7,7 +7,6 @@ import static edu.ie3.datamodel.io.source.sql.SqlDataSource.createBaseQueryString; -import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.connectors.SqlConnector; import edu.ie3.datamodel.io.factory.timeseries.LoadProfileFactory; @@ -22,10 +21,10 @@ import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.datamodel.utils.TimeSeriesUtils; import java.time.ZonedDateTime; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.measure.quantity.Energy; import javax.measure.quantity.Power; @@ -41,13 +40,10 @@ */ public class SqlLoadProfileSource

> extends LoadProfileSource { - protected static final Logger log = LoggerFactory.getLogger(SqlTimeSeriesSource.class); + protected static final Logger log = LoggerFactory.getLogger(SqlLoadProfileSource.class); private final SqlDataSource dataSource; private final String tableName; - private final LoadProfileMetaInformation metaInformation; - private final P loadProfile; - // General fields private static final String WHERE = " WHERE "; private static final String LOAD_PROFILE = "load_profile"; @@ -65,12 +61,10 @@ public SqlLoadProfileSource( LoadProfileMetaInformation metaInformation, Class entryClass, LoadProfileFactory entryFactory) { - super(entryClass, entryFactory); + super(metaInformation, entryClass, entryFactory); this.dataSource = dataSource; this.tableName = "load_profiles"; - this.metaInformation = metaInformation; - this.loadProfile = entryFactory.parseProfile(metaInformation.getProfile()); String dbTimeColumnName = dataSource.getDbColumnName(entryFactory.getTimeFieldString(), tableName); @@ -101,39 +95,39 @@ public void validate() throws ValidationException { // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @Override - public LoadProfileTimeSeries getTimeSeries() { - Set> entries = getEntries(queryFull, ps -> {}); - return entryFactory.build(metaInformation, entries); - } - - @Override - public List getTimeKeysAfter(ZonedDateTime time) { - return List.of(time.plusMinutes(15)); + public Set> getEntries() { + return getEntries(queryFull, ps -> {}); } @Override - public Optional getValue(ZonedDateTime time) throws SourceException { - Set> entries = - getEntries(queryTime, ps -> ps.setInt(1, TimeSeriesUtils.calculateQuarterHourOfDay(time))); - if (entries.isEmpty()) return Optional.empty(); - if (entries.size() > 1) log.warn("Retrieved more than one result value, using the first"); - return entries.stream().findFirst().map(entry -> entry.getValue().getValue(time, loadProfile)); + public Optional getValue(TimeSeriesInputValue data) { + ZonedDateTime time = data.time(); + return queryForValue(time).map(loadValue -> loadValue.getValue(time, profile)); } @Override - public P getLoadProfile() { - return loadProfile; + public Supplier> getValueSupplier(TimeSeriesInputValue data) { + ZonedDateTime time = data.time(); + Optional> loadValueOption = queryForValue(time); + + if (loadValueOption.isPresent()) { + LoadValues

loadValue = loadValueOption.get(); + return () -> Optional.of(loadValue.getValue(time, profile)); + } else { + return Optional::empty; + } } @Override public Optional> getMaxPower() { + // TODO: Improve this calculation return Optional.ofNullable( - entryFactory.calculateMaxPower(loadProfile, getEntries(queryFull, ps -> {}))); + entryFactory.calculateMaxPower(profile, getEntries(queryFull, ps -> {}))); } @Override - public Optional> getLoadProfileEnergyScaling() { - return Optional.ofNullable(entryFactory.getLoadProfileEnergyScaling(loadProfile)); + public Optional> getProfileEnergyScaling() { + return Optional.ofNullable(entryFactory.getLoadProfileEnergyScaling(profile)); } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -153,6 +147,15 @@ private Set> getEntries(String query, SqlDataSource.AddParam .collect(Collectors.toSet()); } + private Optional> queryForValue(ZonedDateTime time) { + Set> entries = + getEntries(queryTime, ps -> ps.setInt(1, TimeSeriesUtils.calculateQuarterHourOfDay(time))); + if (entries.isEmpty()) return Optional.empty(); + if (entries.size() > 1) log.warn("Retrieved more than one result value, using the first"); + + return entries.stream().findFirst().map(LoadProfileEntry::getValue); + } + /** * Build a {@link LoadProfileEntry} of type {@code V}, whereas the underlying {@link Value} does * not need any additional information. @@ -178,7 +181,7 @@ private String createQueryFull(String schemaName, String tableName) { + WHERE + LOAD_PROFILE + " = '" - + loadProfile.getKey() + + profile.getKey() + "'"; } @@ -197,7 +200,7 @@ private String createQueryForTime(String schemaName, String tableName, String ti + WHERE + LOAD_PROFILE + " = '" - + loadProfile.getKey() + + profile.getKey() + "' AND " + timeColumnName + "=?;"; diff --git a/src/main/java/edu/ie3/datamodel/models/profile/LoadProfile.java b/src/main/java/edu/ie3/datamodel/models/profile/LoadProfile.java index 116d3389e..4a2847305 100644 --- a/src/main/java/edu/ie3/datamodel/models/profile/LoadProfile.java +++ b/src/main/java/edu/ie3/datamodel/models/profile/LoadProfile.java @@ -6,16 +6,11 @@ package edu.ie3.datamodel.models.profile; import edu.ie3.datamodel.exceptions.ParsingException; -import java.io.Serializable; import java.util.Arrays; import java.util.stream.Collectors; import java.util.stream.Stream; -public interface LoadProfile extends Serializable { - /** - * @return The identifying String - */ - String getKey(); +public interface LoadProfile extends PowerProfile { /** * Parses the given key to {@link StandardLoadProfile}. diff --git a/src/main/java/edu/ie3/datamodel/models/profile/PowerProfile.java b/src/main/java/edu/ie3/datamodel/models/profile/PowerProfile.java new file mode 100644 index 000000000..a39a3ae0e --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/models/profile/PowerProfile.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.datamodel.models.profile; + +import java.io.Serializable; + +/** Interface defining a power profile. */ +public interface PowerProfile extends Serializable { + + /** + * @return The identifying String + */ + String getKey(); +} diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java index 96e24cd7f..cf430be1a 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/TimeSeries.java @@ -77,15 +77,7 @@ public Optional> getTimeBasedValue(ZonedDateTime time) { * @param time Reference in time * @return The next later known time instant */ - protected abstract Optional getNextDateTime(ZonedDateTime time); - - /** - * Get all {@link ZonedDateTime}s after the given time. - * - * @param time given time - * @return a list of all time keys - */ - public abstract List getTimeKeysAfter(ZonedDateTime time); + public abstract Optional getNextDateTime(ZonedDateTime time); /** * Get the most recent available value before or at the given time step as a TimeBasedValue diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java index d3e196a28..f0b52f0ea 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/individual/IndividualTimeSeries.java @@ -60,13 +60,18 @@ public Optional getPreviousDateTime(ZonedDateTime time) { } @Override - protected Optional getNextDateTime(ZonedDateTime time) { + public Optional getNextDateTime(ZonedDateTime time) { return timeToValue.keySet().stream() .filter(valueTime -> valueTime.compareTo(time) > 0) .min(Comparator.naturalOrder()); } - @Override + /** + * Get all {@link ZonedDateTime}s after the given time. + * + * @param time given time + * @return a list of all time keys + */ public List getTimeKeysAfter(ZonedDateTime time) { return timeToValue.keySet().stream().filter(timeKey -> timeKey.isAfter(time)).sorted().toList(); } diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java index b1075875a..d3c72ea15 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/LoadProfileTimeSeries.java @@ -11,6 +11,7 @@ import edu.ie3.datamodel.utils.TimeSeriesUtils; import java.time.ZonedDateTime; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.measure.quantity.Energy; import javax.measure.quantity.Power; @@ -21,8 +22,8 @@ */ public class LoadProfileTimeSeries

> extends RepetitiveTimeSeries, V, PValue> { - private final P loadProfile; - private final Map valueMapping; + protected final P loadProfile; + protected final Map valueMapping; /** * The maximum average power consumption per quarter-hour calculated over all seasons and weekday @@ -76,21 +77,30 @@ public Set> getEntries() { return set; } + /** + * Method to get a supplier for the next power value based on the provided input time. Depending + * on the implementation the supplier will either always return the same value or each time a + * random value. To return one constant value please use {@link #getValue(ZonedDateTime)}. + * + * @param time Queried time. + * @return A supplier for an option on the value at the given time step. + */ + public Supplier> supplyValue(ZonedDateTime time) { + int quarterHour = TimeSeriesUtils.calculateQuarterHourOfDay(time); + LoadValues

loadValue = valueMapping.get(quarterHour); + return () -> Optional.ofNullable(loadValue.getValue(time, loadProfile)); + } + @Override public Optional getPreviousDateTime(ZonedDateTime time) { return Optional.of(time.minusMinutes(15)); } @Override - protected Optional getNextDateTime(ZonedDateTime time) { + public Optional getNextDateTime(ZonedDateTime time) { return Optional.of(time.plusMinutes(15)); } - @Override - public List getTimeKeysAfter(ZonedDateTime time) { - return List.of(time.plusMinutes(15)); // dummy value that will return next quarter-hour value - } - /** Returns the value mapping. */ protected Map getValueMapping() { return valueMapping; diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RepetitiveTimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RepetitiveTimeSeries.java index 160331c3d..a3b223e91 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RepetitiveTimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RepetitiveTimeSeries.java @@ -32,10 +32,4 @@ protected RepetitiveTimeSeries(Set entries) { public Optional getValue(ZonedDateTime time) { return Optional.ofNullable(calc(time)); } - - @Override - public List getTimeKeysAfter(ZonedDateTime time) { - // dummy value - return getNextDateTime(time).map(List::of).orElseGet(Collections::emptyList); - } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactoryTest.groovy index c82957c8a..e42bd120c 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/BdewLoadProfileFactoryTest.groovy @@ -5,7 +5,7 @@ */ package edu.ie3.datamodel.io.factory.timeseries -import edu.ie3.datamodel.io.naming.timeseries.LoadProfileMetaInformation + import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileEntry import edu.ie3.datamodel.models.value.load.BdewLoadValues @@ -14,8 +14,6 @@ import spock.lang.Shared import spock.lang.Specification import tech.units.indriya.quantity.Quantities -import java.time.Month - class BdewLoadProfileFactoryTest extends Specification { @Shared BdewLoadProfileFactory factory @@ -126,13 +124,8 @@ class BdewLoadProfileFactoryTest extends Specification { } def "A BDEWLoadProfileFactory builds time series from entries"() { - given: - UUID uuid = UUID.fromString("fa3894c1-25af-479c-8a40-1323bb9150a9") - LoadProfileMetaInformation metaInformation = new LoadProfileMetaInformation(uuid, "g0") - - when: - def lpts = factory.build(metaInformation, allEntries) + def lpts = factory.build(BdewStandardLoadProfile.G0, allEntries) then: lpts.loadProfile == BdewStandardLoadProfile.G0 diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactoryTest.groovy index 901a3a3b1..b3d2b54b4 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/RandomLoadProfileFactoryTest.groovy @@ -5,7 +5,7 @@ */ package edu.ie3.datamodel.io.factory.timeseries -import edu.ie3.datamodel.io.naming.timeseries.LoadProfileMetaInformation + import edu.ie3.datamodel.models.profile.LoadProfile import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileEntry import edu.ie3.datamodel.models.value.load.RandomLoadValues @@ -130,13 +130,8 @@ class RandomLoadProfileFactoryTest extends Specification { } def "A RandomLoadProfileFactory builds time series from entries"() { - given: - UUID uuid = UUID.fromString("fa3894c1-25af-479c-8a40-1323bb9150a9") - LoadProfileMetaInformation metaInformation = new LoadProfileMetaInformation(uuid, "random") - - when: - def lpts = factory.build(metaInformation, allEntries) + def lpts = factory.build(LoadProfile.RandomLoadProfile.RANDOM_LOAD_PROFILE, allEntries) then: lpts.loadProfile == LoadProfile.RandomLoadProfile.RANDOM_LOAD_PROFILE diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSourceIT.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSourceIT.groovy index 1fad10d5f..5e2fade92 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSourceIT.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/sql/SqlLoadProfileSourceIT.groovy @@ -5,7 +5,6 @@ */ package edu.ie3.datamodel.io.source.sql - import static edu.ie3.test.common.TimeSeriesSourceTestData.G3_VALUE_00MIN import static edu.ie3.test.common.TimeSeriesSourceTestData.TIME_00MIN @@ -13,6 +12,7 @@ import edu.ie3.datamodel.io.connectors.SqlConnector import edu.ie3.datamodel.io.factory.timeseries.BdewLoadProfileFactory import edu.ie3.datamodel.io.naming.DatabaseNamingStrategy import edu.ie3.datamodel.io.naming.timeseries.LoadProfileMetaInformation +import edu.ie3.datamodel.io.source.PowerValueSource import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile import edu.ie3.datamodel.models.value.load.BdewLoadValues import edu.ie3.test.helper.TestContainerHelper @@ -41,8 +41,6 @@ class SqlLoadProfileSourceIT extends Specification implements TestContainerHelpe static String schemaName = "public" - static UUID timeSeriesUuid = UUID.fromString("9b880468-309c-43c1-a3f4-26dd26266216") - def setupSpec() { // Copy sql import scripts into docker MountableFile sqlImportFile = getMountableFile("_timeseries/") @@ -56,7 +54,7 @@ class SqlLoadProfileSourceIT extends Specification implements TestContainerHelpe } connector = new SqlConnector(postgreSQLContainer.jdbcUrl, postgreSQLContainer.username, postgreSQLContainer.password) - def metaInformation = new LoadProfileMetaInformation(timeSeriesUuid, "g3") + def metaInformation = new LoadProfileMetaInformation("g3") namingStrategy = new DatabaseNamingStrategy() @@ -65,7 +63,7 @@ class SqlLoadProfileSourceIT extends Specification implements TestContainerHelpe def "A SqlTimeSeriesSource can read and correctly parse a single value for a specific date"() { when: - def value = loadSource.getValue(TIME_00MIN) + def value = loadSource.getValue(new PowerValueSource.TimeSeriesInputValue(TIME_00MIN)) then: value.present @@ -74,10 +72,10 @@ class SqlLoadProfileSourceIT extends Specification implements TestContainerHelpe def "A SqlTimeSeriesSource can read all value data"() { when: - def timeSeries = loadSource.timeSeries + def entries = loadSource.entries then: - timeSeries.entries.size() == 3 + entries.size() == 3 } def "The SqlTimeSeriesSource returns the time keys after a given key correctly"() { @@ -85,11 +83,9 @@ class SqlLoadProfileSourceIT extends Specification implements TestContainerHelpe def time = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") when: - def actual = loadSource.getTimeKeysAfter(time) + def actual = loadSource.getNextTimeKey(time) then: - actual == [ - TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:15:00Z") - ] + actual == Optional.of(TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:15:00Z")) } } diff --git a/src/test/groovy/edu/ie3/datamodel/models/timeseries/RandomLoadProfileTimeSeriesTest.groovy b/src/test/groovy/edu/ie3/datamodel/models/timeseries/RandomLoadProfileTimeSeriesTest.groovy new file mode 100644 index 000000000..8f79bb010 --- /dev/null +++ b/src/test/groovy/edu/ie3/datamodel/models/timeseries/RandomLoadProfileTimeSeriesTest.groovy @@ -0,0 +1,31 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.datamodel.models.timeseries + +import edu.ie3.datamodel.io.source.LoadProfileSource +import edu.ie3.util.TimeUtil +import spock.lang.Specification + +class RandomLoadProfileTimeSeriesTest extends Specification { + + + def "A RandomLoadProfileTimeSeries should supply a random value correctly"() { + given: + def lpts = LoadProfileSource.randomLoadProfile.getTimeSeries() + def time = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") + + def supplier = lpts.supplyValue(time) + + when: + def values = [ + supplier.get(), + supplier.get(), + supplier.get() + ].collect { it.get()} + then: + values.size() > 1 + } +}