diff --git a/CHANGELOG.md b/CHANGELOG.md index c05007a28..59212388a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Replaced `return this` with `return thisInstance` in CopyBuilders [#1250](https://github.com/ie3-institute/PowerSystemDataModel/issues/1250) +- Enhancing load profile source [#1294](https://github.com/ie3-institute/PowerSystemDataModel/issues/1294) ## [6.0.0] - 2025-02-27 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 2d10a0c00..78c0ab07a 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 @@ -15,6 +15,8 @@ import edu.ie3.datamodel.models.timeseries.repetitive.BdewLoadProfileTimeSeries; import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileEntry; import edu.ie3.datamodel.models.value.load.BdewLoadValues; +import edu.ie3.datamodel.models.value.load.LoadValues; +import java.time.ZonedDateTime; import java.util.*; import java.util.function.Function; import java.util.stream.Stream; @@ -98,6 +100,12 @@ public BdewStandardLoadProfile parseProfile(String profile) { } } + @Override + public LoadValues.Provider buildProvider( + BdewLoadValues loadValue, ZonedDateTime time, BdewStandardLoadProfile loadProfile) { + return last -> loadValue.getValue(time, loadProfile); + } + @Override public ComparableQuantity calculateMaxPower( BdewStandardLoadProfile loadProfile, Set> entries) { 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 eea6e22d2..396506750 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 @@ -12,6 +12,7 @@ import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileTimeSeries; import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.util.quantities.PowerSystemUnits; +import java.time.ZonedDateTime; import java.util.Set; import javax.measure.quantity.Energy; import javax.measure.quantity.Power; @@ -37,6 +38,16 @@ public abstract LoadProfileTimeSeries build( public abstract P parseProfile(String profile); + /** + * Method to build a {@link LoadValues.Provider}. + * + * @param loadValue used for the provider + * @param time used for the provider + * @param loadProfile used for the provider + * @return a value provider + */ + public abstract LoadValues.Provider buildProvider(V loadValue, ZonedDateTime time, P loadProfile); + /** * Calculates the maximum average power consumption per quarter-hour for a given calculated over * all seasons and weekday types of given load 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 440639388..e8e64bb91 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 @@ -12,8 +12,10 @@ import edu.ie3.datamodel.models.profile.LoadProfile.RandomLoadProfile; import edu.ie3.datamodel.models.timeseries.repetitive.LoadProfileEntry; import edu.ie3.datamodel.models.timeseries.repetitive.RandomLoadProfileTimeSeries; +import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.datamodel.models.value.load.RandomLoadValues; import edu.ie3.util.quantities.PowerSystemUnits; +import java.time.ZonedDateTime; import java.util.List; import java.util.Set; import javax.measure.quantity.Energy; @@ -88,6 +90,12 @@ public RandomLoadProfile parseProfile(String profile) { return RANDOM_LOAD_PROFILE; } + @Override + public LoadValues.Provider buildProvider( + RandomLoadValues loadValue, ZonedDateTime time, RandomLoadProfile loadProfile) { + return last -> loadValue.sample(time); + } + /** * This is the 95 % quantile resulting from 10,000 evaluations of the year 2019. It is only * needed, when the load is meant to be scaled to rated active power. 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 60e30dd4e..5dac68cdc 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java @@ -21,8 +21,6 @@ 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; @@ -48,8 +46,7 @@ protected LoadProfileSource(Class entryClass, LoadProfileFactory entryF } /** - * Build a list of type {@code E}, whereas the underlying {@link Value} does not need any - * additional information. + * Build a {@link LoadProfileEntry} of type {@code V}. * * @param fieldToValues Mapping from field id to values * @return {@link Try} of simple time based value @@ -77,7 +74,7 @@ protected Try, FactoryException> createEntries( * @return an optional * @throws SourceException if an exception occurred */ - public abstract Optional getValue(ZonedDateTime time) throws SourceException; + public abstract Optional getValue(ZonedDateTime time) throws SourceException; /** Returns the load profile of this source. */ public abstract P getLoadProfile(); 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 f3b271617..dd58e0091 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 @@ -14,12 +14,14 @@ 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.value.PValue; 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.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; @@ -72,7 +74,7 @@ public List getTimeKeysAfter(ZonedDateTime time) { } @Override - public Optional getValue(ZonedDateTime time) throws SourceException { + public Optional getValue(ZonedDateTime time) throws SourceException { return loadProfileTimeSeries.getValue(time); } 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 28544f37e..d030c1126 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 @@ -17,7 +17,6 @@ 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.value.PValue; import edu.ie3.datamodel.models.value.Value; import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.datamodel.utils.TimeSeriesUtils; @@ -112,12 +111,14 @@ public List getTimeKeysAfter(ZonedDateTime time) { } @Override - public Optional getValue(ZonedDateTime time) throws SourceException { + 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 Optional.of(entries.stream().toList().get(0).getValue().getValue(time, loadProfile)); + + V loadValue = entries.stream().toList().get(0).getValue(); + return Optional.of(entryFactory.buildProvider(loadValue, time, loadProfile)); } @Override diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/BdewLoadProfileTimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/BdewLoadProfileTimeSeries.java index 524697166..ca7cc8be3 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/BdewLoadProfileTimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/BdewLoadProfileTimeSeries.java @@ -7,6 +7,8 @@ import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile; import edu.ie3.datamodel.models.value.load.BdewLoadValues; +import edu.ie3.datamodel.models.value.load.LoadValues; +import java.time.ZonedDateTime; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -29,6 +31,11 @@ public BdewLoadProfileTimeSeries( super(uuid, loadProfile, values, maxPower, profileEnergyScaling); } + @Override + protected LoadValues.Provider buildFunction(BdewLoadValues loadValue, ZonedDateTime time) { + return last -> loadValue.getValue(time, (BdewStandardLoadProfile) loadProfile); + } + @Override public BdewStandardLoadProfile getLoadProfile() { return (BdewStandardLoadProfile) super.getLoadProfile(); 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 c024e4efc..4b65b6e55 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 @@ -6,7 +6,6 @@ package edu.ie3.datamodel.models.timeseries.repetitive; import edu.ie3.datamodel.models.profile.LoadProfile; -import edu.ie3.datamodel.models.value.PValue; import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.datamodel.utils.TimeSeriesUtils; import java.time.ZonedDateTime; @@ -18,10 +17,12 @@ /** * Describes a load profile time series with repetitive values that can be calculated from a pattern + * + * @param type of {@link LoadValues} used in this time series. */ -public class LoadProfileTimeSeries - extends RepetitiveTimeSeries, V, PValue> { - private final LoadProfile loadProfile; +public abstract class LoadProfileTimeSeries + extends RepetitiveTimeSeries, V, LoadValues.Provider> { + protected final LoadProfile loadProfile; private final Map valueMapping; /** @@ -33,7 +34,7 @@ public class LoadProfileTimeSeries /** The profile energy scaling in kWh. */ private final ComparableQuantity profileEnergyScaling; - public LoadProfileTimeSeries( + protected LoadProfileTimeSeries( UUID uuid, LoadProfile loadProfile, Set> entries, @@ -50,6 +51,21 @@ public LoadProfileTimeSeries( this.profileEnergyScaling = profileEnergyScaling; } + @Override + protected LoadValues.Provider calc(ZonedDateTime time) { + int quarterHour = TimeSeriesUtils.calculateQuarterHourOfDay(time); + return buildFunction(valueMapping.get(quarterHour), time); + } + + /** + * Method to build a {@link LoadValues.Provider} for the given {@link LoadValues} and time. + * + * @param loadValue used for the provider + * @param time used for the provider + * @return a {@link LoadValues.Provider} + */ + protected abstract LoadValues.Provider buildFunction(V loadValue, ZonedDateTime time); + /** * Returns the maximum average power consumption per quarter-hour calculated over all seasons and * weekday types of given load profile in Watt. @@ -97,12 +113,6 @@ protected Map getValueMapping() { return valueMapping; } - @Override - protected PValue calc(ZonedDateTime time) { - int quarterHour = TimeSeriesUtils.calculateQuarterHourOfDay(time); - return valueMapping.get(quarterHour).getValue(time, loadProfile); - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RandomLoadProfileTimeSeries.java b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RandomLoadProfileTimeSeries.java index de9142fd9..e63c6b638 100644 --- a/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RandomLoadProfileTimeSeries.java +++ b/src/main/java/edu/ie3/datamodel/models/timeseries/repetitive/RandomLoadProfileTimeSeries.java @@ -7,7 +7,9 @@ import de.lmu.ifi.dbs.elki.math.statistics.distribution.GeneralizedExtremeValueDistribution; import edu.ie3.datamodel.models.profile.LoadProfile; +import edu.ie3.datamodel.models.value.load.LoadValues; import edu.ie3.datamodel.models.value.load.RandomLoadValues; +import java.time.ZonedDateTime; import java.util.Set; import java.util.UUID; import javax.measure.quantity.Energy; @@ -29,6 +31,11 @@ public RandomLoadProfileTimeSeries( super(uuid, loadProfile, entries, maxPower, profileEnergyScaling); } + @Override + protected LoadValues.Provider buildFunction(RandomLoadValues loadValue, ZonedDateTime time) { + return last -> loadValue.sample(time); + } + @Override public LoadProfile.RandomLoadProfile getLoadProfile() { return (LoadProfile.RandomLoadProfile) super.getLoadProfile(); diff --git a/src/main/java/edu/ie3/datamodel/models/value/load/BdewLoadValues.java b/src/main/java/edu/ie3/datamodel/models/value/load/BdewLoadValues.java index 278dcaa0e..cfe04ac74 100644 --- a/src/main/java/edu/ie3/datamodel/models/value/load/BdewLoadValues.java +++ b/src/main/java/edu/ie3/datamodel/models/value/load/BdewLoadValues.java @@ -12,7 +12,6 @@ import edu.ie3.datamodel.models.BdewSeason; import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile; -import edu.ie3.datamodel.models.profile.LoadProfile; import edu.ie3.datamodel.models.value.PValue; import java.time.ZonedDateTime; import java.util.List; @@ -21,7 +20,7 @@ import tech.units.indriya.quantity.Quantities; /** Load values for a {@link BdewStandardLoadProfile} */ -public class BdewLoadValues implements LoadValues { +public final class BdewLoadValues implements LoadValues { private final double suSa; private final double suSu; private final double suWd; @@ -53,8 +52,13 @@ public BdewLoadValues( this.wiWd = wiWd; } - @Override - public PValue getValue(ZonedDateTime time, LoadProfile loadProfile) { + /** + * Method to calculate an actual load power value for the given time. + * + * @param time given time + * @return a new {@link PValue} + */ + public PValue getValue(ZonedDateTime time, BdewStandardLoadProfile loadProfile) { Map mapping = switch (time.getDayOfWeek()) { case SATURDAY -> Map.of( diff --git a/src/main/java/edu/ie3/datamodel/models/value/load/LoadValues.java b/src/main/java/edu/ie3/datamodel/models/value/load/LoadValues.java index ae7fc4e26..657d9db1f 100644 --- a/src/main/java/edu/ie3/datamodel/models/value/load/LoadValues.java +++ b/src/main/java/edu/ie3/datamodel/models/value/load/LoadValues.java @@ -5,19 +5,38 @@ */ package edu.ie3.datamodel.models.value.load; -import edu.ie3.datamodel.models.profile.LoadProfile; import edu.ie3.datamodel.models.value.PValue; import edu.ie3.datamodel.models.value.Value; -import java.time.ZonedDateTime; +import java.util.Optional; /** Interface for load values. */ public interface LoadValues extends Value { - /** - * Method to calculate an actual load power value for the given time. - * - * @param time given time - * @return a new {@link PValue} - */ - PValue getValue(ZonedDateTime time, LoadProfile loadProfile); + /** Functional interface that is used to provide a {@link PValue}. */ + @FunctionalInterface + interface Provider extends Value { + + /** + * Method to provide a {@link PValue}. + * + * @param lastOption option for the last value. + * @return a new value + */ + PValue provide(Optional lastOption); + + /** Provides a {@link PValue}. */ + default PValue provide() { + return provide(Optional.empty()); + } + + /** + * Provides a {@link PValue} considering the last value. + * + * @param last {@link PValue}. + * @return a new {@link PValue} + */ + default PValue withLast(PValue last) { + return provide(Optional.of(last)); + } + } } diff --git a/src/main/java/edu/ie3/datamodel/models/value/load/RandomLoadValues.java b/src/main/java/edu/ie3/datamodel/models/value/load/RandomLoadValues.java index 3e9684e56..4db77b6ab 100644 --- a/src/main/java/edu/ie3/datamodel/models/value/load/RandomLoadValues.java +++ b/src/main/java/edu/ie3/datamodel/models/value/load/RandomLoadValues.java @@ -9,7 +9,6 @@ import de.lmu.ifi.dbs.elki.math.statistics.distribution.GeneralizedExtremeValueDistribution; import de.lmu.ifi.dbs.elki.utilities.random.RandomFactory; -import edu.ie3.datamodel.models.profile.LoadProfile; import edu.ie3.datamodel.models.value.PValue; import java.time.DayOfWeek; import java.time.ZonedDateTime; @@ -23,7 +22,7 @@ * sampled for each quarter hour of a day, subdivided into workdays, Saturdays and Sundays. In * general the GEV is described by the three parameters "location", "scale" and "shape" */ -public class RandomLoadValues implements LoadValues { +public final class RandomLoadValues implements LoadValues { /** Shape parameter for a Saturday */ private final double kSa; @@ -89,13 +88,18 @@ public RandomLoadValues( RandomFactory factory = RandomFactory.get(new Random().nextLong()); this.gevWd = new GeneralizedExtremeValueDistribution(myWd, sigmaWd, kWd, factory.getRandom()); - this.gevSa = new GeneralizedExtremeValueDistribution(mySa, sigmaSa, kSa, factory.getRandom()); this.gevSu = new GeneralizedExtremeValueDistribution(mySu, sigmaSu, kSu, factory.getRandom()); } - @Override - public PValue getValue(ZonedDateTime time, LoadProfile loadProfile) { + /** + * Method to sample a new random {@link PValue} from the {@link + * GeneralizedExtremeValueDistribution}. + * + * @param time to use for the sampling + * @return a new {@link PValue} + */ + public PValue sample(ZonedDateTime time) { return new PValue(Quantities.getQuantity(getValue(time.getDayOfWeek()), KILOWATT)); } 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 c29dcca76..0594799ed 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 @@ -69,7 +69,7 @@ class SqlLoadProfileSourceIT extends Specification implements TestContainerHelpe then: value.present - value.get().p.get() == G3_VALUE_00MIN.p.get() + value.get().provide().p.get() == G3_VALUE_00MIN.p.get() } def "A SqlTimeSeriesSource can read all value data"() {