PsseFixes.java
/**
* Copyright (c) 2023, 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.psse.model.pf;
import com.powsybl.psse.model.PsseVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Function;
import static com.powsybl.psse.model.pf.PsseValidation.switchedShuntId;
import static com.powsybl.psse.model.pf.PsseValidation.switchedShuntRegulatingBus;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
* @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
*/
public class PsseFixes {
private static final String WINDING_1 = "Winding1";
private static final Logger LOGGER = LoggerFactory.getLogger(PsseFixes.class);
private final PssePowerFlowModel model;
private final PsseVersion version;
public PsseFixes(PssePowerFlowModel model, PsseVersion version) {
this.model = Objects.requireNonNull(model);
this.version = Objects.requireNonNull(version);
}
public void fix() {
fixDuplicatedIds();
// fix the controlled buses before fixing the winding cod
fixControlledBuses();
fixTransformersWindingCod();
}
private void fixDuplicatedIds() {
fixDuplicatedIds(model.getLoads(), l -> l.getI() + "-" + l.getId(), this::loadFixer);
fixDuplicatedIds(model.getGenerators(), g -> g.getI() + "-" + g.getId(), this::generatorFixer);
fixDuplicatedIds(model.getFixedShunts(), sh -> sh.getI() + "-" + sh.getId(), this::fixedShuntFixer);
// For branches and transformers, we build the complete id using the sorted nodes plus the circuit id
fixDuplicatedIds(model.getNonTransformerBranches(), b -> sortedBuses(b.getI(), b.getJ()) + b.getCkt(), this::nonTransformerBranchFixer);
fixDuplicatedIds(model.getTransformers(), b -> sortedBuses(b.getI(), b.getJ(), b.getK()) + b.getCkt(), this::transformerFixer);
if (version.getMajorNumber() >= 35) {
fixDuplicatedIds(model.getSwitchedShunts(), sh -> sh.getI() + "-" + sh.getId(), this::switchedShuntFixer);
}
}
private void fixTransformersWindingCod() {
model.getTransformers().forEach(psseTransformer -> {
if (psseTransformer.getK() == 0) { // TwoWindingsTransformers
fixTransformerWindingCod(psseTransformer, psseTransformer.getWinding1(), WINDING_1);
} else {
fixTransformerWindingCod(psseTransformer, psseTransformer.getWinding1(), WINDING_1);
fixTransformerWindingCod(psseTransformer, psseTransformer.getWinding2(), "Winding2");
fixTransformerWindingCod(psseTransformer, psseTransformer.getWinding3(), "Winding3");
}
});
}
private void fixControlledBuses() {
Set<Integer> buses = new HashSet<>();
model.getBuses().forEach(psseBus -> buses.add(psseBus.getI()));
fixGeneratorsControlledBus(buses);
fixTransformersControlledBus(buses);
fixSwitchedShuntControlledBus(buses);
}
private String sortedBuses(int... buses) {
Arrays.sort(buses);
return Arrays.toString(buses);
}
private <T> void fixDuplicatedIds(List<T> psseObjects, Function<T, String> idBuilder, IdFixer<T> idFixer) {
Set<String> foundIds = new HashSet<>();
Map<String, List<T>> duplicated = new HashMap<>();
psseObjects.forEach(psseObject -> {
String id = idBuilder.apply(psseObject);
if (foundIds.contains(id)) {
duplicated.computeIfAbsent(id, key -> new ArrayList<>()).add(psseObject);
}
foundIds.add(id);
});
duplicated.forEach((id, duplicatedIdObjects) -> {
final Set<String> usedIds = new HashSet<>();
usedIds.add(id);
duplicatedIdObjects.forEach(psseObject -> {
String fixedId = idFixer.fix(psseObject, usedIds);
usedIds.add(fixedId);
});
});
}
private String buildFixedId2(String id, Set<String> usedIds) {
// The id is only two characters long, and we try to preserve the first character
// We avoid setting a 0 as first character because PSSE may interpret them as not valid
String first = id.isEmpty() ? "1" : id.substring(0, 1);
for (char second = '0'; second <= '9'; second++) {
String candidate = first + second;
if (usedIds.contains(candidate)) {
continue;
}
return candidate;
}
// try with an uppercase letter, if all digits have been used
for (char second = 'A'; second <= 'Z'; second++) {
String candidate = first + second;
if (usedIds.contains(candidate)) {
continue;
}
return candidate;
}
// we run out of candidates, return the same id
return id;
}
private String loadFixer(PsseLoad load, Set<String> usedIds) {
String id = load.getId();
String fixedId = buildFixedId2(id, usedIds);
load.setId(fixedId);
warn("Load", load.getI(), id, fixedId);
return fixedId;
}
private String generatorFixer(PsseGenerator generator, Set<String> usedIds) {
String id = generator.getId();
String fixedId = buildFixedId2(id, usedIds);
generator.setId(fixedId);
warn("Generator", generator.getI(), id, fixedId);
return fixedId;
}
private String fixedShuntFixer(PsseFixedShunt fixedShunt, Set<String> usedIds) {
String id = fixedShunt.getId();
String fixedId = buildFixedId2(id, usedIds);
fixedShunt.setId(fixedId);
warn("FixedShunt", fixedShunt.getI(), id, fixedId);
return fixedId;
}
private String switchedShuntFixer(PsseSwitchedShunt switchedShunt, Set<String> usedIds) {
String id = switchedShunt.getId();
String fixedId = buildFixedId2(id, usedIds);
switchedShunt.setId(fixedId);
warn("SwitchedShunt", switchedShunt.getI(), id, fixedId);
return fixedId;
}
private String nonTransformerBranchFixer(PsseNonTransformerBranch branch, Set<String> usedIds) {
String id = branch.getCkt();
String fixedId = buildFixedId2(id, usedIds);
branch.setCkt(fixedId);
warn(branch, id, fixedId);
return fixedId;
}
private String transformerFixer(PsseTransformer transformer, Set<String> usedIds) {
String id = transformer.getCkt();
String fixedId = buildFixedId2(id, usedIds);
transformer.setCkt(fixedId);
warn(transformer, id, fixedId);
return fixedId;
}
private void fixTransformerWindingCod(PsseTransformer transformer, PsseTransformerWinding winding, String windingTag) {
if (Math.abs(winding.getCod()) == 1 && winding.getCont() == 0) {
warn(transformer, windingTag, winding.getCod(), 0);
winding.setCod(0);
}
}
private void fixGeneratorsControlledBus(Set<Integer> buses) {
model.getGenerators().forEach(psseGenerator -> {
if (psseGenerator.getIreg() != 0 && !buses.contains(psseGenerator.getIreg())) {
warn("Generator", String.format("%d, %s, ...", psseGenerator.getI(), psseGenerator.getId()), psseGenerator.getIreg());
psseGenerator.setIreg(0);
}
});
}
private void fixTransformersControlledBus(Set<Integer> buses) {
model.getTransformers().forEach(psseTransformer -> {
if (psseTransformer.getK() == 0) { // TwoWindingsTransformers
fixTransformerWindingControlledBus(buses, psseTransformer, psseTransformer.getWinding1(), WINDING_1);
} else {
fixTransformerWindingControlledBus(buses, psseTransformer, psseTransformer.getWinding1(), WINDING_1);
fixTransformerWindingControlledBus(buses, psseTransformer, psseTransformer.getWinding2(), "Winding2");
fixTransformerWindingControlledBus(buses, psseTransformer, psseTransformer.getWinding3(), "Winding3");
}
});
}
private void fixTransformerWindingControlledBus(Set<Integer> buses, PsseTransformer psseTransformer, PsseTransformerWinding winding, String windingTag) {
if (winding.getCont() != 0 && !buses.contains(winding.getCont())) {
warn("Transformer", String.format("%d, %d, %d, %s, ... %s ", psseTransformer.getI(), psseTransformer.getJ(), psseTransformer.getK(), psseTransformer.getCkt(), windingTag), winding.getCont());
winding.setCont(0);
}
}
private void fixSwitchedShuntControlledBus(Set<Integer> buses) {
model.getSwitchedShunts().forEach(psseSwitchedShunt -> {
if (switchedShuntRegulatingBus(psseSwitchedShunt, version) != 0 && !buses.contains(switchedShuntRegulatingBus(psseSwitchedShunt, version))) {
warn("SwitchedShunt", String.format("%s, ...", switchedShuntId(psseSwitchedShunt, version)), switchedShuntRegulatingBus(psseSwitchedShunt, version));
psseSwitchedShunt.setSwreg(0);
psseSwitchedShunt.setSwrem(0);
}
});
}
private void warn(String type, int i, String id, String fixedId) {
if (LOGGER.isWarnEnabled()) {
if (id.equals(fixedId)) {
LOGGER.warn("Unable to fix {} Id: I {} ID '{}'", type, i, id);
} else {
LOGGER.warn("{} Id fixed: I {} ID '{}'. Fixed ID '{}'", type, i, id, fixedId);
}
}
}
private void warn(PsseNonTransformerBranch branch, String id, String fixedId) {
if (LOGGER.isWarnEnabled()) {
if (id.equals(fixedId)) {
LOGGER.warn("Unable to fix NonTransformerBranch Id: I {} J {} CKT '{}'", branch.getI(), branch.getJ(), id);
} else {
LOGGER.warn("NonTransformerBranch Id fixed: I {} J {} CKT '{}'. Fixed CKT '{}'", branch.getI(), branch.getJ(), id, fixedId);
}
}
}
private void warn(PsseTransformer transformer, String id, String fixedId) {
if (LOGGER.isWarnEnabled()) {
if (id.equals(fixedId)) {
LOGGER.warn("Unable to fix Transformer Id: I {} J {} K {} CKT '{}'", transformer.getI(), transformer.getJ(), transformer.getK(), id);
} else {
LOGGER.warn("Transformer Id fixed: I {} J {} K {} CKT '{}'. Fixed CKT '{}'", transformer.getI(), transformer.getJ(), transformer.getK(), id, fixedId);
}
}
}
private void warn(PsseTransformer transformer, String windingTag, int cod, int fixedCod) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Transformer {} Cod fixed: I {} J {} K {} CKT {}. Cod '{}' Fixed Cod '{}'. Controlled bus is not defined (cont == 0).", windingTag, transformer.getI(), transformer.getJ(), transformer.getK(), transformer.getCkt(), cod, fixedCod);
}
}
private void warn(String type, String recordId, int controlledBus) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("{} {} controlled bus {} not found; fixed to 0", type, recordId, controlledBus);
}
}
@FunctionalInterface
public interface IdFixer<T> {
String fix(T t, Set<String> usedIds);
}
}