All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.chocosolver.util.objects.graphs.MultivaluedDecisionDiagram Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2024, IMT Atlantique. All rights reserved.
 *
 * Licensed under the BSD 4-clause license.
 *
 * See LICENSE file in the project root for full license information.
 */
package org.chocosolver.util.objects.graphs;

import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntIntHashMap;
import org.chocosolver.solver.constraints.extension.Tuples;
import org.chocosolver.solver.variables.IntVar;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * A Multi-valued Decision Diagram (MDD for short) to store
 * 

* Created by cprudhom on 30/10/14. */ public class MultivaluedDecisionDiagram { /** * Define when to compact the MDD. * There are three options: *

    *
  • NEVER: Never compact the MDD
  • *
  • ONCE: Compact the MDD after having added all the tuples
  • *
  • EACH: Compact the MDD after each tuple addition (this is the default value)
  • *
*/ public enum Compact { /** * Never compact the MDD */ NEVER, /** * Compact the MDD after having added all the tuples */ ONCE, /** * Compact the MDD after each tuple addition */ EACH } /** * The terminal node. An extreme (likely unused) value is set */ public static final int TERMINAL = -1; /** * Express "no edge" information */ public static final int EMPTY = 0; /** * Store the number of variables */ private final int nbLayers; /** * Initial domain size */ private final int[] sizes; /** * Initial domain offset */ private final int[] offsets; /** * Store all possible edges for the i^th node, 'EMPTY' otherwise */ private int[] mdd; /** * Point to the first clear cell in 'mdds' */ private int nextFreeCell; private final Compact compact; private final boolean sortTuples; // TEMPORARY DATA STRUCTURE, PREFIX WITH "_", CLEARED AFTER USAGE private TIntIntHashMap _nodesToRemove; // store the nodes to remove and the size of each node private ArrayList[][] _identicalNodes; // store child nodes of a node private TIntArrayList[][] _nodeId; // store node id per layer and nb of mdds private int _removedCells; // define the number of cells erased by the compaction private int[] _pos; private static int[][] flattenDomain(IntVar[] VARIABLES) { int[][] FLATDOM = new int[VARIABLES.length][]; for (int i = 0; i < VARIABLES.length; i++) { int lb = VARIABLES[i].getLB(); int ub = VARIABLES[i].getUB(); int size = ub - lb + 1; FLATDOM[i] = new int[size]; for (int j = 0; j < size; j++) { FLATDOM[i][j] = j + lb; } } return FLATDOM; } /** * Create an MDD based on an array of flatten domains and a set of tuples * Note that the MDD is compacted once all tuples are added. * * @param VARIABLES array of flatten domains * @param TUPLES set of (allowed) tuples */ public MultivaluedDecisionDiagram(IntVar[] VARIABLES, Tuples TUPLES) { this(flattenDomain(VARIABLES), TUPLES); } /** * Create an MDD based on an array of flatten domains and a set of tuples * * @param VARIABLES array of flatten domains * @param TUPLES set of (allowed) tuples * @param compact define when to compact the MDD * @param sortTuple set to true to sort the TUPLES in increasing order before adding them */ public MultivaluedDecisionDiagram(IntVar[] VARIABLES, Tuples TUPLES, Compact compact, boolean sortTuple) { this(flattenDomain(VARIABLES), TUPLES, compact, sortTuple); } /** * Create an MDD based on an array of flatten domains and a set of tuples * Note that the MDD is compacted once all tuples are added. * * @param FLATDOM array of flatten domains * @param TUPLES set of (allowed) tuples */ public MultivaluedDecisionDiagram(int[][] FLATDOM, Tuples TUPLES) { this(FLATDOM, TUPLES, Compact.ONCE, false); } /** * Create an MDD based on an array of flatten domains and a set of tuples * * @param FLATDOM array of flatten domains * @param TUPLES set of (allowed) tuples * @param compact define when to compact the MDD * @param sortTuple set to true to sort the TUPLES in increasing order before adding them */ public MultivaluedDecisionDiagram(int[][] FLATDOM, Tuples TUPLES, Compact compact, boolean sortTuple) { this.nbLayers = FLATDOM.length; this.offsets = new int[nbLayers]; this.sizes = new int[nbLayers]; this.compact = compact; this.sortTuples = sortTuple; int maxDom = 0; for (int i = 0; i < nbLayers; i++) { offsets[i] = FLATDOM[i][0]; sizes[i] = FLATDOM[i][FLATDOM[i].length - 1] - FLATDOM[i][0] + 1; if (maxDom < sizes[i]) { maxDom = sizes[i]; } } mdd = new int[nbLayers * maxDom]; init(TUPLES); } /** * Create an MDD based on an array of flatten domains and a set of transitions. * The first of the graph has to be labeled '0' and the last node of the graph has to be labeled '-1'. * Then, a transition is triple which denotes an arc from node 'f' to node 't' valued to 'v'. * Note that the MDD is compacted once all transitions are added. * * @param VARIABLES array of flatten domains * @param TRANSITIONS list of transitions */ public MultivaluedDecisionDiagram(IntVar[] VARIABLES, int[][] TRANSITIONS) { int[][] FLATDOM = flattenDomain(VARIABLES); this.nbLayers = FLATDOM.length; this.offsets = new int[nbLayers]; this.sizes = new int[nbLayers]; this.compact = Compact.ONCE; this.sortTuples = true; int maxDom = 0; for (int i = 0; i < nbLayers; i++) { offsets[i] = FLATDOM[i][0]; sizes[i] = FLATDOM[i][FLATDOM[i].length - 1] - FLATDOM[i][0] + 1; if (maxDom < sizes[i]) { maxDom = sizes[i]; } } mdd = new int[nbLayers * maxDom]; init(TRANSITIONS); } @SuppressWarnings("unchecked") private void init(Tuples TUPLES) { nextFreeCell = sizes[0]; _pos = new int[nbLayers]; _nodesToRemove = new TIntIntHashMap(16, .5f, -1, -1); _identicalNodes = new ArrayList[nbLayers][]; _nodeId = new TIntArrayList[nbLayers][]; // Then add tuples if (TUPLES.nbTuples() > 0) { if (addTuples(TUPLES) && Compact.ONCE.equals(compact)) { // compact at the end, or not compact(); } } } /** * Add all tuples within the MDD * * @param TUPLES tuples to add */ public boolean addTuples(Tuples TUPLES) { if (sortTuples) TUPLES.sort(); boolean atLeastOne = false; for (int t = 0; t < TUPLES.nbTuples(); t++) { atLeastOne |= addTuple(TUPLES.get(t)); } return atLeastOne; } /** * Add a tuple to the MDD * * @param TUPLE tuple to add */ public boolean addTuple(int[] TUPLE) { for (int i = 0; i < nbLayers; i++) { // if the tuple is out of declared domain if (TUPLE[i] < offsets[i] || TUPLE[i] >= offsets[i] + sizes[i]) { return false; } // get the position of the value relatively to the offset of each variable _pos[i] = TUPLE[i] - offsets[i]; } int p = 0; for (int i = 0; i < nbLayers; i++) { p += _pos[i]; ensureCapacity(p + sizes[i]); if (mdd[p] == EMPTY) { if (i + 1 == nbLayers) { // if this is the last variable => terminal node mdd[p] = TERMINAL; } else { // otherwise, create an edge to a new location, stated by nextFreeCell p = mdd[p] = nextFreeCell; nextFreeCell += sizes[i + 1]; } } else { // if the child already exists p = mdd[p]; } } if (Compact.EACH.equals(compact)) { // compact during the addition or not compact(); } return true; } @SuppressWarnings("unchecked") private void init(int[][] TRANSITIONS) { nextFreeCell = sizes[0]; _pos = new int[nbLayers]; Arrays.sort(TRANSITIONS, (t1, t2) -> { int d = t1[0] - t2[0]; if (d == 0) { return t1[2] - t2[2]; } return d; }); _nodesToRemove = new TIntIntHashMap(16, .5f, -1, -1); _identicalNodes = new ArrayList[nbLayers][]; _nodeId = new TIntArrayList[nbLayers][]; // Then add tuples // 0 is the root node // -1 is the target node int pf = 0; TIntIntHashMap node = new TIntIntHashMap(16, 1.5f, -2, -2); TIntIntHashMap posi = new TIntIntHashMap(16, 1.5f, -2, -2); for (int t = 0; t < TRANSITIONS.length; t++) { //addTransition(TRANSITIONS[t]); int f = TRANSITIONS[t][0]; int v = TRANSITIONS[t][1]; int d = TRANSITIONS[t][2]; if (f == 0) { if (v < offsets[f] || v >= offsets[f] + sizes[f]) { continue; } int p = v - offsets[f]; ensureCapacity(p + sizes[f]); assert mdd[p] == EMPTY; if (d == -1) { // if this is the last variable => terminal node mdd[p] = TERMINAL; } else { // otherwise, create an edge to a new location, stated by nextFreeCell if (!node.containsKey(d)) { mdd[p] = nextFreeCell; pf = f + 1; node.putIfAbsent(d, f + 1); posi.putIfAbsent(d, nextFreeCell); nextFreeCell += sizes[f + 1]; } else { mdd[p] = posi.get(d); } } } else { int _f = node.get(f); if (v < offsets[_f] || v >= offsets[_f] + sizes[_f]) { continue; } int p = posi.get(f) + v - offsets[_f]; if (pf != _f) { // detectIsomorphism(0, pf); // deleteIsomorphism(); pf = f; } ensureCapacity(p + sizes[_f]); if (mdd[p] == EMPTY) { if (d == -1) { // if this is the last variable => terminal node mdd[p] = TERMINAL; } else { // otherwise, create an edge to a new location, stated by nextFreeCell if (!node.containsKey(d)) { mdd[p] = nextFreeCell; node.putIfAbsent(d, _f + 1); posi.putIfAbsent(d, nextFreeCell); nextFreeCell += sizes[_f + 1]; } else { mdd[p] = posi.get(d); } } } } } compact(); } /** * Ensure all data structure are correctly sized. * * @param nsize new size expected */ private void ensureCapacity(int nsize) { if (nsize > mdd.length) { int[] _mdd = mdd; mdd = new int[nsize * 3 / 2 + 1]; System.arraycopy(_mdd, 0, mdd, 0, _mdd.length); } } /** * Compact the MDD by removing equivalent branches */ @SuppressWarnings("unchecked") private void compact() { long card = Arrays.stream(sizes).mapToLong(i -> (long) i) .reduce((a, b) -> a * b).getAsLong(); if (card <= 2_000_000) { _nodesToRemove.clear(); for (int i = 0; i < nbLayers; i++) { _identicalNodes[i] = new ArrayList[sizes[i]]; _nodeId[i] = new TIntArrayList[sizes[i]]; } _removedCells = 0; detectIsomorphism(0, 0); deleteIsomorphism(); } } /** * For a given node related to a given variable (defined by layer), evaluate if two or more mdds are strictly equal. * * @param node node in the MDD * @param layer rank of the variable * @return the node id */ private int detectIsomorphism(int node, int layer) { int[] nodeChild = new int[sizes[layer]]; int nbChild = -1; for (int i = 0; i < sizes[layer]; i++) { switch (mdd[node + i]) { case EMPTY: // nothing to be done break; case TERMINAL: // a terminal node nbChild++; nodeChild[i] = TERMINAL; break; default: // a non terminal node nbChild++; mdd[node + i] = nodeChild[i] = detectIsomorphism(mdd[node + i], layer + 1); break; } } return doStuff(node, layer, nbChild, nodeChild); } private int doStuff(int node, int layer, int nbChild, int[] nodeChild) { boolean known = false; if (_identicalNodes[layer][nbChild] == null) { _identicalNodes[layer][nbChild] = new ArrayList<>(); _nodeId[layer][nbChild] = new TIntArrayList(); } else { for (int j = _identicalNodes[layer][nbChild].size() - 1; j >= 0; j--) { int[] currentNode = _identicalNodes[layer][nbChild].get(j); boolean found = _nodeId[layer][nbChild].get(j) != node; // deal with previously analyzed nodes known |= !found; if (found && Arrays.equals(currentNode, nodeChild)) { int insert = _nodesToRemove.put(node, sizes[layer]); if (insert == -1) { _removedCells += sizes[layer]; } return _nodeId[layer][nbChild].get(j); } } } if (!known) { _nodeId[layer][nbChild].add(node); _identicalNodes[layer][nbChild].add(nodeChild); } return node; } /** * Prune the dead branch of the MDD, based on nodeToRemove. */ private void deleteIsomorphism() { int[] compacted = new int[nextFreeCell - _removedCells]; int[] nodes = new int[_nodesToRemove.size() + 1]; int[] gains = new int[_nodesToRemove.size() + 1]; int idx = 1; if (_nodesToRemove.isEmpty()) { // If no equality detected, simply resize the array System.arraycopy(mdd, 0, compacted, 0, nextFreeCell); mdd = compacted; } else { int to, from = 0; int gain = 0; int[] keys = _nodesToRemove.keys(); Arrays.sort(keys); // otherwise, iterate over nodes to remove for (int k : keys) { // node to remove nodes[idx] = k; to = nodes[idx] - nodes[idx - 1] - gain; System.arraycopy(mdd, nodes[idx - 1] + gain, compacted, from, to); from += to; gain = _nodesToRemove.get(k); gains[idx] = gains[idx - 1] + gain; idx++; } System.arraycopy(mdd, nodes[idx - 1] + gain, compacted, from, compacted.length - from); for (int i = 0; i < compacted.length; i++) { if (compacted[i] > EMPTY) { compacted[i] -= gains[searchClosest(nodes, compacted[i])]; } } mdd = compacted; nextFreeCell -= _removedCells; } } protected int searchClosest(int[] a, int key) { int low = 0; int high = a.length - 1; int mid = (low + high) >> 1; while (low + 1 < high) { if (a[mid] > key) { high = mid; } else { low = mid; } mid = (low + high) >> 1; } if (a[high] <= key) { return high; } else { return low; } } /** * Return the diagram (not a copy) of the MDD */ public int[] getDiagram() { return mdd; } /** * Return the initial domain size of the variable in layer * * @param layer index of the variable * @return original domain size */ public int getNodeSize(int layer) { return sizes[layer]; } /** * Return the initial LB of the variable in layer * * @param layer index of the variable * @return original offset */ public int getOffset(int layer) { return offsets[layer]; } /** * Return the edge valued in the k^th cell of the diagram * * @param k index of the cell */ public int getEdge(int k) { return mdd[k]; } /** * Return true is PATH has a support in the mdd * * @param PATH array of value * @return true if PATH is valid */ public boolean exists(int... PATH) { if (PATH.length == nbLayers) { int p = 0; for (int i = 0; i < nbLayers; i++) { p += PATH[i] - offsets[i]; if (p >= mdd.length || mdd[p] == EMPTY) { return false; } else if (i + 1 == nbLayers) { // if this is the last variable => terminal node return mdd[p] == TERMINAL; } else { p = mdd[p]; } } } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy