Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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.
/* 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 extends S> 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 extends S> 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 extends S> initialNodes) {
Iterable extends S> 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()
}
}