// SPDX-FileCopyrightText: 2020 Roberto Posenato // // SPDX-License-Identifier: LGPL-3.0-or-later /* * Bear in mind that ObjectRBTreeSet is usually a little bit faster than ObjectAVLTreeSet on a medium-sized set. * In very large set sizes, ObjectAVLTreeSet shines! * On a small set, ArraySet is more efficient in time and space. */ package it.univr.di.cstnu.graph; import edu.uci.ics.jung.graph.AbstractTypedGraph; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import it.unimi.dsi.fastutil.chars.Char2ObjectArrayMap; import it.unimi.dsi.fastutil.chars.Char2ObjectMap; import it.unimi.dsi.fastutil.chars.CharSet; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.*; import it.univr.di.Debug; import it.univr.di.cstnu.graph.Edge.ConstraintType; import it.univr.di.labeledvalue.*; import org.apache.commons.lang3.tuple.ImmutableTriple; import org.apache.commons.lang3.tuple.Triple; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.Serial; import java.lang.reflect.Array; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * Represents (dense) temporal network graphs where nodes are {@link it.univr.di.cstnu.graph.LabeledNode} and edges are (an extension of) * {@link it.univr.di.cstnu.graph.Edge}. This class implements the interface {@link edu.uci.ics.jung.graph.DirectedGraph} in order to allow the representation of Graph by the JUNG library. * * @param type of edge * * @author posenato * @version $Rev$ */ @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "I know what I'm doing") public class TNGraph extends AbstractTypedGraph implements DirectedGraph, PropertyChangeListener { /** * Types of networks that this class can represent. The type is determined in the constructor, checking the implemented interface by the given edge class. *
* On 2019-06-13, the considered interfaces are: * *
	 * Edge Interface      Network Type
	 * STNEdge             STN
	 * STNUEdge            STNU
	 * CSTNEdge            CSTN
	 * CSTNUEdge           CSTNU/PCSTNU
	 * CSTNPSUEdge		   CSTPSU
	 * 
