Skip to content

Commit 83bdeba

Browse files
tiferreimtf90
andauthored
L# Algorithm (#141)
* feat: lsharp * fix: LSoracle Rule3 SepSeq mode * fix: tidy pom * chore: tidy imports * chore: spotbugs * chore: spotbugs * chore: spotbugs * chore: spotbugs * chore: spotbugs * chore: pmd * chore: pmd * chore: pmd * chore: pmd * chore: pmd * chore: pmd * chore: checkstyle * chore: checkstyle * chore: checkstyle * chore: checkstyle * fix: use SymbolQueryOracle instead of MembershipOracle in LSharp Thanks to @stateMachinist for noticing this issue and providing us with a patch. Co-authored-by: stateMachinist * chore: update CI * chore: update CI * chore: remove L# test. I couldn't get it to work with MealyIT given the use of the SymbolQueryOracle. * update to latest refactorings * restore old integration test * initial cleanup drop dead code and make code analysis plugins happy * replace LSMealyMachine with CompactMealy * determinize integration tests * de-stream-ify code not having to deal with multithreading (and therefore not having to deal with synchronization) sped up integration tests by a factor of >30 * do not use exceptions for control flow * cleanup dependencies drop guava * fix issues reported by checkerframework * include into existing hierarchies * general cleanups * documentation * ignore LSharp builder in coverage * add comment detailing the scope of the implementation --------- Co-authored-by: Markus Frohme <mtf90@users.noreply.github.com> Co-authored-by: Markus Frohme <markus.frohme@udo.edu>
1 parent bf66741 commit 83bdeba

File tree

29 files changed

+3047
-1
lines changed

29 files changed

+3047
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
99
### Added
1010

