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

net.automatalib.util.minimizer.Minimizer Maven / Gradle / Ivy

Go to download

This artifact provides various common utility operations for analyzing and manipulating automata and graphs, such as traversal, minimization and copying.

There is a newer version: 0.11.0
Show newest version
/* Copyright (C) 2013 TU Dortmund
 * This file is part of AutomataLib, http://www.automatalib.net/.
 * 
 * AutomataLib is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 3.0 as published by the Free Software Foundation.
 * 
 * AutomataLib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with AutomataLib; if not, see
 * http://www.gnu.de/documents/lgpl.en.html.
 */
package net.automatalib.util.minimizer;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.automatalib.commons.smartcollections.DefaultLinkedList;
import net.automatalib.commons.smartcollections.ElementReference;
import net.automatalib.commons.smartcollections.IntrusiveLinkedList;
import net.automatalib.commons.smartcollections.UnorderedCollection;
import net.automatalib.commons.util.mappings.MutableMapping;
import net.automatalib.graphs.UniversalGraph;
import net.automatalib.util.graphs.traversal.GraphTraversal;


/**
 * Automaton minimizer. The automata are accessed via the 
 * {@link UniversalGraph} interface, and may be partially defined. Note that
 * undefined transitions are preserved, thus, they have no semantics that
 * could be modeled otherwise wrt. this algorithm.
 * 

* The implemented algorithm is described in the paper "Minimizing incomplete * automata" by Marie-Pierre Beal and Maxime Crochemore. * * @author Malte Isberner * * @param state class. * @param transition label class. */ public class Minimizer { private static final ThreadLocal> LOCAL_INSTANCE = new ThreadLocal>() { @Override protected Minimizer initialValue() { return new Minimizer(); } }; /** * Retrieves the local instance of this minimizer. * * The minimizer acts like a singleton of which each thread possesses their * own. The minimizer instance returned by this method is the one belonging * to the calling thread. Therefore, it is not safe to share such an instance * between two threads. * * @param state class. * @param transition label class. * @return The minimizers local instance. */ @SuppressWarnings("unchecked") public static Minimizer getLocalInstance() { return (Minimizer)LOCAL_INSTANCE.get(); } /** * Minimizes an automaton. The automaton is not minimized directly, instead, * a {@link MinimizationResult} structure is returned. The automaton is * interfaced using an adapter implementing the {@link UniversalGraph} * interface. * * @param state class. * @param transition label class. * @param graph the automaton interface. * @return the result structure. */ public static MinimizationResult minimize(UniversalGraph graph) { return minimize(graph, null); } public static MinimizationResult minimize(UniversalGraph graph, Collection start) { Minimizer minimizer = getLocalInstance(); return minimizer.performMinimization(graph, start); } // These attributes belong to a specific minimization process. private MutableMapping> stateStorage; private UnorderedCollection> partition; private int numBlocks; // The following attributes may be reused. Most of them are used // as local variables in the split() method, but storing them // as attributes helps to avoid costly re-allocations. private final DefaultLinkedList> splitters = new DefaultLinkedList>(); private final IntrusiveLinkedList> letterList = new IntrusiveLinkedList>(); private final IntrusiveLinkedList> stateList = new IntrusiveLinkedList>(); private final IntrusiveLinkedList> splitBlocks = new IntrusiveLinkedList>(); private final IntrusiveLinkedList> newBlocks = new IntrusiveLinkedList>(); private final IntrusiveLinkedList> finalList = new IntrusiveLinkedList>(); /** * Default constructor. * @deprecated Public instantiation is deprecated. Use {@link #getLocalInstance()} * or {@link #minimize(UniversalGraph)} */ @Deprecated public Minimizer() { } public final MinimizationResult performMinimization(UniversalGraph graph) { return performMinimization(graph, null); } /** * Performs the minimization of an automaton. * * The automaton is accessed via a {@link UniversalGraph}. The result * of the minimization process is effectively a partition on the set of * states, each element (block) in this partition contains equivalent * states that can be merged in a minimized automaton. * * @param edge identifier class. * @param graph the automaton interface. * @return a {@link MinimizationResult} structure, containing the * state partition. */ public final MinimizationResult performMinimization(UniversalGraph graph, Collection initialNodes) { // Initialize the data structures (esp. state records) and build // the initial partition. Collection> initialBlocks = initialize(graph, initialNodes); // Add all blocks from the initial partition as an element // of the partition, and as a potential splitter. partition = new UnorderedCollection>(initialBlocks.size()); ///splitters.hintNextCapacity(initialBlocks.size()); for(Block block : initialBlocks) { if(block == null || block.isEmpty()) continue; addToPartition(block); addToSplitterQueue(block); numBlocks++; } // Split the blocks of the partition, until no splitters // remain while(!splitters.isEmpty()) { Block block = splitters.choose(); removeFromSplitterQueue(block); split(block); updateBlocks(); } // Return the result. MinimizationResult result = new MinimizationResult(stateStorage, partition); // Ensure the garbage collection isn't hampered stateStorage = null; partition = null; numBlocks = 0; return result; } /* * Sets the blockReference-attribute of each state in the collection * to the corresponding ElementReference of the collection. */ private static void updateBlockReferences(Block block) { UnorderedCollection> states = block.getStates(); for(ElementReference ref : states.references()) { State state = states.get(ref); state.setBlockReference(ref); state.setBlock(block); } } /* * Builds the initial data structures and performs the initial * partitioning. */ private Collection> initialize(UniversalGraph graph, Collection initialNodes) { Iterable origStates; if(initialNodes == null || initialNodes.isEmpty()) origStates = graph.getNodes(); else origStates = GraphTraversal.depthFirstOrder(graph, initialNodes); Map> transitionMap = new HashMap>(); stateStorage = graph.createStaticNodeMapping(); int numStates = 0; for(S origState : origStates) { State state = new State(numStates++, origState); stateStorage.put(origState, state); stateList.add(state); } InitialPartitioning initPartitioning = new HashMapInitialPartitioning(graph); for(State state : stateList) { S origState = state.getOriginalState(); Block block = initPartitioning.getBlock(origState); block.addState(state); for(E edge : graph.getOutgoingEdges(origState)) { S origTarget = graph.getTarget(edge); State target = stateStorage.get(origTarget); L label = graph.getEdgeProperty(edge); TransitionLabel transition = transitionMap.get(label); if(transition == null) { transition = new TransitionLabel(label); transitionMap.put(label, transition); } Edge edgeObj = new Edge(state, target, transition); state.addOutgoingEdge(edgeObj); target.addIncomingEdge(edgeObj); } } stateList.quickClear(); return initPartitioning.getInitialBlocks(); } /* * Adds a block to the partition. */ private void addToPartition(Block block) { ElementReference ref = partition.referencedAdd(block); block.setPartitionReference(ref); } /* * Adds a block as a potential splitter. */ private void addToSplitterQueue(Block block) { ElementReference ref = splitters.referencedAdd(block); block.setSplitterQueueReference(ref); } /* * Removes a block from the splitter queue. This is done when it is * split completely and thus no longer existant. */ private boolean removeFromSplitterQueue(Block block) { ElementReference ref = block.getSplitterQueueReference(); if(ref == null) return false; splitters.remove(ref); block.setSplitterQueueReference(null); return true; } /* * Adds all but the largest block of a given collection * to the splitter queue. */ private void addAllToSplitterQueue(Collection> blocks) { for(Block block : blocks) addToSplitterQueue(block); } private void addAllButLargest(Collection> blocks) { Block largest = null; for(Block block : blocks) { if(largest == null) largest = block; else if(block.size() > largest.size()) { addToSplitterQueue(largest); largest = block; } else addToSplitterQueue(block); } } /* * This method performs the actual splitting of blocks, using the * sub block information stored in each block object. */ private void updateBlocks() { for(Block block : splitBlocks) { // Ignore blocks that have no elements in their sub blocks. int inSubBlocks = block.getElementsInSubBlocks(); if(inSubBlocks == 0) continue; boolean blockRemains = (inSubBlocks < block.size()); boolean reuseBlock = !blockRemains; List>> subBlocks = block.getSubBlocks(); // If there is only one sub block which contains all elements of // the block, then no split needs to be performed. if(!blockRemains && subBlocks.size() == 1) { block.clearSubBlocks(); continue; } Iterator>> subBlockIt = subBlocks.iterator(); if(reuseBlock) { UnorderedCollection> first = subBlockIt.next(); block.getStates().swap(first); updateBlockReferences(block); } while(subBlockIt.hasNext()) { UnorderedCollection> subBlockStates = subBlockIt.next(); if(blockRemains) { for(State state : subBlockStates) block.removeState(state.getBlockReference()); } Block subBlock = new Block(numBlocks++, subBlockStates); updateBlockReferences(subBlock); newBlocks.add(subBlock); addToPartition(subBlock); } newBlocks.add(block); block.clearSubBlocks(); // If the split block previously was in the queue, add all newly // created blocks to the queue. Otherwise, it's enough to add // all but the largest if(removeFromSplitterQueue(block)) addAllToSplitterQueue(newBlocks); else addAllButLargest(newBlocks); newBlocks.clear(); } splitBlocks.clear(); } /* * This method realizes the core of the actual minimization, the "split" * procedure. * * A split separates in each block the states, if any, which have different * transition characteristics wrt. a specified block, the splitter. * * This method does not perform actual splits, but instead it modifies * the splitBlocks attribute to containing the blocks that could * potentially be split. The information * on the subsets into which a block is split is contained in the * sub-blocks of the blocks in the result list. * * The actual splitting is performed by the method updateBlocks(). */ private void split(Block splitter) { // STEP 1: Collect the states that have outgoing edges // pointing to states inside the currently considered blocks. // Also, a list of transition labels occuring on these // edges is created. for(State state : splitter.getStates()) { for(Edge edge : state.getIncoming()) { TransitionLabel transition = edge.getTransitionLabel(); State newState = edge.getSource(); // Blocks that only contain a single state cannot // be split any further, and thus are of no // interest. if(newState.isSingletonBlock()) continue; //continue; if(transition.addToSet(newState)) letterList.add(transition); } } // STEP 2: Build the signatures. A signature of a state // is a sequence of the transition labels of its outgoing // edge that point into the considered split block. // The iteration over the label list in the outer loop // guarantees a consistent ordering of the transition labels. for(TransitionLabel letter : letterList) { for(State state : letter.getSet()) { if(state.addToSignature(letter)) { stateList.add(state); state.setSplitPoint(false); } } letter.clearSet(); } letterList.clear(); // STEP 3: Discriminate the states. This is done by weak // sorting the states. At the end of the weak sort, the finalList // will contain the states in such an order that only states belonging // to the same block having the same signature will be contiguous. // First, initialize the buckets of each block. This is done // for grouping the states by their corresponding block. for(State state : stateList) { Block block = state.getBlock(); if(block.addToBucket(state)) splitBlocks.add(block); } stateList.clear(); for(Block block : splitBlocks) stateList.concat(block.getBucket()); // Now, the states are ordered according to their signatures int i = 0; while(!stateList.isEmpty()) { for(State state : stateList) { TransitionLabel letter = state.getSignatureLetter(i); if(letter == null) finalList.pushBack(state); else if(letter.addToBucket(state)) letterList.add(letter); // If this state was the first to be added to the respective // bucket, or it differs from the previous entry in the previous // letter, it is a split point. if(state.getPrev() == null) state.setSplitPoint(true); else if(i > 0 && state.getPrev().getSignatureLetter(i-1) != state.getSignatureLetter(i-1)) state.setSplitPoint(true); } stateList.clear(); for(TransitionLabel letter : letterList) stateList.concat(letter.getBucket()); letterList.clear(); i++; } Block prevBlock = null; State prev = null; for(State state : finalList) { Block currBlock = state.getBlock(); if(currBlock != prevBlock) { currBlock.createSubBlock(); prevBlock = currBlock; } else if(state.isSplitPoint()) currBlock.createSubBlock(); currBlock.addToSubBlock(state); if(prev != null) prev.reset(); prev = state; } if(prev != null) prev.reset(); finalList.clear(); // Step 4 of the algorithm is done in the method // updateBlocks() } }