// 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.Object2IntMap; import it.univr.di.cstnu.algorithms.CSTNU; import it.univr.di.cstnu.algorithms.WellDefinitionException; import it.univr.di.cstnu.graph.*; import it.univr.di.labeledvalue.ALabel; import it.univr.di.labeledvalue.Label; 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.IOException; import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * Reads CSTNU instances and converts them into CSTNPSU (==FTNU) instances, transforming each contingent link into a guarded one. *

* The transformation of a contingent link consists of adding two ordinary constraints between the two nodes of the contingent link with bounds derived from the contingent range. *

*

* If the contingent link is represented by the lower and upper values plus the lower and upper ordinary constraints, this class modifies the ordinary constraints, reducing/increasing them to make a guarded range with the required reduction/increment. *

* * @author posenato * @version $Rev: 733 $ */ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "STCAL", justification = "It is not relevant here!") @SuppressWarnings("FieldMayBeFinal") public final class CSTNU2CSTNPSU { /** * Version of the class */ static public final String VERSIONandDATE = "Version 1.0 - May, 27 2023"; /** * logger */ static final Logger LOG = Logger.getLogger(CSTNU2CSTNPSU.class.getName()); /** * Date formatter */ private final static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); /** * Allows the modification of a set of input instances. *

* Instances are processed sequentially. The removed {@code --nCPUs} option only ran separate * files concurrently, never one conversion in parallel; the memory-bound jobs contended for * bandwidth, caches, garbage collection, and NUMA memory without a throughput benefit. CPU * affinity and standard Java pools preserve that contention. * * @param args an array of {@link String} objects. */ @SuppressWarnings("null") public static void main(final String[] args) { LOG.finest("CSTNU2CSTNPSU " + VERSIONandDATE + "\nStart..."); System.out.println("CSTNU2CSTNPSU " + VERSIONandDATE + "\n" + "\nSPDX-License-Identifier: LGPL-3.0-or-later, Roberto Posenato.\n" + getNow() + ": Start of execution."); final CSTNU2CSTNPSU converter = new CSTNU2CSTNPSU(); if (!converter.manageParameters(args)) { return; } LOG.finest("Parameters ok!"); if (converter.versionReq) { return; } // All parameters are set System.out.println(getNow() + ": Processing instances sequentially."); System.out.println(getNow() + ": Instances to check are STNU instances."); final RunMeter runMeter = new RunMeter(System.currentTimeMillis(), converter.instances.size(), 0); runMeter.printProgress(0); int nTaskSuccessfullyFinished = 0; for (final File file : converter.instances) { if (converter.worker(file, runMeter)) { nTaskSuccessfullyFinished++; } } final String msg = "Number of instances processed successfully over total: " + nTaskSuccessfullyFinished + "/" + converter.instances.size() + "."; LOG.info(msg); System.out.println("\n" + getNow() + ": " + msg); } /** * @return current time in {@link #dateFormatter} format */ private static String getNow() { return dateFormatter.format(new Date()); } /** * Class for representing an edge. */ Class currentEdgeImplClass; /** * Lower decrement */ @Option(depends = "--decrement", name = "-d", usage = "The percentage [0, 100) of the lower guard to remove for determining the lower bound. Lower bound will always be greater than zero and lower guard - 1 (if positive).") private int lowerDecrement = 20;// 10% less /** * Percentage of the lower guard (derived by lowerDecrement in @manageParameters) */ private double decrementFactor; /** * Upper decrement */ @Option(depends = "--increment", name = "-i", usage = "The percentage [0, 100) of the upper guard to add for determining the upper bound. Upper bound will always be greater than upper guard by at least one unit.") private int upperIncrement = 20;// 10% more /** * Percentage of the upper guard (derived by upperIncrement in @manageParameters) */ private double incrementFactor; /** * The input file names. Each file has to contain a CSTNU graph in GraphML format. */ @Argument(required = true, usage = "Input files. Each input file has to be a DC CSTNU graph in GraphML format. The DC property is fundamental!", metaVar = "CSTNU_file_names", handler = StringArrayOptionHandler.class) private String[] inputFiles; /** * */ private List instances; /** * To allow the use of different suffixes. */ @Option(name = "--suffix", usage = "The suffix to set for the converted file.") private String suffix = "cstnpsu"; /** * Software Version. */ @Option(name = "-v", aliases = "--version", usage = "Version") private boolean versionReq; /** * It cannot be used outside. */ private CSTNU2CSTNPSU() { } /** * Print a version of this class in System.out. */ public void printVersion() { // I use a non-static method to have a general method that prints the right name for each derived class. System.out.println(getClass().getName() + " " + CSTNU2CSTNPSU.VERSIONandDATE + ".\nAcademic and non-commercial use only.\n" + "Copyright © 2020, Roberto Posenato"); } /** * Given an instance, for each contingent link, it determines lower and upper bound values and adds them as ordinary constraints between the two nodes. * * @param instance input instance to modify * * @return the instance with each contingent link converted into a guarded one. */ @Nullable private DenseTCGraph contingent2guarded(DenseTCGraph instance) { if (instance == null) { return null; } final int nCtg = instance.getContingentNodeCount(); if (LOG.isLoggable(Level.FINER)) { LOG.finer("Converting " + nCtg + " contingent links to guarded ones"); } final CSTNU cstnu = new CSTNU((TemporalConstraintGraph) instance); cstnu.setContingentAlsoAsOrdinary(false); try { cstnu.initAndCheck(); } catch (WellDefinitionException e) { throw new RuntimeException("Trovato errore durante costruzione CSTNU: " + e.getMessage()); } // instance is already DenseTCGraph-backed, and the dense-only copy constructor below (which changes the // edge implementation class) has no generic equivalent: this is an intentional dense boundary. @SuppressWarnings("deprecation") final DenseTCGraph newInstance = new DenseTCGraph<>(cstnu.getG(), instance.getEdgeImplClass()); int lowerBound, upperBound; boolean added; final Set alreadyChecked = new HashSet<>(100); for (final CSTNUEdge e : newInstance.getEdges()) { final Edge.ConstraintType edgeType = e.getConstraintType(); if (edgeType == Edge.ConstraintType.internal || edgeType == Edge.ConstraintType.derived) { //This edge is not necessary newInstance.removeEdge(e.getName()); continue; } if (!e.isContingentEdge() || alreadyChecked.contains(e)) { continue; } final LabeledNode s = newInstance.getSource(e); final LabeledNode d = newInstance.getDest(e); assert s != null; assert d != null; final Label label = s.getLabel().conjunction(d.getLabel()); final CSTNUEdge invertedE = newInstance.findEdge(d, s); alreadyChecked.add(invertedE); if (d.isContingent()) { assert invertedE != null; assert d.getALabel() != null; upperBound = makeUpperBound(invertedE, d.getALabel()); lowerBound = makeLowerBound(e); added = e.mergeLabeledValue(label, upperBound); if (!added) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Upper bound " + upperBound + " not added to " + e); } } added = invertedE.mergeLabeledValue(label, lowerBound); if (!added) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Lower bound " + lowerBound + " not added to " + invertedE); } } } else { if (!s.isContingent()) { throw new IllegalStateException("For contingent link " + e + " no one of its end points is contingent."); } assert s.getALabel() != null; upperBound = makeUpperBound(e, s.getALabel()); assert invertedE != null; lowerBound = makeLowerBound(invertedE); added = e.mergeLabeledValue(label, lowerBound); if (!added) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Lower bound " + upperBound + " not added to " + e); } } added = invertedE.mergeLabeledValue(label, upperBound); if (!added) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Upper bound " + lowerBound + " not added to " + invertedE); } } } if (LOG.isLoggable(Level.FINER)) { LOG.finer("New contingent link pair:\n\t" + e + " and\n\t" + invertedE); } } return newInstance; } /** * @param fileName the input file name. * * @return new file name with the right suffixes. */ private String getNewFileName(String fileName) { if (fileName == null || !fileName.contains(".cstnu")) { throw new IllegalArgumentException("File name %s is not a CSTNU file name.".formatted(fileName)); } return fileName.replace(".cstnu", suffix); } /** * @param e an edge containing a lower-case value * * @return the lower bound (already negative value) determined using {@link #decrementFactor}*lower case value of e */ private int makeLowerBound(CSTNUEdge e) { //a contingent link has a single upper case value, e.getMinUpperCaseValue() returns it without need to //know return (int) (-e.getLowerCaseValue().getValue() * decrementFactor); } /** * @param e an edge containing an upper-case value * @param aLabel a-label of the contingent node * * @return the upper bound determined using {@link #incrementFactor}*upper case value of e */ private int makeUpperBound(@Nonnull CSTNUEdge e, @Nonnull ALabel aLabel) { //a contingent link has a single upper case value, e.getMinUpperCaseValue() returns it without need to know final Object2IntMap.Entry