Skip to content

Adding a timeseries for voltage values. #1130

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

Merged
merged 12 commits into from
Dec 3, 2024
Merged
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 @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Enhance `TimeSeriesSource` with method to retrieve all time keys after a given key [#543](https://github.com/ie3-institute/PowerSystemDataModel/issues/543)
- Enhance `WeatherSource` with method to retrieve all time keys after a given key [#572](https://github.com/ie3-institute/PowerSystemDataModel/issues/572)
- Adding timeseries for voltage values [#1128](https://github.com/ie3-institute/PowerSystemDataModel/issues/1128)

### Fixed

Expand Down
18 changes: 11 additions & 7 deletions docs/readthedocs/io/csvfiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,34 @@ This is the UUID from the example above `2fcb3e53-b94a-4b96-bea4-c469e499f1a1`.
The following keys are supported until now:
```{list-table}
:widths: auto
:class: wrapping
:header-rows: 1

* - Key
- Information and supported head line
- Information and supported head line.
* - c
- An energy price (e.g. in €/MWh; c stands for charge).
Permissible head line: ``time,price``
* - p
- Active power
- Active power.
Permissible head line: ``time,p``
* - pq
- Active and reactive power
- Active and reactive power.
Permissible head line: ``time,p,q``
* - h
- Heat power demand
- Heat power demand.
Permissible head line: ``time,h``
* - ph
- Active and heat power
- Active and heat power.
Permissible head line: ``time,p,h``
* - pqh
- Active, reactive and heat power
- Active, reactive and heat power.
Permissible head line: ``time,p,q,h``
* - v
- Voltage mangnitude in pu and angle in °.
Permissible head line: ``time,vMag,vAng``
* - weather
- Weather information
- Weather information.
Permissible head line: ``time,coordinate,direct_irradiation,diffuse_irradiation,temperature,wind_velocity,wind_direction``

```
Expand Down
5 changes: 4 additions & 1 deletion docs/readthedocs/models/input/additionaldata/timeseries.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ The following different values are available:

* - `WindValue`
- Combination of wind direction and wind velocity


* - `VoltageValue`
- Combination of voltage magnitude in p.u. and angle in °

* - `WeatherValue`
- Combination of irradiance, temperature and wind information

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package edu.ie3.datamodel.exceptions;

/**
* Is thrown, when an something went wrong during entity field mapping creation in a {@link
* Is thrown, when something went wrong during entity field mapping creation in a {@link
* edu.ie3.datamodel.io.processor.EntityProcessor}
*/
public class EntityProcessorException extends Exception {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/edu/ie3/datamodel/io/factory/FactoryData.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ public <Q extends Quantity<Q>> ComparableQuantity<Q> getQuantity(String field, U
return Quantities.getQuantity(getDouble(field), unit);
}

/**
* Returns field value for given field name, or empty Optional if field does not exist.
*
* @param field field name
* @param unit unit of Quantity
* @param <Q> unit type parameter
* @return field value
*/
public <Q extends Quantity<Q>> Optional<ComparableQuantity<Q>> getQuantityOptional(
String field, Unit<Q> unit) {
return Optional.ofNullable(fieldsToAttributes.get(field))
.filter(str -> !str.isEmpty())
.map(Double::parseDouble)
.map(value -> Quantities.getQuantity(value, unit));
}

/**
* Returns int value for given field name. Throws {@link FactoryException} if field does not exist
* or parsing fails.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public class TimeBasedSimpleValueFactory<V extends Value>
private static final String REACTIVE_POWER = "q";
private static final String HEAT_DEMAND = "heatDemand";

/* voltage */
private static final String VMAG = "vMag";
private static final String VANG = "VAng";

public TimeBasedSimpleValueFactory(Class<? extends V> valueClasses) {
super(valueClasses);
}
Expand All @@ -35,6 +39,7 @@ public TimeBasedSimpleValueFactory(
}

@Override
@SuppressWarnings("unchecked")
protected TimeBasedValue<V> buildModel(SimpleTimeBasedValueData<V> data) {
ZonedDateTime time = timeUtil.toZonedDateTime(data.getField(TIME));
V value;
Expand Down Expand Up @@ -64,6 +69,12 @@ protected TimeBasedValue<V> buildModel(SimpleTimeBasedValueData<V> data) {
data.getQuantity(REACTIVE_POWER, REACTIVE_POWER_IN));
} else if (PValue.class.isAssignableFrom(data.getTargetClass())) {
value = (V) new PValue(data.getQuantity(ACTIVE_POWER, ACTIVE_POWER_IN));
} else if (VoltageValue.class.isAssignableFrom(data.getTargetClass())) {
value =
(V)
new VoltageValue(
data.getQuantity(VMAG, VOLTAGE_MAGNITUDE),
data.getQuantityOptional(VANG, VOLTAGE_ANGLE));
} else {
throw new FactoryException(
"The given factory cannot handle target class '" + data.getTargetClass() + "'.");
Expand All @@ -88,6 +99,8 @@ protected List<Set<String>> getFields(Class<?> entityClass) {
minConstructorParams.addAll(Arrays.asList(ACTIVE_POWER, REACTIVE_POWER));
} else if (PValue.class.isAssignableFrom(entityClass)) {
minConstructorParams.add(ACTIVE_POWER);
} else if (VoltageValue.class.isAssignableFrom(entityClass)) {
minConstructorParams.addAll(List.of(VMAG, VANG));
} else {
throw new FactoryException(
"The given factory cannot handle target class '" + entityClass + "'.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public enum ColumnScheme {
HEAT_DEMAND("h", HeatDemandValue.class),
ACTIVE_POWER_AND_HEAT_DEMAND("ph", HeatAndPValue.class),
APPARENT_POWER_AND_HEAT_DEMAND("pqh", HeatAndSValue.class),
WEATHER("weather", WeatherValue.class);
WEATHER("weather", WeatherValue.class),
VOLTAGE("v", VoltageValue.class);

private final String scheme;
private final Class<? extends Value> valueClass;
Expand Down Expand Up @@ -57,6 +58,7 @@ public static <V extends Value> Optional<ColumnScheme> parse(Class<V> valueClass
if (PValue.class.isAssignableFrom(valueClass)) return Optional.of(ACTIVE_POWER);
if (HeatDemandValue.class.isAssignableFrom(valueClass)) return Optional.of(HEAT_DEMAND);
if (WeatherValue.class.isAssignableFrom(valueClass)) return Optional.of(WEATHER);
if (VoltageValue.class.isAssignableFrom(valueClass)) return Optional.of(VOLTAGE);
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected EntityProcessor(Class<? extends T> registeredClass) throws EntityProce
* during processing
*/
public LinkedHashMap<String, String> handleEntity(T entity) throws EntityProcessorException {
if (!registeredClass.equals(entity.getClass()))
if (!registeredClass.isAssignableFrom(entity.getClass()))
throw new EntityProcessorException(
"Cannot process "
+ entity.getClass().getSimpleName()
Expand Down
90 changes: 90 additions & 0 deletions src/main/java/edu/ie3/datamodel/models/value/VoltageValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* © 2024. TU Dortmund University,
* Institute of Energy Systems, Energy Efficiency and Energy Economics,
* Research group Distribution grid planning and operation
*/
package edu.ie3.datamodel.models.value;

import static edu.ie3.datamodel.models.StandardUnits.VOLTAGE_ANGLE;
import static edu.ie3.util.quantities.PowerSystemUnits.DEGREE_GEOM;
import static edu.ie3.util.quantities.PowerSystemUnits.PU;
import static java.lang.Math.*;

import java.util.Objects;
import java.util.Optional;
import javax.measure.quantity.Angle;
import javax.measure.quantity.Dimensionless;
import tech.units.indriya.ComparableQuantity;
import tech.units.indriya.quantity.Quantities;

/** Describes a voltage value as a pair of magnitude and angle */
public class VoltageValue implements Value {

/** Magnitude of the voltage in p.u. */
private final ComparableQuantity<Dimensionless> magnitude;
/** Angle of the voltage in degree */
private final ComparableQuantity<Angle> angle;

/**
* @param magnitude of the voltage in p.u.
* @param angleOption option for the angle of this voltage in degree
*/
public VoltageValue(
ComparableQuantity<Dimensionless> magnitude,
Optional<ComparableQuantity<Angle>> angleOption) {
this.magnitude = magnitude;
this.angle = angleOption.orElse(Quantities.getQuantity(0.0, VOLTAGE_ANGLE));
}

/**
* @param magnitude of the voltage in p.u.
* @param angle of the voltage in degree
*/
public VoltageValue(
ComparableQuantity<Dimensionless> magnitude, ComparableQuantity<Angle> angle) {
this.magnitude = magnitude;
this.angle = angle;
}

public Optional<ComparableQuantity<Dimensionless>> getMagnitude() {
return Optional.ofNullable(magnitude);
}

public Optional<ComparableQuantity<Angle>> getAngle() {
return Optional.ofNullable(angle);
}

public Optional<ComparableQuantity<Dimensionless>> getRealPart() {
double mag = magnitude.to(PU).getValue().doubleValue();
double ang = angle.to(DEGREE_GEOM).getValue().doubleValue();

double eInPu = mag * cos(toRadians(ang));
return Optional.of(Quantities.getQuantity(eInPu, PU));
}

public Optional<ComparableQuantity<Dimensionless>> getImagPart() {
double mag = magnitude.to(PU).getValue().doubleValue();
double ang = angle.to(DEGREE_GEOM).getValue().doubleValue();

double fInPu = mag * sin(toRadians(ang));
return Optional.of(Quantities.getQuantity(fInPu, PU));
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VoltageValue that = (VoltageValue) o;
return Objects.equals(magnitude, that.magnitude) && Objects.equals(angle, that.angle);
}

@Override
public int hashCode() {
return Objects.hash(magnitude, angle);
}

@Override
public String toString() {
return "VoltageValue{" + "magnitude=" + magnitude + ", angle=" + angle + '}';
}
}
3 changes: 2 additions & 1 deletion src/main/java/edu/ie3/datamodel/utils/TimeSeriesUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public class TimeSeriesUtils {
ENERGY_PRICE,
APPARENT_POWER_AND_HEAT_DEMAND,
ACTIVE_POWER_AND_HEAT_DEMAND,
HEAT_DEMAND);
HEAT_DEMAND,
VOLTAGE);

/** Private Constructor as this class is not meant to be instantiated */
private TimeSeriesUtils() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ class CsvTimeSeriesMetaInformationSourceIT extends Specification implements CsvT
new CsvIndividualTimeSeriesMetaInformation(UUID.fromString("9185b8c1-86ba-4a16-8dea-5ac898e8caa5"), ColumnScheme.ACTIVE_POWER, Path.of('its_p_9185b8c1-86ba-4a16-8dea-5ac898e8caa5')),
new CsvIndividualTimeSeriesMetaInformation(UUID.fromString("3fbfaa97-cff4-46d4-95ba-a95665e87c26"), ColumnScheme.APPARENT_POWER, Path.of('its_pq_3fbfaa97-cff4-46d4-95ba-a95665e87c26')),
new CsvIndividualTimeSeriesMetaInformation(UUID.fromString("46be1e57-e4ed-4ef7-95f1-b2b321cb2047"), ColumnScheme.APPARENT_POWER_AND_HEAT_DEMAND, Path.of('its_pqh_46be1e57-e4ed-4ef7-95f1-b2b321cb2047')),
new CsvIndividualTimeSeriesMetaInformation(UUID.fromString("1061af70-1c03-46e1-b960-940b956c429f"), ColumnScheme.APPARENT_POWER, Path.of('its_pq_1061af70-1c03-46e1-b960-940b956c429f'))
new CsvIndividualTimeSeriesMetaInformation(UUID.fromString("1061af70-1c03-46e1-b960-940b956c429f"), ColumnScheme.APPARENT_POWER, Path.of('its_pq_1061af70-1c03-46e1-b960-940b956c429f')),
new CsvIndividualTimeSeriesMetaInformation(UUID.fromString("eeccbe3c-a47e-448e-8eca-1f369d3c24e6"), ColumnScheme.VOLTAGE, Path.of("its_v_eeccbe3c-a47e-448e-8eca-1f369d3c24e6"))
)

when:
def actual = source.timeSeriesMetaInformation

then:
actual.size() == 7
actual.size() == 8
actual.every {
it.key == it.value.uuid &&
expectedTimeSeries.contains(it.value)
Expand All @@ -62,6 +63,7 @@ class CsvTimeSeriesMetaInformationSourceIT extends Specification implements CsvT
"3fbfaa97-cff4-46d4-95ba-a95665e87c26" || "pq"
"46be1e57-e4ed-4ef7-95f1-b2b321cb2047" || "pqh"
"1061af70-1c03-46e1-b960-940b956c429f" || "pq"
"eeccbe3c-a47e-448e-8eca-1f369d3c24e6" || "v"
}

def "The CSV time series meta information source returns an empty optional for an unknown time series UUID"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,6 @@ class CsvTimeSeriesSourceTest extends Specification implements CsvTestDataMeta {
UUID.fromString("76c9d846-797c-4f07-b7ec-2245f679f5c7") | ColumnScheme.ACTIVE_POWER_AND_HEAT_DEMAND | Path.of("its_ph_76c9d846-797c-4f07-b7ec-2245f679f5c7") || 2 | HeatAndPValue
UUID.fromString("3fbfaa97-cff4-46d4-95ba-a95665e87c26") | ColumnScheme.APPARENT_POWER | Path.of("its_pq_3fbfaa97-cff4-46d4-95ba-a95665e87c26") || 2 | SValue
UUID.fromString("46be1e57-e4ed-4ef7-95f1-b2b321cb2047") | ColumnScheme.APPARENT_POWER_AND_HEAT_DEMAND | Path.of("its_pqh_46be1e57-e4ed-4ef7-95f1-b2b321cb2047") || 2 | HeatAndSValue
UUID.fromString("eeccbe3c-a47e-448e-8eca-1f369d3c24e6") | ColumnScheme.VOLTAGE | Path.of("its_v_eeccbe3c-a47e-448e-8eca-1f369d3c24e6") || 2 | VoltageValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* © 2024. TU Dortmund University,
* Institute of Energy Systems, Energy Efficiency and Energy Economics,
* Research group Distribution grid planning and operation
*/
package edu.ie3.datamodel.models.value

import static edu.ie3.util.quantities.PowerSystemUnits.*

import edu.ie3.util.quantities.QuantityUtil
import spock.lang.Shared
import spock.lang.Specification
import tech.units.indriya.quantity.Quantities

class VoltageValueTest extends Specification {

@Shared
double tolerance = 1e-10

def "A VoltageValue should return the real part correctly"() {
when:
def actual = value.realPart

then:
actual.isPresent()
QuantityUtil.isEquivalentAbs(actual.get(), expected, tolerance)

where:
value | expected
new VoltageValue(Quantities.getQuantity(1, PU), Quantities.getQuantity(0, DEGREE_GEOM)) | Quantities.getQuantity(1, PU)
new VoltageValue(Quantities.getQuantity(1, PU), Quantities.getQuantity(45, DEGREE_GEOM)) | Quantities.getQuantity(0.7071067811865476, PU)
new VoltageValue(Quantities.getQuantity(1, PU), Quantities.getQuantity(90, DEGREE_GEOM)) | Quantities.getQuantity(0, PU)
}


def "A VoltageValue should return the imaginary part correctly"() {
when:
def actual = value.imagPart

then:
actual.isPresent()
QuantityUtil.isEquivalentAbs(actual.get(), expected, tolerance)

where:
value | expected
new VoltageValue(Quantities.getQuantity(1, PU), Quantities.getQuantity(0, DEGREE_GEOM)) | Quantities.getQuantity(0, PU)
new VoltageValue(Quantities.getQuantity(1, PU), Quantities.getQuantity(45, DEGREE_GEOM)) | Quantities.getQuantity(0.7071067811865475, PU)
new VoltageValue(Quantities.getQuantity(1, PU), Quantities.getQuantity(90, DEGREE_GEOM)) | Quantities.getQuantity(1, PU)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"time";"vMag";"vAng"
2020-01-01T00:00:00Z;1.0;45.0
2020-01-01T00:15:00Z;0.9;