All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.powsybl.openloadflow.ac.outerloop.AcIncrementalPhaseControlOuterLoop Maven / Gradle / Ivy

/**
 * 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.openloadflow.ac.outerloop;

import com.powsybl.commons.report.ReportNode;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.math.matrix.DenseMatrix;
import com.powsybl.openloadflow.ac.AcLoadFlowContext;
import com.powsybl.openloadflow.ac.AcLoadFlowParameters;
import com.powsybl.openloadflow.ac.AcOuterLoopContext;
import com.powsybl.openloadflow.ac.equations.AcEquationType;
import com.powsybl.openloadflow.ac.equations.AcVariableType;
import com.powsybl.openloadflow.equations.EquationSystem;
import com.powsybl.openloadflow.equations.EquationTerm;
import com.powsybl.openloadflow.equations.JacobianMatrix;
import com.powsybl.openloadflow.lf.outerloop.AbstractIncrementalPhaseControlOuterLoop;
import com.powsybl.openloadflow.lf.outerloop.IncrementalContextData;
import com.powsybl.openloadflow.lf.outerloop.OuterLoopResult;
import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.util.PerUnit;
import com.powsybl.openloadflow.util.Reports;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.mutable.MutableInt;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Geoffroy Jamgotchian {@literal }
 */
public class AcIncrementalPhaseControlOuterLoop
        extends AbstractIncrementalPhaseControlOuterLoop
        implements AcOuterLoop {

    public static final String NAME = "IncrementalPhaseControl";

    public AcIncrementalPhaseControlOuterLoop() {
        super(LoggerFactory.getLogger(AcIncrementalPhaseControlOuterLoop.class));
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public void initialize(AcOuterLoopContext context) {
        var contextData = new IncrementalContextData();
        context.setData(contextData);

        List controllerBranches = getControllerBranches(context.getNetwork());
        for (LfBranch controllerBranch : controllerBranches) {
            contextData.getControllersContexts().put(controllerBranch.getId(), new IncrementalContextData.ControllerContext(MAX_DIRECTION_CHANGE));
        }

        fixPhaseShifterNecessaryForConnectivity(context.getNetwork(), controllerBranches);
    }

    public static class AcSensitivityContext extends AbstractSensitivityContext {
        public AcSensitivityContext(LfNetwork network, List controllerBranches, EquationSystem equationSystem, JacobianMatrix jacobianMatrix) {
            super(network, controllerBranches, equationSystem, jacobianMatrix);
        }

        public DenseMatrix calculateSensitivityValues(List controllerBranches, int[] controllerBranchIndex,
                                                      EquationSystem equationSystem,
                                                      JacobianMatrix jacobianMatrix) {
            DenseMatrix rhs = new DenseMatrix(equationSystem.getIndex().getSortedEquationsToSolve().size(), controllerBranches.size());
            for (LfBranch controllerBranch : controllerBranches) {
                equationSystem.getEquation(controllerBranch.getNum(), AcEquationType.BRANCH_TARGET_ALPHA1)
                        .ifPresent(equation -> rhs.set(equation.getColumn(), controllerBranchIndex[controllerBranch.getNum()], Math.toRadians(1d)));
            }
            jacobianMatrix.solveTransposed(rhs);
            return rhs;
        }

        @SuppressWarnings("unchecked")
        private EquationTerm getI1(LfBranch controlledBranch) {
            return (EquationTerm) controlledBranch.getI1();
        }

        @SuppressWarnings("unchecked")
        private EquationTerm getI2(LfBranch controlledBranch) {
            return (EquationTerm) controlledBranch.getI2();
        }

        public double calculateSensitivityFromA2I(LfBranch controllerBranch, LfBranch controlledBranch, TwoSides controlledSide) {
            var i = controlledSide == TwoSides.ONE ? getI1(controlledBranch) : getI2(controlledBranch);
            return calculateSensitivityFromA2S(controllerBranch, i);
        }
    }

    private int checkCurrentLimiterPhaseControls(AcSensitivityContext sensitivityContext, IncrementalContextData contextData,
                                                     List currentLimiterPhaseControls) {
        MutableInt numOfCurrentLimiterPstsThatChangedTap = new MutableInt(0);

        for (TransformerPhaseControl phaseControl : currentLimiterPhaseControls) {
            LfBranch controllerBranch = phaseControl.getControllerBranch();
            LfBranch controlledBranch = phaseControl.getControlledBranch();
            double i = computeI(phaseControl);
            if (i > phaseControl.getTargetValue()) {
                var controllerContext = contextData.getControllersContexts().get(controllerBranch.getId());
                double di = phaseControl.getTargetValue() - i;
                double a2i = sensitivityContext.calculateSensitivityFromA2I(controllerBranch, controlledBranch, phaseControl.getControlledSide());
                if (Math.abs(a2i) > SENSI_EPS) {
                    double da = Math.toRadians(di / a2i);
                    double ib = computeIb(phaseControl);
                    logger.trace("Controlled branch '{}' current is {} A and above target value {} A, a phase shift of {}° is required",
                            controlledBranch.getId(), i * ib, phaseControl.getTargetValue() * ib, Math.toDegrees(da));
                    PiModel piModel = controllerBranch.getPiModel();

                    int oldTapPosition = piModel.getTapPosition();
                    double oldA1 = piModel.getA1();
                    Range tapPositionRange = piModel.getTapPositionRange();
                    piModel.updateTapPositionToExceedNewA1(da, MAX_TAP_SHIFT, controllerContext.getAllowedDirection()).ifPresent(direction -> {
                        controllerContext.updateAllowedDirection(direction);
                        numOfCurrentLimiterPstsThatChangedTap.add(1);
                    });

                    if (piModel.getTapPosition() != oldTapPosition) {
                        logger.debug("Controller branch '{}' changed tap from {} to {} to limit current (full range: {})", controllerBranch.getId(),
                                oldTapPosition, piModel.getTapPosition(), tapPositionRange);

                        double discreteDa = piModel.getA1() - oldA1;
                        checkImpactOnOtherPhaseShifters(sensitivityContext, phaseControl, currentLimiterPhaseControls, discreteDa);
                    }
                }
            }
        }
        return numOfCurrentLimiterPstsThatChangedTap.getValue();
    }

    private void checkImpactOnOtherPhaseShifters(AcSensitivityContext sensitivityContext, TransformerPhaseControl phaseControl,
                                                 List currentLimiterPhaseControls, double da) {
        LfBranch controllerBranch = phaseControl.getControllerBranch();
        for (TransformerPhaseControl otherPhaseControl : currentLimiterPhaseControls) {
            if (otherPhaseControl != phaseControl) {
                LfBranch otherControlledBranch = otherPhaseControl.getControlledBranch();
                double i = computeI(otherPhaseControl);
                if (i > otherPhaseControl.getTargetValue()) {
                    // get cross sensitivity of the phase shifter controller branch on the other phase shifter controlled branch
                    double crossA2i = sensitivityContext.calculateSensitivityFromA2I(controllerBranch, otherControlledBranch,
                            otherPhaseControl.getControlledSide());
                    double ib = computeIb(otherPhaseControl);
                    double di = Math.toDegrees(da) * crossA2i;
                    if (di > PHASE_SHIFT_CROSS_IMPACT_MARGIN * (i - otherPhaseControl.getTargetValue())) {
                        logger.warn("Controller branch '{}' tap change significantly impact (≈ {} A) another phase shifter current also above its limit '{}', simulation might not be reliable",
                                controllerBranch.getId(), di * ib, otherPhaseControl.getControlledBranch().getId());
                    }
                }
            }
        }
    }

    private static double computeIb(TransformerPhaseControl phaseControl) {
        LfBus bus = phaseControl.getControlledSide() == TwoSides.ONE
                ? phaseControl.getControlledBranch().getBus1() : phaseControl.getControlledBranch().getBus2();
        return PerUnit.ib(bus.getNominalV());
    }

    private static double computeI(TransformerPhaseControl phaseControl) {
        var i = phaseControl.getControlledSide() == TwoSides.ONE
                ? phaseControl.getControlledBranch().getI1() : phaseControl.getControlledBranch().getI2();
        return i.eval();
    }

    @Override
    public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) {

        var contextData = (IncrementalContextData) context.getData();

        LfNetwork network = context.getNetwork();

        List controllerBranches = getControllerBranches(network);

        // find list of phase controls that are in current limiter and active power control
        List activePowerControlPhaseControls = new ArrayList<>();
        List currentLimiterPhaseControls = new ArrayList<>();
        for (LfBranch controllerBranch : controllerBranches) {
            controllerBranch.getPhaseControl().ifPresent(phaseControl -> {
                switch (phaseControl.getMode()) {
                    case CONTROLLER:
                        activePowerControlPhaseControls.add(phaseControl);
                        break;
                    case LIMITER:
                        currentLimiterPhaseControls.add(phaseControl);
                        break;
                    default:
                        break;
                }
            });
        }

        OuterLoopStatus status = OuterLoopStatus.STABLE;

        if (currentLimiterPhaseControls.isEmpty() && activePowerControlPhaseControls.isEmpty()) {
            return new OuterLoopResult(this, status);
        }

        var sensitivityContext = new AcSensitivityContext(network,
                                                        controllerBranches,
                                                        context.getLoadFlowContext().getEquationSystem(),
                                                        context.getLoadFlowContext().getJacobianMatrix());

        final int numOfCurrentLimiterPstsThatChangedTap;
        final int numOfActivePowerControlPstsThatChangedTap;
        if (!currentLimiterPhaseControls.isEmpty()) {
            numOfCurrentLimiterPstsThatChangedTap = checkCurrentLimiterPhaseControls(sensitivityContext,
                                                                                         contextData,
                                                                                         currentLimiterPhaseControls);
        } else {
            numOfCurrentLimiterPstsThatChangedTap = 0;
        }

        if (!activePowerControlPhaseControls.isEmpty()) {
            numOfActivePowerControlPstsThatChangedTap = checkActivePowerControlPhaseControls(sensitivityContext,
                                                                                                 contextData,
                                                                                                 activePowerControlPhaseControls);
        } else {
            numOfActivePowerControlPstsThatChangedTap = 0;
        }

        if (numOfCurrentLimiterPstsThatChangedTap + numOfActivePowerControlPstsThatChangedTap != 0) {
            status = OuterLoopStatus.UNSTABLE;
            ReportNode iterationReportNode = Reports.createOuterLoopIterationReporter(reportNode, context.getOuterLoopTotalIterations() + 1);
            if (numOfCurrentLimiterPstsThatChangedTap != 0) {
                Reports.reportCurrentLimiterPstsChangedTaps(iterationReportNode, numOfCurrentLimiterPstsThatChangedTap);
            }
            if (numOfActivePowerControlPstsThatChangedTap != 0) {
                Reports.reportActivePowerControlPstsChangedTaps(iterationReportNode, numOfActivePowerControlPstsThatChangedTap);
            }
        }

        return new OuterLoopResult(this, status);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy