-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Tabakov-Vardi random automata generator #69
Changes from 1 commit
fa2cfce
a68dac5
1ed3b8e
f90eeaf
a0101bd
fa30da8
f66ce10
870311a
f66bf06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/* Copyright (C) 2013-2024 TU Dortmund University | ||
* This file is part of AutomataLib, http://www.automatalib.net/. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package net.automatalib.util.automaton.random; | ||
|
||
import java.util.Random; | ||
|
||
import net.automatalib.alphabet.Alphabet; | ||
import net.automatalib.automaton.fsa.impl.CompactNFA; | ||
import net.automatalib.common.util.random.RandomUtil; | ||
|
||
public final class TabakovVardiRandomAutomata { | ||
private TabakovVardiRandomAutomata() { | ||
// prevent instantiation | ||
} | ||
|
||
/** | ||
* Generate random NFA using Tabakov and Vardi's approach, described in the paper | ||
* <a href="https://doi.org/10.1007/11591191_28">Experimental Evaluation of Classical Automata Constructions</a> | ||
* by Deian Tabakov and Moshe Y. Vardi. | ||
* | ||
* @param r | ||
* random instance | ||
* @param size | ||
* number of states | ||
* @param td | ||
* transition density, in [0,size] | ||
* @param ad | ||
* acceptance density, in (0,1]. 0.5 is the usual value | ||
* @param alphabet | ||
* alphabet | ||
* @return | ||
* a random NFA, not necessarily connected | ||
*/ | ||
public static CompactNFA<Integer> generateNFA( | ||
Random r, int size, float td, float ad, Alphabet<Integer> alphabet) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you maybe generalize this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, easy to generalize to I'm not sure I understand the distinction with a MutableNFA change, or how to implement it (I tend to get lost in abstraction layers). So if you're okay with it, I'll leave that part like it is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I can push the changes myself afterwards. |
||
return generateNFA(r, size, Math.round(td * size), Math.round(ad * size), alphabet); | ||
} | ||
|
||
/** | ||
* Generate random NFA, with fixed number of accept states and edges (per letter). | ||
* | ||
* @param r | ||
* random instance | ||
* @param size | ||
* number of states | ||
* @param edgeNum | ||
* number of edges (per letter) | ||
* @param acceptNum | ||
* number of accepting states (at least one) | ||
* @param alphabet | ||
* alphabet | ||
* @return | ||
* a random NFA, not necessarily connected | ||
*/ | ||
public static CompactNFA<Integer> generateNFA( | ||
Random r, int size, int edgeNum, int acceptNum, Alphabet<Integer> alphabet) { | ||
assert acceptNum > 0 && acceptNum <= size; | ||
assert edgeNum >= 0 && edgeNum <= size*size; | ||
|
||
CompactNFA<Integer> nfa = basicNFA(size, alphabet); | ||
|
||
// Set final states other than the initial state. | ||
// We want exactly acceptNum-1 of them, from the elements [1,size). | ||
// This works even if acceptNum == 1. | ||
int[] finalStates = RandomUtil.distinctIntegers(r, acceptNum - 1, 1, size); | ||
for (int f : finalStates) { | ||
nfa.setAccepting(f, true); | ||
} | ||
|
||
// For each letter, add edgeNum transitions. | ||
for (int a: alphabet) { | ||
for (int edgeIndex: RandomUtil.distinctIntegers(r, edgeNum, size*size)) { | ||
nfa.addTransition(edgeIndex / size, a, edgeIndex % size); | ||
} | ||
} | ||
|
||
return nfa; | ||
} | ||
|
||
// Helper method to generate NFA with initial accepting state. | ||
private static CompactNFA<Integer> basicNFA(int size, Alphabet<Integer> alphabet) { | ||
CompactNFA<Integer> basicNFA = new CompactNFA<>(alphabet); | ||
|
||
// Create states | ||
for (int i = 0; i < size; i++) { | ||
basicNFA.addState(false); | ||
} | ||
// per the paper, the first state is always initial and accepting | ||
basicNFA.setInitial(0, true); | ||
basicNFA.setAccepting(0, true); | ||
return basicNFA; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* Copyright (C) 2013-2024 TU Dortmund University | ||
* This file is part of AutomataLib, http://www.automatalib.net/. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package net.automatalib.util.automaton.random; | ||
|
||
import java.util.Random; | ||
|
||
import net.automatalib.alphabet.Alphabet; | ||
import net.automatalib.alphabet.impl.Alphabets; | ||
import net.automatalib.automaton.fsa.impl.CompactNFA; | ||
import org.testng.Assert; | ||
import org.testng.annotations.Test; | ||
|
||
class TabakovVardiRandomAutomataTest { | ||
@Test | ||
void testGenerateNFA() { | ||
Random r = new Random(42); | ||
int size = 4; | ||
float td = 1.25f; // exactly 5 transitions per letter | ||
float ad = 0.5f; // exactly 2 accepting states | ||
Alphabet<Integer> alphabet = Alphabets.integers(0, 1); | ||
CompactNFA<Integer> compactNFA = TabakovVardiRandomAutomata.generateNFA(r, size, td, ad, alphabet); | ||
Assert.assertEquals(size, compactNFA.size()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The parameters of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Apparently I've gotten that wrong for years. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just FYI -- surprisingly, TestNG and JUnit use different parameter orders. |
||
Assert.assertEquals(1, compactNFA.getInitialStates().size()); | ||
for (int s : compactNFA.getStates()) { | ||
if (s == 0 || s == 3) { | ||
// 2 accepting states | ||
Assert.assertTrue(compactNFA.isAccepting(s)); | ||
} else { | ||
Assert.assertFalse(compactNFA.isAccepting(s)); | ||
} | ||
} | ||
StringBuilder sb = new StringBuilder(); | ||
for (int s : compactNFA.getStates()) { | ||
sb.append(compactNFA.getTransitions(s, 0)); | ||
} | ||
// 5 transitions for 0 | ||
Assert.assertEquals("[0, 3][][1, 2][3]", sb.toString()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a fan of String-based comparisons. Could you please compare the original |
||
|
||
sb = new StringBuilder(); | ||
for (int s : compactNFA.getStates()) { | ||
sb.append(compactNFA.getTransitions(s, 1)); | ||
} | ||
// 5 transitions for 1 | ||
Assert.assertEquals("[3][1][0][1, 2]", sb.toString()); | ||
} | ||
|
||
@Test | ||
void testGenerateNFAEdgeCase() { | ||
Random r = new Random(42); | ||
int size = 2; | ||
float td = 0f; // 0 transitions | ||
float ad = 0.5f; // exactly 1 accepting state | ||
Alphabet<Integer> alphabet = Alphabets.integers(0, 1); | ||
CompactNFA<Integer> compactNFA = TabakovVardiRandomAutomata.generateNFA(r, size, td, ad, alphabet); | ||
Assert.assertEquals(size, compactNFA.size()); | ||
Assert.assertEquals(1, compactNFA.getInitialStates().size()); | ||
Assert.assertTrue(compactNFA.isAccepting(0)); | ||
Assert.assertFalse(compactNFA.isAccepting(1)); | ||
Assert.assertEquals(0, compactNFA.getTransitions(0).size()); | ||
Assert.assertEquals(0, compactNFA.getTransitions(1).size()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you maybe add this description to the class level so that the description is directly available at the overview page (like here)? You can then use a more generic description for the method (like the second
generateNFA
method). However, be careful with dots as thejavadoc
tool typically interprets them as the end of the description. So "Moshe Y. Vardi." probably needs to be encoded asMoshe Y Vardi.
. You can check the result withmvn site
.