TopologyTester.java
/**
* Copyright (c) 2017-2018, 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.cgmes.conversion.test;
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.cgmes.model.CgmesModel;
import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Network;
import com.powsybl.triplestore.api.PropertyBags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
*/
class TopologyTester {
TopologyTester(CgmesModel cgmes, Network n) {
this.cgmes = cgmes;
this.network = n;
}
// TODO a topologicalNode contains connectivity nodes linked by non-retained
// closed switches
// For current validation we are not taking into account the retained flag
// When we create the voltage-level connectivity at node-breaker level we will
// be able to
// set the retained flag for each switch and this problem will be avoided
// For connectivity created at bus-breaker level we can not set the "retained"
// flag
boolean test(boolean strict) {
// Only makes sense if the network has been obtained
// from CGMES node-breaker detailed data
if (!network.getProperty(Conversion.NETWORK_PS_CGMES_MODEL_DETAIL)
.equals(Conversion.NETWORK_PS_CGMES_MODEL_DETAIL_NODE_BREAKER)) {
return true;
}
Map<String, Set<String>> tpcns = new HashMap<>();
Map<String, String> cn2tp = new HashMap<>();
LOG.info("testTopology (strict : {})", strict);
LOG.info(" preparing mapping between CGMES connectivityNodes and topologicalNodes ...");
PropertyBags cgmescn = cgmes.connectivityNodes();
Set<String> boundarycn = cgmes.boundaryNodes().stream()
.map(bnp -> bnp.getId("ConnectivityNode"))
.collect(Collectors.toSet());
cgmescn.forEach(cnp -> {
String cn = cnp.getId("ConnectivityNode");
// Ignore connectivity nodes belonging to boundaries
if (!boundarycn.contains(cn)) {
String tp = cnp.getId("TopologicalNode");
tpcns.computeIfAbsent(tp, x -> new HashSet<>()).add(cn);
cn2tp.put(cn, tp);
}
});
LOG.info(" preparing mapping between IIDM busbarSections and busBreaker buses ...");
Map<String, Set<String>> mbbbss = new HashMap<>();
Map<String, String> bbs2mb = new HashMap<>();
network.getVoltageLevels().forEach(vl -> {
vl.getNodeBreakerView().getBusbarSections().forEach(bbs -> {
Bus b = bbs.getTerminal().getBusBreakerView().getBus();
if (b != null) {
mbbbss.computeIfAbsent(b.getId(), x -> new HashSet<>()).add(bbs.getId());
bbs2mb.put(bbs.getId(), b.getId());
}
});
});
// Review all topological nodes present in CGMES model
int numNodes = 0;
int numFails = 0;
int numWarnings = 0;
Set<String> badTPs = new HashSet<>();
Iterator<Map.Entry<String, Set<String>>> k = tpcns.entrySet().iterator();
LOG.info(" analyzing all CGMES topologicalNodes ...");
while (k.hasNext()) {
Map.Entry<String, Set<String>> e = k.next();
String tp = e.getKey();
Set<String> cns = e.getValue();
String cn = cns.iterator().next();
// For the topology test to be valid,
// BusbarSections should have been created in IIDM
// for each connectivity node
String bbs = cn;
String mb = bbs2mb.get(bbs);
Set<String> bbss = mbbbss.get(mb);
boolean hasBbss = bbss != null;
if (LOG.isInfoEnabled()) {
LOG.info(" analyzing topologicalNode {}", tp);
LOG.info(" connectivityNodes in same CGMES topologicalNode {} {} {}",
cns.size(),
Arrays.toString(cns.toArray()),
tp);
if (hasBbss) {
LOG.info(" busbarSections in same IIDM mergedBus {} {} {}",
bbss.size(),
Arrays.toString(bbss.toArray()),
mb);
}
}
numNodes++;
if (!cns.equals(bbss)) {
if (strict) {
reportTopologyError(cn, cns, bbss);
numFails++;
} else {
// All connectivity nodes in the same topological node
// that are not in the set of bus bar sections must be invalid
// (they should not have a merged bus)
Set<String> cns1 = new HashSet<>(cns);
if (hasBbss) {
cns1.removeAll(bbss);
}
cns1 = cns1.stream()
.filter(cn1 -> bbs2mb.get(cn1) != null)
.collect(Collectors.toSet());
if (!cns1.isEmpty()) {
badTPs.add(tp);
if (hasBbss) {
reportTopologyError(cn, cns, bbss, cns1);
numFails++;
} else {
reportTopologyWarning(cn, cns, cns1);
numWarnings++;
}
}
}
}
}
LOG.info(" completed analyzing all {} topologicalNodes", numNodes);
if (!badTPs.isEmpty()) {
LOG.warn(" bad topologicalNodes : {} / {}", badTPs.size(), numNodes);
badTPs.forEach(tp -> LOG.warn(" {}", tp));
}
if (numFails > 0 || numWarnings > 0) {
String reason = String.format(
"testTopology. Failed %d, warnings %d of %d topologicalNodes analyzed",
numFails,
numWarnings,
numNodes);
LOG.error(reason);
return false;
}
return true;
}
private void reportTopologyError(String cn, Set<String> cns, Set<String> cbs) {
LOG.error(" Fail, connectivityNode {}", cn);
if (LOG.isDebugEnabled()) {
LOG.debug(" cns : {}", cns);
LOG.debug(" cbs : {}", cbs);
}
}
private void reportTopologyWarning(String cn, Set<String> cns, Set<String> cbs1) {
LOG.warn(" TopologicalNode contains invalid connectivityNodes, connectivityNode {}", cn);
if (LOG.isDebugEnabled()) {
LOG.debug(" cns : {}", cns);
LOG.debug(" cbs1 : {}", cbs1);
}
}
private void reportTopologyError(String cn, Set<String> cns, Set<String> cbs, Set<String> cbs1) {
LOG.error(" Fail after removing invalid connectivityNodes, connectivityNode {}", cn);
if (LOG.isDebugEnabled()) {
LOG.error(" cns : {}", cns);
LOG.error(" cbs : {}", cbs);
LOG.error(" cbs1 : {}", cbs1);
}
}
private final CgmesModel cgmes;
private final Network network;
private static final Logger LOG = LoggerFactory.getLogger(TopologyTester.class);
}