DCIsland.java
/**
* Copyright (c) 2025, 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.elements.dc;
import com.powsybl.cgmes.conversion.CgmesReports;
import com.powsybl.cgmes.conversion.Context;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.powsybl.cgmes.conversion.elements.dc.DCConfiguration.*;
/**
* @author Romain Courtier {@literal <romain.courtier at rte-france.com>}
*/
public record DCIsland(Set<DCIslandEnd> dcIslandEnds) {
public boolean valid(Context context) {
boolean validDcLineSegments = validDcLineSegments(context);
boolean validAcDcConverters = validAcDcConverters(context);
boolean validConfiguration = validConfiguration(context);
return validDcLineSegments && validAcDcConverters && validConfiguration;
}
private boolean validDcLineSegments(Context context) {
// Check that each DCLineSegment is present in exactly 2 DCIslandEnd.
boolean valid = true;
Map<DCEquipment, Long> dcLineSegmentsOccurrences = dcIslandEnds.stream()
.flatMap(end -> end.dcEquipments().stream())
.filter(DCEquipment::isLine)
.collect(Collectors.groupingBy(e -> e, Collectors.counting()));
for (Map.Entry<DCEquipment, Long> dcLineSegmentOccurrences : dcLineSegmentsOccurrences.entrySet()) {
if (dcLineSegmentOccurrences.getValue() != 2) {
CgmesReports.dcLineSegmentNotInTwoDCIslandEndReport(context.getReportNode(), dcLineSegmentOccurrences.getKey().id());
valid = false;
}
}
return valid;
}
private boolean validAcDcConverters(Context context) {
DCConfiguration dcConfiguration = getDcConfiguration();
if (dcConfiguration == POINT_TO_POINT) {
List<DCIslandEnd> ends = dcIslandEnds.stream().toList();
long numberOfCsConverters1 = ends.get(0).dcEquipments().stream().filter(DCEquipment::isCsConverter).count();
long numberOfCsConverters2 = ends.get(1).dcEquipments().stream().filter(DCEquipment::isCsConverter).count();
long numberOfVsConverters1 = ends.get(0).dcEquipments().stream().filter(DCEquipment::isVsConverter).count();
long numberOfVsConverters2 = ends.get(1).dcEquipments().stream().filter(DCEquipment::isVsConverter).count();
if (numberOfCsConverters1 != numberOfCsConverters2 || numberOfVsConverters1 != numberOfVsConverters2) {
CgmesReports.inconsistentNumberOfConvertersReport(context.getReportNode(), getConverterIds());
return false;
}
}
return true;
}
private boolean validConfiguration(Context context) {
DCConfiguration dcConfiguration = getDcConfiguration();
if (dcConfiguration != POINT_TO_POINT) {
CgmesReports.unsupportedDcConfigurationReport(context.getReportNode(), getConverterIds(), dcConfiguration.name());
return false;
} else {
List<DCIslandEnd> ends = dcIslandEnds.stream().toList();
int numberOfLines = ends.get(0).getDcLineSegments().size();
int numberOfConverterPairs = ends.get(0).getAcDcConverters().size();
if (numberOfLines > numberOfConverterPairs + 1 || numberOfConverterPairs > 2 * numberOfLines) {
// There is more line that the number of converters + a metallic return line
// or there is more converter pairs than 2 bridges per dc line.
CgmesReports.unexpectedPointToPointDcConfigurationReport(
context.getReportNode(), getConverterIds(), numberOfLines, numberOfConverterPairs);
return false;
}
}
return true;
}
private DCConfiguration getDcConfiguration() {
if (dcIslandEnds.size() > 2) {
return MULTI_TERMINAL;
} else if (dcIslandEnds.size() == 2) {
return POINT_TO_POINT;
} else {
return BACK_TO_BACK;
}
}
private String getConverterIds() {
return String.join(", ",
dcIslandEnds.stream()
.flatMap(end -> end.dcEquipments().stream())
.filter(DCEquipment::isConverter)
.map(DCEquipment::id)
.sorted()
.toList());
}
public boolean isGrounded(DCEquipment dcEquipment) {
return dcIslandEnds.stream()
.flatMap(e -> e.dcEquipments().stream())
.filter(DCEquipment::isGround)
.anyMatch(g -> g.isAdjacentTo(dcEquipment));
}
}