ActionExpressionEvaluator.java
/**
* Copyright (c) 2017, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.action.ial.dsl.ast;
import com.powsybl.commons.PowsyblException;
import com.powsybl.dsl.GroovyUtil;
import com.powsybl.dsl.ast.ExpressionEvaluator;
import com.powsybl.dsl.ast.ExpressionNode;
import com.powsybl.iidm.network.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public class ActionExpressionEvaluator extends ExpressionEvaluator implements ActionExpressionVisitor<Object, Void> {
private final EvaluationContext context;
public ActionExpressionEvaluator(EvaluationContext context) {
this.context = Objects.requireNonNull(context);
}
public static Object evaluate(ExpressionNode node, EvaluationContext context) {
return node.accept(new ActionExpressionEvaluator(context), null);
}
@Override
public Object visitNetworkComponent(NetworkComponentNode node, Void arg) {
Identifiable identifiable = context.getNetwork().getIdentifiable(node.getComponentId());
if (identifiable == null) {
throw new PowsyblException("Network component '" + node.getComponentId() + "' not found");
}
return identifiable;
}
@Override
public Object visitNetworkProperty(NetworkPropertyNode node, Void arg) {
Object parentValue = node.getParent().accept(this, arg);
if (parentValue == null) {
throw new PowsyblException("Cannot call a property '" + node.getPropertyName() + "' on a null object");
}
return GroovyUtil.callProperty(parentValue, node.getPropertyName());
}
@Override
public Object visitNetworkMethod(NetworkMethodNode node, Void arg) {
Object parentValue = node.getParent().accept(this, arg);
if (parentValue == null) {
throw new PowsyblException("Cannot call a method '" + node.getMethodName() + "' on a null object");
}
return GroovyUtil.callMethod(parentValue, node.getMethodName(), node.getArgs());
}
@Override
public Object visitActionTaken(ActionTakenNode node, Void arg) {
return context.isActionTaken(node.getActionId());
}
@Override
public Object visitContingencyOccurred(ContingencyOccurredNode node, Void arg) {
return context.getContingency() != null &&
(node.getContingencyId() == null || context.getContingency().getId().equals(node.getContingencyId()));
}
/**
* Utility class to compare loading on one side of a branch to loading of one side of another branch
*/
private static final class BranchAndSide implements Comparable<BranchAndSide> {
private final Branch branch;
private final TwoSides side;
private BranchAndSide(Branch branch, TwoSides side) {
this.branch = Objects.requireNonNull(branch);
this.side = Objects.requireNonNull(side);
}
private Branch getBranch() {
return branch;
}
private TwoSides getSide() {
return side;
}
/**
* TODO: to move to IIDM
*/
private static double getPermanentLimit(Branch<?> branch, TwoSides side) {
Objects.requireNonNull(branch);
Objects.requireNonNull(side);
double permanentLimit1 = branch.getCurrentLimits1().map(LoadingLimits::getPermanentLimit).orElse(Double.NaN);
double permanentLimit2 = branch.getCurrentLimits2().map(LoadingLimits::getPermanentLimit).orElse(Double.NaN);
return side == TwoSides.ONE ? permanentLimit1 : permanentLimit2;
}
private static int compare(double value1, double value2) {
if (Double.isNaN(value1) && Double.isNaN(value2)) {
return 0;
} else if (Double.isNaN(value1) && !Double.isNaN(value2)) {
return -1;
} else if (!Double.isNaN(value1) && Double.isNaN(value2)) {
return 1;
} else {
return Double.compare(value1, value2);
}
}
private static int compare(BranchAndSide branchAndSide1, BranchAndSide branchAndSide2) {
Overload overload1 = branchAndSide1.getBranch().checkTemporaryLimits(branchAndSide1.getSide(), LimitType.CURRENT);
Overload overload2 = branchAndSide2.getBranch().checkTemporaryLimits(branchAndSide2.getSide(), LimitType.CURRENT);
double i1 = branchAndSide1.getBranch().getTerminal(branchAndSide1.getSide()).getI();
double i2 = branchAndSide2.getBranch().getTerminal(branchAndSide2.getSide()).getI();
double permanentLimit1 = getPermanentLimit(branchAndSide1.getBranch(), branchAndSide1.getSide());
double permanentLimit2 = getPermanentLimit(branchAndSide2.getBranch(), branchAndSide2.getSide());
int c;
if (overload1 == null) {
if (overload2 == null) {
// no overload, compare load based on permanent limit
c = compare(i1 / permanentLimit1, i2 / permanentLimit2);
} else {
c = -1;
}
} else {
if (overload2 == null) {
c = 1;
} else {
// first compare acceptable duration
c = -Integer.compare(overload1.getTemporaryLimit().getAcceptableDuration(),
overload2.getTemporaryLimit().getAcceptableDuration());
if (c == 0) {
// and then overload based on temporary limit
c = compare(i1 / overload1.getTemporaryLimit().getValue(),
i2 / overload2.getTemporaryLimit().getValue());
}
}
}
return c;
}
@Override
public int hashCode() {
return Objects.hash(branch, side);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BranchAndSide branchAndSide) {
return branchAndSide.compareTo(this) == 0;
}
return false;
}
@Override
public int compareTo(BranchAndSide o) {
return compare(this, o);
}
@Override
public String toString() {
return branch.getId() + "/" + side;
}
}
private List<String> sortBranches(List<String> branchIds) {
return branchIds.stream()
.map(this::getBranch)
.map(branch -> {
BranchAndSide branchAndSide1 = new BranchAndSide(branch, TwoSides.ONE);
BranchAndSide branchAndSide2 = new BranchAndSide(branch, TwoSides.TWO);
int c = branchAndSide1.compareTo(branchAndSide2);
return c >= 0 ? branchAndSide1 : branchAndSide2;
})
.sorted()
.map(branchAndSide -> branchAndSide.getBranch().getId())
.collect(Collectors.toList());
}
@Override
public Object visitLoadingRank(LoadingRankNode node, Void arg) {
List<String> branchIds = new ArrayList<>();
node.getBranchIds().forEach(e -> branchIds.add((String) e.accept(this, arg)));
String branchIdToRank = (String) node.getBranchIdToRankNode().accept(this, arg);
if (!branchIds.contains(branchIdToRank)) {
throw new PowsyblException("Branch to rank has to be in the list");
}
List<String> sortedBranchIds = sortBranches(branchIds);
int i = sortedBranchIds.indexOf(branchIdToRank);
if (i == -1) {
throw new IllegalStateException();
}
return sortedBranchIds.size() - i; // just a convention
}
@Override
public Object visitMostLoaded(MostLoadedNode node, Void arg) {
List<String> sortedBranchIds = sortBranches(node.getBranchIds());
return sortedBranchIds.get(sortedBranchIds.size() - 1);
}
@Override
public Object visitIsOverloaded(IsOverloadedNode isOverloadedNode, Void arg) {
double limitReduction = isOverloadedNode.getLimitReduction();
// Iterate over all the branch Ids to be sure that all the branches exist in the network
return isOverloadedNode.getBranchIds().stream()
.map(id -> getBranch(id).isOverloaded(limitReduction))
.reduce(false, (a, b) -> a || b);
}
@Override
public Object visitAllOverloaded(AllOverloadedNode allOverloadedNode, Void arg) {
double limitReduction = allOverloadedNode.getLimitReduction();
// Iterate over all the branch Ids to be sure that all the branches exist in the network
return allOverloadedNode.getBranchIds().stream()
.map(id -> getBranch(id).isOverloaded(limitReduction))
.reduce(true, (a, b) -> a && b);
}
private Branch getBranch(String branchId) {
Branch branch = context.getNetwork().getBranch(branchId);
if (branch == null) {
throw new PowsyblException("Branch '" + branchId + "' not found");
}
return branch;
}
}