// SPDX-FileCopyrightText: 2020 Roberto Posenato // // SPDX-License-Identifier: LGPL-3.0-or-later package it.univr.di.labeledvalue; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap.BasicEntry; import it.unimi.dsi.fastutil.objects.*; import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; import it.univr.di.Debug; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Serial; import java.io.Serializable; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; /** * Allows to manage conjoin-upper-case values that are also associated to propositional labels.
Labeled values are grouped by alphabetic labels * {@link ALabel}. Each group of labeled values is represented as {@link LabeledIntTreeMap}.
Therefore, such a class realizes the map * {@link ALabel}-->{@link LabeledIntTreeMap}.
*

* Be careful!
Since lower-case value are singular for each edge, it not convenient to represent it as LabeledALabelIntTreeMap.
A specialized class has * been developed to represent such values: {@link LabeledLowerCaseValue}. *

* At the start of this implementation, I made some experiments for evaluating if it is better to use a Object2ObjectRBTreeMap or a ObjectArrayMap for * representing the internal map. In October 2017, I verified that even with medium-size network, some edges can contain around 5000 labeled UC values. So, I * tested the two implementations again considering up to 10000 labeled UC values. It resulted that up to 1000 values, the two implementations show still almost * equivalent performance, BUT when the keys are ≥5000, to retrieve the keys from ObjectArrayMap requires more than ONE hour, while it requires almost ~96 ms * using Object2ObjectRBTreeMap! Details using Object2ObjectRBTreeMap: * *

 * Time to retrieve 50 elements using entrySet(): ---
 * Time to retrieve 50 elements using aLabelSet(): 0.012000000000000004ms
 *
 * Time to retrieve 100 elements using entrySet(): ---
 * Time to retrieve 100 elements using aLabelSet(): 0.006000000000000003ms
 *
 * Time to retrieve 1000 elements using entrySet(): 0.045ms
 * Time to retrieve 1000 elements using aLabelSet(): 0.034ms
 * The difference is 0.025000000000000015 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 5000 elements using entrySet(): 0.9623700000000001ms
 * Time to retrieve 5000 elements using aLabelSet(): 0.352ms
 * The difference is 0.6139400000000002 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 10000 elements using entrySet(): --
 * Time to retrieve 10000 elements using aLabelSet(): 1.292ms
 * 
* Considering then, RB Tree instead of RB Tree: * *
 * Time to retrieve 50 elements using aLabelSet(): 0.012000000000000002ms
 *
 * Time to retrieve 100 elements using aLabelSet(): 0.007ms
 *
 * Time to retrieve 1000 elements using entrySet(): ---
 * Time to retrieve 1000 elements using aLabelSet(): 0.038ms
 * The difference is 0.025000000000000015 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 5000 elements using entrySet(): ---
 * Time to retrieve 5000 elements using aLabelSet(): 0.388ms
 * The difference is 0.6139400000000002 ms. It is better to use: aLabelSet() approach.
 *
 * Time to retrieve 10000 elements using entrySet(): --
 * Time to retrieve 10000 elements using aLabelSet(): 1.314ms
 * 
