AbstractContextBuilder.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.dynawo;

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.dynamicsimulation.DynamicSimulationParameters;
import com.powsybl.dynamicsimulation.OutputVariable;
import com.powsybl.dynawo.builders.VersionInterval;
import com.powsybl.dynawo.commons.DynawoConstants;
import com.powsybl.dynawo.commons.DynawoVersion;
import com.powsybl.dynawo.models.BlackBoxModel;
import com.powsybl.dynawo.models.Model;
import com.powsybl.dynawo.models.buses.AbstractBus;
import com.powsybl.dynawo.models.frequencysynchronizers.*;
import com.powsybl.dynawo.parameters.ParametersSet;
import com.powsybl.iidm.network.Network;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Laurent Issertial {@literal <laurent.issertial at rte-france.com>}
 */
public abstract class AbstractContextBuilder<T extends AbstractContextBuilder<T>> {

    protected final Network network;
    protected DynamicSimulationParameters simulationParameters = null;
    protected DynawoSimulationParameters dynawoParameters = null;
    protected String workingVariantId;
    protected List<BlackBoxModel> dynamicModels;
    protected BlackBoxModelSupplier blackBoxModelSupplier;
    protected List<BlackBoxModel> eventModels = Collections.emptyList();
    protected List<ParametersSet> dynamicModelsParameters = new ArrayList<>();
    protected SimulationModels simulationModels;
    protected Map<OutputVariable.OutputType, List<OutputVariable>> outputVariables = Collections.emptyMap();
    protected FinalStepConfig finalStepConfig = null;
    protected List<BlackBoxModel> finalStepDynamicModels = Collections.emptyList();
    protected FinalStepModels finalStepModels = null;
    protected SimulationTime simulationTime;
    protected SimulationTime finalStepTime = null;
    protected DynawoVersion dynawoVersion = DynawoConstants.VERSION_MIN;
    protected ReportNode reportNode = ReportNode.NO_OP;

    protected AbstractContextBuilder(Network network, List<BlackBoxModel> dynamicModels) {
        this.network = network;
        this.dynamicModels = dynamicModels;
    }

    public T workingVariantId(String workingVariantId) {
        this.workingVariantId = Objects.requireNonNull(workingVariantId);
        return self();
    }

    public T dynawoParameters(DynawoSimulationParameters dynawoParameters) {
        this.dynawoParameters = Objects.requireNonNull(dynawoParameters);
        return self();
    }

    public T currentVersion(DynawoVersion currentVersion) {
        this.dynawoVersion = currentVersion;
        return self();
    }

    public T reportNode(ReportNode reportNode) {
        this.reportNode = DynawoSimulationReports.createDynawoSimulationContextReportNode(reportNode);
        return self();
    }

    protected final void setup() {
        setupData();
        setupMacroConnections();
    }

    protected void setupData() {
        if (workingVariantId == null) {
            workingVariantId = network.getVariantManager().getWorkingVariantId();
        }
        if (dynawoParameters == null) {
            dynawoParameters = DynawoSimulationParameters.load();
        }
        setupSimulationTime();
        setupDynamicModels();
    }

    protected void setupMacroConnections() {
        simulationModels = SimulationModels.createFrom(blackBoxModelSupplier, dynamicModels, eventModels, dynamicModelsParameters::add,
                dynawoParameters.getNetworkParameters(), reportNode);
        if (!finalStepDynamicModels.isEmpty()) {
            finalStepModels = FinalStepModels.createFrom(blackBoxModelSupplier, simulationModels, finalStepDynamicModels,
                    dynamicModelsParameters::add, reportNode);
        }
    }

    protected void setupSimulationTime() {
        if (simulationParameters == null) {
            simulationParameters = DynamicSimulationParameters.load();
        }
        this.simulationTime = new SimulationTime(simulationParameters.getStartTime(), simulationParameters.getStopTime());
        if (finalStepConfig != null) {
            this.finalStepTime = new SimulationTime(simulationTime.stopTime(), finalStepConfig.stopTime());
        }
    }

    private void setupDynamicModels() {
        Stream<BlackBoxModel> uniqueIdsDynamicModels = Objects.requireNonNull(dynamicModels).stream()
                .filter(distinctByDynamicId(reportNode).and(supportedVersion(dynawoVersion, reportNode)));
        if (dynawoParameters.isUseModelSimplifiers()) {
            uniqueIdsDynamicModels = simplifyModels(uniqueIdsDynamicModels);
        }

        if (finalStepConfig != null) {
            Map<Boolean, List<BlackBoxModel>> splitModels = uniqueIdsDynamicModels
                    .collect(Collectors.partitioningBy(finalStepConfig.modelsPredicate(), Collectors.toCollection(ArrayList::new)));
            dynamicModels = splitModels.get(false);
            finalStepDynamicModels = splitModels.get(true);
        } else {
            dynamicModels = uniqueIdsDynamicModels.collect(Collectors.toCollection(ArrayList::new));
        }
        setupFrequencySynchronizer();
        blackBoxModelSupplier = BlackBoxModelSupplier.createFrom(dynamicModels);
    }

    private Stream<BlackBoxModel> simplifyModels(Stream<BlackBoxModel> inputBbm) {
        Stream<BlackBoxModel> outputBbm = inputBbm;
        for (ModelsRemovalSimplifier modelsSimplifier : ServiceLoader.load(ModelsRemovalSimplifier.class)) {
            outputBbm = outputBbm.filter(modelsSimplifier.getModelRemovalPredicate(reportNode));
        }
        for (ModelsSubstitutionSimplifier modelsSimplifier : ServiceLoader.load(ModelsSubstitutionSimplifier.class)) {
            outputBbm = outputBbm.map(modelsSimplifier.getModelSubstitutionFunction(network, dynawoParameters, reportNode))
                    .filter(Objects::nonNull);
        }
        return outputBbm;
    }

    private void setupFrequencySynchronizer() {
        List<SignalNModel> signalNModels = filterDynamicModels(SignalNModel.class);
        List<FrequencySynchronizedModel> frequencySynchronizedModels = filterDynamicModels(FrequencySynchronizedModel.class);
        boolean hasSpecificBuses = dynamicModels.stream().anyMatch(AbstractBus.class::isInstance);
        boolean hasFrequencySynchronizedModels = !frequencySynchronizedModels.isEmpty();
        boolean hasSignalNModels = !signalNModels.isEmpty();
        String defaultParFile = DynawoSimulationConstants.getSimulationParFile(network);
        if (hasFrequencySynchronizedModels && hasSignalNModels) {
            throw new PowsyblException("Signal N and frequency synchronized generators cannot be used with one another");
        }
        if (hasSignalNModels) {
            dynamicModels.add(new SignalN(signalNModels, defaultParFile));
        } else if (hasFrequencySynchronizedModels) {
            dynamicModels.add(hasSpecificBuses
                    ? new SetPoint(frequencySynchronizedModels, defaultParFile)
                    : new OmegaRef(frequencySynchronizedModels, defaultParFile, dynawoParameters));
        }
    }

    private <R extends Model> List<R> filterDynamicModels(Class<R> modelClass) {
        return dynamicModels.stream()
                .filter(modelClass::isInstance)
                .map(modelClass::cast)
                .toList();
    }

    protected static Predicate<BlackBoxModel> distinctByDynamicId(ReportNode reportNode) {
        Set<String> seen = new HashSet<>();
        return bbm -> {
            if (!seen.add(bbm.getDynamicModelId())) {
                DynawoSimulationReports.reportDuplicateDynamicId(reportNode, bbm.getDynamicModelId(), bbm.getName());
                return false;
            }
            return true;
        };
    }

    protected static Predicate<BlackBoxModel> supportedVersion(DynawoVersion currentVersion, ReportNode reportNode) {
        return bbm -> {
            VersionInterval versionInterval = bbm.getVersionInterval();
            if (currentVersion.compareTo(versionInterval.min()) < 0) {
                DynawoSimulationReports.reportDynawoVersionTooHigh(reportNode, bbm.getName(), bbm.getDynamicModelId(), versionInterval.min(), currentVersion);
                return false;
            }
            if (versionInterval.max() != null && currentVersion.compareTo(versionInterval.max()) >= 0) {
                DynawoSimulationReports.reportDynawoVersionTooLow(reportNode, bbm.getName(), bbm.getDynamicModelId(), versionInterval.max(), currentVersion, versionInterval.endCause());
                return false;
            }
            return true;
        };
    }

    protected abstract T self();

    public abstract DynawoSimulationContext build();
}