Skip to content

Commit

Permalink
Matpower limits export (#2665)
Browse files Browse the repository at this point in the history
Signed-off-by: Geoffroy Jamgotchian <geoffroy.jamgotchian@rte-france.com>
  • Loading branch information
geofjamg authored Sep 18, 2023
1 parent 679d850 commit d5d1411
Show file tree
Hide file tree
Showing 7 changed files with 704 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import com.powsybl.iidm.network.extensions.SlackTerminal;
import com.powsybl.iidm.network.util.HvdcUtils;
import com.powsybl.matpower.model.*;

import org.apache.commons.math3.complex.Complex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -26,6 +25,8 @@
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.util.*;
import java.util.function.DoubleUnaryOperator;
import java.util.stream.Stream;

/**
* @author Geoffroy Jamgotchian <geoffroy.jamgotchian at rte-france.com>
Expand Down Expand Up @@ -220,11 +221,137 @@ private static void createBuses(Network network, MatpowerModel model, Context co
createTransformerStarBuses(network, model, context);
}

private static boolean isEmergencyLimit(LoadingLimits.TemporaryLimit limit) {
return limit.getAcceptableDuration() <= 60;
}

private static Optional<LoadingLimits.TemporaryLimit> findShortTermLimit(Stream<LoadingLimits.TemporaryLimit> limitStream) {
return limitStream.filter(limit -> !isEmergencyLimit(limit))
.max(Comparator.comparing(LoadingLimits.TemporaryLimit::getAcceptableDuration));
}

private static Optional<LoadingLimits.TemporaryLimit> findEmergencyLimit(Stream<LoadingLimits.TemporaryLimit> limitStream) {
return limitStream.filter(MatpowerExporter::isEmergencyLimit)
.min(Comparator.comparing(LoadingLimits.TemporaryLimit::getAcceptableDuration));
}

private static Optional<LoadingLimits.TemporaryLimit> previousLimit(Collection<LoadingLimits.TemporaryLimit> limits, LoadingLimits.TemporaryLimit limit) {
return limits.stream().filter(l -> l.getAcceptableDuration() > limit.getAcceptableDuration())
.min(Comparator.comparing(LoadingLimits.TemporaryLimit::getAcceptableDuration));
}

private static Optional<LoadingLimits.TemporaryLimit> nextLimit(Collection<LoadingLimits.TemporaryLimit> limits, LoadingLimits.TemporaryLimit limit) {
return limits.stream().filter(l -> l.getAcceptableDuration() < limit.getAcceptableDuration())
.max(Comparator.comparing(LoadingLimits.TemporaryLimit::getAcceptableDuration));
}

private static double toApparentPower(double current, VoltageLevel vl) {
return current * vl.getNominalV() / 1000d;
}

private static void createLimits(MBranch mBranch, LoadingLimits limits, DoubleUnaryOperator converter) {
// rateA is mapped to permanent limit
if (!Double.isNaN(limits.getPermanentLimit())) {
mBranch.setRateA(converter.applyAsDouble(limits.getPermanentLimit()));
}
// rateB is mapped to the shortest term limit, if not an emergency limit (tempo <= 60s)
LoadingLimits.TemporaryLimit limitB = findShortTermLimit(limits.getTemporaryLimits().stream())
.filter(limit -> !isEmergencyLimit(limit) && limit.getValue() != Double.MAX_VALUE)
.orElse(null);
if (limitB != null) {
mBranch.setRateB(converter.applyAsDouble(limitB.getValue()));
}
// rateC is mapped to the emergency limit (tempo <= 60s)
findEmergencyLimit(limits.getTemporaryLimits().stream())
.flatMap(limit -> previousLimit(limits.getTemporaryLimits(), limit))
.filter(limit -> limitB == null || limit.getAcceptableDuration() != limitB.getAcceptableDuration())
.ifPresent(limitC -> mBranch.setRateC(converter.applyAsDouble(limitC.getValue())));
}

private static void createLimits(List<FlowsLimitsHolder> limitsHolders, VoltageLevel vl, MBranch mBranch) {
limitsHolders.stream().flatMap(limitsHolder -> Stream.concat(limitsHolder.getApparentPowerLimits().stream(), // apparrent power limits first then current limits
limitsHolder.getCurrentLimits().stream()))
.filter(limits -> !Double.isNaN(limits.getPermanentLimit())) // skip when there is no permanent
.max(Comparator.comparingInt(loadingLimit -> loadingLimit.getTemporaryLimits().size())) // many tempary limits first
.ifPresent(limits -> {
if (limits.getLimitType() == LimitType.CURRENT) {
createLimits(mBranch, limits, current -> toApparentPower(current, vl)); // convert from A to MVA
} else {
createLimits(mBranch, limits, DoubleUnaryOperator.identity());
}
});
}

/**
* Arbitrary adapted on side one.
*/
private static class FlowsLimitsHolderBranchAdapter implements FlowsLimitsHolder {

private final Branch<?> branch;

private final Branch.Side side;

public FlowsLimitsHolderBranchAdapter(Branch<?> branch, Branch.Side side) {
this.branch = branch;
this.side = side;
}

@Override
public Optional<CurrentLimits> getCurrentLimits() {
return branch.getCurrentLimits(side);
}

@Override
public CurrentLimits getNullableCurrentLimits() {
return branch.getNullableCurrentLimits(side);
}

@Override
public Optional<ActivePowerLimits> getActivePowerLimits() {
return branch.getActivePowerLimits(side);
}

@Override
public ActivePowerLimits getNullableActivePowerLimits() {
return branch.getNullableActivePowerLimits(side);
}

@Override
public Optional<ApparentPowerLimits> getApparentPowerLimits() {
return branch.getApparentPowerLimits(side);
}

@Override
public ApparentPowerLimits getNullableApparentPowerLimits() {
return branch.getNullableApparentPowerLimits(side);
}

@Override
public CurrentLimitsAdder newCurrentLimits() {
throw new UnsupportedOperationException();
}

@Override
public ApparentPowerLimitsAdder newApparentPowerLimits() {
throw new UnsupportedOperationException();
}

@Override
public ActivePowerLimitsAdder newActivePowerLimits() {
throw new UnsupportedOperationException();
}
}

private void createLines(Network network, MatpowerModel model, Context context) {
for (Line l : network.getLines()) {
Terminal t1 = l.getTerminal1();
Terminal t2 = l.getTerminal2();
createMBranch(t1, t2, l.getR(), l.getX(), l.getB1(), l.getB2(), context).ifPresent(model::addBranch);
createMBranch(t1, t2, l.getR(), l.getX(), l.getB1(), l.getB2(), context)
.ifPresent(branch -> {
createLimits(List.of(new FlowsLimitsHolderBranchAdapter(l, Branch.Side.ONE), new FlowsLimitsHolderBranchAdapter(l, Branch.Side.TWO)),
t1.getVoltageLevel(), branch);
model.addBranch(branch);
});
}
}

Expand Down Expand Up @@ -265,6 +392,8 @@ private void createTransformers2(Network network, MatpowerModel model, Context c
mBranch.setR(r / zb);
mBranch.setX(x / zb);
mBranch.setB(b * zb);
createLimits(List.of(new FlowsLimitsHolderBranchAdapter(twt, Branch.Side.ONE), new FlowsLimitsHolderBranchAdapter(twt, Branch.Side.TWO)),
t1.getVoltageLevel(), mBranch);
model.addBranch(mBranch);
}
}
Expand All @@ -274,7 +403,12 @@ private void createTieLines(Network network, MatpowerModel model, Context contex
for (TieLine l : network.getTieLines()) {
Terminal t1 = l.getDanglingLine1().getTerminal();
Terminal t2 = l.getDanglingLine2().getTerminal();
createMBranch(t1, t2, l.getR(), l.getX(), l.getB1(), l.getB2(), context).ifPresent(model::addBranch);
createMBranch(t1, t2, l.getR(), l.getX(), l.getB1(), l.getB2(), context)
.ifPresent(branch -> {
createLimits(List.of(new FlowsLimitsHolderBranchAdapter(l, Branch.Side.ONE), new FlowsLimitsHolderBranchAdapter(l, Branch.Side.TWO)),
t1.getVoltageLevel(), branch);
model.addBranch(branch);
});
}
}

