diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d25d854..31e430051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enhancing the `LoadProfileSource` to return the resolution [1288](https://github.com/ie3-institute/PowerSystemDataModel/issues/1288) - Enhancing Validation for sRated of `HpTypeInput` [1394](https://github.com/ie3-institute/PowerSystemDataModel/issues/1394) - Added updated `BdewStandardLoadProfiles` [#1292](https://github.com/ie3-institute/PowerSystemDataModel/issues/1292) +- Added support for custom load profiles [#1362](https://github.com/ie3-institute/PowerSystemDataModel/issues/1362) ### Changed - Fixed CFF-Version [#1392](https://github.com/ie3-institute/PowerSystemDataModel/issues/1392) diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java index 71af7c515..360660da7 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java @@ -5,7 +5,6 @@ */ package edu.ie3.datamodel.io.factory.input.participant; -import edu.ie3.datamodel.exceptions.ParsingException; import edu.ie3.datamodel.models.OperationTime; import edu.ie3.datamodel.models.StandardUnits; import edu.ie3.datamodel.models.input.EmInput; @@ -17,13 +16,10 @@ import java.util.UUID; import javax.measure.quantity.Energy; import javax.measure.quantity.Power; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import tech.units.indriya.ComparableQuantity; public class LoadInputFactory extends SystemParticipantInputEntityFactory { - private static final Logger logger = LoggerFactory.getLogger(LoadInputFactory.class); private static final String LOAD_PROFILE = "loadProfile"; private static final String E_CONS_ANNUAL = "eConsAnnual"; @@ -48,16 +44,8 @@ protected LoadInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - LoadProfile loadProfile; - try { - loadProfile = LoadProfile.parse(data.getField(LOAD_PROFILE)); - } catch (ParsingException e) { - logger.warn( - "Cannot parse the standard load profile \"{}\" of load \"{}\". Assign no load profile instead.", - data.getField(LOAD_PROFILE), - id); - loadProfile = LoadProfile.DefaultLoadProfiles.NO_LOAD_PROFILE; - } + LoadProfile loadProfile = LoadProfile.parse(data.getField(LOAD_PROFILE)); + final EmInput em = data.getControllingEm().orElse(null); final ComparableQuantity eConsAnnual = 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 fa49931ad..c203b313e 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java @@ -89,19 +89,24 @@ protected Try, FactoryException> createEntries( public abstract Optional> getLoadProfileEnergyScaling(); /** - * Returns the resolution for the given {@link LoadProfile}. + * Returns an option for the resolution for the given {@link LoadProfile}. + * + *

Note: This method does not support {@link LoadProfile.CustomLoadProfile}. If a custom load + * profile is provided, no resolution is returned. * * @param loadProfile given load profile * @return the resolution in seconds. */ - public static long getResolution(LoadProfile loadProfile) { - + public static Optional getResolution(LoadProfile loadProfile) { if (loadProfile == LoadProfile.DefaultLoadProfiles.NO_LOAD_PROFILE) { - // since no load profile was assigned, we return the maximal possible value - return Long.MAX_VALUE; + // since no load profile was assigned, we return no resolution + return Optional.empty(); + } else if (loadProfile instanceof LoadProfile.CustomLoadProfile c) { + log.info("Custom load profile {} found. Cannot provide resolution!", c.key()); + return Optional.empty(); } else { // currently all registered profiles and all sources use 15 minutes intervals - return 900L; + return Optional.of(900L); } } 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 c68ba3435..fad725e3b 100644 --- a/src/main/java/edu/ie3/datamodel/models/profile/LoadProfile.java +++ b/src/main/java/edu/ie3/datamodel/models/profile/LoadProfile.java @@ -5,10 +5,8 @@ */ 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 { @@ -20,9 +18,8 @@ public interface LoadProfile extends Serializable { * * @param key Key to parse * @return Matching {@link StandardLoadProfile} - * @throws ParsingException If key cannot be parsed */ - static LoadProfile parse(String key) throws ParsingException { + static LoadProfile parse(String key) { if (key == null || key.isEmpty()) return LoadProfile.DefaultLoadProfiles.NO_LOAD_PROFILE; return LoadProfile.getProfile(getAllProfiles(), key); @@ -38,25 +35,21 @@ static LoadProfile[] getAllProfiles() { } /** - * Looks for load profile with given key and returns it. + * Looks for load profile with given key and returns it. If no suitable profile is found, a {@link + * CustomLoadProfile} with the given key is returned. * * @param profiles we search within * @param key to look for * @return the matching load profile */ - static T getProfile(T[] profiles, String key) throws ParsingException { + @SuppressWarnings("unchecked") + static T getProfile(T[] profiles, String key) { + String uniformKey = getUniformKey(key); + return Arrays.stream(profiles) - .filter(loadProfile -> loadProfile.getKey().equalsIgnoreCase(getUniformKey(key))) + .filter(loadProfile -> loadProfile.getKey().equalsIgnoreCase(uniformKey)) .findFirst() - .orElseThrow( - () -> - new ParsingException( - "No predefined load profile with key '" - + key - + "' found. Please provide one of the following keys: " - + Arrays.stream(profiles) - .map(LoadProfile::getKey) - .collect(Collectors.joining(", ")))); + .orElseGet(() -> (T) new CustomLoadProfile(uniformKey)); } private static String getUniformKey(String key) { @@ -72,6 +65,13 @@ public String getKey() { } } + record CustomLoadProfile(String key) implements LoadProfile { + @Override + public String getKey() { + return key; + } + } + enum RandomLoadProfile implements LoadProfile { RANDOM_LOAD_PROFILE; diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java index 083f5379f..c87e6d1e0 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java @@ -375,9 +375,7 @@ private static List> checkLoad(LoadInput loadI exceptions.add( Try.ofVoid( loadInput.getLoadProfile() == null, - () -> - new InvalidEntityException( - "No standard load profile defined for load", loadInput))); + () -> new InvalidEntityException("No load profile defined for load", loadInput))); exceptions.addAll( Try.ofVoid( diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/LoadProfileSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/LoadProfileSourceTest.groovy index 54270c5c4..737fdd066 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/LoadProfileSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/LoadProfileSourceTest.groovy @@ -22,7 +22,7 @@ class LoadProfileSourceTest extends Specification { def resolutions = Arrays.stream(allProfiles).map { it -> LoadProfileSource.getResolution(it) }.toList() then: - resolutions.every { resolution -> resolution == 900 } + resolutions.every { resolution -> resolution == Optional.of(900L) } } diff --git a/src/test/groovy/edu/ie3/datamodel/models/profile/LoadProfileTest.groovy b/src/test/groovy/edu/ie3/datamodel/models/profile/LoadProfileTest.groovy index a28d64bed..34bf43896 100644 --- a/src/test/groovy/edu/ie3/datamodel/models/profile/LoadProfileTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/models/profile/LoadProfileTest.groovy @@ -7,7 +7,6 @@ package edu.ie3.datamodel.models.profile import static edu.ie3.datamodel.models.profile.LoadProfile.RandomLoadProfile.RANDOM_LOAD_PROFILE -import edu.ie3.datamodel.exceptions.ParsingException import spock.lang.Specification class LoadProfileTest extends Specification { @@ -211,12 +210,19 @@ class LoadProfileTest extends Specification { "ez_2" || NbwTemperatureDependantLoadProfile.EZ2 } - def "Throws an exception when encountering an unknown key"() { + def "Return a custom load profile when encountering an unknown key"() { when: - LoadProfile.parse("not_a_key") + def custom = LoadProfile.parse("not_a_key") then: - def e = thrown(ParsingException) - e.message == "No predefined load profile with key 'not_a_key' found. Please provide one of the following keys: h0, h25, l0, l1, l2, l25, g0, g1, g2, g3, g4, g5, g6, g25, p25, s25, ep1, ez2, random" + custom == new LoadProfile.CustomLoadProfile("notakey") + } + + def "Return nothing when encountering an empty key"() { + when: + def actual = LoadProfile.parse(null) + + then: + actual == LoadProfile.DefaultLoadProfiles.NO_LOAD_PROFILE } } diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy index 9086568f6..74c6a7133 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy @@ -324,7 +324,7 @@ class SystemParticipantValidationUtilsTest extends Specification { where: invalidLoad || expectedSize || expectedException - SystemParticipantTestData.loadInput.copy().loadprofile(null).build() || 1 || new InvalidEntityException("No standard load profile defined for load", invalidLoad) + SystemParticipantTestData.loadInput.copy().loadprofile(null).build() || 1 || new InvalidEntityException("No load profile defined for load", invalidLoad) SystemParticipantTestData.loadInput.copy().sRated(Quantities.getQuantity(-25d, ACTIVE_POWER_IN)).eConsAnnual(Quantities.getQuantity(-4000, ENERGY_IN)).build() || 1 || new InvalidEntityException("The following quantities have to be zero or positive: -25 kVA, -4000 kWh", invalidLoad) SystemParticipantTestData.loadInput.copy().cosPhiRated(2).build() || 1 || new InvalidEntityException("Rated power factor of LoadInput must be between 0 and 1", invalidLoad) }