PsseExporter.java

/**
 * Copyright (c) 2021, 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.psse.converter;

import com.google.auto.service.AutoService;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.parameters.ConfiguredParameter;
import com.powsybl.commons.parameters.ParameterDefaultValueConfig;
import com.powsybl.commons.parameters.ParameterType;
import com.powsybl.iidm.network.Exporter;
import com.powsybl.iidm.network.*;
import com.powsybl.commons.parameters.Parameter;
import com.powsybl.psse.converter.extensions.PsseConversionContextExtension;
import com.powsybl.psse.converter.extensions.PsseModelExtension;
import com.powsybl.psse.model.PsseException;
import com.powsybl.psse.model.PsseVersion;
import com.powsybl.psse.model.io.Context;
import com.powsybl.psse.model.io.FileFormat;
import com.powsybl.psse.model.pf.PsseCaseIdentification;
import com.powsybl.psse.model.pf.PssePowerFlowModel;
import com.powsybl.psse.model.pf.io.*;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.format.DateTimeFormatter;
import java.util.*;

import static com.powsybl.psse.converter.VoltageLevelConverter.*;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 * @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
 */
@AutoService(Exporter.class)
public class PsseExporter implements Exporter {

    private static final double BASE_MVA = 100;

    private static final String FORMAT = "PSS/E";

    private static final Parameter EXPORT_UPDATE = new Parameter("psse.export.update",
            ParameterType.BOOLEAN,
            "Export by updating values on the imported psse model",
            Boolean.TRUE);

    private static final Parameter EXPORT_AS_RAW_FORMAT = new Parameter("psse.export.raw-format",
            ParameterType.BOOLEAN,
            "Export as raw format",
            Boolean.TRUE);

    @Override
    public String getFormat() {
        return FORMAT;
    }

    @Override
    public List<Parameter> getParameters() {
        return ConfiguredParameter.load(STATIC_PARAMETERS, getFormat(), ParameterDefaultValueConfig.INSTANCE);
    }

    private static final List<Parameter> STATIC_PARAMETERS = List.of(
            EXPORT_UPDATE,
            EXPORT_AS_RAW_FORMAT);

    @Override
    public String getComment() {
        return "Update IIDM to PSS/E ";
    }