Expand Down Expand Up @@ -336,6 +470,7 @@ private void createDanglingLineBranches(Network network, MatpowerModel model, Co
mBranch.setR(dl.getR() / zb);
mBranch.setX(dl.getX() / zb);
mBranch.setB(dl.getB() * zb);
createLimits(List.of(dl), t.getVoltageLevel(), mBranch);
model.addBranch(mBranch);
}
}
Expand Down Expand Up @@ -389,6 +524,7 @@ private static MBranch createTransformerLeg(ThreeWindingsTransformer twt, ThreeW
mBranch.setX(x / zb);
mBranch.setB(b * zb);
mBranch.setRatio(1d / rho);
createLimits(List.of(leg), leg.getTerminal().getVoltageLevel(), mBranch);
return mBranch;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.powsybl.commons.datasource.FileDataSource;
import com.powsybl.commons.datasource.MemDataSource;
import com.powsybl.commons.test.AbstractConverterTest;
import com.powsybl.iidm.network.Line;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.NetworkFactory;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
Expand Down Expand Up @@ -128,6 +129,60 @@ void testNonRegulatingGenOnPVBus() throws IOException {
exportToMatAndCompareTo(network, "/sim1-with-non-regulating-gen.json");
}

@Test
void testWithCurrentLimits() throws IOException {
var network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
exportToMatAndCompareTo(network, "/sim1-with-current-limits.json");
}

@Test
void testWithCurrentLimits2() throws IOException {
var network = EurostagTutorialExample1Factory.create();
Line line = network.getLine("NHV1_NHV2_1");
line.newCurrentLimits1()
.setPermanentLimit(1000)
.beginTemporaryLimit()
.setName("20'")
.setAcceptableDuration(20 * 60)
.setValue(1100)
.endTemporaryLimit()
.beginTemporaryLimit()
.setName("10'")
.setAcceptableDuration(10 * 60)
.setValue(1200)
.endTemporaryLimit()
.beginTemporaryLimit()
.setName("1'")
.setAcceptableDuration(60)
.setValue(1300)
.endTemporaryLimit()
.beginTemporaryLimit()
.setName("N/A")
.setAcceptableDuration(0)
.setValue(Double.MAX_VALUE)
.endTemporaryLimit()
.add();
exportToMatAndCompareTo(network, "/sim1-with-current-limits2.json");
}

@Test
void testWithApparentPowerLimits() throws IOException {
var network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
var l = network.getLine("NHV1_NHV2_1");
l.newApparentPowerLimits1()
.setPermanentLimit(1000)
.beginTemporaryLimit()
.setName("1'")
.setAcceptableDuration(60)
.setValue(1500)
.endTemporaryLimit()
.add();
l.newCurrentLimits2()
.setPermanentLimit(1000)
.add();
exportToMatAndCompareTo(network, "/sim1-with-apparent-power-limits.json");
}

@Test
void testNanTargetQIssue() throws IOException {
var network = EurostagTutorialExample1Factory.create();
Expand Down
Loading

0 comments on commit d5d1411

Please sign in to comment.