PowerFactoryImporter.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.powerfactory.converter;
import com.google.auto.service.AutoService;
import com.google.common.base.Stopwatch;
import com.google.common.io.ByteStreams;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.datasource.ReadOnlyDataSource;
import com.powsybl.iidm.network.Importer;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.NetworkFactory;
import com.powsybl.iidm.network.util.ContainersMapping;
import com.powsybl.powerfactory.converter.AbstractConverter.NodeRef;
import com.powsybl.powerfactory.model.DataObject;
import com.powsybl.powerfactory.model.PowerFactoryDataLoader;
import com.powsybl.powerfactory.model.PowerFactoryException;
import com.powsybl.powerfactory.model.StudyCase;
import org.apache.commons.lang3.mutable.MutableInt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
@AutoService(Importer.class)
public class PowerFactoryImporter implements Importer {
private static final Logger LOGGER = LoggerFactory.getLogger(PowerFactoryImporter.class);
private static final String FORMAT = "POWER-FACTORY";
@Override
public String getFormat() {
return FORMAT;
}
@Override
public List<String> getSupportedExtensions() {
return PowerFactoryDataLoader.find(StudyCase.class).stream().map(PowerFactoryDataLoader::getExtension).toList();
}
@Override
public String getComment() {
return "PowerFactory to IIDM converter";
}
private Optional<PowerFactoryDataLoader<StudyCase>> findProjectLoader(ReadOnlyDataSource dataSource) {
for (PowerFactoryDataLoader<StudyCase> studyCaseLoader : PowerFactoryDataLoader.find(StudyCase.class)) {
try {
if (dataSource.isDataExtension(studyCaseLoader.getExtension()) && dataSource.exists(null, studyCaseLoader.getExtension())) {
return Optional.of(studyCaseLoader);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return Optional.empty();
}
@Override
public boolean exists(ReadOnlyDataSource dataSource) {
return findProjectLoader(dataSource).filter(studyCaseLoader -> {
try (InputStream is = dataSource.newInputStream(null, studyCaseLoader.getExtension())) {
return studyCaseLoader.test(is);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}).isPresent();
}
@Override
public void copy(ReadOnlyDataSource fromDataSource, DataSource toDataSource) {
Objects.requireNonNull(fromDataSource);
Objects.requireNonNull(toDataSource);
findProjectLoader(fromDataSource).ifPresent(studyCaseLoader -> {
try (InputStream is = fromDataSource.newInputStream(null, studyCaseLoader.getExtension());
OutputStream os = toDataSource.newOutputStream(null, studyCaseLoader.getExtension(), false)) {
ByteStreams.copy(is, os);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
static class ImportContext {
final ContainersMapping containerMapping;
// object id to node (IIDM node/breaker node) mapping
final Map<Long, List<NodeRef>> objIdToNode = new HashMap<>();
final Map<String, MutableInt> nodeCountByVoltageLevelId = new HashMap<>();
// elmTerm object id to busbarSection id mapping
final Map<Long, NodeRef> elmTermIdToNode = new HashMap<>();
List<DataObject> cubiclesObjectNotFound = new ArrayList<>();
ImportContext(ContainersMapping containerMapping) {
this.containerMapping = containerMapping;
}
}
private Network createNetwork(StudyCase studyCase, NetworkFactory networkFactory) {
Network network = networkFactory.createNetwork(studyCase.getName(), FORMAT);
List<DataObject> elmNets = studyCase.getElmNets();
if (elmNets.isEmpty()) {
throw new PowerFactoryException("No ElmNet object found");
}
LOGGER.info("Study case has {} network(s): {}", elmNets.size(), elmNets.stream().map(DataObject::getLocName).toList());
// case date
ZonedDateTime caseDate = ZonedDateTime.ofInstant(studyCase.getTime(), ZoneId.systemDefault());
network.setCaseDate(caseDate);
List<DataObject> elmTerms = studyCase.getElmNets().stream()
.flatMap(elmNet -> elmNet.search(".*.ElmTerm").stream())
.collect(Collectors.toList());
LOGGER.info("Creating containers...");
ContainersMapping containerMapping = ContainersMappingHelper.create(studyCase.getIndex(), elmTerms);
ImportContext importContext = new ImportContext(containerMapping);
LOGGER.info("Creating topology graphs...");
// Identify Hvdc configurations
List<DataObject> elmVscs = studyCase.getElmNets().stream()
.flatMap(elmNet -> elmNet.search(".*.ElmVsc").stream())
.collect(Collectors.toList());
HvdcConverter hvdcConverter = new HvdcConverter(importContext, network);
hvdcConverter.computeConfigurations(elmTerms, elmVscs);
// process terminals
for (DataObject elmTerm : elmTerms) {
if (!hvdcConverter.isDcNode(elmTerm)) {
new NodeConverter(importContext, network).createAndMapConnectedObjs(elmTerm);
}
}
if (!importContext.cubiclesObjectNotFound.isEmpty()) {
LOGGER.warn("{} cubicles have a missing connected object", importContext.cubiclesObjectNotFound.size());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Cubicles with missing connected object: {}", importContext.cubiclesObjectNotFound);
}
}
LOGGER.info("Creating equipment...");
List<DataObject> slackObjects = new ArrayList<>();
// Create main equipment
convertEquipment(studyCase, importContext, hvdcConverter, network, slackObjects);
// Create Hvdc Links
hvdcConverter.create();
// Attach a slack bus
new SlackConverter(importContext, network).create(slackObjects);
LOGGER.info("{} substations, {} voltage levels, {} lines, {} 2w-transformers, {} 3w-transformers, {} generators, {} loads, {} shunts have been created",
network.getSubstationCount(), network.getVoltageLevelCount(), network.getLineCount(), network.getTwoWindingsTransformerCount(),
network.getThreeWindingsTransformerCount(), network.getGeneratorCount(), network.getLoadCount(), network.getShuntCompensatorCount());
setVoltagesAndAngles(network, importContext, elmTerms);
return network;
}
private static void convertEquipment(StudyCase studyCase, ImportContext importContext, HvdcConverter hvdcConverter,
Network network, List<DataObject> slackObjects) {
var objs = studyCase.getElmNets().stream()
.flatMap(elmNet -> elmNet.search(".*").stream())
.toList();
for (DataObject obj : objs) {
switch (obj.getDataClassName()) {
case "ElmCoup":
new SwitchConverter(importContext, network).createFromElmCoup(obj);
break;
case "ElmSym":
case "ElmAsm":
case "ElmGenstat":
new GeneratorConverter(importContext, network).create(obj);
if (GeneratorConverter.isSlack(obj)) {
slackObjects.add(obj);
}
break;
case "ElmLod":
new LoadConverter(importContext, network).create(obj);
break;
case "ElmShnt":
new ShuntConverter(importContext, network).create(obj);
break;
case "ElmLne":
if (!hvdcConverter.isDcLink(obj)) {
new LineConverter(importContext, network).create(obj);
}
break;
case "ElmTow":
new LineConverter(importContext, network).createTower(obj);
break;
case "ElmTr2":
new TransformerConverter(importContext, network).createTwoWindings(obj);
break;
case "ElmTr3":
new TransformerConverter(importContext, network).createThreeWindings(obj);
break;
case "ElmZpu":
new CommonImpedanceConverter(importContext, network).create(obj);
break;
case "ElmNet":
case "ElmSubstat":
case "ElmTrfstat":
case "StaCubic":
case "StaSwitch":
case DataAttributeNames.ELMTERM:
// already processed
break;
case "TypLne":
case "TypSym":
case "TypLod":
case "TypTr2":
case "TypTr3":
// Referenced by other objects
break;
case "BlkDef":
case "ChaRef":
case "ChaVec":
case "ElmArea":
case "ElmBmu":
case "ElmBoundary":
case "ElmBranch":
case "ElmComp":
case "ElmDcubi":
case "ElmDsl":
case "ElmFile":
case "ElmPhi__pll":
case "ElmRelay":
case "ElmSecctrl":
case "ElmSite":
case "ElmStactrl":
case "ElmValve":
case "ElmVsc":
case "ElmZone":
case "IntCalcres":
case "IntCondition":
case "IntEvt":
case "IntEvtrel":
case "IntFolder":
case "IntForm":
case "IntGate":
case "IntGrf":
case "IntGrfcon":
case "IntGrflayer":
case "IntGrfnet":
case "IntMat":
case "IntMon":
case "IntQlim":
case "IntRas":
case "IntRef":
case "IntTemplate":
case "IntWdt":
case "OptElmgenstat":
case "OptElmrecmono":
case "OptElmsym":
case "RelChar":
case "RelDir":
case "RelDisdir":
case "RelDisloadenc":
case "RelDismho":
case "RelDispoly":
case "RelDispspoly":
case "RelFdetabb":
case "RelFdetaegalst":
case "RelFdetect":
case "RelFdetsie":
case "RelFmeas":
case "RelFrq":
case "RelIoc":
case "RelLogdip":
case "RelLogic":
case "RelLslogic":
case "RelMeasure":
case "RelRecl":
case "RelSeldir":
case "RelTimer":
case "RelToc":
case "RelUlim":
case "RelZpol":
case "StaCt":
case "StaPqmea":
case "StaVmea":
case "StaVt":
case "TypChatoc":
case "TypCon":
case "TypCt":
case "TypRelay":
case "TypVt":
// not interesting
break;
default:
LOGGER.warn("Unexpected data class '{}' ('{}')", obj.getDataClassName(), obj);
}
}
}
private static void setVoltagesAndAngles(Network network, ImportContext importContext, List<DataObject> elmTerms) {
VoltageAndAngle va = new VoltageAndAngle(importContext, network);
for (DataObject elmTerm : elmTerms) {
va.update(elmTerm);
}
}
@Override
public Network importData(ReadOnlyDataSource dataSource, NetworkFactory networkFactory, Properties parameters) {
return findProjectLoader(dataSource).map(studyCaseLoader -> {
LOGGER.info("Starting PowerFactory import...");
Stopwatch stopwatch = Stopwatch.createStarted();
try (InputStream is = dataSource.newInputStream(null, studyCaseLoader.getExtension())) {
StudyCase studyCase = studyCaseLoader.doLoad(dataSource.getBaseName(), is);
return createNetwork(studyCase, networkFactory);
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
stopwatch.stop();
LOGGER.info("PowerFactory import done in {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
}).orElseThrow(() -> new PowerFactoryException("This is not a supported PowerFactory file"));
}
}