Skip to content

Adding support for custom load profiles. #1368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<LoadInput, SystemParticipantEntityData> {
private static final Logger logger = LoggerFactory.getLogger(LoadInputFactory.class);

private static final String LOAD_PROFILE = "loadProfile";
private static final String E_CONS_ANNUAL = "eConsAnnual";
Expand All @@ -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<Energy> eConsAnnual =
Expand Down
17 changes: 11 additions & 6 deletions src/main/java/edu/ie3/datamodel/io/source/LoadProfileSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,24 @@ protected Try<LoadProfileEntry<V>, FactoryException> createEntries(
public abstract Optional<ComparableQuantity<Energy>> getLoadProfileEnergyScaling();

/**
* Returns the resolution for the given {@link LoadProfile}.
* Returns an option for the resolution for the given {@link LoadProfile}.
*
* <p>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<Long> 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);
}
}

Expand Down
32 changes: 16 additions & 16 deletions src/main/java/edu/ie3/datamodel/models/profile/LoadProfile.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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 extends LoadProfile> T getProfile(T[] profiles, String key) throws ParsingException {
@SuppressWarnings("unchecked")
static <T extends LoadProfile> 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) {
Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,7 @@ private static List<Try<Void, InvalidEntityException>> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down