// SPDX-FileCopyrightText: 2020 Roberto Posenato // // SPDX-License-Identifier: LGPL-3.0-or-later package it.univr.di.cstnu.util; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; import it.univr.di.cstnu.algorithms.AbstractCSTN.CSTNCheckStatus; import it.univr.di.cstnu.algorithms.AbstractCSTN.DCSemantics; import it.univr.di.cstnu.algorithms.*; import it.univr.di.cstnu.algorithms.CSTNU.CSTNUCheckStatus; import it.univr.di.cstnu.algorithms.STN.STNCheckStatus; import it.univr.di.cstnu.algorithms.STNU.CheckAlgorithm; import it.univr.di.cstnu.algorithms.STNU.STNUCheckStatus; import it.univr.di.cstnu.graph.*; import it.univr.di.cstnu.graph.TNGraph.NetworkType; import it.univr.di.labeledvalue.Constants; import it.univr.di.labeledvalue.Label; import net.openhft.affinity.AffinityStrategies; import net.openhft.affinity.AffinityThreadFactory; 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.Locale; import java.util.concurrent.*; import java.util.logging.Level; import java.util.logging.Logger; /** * Simple class to determine the average execution time (and std dev) of the (C)STN(U) DC checking algorithm on a given * set of (C)STN(U)s. * * @author posenato * @version $Rev: 732 $ */ @SuppressWarnings({"NonAsciiCharacters", "AutoBoxing"}) @SuppressFBWarnings(value = "STCAL", justification = "It is not relevant here!") public class Checker { /** * CSV separator */ static final String CSVSep = ";\t"; /** * Utility internal class to store #contingent, #nodes, and #propositions and allows the ordering among objects of * this class. * * @author posenato */ public static class GlobalStatisticKey implements Comparable { /** * # contingents */ protected final int contingents; /** * # of nodes */ protected final int nodes; /** * # of propositions */ protected final int propositions; /** * default constructor * * @param inputNodes #nodes * @param inputPropositions #prop * @param inputContingents # cont */ public GlobalStatisticKey(final int inputNodes, final int inputPropositions, final int inputContingents) { nodes = inputNodes; propositions = inputPropositions; contingents = inputContingents; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof GlobalStatisticKey o1)) { return false; } return o1.compareTo(this) == 0; } /** * 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 proposition than the * parameter 'o'; a positive value in the opposite case, 0 when all three values are equals to the * corresponding values in 'o'. */ @SuppressWarnings("SubtractionInCompareTo") @Override public int compareTo(@Nonnull GlobalStatisticKey o) { final long d = (long) nodes - o.nodes; if (d == 0) { final int d1 = contingents - o.contingents; if (d1 == 0) { return propositions - o.propositions; } return d1; } return (int) d; } /** * @return #cont */ public int getContingent() { return contingents; } /** * @return #nodes */ public int getNodes() { return nodes; } /** * @return # prop */ public int getPropositions() { return propositions; } @Override public int hashCode() { return (contingents + propositions * 31) * 31 + nodes; } } /** * */ static final String GLOBAL_HEADER_ROW = "%d" + CSVSep + "%d" + CSVSep + "%d" + CSVSep + "%d" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%E" + CSVSep + "%n"; /** * class logger */ static final Logger LOG = Logger.getLogger(Checker.class.getName()); /** * Output file header */ static final String OUTPUT_HEADER = "fileName" + CSVSep + "#nodes" + CSVSep + "#edges" + CSVSep + "#propositions" + CSVSep + "#ctg" + CSVSep + "#minEdgeValue" + CSVSep + "#maxEdgeValue" + CSVSep + "avgTime[s]" + CSVSep + "std.dev.[s]" + CSVSep + "#ruleExecuted/cycles" + CSVSep + "DC"; /** * Header */ static final String OUTPUT_HEADER_STNU = OUTPUT_HEADER + CSVSep + "rateAddedEdges" + CSVSep + "rateWaitAndOrdinaryEdge" + CSVSep + "negativeEdgeFromContingent" + CSVSep; /** * CSTN header */ static final String OUTPUT_HEADER_CSTN = OUTPUT_HEADER + CSVSep + "#R0" + CSVSep + "#R3" + CSVSep + "#LP" + CSVSep + "#PotentialUpdate" + CSVSep; /** * CSTNU header */ static final String OUTPUT_HEADER_CSTNU = OUTPUT_HEADER_CSTN + "#LUC+FLUC+LCUC"// upperCaseRuleCalls + CSVSep + "#LLC"// lowerCaseRuleCalls + CSVSep + "#LCC"// crossCaseRuleCalls + CSVSep + "#LLR"// letterRemovalRuleCalls + CSVSep; /** * CSTNPSU header */ static final String OUTPUT_HEADER_CSTNPSU = OUTPUT_HEADER_CSTN + "PrototypalTime[s]" + CSVSep + "PrototypalTimeStdDev[s]" + CSVSep + "Prototypal" + CSVSep; /** * */ static final String OUTPUT_ROW_GRAPH = "%s" + CSVSep + "%d" + CSVSep + "%d" + CSVSep + "%d" + CSVSep + "%d" + CSVSep + "%d" + CSVSep + "%d" + CSVSep; /** * */ static final String OUTPUT_ROW_TIME = "%E"// average time + CSVSep + "%E"// std dev + CSVSep + "%d"// #ruleExecuted or cycles + CSVSep + "%s"// true of false for DC + CSVSep; /** * */ static final String OUTPUT_ROW_TIME_CSTN = "%d"// r0 + CSVSep + "%d"// r3 + CSVSep + "%d"// LNC + CSVSep + "%d"// PotentialUpdate + CSVSep; /** * For CSTNPSU statistics */ static final String OUTPUT_ROW_TIME_STATS_CSTNPSU = "%E"// PrototypalTime[s] + CSVSep + "%s" //std dev of prototypal time + CSVSep + "%s"// Prototypal + CSVSep; /** * */ static final String OUTPUT_ROW_TIME_STATS_CSTNU = "%d"// upperCaseRuleCalls + CSVSep + "%d"// lowerCaseRuleCalls + CSVSep + "%d"// crossCaseRuleCalls + CSVSep + "%d"// letterRemovalRuleCalls + CSVSep; /** * */ static final String OUTPUT_ROW_TIME_STNU = "%E"// added edges + CSVSep + "%E" // waitAndOrdEdgeRate + CSVSep + "%E" // negativeOutgoingFromC + CSVSep; /** * Version */ // static final String VERSIONandDATE = "1.0, March, 22 2015"; // static final String VERSIONandDATE = "1.1, November, 18 2015"; // static final String VERSIONandDATE = "1.2, October, 10 2017"; // static final String VERSIONandDATE = "1.3, October, 16 2017";// executor code cleaned // static final String VERSIONandDATE = "1.4, November, 09 2017";// code cleaned // static final String VERSIONandDATE = "2.0, November, 13 2017";// Multi-thread version. // static final String VERSIONandDATE = "2.1, November, 14 2017";// Multi-thread version. Fixed a slip! // static final String VERSIONandDATE = "2.2, November, 15 2017";// Added the possibility to test // CSTNEpsilonWONodeLabels and CSTN2CSTN0 // static final String VERSIONandDATE = "2.23, November, 30 2017";// Improved the print of statistics file: std dev is print only when # checks > 1 // static final String VERSIONandDATE = "2.24, December, 04 2017";// Added CSTNEpsilon3R // static final String VERSIONandDATE = "2.25, January, 17 2018";// Improved print of statistics. // static final String VERSIONandDATE = "2.26, December, 18 2018";// Improved print of statistics adding the total number of applied rules // static final String VERSIONandDATE = "2.27, June, 9 2019";// Refactoring Edge // static final String VERSIONandDATE = "2.5, November, 09 2019";// Removed all potential counters // static final String VERSIONandDATE = "3, June, 29 2020";// Add check for STN and STNU // static final String VERSIONandDATE = "3.1, July, 28 2020";// Refined stats for STNU // static final String VERSIONandDATE = "3.2, January, 13 2021";// Fixed file encoding // static final String VERSIONandDATE = "3.5, January, 13 2022"; static final String VERSIONandDATE = "3.6, January, 13 2022"; // Cleaned after refactoring CSTNUEdge /** * Date formatter */ private final static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); /** * */ EdgeSupplier currentEdgeFactory; // added STNU statistics about waitAndOrdinaryEdge and negativeEdgeFromContingent /** * Class for representing edge . */ Class currentEdgeImplClass; /** * Allows to check the execution time of DC checking algorithm giving a set of instances. The set of instances are * checked in parallel if the machine is a multi-cpus one.
Moreover, this method tries to exploit thread affinity if kernel allows it.
So, if it is * possible to reserve some CPU modifying the kernel as explained in thread affinity page. it is possible to run the * parallel thread in the better conditions. * * @param args an array of {@link java.lang.String} objects. */ @SuppressWarnings("null") public static void main(String[] args) { LOG.finest("Checker " + VERSIONandDATE + "\nStart..."); System.out.println("Checker " + VERSIONandDATE + "\n" + getNow() + ": Start of execution."); final Checker tester = new Checker(); if (!tester.manageParameters(args)) { return; } LOG.finest("Parameters ok!"); if (tester.versionReq) { return; } // All parameters are set /* * AffinityLock allows to lock a CPU to a thread. * It seems that allows better performance when a CPU-bound task has to be executed! * To work, it requires to reserve some CPUs. * In our server I modified /boot/grub/grub.cfg adding "isolcpus=4,5,6,7,8,9,10,11" to the line that boot the kernel to reserve 8 CPUs. * Such CPU have (socketId-coreId): 4(0-4), 5(0-5), 6(1-0), 7(1-1), 8(1-2), 9(1-3), 10(1-4), 11(1-5). * Then I reboot the server. * This class has to be started as normal (no using taskset!) * I don't modify in /etc/default/grub and, then, update-grub because it changes a lot of things. * **NOTE** * After some simulations on AMD Opteron™ 4334, I discovered that: * 0) The best performance is obtained checking one file at time! * 1) It doesn't worth to run more than 2 processor in parallel because this kind of app does not allow to scale. * For each added process, the performance lowers about 10%. * 2) Running two processes in the two different sockets lowers the performance about the 20%! * It is better to run the two process on the same socket. * 3) Therefore, I modified /boot/grub/grub.cfg setting "isolcpus=8,9,10,11" */ final int nCPUs = tester.nCPUs;// Runtime.getRuntime().availableProcessors(); // Logging stuff for learning Affinity behaviour. // System.out.println("Base CPU affinity mask: " + AffinityLock.BASE_AFFINITY); // System.out.println("Reserved CPU affinity mask: " + AffinityLock.RESERVED_AFFINITY); // System.out.println("Current CPU affinity: " + Affinity.getCpu()); // CpuLayout cpuLayout = AffinityLock.cpuLayout(); // System.out.println("CPU Layout: " + cpuLayout.toString()); // for (int k = 11; k > 3; k--) { // System.out.println("Cpu " + k + "\nSocket: " + cpuLayout.socketId(k) + ". Core:" + cpuLayout.coreId(k)); // } /* * check all files in parallel. */ /* * 1st method using streams (parallelStream) * Very nice, but it is affected by a known problem with streams: * the use of default ForkJoinPool in the implementation of parallel() makes possible that * a heavy task can block following tasks. */ // tester.inputCSTNFile.parallelStream().forEach(file -> cstnWorker(tester, file, executor, edgeFactory)); /* * 2nd method using Callable. * A newFixedThreadPool executor create nProcessor threads and pipeline all process associated to file to such pool. * There is no problem if one thread requires a lot of time. * Final synchronization is obtained requesting .get from Callable. * AffinityThreadFactory allows to lock a thread in one core for all the time (less overhead) */ final ExecutorService cstnExecutor = (nCPUs > 0) ? Executors.newFixedThreadPool(nCPUs, new AffinityThreadFactory("cstnWorker", AffinityStrategies.DIFFERENT_CORE)) : null; /* * To collect statistics w.r.t. the dimension of networks */ final Object2ObjectMap groupExecutionTimeStatisticsInSec = new Object2ObjectAVLTreeMap<>(); final Object2ObjectMap groupPrototypalExecutionTimeStatisticsInSec = new Object2ObjectAVLTreeMap<>(); final Object2ObjectMap groupRuleExecutionStatistics = new Object2ObjectAVLTreeMap<>(); final Object2ObjectMap groupAddedEdgeStatistics = new Object2ObjectAVLTreeMap<>(); final Object2ObjectMap groupWaitAndOrdinaryEdgeStatistics = new Object2ObjectAVLTreeMap<>(); final Object2ObjectMap groupNegativeFromContingentEdgeStatistics = new Object2ObjectAVLTreeMap<>(); System.out.println(getNow() + ": #Processors for computation: " + nCPUs); System.out.println(getNow() + ": Instances to check are " + tester.networkType + " instances."); final RunMeter runMeter = new RunMeter(System.currentTimeMillis(), tester.instances.size(), 0); runMeter.printProgress(0); final List> future = new ArrayList<>(8); tester.output.println("*".repeat(79)); tester.output.println("* Trial date: " + getNow()); tester.output.println("*".repeat(79)); tester.output.println(tester.getHeader()); tester.output.flush(); int nTaskSuccessfullyFinished = 0; for (final File file : tester.instances) { if (nCPUs > 0) { future.add(cstnExecutor.submit( () -> tester.worker(file, runMeter, groupExecutionTimeStatisticsInSec, groupRuleExecutionStatistics, groupAddedEdgeStatistics, groupWaitAndOrdinaryEdgeStatistics, groupNegativeFromContingentEdgeStatistics, groupPrototypalExecutionTimeStatisticsInSec))); } else { if (tester.worker(file, runMeter, groupExecutionTimeStatisticsInSec, groupRuleExecutionStatistics, groupAddedEdgeStatistics, groupWaitAndOrdinaryEdgeStatistics, groupNegativeFromContingentEdgeStatistics, groupPrototypalExecutionTimeStatisticsInSec)) { nTaskSuccessfullyFinished++; } } } if (nCPUs > 0) { // System.out.println(getNow() + ": #Tasks queued: " + future.size()); // wait all tasks have been finished and count! for (final Future f : future) { try { if (f.get()) { nTaskSuccessfullyFinished++; } } catch (Exception ex) { System.out.println("\nA problem occurred during a check: " + ex.getMessage() + ". File ignored."); } finally { if (!f.isDone()) { LOG.warning("It is necessary to cancel the task before continuing."); f.cancel(true); } } } } 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); for (final it.unimi.dsi.fastutil.objects.Object2ObjectMap.Entry entry : groupExecutionTimeStatisticsInSec.object2ObjectEntrySet()) { final GlobalStatisticKey globalStatisticsKey = entry.getKey(); tester.output.printf(GLOBAL_HEADER_ROW, entry.getValue().getN(), globalStatisticsKey.getNodes(), globalStatisticsKey.getContingent(), globalStatisticsKey.getPropositions(), entry.getValue().getMean(), entry.getValue().getStandardDeviation(), groupRuleExecutionStatistics.get(globalStatisticsKey).getMean(), groupRuleExecutionStatistics.get(globalStatisticsKey).getStandardDeviation(), groupAddedEdgeStatistics.get(globalStatisticsKey).getMean(), groupAddedEdgeStatistics.get(globalStatisticsKey).getStandardDeviation(), groupWaitAndOrdinaryEdgeStatistics.get(globalStatisticsKey).getMean(), groupWaitAndOrdinaryEdgeStatistics.get(globalStatisticsKey).getStandardDeviation(), groupNegativeFromContingentEdgeStatistics.get(globalStatisticsKey).getMean(), groupNegativeFromContingentEdgeStatistics.get(globalStatisticsKey).getStandardDeviation(), groupPrototypalExecutionTimeStatisticsInSec.get(globalStatisticsKey).getMean(), groupPrototypalExecutionTimeStatisticsInSec.get(globalStatisticsKey).getStandardDeviation()); } tester.output.printf("%n%n%n"); if (nCPUs > 0) { // executor shutdown! try { System.out.println(getNow() + ": Shutdown executors."); cstnExecutor.shutdown(); if (!cstnExecutor.awaitTermination(2, TimeUnit.SECONDS)) { System.err.println(getNow() + ": shutdown no yet finished..."); } } catch (InterruptedException e) { System.err.println(getNow() + ": Tasks interrupted."); } finally { if (!cstnExecutor.isTerminated()) { System.err.println(getNow() + ": remove non-finished tasks."); } cstnExecutor.shutdownNow(); System.out.println(getNow() + ": Shutdown finished.\nExecution finished."); } } tester.output.close(); } /** * Simple method to manage command line parameters using {@code args4j} library. * * @param args input arguments * @return false if a parameter is missing, or it is wrong. True if every parameter is given in a 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.Checker [options...] arguments..."); // print the list of available options parser.printUsage(System.err); System.err.println(); // print option sample. This is useful some time // 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 " + outputFile.getAbsolutePath() + " cannot be renamed!"; 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; } if (reactionTime <= 0) { System.err.println("Reaction time must be > 0"); return false; } final String suffix; //if you add a new type, update also getHeader(). if (networkType == NetworkType.CSTN) { currentEdgeImplClass = CSTNEdgePluggable.class; suffix = "cstn"; } else { if (networkType == NetworkType.CSTNU) { currentEdgeImplClass = CSTNUEdgePluggable.class; suffix = "cstnu"; } else { if (networkType == NetworkType.CSTNPSU) { currentEdgeImplClass = CSTNPSUEdgePluggable.class; suffix = "cstnpsu"; } else { if (networkType == NetworkType.STNU) { currentEdgeImplClass = STNUEdgeInt.class; suffix = "stnu"; } else { if (networkType == NetworkType.STN) { currentEdgeImplClass = STNEdgeInt.class; suffix = "stn"; } else { final String msg = "Type of network not managed by current version of this class. Game over :-/"; throw new IllegalArgumentException(msg); } } } } } currentEdgeFactory = new EdgeSupplier<>(currentEdgeImplClass); // LOG.finest("File number: " + this.fileNameInput.length); // LOG.finest("File names: " + Arrays.deepToString(this.fileNameInput)); instances = new ArrayList<>(inputFiles.length); for (final String fileName : inputFiles) { final File file = new File(fileName); if (!file.exists()) { System.err.println("File " + fileName + " does not exit."); parser.printUsage(System.err); System.err.println(); return false; } if (!file.getName().endsWith(suffix)) { System.err.println( "File " + fileName + " has not the right suffix associated to the suffix of the given network type (right suffix: " + suffix + "). Game over :-/"); parser.printUsage(System.err); System.err.println(); return false; } instances.add(file); } return true; } /** * Loads the file and execute all the actions (specified as instance parameter) on the network represented by the * file. * * @param type of edge * @param file input file * @param runState current state * @param globalExecutionTimeStatisticsInSecMap global statistics * @param globalRuleExecutionStatisticsMap another global statistics * @param globalAddedEdgeStatisticsMap only for STNU * @param globalPrototypalTimeStatisticsInSecMap only for CSTNPSU * @return true if required task ends successfully, false otherwise. */ @SuppressWarnings({"unchecked", "null"}) private boolean worker( File file, RunMeter runState, Object2ObjectMap globalExecutionTimeStatisticsInSecMap, Object2ObjectMap globalRuleExecutionStatisticsMap, Object2ObjectMap globalAddedEdgeStatisticsMap, Object2ObjectMap globalWaitAndOrdEdgeStatisticsMap, Object2ObjectMap globalNegativeFromContingentStatisticsMap, Object2ObjectMap globalPrototypalTimeStatisticsInSecMap ) { // System.out.println("Analyzing file " + file.getName() + "..."); if (LOG.isLoggable(Level.FINER)) { LOG.finer("Loading " + file.getName() + "..."); } final TNGraphMLReader graphMLReader = new TNGraphMLReader<>(); final TNGraph graphToCheck; try { graphToCheck = graphMLReader.readGraph(file, (Class) 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!"); /* * ************************************************* * Possible required modifications of the structure. * ************************************************* */ if (cuttingEdgeFactor > 1 || removeValue != Constants.INT_NULL) { if (cuttingEdgeFactor > 1) { LOG.info("Cutting all edge values by a factor " + cuttingEdgeFactor + "..."); for (final Edge e : graphToCheck.getEdges()) { if (e.isSTNEdge()) { ((STNEdge) e).setValue(((STNEdge) e).getValue() / cuttingEdgeFactor); } if (isCSTN() || isCSTNU()) { final CSTNEdge eNew = (CSTNEdge) currentEdgeFactory.get(e); for (final Entry