DCIslandEnd.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 java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author Romain Courtier {@literal <romain.courtier at rte-france.com>}
 */
public record DCIslandEnd(Set<DCEquipment> dcEquipments) {

    boolean isAdjacentTo(DCIslandEnd otherDcIslandEnd) {
        // Two DCIslandEnd are adjacent if they share a DCLineSegment.
        Set<DCEquipment> commonDcLineSegments = dcEquipments.stream()
                .filter(DCEquipment::isLine)
                .collect(Collectors.toSet());
        commonDcLineSegments.retainAll(otherDcIslandEnd.dcEquipments);
        return !commonDcLineSegments.isEmpty();
    }

    public List<DCEquipment> getAcDcConverters() {
        return dcEquipments.stream()
                .filter(DCEquipment::isConverter)
                .sorted(Comparator.comparing(DCEquipment::id))
                .toList();
    }

    public List<DCEquipment> getDcLineSegments() {
        // Return DCLineSegment sorted by total distance to converters (furthest to nearest), then by id.
        // This allows DMR in case of bipole configuration to be the last element of the list as
        // since it's central, it's the closest to all converters.
        return dcEquipments.stream()
                .filter(DCEquipment::isLine)
                .sorted(Comparator.comparing(this::getTotalDistanceToConverters).reversed()
                        .thenComparing(DCEquipment::id))
                .toList();
    }

    public DCEquipment getNearestConverter(DCEquipment dcEquipment, Predicate<DCEquipment> isEligibleConverter) {
        return getEquipmentDistances(dcEquipment)
                .entrySet().stream()
                .filter(e -> !e.getKey().equals(dcEquipment)
                        && isEligibleConverter.test(e.getKey()))
                .min(Map.Entry.<DCEquipment, Integer>comparingByValue()
                        .thenComparing(e -> e.getKey().id()))
                .map(Map.Entry::getKey)
                .orElseThrow();
    }

    private Integer getTotalDistanceToConverters(DCEquipment dcEquipment) {
        return getEquipmentDistances(dcEquipment).entrySet().stream()
                .filter(e -> e.getKey().isConverter())
                .map(Map.Entry::getValue)
                .mapToInt(Integer::intValue)
                .sum();
    }

    private Map<DCEquipment, Integer> getEquipmentDistances(DCEquipment dcEquipment) {
        Map<DCEquipment, Integer> equipmentDistances = new HashMap<>();
        computeEquipmentDistances(dcEquipment, 0, equipmentDistances);
        return equipmentDistances;
    }

    private void computeEquipmentDistances(DCEquipment dcEquipment, int distance, Map<DCEquipment, Integer> equipmentDistances) {
        if (equipmentDistances.computeIfAbsent(dcEquipment, e -> Integer.MAX_VALUE) > distance) {
            equipmentDistances.put(dcEquipment, distance);
            Set<DCEquipment> nextDcEquipments = dcEquipments.stream()
                    .filter(e -> e != dcEquipment && e.isAdjacentTo(dcEquipment))
                    .collect(Collectors.toSet());
            for (DCEquipment nextDcEquipment : nextDcEquipments) {
                computeEquipmentDistances(nextDcEquipment, distance + 1, equipmentDistances);
            }
        }
    }

}