1111
* LearnLib now supports JPMS modules. All artifacts now provide a `module-info` descriptor except of the distribution artifacts (for Maven-less environments) which only provide an `Automatic-Module-Name` due to non-modular dependencies. Note that while this is a Java 9+ feature, LearnLib still supports Java 8 byte code for the remaining class files.
12+
* Added the L# active learning algorithm (thanks to [Tiago Ferreira](https://github.com/tiferrei)).
1213
* The `ADTLearner` has been refactored to no longer use the (now-removed) `SymbolQueryOracle` but a new `AdaptiveMembershipOracle` instead which supports answering queries in parallel (thanks to [Leon Vitorovic](https://github.com/leonthalee)).
1314
* Added an `InterningMembershipOracle` (including refinements) to the `learnlib-cache` artifact that interns query responses to reduce memory consumption of large data structures. This exports the internal concepts of the DHC learner (which no longer interns query responses automatically).
1415
* `StaticParallelOracleBuilder` now supports custom executor services.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Currently, the following learning algorithms with respective target models are s
2424
| DHC | `Mealy` | | | |
2525
| Kearns & Vazirani | `DFA` `Mealy` | | | |
2626
| Lambda | `DFA` `Mealy` | | | |
27+
| L# | `Mealy` | | | |
2728
| L* (incl. variants) | `DFA` `Mealy` `Moore` | | | |
2829
| NL* | `NFA` | | | |
2930
| Observation Pack | `DFA` `Mealy` `Moore` `VPA` | | | |

algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import de.learnlib.oracle.membership.MealySimulatorOracle;
4242
import de.learnlib.oracle.membership.SULAdaptiveOracle;
4343
import de.learnlib.sul.SUL;
44+
import de.learnlib.testsupport.MQ2AQWrapper;
4445
import de.learnlib.testsupport.it.learner.AbstractMealyLearnerIT;
4546
import de.learnlib.testsupport.it.learner.LearnerVariantList;
4647
import de.learnlib.util.Experiment.MealyExperiment;

algorithms/active/lsharp/pom.xml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
Copyright (C) 2013-2025 TU Dortmund University
4+
This file is part of LearnLib <https://learnlib.de>.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
19+
<modelVersion>4.0.0</modelVersion>
20+
21+
<parent>
22+
<groupId>de.learnlib</groupId>
23+
<artifactId>learnlib-algorithms-active-parent</artifactId>
24+
<version>0.18.0-SNAPSHOT</version>
25+
<relativePath>../pom.xml</relativePath>
26+
</parent>
27+
28+
<artifactId>learnlib-lsharp</artifactId>
29+
30+
<name>LearnLib :: Algorithms :: L#</name>
31+
<description>
32+
This artifact provides the implementation of the L# algorithm as described in the paper "A New Approach for
33+
Active Automata Learning Based on Apartness" (https://doi.org/10.1007/978-3-030-99524-9_12) by Frits Vaandrager,
34+
Bharat Garhewal, Jurriaan Rot, and Thorsten Wißmann.
35+
</description>
36+
37+
<dependencies>
38+
<!-- internal -->
39+
<dependency>
40+
<groupId>de.learnlib</groupId>
41+
<artifactId>learnlib-api</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>de.learnlib</groupId>
45+
<artifactId>learnlib-util</artifactId>
46+
</dependency>
47+
48+
<!-- external -->
49+
<dependency>
50+
<groupId>net.automatalib</groupId>
51+
<artifactId>automata-api</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>net.automatalib</groupId>
55+
<artifactId>automata-core</artifactId>
56+
</dependency>
57+
<dependency>
58+
<groupId>net.automatalib</groupId>
59+
<artifactId>automata-commons-util</artifactId>
60+
</dependency>
61+
62+
<!-- build -->
63+
<dependency>
64+
<groupId>de.learnlib.tooling</groupId>
65+
<artifactId>annotations</artifactId>
66+
</dependency>
67+
<dependency>
68+
<groupId>org.checkerframework</groupId>
69+
<artifactId>checker-qual</artifactId>
70+
</dependency>
71+
72+
<!-- test -->
73+
<dependency>
74+
<groupId>de.learnlib.testsupport</groupId>
75+
<artifactId>learnlib-learner-it-support</artifactId>
76+
</dependency>
77+
<dependency>
78+
<groupId>de.learnlib.testsupport</groupId>
79+
<artifactId>learnlib-test-support</artifactId>
80+
</dependency>
81+
82+
<dependency>
83+
<groupId>net.automatalib</groupId>
84+
<artifactId>automata-serialization-dot</artifactId>
85+
<scope>test</scope>
86+
</dependency>
87+
88+
<dependency>
89+
<groupId>org.testng</groupId>
90+
<artifactId>testng</artifactId>
91+
</dependency>
92+
93+
</dependencies>
94+
</project>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* Copyright (C) 2013-2025 TU Dortmund University
2+
* This file is part of LearnLib <https://learnlib.de>.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.learnlib.algorithm.lsharp;
17+
18+
import java.util.ArrayDeque;
19+
import java.util.Deque;
20+
import java.util.Objects;
21+
22+
import net.automatalib.automaton.transducer.MealyMachine;
23+
import net.automatalib.common.util.Pair;
24+
import net.automatalib.word.Word;
25+
import org.checkerframework.checker.nullness.qual.NonNull;
26+
import org.checkerframework.checker.nullness.qual.Nullable;
27+
28+
public final class ApartnessUtil {
29+
30+
private ApartnessUtil() {
31+
// prevent instantiation
32+
}
33+
34+
public static <S extends Comparable<S>, I, O> @Nullable Word<I> computeWitness(ObservationTree<S, I, O> tree,
35+
S s1,
36+
S s2) {
37+
S t = showsStatesAreApart(tree, s1, s2);
38+
if (t == null) {
39+
return null;
40+
}
41+
42+
return tree.getTransferSeq(t, s1);
43+
}
44+
45+
public static <S extends Comparable<S>, I, O> boolean statesAreApart(ObservationTree<S, I, O> tree, S s1, S s2) {
46+
return showsStatesAreApart(tree, s1, s2) != null;
47+
}
48+
49+
public static <S extends Comparable<S>, I, O> boolean accStatesAreApart(ObservationTree<S, I, O> tree,
50+
Word<I> s1a,
51+
Word<I> s2a) {
52+
S s1 = tree.getSucc(tree.defaultState(), s1a);
53+
assert s1 != null;
54+
S s2 = tree.getSucc(tree.defaultState(), s2a);
55+
assert s2 != null;
56+
return statesAreApart(tree, s1, s2);
57+
}
58+
59+
public static <S1 extends Comparable<S1>, S2, I, O> @Nullable Word<I> treeAndHypComputeWitness(ObservationTree<S1, I, O> tree,
60+
S1 st,
61+
MealyMachine<S2, I, ?, O> fsm,
62+
S2 sh) {
63+
S1 s = treeAndHypShowsStatesAreApart(tree, st, sh, fsm);
64+
if (s == null) {
65+
return null;
66+
}
67+
68+
return tree.getTransferSeq(s, st);
69+
}
70+
71+
public static <S1 extends Comparable<S1>, S2, I, O> @Nullable S1 treeAndHypShowsStatesAreApart(ObservationTree<S1, I, O> tree,
72+
S1 st,
73+
S2 sh,
74+
MealyMachine<S2, I, ?, O> fsm) {
75+
Deque<Pair<S1, S2>> queue = new ArrayDeque<>();
76+
queue.push(Pair.of(st, sh));
77+
while (!queue.isEmpty()) {
78+
@SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399
79+
@NonNull Pair<S1, S2> pair = queue.poll();
80+
S1 q = pair.getFirst();
81+
S2 r = pair.getSecond();
82+
83+
for (I i : tree.getInputAlphabet()) {
84+
Pair<O, S1> stepFree = tree.getOutSucc(q, i);
85+
if (stepFree != null) {
86+
S2 dh = fsm.getSuccessor(r, i);
87+
assert dh != null;
88+
O outHyp = fsm.getOutput(r, i);
89+
assert outHyp != null;
90+
91+
if (outHyp.equals(stepFree.getFirst())) {
92+
queue.push(Pair.of(stepFree.getSecond(), dh));
93+
} else {
94+
return stepFree.getSecond();
95+
}
96+
}
97+
}
98+
}
99+
100+
return null;
101+
}
102+
103+
private static <S extends Comparable<S>, I, O> @Nullable Pair<O, S> step(ObservationTree<S, I, O> tree, S x, I i) {
104+
return tree.getOutSucc(x, i);
105+
}
106+
107+
private static <S extends Comparable<S>, I, O> @Nullable Pair<Pair<O, S>, Pair<O, S>> treeRespPairInput(
108+
ObservationTree<S, I, O> tree,
109+
S x,
110+
S y,
111+
I i) {
112+
Pair<O, S> s1 = step(tree, x, i);
113+
Pair<O, S> s2 = step(tree, y, i);
114+
115+
if (s1 == null || s2 == null) {
116+
return null;
117+
}
118+
119+
return Pair.of(s1, s2);
120+
121+
}
122+
123+
public static <S extends Comparable<S>, I, O> @Nullable S showsStatesAreApart(ObservationTree<S, I, O> tree,
124+
S s1,
125+
S s2) {
126+
Deque<Pair<S, S>> workList = new ArrayDeque<>();
127+
workList.add(Pair.of(s1, s2));
128+
while (!workList.isEmpty()) {
129+
Pair<S, S> pair = workList.pop();
130+
S fst = pair.getFirst();
131+
S snd = pair.getSecond();
132+
133+
for (I i : tree.getInputAlphabet()) {
134+
Pair<Pair<O, S>, Pair<O, S>> p = treeRespPairInput(tree, fst, snd, i);
135+
if (p != null) {
136+
Pair<O, S> fstOD = p.getFirst();
137+
Pair<O, S> sndOD = p.getSecond();
138+
139+
O fstO = fstOD.getFirst();
140+
S fstD = fstOD.getSecond();
141+
O sndO = sndOD.getFirst();
142+
S sndD = sndOD.getSecond();
143+
if (Objects.equals(fstO, sndO)) {
144+
workList.push(Pair.of(fstD, sndD));
145+
} else {
146+
return fstD;
147+
}
148+
}
149+
}
150+
}
151+
152+
return null;
153+
}
154+
}

0 commit comments

Comments
 (0)