* * This is not a correct design choice, but it allows one to write classes that can use TNGraph<Edge> * objects and make only different operations according to the type of the network. * * @author posenato */ public enum NetworkType { /** * Conditional STN */ CSTN, /** * FTNU === CSTNPSU Conditional STN partially shrinkable uncertainty */ CSTNPSU, /** * Conditional STNU */ CSTNU, /** * Parameterized CSTNU */ PCSTNU, /** * Simple Temporal Network */ STN, /** * Simple Temporal Network with Uncertainty */ STNU, /** * STNU with oracles */ OSTNU, /** * Probabilistic simple temporal network. This type must be set manually (it cannot be inferred from the kind of edges). */ PSTN } /** * Unmodifiable version of this graph. * * @param the type of edges * * @author posenato */ public static class UnmodifiableTNGraph extends TNGraph { /** * */ @Serial private static final long serialVersionUID = 2L; /** * Default constructor * * @param network to make unmodifiable */ public UnmodifiableTNGraph(final TNGraph network) { super.takeFrom(network); } /** * Unsupported. */ @Override public void addChildToObserverNode(@Nonnull LabeledNode obs, char child) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean addEdge(@Nonnull K e, @Nonnull final LabeledNode v1, @Nonnull final LabeledNode v2) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean addEdge(@Nonnull K e, @Nonnull final LabeledNode v1, @Nonnull final LabeledNode v2, @Nonnull final EdgeType edge_type1) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean addEdge(@Nonnull K edge, @Nonnull Pair endpoints, @Nonnull EdgeType edgeType) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void addEdge(@Nonnull K e, @Nonnull final String v1Name, @Nonnull final String v2Name) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean addVertex(@Nonnull LabeledNode vertex) { throw new UnsupportedOperationException(); } /** * Makes this graph empty. */ @Override public void clear() { final TNGraph g = new TNGraph<>(this.getName(), getEdgeImplClass()); super.takeFrom(g); } /** * Unsupported */ @Override public void clear(int initialAdjSize) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void clearCache() { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void copy(@Nonnull final TNGraph g) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void copyCleaningRedundantLabels(@Nonnull TNGraph g) { throw new UnsupportedOperationException(); } /** * */ @Override public void propertyChange(@Nonnull PropertyChangeEvent pce) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean removeEdge(@Nonnull K edge) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean removeEdge(@Nonnull String edgeName) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean removeEmptyEdges() { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public boolean removeVertex(@Nonnull LabeledNode removingNode) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void reverse() { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void setInputFile(File file) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void setName(@Nonnull final String graphName) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void setZ(final LabeledNode z) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void takeFrom(@Nonnull final TNGraph g) { throw new UnsupportedOperationException(); } /** * Unsupported. */ @Override public void transpose() { throw new UnsupportedOperationException(); } } /** * Adjacency grow factor: This represents the multiplication factor used to increase the dimension of the adjacency matrix. It has to be at least 1.5. */ static final float growFactor = 1.8f; /** * */ private static final Logger LOG = Logger.getLogger(TNGraph.class.getName()); /** * */ @Serial private static final long serialVersionUID = 1L; /** * Returns an unmodifiable TNGraph backed by the given TNGraph. * * @param g The TNGraph is to be wrapped in an unmodifiable map. * @param the type of edges * * @return an unmodifiable view of the specified TNGraph. */ @SuppressWarnings("ClassReferencesSubclass") @Nonnull public static UnmodifiableTNGraph unmodifiable(TNGraph g) { return new UnmodifiableTNGraph<>(g); } /** * @return an instance for the childrenOfObserver field. */ @Nonnull private static Object2ObjectMap newChildrenObserverInstance() { // in Label class, I showed that for a small map, ArrayMap is faster than Object2ObjectRBTreeMap and // Object2ObjectAVLTreeMap. return new Object2ObjectArrayMap<>(); } /** * @return an instance for the propositionToNode field. */ @Nonnull private static Char2ObjectMap newProposition2NodeInstance() { final Char2ObjectMap map = new Char2ObjectArrayMap<>(); map.defaultReturnValue(null); return map;// I verified that Char2ObjectArrayMap is faster than Open hash when a proposition is limited, as in this application. } /** * Represents the association of an edge with its position in the adjacency matrix of the graph. * * @author posenato */ private class EdgeIndex { /* * It is not possible to use the technique used for Node (extending LabeledIntNode class) because if I extended * E, then edges are viewed as E, and parameterized type T cannot be used as a base class for extending. */ /** * */ int colAdj; /** * */ E edge; /** * */ int rowAdj; /** * @param e edge * @param row row * @param col col */ EdgeIndex(E e, int row, int col) { edge = e; rowAdj = row; colAdj = col; } @Override @Nonnull public String toString() { return String.format("%s->(%dX%d)", edge.getName(), Integer.valueOf(rowAdj), Integer.valueOf(colAdj)); } } /** * The graph is represented by its adjacency matrix. */ private E[][] adjacency; /** * Alphabet for A-Label */ private ALabelAlphabet aLabelAlphabet; /** * Children of observation nodes */ private Map childrenOfObserver; /** * Map (edge-->adjacency position) */ private Object2ObjectMap edge2index; /** * Edge factory */ private EdgeSupplier edgeFactory; /** * Map (adjacency row-->node) */ private Int2ObjectMap index2node; /** * Map (node) --> list of its `(in-going edge, sourceNode)` pairs. It works as a cache. */ private Map>> inEdgesCache; /** * Map (node) --> list of its `(outgoing edge, destinationNode)` pairs. It works as a cache. */ private Map>> outEdgesCache; /** * A possible input file containing this graph. */ private File inputFile; /** * List of edges with their lower-case label set not empty */ private ObjectList lowerCaseEdges; /** * Name */ private String name; /** * Node factory */ private LabeledNodeSupplier nodeFactory; /** * Map (node-->adjacency row) */ private Object2IntMap nodeName2index; /** * List of edges from observers to Z */ private ObjectList observer2Z; /** * Current number of nodes; */ private int order; /** * Map of (proposition-->Observer node). */ private Char2ObjectMap proposition2Observer; /** * Type of network */ private NetworkType type; /** * Zero node. In a temporal constraint network, such a node is the first node to execute. */ private LabeledNode Z; /** * Constructor for TNGraph. * * @param type of edge * @param graphName a name for the graph * @param inputEdgeImplClass type of edges */ public TNGraph(@Nonnull final String graphName, @Nonnull Class inputEdgeImplClass) { super(EdgeType.DIRECTED); if (OSTNUEdgePluggable.class.isAssignableFrom(inputEdgeImplClass)) { type = NetworkType.OSTNU; } else if (CSTNPSUEdge.class.isAssignableFrom(inputEdgeImplClass)) { type = NetworkType.CSTNPSU; } else if (CSTNUEdge.class.isAssignableFrom(inputEdgeImplClass)) { type = NetworkType.CSTNU; } else if (CSTNEdge.class.isAssignableFrom(inputEdgeImplClass)) { type = NetworkType.CSTN; } else if (STNUEdge.class.isAssignableFrom(inputEdgeImplClass)) { type = NetworkType.STNU; } else if (STNEdge.class.isAssignableFrom(inputEdgeImplClass)) { type = NetworkType.STN; } //For the probabilistic STN, the inputEdgeImplClass is STNUEdge, so it is necessary to check the filename extension if (type == NetworkType.STNU && graphName.endsWith(".pstn")) { type = NetworkType.PSTN; } if (BasicCSTNUEdge.class.isAssignableFrom(inputEdgeImplClass) || STNUEdge.class.isAssignableFrom(inputEdgeImplClass)) { aLabelAlphabet = new ALabelAlphabet(); } edgeFactory = new EdgeSupplier<>(inputEdgeImplClass);// , inputLabeledValueMapImplClass edge2index = new Object2ObjectOpenHashMap<>(); // From fastutil javadoc: // In general, AVL trees have slightly slower updates but faster searches; // however, on very large collections, the smaller height may lead, in fact, to faster updates, too. // Posenato: I find experimentally that in many applications, getDest and getEdge are the most executed operations. // For such operations, Object2ObjectOpenHashMap is faster than any tree! nodeFactory = new LabeledNodeSupplier();// inputLabeledValueMapImplClass order = 0; adjacency = createAdjacency(10); nodeName2index = new Object2IntOpenHashMap<>(); nodeName2index.defaultReturnValue(Constants.INT_NULL); index2node = new Int2ObjectOpenHashMap<>(); inEdgesCache = new Object2ObjectOpenHashMap<>(); outEdgesCache = new Object2ObjectOpenHashMap<>(); name = graphName; } /** * Constructor for TNGraph. * * @param type of edge * @param graphName a name for the graph * @param inputEdgeImplClass type of edges * @param alphabet alphabet for upper-case letters used to label values in the edges. */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "For efficiency reasons, it includes an external mutable object.") public TNGraph(@Nonnull final String graphName, @Nonnull Class inputEdgeImplClass, @Nullable ALabelAlphabet alphabet) { this(graphName, inputEdgeImplClass); aLabelAlphabet = alphabet; } /** * A constructor that copies a given graph g using the copy constructor for internal structures.
If g is null, this new graph will be empty. *
* Be careful: this copy constructor is good only to create graphs that are not manipulated by {@link it.univr.di.cstnu.visualization.TNEditor} because all listener about nodes and edges are not activated. * * @param type of edge * @param inputGraph the graph to be cloned * @param edgeImplClass class * @param sameNodes if true, the nodes are shared between the given graph and the generated one. * @param useFastAddEdge if true, it adds edges without making any checks about the possible redundant names, missing nodes, etc. Use it to create a big graph only for internal use. */ public TNGraph(@Nonnull final TNGraph inputGraph, @Nonnull Class edgeImplClass, boolean sameNodes, boolean useFastAddEdge) { this(inputGraph.name, edgeImplClass); if (Debug.ON) { LOG.fine("TNGraph creator started."); } aLabelAlphabet = (inputGraph.aLabelAlphabet != null) ? inputGraph.aLabelAlphabet : aLabelAlphabet;// g.aLabelAlphabet has already been used for representing 'node.aLabel' field and an upper-case label in contingent edges inputFile = inputGraph.inputFile; adjacency = createAdjacency(inputGraph.adjacency.length); nodeFactory = inputGraph.nodeFactory; //Initialize node structures and clone all nodes of g. order = 0; inEdgesCache.clear(); outEdgesCache.clear(); index2node.clear(); nodeName2index.clear(); nodeName2index.defaultReturnValue(Constants.INT_NULL); // clone all nodes if (Debug.ON) { LOG.finer("Start copy/clone nodes."); } for (final LabeledNode node : inputGraph.getVertices()) { final LabeledNode newNode; if (sameNodes) { newNode = node; addVertexWithoutCheck(node); } else { newNode = LabeledNodeSupplier.get(node); addVertex(newNode); if (node.getALabel() != null) { newNode.setALabel(new ALabel(node.getALabel().toString(), aLabelAlphabet)); } } if (node.equalsByName(inputGraph.Z)) { Z = newNode; } } if (Debug.ON) { LOG.finer("Finished copy/clone nodes.\nStart to clone all " + inputGraph.getEdgeCount() + " edges."); } // clone all edges, giving the right new endpoints corresponding to the old ones. E eNew; for (final E e : inputGraph.getEdges()) { eNew = edgeFactory.get(e); final String sourceName = Objects.requireNonNull(inputGraph.getSource(e.getName())).getName(); final String destName = Objects.requireNonNull(inputGraph.getDest(e.getName())).getName(); if (useFastAddEdge) { addEdgeWithoutCheck(eNew, sourceName, destName); } else { addEdge(eNew, sourceName, destName); } } if (Debug.ON) { LOG.fine("TNGraph creation finished."); } } /** * A constructor that copies a given graph g using the copy constructor for internal structures.
If g is null, this new graph will be empty. * * @param type of edge * @param inputGraph the graph to be cloned * @param edgeImplClass class */ public TNGraph(@Nonnull final TNGraph inputGraph, @Nonnull Class edgeImplClass) { this(inputGraph, edgeImplClass, false, false); } /** * It must not be used outside. */ private TNGraph() { super(EdgeType.DIRECTED); } /** * Add child to obs. It is the user's responsibility to ensure that 'child' is a child in the CSTN sense of 'obs'. No validity check is made using this method. * * @param obs a {@link it.univr.di.cstnu.graph.LabeledNode} object. * @param child a char. */ public void addChildToObserverNode(@Nonnull LabeledNode obs, char child) { if (childrenOfObserver == null) { childrenOfObserver = newChildrenObserverInstance(); } Label children = childrenOfObserver.get(obs); if (children == null) { children = Label.emptyLabel; } children = children.conjunction(child, Literal.STRAIGHT); childrenOfObserver.put(obs, children); } /** * Adds the edge if there is no edge between v1 and v2. Moreover, node(s) are added if they are not present in the graph. It calls {@link #addEdge(Edge, String, String)} method. * * @see #addEdge(Edge, String, String) */ @Override public boolean addEdge(@Nonnull E e, final @Nonnull LabeledNode v1, final @Nonnull LabeledNode v2) { if (!nodeName2index.containsKey(v1.getName())) { addVertex(v1); } if (!nodeName2index.containsKey(v2.getName())) { addVertex(v2); } addEdge(e, v1.getName(), v2.getName()); return true; } /** * */ @Override public boolean addEdge(@Nonnull E e, final @Nonnull LabeledNode v1, final @Nonnull LabeledNode v2, final @Nonnull EdgeType edge_type1) { return addEdge(e, v1, v2); } /** * */ @Override public boolean addEdge(@Nonnull E edge, @Nonnull Pair endpoints, @Nonnull EdgeType edgeType) { return addEdge(edge, endpoints.getFirst(), endpoints.getSecond()); } /** * Unsupported */ @Override public boolean addEdge(@Nonnull E edge, @Nonnull Collection vertices) { throw new UnsupportedOperationException(); } /** * Unsupported */ @Override public boolean addEdge(@Nonnull E edge, @Nonnull Collection vertices, EdgeType t) { throw new UnsupportedOperationException(); } /** * Optimized method for adding an edge. It exploits the internal structure of the class. *
* Adds the input edge if an edge between v1Name and v2Name does not exist. * * @param e not null * @param v1Name not null * @param v2Name not null * * @throws IllegalArgumentException if e has an equal name to another edge in the graph, or if there is already an edge between the two nodes. */ public void addEdge(@Nonnull E e, @Nonnull final String v1Name, @Nonnull final String v2Name) { /* * It is necessary to copy the general method here because otherwise it calls the general `addEdge(e, new Pair(v1, v2), edge_type)`!!! * @see edu.uci.ics.jung.graph.AbstractGraph#addEdge(Object, Object, Object, EdgeType) */ if (edge2index.containsKey(e.getName())) { final String msg = "An edge with name %s already exists. The new edge cannot be added.".formatted(e.getName()); LOG.severe(msg); throw new IllegalArgumentException(msg); } if (!nodeName2index.containsKey(v1Name)) { addVertex(new LabeledNode(v1Name)); } if (!nodeName2index.containsKey(v2Name)) { addVertex(new LabeledNode(v2Name)); } final int sourceIndex = nodeName2index.getInt(v1Name); if (sourceIndex == Constants.INT_NULL) { final String msg = "The source node of the new edge %s is null. The new edge cannot be added.".formatted(e); LOG.severe(msg); throw new IllegalArgumentException(msg); } final int destIndex = nodeName2index.getInt(v2Name); if (destIndex == Constants.INT_NULL) { final String msg = "The destination node of the edge %s is null. The new edge cannot be added.".formatted(e); LOG.severe(msg); throw new IllegalArgumentException(msg); } final E old = adjacency[sourceIndex][destIndex]; if (old != null) { final String msg = "Between node %s and node %s there exists the edge %s. Remove it before adding a new one %s".formatted(v1Name, v2Name, old, e); LOG.severe(msg); throw new IllegalArgumentException(msg); } // removeEdgeFromIndex(old); adjacency[sourceIndex][destIndex] = e; final var retValue = edge2index.put(e.getName(), new EdgeIndex(e, sourceIndex, destIndex)); if (retValue != null) { // redundant check final String msg = "The new edge %s overwrote the entry %s in an edge2index map. This should be impossible.".formatted(e, retValue); LOG.severe(msg); throw new IllegalStateException(msg); } if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Added edge " + e); } } lowerCaseEdges = null; inEdgesCache.remove(index2node.get(destIndex)); outEdgesCache.remove(index2node.get(sourceIndex)); ((AbstractEdge) e).addObserver("edgeType", this); ((AbstractEdge) e).addObserver("edgeName", this); } /** * Unsupported */ @Override public boolean addEdge(@Nonnull E edge, @Nonnull Pair vertices) { throw new UnsupportedOperationException(); } /** * Optimized method for adding an edge without any security check and listener modification for edges. Use this method if and only if the graph does not * require being managed by {@link it.univr.di.cstnu.visualization.TNEditor} and it is necessary to add edges in a very fast way. *
* Adds the input edge if an edge between v1Name and v2Name does not exist. * * @param e not null * @param v1Name not null * @param v2Name not null * * @throws IllegalArgumentException if e has an equal name to another edge in the graph, or if there is already an edge between the two nodes. */ public void addEdgeWithoutCheck(@Nonnull E e, @Nonnull final String v1Name, @Nonnull final String v2Name) { final int sourceIndex = nodeName2index.getInt(v1Name); final int destIndex = nodeName2index.getInt(v2Name); adjacency[sourceIndex][destIndex] = e; edge2index.put(e.getName(), new EdgeIndex(e, sourceIndex, destIndex)); if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Fast add of edge " + e); } } lowerCaseEdges = null; inEdgesCache.clear(); outEdgesCache.clear(); } /** * Adds the given vertex to the graph. * * @throws IllegalArgumentException if a vertex has an equal name to another vertex in the graph, or if it is an observer of a condition for which there is another observer already in the graph. */ @Override public boolean addVertex(@Nonnull LabeledNode vertex) { if (nodeName2index.containsKey(vertex.getName()) || (vertex.getPropositionObserved() != Constants.UNKNOWN && getObserver(vertex.getPropositionObserved()) != null)) { final String msg = "The new vertex %s is already present or is an observer of a proposition for which there is already an observer.".formatted(vertex); if (Debug.ON) { if (LOG.isLoggable(Level.SEVERE)) { LOG.severe(msg); } } throw new IllegalArgumentException(msg); } final int currentSize = adjacency.length; if (currentSize == order) { final int newSize = (int) (currentSize * growFactor); final E[][] newAdjacency = createAdjacency(newSize); for (int i = currentSize; i-- != 0; ) { for (int j = currentSize; j-- != 0; ) { newAdjacency[i][j] = adjacency[i][j]; } } adjacency = newAdjacency; } // now it is possible to add a node in the position 'order' nodeName2index.put(vertex.getName(), order); index2node.put(order, vertex); order++; clearCache(); vertex.addObserver("nodeName", this); vertex.addObserver("nodeLabel", this); vertex.addObserver("nodeProposition", this); if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Added node " + vertex); } } return true; } /** * Adds the given vertex to the graph without checking its previous presence and without adding any listeners to it. *

* Use this method if and only if the graph does not require being managed by {@link it.univr.di.cstnu.visualization.TNEditor} and it is necessary to add edges in a very fast way. * * @param vertex the input vertex * * @throws IllegalArgumentException if a vertex has an equal name to another vertex in the graph, or if it is an observer of a condition for which there is already another observer in the graph. */ public void addVertexWithoutCheck(@Nonnull LabeledNode vertex) { final int currentSize = adjacency.length; if (currentSize == order) { final int newSize = (int) (currentSize * growFactor); final E[][] newAdjacency = createAdjacency(newSize); for (int i = currentSize; i-- != 0; ) { for (int j = currentSize; j-- != 0; ) { newAdjacency[i][j] = adjacency[i][j]; } } adjacency = newAdjacency; } // now it is possible to add a node in the position 'order' nodeName2index.put(vertex.getName(), order); index2node.put(order, vertex); order++; clearCache(); // vertex.addObserver("nodeName", this); // vertex.addObserver("nodeLabel", this); // vertex.addObserver("nodeProposition", this); if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Added node " + vertex); } } } /** * Clear all internal structures. The graph will be empty. */ public void clear() { clear(10); } /** * Clear all internal structures. The graph will be erased. * * @param initialAdjSize initial number of vertices. */ public void clear(int initialAdjSize) { order = 0;// addVertex adjusts the value if (adjacency != null && adjacency.length <= initialAdjSize) { // reuse the array! This is important in some applications where many graphs are created in a very short time. Arrays.fill(adjacency[0], null); final int adjSize = adjacency.length; for (int i = 1; i < adjSize; i++) { System.arraycopy(adjacency[0], 0, adjacency[i], 0, adjSize); } } else { adjacency = createAdjacency(initialAdjSize); } nodeName2index.clear(); index2node.clear(); edge2index.clear(); clearCache(); } /** * Clear all internal caches. *

* Caches are automatically created during any modification or query about the graph structure. */ public void clearCache() { lowerCaseEdges = null; proposition2Observer = null; childrenOfObserver = null; observer2Z = null; inEdgesCache.clear(); outEdgesCache.clear(); } /** * */ @Override public boolean containsEdge(@Nonnull E edge) { return edge2index.containsKey(edge.getName()); } /** * */ @Override public boolean containsVertex(@Nonnull LabeledNode vertex) { return nodeName2index.containsKey(vertex.getName()); } /** * A defensive copy of all 'g' internal structures. *

This method is useful to copy a graph into the current without modifying the reference to the current. *

'g' internal structures are defensively copied as they are. * * @param g the graph to copy. */ public void copy(@Nonnull final TNGraph g) { name = g.name; order = 0;// addVertex adjusts the value adjacency = createAdjacency(g.getVertexCount()); nodeName2index.clear(); index2node.clear(); edge2index.clear(); clearCache(); // Clone all nodes LabeledNode vNew; for (final LabeledNode v : g.getVertices()) { vNew = LabeledNodeSupplier.get(v); addVertex(vNew); if (v.equalsByName(g.Z)) { Z = vNew; } } // Clone all edges, giving the right new endpoints corresponding to the old ones. for (final E e : g.getEdges()) { addEdge(edgeFactory.get(e), Objects.requireNonNull(g.getSource(e)).getName(), Objects.requireNonNull(g.getDest(e)).getName()); } } /** * Makes a copy as {@link #copy(TNGraph)} removing all labeled values having unknown literal(s) or -∞ value. * * @param g a TNGraph. */ public void copyCleaningRedundantLabels(@Nonnull TNGraph g) { name = g.name; order = 0;// addVertex adjusts the value adjacency = createAdjacency(g.getVertexCount()); nodeName2index.clear(); index2node.clear(); edge2index.clear(); clearCache(); // clone all nodes LabeledNode vNew; for (final LabeledNode v : g.getVertices()) { vNew = LabeledNodeSupplier.get(v); for (final Label label : vNew.getLabeledPotential().keySet()) { if (label.containsUnknown()) { vNew.removeLabeledPotential(label);//ok } } for (final Label label : vNew.getLabeledUpperPotential().keySet()) { if (label.containsUnknown()) { vNew.removeLabeledUpperPotential(label);//ok } } addVertex(vNew); if (v.equalsByName(g.Z)) { Z = vNew; } } // clone all edges, giving the right new endpoints corresponding to the old ones. E eNew; int value; Label label; for (final E e : g.getEdges()) { if (e.isEmpty()) { continue; } eNew = edgeFactory.get(e.getName());// I don't copy values! eNew.setConstraintType(e.getConstraintType()); if (e.isSTNEdge()) { ((STNEdge) eNew).setValue(((STNEdge) e).getValue()); addEdge(eNew, Objects.requireNonNull(g.getSource(e)).getName(), Objects.requireNonNull(g.getDest(e)).getName()); continue; } if (e.isCSTNEdge()) { for (final Object2IntMap.Entry

* This method returns the set of children of a given node as a label of straight propositions associated with the children instead of a set of * children nodes. * * @param obs a {@link it.univr.di.cstnu.graph.LabeledNode} object. * * @return the set of children of observation node {@code obs}, null if there are no children. */ @Nullable public Label getChildrenOf(@Nonnull LabeledNode obs) { // The soundness of this method is based on the property that the observed proposition of an observation node is represented as a straight literal. if (childrenOfObserver == null) { // Build the cache map of childrenOfObserver childrenOfObserver = newChildrenObserverInstance(); for (final Char2ObjectMap.Entry entryObservedObserverNode : getObservedAndObserver().char2ObjectEntrySet()) { final char observedProposition = entryObservedObserverNode.getCharKey(); final LabeledNode observator = entryObservedObserverNode.getValue(); final Label observatorLabel = observator.getLabel(); for (final char propInObsLabel : observatorLabel.getPropositions()) { final LabeledNode father = getObserver(propInObsLabel);// for the well-definedness property, father must exist! Label children = childrenOfObserver.get(father); if (children == null) { children = Label.emptyLabel; } children = children.conjunction(observedProposition, Literal.STRAIGHT); childrenOfObserver.put(father, children); } } } return childrenOfObserver.get(obs); } /** * @return the number of contingent nodes. */ public int getContingentNodeCount() { int c = 0; for (final E e : getEdges()) { if (e.isContingentEdge()) { c++; } } return c / 2; } /** * Wrapper {@link #getDest(Edge)} * * @param edgeName a {@link java.lang.String} object. * * @return a {@link it.univr.di.cstnu.graph.LabeledNode} object. */ @Nullable public LabeledNode getDest(@Nonnull String edgeName) { final EdgeIndex ei = edge2index.get(edgeName); if (ei == null) { return null; } return index2node.get(ei.colAdj); } /** * */ @Override @Nullable public LabeledNode getDest(@Nonnull E directedEdge) { final EdgeIndex ei = edge2index.get(directedEdge.getName()); if (ei == null) { return null; } return index2node.get(ei.colAdj); } /** * Returns the edge associated with the name. * * @param s a {@link java.lang.String} object. * * @return the edge associated with the name. */ @Nullable public E getEdge(@Nonnull final String s) { if (s.isEmpty()) { return null; } final EdgeIndex ei = edge2index.get(s); if (ei == null) { return null; } return ei.edge; } /** * */ @Override public int getEdgeCount() { return edge2index.size(); } /** * Getter for the field {@code edgeFactory}. * * @return the edgeFactory */ public EdgeSupplier getEdgeFactory() { return edgeFactory; } /** * @return the edgeImplementationClass */ // @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "False positive.") // @Nonnull public Class getEdgeImplClass() { return edgeFactory.getEdgeImplClass(); } /** * Returns an independent collection of the edges of this graph. */ @Override @Nonnull public Collection getEdges() { final ObjectArrayList coll = new ObjectArrayList<>(getEdgeCount()); for (final EdgeIndex ei : edge2index.values()) { coll.add(ei.edge); } return coll; // ObjectCollection tmp = edge2index.values(); // return (Collection) tmp; } /** * It is an optimization of {@link #getInEdges(LabeledNode)} that also returns the source node of the incoming edge. * * @return the pair (edge, sourceNode) */ @Nonnull public ObjectList> getEdgesAndNodes() { // final int nodeIndex; E e; final ObjectList> edgeNodeList = new ObjectArrayList<>(); final int n = order; for (int i = 0; i < n; i++) { final LabeledNode source = index2node.get(i); for (int j = 0; j < n; j++) { e = adjacency[i][j]; if (e != null) { edgeNodeList.add(new ImmutableTriple<>(source, e, index2node.get(j))); } } } return edgeNodeList; } /** * @return An independent collection of the edges of this graph ordered by name. */ @Nonnull public Collection getEdgesOrdered() { return new ObjectAVLTreeSet<>(this.getEdges()); } /** * getEdgesArray. */ @Nullable @Override public Pair getEndpoints(@Nonnull E edge) { final EdgeIndex ei = edge2index.get(edge.getName()); if (ei == null) { return null; } return new Pair<>(index2node.get(ei.rowAdj), index2node.get(ei.colAdj)); } /** * @return the name of the file that contains this graph. */ public File getFileName() { return inputFile; } /** * */ @Override @Nonnull public ObjectList getInEdges(@Nonnull LabeledNode vertex) { final int nodeIndex; if ((nodeIndex = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return new ObjectArrayList<>(); } final ObjectList inEdges = new ObjectArrayList<>(); E e; final int n = order; for (int i = n; --i >= 0; ) { e = adjacency[i][nodeIndex]; if (e != null) { inEdges.add(e); } } return inEdges; } /** * It is an optimization of {@link #getInEdges(LabeledNode)} that also returns the source node of the incoming edge. * * @param vertex the destination node * * @return the pair (edge, sourceNode) */ @Nonnull public ObjectList> getInEdgesAndNodes(@Nonnull LabeledNode vertex) { final int nodeIndex; if ((nodeIndex = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return new ObjectArrayList<>(); } E e; ObjectList> edgeNodeList = inEdgesCache.get(vertex); if (edgeNodeList != null) { return edgeNodeList; } edgeNodeList = new ObjectArrayList<>(); final int n = order; for (int i = n; --i >= 0; ) { e = adjacency[i][nodeIndex]; if (e != null) { edgeNodeList.add(new ObjectObjectImmutablePair<>(e, index2node.get(i))); } } inEdgesCache.put(vertex, edgeNodeList); return edgeNodeList; } /** * */ @Override @Nonnull public ObjectList getIncidentEdges(@Nonnull LabeledNode vertex) { final int index; final ObjectArrayList coll = new ObjectArrayList<>(); if ((index = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return coll; } E e; for (int i = 0; i < order; i++) { e = adjacency[index][i]; if (e != null) { coll.add(e); } if (i == index) { continue; } e = adjacency[i][index]; if (e != null) { coll.add(e); } } return coll; } /** * @return the list of edges containing Lower Case Labels (when the type of network is CSTNU/CSTPSU). If there are no such edges, it returns an empty list. */ @Nonnull public ObjectList getLowerLabeledEdges() { if (lowerCaseEdges == null) { lowerCaseEdges = new ObjectArrayList<>(); if (type == NetworkType.CSTNU || type == NetworkType.CSTNPSU) { for (int i = 0; i < order; i++) { for (int j = 0; j < order; j++) { final BasicCSTNUEdge edge = (BasicCSTNUEdge) adjacency[i][j]; if (edge == null || !edge.isContingentEdge() || edge.lowerCaseValueSize() != 1) { continue; } lowerCaseEdges.add(edge); } } } } return lowerCaseEdges; } /** * @return the name */ public String getName() { return name; } /** * Returns the node associated with the name. * * @param s a {@link java.lang.String} object. * * @return the node associated with the name if present, null otherwise. */ @Nullable public LabeledNode getNode(@Nonnull final String s) { if (s.isEmpty()) { return null; } return index2node.get(nodeName2index.getInt(s)); } /** * */ @Nullable @Override public Collection getNeighbors(@Nonnull LabeledNode vertex) { final int nodeIndex; if ((nodeIndex = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return null; } final ObjectArraySet neighbors = new ObjectArraySet<>(); for (int i = 0; i < order; i++) { if (adjacency[nodeIndex][i] != null) { neighbors.add(index2node.get(i)); } if (adjacency[i][nodeIndex] != null) { neighbors.add(index2node.get(i)); } } return neighbors; } /** * @return return the set of nodes ordered with respect to the lexicographical order of their names. * * @see #getVertices() */ @Nonnull public Collection getNodesOrdered() { return new ObjectAVLTreeSet<>(this.getVertices()); } /** * @return the nodeFactory */ public LabeledNodeSupplier getNodeFactory() { return nodeFactory; } /** * @return the map of propositions and their observers (nodes). If there is no observer node, it returns an empty map. * * @throws IllegalStateException sanity check: if there are two observers for the same proposition. Such an error should never occur. */ @Nonnull public Char2ObjectMap getObservedAndObserver() throws IllegalStateException { if (proposition2Observer == null) { proposition2Observer = newProposition2NodeInstance(); char proposition; for (final LabeledNode n : getVertices()) { if ((proposition = n.getPropositionObserved()) != Constants.UNKNOWN) { if (proposition2Observer.put(proposition, n) != null) { throw new IllegalStateException("There are two observer nodes for the same proposition " + proposition); } } } } assert proposition2Observer != null; return proposition2Observer; } /** * @param c the proposition * * @return the node that observes the proposition if it exists; otherwise, it is null. */ @Nullable public LabeledNode getObserver(final char c) { final Char2ObjectMap observer = getObservedAndObserver(); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { TNGraph.LOG.finest("Proposition: " + c + "; observer: " + observer.get(c)); } } return observer.get(c); } /** * Be careful! The returned value is not a copy, as the edges are contained! * * @return the list of edges from observers to Z if Z is defined, an empty set otherwise. */ @Nonnull public ObjectList getObserver2ZEdges() { if (observer2Z == null) { buildObserver2ZEdgesSet(); } assert observer2Z != null; return observer2Z; } /** * It is an optimization of {@link #getOutEdges(LabeledNode)} that also returns the destination node of the outgoing edge. * * @param vertex the source node * * @return the pair (edge,destinationNode) */ @Nonnull public ObjectList> getOutEdgesAndNodes(@Nonnull LabeledNode vertex) { final int nodeIndex; if ((nodeIndex = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return new ObjectArrayList<>(); } E e; ObjectList> edgeNodeList = outEdgesCache.get(vertex); if (edgeNodeList != null) { return edgeNodeList; } edgeNodeList = new ObjectArrayList<>(); final int n = order; for (int i = n; --i >= 0; ) { e = adjacency[nodeIndex][i]; if (e != null) { edgeNodeList.add(new ObjectObjectImmutablePair<>(e, index2node.get(i))); } } outEdgesCache.put(vertex, edgeNodeList); return edgeNodeList; } /** * @return the number of observers. */ public int getObserverCount() { return getObservers().size(); } /** * @return the set of observer time-points. */ @Nonnull public Collection getObservers() { if (proposition2Observer == null) { getObservedAndObserver(); } assert proposition2Observer != null; return proposition2Observer.values(); } /** * */ @Override @Nonnull public ObjectList getOutEdges(@Nonnull LabeledNode vertex) { final int nodeIndex; if ((nodeIndex = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return new ObjectArrayList<>(); } final ObjectList outEdges = new ObjectArrayList<>(); E e; final int n = order; for (int i = n; --i >= 0; ) { e = adjacency[nodeIndex][i]; if (e != null) { outEdges.add(e); } } return outEdges; } /** * @param edgeName a desired name for an edge * * @return name with a possible suffix for making it a name of the new edge without conflicts with the names of already present edges. */ public String getUniqueEdgeName(@Nonnull String edgeName) { int i = 0; String s = edgeName; while (getEdge(s) != null) { s = edgeName + "_" + i++; } return s; } /** * */ @Override @Nonnull public Collection getPredecessors(@Nonnull LabeledNode vertex) { final ObjectArrayList predecessor = new ObjectArrayList<>(); for (final E e : getInEdges(vertex)) { predecessor.add(getSource(e)); } return predecessor; } /** * getPropositions. * * @return the set of propositions of the graph. */ @Nonnull public CharSet getPropositions() { if (proposition2Observer == null) { getObservedAndObserver(); } assert proposition2Observer != null; return proposition2Observer.keySet(); } /** * */ @Nullable @Override public LabeledNode getSource(@Nonnull E edge) { return getSource(edge.getName()); } /** * Wrapper of {@link #getSource(Edge)} * * @param edgeName a {@link java.lang.String} object. * * @return a {@link it.univr.di.cstnu.graph.LabeledNode} object. */ @Nullable public LabeledNode getSource(@Nonnull String edgeName) { final EdgeIndex ei = edge2index.get(edgeName); if (ei == null) { return null; } return index2node.get(ei.rowAdj); } /** * */ @Override @Nonnull public Collection getSuccessors(@Nonnull LabeledNode vertex) { final ObjectArrayList successors = new ObjectArrayList<>(); for (final E e : getOutEdges(vertex)) { successors.add(getDest(e)); } return successors; } /** * @return the type of network */ public NetworkType getType() { return type; } /** * getVerticesArray. * * @return the array of vertices as an array ordered with respect to the name of the nodes in ascending order. */ // @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE", justification = "It is not important!") public LabeledNode[] getVerticesArray() { final LabeledNode[] nodes = getVertices().toArray(new LabeledNode[0]); Arrays.sort(nodes); return nodes; } /** * Returns true if this graph has the same set of edges as g1. * * @param g1 a graph. * * @return true if this graph contains edges equal to g1 edges w.r.t. their name and their values. False, otherwise. */ public boolean hasSameEdgesOf(@Nonnull final TNGraph g1) { return this.differentEdgesOf(g1).isEmpty(); } /** * @return the set of edges containing Upper Case Label only for TNGraphs that contain BasicCSTNEdge or derived. */ public Set getUpperLabeledEdges() { final ObjectArraySet es1 = new ObjectArraySet<>(); for (final E e : getEdges()) { final BasicCSTNUEdge e1 = ((BasicCSTNUEdge) e); if (e1.upperCaseValueSize() > 0) { es1.add(e1); } } return es1; } /** * */ @Override public int getVertexCount() { return nodeName2index.size(); } /** * */ @SuppressWarnings("NP_NONNULL_RETURN_VIOLATION") @Override public @Nonnull Collection getVertices() { return index2node.values(); } /** * @param g1 the given graph * * @return true if g1 has the set of vertices equal to the set of vertices of this. */ public boolean hasSameVerticesOf(@Nonnull final TNGraph g1) { boolean sameNodes = true; final ObjectSet allNodes = new ObjectAVLTreeSet<>(getVertices()); allNodes.addAll(g1.getVertices()); LabeledNode nodeG, nodeG1; for (final LabeledNode n : allNodes) { nodeG = getNode(n.getName()); nodeG1 = g1.getNode(n.getName()); if (nodeG == null || nodeG1 == null || nodeG.propositionObserved != nodeG1.propositionObserved || !nodeG.getLabel().equals(nodeG1.getLabel())) { sameNodes = false; } } return sameNodes; } /** * @return the Z node */ public @Nullable LabeledNode getZ() { return Z; } /** * Creates a new edge proper for this graph using edgeName as a proposed name (it will be modified by adding a suffix if there is already another edge with name edgeName) and setting the type to type. * * @param edgeName the proposed name for the edge. * @param edgeType the type of the edge. * * @return a new edge (without adding it to the graph) of the proper class for this graph. */ public E makeNewEdge(@Nonnull final String edgeName, @Nonnull final ConstraintType edgeType) { final String s = getUniqueEdgeName(edgeName); final E e = edgeFactory.get(s); e.setConstraintType(edgeType); return e; } /** * */ @SuppressWarnings("ChainOfInstanceofChecks") @Override public void propertyChange(@Nonnull PropertyChangeEvent pce) { final Object o = pce.getSource(); final String property = pce.getPropertyName(); final Object old = pce.getOldValue(); if (o instanceof LabeledNode node) { if ("nodeName".equals(property)) { final String oldValue = (String) old; final int oldI = nodeName2index.getInt(oldValue); final int newI = nodeName2index.getInt(node.getName()); if (newI != Constants.INT_NULL) { if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Values in nodeName2index: " + nodeName2index.toString()); } } if (Debug.ON) { LOG.severe("It is not possible to rename node %s with %s because there is already a node with name %s".formatted(oldValue, node.getName(), node.getName())); } node.name = oldValue; return; } if (nodeName2index.removeInt(oldValue) != oldI) { LOG.severe("It is not possible to remove the node " + oldValue); } nodeName2index.put(node.getName(), oldI); if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("The nodeName2Index is updated. Removed old name %s. Add the new one: %s at position %d\nnodeName2index.size(): %d".formatted(oldValue, node, oldI, nodeName2index.size())); } } node.setALabel(null); if (Z != null && oldValue.equals(Z.getName())) { setZ(null); } return; } if ("nodeProposition".equals(property)) { final char newP = node.propositionObserved; // It isn't very easy to rely on proposition2Observer because it can be erased for some reason. // So, the check is made for all nodes. if (newP != Constants.UNKNOWN) { for (final LabeledNode n : getVertices()) { if (n != node && n.getPropositionObserved() == newP) { if (Debug.ON) { LOG.severe("It is not possible to assign the proposition %s to the node %s because there is already a node that observes the proposition: node %s".formatted(newP, node.getName(), n)); node.propositionObserved = (Character) old; } return; } } } proposition2Observer = null; childrenOfObserver = null; observer2Z = null; return; } if ("nodeLabel".equals(property)) { childrenOfObserver = null; } } // LabeledNode if (o instanceof AbstractEdge edge) { if ("edgeName".equals(property)) { final String oldName = (String) old; final EdgeIndex oldI = edge2index.get(oldName); final EdgeIndex newI = edge2index.get(edge.getName()); if (newI != null) { if (Debug.ON) { LOG.severe("oldIndex: " + oldI + "\nnewIndex: " + newI + "\nIt is not possible to rename edge " + (oldName) + " with " + edge.getName() + " because there is already an edge with the same name. Rollback."); } edge.name = (String) old; return; } if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Edge2index size before remove: " + edge2index.size()); } } edge2index.remove(oldName); edge2index.put(edge.getName(), oldI); if (Debug.ON) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Edge2index size after the 'add': %d".formatted(edge2index.size())); LOG.finer("The edge2index is updated. Removed old name %s. Add the new one: %s at position %s".formatted(oldName, edge, oldI)); } } return; } if ("lowerLabel:remove".equals(property)) { final BasicCSTNUEdge e1 = (BasicCSTNUEdge) edge; lowerCaseEdges.remove(e1); return; } if ("lowerLabel:add".equals(property)) { final BasicCSTNUEdge e1 = (BasicCSTNUEdge) edge; if (lowerCaseEdges == null) { lowerCaseEdges = new ObjectArrayList<>(); } lowerCaseEdges.add(e1); return; } if ("edgeType".equals(property)) { final ConstraintType oldT = (ConstraintType) (old); final ConstraintType newT = (ConstraintType) pce.getNewValue(); if (oldT != newT && newT != ConstraintType.contingent) { lowerCaseEdges = null; } } } } /** * Removes the edge from this graph and clears all cache data structures that could contain the removed edge. * * @param edgeName a {@link java.lang.String} object. * * @return true if the edge was removed, false if the edge is not present. */ public boolean removeEdge(@Nonnull String edgeName) { final EdgeIndex ei; if ((ei = edge2index.get(edgeName)) == null) { return false; } adjacency[ei.rowAdj][ei.colAdj] = null; removeEdgeFromIndex(getEdge(edgeName)); lowerCaseEdges = null; inEdgesCache.remove(index2node.get(ei.colAdj)); outEdgesCache.remove(index2node.get(ei.rowAdj)); return true; } /** * */ @Override public int inDegree(@Nonnull LabeledNode vertex) { final int nodeIndex; if ((nodeIndex = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return Constants.INT_NULL; } final ObjectList> cache = inEdgesCache.get(vertex); if (cache != null) { return cache.size(); } int count = 0; final int n = order; for (int i = n; --i >= 0; ) { if (adjacency[i][nodeIndex] != null) { count++; } } return count; } // /** // * Since 'equals' has been specialized, hashCode too. // * TNGraphReader needs original hashCode() method, so I maintain the Object#equals. // */ // @Override // public int hashCode() { // return super.hashCode(); // } /** * */ @Override public boolean isDest(@Nonnull LabeledNode vertex, @Nonnull E edge) { final int nodeIndex = nodeName2index.getInt(vertex.getName()); if (nodeIndex == Constants.INT_NULL) { return false; } for (int i = 0; i < order; i++) { if (edge.equalsByName(adjacency[i][nodeIndex])) { return true; } } return false; } /** * */ @Override public boolean isSource(@Nonnull LabeledNode vertex, @Nonnull E edge) { final int nodeIndex = nodeName2index.getInt(vertex.getName()); if (nodeIndex == Constants.INT_NULL) { return false; } for (int i = 0; i < order; i++) { if (edge.equalsByName(adjacency[nodeIndex][i])) { return true; } } return false; } /** * Removes the given node and all edges having it as an endpoint. * * @return true if the node was removed, false if the node was not present and, therefore, not removed. */ @Override public boolean removeVertex(@Nonnull LabeledNode removingNode) { /* * I don't touch the adjacency size. I just moved the last column and row to the column and row that have to be removed. */ final int removingNodeIndex; if ((removingNodeIndex = nodeName2index.getInt(removingNode.getName())) == Constants.INT_NULL) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("I cannot remove vertex " + removingNode + " because it is null or its index is null."); } return false; } removingNode.removeObserver("nodeLabel", this); removingNode.removeObserver("nodeName", this); removingNode.removeObserver("nodeProposition", this); final int last = order - 1; // Move the removed node to the end of the adjacency matrix and remove all its edges. if (removingNodeIndex == last) { for (int i = 0; i < last; i++) { // I just nullify the last column and the last row removeEdgeFromIndex(adjacency[i][last]); removeEdgeFromIndex(adjacency[last][i]); adjacency[i][last] = adjacency[last][i] = null; } removeEdgeFromIndex(adjacency[last][last]); adjacency[last][last] = null; } else { for (int i = 0; i < last; i++) { if (i == removingNodeIndex) { removeEdgeFromIndex(adjacency[i][i]); adjacency[i][i] = adjacency[last][last]; updateEdgeInIndex(adjacency[i][i], i, i); removeEdgeFromIndex(adjacency[i][last]); removeEdgeFromIndex(adjacency[last][i]); adjacency[last][last] = adjacency[i][last] = adjacency[last][i] = null; continue; } removeEdgeFromIndex(adjacency[i][removingNodeIndex]); adjacency[i][removingNodeIndex] = adjacency[i][last]; updateEdgeInIndex(adjacency[i][removingNodeIndex], i, removingNodeIndex); adjacency[i][last] = null; removeEdgeFromIndex(adjacency[removingNodeIndex][i]); adjacency[removingNodeIndex][i] = adjacency[last][i]; updateEdgeInIndex(adjacency[removingNodeIndex][i], removingNodeIndex, i); adjacency[last][i] = null; } } // End of moving the node to the end of the adjacency matrix and removing all its edges. index2node.remove(removingNodeIndex); nodeName2index.removeInt(removingNode.getName()); if (removingNodeIndex != last) { final LabeledNode nodeMovedToRemovedNodePosition = index2node.get(last); index2node.remove(last); index2node.put(removingNodeIndex, nodeMovedToRemovedNodePosition); nodeName2index.put(nodeMovedToRemovedNodePosition.getName(), removingNodeIndex); } order = last; clearCache(); return true; } /** * */ @Override public int outDegree(@Nonnull LabeledNode vertex) { final int nodeIndex; if ((nodeIndex = nodeName2index.getInt(vertex.getName())) == Constants.INT_NULL) { return Constants.INT_NULL; } final ObjectList> cache = outEdgesCache.get(vertex); if (cache != null) { return cache.size(); } int count = 0; final int n = order; for (int i = n; --i >= 0; ) { if (adjacency[nodeIndex][i] != null) { count++; } } return count; } /** * @param graphName the name to set */ public void setName(@Nonnull final String graphName) { name = graphName; } /** * */ @Override public boolean removeEdge(@Nonnull E edge) { return removeEdge(edge.getName()); } /** * Sets the type of network.
* Warning: this method was introduced for setting the PSTN type because it is not simple to determine it from the kind of edges. Don't use this method for other types because the constructor sets the type automatically. Changing the type of network after its creation can make many edge properties unreadable. * * @param type is a new type for the network. */ public void setType(NetworkType type) { if (type == null) { return; } this.type = type; } /** * Removes all empty edges in the graph. * * @return true if at least one edge was removed. */ public boolean removeEmptyEdges() { boolean removed = false; for (final E e : getEdges()) { if (e.isEmpty()) { removeEdge(e); removed = true; } } return removed; } /** * Sets {@code z} as the first node (Zero node) of the temporal constraint network. * * @param z the node to be set as the Z node of the network. If z is null, then the Z information is nullified. */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "For efficiency reasons, it includes an external mutable object.") public void setZ(@Nullable final LabeledNode z) { if (z == null) { Z = null; return; } if (getNode(z.getName()) == null) { addVertex(z); } Z = z; } /** * Reverse (transpose) the current graph. */ public void reverse() { transpose(); } /** * Sets the potential of each node to {@code v}. * * @param v new potential value */ public void setAllPotential(final int v) { this.getVertices().forEach((node) -> node.setPotential(v)); } /** * @param file a {@link java.io.File} object. */ public void setInputFile(File file) { inputFile = file; } /** * Takes from g1 all its internal structures. *
* This method is useful for copying the references of the internal data structure of the given graph 'g' into the current. *
* After this method, the internal structures with input g are shared. * * @param g1 the graph to cannibalize. */ @SuppressWarnings("unchecked") public void takeFrom(@Nonnull final TNGraph g1) { final TNGraph g = (TNGraph) g1; adjacency = g.adjacency; aLabelAlphabet = g.aLabelAlphabet; childrenOfObserver = g.childrenOfObserver; edge2index = g.edge2index; edgeFactory = g.edgeFactory; index2node = g.index2node; inputFile = g.inputFile; lowerCaseEdges = g.lowerCaseEdges; name = g.name; nodeName2index = g.nodeName2index; observer2Z = g.observer2Z; order = g.order; proposition2Observer = g.proposition2Observer; Z = g.Z; type = g.type; inEdgesCache = g.inEdgesCache; outEdgesCache = g.outEdgesCache; } /** * */ @Override public @Nonnull String toString() { final StringBuilder sb = new StringBuilder("%TNGraph: " + ((name != null) ? name : (inputFile != null) ? inputFile.toString() : "no name") // + "\n%TNGraph Syntax\n" // + "%LabeledNode: \n" // + "%T: " + "\n"); sb.append("%Nodes:\n"); for (final LabeledNode n : getVertices().stream().sorted().toList()) { sb.append(n.toString()); sb.append("\n"); } sb.append("%Edges:\n"); for (final E e : getEdges().stream().sorted().toList()) { sb.append(Objects.requireNonNull(getSource(e))).append("--").append(e).append("-->").append(Objects.requireNonNull(getDest(e))); sb.append("\n"); } return sb.toString(); } /** * Transposes {@code this}, inverting only the source/destination of each edge. All other attributes of an edge are not modified. */ public void transpose() { final int n = getVertexCount(); for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { final E eIJ = adjacency[i][j]; final E eJI = adjacency[j][i]; adjacency[i][j] = eJI; adjacency[j][i] = eIJ; updateEdgeInIndex(eIJ, j, i); updateEdgeInIndex(eJI, i, j); } } inEdgesCache.clear(); outEdgesCache.clear(); } /** * builds this.observer2Z; */ private void buildObserver2ZEdgesSet() { observer2Z = new ObjectArrayList<>(); if (Z == null) { return; } final Char2ObjectMap observers = getObservedAndObserver(); for (final LabeledNode node : observers.values()) { final E e = findEdge(node, Z); if (e != null) { observer2Z.add(e); } } } /** * @param ord the wanted order of the graph * * @return a bi-dimensional size x size vector for containing T elements. */ @SuppressWarnings("unchecked") private E[][] createAdjacency(int ord) { return (E[][]) Array.newInstance(edgeFactory.getEdgeImplClass(), ord, ord); } /** * Removes input edge from {@code edge2index} and remove its observers. * * @param e input edge */ private void removeEdgeFromIndex(E e) { if (e == null) { return; } else { e.getName(); } ((AbstractEdge) e).pcs = new PropertyChangeSupport(e); // ((AbstractEdge) e).removeObserver("edgeType", this);//Don't activate this remover because it slows down // ((AbstractEdge) e).removeObserver("edgeName", this);//the DC checking a lot. edge2index.remove(e.getName()); } /** * @param e edge * @param row where the edge must be put * @param col where the edge must be put */ private void updateEdgeInIndex(E e, int row, int col) { if (e == null) { return; } else { e.getName(); } final EdgeIndex ei = edge2index.get(e.getName()); ei.rowAdj = row; ei.colAdj = col; } }