Java tutorial
/** * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium) * 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/. */ package eu.itesla_project.eurostag; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import eu.itesla_project.commons.Version; import eu.itesla_project.commons.config.PlatformConfig; import eu.itesla_project.computation.*; import eu.itesla_project.contingency.ContingenciesProvider; import eu.itesla_project.iidm.eurostag.export.EurostagDictionary; import eu.itesla_project.iidm.network.*; import eu.itesla_project.iidm.network.util.Identifiables; import eu.itesla_project.iidm.network.util.Networks; import eu.itesla_project.contingency.Contingency; import eu.itesla_project.contingency.ContingencyElement; import eu.itesla_project.simulation.securityindexes.SecurityIndex; import eu.itesla_project.simulation.securityindexes.SecurityIndexParser; import eu.itesla_project.simulation.*; import eu.itesla_project.simulation.ImpactAnalysis; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.HierarchicalINIConfiguration; import org.apache.commons.configuration.SubnodeConfiguration; import org.jboss.shrinkwrap.api.Domain; import org.jboss.shrinkwrap.api.GenericArchive; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.nio.file.ShrinkWrapFileSystems; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.CompletableFuture; import static eu.itesla_project.computation.FilePostProcessor.FILE_GZIP; import static eu.itesla_project.computation.FilePreProcessor.ARCHIVE_UNZIP; import static eu.itesla_project.computation.FilePreProcessor.FILE_GUNZIP; /** * Example: * <p><pre> *import eu.itesla_project.modules.Stabilization; *import eu.itesla_project.modules.ImpactAnalysis; *import eu.itesla_project.modules.SimulationParameters; *import eu.itesla_project.modules.StabilizationResult; *import eu.itesla_project.modules.ImpactAnalysisResult; *import eu.itesla_project.iidm.ddb.eurostag_imp_exp.DynamicDatabaseClient; *import eu.itesla_project.modules.ContingenciesAndActionsDatabaseClient; *import eu.itesla_project.modules.test.AutomaticContingenciesAndActionsDatabaseClient; *import eu.itesla_project.computation.ComputationPlatformClient; *import eu.itesla_project.computation.local.LocalComputationPlatformClient; *import eu.itesla_project.eurostag.EurostagConfig; *import eu.itesla_project.eurostag.EurostagStabilization; *import eu.itesla_project.eurostag.EurostagImpactAnalysis; *import eu.itesla_project.iidm.ddb.eurostag_imp_exp.DdbDtaImpExp; *import eu.itesla_project.iidm.network.Network; *import java.nio.file.Path; *import java.nio.file.Paths; * *public class EurostagDemo { * * public static void main(String[] args) throws Exception { * Network network = ...; * ComputationPlatformClient cpClient = new LocalComputationPlatformClient(Paths.get("/tmp")); * DynamicDatabaseClient ddbClient = new DdbDtaImpExp(); * ContingenciesAndActionsDatabaseClient cadbClient = new AutomaticContingenciesAndActionsDatabaseClient(5); * EurostagConfig config = new EurostagConfig(); * try (Stabilization stabilization = new EurostagStabilization(network, cpClient, ddbClient); * ImpactAnalysis impactAnalysis = new EurostagImpactAnalysis(network, cpClient, cadbClient, config)) { * Map<String, Object> initContext = new HashMap<>(); * SimulationParameters simulationParameters = new SimulationParameters(); * stabilization.init(simulationParameters, initContext); * impactAnalysis.init(simulationParameters, initContext); * StabilizationResult sr = stabilization.run(); * ImpactAnalysisResult result = impactAnalysis.run(sr.getState()); * System.out.println("Simulation complete"); * } * } *} * </pre> * @author Geoffroy Jamgotchian <geoffroy.jamgotchian at rte-france.com> */ public class EurostagImpactAnalysis implements ImpactAnalysis, EurostagConstants { private static final Logger LOGGER = LoggerFactory.getLogger(EurostagImpactAnalysis.class); private static final String PARTIAL_SCENARIOS_ZIP_FILE_NAME = "eurostag-partial-scenarios.zip"; private static final String FAULT_BASE_NAME = FAULT_SEQ_FILE_NAME.replace(".seq", ""); private static final String FAULT_OUT_FILE_NAME = FAULT_SEQ_FILE_NAME.replace(".seq", ".out"); private static final String FAULT_OUT_GZ_FILE_NAME = FAULT_OUT_FILE_NAME + ".gz"; private static final String CURRENT_LIMITS_CSV = "current_limits.csv"; private static final String VOLTAGE_LIMITS_CSV = "voltage_limits.csv"; private static final String TSO_LIMITS_SECURITY_INDEX_FILE_NAME = FAULT_SEQ_FILE_NAME.replace(".seq", "_tso_limits_security_indexes.xml"); private static final String WP43_SMALLSIGNAL_SECURITY_INDEX_FILE_NAME = FAULT_SEQ_FILE_NAME.replace(".seq", "_wp43_smallsignal_security_indexes.xml"); private static final String WP43_TRANSIENT_SECURITY_INDEX_FILE_NAME = FAULT_SEQ_FILE_NAME.replace(".seq", "_wp43_transient_security_indexes.xml"); private static final String WP43_OVERLOAD_SECURITY_INDEX_FILE_NAME = FAULT_SEQ_FILE_NAME.replace(".seq", "_wp43_overload_security_indexes.xml"); private static final String WP43_UNDEROVERVOLTAGE_SECURITY_INDEX_FILE_NAME = FAULT_SEQ_FILE_NAME.replace(".seq", "_wp43_underovervoltage_security_indexes.xml"); private static final String WP43_ALL_CONFIGS_ZIP_FILE_NAME = "wp43-all-configs.zip"; private static final String WP43_PARTIAL_CONFIGS_ZIP_FILE_NAME = "wp43-partial-configs.zip"; private static final String WP43_CONFIGS_FILE_NAME = "wp43adapter.properties"; private static final String WP43_CONFIGS_PER_FAULT_FILE_NAME = "wp43adapter_fault_" + Command.EXECUTION_NUMBER_PATTERN + ".properties"; private static final String WORKING_DIR_PREFIX = "itesla_eurostag_impact_analysis_"; private final Network network; private final ComputationManager computationManager; private final ContingenciesProvider contingenciesProvider; private final int priority; private final EurostagConfig config; private final Command allCmd; private final List<Contingency> allContingencies = new ArrayList<>(); private final Command subsetCmd; private EurostagDictionary dictionary; private SimulationParameters parameters; public EurostagImpactAnalysis(Network network, ComputationManager computationManager, int priority, ContingenciesProvider contingenciesProvider) { this(network, computationManager, priority, contingenciesProvider, EurostagConfig.load()); } public EurostagImpactAnalysis(Network network, ComputationManager computationManager, int priority, ContingenciesProvider contingenciesProvider, EurostagConfig config) { Objects.requireNonNull(network, "network is null"); Objects.requireNonNull(computationManager, "computation manager is null"); Objects.requireNonNull(contingenciesProvider, "contingencies provider is null"); Objects.requireNonNull(config, "config is null"); this.network = network; this.computationManager = computationManager; this.priority = priority; this.contingenciesProvider = contingenciesProvider; this.config = config; allCmd = createCommand(ALL_SCENARIOS_ZIP_FILE_NAME, WP43_ALL_CONFIGS_ZIP_FILE_NAME); subsetCmd = createCommand(PARTIAL_SCENARIOS_ZIP_FILE_NAME, WP43_PARTIAL_CONFIGS_ZIP_FILE_NAME); } private Command createCommand(String scenarioZipFileName, String wp43ConfigsZipFileName) { return new GroupCommandBuilder().id("esg_fs") .inputFiles(new InputFile(PRE_FAULT_SAC_GZ_FILE_NAME, FILE_GUNZIP), new InputFile(LIMITS_ZIP_FILE_NAME, ARCHIVE_UNZIP), new InputFile(scenarioZipFileName, ARCHIVE_UNZIP), new InputFile(wp43ConfigsZipFileName, ARCHIVE_UNZIP), new InputFile(DDB_DICT_GENS_CSV)) .subCommand().program(EUSTAG_CPT).args("-s", FAULT_SEQ_FILE_NAME, PRE_FAULT_SAC_FILE_NAME) .timeout(config.getSimTimeout()).add().subCommand().program(TSOINDEXES).args(".", FAULT_BASE_NAME) .timeout(config.getIdxTimeout()).add().subCommand().program(WP43) .args("./", FAULT_BASE_NAME, WP43_CONFIGS_PER_FAULT_FILE_NAME).timeout(config.getIdxTimeout()).add() .outputFiles(new OutputFile(TSO_LIMITS_SECURITY_INDEX_FILE_NAME), new OutputFile(WP43_SMALLSIGNAL_SECURITY_INDEX_FILE_NAME), new OutputFile(WP43_TRANSIENT_SECURITY_INDEX_FILE_NAME), new OutputFile(WP43_OVERLOAD_SECURITY_INDEX_FILE_NAME), new OutputFile(WP43_UNDEROVERVOLTAGE_SECURITY_INDEX_FILE_NAME), new OutputFile(FAULT_OUT_FILE_NAME, FILE_GZIP)) .build(); } @Override public String getName() { return EurostagUtil.PRODUCT_NAME; } @Override public String getVersion() { return ImmutableMap.builder().put("eurostagVersion", EurostagUtil.VERSION).putAll(Version.VERSION.toMap()) .build().toString(); } //needed by wp43 integration //contains lines, only private static void dumpLinesDictionary(Network network, EurostagDictionary dictionary, Path dir) throws IOException { try (BufferedWriter os = Files.newBufferedWriter(dir.resolve("dict_lines.csv"), StandardCharsets.UTF_8)) { for (Identifiable obj : Identifiables.sort(Iterables.concat(network.getLines(), network.getTwoWindingsTransformers(), network.getDanglingLines()))) { os.write(obj.getId() + ";" + dictionary.getEsgId(obj.getId())); os.newLine(); } for (ThreeWindingsTransformer twt : Identifiables.sort(network.getThreeWindingsTransformers())) { throw new AssertionError("TODO"); } } } //needed by wp43 integration //contains buses, only private static void dumpBusesDictionary(Network network, EurostagDictionary dictionary, Path dir) throws IOException { try (BufferedWriter os = Files.newBufferedWriter(dir.resolve("dict_buses.csv"), StandardCharsets.UTF_8)) { for (Bus bus : Identifiables.sort(network.getBusBreakerView().getBuses())) { os.write(bus.getId() + ";" + dictionary.getEsgId(bus.getId())); os.newLine(); } } } private static void dumpLimits(EurostagDictionary dictionary, BufferedWriter writer, TwoTerminalsConnectable branch) throws IOException { dumpLimits(dictionary, writer, branch.getId(), branch.getCurrentLimits1(), branch.getCurrentLimits2(), branch.getTerminal1().getVoltageLevel().getNominalV(), branch.getTerminal2().getVoltageLevel().getNominalV()); } private static void dumpLimits(EurostagDictionary dictionary, BufferedWriter writer, String branchId, CurrentLimits cl1, CurrentLimits cl2, float nominalV1, float nominalV2) throws IOException { writer.write(dictionary.getEsgId(branchId)); writer.write(";"); writer.write(Float.toString(cl1 != null ? cl1.getPermanentLimit() : Float.MAX_VALUE)); writer.write(";"); writer.write(Float.toString(cl2 != null ? cl2.getPermanentLimit() : Float.MAX_VALUE)); writer.write(";"); writer.write(Float.toString(nominalV1)); writer.write(";"); writer.write(Float.toString(nominalV2)); writer.write(";"); writer.write(branchId); writer.newLine(); } private static void writeLimits(Network network, EurostagDictionary dictionary, Domain domain, OutputStream os) throws IOException { GenericArchive archive = domain.getArchiveFactory().create(GenericArchive.class); try (FileSystem fileSystem = ShrinkWrapFileSystems.newFileSystem(archive)) { Path rootDir = fileSystem.getPath("/"); // dump first current limits for each of the branches try (BufferedWriter writer = Files.newBufferedWriter(rootDir.resolve(CURRENT_LIMITS_CSV), StandardCharsets.UTF_8)) { for (Line l : network.getLines()) { dumpLimits(dictionary, writer, l); } for (TwoWindingsTransformer twt : network.getTwoWindingsTransformers()) { dumpLimits(dictionary, writer, twt); } for (DanglingLine dl : network.getDanglingLines()) { dumpLimits(dictionary, writer, dl.getId(), dl.getCurrentLimits(), null, dl.getTerminal().getVoltageLevel().getNominalV(), dl.getTerminal().getVoltageLevel().getNominalV()); } } try (BufferedWriter writer = Files.newBufferedWriter(rootDir.resolve(VOLTAGE_LIMITS_CSV), StandardCharsets.UTF_8)) { for (Bus b : network.getBusBreakerView().getBuses()) { VoltageLevel vl = b.getVoltageLevel(); if (!Float.isNaN(vl.getLowVoltageLimit()) && !Float.isNaN(vl.getHighVoltageLimit())) { writer.write(dictionary.getEsgId(b.getId())); writer.write(";"); writer.write(Float.toString(vl.getLowVoltageLimit())); writer.write(";"); writer.write(Float.toString(vl.getHighVoltageLimit())); writer.write(";"); writer.write(Float.toString(vl.getNominalV())); writer.newLine(); } } } //dump lines dictionary, for WP43 integration dumpLinesDictionary(network, dictionary, rootDir); //dump buses dictionary, for WP43 integration dumpBusesDictionary(network, dictionary, rootDir); } archive.as(ZipExporter.class).exportTo(os); } private void writeLimits(Domain domain, OutputStream os) throws IOException { writeLimits(network, dictionary, domain, os); } static void writeLimits(Network network, EurostagDictionary dictionary, OutputStream os) throws IOException { writeLimits(network, dictionary, ShrinkWrap.createDomain(), os); } private void writeScenarios(Domain domain, List<Contingency> contingencies, OutputStream os) throws IOException { GenericArchive archive = new EurostagScenario(parameters, config).writeFaultSeqArchive(domain, contingencies, network, dictionary, faultNum -> FAULT_SEQ_FILE_NAME .replace(Command.EXECUTION_NUMBER_PATTERN, Integer.toString(faultNum))); archive.as(ZipExporter.class).exportTo(os); } private void writeAllScenarios(Domain domain, OutputStream os) throws IOException { writeScenarios(domain, allContingencies, os); } private double getFaultDuration(Contingency contingency, ContingencyElement element) { switch (element.getType()) { case GENERATOR: return parameters.getGeneratorFaultShortCircuitDuration(contingency.getId(), element.getId()); case LINE: return parameters.getBranchFaultShortCircuitDuration(contingency.getId(), element.getId()); default: throw new AssertionError(); } } private void writeWp43Configs(List<Contingency> contingencies, Path workingDir) throws IOException, ConfigurationException { Path baseWp43ConfigFile = PlatformConfig.CONFIG_DIR.resolve(WP43_CONFIGS_FILE_NAME); // generate one variant of the base config for all the contingency // this allow to add extra variables for some indexes HierarchicalINIConfiguration configuration = new HierarchicalINIConfiguration(baseWp43ConfigFile.toFile()); SubnodeConfiguration node = configuration.getSection("smallsignal"); node.setProperty("f_instant", parameters.getFaultEventInstant()); for (int i = 0; i < contingencies.size(); i++) { Contingency contingency = contingencies.get(i); if (contingency.getElements().isEmpty()) { throw new AssertionError("Empty contingency " + contingency.getId()); } Iterator<ContingencyElement> it = contingency.getElements().iterator(); // compute the maximum fault duration double maxDuration = getFaultDuration(contingency, it.next()); while (it.hasNext()) { maxDuration = Math.max(maxDuration, getFaultDuration(contingency, it.next())); } node.setProperty("f_duration", maxDuration); Path wp43Config = workingDir.resolve(WP43_CONFIGS_PER_FAULT_FILE_NAME .replace(Command.EXECUTION_NUMBER_PATTERN, Integer.toString(i))); try (Writer writer = Files.newBufferedWriter(wp43Config, StandardCharsets.UTF_8)) { configuration.save(writer); } } } private void writeWp43Configs(Domain domain, List<Contingency> contingencies, OutputStream os) throws IOException, ConfigurationException { // copy wp43 configuration files GenericArchive archive = domain.getArchiveFactory().create(GenericArchive.class); try (FileSystem fileSystem = ShrinkWrapFileSystems.newFileSystem(archive)) { Path rootDir = fileSystem.getPath("/"); writeWp43Configs(contingencies, rootDir); } archive.as(ZipExporter.class).exportTo(os); } private void writeAllWp43Configs(Domain domain, OutputStream os) throws IOException, ConfigurationException { writeWp43Configs(domain, allContingencies, os); } private void readSecurityIndexes(List<Contingency> contingencies, Path workingDir, ImpactAnalysisResult result) throws IOException { long start = System.currentTimeMillis(); int files = 0; for (int i = 0; i < contingencies.size(); i++) { Contingency contingency = contingencies.get(i); for (String securityIndexFileName : Arrays.asList(TSO_LIMITS_SECURITY_INDEX_FILE_NAME, WP43_SMALLSIGNAL_SECURITY_INDEX_FILE_NAME, WP43_TRANSIENT_SECURITY_INDEX_FILE_NAME, WP43_OVERLOAD_SECURITY_INDEX_FILE_NAME, WP43_UNDEROVERVOLTAGE_SECURITY_INDEX_FILE_NAME)) { Path file = workingDir.resolve( securityIndexFileName.replace(Command.EXECUTION_NUMBER_PATTERN, Integer.toString(i))); if (Files.exists(file)) { try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { for (SecurityIndex index : SecurityIndexParser.fromXml(contingency.getId(), reader)) { result.addSecurityIndex(index); } } files++; } } // also scan errors in output EurostagUtil.searchErrorMessage( workingDir.resolve( FAULT_OUT_GZ_FILE_NAME.replace(Command.EXECUTION_NUMBER_PATTERN, Integer.toString(i))), result.getMetrics(), i); } LOGGER.trace("{} security indexes files read in {} ms", files, (System.currentTimeMillis() - start)); } private static EurostagDictionary getDictionary(Map<String, Object> context) { Object dictionary = context.get("dictionary"); if (dictionary == null) { throw new RuntimeException("Stabilization must be initialized first"); } if (!(dictionary instanceof EurostagDictionary)) { throw new RuntimeException("Incompatiblity between stabilization and impact analysis implementations"); } return (EurostagDictionary) dictionary; } @Override public void init(SimulationParameters parameters, Map<String, Object> context) throws Exception { Objects.requireNonNull(parameters, "parameters is null"); Objects.requireNonNull(context, "context is null"); this.parameters = parameters; dictionary = getDictionary(context); // read all contingencies allContingencies.addAll(contingenciesProvider.getContingencies(network)); if (config.isUseBroadcast()) { Domain domain = ShrinkWrap.createDomain(); try (OutputStream os = computationManager.newCommonFile(ALL_SCENARIOS_ZIP_FILE_NAME)) { writeAllScenarios(domain, os); } try (OutputStream os = computationManager.newCommonFile(WP43_ALL_CONFIGS_ZIP_FILE_NAME)) { writeAllWp43Configs(domain, os); } try (OutputStream os = computationManager.newCommonFile(LIMITS_ZIP_FILE_NAME)) { writeLimits(domain, os); } } } private static void checkState(SimulationState state) { Objects.requireNonNull(state, "state is null"); if (!(state instanceof EurostagState)) { throw new RuntimeException("Incompatiblity between stabilization and impact analysis implementations"); } } @Override public ImpactAnalysisResult run(SimulationState state) throws Exception { return run(state, null); } private Command before(SimulationState state, Set<String> contingencyIds, Path workingDir, List<Contingency> contingencies) throws IOException { // dump state info for debugging if (config.isDebug()) { Networks.dumpStateId(workingDir, state.getName()); } try (OutputStream os = Files.newOutputStream(workingDir.resolve(PRE_FAULT_SAC_GZ_FILE_NAME))) { os.write(((EurostagState) state).getSacGz()); } Supplier<Domain> domain = Suppliers.memoize(ShrinkWrap::createDomain); if (!config.isUseBroadcast()) { Files.write(workingDir.resolve(DDB_DICT_GENS_CSV), ((EurostagState) state).getDictGensCsv()); try (OutputStream os = Files.newOutputStream(workingDir.resolve(LIMITS_ZIP_FILE_NAME))) { writeLimits(domain.get(), os); } } Command cmd; if (contingencyIds == null) { // take all contingencies contingencies.addAll(allContingencies); cmd = allCmd; if (config.isUseBroadcast()) { // all scenarios zip file has already been sent in the common dir } else { try (OutputStream os = Files.newOutputStream(workingDir.resolve(ALL_SCENARIOS_ZIP_FILE_NAME))) { writeAllScenarios(domain.get(), os); } try (OutputStream os = Files.newOutputStream(workingDir.resolve(WP43_ALL_CONFIGS_ZIP_FILE_NAME))) { writeAllWp43Configs(domain.get(), os); } catch (ConfigurationException e) { throw new RuntimeException(e); } } } else { // filter contingencies for (Contingency c : contingenciesProvider.getContingencies(network)) { if (contingencyIds.contains(c.getId())) { contingencies.add(c); } } cmd = subsetCmd; // write scenarios subset in the working dir try (OutputStream os = Files.newOutputStream(workingDir.resolve(PARTIAL_SCENARIOS_ZIP_FILE_NAME))) { writeScenarios(domain.get(), contingencies, os); } try (OutputStream os = Files.newOutputStream(workingDir.resolve(WP43_PARTIAL_CONFIGS_ZIP_FILE_NAME))) { writeWp43Configs(domain.get(), contingencies, os); } catch (ConfigurationException e) { throw new RuntimeException(e); } } return cmd; } private ImpactAnalysisResult after(Path workingDir, List<Contingency> contingencies, ExecutionReport report) throws IOException { report.log(); // read security indexes files generated by impact analysis Map<String, String> metrics = new HashMap<>(); fillMetrics(contingencies, report, metrics); ImpactAnalysisResult result = new ImpactAnalysisResult(metrics); readSecurityIndexes(contingencies, workingDir, result); return result; } @Override public ImpactAnalysisResult run(SimulationState state, Set<String> contingencyIds) throws Exception { checkState(state); try (CommandExecutor executor = computationManager.newCommandExecutor(EurostagUtil.createEnv(config), WORKING_DIR_PREFIX, config.isDebug())) { Path workingDir = executor.getWorkingDir(); List<Contingency> contingencies = new ArrayList<>(); Command cmd = before(state, contingencyIds, workingDir, contingencies); // start execution ExecutionReport report = executor.start(new CommandExecution(cmd, contingencies.size(), priority, ImmutableMap.of("state", state.getName()))); return after(workingDir, contingencies, report); } } @Override public CompletableFuture<ImpactAnalysisResult> runAsync(SimulationState state, Set<String> contingencyIds, ImpactAnalysisProgressListener listener) { checkState(state); return computationManager.execute( new ExecutionEnvironment(EurostagUtil.createEnv(config), WORKING_DIR_PREFIX, config.isDebug()), new DefaultExecutionHandler<ImpactAnalysisResult>() { private final List<Contingency> contingencies = new ArrayList<>(); @Override public List<CommandExecution> before(Path workingDir) throws IOException { Command cmd = EurostagImpactAnalysis.this.before(state, contingencyIds, workingDir, contingencies); return Arrays.asList(new CommandExecution(cmd, contingencies.size(), priority, ImmutableMap.of("state", state.getName()))); } @Override public void onProgress(CommandExecution execution, int executionIndex) { if (listener != null) { listener.onProgress(executionIndex); } } @Override public ImpactAnalysisResult after(Path workingDir, ExecutionReport report) throws IOException { return EurostagImpactAnalysis.this.after(workingDir, contingencies, report); } }); } private void fillMetrics(List<Contingency> contingencies, ExecutionReport report, Map<String, String> metrics) { float successPercent = 100f * (1 - ((float) report.getErrors().size()) / contingencies.size()); metrics.put("successPercent", Float.toString(successPercent)); EurostagUtil.putBadExitCode(report, metrics); } }