
net.automatalib.incremental.dfa.AbstractIncrementalDFABuilder Maven / Gradle / Ivy
/* 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.incremental.dfa;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import net.automatalib.automata.abstractimpl.AbstractDeterministicAutomaton;
import net.automatalib.automata.concepts.StateIDs;
import net.automatalib.automata.fsa.DFA;
import net.automatalib.automata.fsa.impl.compact.CompactDFA;
import net.automatalib.automata.graphs.AbstractAutomatonGraph;
import net.automatalib.commons.util.UnionFind;
import net.automatalib.commons.util.mappings.MutableMapping;
import net.automatalib.graphs.UniversalGraph;
import net.automatalib.graphs.concepts.NodeIDs;
import net.automatalib.graphs.dot.DOTPlottableGraph;
import net.automatalib.graphs.dot.GraphDOTHelper;
import net.automatalib.incremental.ConflictException;
import net.automatalib.incremental.IncrementalConstruction;
import net.automatalib.words.Alphabet;
import net.automatalib.words.Word;
import net.automatalib.words.WordBuilder;
public abstract class AbstractIncrementalDFABuilder extends
AbstractDeterministicAutomaton implements
UniversalGraph,
DOTPlottableGraph,
IncrementalConstruction, I> {
protected final Map register = new HashMap<>();
protected final Alphabet inputAlphabet;
protected final int alphabetSize;
protected final State init;
protected State sink;
public AbstractIncrementalDFABuilder(Alphabet inputAlphabet) {
this.inputAlphabet = inputAlphabet;
this.alphabetSize = inputAlphabet.size();
StateSignature sig = new StateSignature(alphabetSize, Acceptance.DONT_KNOW);
this.init = new State(sig);
register.put(null, init);
}
public abstract Acceptance lookup(Word word);
public abstract void insert(Word word, boolean accepting) throws ConflictException;
/**
* Inserts a word into the set of accepted words.
* @param word the word to insert
* @throws ConflictException if the word is already contained in the set of definitely
* rejected words
*/
public final void insert(Word word) throws ConflictException {
insert(word, true);
}
/*
* (non-Javadoc)
* @see net.automatalib.ts.abstractimpl.AbstractFiniteDTS#size()
*/
@Override
public int size() {
return register.size() + ((sink != null) ? 1 : 0);
}
@Override
public Collection getNodes() {
if(sink == null)
return Collections.unmodifiableCollection(register.values());
List result = new ArrayList<>(register.size() + 1);
result.addAll(register.values());
result.add(sink);
return result;
}
@Override
public Collection getOutgoingEdges(State node) {
List edges = new ArrayList();
for(int i = 0; i < alphabetSize; i++) {
if((sink != null && node == sink) || node.getSuccessor(i) != null)
edges.add(new EdgeRecord(node, i));
}
return edges;
}
@Override
public State getTarget(EdgeRecord edge) {
if(sink != null && edge.source == sink)
return edge.source;
return edge.source.getSuccessor(edge.transIdx);
}
@Override
public Acceptance getNodeProperty(State node) {
if(sink != null && node == sink)
return Acceptance.FALSE;
return node.getAcceptance();
}
@Override
public I getEdgeProperty(EdgeRecord edge) {
return inputAlphabet.getSymbol(edge.transIdx);
}
@Override
public State getTransition(State state, I input) {
if(sink != null && state == sink)
return sink;
int idx = inputAlphabet.getSymbolIndex(input);
return state.getSuccessor(idx);
}
@Override
public State getSuccessor(State transition) {
return transition;
}
@Override
public State getInitialState() {
return init;
}
@Override
public Collection getStates() {
return getNodes();
}
@Override
public Alphabet getInputAlphabet() {
return inputAlphabet;
}
@Override
public Word findSeparatingWord(DFA, I> target, Collection extends I> inputs, boolean omitUndefined) {
return doFindSeparatingWord(target, inputs, omitUndefined);
}
@Override
public DFA, I> toAutomaton() {
CompactDFA result = new CompactDFA<>(inputAlphabet, register.size() + ((sink != null) ? 1 : 0));
Map stateMap = new HashMap<>();
for(State s : register.values()) {
Integer id;
boolean acc = (s.getAcceptance() == Acceptance.TRUE);
if(s == init)
id = result.addInitialState(acc);
else
id = result.addState(acc);
stateMap.put(s, id);
}
if(sink != null)
stateMap.put(sink, result.addState(false));
for(Map.Entry e : stateMap.entrySet()) {
State s = e.getKey();
Integer srcId = e.getValue();
for(int i = 0; i < inputAlphabet.size(); i++) {
State succ;
if(s == sink)
succ = sink;
else {
succ = s.getSuccessor(i);
if(succ == null)
continue;
}
I sym = inputAlphabet.getSymbol(i);
Integer succId = stateMap.get(succ);
result.addTransition(srcId, sym, succId);
}
}
return result;
}
/*
* (non-Javadoc)
* @see net.automatalib.incremental.IncrementalConstruction#hasDefinitiveInformation(net.automatalib.words.Word)
*/
@Override
public boolean hasDefinitiveInformation(Word word) {
State s = getState(word);
if(s == null)
return false;
if(s == sink)
return true;
return (s.getAcceptance() != Acceptance.DONT_KNOW);
}
/*
* (non-Javadoc)
* @see net.automatalib.graphs.dot.DOTPlottableGraph#getGraphDOTHelper()
*/
@Override
public GraphDOTHelper getGraphDOTHelper() {
return new DOTHelper(inputAlphabet, init);
}
@Override
public NodeIDs nodeIDs() {
return AbstractAutomatonGraph.nodeIDs(this);
}
@Override
public MutableMapping createStaticNodeMapping() {
return AbstractAutomatonGraph.createStaticNodeMapping(this);
}
@Override
public MutableMapping createDynamicNodeMapping() {
return AbstractAutomatonGraph.createDynamicNodeMapping(this);
}
private static int getStateId(State s, Map idMap) {
Integer id = idMap.get(s);
if(id != null)
return id.intValue();
idMap.put(s, id = idMap.size());
return id.intValue();
}
private static final class Record {
public final State state1;
public final S state2;
public final I reachedVia;
public final Record reachedFrom;
public final int depth;
public Record(State state1, S state2) {
this.state1 = state1;
this.state2 = state2;
this.reachedVia = null;
this.reachedFrom = null;
this.depth = 0;
}
public Record(State state1, S state2, I reachedVia, Record reachedFrom) {
this.state1 = state1;
this.state2 = state2;
this.reachedVia = reachedVia;
this.reachedFrom = reachedFrom;
this.depth = reachedFrom.depth + 1;
}
}
private Word doFindSeparatingWord(DFA target, Collection extends I> inputs, boolean omitUndefined) {
int thisStates = register.size();
Map stateIds = new HashMap<>();
if(sink != null) {
stateIds.put(sink, 0);
thisStates++;
}
int targetStates = target.size();
if(!omitUndefined)
targetStates++;
UnionFind uf = new UnionFind(thisStates + targetStates);
State init1 = init;
S init2 = target.getInitialState();
if(init2 == null && omitUndefined)
return null;
boolean acc = target.isAccepting(init2);
if(init1.getAcceptance().conflicts(acc))
return Word.epsilon();
StateIDs tgtIds = target.stateIDs();
int id1 = getStateId(init1, stateIds);
int id2 = ((init2 != null) ? tgtIds.getStateId(init2) : (targetStates - 1)) + thisStates;
uf.link(id1, id2);
Queue> queue = new ArrayDeque<>();
queue.offer(new Record(init1, init2));
I lastSym = null;
Record current;
explore:while((current = queue.poll()) != null) {
State state1 = current.state1;
S state2 = current.state2;
for(I sym : inputs) {
S succ2 = (state2 != null) ? target.getSuccessor(state2, sym) : null;
if(succ2 == null && omitUndefined)
continue;
int idx = inputAlphabet.getSymbolIndex(sym);
State succ1 = (state1 != sink) ? state1.getSuccessor(idx) : sink;
if(succ1 == null)
continue;
id1 = getStateId(succ1, stateIds);
id2 = ((succ2 != null) ? tgtIds.getStateId(succ2) : (targetStates-1)) + thisStates;
int r1 = uf.find(id1), r2 = uf.find(id2);
if(r1 == r2)
continue;
if(succ1 == sink) {
if(succ2 == null)
continue;
if(target.isAccepting(succ2)) {
lastSym = sym;
break explore;
}
}
else {
boolean succ2acc = (succ2 != null) ? target.isAccepting(succ2) : false;
if(succ1.getAcceptance().conflicts(succ2acc)) {
lastSym = sym;
break explore;
}
}
uf.link(r1, r2);
queue.offer(new Record<>(succ1, succ2, sym, current));
}
}
if(current == null)
return null;
int ceLength = current.depth;
if(lastSym != null)
ceLength++;
WordBuilder wb = new WordBuilder(null, ceLength);
int index = ceLength;
if(lastSym != null)
wb.setSymbol(--index, lastSym);
while(current.reachedFrom != null) {
wb.setSymbol(--index, current.reachedVia);
current = current.reachedFrom;
}
return wb.toWord();
}
protected abstract State getState(Word word);
/**
* Returns (and possibly creates) the canonical state for the given signature.
* @param sig the signature
* @return the canonical state for the given signature
*/
protected State replaceOrRegister(StateSignature sig) {
State state = register.get(sig);
if(state != null)
return state;
register.put(sig, state = new State(sig));
for(int i = 0; i < sig.successors.length; i++) {
State succ = sig.successors[i];
if(succ != null)
succ.increaseIncoming();
}
return state;
}
/**
* Returns the canonical state for the given state's signature, or registers the
* state as canonical if no state with that signature exists.
* @param state the state
* @return the canonical state for the given state's signature
*/
protected State replaceOrRegister(State state) {
StateSignature sig = state.getSignature();
State other = register.get(sig);
if(other != null) {
if(state != other) {
for(int i = 0; i < sig.successors.length; i++) {
State succ = sig.successors[i];
if(succ != null)
succ.decreaseIncoming();
}
}
return other;
}
register.put(sig, state);
return state;
}
protected void updateInitSignature(Acceptance acc) {
StateSignature sig = init.getSignature();
sig.acceptance = acc;
}
/**
* Updates the signature for a given state.
* @param state the state
* @param acc the new acceptance value
* @return the canonical state for the updated signature
*/
protected State updateSignature(State state, Acceptance acc) {
StateSignature sig = state.getSignature();
if(sig.acceptance == acc)
return state;
register.remove(sig);
sig.acceptance = acc;
sig.updateHashCode();
return replaceOrRegister(state);
}
protected void updateInitSignature(int idx, State succ) {
StateSignature sig = init.getSignature();
State oldSucc = sig.successors[idx];
if(oldSucc == succ)
return;
if(oldSucc != null)
oldSucc.decreaseIncoming();
sig.successors[idx] = succ;
succ.increaseIncoming();
}
/**
* Updates the signature for a given state.
* @param state the state
* @param idx the index of the transition to change
* @param succ the new successor for the above index
* @return the canonical state for the updated signature
*/
protected State updateSignature(State state, int idx, State succ) {
StateSignature sig = state.getSignature();
if(sig.successors[idx] == succ)
return state;
register.remove(sig);
if(sig.successors[idx] != null)
sig.successors[idx].decreaseIncoming();
sig.successors[idx] = succ;
succ.increaseIncoming();
sig.updateHashCode();
return replaceOrRegister(state);
}
protected State updateSignature(State state, Acceptance acc, int idx, State succ) {
StateSignature sig = state.getSignature();
if(sig.successors[idx] == succ && sig.acceptance == acc)
return state;
register.remove(sig);
sig.successors[idx] = succ;
sig.acceptance = acc;
return replaceOrRegister(state);
}
/**
* Clones a state, changing the signature.
* @param other the state to clone
* @param acc the new acceptance value
* @return the canonical state for the derived signature
*/
protected State clone(State other, Acceptance acc) {
StateSignature sig = other.getSignature();
if(sig.acceptance == acc)
return other;
sig = sig.clone();
sig.acceptance = acc;
sig.updateHashCode();
return replaceOrRegister(sig);
}
/**
* Clones a state, changing the signature.
* @param other the state to clone
* @param idx the index of the transition to change
* @param succ the new successor state
* @return the canonical state for the derived signature
*/
protected State clone(State other, int idx, State succ) {
StateSignature sig = other.getSignature();
if(sig.successors[idx] == succ)
return other;
sig = sig.clone();
sig.successors[idx] = succ;
sig.updateHashCode();
return replaceOrRegister(sig);
}
protected State clone(State other, Acceptance acc, int idx, State succ) {
StateSignature sig = other.getSignature();
if(sig.successors[idx] == succ && sig.acceptance == acc)
return other;
sig = sig.clone();
sig.successors[idx] = succ;
sig.acceptance = acc;
return replaceOrRegister(sig);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy