// SPDX-FileCopyrightText: 2020 Roberto Posenato // // SPDX-License-Identifier: LGPL-3.0-or-later package it.univr.di.cstnu.util; import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectList; import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; import it.univr.di.cstnu.algorithms.STNU; import it.univr.di.cstnu.graph.STNUEdge; import it.univr.di.cstnu.graph.STNUEdgeInt; import it.univr.di.cstnu.graph.TNGraph; import it.univr.di.cstnu.graph.TNGraphMLReader; import it.univr.di.labeledvalue.Constants; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.kohsuke.args4j.spi.StringArrayOptionHandler; import org.xml.sax.SAXException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.CancellationException; import java.util.logging.Level; import java.util.logging.Logger; /** * Simple class to determine the average execution time (and standard dev) of the STN(U) dispatchability algorithms on a given set of STNUs. *

* The main idea is the following: *

* * @author posenato * @version $Rev: 732 $ */ public class DispatchabilityBenchmarkRunner { /** * Each instance of this class represents a set of global statistics. Each global statistics element represents a map * {@code (GlobalStatisticsKey, SummaryStatistics)}. Each SummaryStatistics element allows the determination of different statistics about all added items * to the element. */ static private class GlobalStatistics { Object2ObjectMap FD_STNUExecTimeInSec = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap FD_STNUNetworkEdges = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap maxMinEdges = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap minDispOfFD_STNUExecTimeInSec = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap minDispOfFD_STNUNetworkEdges = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap minDispOfMorris2014ExecTimeInSec = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap minDispOfMorris2014NetworkEdges = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap morris2014ExecTimeInSec = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap morris2014NetworkEdges = new Object2ObjectAVLTreeMap<>(); Object2ObjectMap networkEdges = new Object2ObjectAVLTreeMap<>(); } /** * Represents a key composed by {@code (nodes, contingents)}. *

* Implements a natural order based on increasing pair {@code (nodes, contingents)}. * * @author posenato */ static private class GlobalStatisticsKey implements Comparable { /** * # contingents */ final int contingents; /** * # of nodes */ final int nodes; /** * default constructor * * @param inputNodes #nodes * @param inputContingents # cont */ GlobalStatisticsKey(final int inputNodes, final int inputContingents) { nodes = inputNodes; contingents = inputContingents; } /** * The order is respect to #nodes,#contingents, and #propositions. * * @param o the object to be compared. * * @return negative if this has, in order, fewer nodes or fewer contingents or fewer propositions than the parameter 'o'; a positive value in the * opposite case, 0 when all three values are equal to the corresponding values in 'o'. */ @Override public int compareTo(@Nonnull GlobalStatisticsKey o) { final long d = (long) nodes - o.nodes; if (d == 0) { return contingents - o.contingents; } return (int) d; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof GlobalStatisticsKey o1)) { return false; } return o1.compareTo(this) == 0; } /** * @return #cont */ public int getContingent() { return contingents; } /** * @return #nodes */ public int getNodes() { return nodes; } @Override public int hashCode() { return (contingents) * 100000 + nodes; } } /** * CSV separator */ static final String CSVSep = ";\t"; /** * Global header */ static final String GLOBAL_HEADER = "%n%nGlobal statistics%n" // + "#networks" + CSVSep // + "#nodes" + CSVSep // + "#contingents" + CSVSep// + "#avgEdges" + CSVSep // + "stdDevEdges" + CSVSep // + "avgExeTimeMorris2014[s]" + CSVSep// + "stdDevExeTimeMorris2014[s]" + CSVSep// + "avgEdgesMorris2014" + CSVSep // + "stdDevEdgesMorris2014" + CSVSep// + "avnExeTimeMinDispESTNU_Morris[s]" + CSVSep// + "stdDevExeTimeMinDispESTNU_Morris[s]" + CSVSep// + "avgEdgesMinDispESTNU_Morris" + CSVSep // + "stdDevEdgesMinDispESTNU_Morris" + CSVSep// + "avgExeTimeFD_STNU[s]" + CSVSep // + "stdDevFD_STNU[s]" + CSVSep // + "avgEdgesFD_STNU" + CSVSep// + "stdDevEdgesFD_STNU" + CSVSep// + "avgExeTimeMinDispESTNU_FD_STNU[s]" + CSVSep// + "stdDevMinDispESTNU_FD_STNU[s]" + CSVSep // + "avgEdgesMinDispESTNU_FD_STNU" + CSVSep // + "stdDevEdgesMinDispESTNU_FD_STNU" + CSVSep// + "avgMaxMinEdges" // + "%n";// /** * */ static final String GLOBAL_HEADER_ROW = "%d" + CSVSep // + "%d" + CSVSep // + "%d" + CSVSep // + "%E" + CSVSep // avgEdges + "%E" + CSVSep // avgEdges std.dev. + "%E" + CSVSep // Morris2014 avgExe + "%E" + CSVSep // Morris2014 ExeTime std.dev. + "%E" + CSVSep // Morris2014 avgEdges + "%E" + CSVSep // Morris2014 Edges std.dev. + "%E" + CSVSep // minDispESTNU(Morris) avgExeTime + "%E" + CSVSep // minDispESTNU(Morris) ExeTime std.dev. + "%E" + CSVSep // minDispESTNU(Morris) avgEdges + "%E" + CSVSep // minDispESTNU(Morris) Edges std.dev. + "%E" + CSVSep // FD_STNU avgExeTime + "%E" + CSVSep // FD_STNU ExeTime std.dev. + "%E" + CSVSep // FD_STNU avgEdges + "%E" + CSVSep // FD_STNU Edges std.dev. + "%E" + CSVSep // minDispESTNU(FD_STNU) avgExeTime + "%E" + CSVSep // minDispESTNU(FD_STNU) ExeTime std.dev. + "%E" + CSVSep // minDispESTNU(FD_STNU) avgEdges + "%E" + CSVSep // minDispESTNU(FD_STNU) Edges std.dev. + "%E" // avgMaxMinEdges + "%n"; /** * class logger */ static final Logger LOG = Logger.getLogger(DispatchabilityBenchmarkRunner.class.getName()); /** * Output file header */ static final String OUTPUT_HEADER = "fileName" + CSVSep // + "#nodes" + CSVSep// + "#contingents" + CSVSep // + "#edges" + CSVSep // + "Morris2014 avgExeTime[s]" + CSVSep // + "std.dev.[s]" + CSVSep // + "Morris2014 #edges" + CSVSep// + "DC Morris2014" + CSVSep // + "minDispESTNU(Morris) avgExeTime[s]" + CSVSep // + "std.dev." + CSVSep // + "minDispESTNU(Morris) #edges" + CSVSep // + "maxMinEdgesMorris" + CSVSep // + "FD_STNU avgExeTime[s]" + CSVSep // + "std.dev.[s]" + CSVSep // + "FD_STNU #edges" + CSVSep// + "DC FD_STNU" + CSVSep // + "minDispESTNU(FD_STNU) avgExeTime[s]" + CSVSep// + "std.dev." + CSVSep // + "minDispESTNU(FD_STNU) #edges" + CSVSep// + "maxMinEdgesFD";// /** * OUTPUT_ROW is split in OUTPUT_ROW_GRAPH + OUTPUT_ROW_ALG_STATS + OUTPUT_ROW_ALG_STATS + OUTPUT_ROW_ALG_STATS */ static final String OUTPUT_ROW_ALG_STATS = "%E" + CSVSep // alg avgExe + "%E" + CSVSep // alg avgExe dev std + "%d" + CSVSep // alg #edges + "%s" + CSVSep // alg status ; /** * OUTPUT_ROW is split in OUTPUT_ROW_GRAPH + OUTPUT_ROW_ALG_STATS + OUTPUT_ROW_ALG_STATS + OUTPUT_ROW_ALG_STATS */ static final String OUTPUT_ROW_GRAPH = "%s" + CSVSep // + "%d" + CSVSep // + "%d" + CSVSep // + "%d" + CSVSep;// /** * Version */ // static final String VERSIONandDATE = "1.0, December, 01 2023"; static final String VERSIONandDATE = "2.0, February 07, 2025";// added the possibility to test 'minDispatchableESTNU4Diamonds' /** * Date formatter */ private final static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); /** * Allows checking the execution time of algorithms on a set of instances. * * @param args an array of {@link String} objects. */ public static void main(String[] args) { LOG.finest("Checker " + VERSIONandDATE + "\nStart..."); System.out.println("Checker " + VERSIONandDATE + "\n" + getNow() + ": Start of execution."); final DispatchabilityBenchmarkRunner tester = new DispatchabilityBenchmarkRunner(); if (!tester.manageParameters(args)) { return; } LOG.finest("Parameters ok!"); if (tester.versionReq) { return; } LOG.finest("minDispatchable4Diamonds: " + tester.minDispatchable4Diamonds); /* * To collect statistics w.r.t. the dimension of networks */ final GlobalStatistics globalStatistics = new GlobalStatistics(); final RunMeter runMeter = new RunMeter(System.currentTimeMillis(), tester.instances.size(), 0); runMeter.printProgress(0); tester.output.println("*".repeat(79)); tester.output.println("* Trial date: " + getNow()); tester.output.println("*".repeat(79)); if (tester.minDispatchable4Diamonds) { tester.output.println("\nThe following statistics labeled by `MinDispESTNU[.*]` are determined using the STNU.applyMinDispatchableESTNU(true) method, i.e., applyMinDispatchableESTNU optimized for STNUs having diamonds.\n"); tester.output.println("-".repeat(79)); } tester.output.println(OUTPUT_HEADER); tester.output.flush(); int nTaskSuccessfullyFinished = 0; for (final File file : tester.instances) { if (tester.worker(file, runMeter, globalStatistics)) { nTaskSuccessfullyFinished++; } } final String msg = "Number of instances processed successfully over total: " + nTaskSuccessfullyFinished + "/" + tester.instances.size() + "."; LOG.info(msg); System.out.println("\n" + getNow() + ": " + msg); tester.output.printf(GLOBAL_HEADER); // Use one of the elements in globalStatistics to extract all the possible globalStatisticsKeys for (final Object2ObjectMap.Entry entryNetworkEdges : globalStatistics.networkEdges.object2ObjectEntrySet()) { final GlobalStatisticsKey globalStatisticsKey = entryNetworkEdges.getKey(); tester.output.printf(GLOBAL_HEADER_ROW, Long.valueOf(entryNetworkEdges.getValue().getN()), Integer.valueOf(globalStatisticsKey.getNodes()), Integer.valueOf(globalStatisticsKey.getContingent()), Double.valueOf(entryNetworkEdges.getValue().getMean()), Double.valueOf(entryNetworkEdges.getValue().getStandardDeviation()), Double.valueOf(globalStatistics.morris2014ExecTimeInSec.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.morris2014ExecTimeInSec.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.morris2014NetworkEdges.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.morris2014NetworkEdges.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.minDispOfMorris2014ExecTimeInSec.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.minDispOfMorris2014ExecTimeInSec.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.minDispOfMorris2014NetworkEdges.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.minDispOfMorris2014NetworkEdges.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.FD_STNUExecTimeInSec.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.FD_STNUExecTimeInSec.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.FD_STNUNetworkEdges.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.FD_STNUNetworkEdges.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.minDispOfFD_STNUExecTimeInSec.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.minDispOfFD_STNUExecTimeInSec.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.minDispOfFD_STNUNetworkEdges.get(globalStatisticsKey).getMean()), Double.valueOf(globalStatistics.minDispOfFD_STNUNetworkEdges.get(globalStatisticsKey).getStandardDeviation()), Double.valueOf(globalStatistics.maxMinEdges.get(globalStatisticsKey).getMean())); } tester.output.printf("%n%n%n"); tester.output.close(); } /** * @return current time in {@link #dateFormatter} format */ private static String getNow() { return dateFormatter.format(new Date()); } /** * @param value value in nanoseconds * * @return the value in seconds */ private static double nanoSeconds2Seconds(double value) { return value * 1.0E-9; } /** * Class for representing an edge. */ private final Class currentEdgeImplClass = STNUEdgeInt.class; /** * Check a STNU instance using the Morris algorithm */ @Option(name = "--fd", usage = "Check a STNU instance using FD_STNU.") private boolean fd_stnu; /** * The input file names. Each file has to contain a CSTN graph in GraphML format. */ @Argument(required = true, usage = "Input files. Each input file has to be an STNU graph in GraphML format.", metaVar = "STNU_file_names", handler = StringArrayOptionHandler.class) private String[] inputFiles; /** * */ private List instances; /** * Determine which is the minimum dispatchable. */ @Option(name = "--minDisp4Diamonds", usage = "Execute the min dispatchable ESTNU suitable when the network contains many diamond structures.") private boolean minDispatchable4Diamonds; /** * Determine min dispatchable. */ @Option(name = "--minFD", usage = "Execute the min dispatchable ESTNU after FD_STNU check.") private boolean minDispatchableFD; /** * Determine min dispatchable. */ @Option(name = "--minMorris", usage = "Execute the min dispatchable ESTNU after Morris2014.") private boolean minDispatchableMorris; /** * Check a STNU instance using the Morris algorithm */ @Option(name = "--morris", usage = "Check a STNU instance using Morris2014Dispatchable.") private boolean morris2014; /** * Parameter for asking how many times to check the DC for each STNU. */ @Option(name = "--numRepetitionDCCheck", usage = "Number of times to re-execute DC checking") private int nDCRepetition = 30; /** * Parameter for reusing previously checked STNU instances in the minimization phase. */ @Option(name = "--reuseFDCheckedSTNU", depends = "--minFD", usage = "Suffix of the file representing the already dispatchable instance (made by FD_STNU) to use in the minimization phase. Such a parameter can be used when --minFD [--minDisp4Diamonds] is specified. Example: if the instance is named 'stnu000.stnu' and the dispatchable instance 'stnu000.FD_STNU-checked.stnu'; This parameter must be '.FD_STNU-checked.stnu' to use the saved instance 'stnu000.FD_STNU-checked.-minimized.stnu'") private String reuseFDCheckedSTNU; /** * Parameter for reusing previously checked STNU instances in the minimization phase. */ @Option(name = "--reuseMorrisCheckedSTNU", depends = "--minMorris", usage = "Suffix of the file representing the already dispatchable instance (made by Morris) to use in the minimization phase. Such a parameter can be used when --minMorris [--minDisp4Diamonds] is specified. Example: if the instance is named 'stnu000.stnu' and the dispatchable instance 'stnu000.Morris2014Dispatchable-checked.stnu'; This parameter must be '.Morris2014Dispatchable-checked.stnu' to use the saved instance 'stnu000.Morris2014Dispatchable-checked.-minimized.stnu'") private String reuseMorrisCheckedSTNU; /** * Output stream to outputFile */ private PrintStream output; /** * Output file where to write the determined experimental execution times in CSV format. */ @Option(name = "-o", aliases = "--output", usage = "Output to this file in CSV format. If the file is already present, data will be added.", metaVar = "outputFile") private File outputFile; /** * */ @Option(name = "--save", usage = "Save all checked instances.") private boolean save; /** * Parameter for asking timeout in sec. */ @Option(name = "--timeOut", usage = "Time in seconds.") private int timeOut = 1800; // 20 min /** * Software Version. */ @Option(name = "-v", aliases = "--version", usage = "Version") private boolean versionReq; /** * Checks the STNU using stnuCheckAlgorithm and updates it. Then, it adds statistics to gExecTimeInSec and gNetworkEdges. It returns the string representing the * ({@link #OUTPUT_ROW_ALG_STATS}) part to add to the row in the statistics file. */ private String dcCheckTester(@Nonnull STNU stnu, @Nonnull STNU.CheckAlgorithm stnuCheckAlgorithm, @Nonnull String rowToWrite, @Nonnull SummaryStatistics gExecTimeInSec, @Nonnull SummaryStatistics gNetworkEdges) { String msg; boolean checkInterrupted = false; final TNGraph graphToCheck; TNGraph g; graphToCheck = stnu.getG(); final String fileName = graphToCheck.getFileName().getName(); STNU.STNUCheckStatus status = stnu.getCheckStatus(); final SummaryStatistics localSummaryStat = new SummaryStatistics(); String error = ""; for (int j = 0; j < nDCRepetition && !checkInterrupted && !stnu.getCheckStatus().timeout; j++) { LOG.info("Test " + (j + 1) + "/" + nDCRepetition + " for STNU " + fileName); // It is necessary to reset the graph! g = new TNGraph<>(graphToCheck, currentEdgeImplClass); stnu.setG(g); status = stnu.getCheckStatus(); try { stnu.dynamicControllabilityCheck(stnuCheckAlgorithm); } catch (CancellationException ex) { msg = getNow() + ": Cancellation has occurred. " + graphToCheck.getFileName() + " STNU is ignored."; System.out.println(msg); LOG.severe(msg); checkInterrupted = true; status.consistency = false; continue; } catch (Exception e) { msg = getNow() + ": exception during DC check on " + graphToCheck.getFileName() + ". STNU is ignored.\nError details: " + e; System.out.println(msg); LOG.severe(msg); checkInterrupted = true; status.consistency = false; error = e.getMessage(); continue; } localSummaryStat.addValue(status.executionTimeNS); } // end for checking repetition for a single file if (status.timeout || checkInterrupted) { if (status.timeout) { msg = "\n\n" + getNow() + ": Timeout or interrupt occurred. " + graphToCheck.getFileName() + " STNU is ignored.\n"; System.out.println(msg); } status.finished = false; } else { status.executionTimeNS = (long) localSummaryStat.getMean(); status.stdDevExecutionTimeNS = (long) localSummaryStat.getStandardDeviation(); } final int nEdgesAfterChecking = stnu.getG().getEdgeCount(); // NOW DETERMINE STATISTICS if (!status.finished) { // time out or generic error rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.valueOf(nanoSeconds2Seconds(status.executionTimeNS)), Double.valueOf(nanoSeconds2Seconds(Double.NaN)), // Double.NaN because in the case of a Timeout, only one time is considered. Integer.valueOf(nEdgesAfterChecking), ((status.executionTimeNS != Constants.INT_NULL) ? "Timeout of " + timeOut + " seconds." : error)); gExecTimeInSec.addValue(timeOut); return rowToWrite; } final double localAvgInSec = nanoSeconds2Seconds(status.executionTimeNS); final double localStdDevInSec = nanoSeconds2Seconds(status.stdDevExecutionTimeNS); gExecTimeInSec.addValue(localAvgInSec); gNetworkEdges.addValue(nEdgesAfterChecking); LOG.info(fileName + " has been checked (algorithm ends in a stable state): " + status.finished); LOG.info(fileName + " is DC: " + status.consistency); LOG.info(fileName + " average checking time [s]: " + localAvgInSec); LOG.info(fileName + " std. deviation [s]: " + localStdDevInSec); rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.valueOf(localAvgInSec), Double.valueOf((nDCRepetition > 1) ? localStdDevInSec : Double.NaN), Integer.valueOf(nEdgesAfterChecking), (status.consistency) ? "TRUE" : "FALSE"); if (status.consistency && this.save) { // save the result final String outFileName = fileName.substring(0, fileName.length() - 4) + stnuCheckAlgorithm + "-checked.stnu"; stnu.setfOutput(new File(outFileName)); stnu.saveGraphToFile(); } return rowToWrite; } /** * Returns an STNU object filled with the given graph and timeOut set as the given parameter {@link #timeOut}. * * @param g input graph * * @return an STNU instance */ private STNU makeSTNUInstance(TNGraph g) { return new STNU(g, timeOut); } /** * Simple method to manage command line parameters using {@code args4j} library. * * @param args input arguments * * @return false if a parameter is missing or wrong. True if every parameter is given in the right format. */ private boolean manageParameters(String[] args) { final CmdLineParser parser = new CmdLineParser(this); try { parser.parseArgument(args); } catch (CmdLineException e) { //If there's a problem in the command line, you'll get this exception. This will report an error message. System.err.println(e.getMessage()); System.err.println("java -cp CSTNU-.jar -cp it.univr.di.cstnu.DispatchabilityBenchmarkRunner [options...] arguments..."); //Print the list of available options parser.printUsage(System.err); System.err.println(); // print option sample. This is useful sometimes // System.err.println("Example: java -jar Checker.jar" + parser.printExample(OptionHandlerFilter.REQUIRED) + // " ..."); return false; } if (versionReq) { System.out.print(getClass().getName() + " " + VERSIONandDATE + ". Academic and non-commercial use only.\n" + "Copyright © 2017-2025, Roberto Posenato"); return true; } if (outputFile != null) { if (outputFile.isDirectory()) { System.err.println("Output file is a directory."); parser.printUsage(System.err); System.err.println(); return false; } //'filename' has to end with .csv if (!outputFile.getName().endsWith(".csv")) { if (!outputFile.renameTo(new File(outputFile.getAbsolutePath() + ".csv"))) { final String m = "File %s cannot be renamed!".formatted(outputFile.getAbsolutePath()); LOG.severe(m); System.err.println(m); return false; } } try { output = new PrintStream(new FileOutputStream(outputFile, true), true, StandardCharsets.UTF_8); } catch (IOException e) { System.err.println("Output file cannot be created: " + e.getMessage()); parser.printUsage(System.err); System.err.println(); return false; } } else { output = System.out; } final String suffix = "stnu"; instances = new ArrayList<>(inputFiles.length); for (final String fileName : inputFiles) { final File file = new File(fileName); if (!file.exists()) { System.err.printf("File %s does not exit.%n", fileName); parser.printUsage(System.err); System.err.println(); return false; } if (!file.getName().endsWith(suffix)) { System.err.printf("File %s has not the right suffix associated with the suffix of the given network type (right suffix: %s).%n", fileName, suffix); parser.printUsage(System.err); System.err.println(); return false; } instances.add(file); } return true; } /** * Determines the minDispatchESTNU of stnu without modifying it. Then, it adds statistics to gExecTimeInSec and gNetworkEdges. It returns the string * representing the ({@link #OUTPUT_ROW_ALG_STATS}) part to add to the row in the statistics file. * * @param inputSTNU input DC checked and dispatchable STNU * @param minimizedGraph graph that will contain the minimized dispatchable STNU * @param isFD true if the inputSTNU was checked by the FD_STNU algorithm * @param minDispatchable4Diamonds true if the minDispatch algorithm suited for managing diamonds in the instance must be used. */ @SuppressWarnings("UnnecessaryCallToStringValueOf") private String minDispatcherTester(@Nonnull STNU inputSTNU, @Nonnull String rowToWrite, @Nonnull SummaryStatistics gExecTimeInSec, @Nonnull SummaryStatistics gNetworkEdges, @Nullable SummaryStatistics gMaxMinEdges, @Nonnull TNGraph minimizedGraph, boolean isFD, boolean minDispatchable4Diamonds) { String msg; boolean checkInterrupted = false; String fileName = inputSTNU.getG().getFileName().getName(); STNU stnuMinimized; if (this.reuseFDCheckedSTNU != null || this.reuseMorrisCheckedSTNU != null) { final TNGraph graphToCheck; final TNGraphMLReader graphMLReader = new TNGraphMLReader<>(); String checkedFileName = fileName.substring(0, fileName.length() - 5); if (!isFD && this.reuseMorrisCheckedSTNU != null) { checkedFileName += this.reuseMorrisCheckedSTNU; } if (isFD && this.reuseFDCheckedSTNU != null) { checkedFileName += this.reuseFDCheckedSTNU; } final File file = new File(checkedFileName); fileName = checkedFileName; try { graphToCheck = graphMLReader.readGraph(file, currentEdgeImplClass); } catch (IOException | ParserConfigurationException | SAXException e2) { msg = "File " + file.getName() + " cannot be found or parsed. Details: " + e2.getMessage() + ".\nIgnored."; LOG.warning(msg); System.err.println(msg); rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.NaN, Double.NaN, // Double.NaN because in the case of a Timeout, only one time is considered. 0, "STNU cannot be found or parsed. Ignored"); return rowToWrite; } inputSTNU = new STNU(graphToCheck); //We have to ensure that the network has already been checked. try { inputSTNU.initAndCheck(); } catch (Exception e) { msg = getNow() + ": " + file.getName() + " is not a not well-defined instance. Details:" + e.getMessage() + "\nIgnored."; System.err.println(msg); LOG.severe(msg); rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.NaN, Double.NaN, // Double.NaN because in the case of a Timeout, only one time is considered. 0, "STNU is not well defined. Ignored."); return rowToWrite; } final STNU.STNUCheckStatus status = inputSTNU.getCheckStatus(); status.consistency = true; status.setCheckAlgorithm((isFD) ? STNU.CheckAlgorithm.FD_STNU : STNU.CheckAlgorithm.Morris2014Dispatchable); } stnuMinimized = new STNU(inputSTNU); final SummaryStatistics localSummaryStat = new SummaryStatistics(); String error = ""; for (int j = 0; j < nDCRepetition && !checkInterrupted && !stnuMinimized.getCheckStatus().timeout; j++) { LOG.info("Test " + (j + 1) + "/" + nDCRepetition + " for STNU " + fileName); // It is necessary to reset the graph! if (j > 0) { stnuMinimized = new STNU(inputSTNU); } final boolean minimized; try { minimized = stnuMinimized.applyMinDispatchableESTNU(minDispatchable4Diamonds); } catch (CancellationException ex) { msg = getNow() + ": Cancellation has occurred. " + fileName + " STNU is ignored."; System.err.println(msg); LOG.severe(msg); checkInterrupted = true; continue; } catch (Exception e) { msg = getNow() + ": exception during minDispatch on " + fileName + ". STNU is ignored.\nError details: " + e; System.err.println(msg); LOG.severe(msg); checkInterrupted = true; error = e.getMessage(); continue; } if (!minimized) { checkInterrupted = true; } localSummaryStat.addValue(stnuMinimized.getCheckStatus().executionTimeNS); } // end for checking repetition for a single file if (stnuMinimized.getCheckStatus().timeout || checkInterrupted) { msg = "\n\n" + getNow() + ": Timeout or interrupt occurred. " + fileName + " STNU is ignored.\n"; System.err.println(msg); } else { msg = getNow() + ": done! "; LOG.info(msg); stnuMinimized.getCheckStatus().executionTimeNS = (long) localSummaryStat.getMean(); stnuMinimized.getCheckStatus().stdDevExecutionTimeNS = (long) localSummaryStat.getStandardDeviation(); } // NOW DETERMINE STATISTICS final int nEdgesAfterMinimization = stnuMinimized.getG().getEdgeCount(); if (!stnuMinimized.getCheckStatus().finished || checkInterrupted) { // time out or generic error rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.valueOf(nanoSeconds2Seconds(stnuMinimized.getCheckStatus().executionTimeNS)), Double.NaN, // Double.NaN because in the case of a Timeout, only one time is considered. Integer.valueOf(nEdgesAfterMinimization), ((stnuMinimized.getCheckStatus().executionTimeNS != Constants.INT_NULL) ? "Timeout of " + timeOut + " seconds." : error)); // gExecTimeInSec.addValue(timeOut); return rowToWrite; } final double localAvgInSec = nanoSeconds2Seconds(stnuMinimized.getCheckStatus().executionTimeNS); final double localStdDevInSec = nanoSeconds2Seconds(stnuMinimized.getCheckStatus().stdDevExecutionTimeNS); gExecTimeInSec.addValue(localAvgInSec); gNetworkEdges.addValue(nEdgesAfterMinimization); LOG.info("%s has been minimized.".formatted(fileName)); LOG.info("%s average execution time [s]: %s".formatted(fileName, localAvgInSec)); LOG.info("%s std. deviation [s]: %s".formatted(fileName, localStdDevInSec)); rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.valueOf(localAvgInSec), Double.valueOf((nDCRepetition > 1) ? localStdDevInSec : Double.NaN), Integer.valueOf(nEdgesAfterMinimization), String.valueOf(stnuMinimized.getCheckStatus().getMaxMinConstraint())); // here it is always minimized minimizedGraph.takeFrom(stnuMinimized.getGChecked()); if (gMaxMinEdges != null) { gMaxMinEdges.addValue(stnuMinimized.getCheckStatus().getMaxMinConstraint()); } if (this.save) { // save the result final String outFileName = fileName.substring(0, fileName.length() - 5) + ((this.minDispatchable4Diamonds) ? "-minimized4Diamonds.stnu" : "-minimized.stnu"); stnuMinimized.setfOutput(new File(outFileName)); stnuMinimized.saveGraphToFile(); } return rowToWrite; } /** * Loads the file and executes all the actions (specified as instance parameters) on the network represented by the file. * * @param file input file * @param runState current state * @param globalStatistics global statistics * * @return true if the required task ends successfully, false otherwise. */ private boolean worker(@Nonnull File file, @Nonnull RunMeter runState, @Nonnull GlobalStatistics globalStatistics) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Loading " + file.getName() + "..."); } final TNGraphMLReader graphMLReader = new TNGraphMLReader<>(); final TNGraph graphToCheck; try { graphToCheck = graphMLReader.readGraph(file, currentEdgeImplClass); } catch (IOException | ParserConfigurationException | SAXException e2) { final String msg = "File " + file.getName() + " cannot be parsed. Details: " + e2.getMessage() + ".\nIgnored."; LOG.warning(msg); System.out.println(msg); return false; } LOG.finer("...done!"); // NODES and EDGES in the original graph. DO NOT MOVE such a variable. final int nNodes = graphToCheck.getVertexCount(); final int nEdges = graphToCheck.getEdgeCount(); final STNU stnu; stnu = makeSTNUInstance(graphToCheck); try { stnu.initAndCheck(); } catch (Exception e) { final String msg = getNow() + ": " + file.getName() + " is not a not well-defined instance. Details:" + e.getMessage() + "\nIgnored."; System.out.println(msg); LOG.severe(msg); return false; } final STNU stnuCopy = new STNU(stnu);// for FD! // Only now, the contingent node number is significant final int nContingents = graphToCheck.getContingentNodeCount(); final GlobalStatisticsKey globalStatisticsKey = new GlobalStatisticsKey(nNodes, nContingents); SummaryStatistics gNetworkEdges = globalStatistics.networkEdges.get(globalStatisticsKey); if (gNetworkEdges == null) { gNetworkEdges = new SummaryStatistics(); globalStatistics.networkEdges.put(globalStatisticsKey, gNetworkEdges); } gNetworkEdges.addValue(nEdges); SummaryStatistics gMorris2014ExecTimeInSec = globalStatistics.morris2014ExecTimeInSec.get(globalStatisticsKey); if (gMorris2014ExecTimeInSec == null) { gMorris2014ExecTimeInSec = new SummaryStatistics(); globalStatistics.morris2014ExecTimeInSec.put(globalStatisticsKey, gMorris2014ExecTimeInSec); } SummaryStatistics gMorris2014NetworkEdges = globalStatistics.morris2014NetworkEdges.get(globalStatisticsKey); if (gMorris2014NetworkEdges == null) { gMorris2014NetworkEdges = new SummaryStatistics(); globalStatistics.morris2014NetworkEdges.put(globalStatisticsKey, gMorris2014NetworkEdges); } SummaryStatistics gMinDispOfMorris2014ExecTimeInSec = globalStatistics.minDispOfMorris2014ExecTimeInSec.get(globalStatisticsKey); if (gMinDispOfMorris2014ExecTimeInSec == null) { gMinDispOfMorris2014ExecTimeInSec = new SummaryStatistics(); globalStatistics.minDispOfMorris2014ExecTimeInSec.put(globalStatisticsKey, gMinDispOfMorris2014ExecTimeInSec); } SummaryStatistics gMinDispOfMorris2014NetworkEdges = globalStatistics.minDispOfMorris2014NetworkEdges.get(globalStatisticsKey); if (gMinDispOfMorris2014NetworkEdges == null) { gMinDispOfMorris2014NetworkEdges = new SummaryStatistics(); globalStatistics.minDispOfMorris2014NetworkEdges.put(globalStatisticsKey, gMinDispOfMorris2014NetworkEdges); } SummaryStatistics gFD_STNUExecTimeInSec = globalStatistics.FD_STNUExecTimeInSec.get(globalStatisticsKey); if (gFD_STNUExecTimeInSec == null) { gFD_STNUExecTimeInSec = new SummaryStatistics(); globalStatistics.FD_STNUExecTimeInSec.put(globalStatisticsKey, gFD_STNUExecTimeInSec); } SummaryStatistics gFD_STNUNetworkEdges = globalStatistics.FD_STNUNetworkEdges.get(globalStatisticsKey); if (gFD_STNUNetworkEdges == null) { gFD_STNUNetworkEdges = new SummaryStatistics(); globalStatistics.FD_STNUNetworkEdges.put(globalStatisticsKey, gFD_STNUNetworkEdges); } SummaryStatistics gMinDispOfFD_STNUExecTimeInSec = globalStatistics.minDispOfFD_STNUExecTimeInSec.get(globalStatisticsKey); if (gMinDispOfFD_STNUExecTimeInSec == null) { gMinDispOfFD_STNUExecTimeInSec = new SummaryStatistics(); globalStatistics.minDispOfFD_STNUExecTimeInSec.put(globalStatisticsKey, gMinDispOfFD_STNUExecTimeInSec); } SummaryStatistics gMinDispOfFD_STNUNetworkEdges = globalStatistics.minDispOfFD_STNUNetworkEdges.get(globalStatisticsKey); if (gMinDispOfFD_STNUNetworkEdges == null) { gMinDispOfFD_STNUNetworkEdges = new SummaryStatistics(); globalStatistics.minDispOfFD_STNUNetworkEdges.put(globalStatisticsKey, gMinDispOfFD_STNUNetworkEdges); } SummaryStatistics gMaxMinEdges = globalStatistics.maxMinEdges.get(globalStatisticsKey); if (gMaxMinEdges == null) { gMaxMinEdges = new SummaryStatistics(); globalStatistics.maxMinEdges.put(globalStatisticsKey, gMaxMinEdges); } String rowToWrite = String.format(OUTPUT_ROW_GRAPH, file.getName(), Integer.valueOf(nNodes), Integer.valueOf(nContingents), Integer.valueOf(nEdges)); if (this.morris2014) { LOG.info(getNow() + ": Morris2014Dispatchable: start."); rowToWrite = dcCheckTester(stnu, STNU.CheckAlgorithm.Morris2014Dispatchable, rowToWrite, gMorris2014ExecTimeInSec, gMorris2014NetworkEdges); } else { rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.NaN, Double.NaN, 0, "NOT EXECUTED"); } final TNGraph morrisMinimized = new TNGraph<>("morrisMinimized", currentEdgeImplClass); if (this.minDispatchableMorris) { LOG.info(getNow() + ": MinDispatchable of Morris2014Dispatchable: start."); rowToWrite = minDispatcherTester(stnu, rowToWrite, gMinDispOfMorris2014ExecTimeInSec, gMinDispOfMorris2014NetworkEdges, null, morrisMinimized, false, this.minDispatchable4Diamonds); } else { rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.NaN, Double.NaN, 0, "NOT EXECUTED"); } // use the second stnu to check with FD if (this.fd_stnu) { LOG.info(getNow() + ": FD_STNU: start."); rowToWrite = dcCheckTester(stnuCopy, STNU.CheckAlgorithm.FD_STNU, rowToWrite, gFD_STNUExecTimeInSec, gFD_STNUNetworkEdges); } else { rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.NaN, Double.NaN, 0, "NOT EXECUTED"); } final TNGraph ftMinimized = new TNGraph<>("fdMinimized", currentEdgeImplClass); if (this.minDispatchableFD) { LOG.info(getNow() + ": MinDispatchable of FD_STNU: start."); rowToWrite = minDispatcherTester(stnuCopy, rowToWrite, gMinDispOfFD_STNUExecTimeInSec, gMinDispOfFD_STNUNetworkEdges, gMaxMinEdges, ftMinimized, true, this.minDispatchable4Diamonds); } else { rowToWrite += String.format(OUTPUT_ROW_ALG_STATS, Double.NaN, Double.NaN, 0, "NOT EXECUTED"); } if (this.minDispatchableMorris && this.minDispatchableFD) { final ObjectList> differentEdges = morrisMinimized.differentEdgesOf(ftMinimized); if (differentEdges.size() > 0) { System.err.println("Different edges are: " + differentEdges.size() + ":\n" + differentEdges); } } output.println(rowToWrite); output.flush(); runState.printProgress(); return true; } }