LfGeneratorImpl.java

/**
 * Copyright (c) 2019, 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.network.impl;

import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.Generator;
import com.powsybl.iidm.network.ReactiveLimits;
import com.powsybl.iidm.network.extensions.*;
import com.powsybl.openloadflow.OpenLoadFlowParameters;
import com.powsybl.openloadflow.network.LfAsymGenerator;
import com.powsybl.openloadflow.network.LfNetwork;
import com.powsybl.openloadflow.network.LfNetworkParameters;
import com.powsybl.openloadflow.network.LfNetworkStateUpdateParameters;
import com.powsybl.openloadflow.util.PerUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.Optional;
import java.util.OptionalDouble;

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
public final class LfGeneratorImpl extends AbstractLfGenerator {

    private static final Logger LOGGER = LoggerFactory.getLogger(LfGeneratorImpl.class);

    private final Ref<Generator> generatorRef;

    private final boolean initialParticipating;

    private boolean participating;

    private final double droop;

    private final double participationFactor;

    private Double qPercent;

    private final boolean forceVoltageControl;

    private final double maxTargetP;

    private final double minTargetP;

    private final boolean forceTargetQInReactiveLimits;

    private LfGeneratorImpl(Generator generator, LfNetwork network, LfNetworkParameters parameters, LfNetworkLoadingReport report) {
        super(network, generator.getTargetP() / PerUnit.SB, parameters);
        this.generatorRef = Ref.create(generator, parameters.isCacheEnabled());
        // we force voltage control of generators tagged as condensers or tagged as fictitious if the dedicated mode is activated.
        forceVoltageControl = generator.isCondenser() || generator.isFictitious() && parameters.getFictitiousGeneratorVoltageControlCheckMode() == OpenLoadFlowParameters.FictitiousGeneratorVoltageControlCheckMode.FORCED;
        var apcHelper = ActivePowerControlHelper.create(generator, generator.getMinP(), generator.getMaxP());
        initialParticipating = apcHelper.participating();
        participating = initialParticipating;
        participationFactor = apcHelper.participationFactor();
        droop = apcHelper.droop();
        minTargetP = apcHelper.minTargetP();
        maxTargetP = apcHelper.maxTargetP();

        forceTargetQInReactiveLimits = parameters.isForceTargetQInReactiveLimits() && parameters.isReactiveLimits();

        setReferencePriority(ReferencePriority.get(generator));

        if (!checkActivePowerControl(generator.getId(), generator.getTargetP(), generator.getMaxP(), minTargetP, maxTargetP,
                parameters.getPlausibleActivePowerLimit(), parameters.isUseActiveLimits(), report)) {
            participating = false;
        }

        if (generator.isVoltageRegulatorOn()) {
            setVoltageControl(generator.getTargetV(), generator.getTerminal(), generator.getRegulatingTerminal(), parameters,
                    report);
        }

        RemoteReactivePowerControl reactivePowerControl = generator.getExtension(RemoteReactivePowerControl.class);
        if (reactivePowerControl != null && reactivePowerControl.isEnabled() && !generator.isVoltageRegulatorOn() && parameters.isGeneratorReactivePowerRemoteControl()) {
            setRemoteReactivePowerControl(reactivePowerControl.getRegulatingTerminal(), reactivePowerControl.getTargetQ());
        }

        CoordinatedReactiveControl coordinatedReactiveControl = getGenerator().getExtension(CoordinatedReactiveControl.class);
        if (coordinatedReactiveControl != null) {
            if (coordinatedReactiveControl.getQPercent() == 0) {
                LOGGER.trace("Generator '{}' remote voltage control reactive power key value is zero", generator.getId());
                report.generatorsWithZeroRemoteVoltageControlReactivePowerKey++;
            } else {
                qPercent = coordinatedReactiveControl.getQPercent();
            }
        }
    }

    @Override
    public void reApplyActivePowerControlChecks(LfNetworkParameters parameters, LfNetworkLoadingReport report) {
        participating = initialParticipating;
        var generator = getGenerator();
        if (!checkActivePowerControl(generator.getId(), targetP * PerUnit.SB, generator.getMaxP(), minTargetP, maxTargetP,
                parameters.getPlausibleActivePowerLimit(), parameters.isUseActiveLimits(), report)) {
            participating = false;
        }
    }

    private static void createAsym(Generator generator, LfGeneratorImpl lfGenerator) {
        var extension = generator.getExtension(GeneratorFortescue.class);
        if (extension != null) {
            double vNom = generator.getTerminal().getVoltageLevel().getNominalV();
            double zb = vNom * vNom / PerUnit.SB;
            double r0 = extension.getRz() / zb;
            double x0 = extension.getXz() / zb;
            double r2 = extension.getRn() / zb;
            double x2 = extension.getXn() / zb;
            double z0Square = r0 * r0 + x0 * x0;
            double z2Square = r2 * r2 + x2 * x2;
            double epsilon = 0.0000000001;
            double bZero;
            double gZero;
            double bNegative;
            double gNegative;
            if (z0Square > epsilon) {
                bZero = -x0 / z0Square;
                gZero = r0 / z0Square;
            } else {
                throw new PowsyblException("Generator '" + generator.getId() + "' has fortescue zero sequence values that will bring singularity in the equation system");
            }
            if (z2Square > epsilon) {
                bNegative = -x2 / z2Square;
                gNegative = r2 / z2Square;
            } else {
                throw new PowsyblException("Generator '" + generator.getId() + "' has fortescue negative sequence values that will bring singularity in the equation system");
            }
            lfGenerator.setAsym(new LfAsymGenerator(gZero, bZero, gNegative, bNegative));
        }
    }

    public static LfGeneratorImpl create(Generator generator, LfNetwork network, LfNetworkParameters parameters,
                                         LfNetworkLoadingReport report) {
        Objects.requireNonNull(generator);
        Objects.requireNonNull(network);
        Objects.requireNonNull(parameters);
        Objects.requireNonNull(report);
        LfGeneratorImpl lfGenerator = new LfGeneratorImpl(generator, network, parameters, report);
        if (parameters.isAsymmetrical()) {
            createAsym(generator, lfGenerator);
        }
        return lfGenerator;
    }

    private Generator getGenerator() {
        return generatorRef.get();
    }

    @Override
    public String getId() {
        return getGenerator().getId();
    }

    @Override
    public boolean isFictitious() {
        return getGenerator().isFictitious();
    }

    @Override
    public OptionalDouble getRemoteControlReactiveKey() {
        return qPercent != null ? OptionalDouble.of(qPercent) : OptionalDouble.empty();
    }

    @Override
    public double getTargetQ() {
        double targetQ = Networks.zeroIfNan(getGenerator().getTargetQ()) / PerUnit.SB;
        if (forceTargetQInReactiveLimits) {
            double minQ = getMinQ();
            double maxQ = getMaxQ();
            if (targetQ < minQ) {
                targetQ = minQ;
            } else if (targetQ > maxQ) {
                targetQ = maxQ;
            }
        }
        return targetQ;
    }

    @Override
    public double getMinP() {
        return getGenerator().getMinP() / PerUnit.SB;
    }

    @Override
    public double getMaxP() {
        return getGenerator().getMaxP() / PerUnit.SB;
    }

    @Override
    public double getMinTargetP() {
        return minTargetP / PerUnit.SB;
    }

    @Override
    public double getMaxTargetP() {
        return maxTargetP / PerUnit.SB;
    }

    @Override
    protected Optional<ReactiveLimits> getReactiveLimits() {
        return Optional.of(getGenerator().getReactiveLimits());
    }

    @Override
    public boolean isParticipating() {
        return participating;
    }

    @Override
    public void setParticipating(boolean participating) {
        this.participating = participating;
    }

    @Override
    public double getDroop() {
        return droop;
    }

    @Override
    public double getParticipationFactor() {
        return participationFactor;
    }

    @Override
    public void updateState(LfNetworkStateUpdateParameters parameters) {
        var generator = getGenerator();
        generator.getTerminal()
                .setP(-targetP * PerUnit.SB)
                .setQ(Double.isNaN(calculatedQ) ? -getTargetQ() * PerUnit.SB : -calculatedQ * PerUnit.SB);
        if (parameters.isWriteReferenceTerminals() && isReference()) {
            ReferenceTerminals.addTerminal(generator.getTerminal());
        }
    }

    @Override
    protected boolean checkIfGeneratorStartedForVoltageControl(LfNetworkLoadingReport report) {
        return forceVoltageControl || super.checkIfGeneratorStartedForVoltageControl(report);
    }

    @Override
    protected boolean checkIfGeneratorIsInsideActivePowerLimitsForVoltageControl(LfNetworkParameters parameters, LfNetworkLoadingReport report) {
        return forceVoltageControl || super.checkIfGeneratorIsInsideActivePowerLimitsForVoltageControl(parameters, report);
    }
}