Skip to content

Commit 8d868a7

Browse files
improved documentation
1 parent 72524f6 commit 8d868a7

File tree

6 files changed

+89
-51
lines changed

6 files changed

+89
-51
lines changed

algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/CoreRow.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
import net.automatalib.word.Word;
2424

2525
/**
26-
* Each core row represents some hypothesis state and stores its outputs for all table suffixes.
26+
* Each core row represents some hypothesis state
27+
* and stores its outputs for all table suffixes.
2728
*/
2829
class CoreRow<S, I, O> extends Row<S, I, O> {
2930

@@ -34,6 +35,7 @@ class CoreRow<S, I, O> extends Row<S, I, O> {
3435

3536
/**
3637
* Index of this row in the core row list.
38+
* Used as a unique address.
3739
*/
3840
final int idx;
3941

@@ -43,7 +45,8 @@ class CoreRow<S, I, O> extends Row<S, I, O> {
4345
final Map<Word<I>, Word<O>> sufToOut;
4446

4547
/**
46-
* Identifiers of all suffix-output pairs in this row, used for fast compatibility checking.
48+
* Identifiers of all suffix-output pairs in this row,
49+
* used for fast compatibility checking.
4750
*/
4851
final Set<Integer> cellIds;
4952

@@ -52,7 +55,7 @@ class CoreRow<S, I, O> extends Row<S, I, O> {
5255
this.state = state;
5356
this.idx = idx;
5457
sufToOut = new HashMap<>();
55-
cellIds = new HashSet<>(); // use HashSet to enable fast containment checks
58+
cellIds = new HashSet<>(); // use HashSet for fast containment checks
5659
}
5760

5861
void addSuffix(Word<I> suf, Word<O> out, int cell) {

algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/FringeRow.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class FringeRow<S, I, O> extends Row<S, I, O> {
4141
final I transIn;
4242

4343
/**
44-
* Output symbol (determined dynamically).
44+
* Output symbol (determined lazily).
4545
*/
4646
O transOut;
4747

algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ class GenericSparseLearner<S, I, O> implements MealyLearner<I, O> {
4848
private final Deque<Word<I>> sufs;
4949

5050
/**
51-
* Core rows.
51+
* List of core rows. Rows can be addressed by their index in this list.
5252
*/
5353
private final List<CoreRow<S, I, O>> cRows;
5454

5555
/**
56-
* Fringe rows.
56+
* Fringe rows (stored in a stack for LIFO iteration).
5757
*/
5858
private final Deque<FringeRow<S, I, O>> fRows;
5959

@@ -63,12 +63,13 @@ class GenericSparseLearner<S, I, O> implements MealyLearner<I, O> {
6363
private final Map<Word<I>, FringeRow<S, I, O>> prefToFringe;
6464

6565
/**
66-
* List of unique suffix-output cells.
66+
* List of unique suffix-output pairs, addressable by index.
67+
* Used for table compression: table entries only hold cell index.
6768
*/
6869
private final List<Pair<Word<I>, Word<O>>> cells;
6970

7071
/**
71-
* Maps each suffix-output cell to its list index.
72+
* Maps each suffix-output pair to its index (see {@link #cells}).
7273
*/
7374
private final Map<Pair<Word<I>, Word<O>>, Integer> cellToIdx;
7475

@@ -159,13 +160,14 @@ private void updateHypothesis() {
159160
return;
160161
} else {
161162
assert f.transOut != null;
163+
assert f.leaf.cRow != null;
162164
hyp.setTransition(f.srcState, f.transIn, f.leaf.cRow.state, f.transOut);
163165
}
164166
}
165167
}
166168

167169
private void classifyFringePrefix(FringeRow<S, I, O> f) {
168-
f.leaf.update(cRows, sufs.size());
170+
f.leaf.update(cRows);
169171
if (f.leaf.isUnsplit()) {
170172
return;
171173
}
@@ -174,7 +176,7 @@ private void classifyFringePrefix(FringeRow<S, I, O> f) {
174176
if (sep != null) {
175177
followNode(f, sep);
176178
} else {
177-
f.leaf.sep = new Separator<>(pickSuffix(f.leaf.remRows), f.leaf.remRows, f.leaf.cellsIds);
179+
f.leaf.sep = new Separator<>(pickSuffix(f.leaf.remRows), f.leaf.remRows, f.leaf.cellIds);
178180
followNode(f, f.leaf.sep);
179181
}
180182
}
@@ -212,7 +214,7 @@ private Word<I> pickSuffix(BitSet remRows) {
212214
}
213215

214216
private void followNode(FringeRow<S, I, O> f, Node<S, I, O> n) {
215-
if (n instanceof Leaf) {
217+
if (n instanceof Leaf) { // TODO simplify when switching to newer java
216218
final Leaf<S, I, O> l = (Leaf<S, I, O>) n;
217219
assert l.isUnsplit();
218220
f.leaf = l;
@@ -230,7 +232,7 @@ private void followNode(FringeRow<S, I, O> f, Node<S, I, O> n) {
230232

231233
final BitSet remRows = new BitSet();
232234
sep.remRows.stream().filter(i -> cRows.get(i).cellIds.contains(cellIdx)).forEach(remRows::set);
233-
final List<Integer> cellIds = new ArrayList<>(sep.cellsIds); // important: copy elements!
235+
final List<Integer> cellIds = new ArrayList<>(sep.cellIds); // important: copy elements!
234236
cellIds.add(cellIdx);
235237
if (remRows.isEmpty()) {
236238
// no compatible core prefix
@@ -249,7 +251,7 @@ private void followNode(FringeRow<S, I, O> f, Node<S, I, O> n) {
249251

250252
private Word<O> query(Row<S, I, O> r, Word<I> suf) {
251253
final Word<O> out = oracle.answerQuery(r.prefix.concat(suf));
252-
if (r instanceof FringeRow) {
254+
if (r instanceof FringeRow) { // TODO simplify when switching to newer java
253255
final FringeRow<S, I, O> f = (FringeRow<S, I, O>) r;
254256
f.transOut = out.prefix(f.prefix.length()).lastSymbol();
255257
}
@@ -301,6 +303,7 @@ private int moveToCore(FringeRow<S, I, O> f, List<Integer> cellIds) {
301303
* and returns a list containing the observations for all suffixes.
302304
*/
303305
private List<Integer> completeRowObservations(FringeRow<S, I, O> f, List<Integer> cellIds) {
306+
// TODO simplify collector calls when switching to newer java
304307
final List<Word<I>> sufsPresent = cellIds.stream().map(c -> this.cells.get(c).getFirst()).collect(Collectors.toList());
305308
final List<Word<I>> sufsMissing = sufs.stream().filter(s -> !sufsPresent.contains(s)).collect(Collectors.toList());
306309
final List<Integer> cellIdsFull = new ArrayList<>(cellIds); // important: copy elements!
@@ -325,8 +328,8 @@ private void identifyNewState(DefaultQuery<I, Word<O>> q) {
325328
final Word<I> u = accSeq.apply(cex.prefix(idxSym));
326329
final Word<I> ui = u.append(cex.getSymbol(idxSym));
327330
final FringeRow<S, I, O> f = prefToFringe.get(ui);
328-
assert f.leaf.isUnsplit();
329-
final int cRowIdx = moveToCore(f, f.leaf.cellsIds);
331+
final int cRowIdx = moveToCore(f, f.leaf.cellIds);
332+
assert f.leaf.cRow != null;
330333
if (f.leaf.cRow.cellIds.containsAll(cRows.get(cRowIdx).cellIds)) {
331334
// only add new suffix if the row is not yet distinguished
332335
addSuffixToTable(cex.subWord(idxSuf));

algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Leaf.java

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,67 +20,85 @@
2020

2121
import org.checkerframework.checker.nullness.qual.Nullable;
2222

23+
/**
24+
* Leaves can be split or unsplit.
25+
* An unsplit leaf has a single compatible core row to which it points.
26+
* As new core rows emerge, the observations of the leaf may not suffice
27+
* anymore to uniquely assign it to some core row. Then, it becomes split.
28+
* Split leaves cache suffix selection by reference to separators.
29+
* Leaves remember how many core rows and suffixes existed at their last visit.
30+
* This information is used as a logical timestamp to check
31+
* if the separator is still known to be optimal
32+
* or if it needs to be recomputed.
33+
*/
2334
class Leaf<S, I, O> extends Node<S, I, O> {
2435

25-
final @Nullable CoreRow<S, I, O> cRow;
26-
private boolean split;
27-
private int lastNumCRows;
28-
private int lastNumSufs;
36+
/**
37+
* Core row associated with this leaf (null if split, see {@link Leaf}).
38+
*/
39+
@Nullable CoreRow<S, I, O> cRow;
2940

3041
/**
31-
* Split leafs always remember how many core rows and suffixes the table contained
32-
* at their last visit. This information is used as a logical timestamp to check
33-
* if the separator is still guaranteed to be optimal or if it needs to be recomputed.
42+
* Separator cached by this leaf (see {@link Leaf}).
3443
*/
3544
@Nullable Separator<S, I, O> sep;
3645

46+
private int lastNumCRows;
47+
private int lastNumSufs;
48+
49+
private Leaf(int numCRows, int numSufs, List<Integer> cellIds) {
50+
super(cellIds);
51+
this.lastNumCRows = numCRows;
52+
this.lastNumSufs = numSufs;
53+
}
54+
3755
/**
38-
* Creates split leaf without observations.
56+
* Creates split leaf without observations (see {@link Leaf}).
3957
*/
4058
Leaf() {
41-
super(Collections.emptyList());
42-
cRow = null;
43-
split = true;
44-
lastNumCRows = 0;
45-
lastNumSufs = 0;
59+
this(0, 0, Collections.emptyList());
4660
// timestamps will be updated automatically
61+
cRow = null;
4762
}
4863

4964
/**
50-
* Creates unsplit leaf associated with the given core row and observations.
65+
* Creates unsplit leaf associated with the given core row and observations
66+
* (see {@link Leaf}).
5167
*/
5268
Leaf(CoreRow<S, I, O> cRow, int numCRows, int numSufs, List<Integer> cellIds) {
53-
super(cellIds);
69+
this(numCRows, numSufs, cellIds);
5470
this.cRow = cRow;
5571
remRows.set(cRow.idx);
56-
split = false;
57-
lastNumCRows = numCRows;
58-
lastNumSufs = numSufs;
5972
}
6073

74+
/**
75+
* See {@link Leaf}.
76+
*/
6177
boolean isUnsplit() {
62-
return !split;
78+
return cRow != null;
6379
}
6480

65-
void update(List<CoreRow<S, I, O>> cRows, int numSufs) {
81+
void update(List<CoreRow<S, I, O>> cRows) {
6682
assert lastNumCRows <= cRows.size();
6783
if (lastNumCRows == cRows.size()) {
68-
assert lastNumSufs == numSufs;
6984
return;
70-
} else if (numSufs > lastNumSufs) {
85+
}
86+
87+
final int numSufs = cRows.get(0).sufToOut.size();
88+
if (numSufs > lastNumSufs) {
7189
lastNumSufs = numSufs;
7290
sep = null;
7391
}
7492

75-
// Since suffixes and core rows grow monotonically,
93+
// since suffixes and core rows grow monotonically,
7694
// the separator only needs to be recomputed whenever
77-
// new compatible core prefixes emerge or the suffix set grows.
95+
// new compatible core prefixes emerge or the suffix set grows
7896

7997
for (int i = lastNumCRows; i < cRows.size(); i++) {
8098
final CoreRow<S, I, O> c = cRows.get(i);
81-
if (c.cellIds.containsAll(cellsIds)) {
99+
if (c.cellIds.containsAll(cellIds)) {
82100
remRows.set(c.idx);
83-
split = true;
101+
cRow = null; // split leaf
84102
sep = null;
85103
}
86104
}

algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Node.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,33 @@
1818
import java.util.BitSet;
1919
import java.util.List;
2020

21+
/**
22+
* For table compression and to cache suffix selection,
23+
* fringe rows do not store observations, but instead map to some node.
24+
* Each node is associated with a set of suffix-output pairs,
25+
* potentially representing multiple rows with identical observations.
26+
* Nodes are either leaves or separators.
27+
*/
2128
class Node<S, I, O> { // type parameters required for safe casting
2229

2330
/**
24-
* Suffix-output cell identifiers of the fringe rows that share this node.
31+
* Identifiers of suffix-output pairs associated with this node.
2532
*/
26-
final List<Integer> cellsIds;
33+
final List<Integer> cellIds;
2734

2835
/**
29-
* Bit vector indicating the core rows that remain compatible
30-
* with the observations associated with this node.
36+
* Bit vector encoding which core rows remain compatible
37+
* with the observations at this node.
38+
* Rows are represented by their index.
3139
*/
3240
final BitSet remRows;
3341

34-
protected Node(List<Integer> cellsIds) {
35-
this(cellsIds, new BitSet());
42+
protected Node(List<Integer> cellIds) {
43+
this(cellIds, new BitSet());
3644
}
3745

38-
protected Node(List<Integer> cellsIds, BitSet remRows) {
39-
this.cellsIds = cellsIds;
46+
protected Node(List<Integer> cellIds, BitSet remRows) {
47+
this.cellIds = cellIds;
4048
this.remRows = remRows;
4149
}
4250
}

algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Separator.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@
2222

2323
import net.automatalib.word.Word;
2424

25+
/**
26+
* Separators guide the classification of fringe prefixes.
27+
* A separator holds a suffix and a branch map.
28+
* The map points to the next node,
29+
* depending on the output produced by the suffix.
30+
*/
2531
class Separator<S, I, O> extends Node<S, I, O> {
2632
final Word<I> suffix;
2733
final Map<Word<O>, Node<S, I, O>> branchMap;
2834

29-
Separator(Word<I> suffix, BitSet remRows, List<Integer> cells) {
30-
super(cells, remRows);
35+
Separator(Word<I> suffix, BitSet remRows, List<Integer> cellIds) {
36+
super(cellIds, remRows);
3137
this.suffix = suffix;
3238
branchMap = new HashMap<>();
3339
}

0 commit comments

Comments
 (0)