* * All code for testing is in LabeledALabelIntTreeMapTest class (not public available). * * @author Roberto Posenato * @version $Rev$ */ @SuppressWarnings("UnusedReturnValue") public class LabeledALabelIntTreeMap implements Serializable { /** * A read-only view of a LabeledALabelIntTreeMap object. * * @author posenato */ public static class LabeledALabelIntTreeMapView extends LabeledALabelIntTreeMap { /** * */ @Serial private static final long serialVersionUID = 1L; /** * @param inputMap the input map */ public LabeledALabelIntTreeMapView(LabeledALabelIntTreeMap inputMap) { this.map = inputMap.map; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(Label newLabel, ALabel newAlabel, int newValue, boolean force) { return false; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(Label l, ALabel p, int i) { return false; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(String label, ALabel p, int i) { return false; } /** * Object Read-only. It does nothing. */ @Override public boolean mergeTriple(String label, ALabel p, int i, boolean force) { return false; } // /** // * Object Read-only. It does nothing. // */ // @Nullable // @Override // public LabeledIntTreeMap put(ALabel upperCaseLabel, LabeledIntMap labeledValueMap) { // return null; // } /** * Object Read-only. It does nothing. */ @Override public boolean putTriple(Label l, ALabel p, int i) { return false; } /** * Object Read-only. It does nothing. */ @Override public int remove(Label l, ALabel p) { return Constants.INT_NULL; } } /** * Keyword \w because it is necessary to accept also node names! */ @SuppressWarnings("RegExpRedundantEscape") static final String labelCharsRE = ALabelAlphabet.ALETTER + ALabel.ALABEL_SEPARATORstring + ",\\-" + Constants.NOTstring + Constants.EMPTY_LABELstring + Constants.INFINITY_SYMBOLstring + Constants.UNKNOWNstring + Constants.EMPTY_UPPER_CASE_LABELstring + Literal.PROPOSITIONS; /** * Matcher for RE */ @SuppressWarnings("RegExpRedundantEscape") static final Pattern patternLabelCharsRE = Pattern.compile("\\{[\\(" + labelCharsRE + "\\) ]*\\}"); /** * Matcher for RE */ private static final Pattern PARENTESIS_PATTERN = Pattern.compile("[{}]"); /** * logger */ private static final Logger LOG = Logger.getLogger(LabeledALabelIntTreeMap.class.getName()); /** * Serial number */ @Serial private static final long serialVersionUID = 3L; /** * Parse a string representing a LabeledValueTreeMap and return an object containing the labeled values represented by the string.
The format of the * string is given by the method {@link #toString()}. For historical reasons, the method is capable to parse two different map * format:{@code "{[(⟨label⟩, ⟨Alabel⟩, ⟨value⟩) ]*}} or * {@code "{[(⟨Alabel⟩, ⟨value⟩, ⟨label⟩) ]*}"}, where [a]* is a meta constructor for saying zero o more 'a'. * * @param arg a {@link String} object. * @param alphabet the alphabet to use to code the labels * @param labeledValueMapImple the class used for storing labeled values. If null, it is {@link LabeledIntMapSupplier#DEFAULT_LABELEDINTMAP_CLASS}. * * @return a LabeledPairMap object if args represents a valid map, null otherwise. */ @Nullable public static LabeledALabelIntTreeMap parse(String arg, ALabelAlphabet alphabet, final Class labeledValueMapImple) { // final Pattern splitterNode = Pattern.compile("〈|; "); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Begin parse: " + arg); } } if ((arg == null) || (arg.length() < 3)) { return null; } if (!patternLabelCharsRE.matcher(arg).matches()) { return null; } final LabeledALabelIntTreeMap newMap = new LabeledALabelIntTreeMap(labeledValueMapImple); arg = PARENTESIS_PATTERN.matcher(arg).replaceAll(""); // arg = arg.substring(1, arg.length() - 2); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Before split: '" + arg + "'"); } } @SuppressWarnings("RegExpSingleCharAlternation") final Pattern splitterEntry = Pattern.compile("\\)|\\("); final String[] entryThreesome = splitterEntry.split(arg); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("EntryThreesome: " + Arrays.toString(entryThreesome)); } } final Pattern splitterTriple = Pattern.compile(", "); if (alphabet == null) { alphabet = new ALabelAlphabet(); } int j; String labelStr, aLabelStr, valueStr; for (final String s : entryThreesome) { if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("s: '" + s + "'"); } } if (s.length() > 1) {// s can be empty or a space. final String[] triple = splitterTriple.split(s); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("triple: " + Arrays.toString(triple)); } } Label l = Label.parse(triple[2]); if (l == null) { // probably it is the old format labelStr = triple[0]; aLabelStr = triple[1]; valueStr = triple[2]; } else { // new format aLabelStr = triple[0]; valueStr = triple[1]; labelStr = triple[2]; } if (l == null) { l = Label.parse(labelStr); } if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Label: " + l); } } if (valueStr.equals("-" + Constants.INFINITY_SYMBOLstring)) { j = Constants.INT_NEG_INFINITE; } else { j = Integer.parseInt(valueStr); } if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Value: " + j); } } // LabeledNode is represented as " 〈; {}; Obs: null〉 " // final String nodePart = labLitInt[1];//splitterNode.split(labLitInt[1]); // System.out.println(aLabelStr); final ALabel node = ALabel.parse(aLabelStr, alphabet); if (Debug.ON) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("LabeledNode: " + node); } } newMap.mergeTriple(l, node, j, false); } } return newMap; } /** * Merges a label case value {@code (p,l,i)}. *

* The value is insert if there is not a labeled value in the set with label {@code (l,p)} or it is present with a value higher than {@code i}. *

* The method can remove or modify other labeled values of the set in order to minimize the labeled values present guaranteeing that no info is lost. * * @param newLabel a {@link Label} object. * @param newAlabel a case name. * @param newValue the new value. * @param force true if the value has to be stored without label optimization. * * @return true if the triple is stored, false otherwise. */ public boolean mergeTriple(final Label newLabel, final ALabel newAlabel, final int newValue, final boolean force) { if (!force && alreadyRepresents(newLabel, newAlabel, newValue)) { return false; } final int prioriNewAlabelMapSize; final int newAlabelSize = newAlabel.size(); LabeledIntMap newAlabelMap = map.get(newAlabel); if (newAlabelMap == null) { newAlabelMap = (new LabeledIntMapSupplier<>(this.labeledValueMapImpl)).get(); map.put(ALabel.clone(newAlabel), newAlabelMap); prioriNewAlabelMapSize = 0; } else { prioriNewAlabelMapSize = newAlabelMap.size(); } final boolean added; if (force) { newAlabelMap.putForcibly(newLabel, newValue); added = true; } else { added = newAlabelMap.put(newLabel, newValue); } // update the count count += newAlabelMap.size() - prioriNewAlabelMapSize; if (force) { return true; } /* * 2017-10-31 * Algorithm removes all a-labeled values that will become redundant after the insertion of the input a-labeled value. * The a-label removed contain newALabel strictly. * I verified that following optimization reduces global computation time. */ final boolean isNewAlabelMapModified = prioriNewAlabelMapSize == newAlabelMap.size(); final ObjectSet> newAlabelEntrySet = newAlabelMap.entrySet();//read-only LabeledIntMap otherALabelValueMap; for (final ALabel otherALabel : aLabelSet()) {//scan the other labeledValueMaps // Check only a-labels that contain newALabel strictly. if (otherALabel.equals(newAlabel) || otherALabel.size() < newAlabelSize || !otherALabel.contains(newAlabel)) { continue; } otherALabelValueMap = get(otherALabel); for (final Label otherLabel : otherALabelValueMap.keySet()) { // otherALabelValueMap could be modified final int otherValue = otherALabelValueMap.get(otherLabel); if (isNewAlabelMapModified) { // it is necessary to check all values in the newAlabelMap for (final Entry

    *
  1. A Upper/Lower Case value is a pair (nodeName, value) where nodeName is a name of a node * and can be written either in all UPPER case or in all lower case. * Such kind of constraint has been introduced by Morris Muscettola 2005. *
  2. A labeled Upper/Lower Case value is a pair (nodeName, (proposition_label, value)), where proposition_label * represents scenario where value holds. * Such kind of constraint has been introduced by Hunsberger, Combi, and Posenato in 2012. *
  3. Each proposition_label is a conjunction of literals, i.e., of type {@link Label}.
  4. *
  5. Since there may be more pairs with the same 'nodeName', a labeled Upper/Lower Case value is as a map of (nodeName, LabeledIntMap).
    * See {@link LabeledIntMap}. *
  6. In 2017-10, nodeName has been substituted by Alabel. ALabel represent the name of a node or a conjunction of node names.
    * Such modification has been introduced because CSTNU DC checking algorithm requires such kind of values. *
*/ // ObjectArrayMap is not suitable when the map is greater than 1000 values! Object2ObjectRBTreeMap map;//it is important to maintain the a-label ordered. /** * Constructor to clone the structure. All internal maps will be independent of 'lvm' ones. * * @param lvm the map to clone. If null, 'this' will be an empty map. * @param labeledValueMapImple the class used for storing labeled values. If null, it is {@link LabeledIntMapSupplier#DEFAULT_LABELEDINTMAP_CLASS}. */ public LabeledALabelIntTreeMap(final LabeledALabelIntTreeMap lvm, final Class labeledValueMapImple) { this(labeledValueMapImple); if (lvm == null) { return; } final LabeledIntMapSupplier labeledIntMapSupplier = new LabeledIntMapSupplier<>(this.labeledValueMapImpl); for (final ALabel alabel : lvm.aLabelSet()) { final LabeledIntMap map1 = labeledIntMapSupplier.get(lvm.get(alabel)); map.put(alabel, map1); count += map1.size(); } } /** * Simple constructor. The internal structure is built and empty. * * @param labeledValueMapImple the class used for storing labeled values. If null, it is {@link LabeledIntMapSupplier#DEFAULT_LABELEDINTMAP_CLASS}. */ public LabeledALabelIntTreeMap(final Class labeledValueMapImple) { map = new Object2ObjectRBTreeMap<>(); count = 0; this.labeledValueMapImpl = (labeledValueMapImple == null) ? LabeledIntMapSupplier.DEFAULT_LABELEDINTMAP_CLASS : labeledValueMapImple; } /** * Simple constructor. The internal structure is built and empty. */ private LabeledALabelIntTreeMap() { } /** * */ public void clear() { map.clear(); count = 0; } /** * @return the minimal value of this map in the format ({@link Label}, ({@link ALabel}, int)).
If the map is null, returns the entry * ({@link Label#emptyLabel}, ({@link ALabel#emptyLabel}, {@link Constants#INT_NULL})). */ @SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE", justification = "v is used.") public Object2ObjectMap.Entry> getMinValue() { if (size() == 0) { return new AbstractObject2ObjectMap.BasicEntry<>(Label.emptyLabel, new BasicEntry<>(ALabel.emptyLabel, Constants.INT_NULL)); } int min = Integer.MAX_VALUE; int v; Entry