    @Override
    public void export(Network network, Properties parameters, DataSource dataSource) {

        boolean updateExport = Parameter.readBoolean(FORMAT, parameters, EXPORT_UPDATE,
                ParameterDefaultValueConfig.INSTANCE);

        PssePowerFlowModel updatePsseModel;
        Context context;
        boolean isFullExport = isFullExport(network, updateExport);
        if (isFullExport) {
            boolean rawFormat = Parameter.readBoolean(FORMAT, parameters, EXPORT_AS_RAW_FORMAT,
                    ParameterDefaultValueConfig.INSTANCE);

            updatePsseModel = createPsseModel(network);
            context = PowerFlowDataFactory.createPsseContext(rawFormat);
        } else {
            // use the same format as the imported case
            PssePowerFlowModel psseModel = network.getExtension(PsseModelExtension.class).getPsseModel();
            updatePsseModel = createUpdatePsseModel(network, psseModel);
            context = network.getExtension(PsseConversionContextExtension.class).getContext();
        }

        PsseVersion version = PsseVersion.fromRevision(updatePsseModel.getCaseIdentification().getRev());
        if (context.getFileFormat() == FileFormat.JSON) {
            if (Objects.requireNonNull(version.major()) == PsseVersion.Major.V35) {
                PowerFlowRawxData35 rawXData35 = new PowerFlowRawxData35();
                try {
                    rawXData35.write(updatePsseModel, context, dataSource);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            } else {
                throw new PsseException("Unsupported version " + version);
            }
        } else {
            exportNotJson(context, updatePsseModel, version, dataSource);
        }
    }

    private static boolean isFullExport(Network network, boolean updateExport) {
        return !(updateExport && network.getExtension(PsseModelExtension.class) != null);
    }

    private void exportNotJson(Context context, PssePowerFlowModel updatePsseModel, PsseVersion version, DataSource dataSource) {
        switch (version.major()) {
            case V35 -> {
                PowerFlowRawData35 rawData35 = new PowerFlowRawData35();
                try {
                    rawData35.write(updatePsseModel, context, dataSource);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
            case V33 -> {
                PowerFlowRawData33 rawData33 = new PowerFlowRawData33();
                try {
                    rawData33.write(updatePsseModel, context, dataSource);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
            case V32 -> {
                PowerFlowRawData32 rawData32 = new PowerFlowRawData32();
                try {
                    rawData32.write(updatePsseModel, context, dataSource);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
            default -> throw new PsseException("Unsupported version " + version);
        }
    }

    // New equipment is not supported
    // Antennas (Branches connected only at one end) are exported as out of service (both sides open)
    // New buses are created in voltageLevels with nodeBreaker topology when buses are split
    private static PssePowerFlowModel createUpdatePsseModel(Network network, PssePowerFlowModel psseModel) {
        // Only the updated blocks are copied, non-updated blocks are referenced
        PssePowerFlowModel updatedPsseModel = psseModel.referenceAndCopyPssePowerFlowModel();
        updateModifiedBlocks(network, updatedPsseModel);
        return updatedPsseModel;
    }

    private static void updateModifiedBlocks(Network network, PssePowerFlowModel updatedPsseModel) {
        ContextExport contextExport = VoltageLevelConverter.createContextExport(network, updatedPsseModel, false);

        VoltageLevelConverter.updateSubstations(network, contextExport);

        BusConverter.update(updatedPsseModel, contextExport);
        LoadConverter.update(network, updatedPsseModel);
        FixedShuntCompensatorConverter.update(network, updatedPsseModel);
        GeneratorConverter.update(network, updatedPsseModel);
        LineConverter.update(network, updatedPsseModel);
        TransformerConverter.update(network, updatedPsseModel);
        TwoTerminalDcConverter.update(network, updatedPsseModel);
        VscDcTransmissionLineConverter.update(network, updatedPsseModel);
        FactsDeviceConverter.update(network, updatedPsseModel);
        SwitchedShuntCompensatorConverter.update(network, updatedPsseModel);
    }

    private static PssePowerFlowModel createPsseModel(Network network) {
        PerUnitContext perUnitContext = new PerUnitContext(BASE_MVA);
        PsseCaseIdentification caseIdentification = createCaseIdentification(network, perUnitContext);
        PssePowerFlowModel psseModel = new PssePowerFlowModel(caseIdentification);
        ContextExport contextExport = createContextExport(network, psseModel, true);

        VoltageLevelConverter.createSubstations(psseModel, contextExport);

        BusConverter.create(psseModel, contextExport);
        LoadConverter.create(network, psseModel, contextExport);
        FixedShuntCompensatorConverter.create(network, psseModel, contextExport);
        GeneratorConverter.create(network, psseModel, contextExport, perUnitContext);
        LineConverter.create(network, psseModel, contextExport, perUnitContext);
        TransformerConverter.create(network, psseModel, contextExport, perUnitContext);
        TwoTerminalDcConverter.create(network, psseModel, contextExport);
        VscDcTransmissionLineConverter.create(network, psseModel, contextExport);
        FactsDeviceConverter.create(network, psseModel, contextExport);
        SwitchedShuntCompensatorConverter.create(network, psseModel, contextExport);

        BatteryConverter.create(network, psseModel, contextExport);
        TieLineConverter.create(network, psseModel, contextExport, perUnitContext);
        DanglingLineConverter.create(network, psseModel, contextExport, perUnitContext);
        return psseModel;
    }

    private static PsseCaseIdentification createCaseIdentification(Network network, PerUnitContext perUnitContext) {
        PsseCaseIdentification caseIdentification = new PsseCaseIdentification();
        caseIdentification.setIc(0);
        caseIdentification.setSbase(perUnitContext.sBase);
        caseIdentification.setRev(35);
        caseIdentification.setXfrrat(0.0);
        caseIdentification.setNxfrat(0.0);
        caseIdentification.setBasfrq(50.0);
        String caseDate = network.getCaseDate().format(DateTimeFormatter.RFC_1123_DATE_TIME);
        String caseName = network.getNameOrId();
        caseIdentification.setTitle1(String.format("%s %s", caseDate, caseName));
        caseIdentification.setTitle2("");

        return caseIdentification;
    }

    record PerUnitContext(double sBase) {
    }
}