Skip to content

Commit

Permalink
Add NFAs, DFAs, and Automata documentation and coverage (#71)
Browse files Browse the repository at this point in the history
* Add documentation for determinize methods.
Add more test coverage.

* Add more DFA coverage.

* Add invasiveMinimize comments.

* Don't change code here unless necessary.

* Fix checkstyle

* add type parameter docs + formatting

---------

Co-authored-by: Markus Frohme <markus.frohme@udo.edu>
  • Loading branch information
jn1z and mtf90 committed Feb 8, 2024
1 parent 2765fd5 commit 63210a8
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 0 deletions.
25 changes: 25 additions & 0 deletions util/src/main/java/net/automatalib/util/automaton/Automata.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,28 @@ public static <S, I, T> Graph<S, TransitionEdge<I, T>> asGraph(Automaton<S, I, T
return output;
}

/**
* Minimize the given mutable DFA in-place.
*
* @param automaton
* mutable DFA
* @param inputs
* the input symbols
* @param <S>
* state type
* @param <I>
* input symbol type
* @param <T>
* transition type
* @param <SP>
* state property type
* @param <TP>
* transition property type
* @param <A>
* automaton type
*
* @return the same mutable DFA (for convenience)
*/
@SuppressWarnings("unchecked")
public static <S, I, T, SP, TP, A extends MutableDeterministic<S, I, T, SP, TP>> A invasiveMinimize(A automaton,
Collection<? extends I> inputs) {
Expand All @@ -116,6 +138,7 @@ public static <S, I, T, SP, TP, A extends MutableDeterministic<S, I, T, SP, TP>>

ResultStateRecord<SP, TP>[] records = new ResultStateRecord[mr.getNumBlocks()];

// Store minimized automaton data in the records array
for (Block<S, TransitionEdge.Property<I, TP>> blk : mr.getBlocks()) {
int id = blk.getId();
S state = mr.getRepresentative(blk);
Expand All @@ -138,6 +161,7 @@ public static <S, I, T, SP, TP, A extends MutableDeterministic<S, I, T, SP, TP>>

automaton.clear();

// Add states from records
@Nullable Object[] states = new Object[records.length];
for (int i = 0; i < records.length; i++) {
ResultStateRecord<SP, TP> rec = records[i];
Expand All @@ -151,6 +175,7 @@ public static <S, I, T, SP, TP, A extends MutableDeterministic<S, I, T, SP, TP>>
states[i] = state;
}

// Add transitions from records
for (int i = 0; i < records.length; i++) {
ResultStateRecord<SP, TP> rec = records[i];
S state = (S) states[i];
Expand Down
105 changes: 105 additions & 0 deletions util/src/main/java/net/automatalib/util/automaton/fsa/NFAs.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,38 @@ public static <I, S, A extends MutableNFA<S, I>> A impl(NFA<?, I> nfa1,
return combine(nfa1, nfa2, inputs, out, AcceptanceCombiner.IMPL);
}

/**
* Determinizes the given NFA, and returns the result as a new complete DFA.
*
* @param nfa
* the original NFA
* @param inputAlphabet
* the input alphabet
* @param <I>
* input symbol type
*
* @return the determinized NFA
*/
public static <I> CompactDFA<I> determinize(NFA<?, I> nfa, Alphabet<I> inputAlphabet) {
return determinize(nfa, inputAlphabet, false, true);
}

/**
* Determinizes the given NFA, and returns the result as a new DFA.
*
* @param nfa
* the original NFA
* @param inputAlphabet
* the input alphabet
* @param partial
* allows the new DFA to be partial
* @param minimize
* whether to minimize the DFA
* @param <I>
* input symbol type
*
* @return the determinized NFA
*/
public static <I> CompactDFA<I> determinize(NFA<?, I> nfa,
Alphabet<I> inputAlphabet,
boolean partial,
Expand All @@ -301,6 +329,22 @@ public static <I> CompactDFA<I> determinize(NFA<?, I> nfa,
return result;
}

/**
* Determinizes the given NFA, and stores the result in a given mutable DFA.
*
* @param nfa
* the original NFA
* @param inputs
* the input symbols to consider
* @param out
* a mutable DFA for storing the result
* @param partial
* allows the new DFA to be partial
* @param minimize
* whether to minimize the DFA
* @param <I>
* input symbol type
*/
public static <I> void determinize(NFA<?, I> nfa,
Collection<? extends I> inputs,
MutableDFA<?, I> out,
Expand All @@ -312,20 +356,78 @@ public static <I> void determinize(NFA<?, I> nfa,
}
}

/**
* Determinizes the given NFA, and returns the result as a new DFA.
*
* @param nfa
* the original NFA
* @param <I>
* input symbol type
* @param <A>
* automaton type
*
* @return the determinized NFA
*/
public static <I, A extends NFA<?, I> & InputAlphabetHolder<I>> CompactDFA<I> determinize(A nfa) {
return determinize(nfa, false, true);
}

/**
* Determinizes the given NFA, and returns the result as a new DFA.
*
* @param nfa
* the original NFA
* @param partial
* allows the new DFA to be partial
* @param minimize
* whether to minimize the DFA
* @param <I>
* input symbol type
* @param <A>
* automaton type
*
* @return the determinized NFA
*/
public static <I, A extends NFA<?, I> & InputAlphabetHolder<I>> CompactDFA<I> determinize(A nfa,
boolean partial,
boolean minimize) {
return determinize(nfa, nfa.getInputAlphabet(), partial, minimize);
}

/**
* Determinizes the given NFA, and stores the result in a given mutable DFA.
*
* @param nfa
* the original NFA
* @param inputs
* the input symbols to consider
* @param out
* a mutable DFA for storing the result
* @param <I>
* input symbol type
*/
public static <I> void determinize(NFA<?, I> nfa, Collection<? extends I> inputs, MutableDFA<?, I> out) {
determinize(nfa, inputs, out, false, true);
}

/**
* Determinize the given NFA via the Subset (also called Powerset) Construction.
*
* @param nfa
* the original NFA
* @param inputs
* the input symbols to consider
* @param out
* a mutable DFA for storing the result
* @param partial
* allows the new DFA to be partial
* @param <I>
* input symbol type
* @param <SI>
* state type of the input automaton
* @param <SO>
* state type of the output automaton
*/
private static <I, SI, SO> void doDeterminize(NFA<SI, I> nfa,
Collection<? extends I> inputs,
MutableDFA<SO, I> out,
Expand All @@ -336,6 +438,7 @@ private static <I, SI, SO> void doDeterminize(NFA<SI, I> nfa,

Deque<DeterminizeRecord<SI, SO>> stack = new ArrayDeque<>();

// Add union of initial states to DFA and to stack
List<SI> initList = new ArrayList<>(nfa.getInitialStates());
BitSet initBs = new BitSet();
for (SI init : initList) {
Expand All @@ -359,6 +462,7 @@ private static <I, SI, SO> void doDeterminize(NFA<SI, I> nfa,
BitSet succBs = new BitSet();
List<SI> succList = new ArrayList<>();

// Determine the union of the successors of the given state and symbol
for (SI inState : inStates) {
for (SI succState : nfa.getSuccessors(inState, sym)) {
int succId = stateIds.getStateId(succState);
Expand All @@ -372,6 +476,7 @@ private static <I, SI, SO> void doDeterminize(NFA<SI, I> nfa,
if (!partial || !succList.isEmpty()) {
SO outSucc = outStateMap.get(succBs);
if (outSucc == null) {
// add new state to DFA and to stack
outSucc = out.addState(nfa.isAccepting(succList));
outStateMap.put(succBs, outSucc);
stack.push(new DeterminizeRecord<>(succList, outSucc));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import net.automatalib.automaton.fsa.DFA;
import net.automatalib.automaton.fsa.impl.CompactDFA;
import net.automatalib.util.automaton.Automata;
import net.automatalib.util.ts.acceptor.AcceptanceCombiner;
import org.testng.Assert;
import org.testng.annotations.Test;

Expand Down Expand Up @@ -67,6 +68,14 @@ private CompactDFA<Integer> forVector(boolean... boolVec) {
return result;
}

@Test
public void testCombine() {
DFA<?, Integer> expected = forVector(AND_RESULT);
DFA<?, Integer> actual = DFAs.combine(testDfa1, testDfa2, testAlphabet, AcceptanceCombiner.AND);

Assert.assertTrue(Automata.testEquivalence(actual, expected, testAlphabet));
}

@Test
public void testAnd() {
DFA<?, Integer> expected = forVector(AND_RESULT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import net.automatalib.automaton.fsa.impl.CompactDFA;
import net.automatalib.automaton.fsa.impl.CompactNFA;
import net.automatalib.util.automaton.Automata;
import net.automatalib.util.ts.acceptor.AcceptanceCombiner;
import net.automatalib.word.Word;
import org.testng.Assert;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -67,6 +68,13 @@ private CompactNFA<Integer> forVector(boolean... boolVec) {
return result;
}

@Test
public void testCombine() {
NFA<?, Integer> expected = forVector(AND_RESULT);
NFA<?, Integer> actual = NFAs.combine(testNfa1, testNfa2, testAlphabet, AcceptanceCombiner.AND);
assertEquivalence(actual, expected, testAlphabet);
}

@Test
public void testAnd() {
NFA<?, Integer> expected = forVector(AND_RESULT);
Expand Down Expand Up @@ -126,6 +134,8 @@ public void testDeterminize() {
CompactDFA<Integer> dfa = NFAs.determinize(nfa);

Assert.assertEquals(dfa.size(), 2);
Assert.assertTrue(dfa.accepts(Word.fromSymbols(0, 1, 0, 1)));
Assert.assertFalse(dfa.accepts(Word.fromSymbols(0, 1, 0, 1, 0)));
}

private <I> void assertEquivalence(NFA<?, I> nfa1, NFA<?, I> nfa2, Alphabet<I> inputs) {
Expand Down

0 comments on commit 63210a8

Please sign in to comment.