• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LearnLib / automatalib / 6685076669

29 Oct 2023 06:24PM UTC coverage: 89.857% (+0.06%) from 89.796%
6685076669

push

github

mtf90
align core packages with api packages

15814 of 17599 relevant lines covered (89.86%)

1.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.01
/util/src/main/java/net/automatalib/util/automaton/vpa/OneSEVPAs.java
1
/* Copyright (C) 2013-2023 TU Dortmund
2
 * This file is part of AutomataLib, http://www.automatalib.net/.
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 net.automatalib.util.automaton.vpa;
17

18
import java.util.ArrayDeque;
19
import java.util.ArrayList;
20
import java.util.Collection;
21
import java.util.Deque;
22
import java.util.Iterator;
23
import java.util.List;
24
import java.util.Queue;
25
import java.util.Set;
26
import java.util.function.Predicate;
27

28
import com.google.common.collect.Sets;
29
import net.automatalib.alphabet.VPAlphabet;
30
import net.automatalib.automaton.vpa.DefaultOneSEVPA;
31
import net.automatalib.automaton.vpa.OneSEVPA;
32
import net.automatalib.common.smartcollection.ArrayStorage;
33
import net.automatalib.common.util.IntDisjointSets;
34
import net.automatalib.common.util.Pair;
35
import net.automatalib.common.util.UnionFindRemSP;
36
import net.automatalib.util.automaton.vpa.SPAConverter.ConversionResult;
37
import net.automatalib.util.minimizer.OneSEVPAMinimizer;
38
import net.automatalib.util.ts.acceptor.AcceptanceCombiner;
39
import net.automatalib.word.Word;
40
import org.checkerframework.checker.nullness.qual.NonNull;
41
import org.checkerframework.checker.nullness.qual.Nullable;
42
import org.checkerframework.checker.nullness.qual.PolyNull;
43

44
/**
45
 * Operations on {@link OneSEVPA}s.
46
 */
47
public final class OneSEVPAs {
2✔
48

49
    private OneSEVPAs() {
50
        // prevent instantiation
51
    }
52

53
    /**
54
     * Returns a view on the conjunction ("and") of two {@link OneSEVPA}s.
55
     *
56
     * @param sevpa1
57
     *         the first SEVPA
58
     * @param sevpa2
59
     *         the second SEVPA
60
     * @param alphabet
61
     *         the input alphabet
62
     *
63
     * @return a view representing the conjunction of the specified {@link OneSEVPA}s
64
     */
65
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> and(OneSEVPA<L1, I> sevpa1,
66
                                                            OneSEVPA<L2, I> sevpa2,
67
                                                            VPAlphabet<I> alphabet) {
68
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.AND);
×
69
    }
70

71
    /**
72
     * Most general way of combining two {@link OneSEVPA}s. The {@link AcceptanceCombiner} specified via the
73
     * {@code combiner} parameter specifies how acceptance values of the {@link OneSEVPA}s will be combined to an
74
     * acceptance value in the resulting {@link ProductOneSEVPA}.
75
     *
76
     * @param sevpa1
77
     *         the first SEVPA
78
     * @param sevpa2
79
     *         the second SEVPA
80
     * @param alphabet
81
     *         the input alphabet
82
     * @param combiner
83
     *         combination method for acceptance values
84
     *
85
     * @return a view representing the given combination of the specified {@link OneSEVPA}s
86
     */
87
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> combine(OneSEVPA<L1, I> sevpa1,
88
                                                                OneSEVPA<L2, I> sevpa2,
89
                                                                VPAlphabet<I> alphabet,
90
                                                                AcceptanceCombiner combiner) {
91
        return new ProductOneSEVPA<>(alphabet, sevpa1, sevpa2, combiner);
2✔
92
    }
93

94
    /**
95
     * Returns a view on the disjunction ("or") of two {@link OneSEVPA}s.
96
     *
97
     * @param sevpa1
98
     *         the first SEVPA
99
     * @param sevpa2
100
     *         the second SEVPA
101
     * @param alphabet
102
     *         the input alphabet
103
     *
104
     * @return a view representing the disjunction of the specified {@link OneSEVPA}s
105
     */
106
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> or(OneSEVPA<L1, I> sevpa1,
107
                                                           OneSEVPA<L2, I> sevpa2,
108
                                                           VPAlphabet<I> alphabet) {
109
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.OR);
×
110
    }
111

112
    /**
113
     * Returns a view on the exclusive-or ("xor") of two {@link OneSEVPA}s.
114
     *
115
     * @param sevpa1
116
     *         the first SEVPA
117
     * @param sevpa2
118
     *         the second SEVPA
119
     * @param alphabet
120
     *         the input alphabet
121
     *
122
     * @return a view representing the exclusive-or of the specified {@link OneSEVPA}s
123
     */
124
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> xor(OneSEVPA<L1, I> sevpa1,
125
                                                            OneSEVPA<L2, I> sevpa2,
126
                                                            VPAlphabet<I> alphabet) {
127
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.XOR);
2✔
128
    }
129

130
    /**
131
     * Returns a view on  the equivalence ("&lt;=&gt;") of two {@link OneSEVPA}s.
132
     *
133
     * @param sevpa1
134
     *         the first SEVPA
135
     * @param sevpa2
136
     *         the second SEVPA
137
     * @param alphabet
138
     *         the input alphabet
139
     *
140
     * @return a view representing the equivalence of the specified {@link OneSEVPA}s
141
     */
142
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> equiv(OneSEVPA<L1, I> sevpa1,
143
                                                              OneSEVPA<L2, I> sevpa2,
144
                                                              VPAlphabet<I> alphabet) {
145
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.EQUIV);
×
146
    }
147

148
    /**
149
     * Returns a view on  the implication ("=&gt;") of two {@link OneSEVPA}s.
150
     *
151
     * @param sevpa1
152
     *         the first SEVPA
153
     * @param sevpa2
154
     *         the second SEVPA
155
     * @param alphabet
156
     *         the input alphabet
157
     *
158
     * @return a view representing the implication of the specified {@link OneSEVPA}s
159
     */
160
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> impl(OneSEVPA<L1, I> sevpa1,
161
                                                             OneSEVPA<L2, I> sevpa2,
162
                                                             VPAlphabet<I> alphabet) {
163
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.IMPL);
×
164
    }
165

166
    /**
167
     * Minimizes the given {@link OneSEVPA} over the given alphabet. This method does not modify the given
168
     * {@link OneSEVPA}, but returns the minimized version as a new instance.
169
     * <p>
170
     * <b>Note:</b> the method currently does not support partial {@link OneSEVPA}s.
171
     *
172
     * @param sevpa
173
     *         the SEVPA to be minimized
174
     * @param alphabet
175
     *         the input alphabet to consider for minimization (this will also be the input alphabet of the resulting
176
     *         automaton)
177
     *
178
     * @return a minimized version of the specified {@link OneSEVPA}
179
     */
180
    public static <I> DefaultOneSEVPA<I> minimize(OneSEVPA<?, I> sevpa, VPAlphabet<I> alphabet) {
181
        return OneSEVPAMinimizer.minimize(sevpa, alphabet);
2✔
182
    }
183

184
    /**
185
     * Computes an access sequence over the given alphabet (if existent) such that it reaches a location of the given
186
     * SEVPA that satisfies the given predicate.
187
     *
188
     * @param sevpa
189
     *         the SEVPA
190
     * @param alphabet
191
     *         the alphabet symbols to consider
192
     * @param predicate
193
     *         the predicate to satisfy
194
     * @param <L>
195
     *         the location type
196
     * @param <I>
197
     *         input symbol type
198
     *
199
     * @return an access sequence {@code as} such that {@code predicate.test(sevpa.getState(as).getLocation()) == true},
200
     * or {@code null} if such a sequence does not exist.
201
     */
202
    public static <L, I> @Nullable Word<I> computeAccessSequence(OneSEVPA<L, I> sevpa,
203
                                                                 VPAlphabet<I> alphabet,
204
                                                                 Predicate<? super L> predicate) {
205
        final ReachResult<L, I> result = computeAccessSequences(sevpa, alphabet, true, predicate);
2✔
206
        L resultLoc = result.terminateLoc;
2✔
207
        if (resultLoc != null) {
2✔
208
            return result.accessSequences.get(sevpa.getLocationId(resultLoc));
×
209
        }
210
        return null;
2✔
211
    }
212

213
    /**
214
     * Computes all access sequences over the given alphabet (if existent) to the locations of the given SEVPA.
215
     *
216
     * @param sevpa
217
     *         the SEVPA
218
     * @param alphabet
219
     *         the alphabet symbols to consider
220
     * @param <L>
221
     *         the location type
222
     * @param <I>
223
     *         input symbol type
224
     *
225
     * @return a list of access sequences, indexed by their respective {@link OneSEVPA#getLocationId(Object) id}.
226
     */
227
    public static <L, I> ArrayStorage<Word<I>> computeAccessSequences(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
228
        return computeAccessSequences(sevpa, alphabet, true, l -> false).accessSequences;
2✔
229
    }
230

231
    private static <L, I> ReachResult<L, I> computeAccessSequences(OneSEVPA<L, I> sevpa,
232
                                                                   VPAlphabet<I> alphabet,
233
                                                                   boolean computeAs,
234
                                                                   Predicate<? super L> terminatePred) {
235
        final ArrayStorage<Word<I>> result = new ArrayStorage<>(sevpa.size());
2✔
236

237
        final L initLoc = sevpa.getInitialLocation();
2✔
238
        final List<L> reachable = new ArrayList<>();
2✔
239
        reachable.add(initLoc);
2✔
240
        result.set(sevpa.getLocationId(initLoc), Word.epsilon());
2✔
241

242
        if (terminatePred.test(initLoc)) {
2✔
243
            return new ReachResult<>(initLoc, reachable, result);
×
244
        }
245

246
        int queuePtr = 0;
2✔
247
        while (queuePtr < reachable.size()) {
2✔
248
            final L curr = reachable.get(queuePtr++);
2✔
249
            final Word<I> currAs = result.get(sevpa.getLocationId(curr));
2✔
250

251
            for (I intSym : alphabet.getInternalAlphabet()) {
2✔
252
                final L succ = sevpa.getInternalSuccessor(curr, intSym);
2✔
253
                if (succ == null) {
2✔
254
                    continue;
×
255
                }
256
                final int succIdx = sevpa.getLocationId(succ);
2✔
257
                if (result.get(succIdx) != null) {
2✔
258
                    continue;
2✔
259
                }
260
                final Word<I> succAs = computeAs ? currAs.append(intSym) : Word.epsilon();
2✔
261
                result.set(succIdx, succAs);
2✔
262
                if (terminatePred.test(succ)) {
2✔
263
                    return new ReachResult<>(succ, reachable, result);
×
264
                }
265
                reachable.add(succ);
2✔
266
            }
2✔
267

268
            for (I callSym : alphabet.getCallAlphabet()) {
2✔
269
                for (I returnSym : alphabet.getReturnAlphabet()) {
2✔
270
                    for (int i = 0; i < queuePtr; i++) {
2✔
271
                        final L src = reachable.get(i);
2✔
272
                        int stackSym = sevpa.encodeStackSym(src, callSym);
2✔
273
                        L succ = sevpa.getReturnSuccessor(curr, returnSym, stackSym);
2✔
274
                        if (succ == null) {
2✔
275
                            continue;
×
276
                        }
277
                        int succIdx = sevpa.getLocationId(succ);
2✔
278
                        if (result.get(succIdx) == null) {
2✔
279
                            Word<I> succAs = computeAs ?
2✔
280
                                    result.get(sevpa.getLocationId(src))
2✔
281
                                          .append(callSym)
2✔
282
                                          .concat(currAs.append(returnSym)) :
2✔
283
                                    Word.epsilon();
2✔
284
                            result.set(succIdx, succAs);
2✔
285
                            if (terminatePred.test(succ)) {
2✔
286
                                return new ReachResult<>(succ, reachable, result);
×
287
                            }
288
                            reachable.add(succ);
2✔
289
                        }
290

291
                        if (src != curr) {
2✔
292
                            stackSym = sevpa.encodeStackSym(curr, callSym);
2✔
293
                            succ = sevpa.getReturnSuccessor(src, returnSym, stackSym);
2✔
294
                            if (succ == null) {
2✔
295
                                continue;
×
296
                            }
297
                            succIdx = sevpa.getLocationId(succ);
2✔
298
                            if (result.get(succIdx) == null) {
2✔
299
                                final Word<I> succAs = computeAs ?
2✔
300
                                        currAs.append(callSym)
2✔
301
                                              .concat(result.get(sevpa.getLocationId(src)).append(returnSym)) :
2✔
302
                                        Word.epsilon();
2✔
303
                                result.set(succIdx, succAs);
2✔
304
                                if (terminatePred.test(succ)) {
2✔
305
                                    return new ReachResult<>(succ, reachable, result);
×
306
                                }
307
                                reachable.add(succ);
2✔
308
                            }
309
                        }
310
                    }
311
                }
2✔
312
            }
2✔
313
        }
2✔
314

315
        return new ReachResult<>(null, reachable, result);
2✔
316
    }
317

318
    /**
319
     * Tests whether two SEVPAs are equivalent, i.e. whether there exists a
320
     * {@link #findSeparatingWord(OneSEVPA, OneSEVPA, VPAlphabet) separating word} for the two given SEVPAs.
321
     *
322
     * @param <I>
323
     *         input symbol type
324
     * @param sevpa1
325
     *         the one SEVPA to consider
326
     * @param sevpa2
327
     *         the other SEVPA to consider
328
     * @param alphabet
329
     *         the input symbols to consider
330
     *
331
     * @return {@code true} if the SEVPA are equivalent, {@code false} otherwise.
332
     *
333
     * @see #findSeparatingWord(OneSEVPA, OneSEVPA, VPAlphabet)
334
     */
335
    public static <I> boolean testEquivalence(OneSEVPA<?, I> sevpa1, OneSEVPA<?, I> sevpa2, VPAlphabet<I> alphabet) {
336
        return findSeparatingWord(sevpa1, sevpa2, alphabet) == null;
2✔
337
    }
338

339
    /**
340
     * Returns a well-matched word (over the given alphabet) that is accepted by the given SEVPA, if existent.
341
     *
342
     * @param <L>
343
     *         location type
344
     * @param <I>
345
     *         input symbol type
346
     * @param sevpa
347
     *         the SEVPA to consider
348
     * @param alphabet
349
     *         the input symbols to consider
350
     *
351
     * @return a {@link VPAlphabet#isWellMatched(Word) well-matched} word {@code w} such that
352
     * {@code sevpa.accepts(w) == true}, {@code null} if such a word does not exist.
353
     */
354
    public static <L, I> @Nullable Word<I> findAcceptedWord(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
355
        return computeAccessSequence(sevpa, alphabet, sevpa::isAcceptingLocation);
2✔
356
    }
357

358
    /**
359
     * Returns a well-matched word (over the given alphabet) that is rejected by the given SEVPA, if existent.
360
     *
361
     * @param <L>
362
     *         location type
363
     * @param <I>
364
     *         input symbol type
365
     * @param sevpa
366
     *         the SEVPA to consider
367
     * @param alphabet
368
     *         the input symbols to consider
369
     *
370
     * @return a {@link VPAlphabet#isWellMatched(Word) well-matched} word {@code w} such that
371
     * {@code sevpa.accepts(w) == false}, {@code null} if such a word does not exist.
372
     */
373
    public static <L, I> @Nullable Word<I> findRejectedWord(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
374
        return computeAccessSequence(sevpa, alphabet, l -> !sevpa.isAcceptingLocation(l));
×
375
    }
376

377
    /**
378
     * Returns a list of locations that are reachable from the initial location of the given SEVPA via symbols of the
379
     * given alphabet.
380
     *
381
     * @param <L>
382
     *         location type
383
     * @param <I>
384
     *         input symbol type
385
     * @param sevpa
386
     *         the SEVPA to consider
387
     * @param alphabet
388
     *         the input symbols to consider
389
     *
390
     * @return a list of locations that are reachable via symbols of the given alphabet
391
     */
392
    public static <L, I> List<L> findReachableLocations(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
393
        return computeAccessSequences(sevpa, alphabet, false, l -> false).reachableLocs;
2✔
394
    }
395

396
    /**
397
     * Finds a separating word for two SEVPAs, if existent. A separating word is a word that exposes a different
398
     * acceptance behavior between the two SEVPAs.
399
     *
400
     * @param <I>
401
     *         input symbol type
402
     * @param sevpa1
403
     *         the one SEVPA to consider
404
     * @param sevpa2
405
     *         the other SEVPA to consider
406
     * @param alphabet
407
     *         the input symbols to consider
408
     *
409
     * @return a separating word, or {@code null} if no such word could be found.
410
     */
411
    public static <I> @Nullable Word<I> findSeparatingWord(OneSEVPA<?, I> sevpa1,
412
                                                           OneSEVPA<?, I> sevpa2,
413
                                                           VPAlphabet<I> alphabet) {
414
        final OneSEVPA<?, I> prod = OneSEVPAs.xor(sevpa1, sevpa2, alphabet);
2✔
415
        return findAcceptedWord(prod, alphabet);
2✔
416
    }
417

418
    /**
419
     * Finds a separating word for two locations of a SEVPAs, if existent. A separating word is a word that exposes a
420
     * different acceptance behavior between the two SEVPAs. Note that the separating word for two explicit locations
421
     * consists of a prefix and a suffix since locations are typically distinguished in regard to the syntactical
422
     * congruence.
423
     *
424
     * @param <L>
425
     *         location type
426
     * @param <I>
427
     *         input symbol type
428
     * @param sevpa
429
     *         the SEVPA to consider
430
     * @param init1
431
     *         the one location to consider
432
     * @param init2
433
     *         the other location to consider
434
     * @param alphabet
435
     *         the input symbols to consider
436
     *
437
     * @return a separating word for the two locations, or {@code null} if no such word could be found.
438
     */
439
    public static <L, I> @Nullable Pair<Word<I>, Word<I>> findSeparatingWord(OneSEVPA<L, I> sevpa,
440
                                                                             L init1,
441
                                                                             L init2,
442
                                                                             VPAlphabet<I> alphabet) {
443
        if (sevpa.isAcceptingLocation(init1) != sevpa.isAcceptingLocation(init2)) {
2✔
444
            return Pair.of(Word.epsilon(), Word.epsilon());
2✔
445
        }
446

447
        final ArrayStorage<Word<I>> as = computeAccessSequences(sevpa, alphabet);
2✔
448
        final IntDisjointSets uf = new UnionFindRemSP(sevpa.size());
2✔
449
        uf.link(sevpa.getLocationId(init1), sevpa.getLocationId(init2));
2✔
450

451
        final Queue<Record<L, I>> queue = new ArrayDeque<>();
2✔
452
        queue.add(new Record<>(init1, init2));
2✔
453

454
        Pair<Word<I>, Word<I>> lastPair = null;
2✔
455
        Record<L, I> current;
456

457
        explore:
458
        while ((current = queue.poll()) != null) {
2✔
459
            final L l1 = current.l1;
2✔
460
            final L l2 = current.l2;
2✔
461

462
            for (I i : alphabet.getInternalAlphabet()) {
2✔
463
                final Pair<Word<I>, Word<I>> pair = Pair.of(Word.epsilon(), Word.fromLetter(i));
2✔
464

465
                final L succ1 = sevpa.getInternalSuccessor(l1, i);
2✔
466
                final L succ2 = sevpa.getInternalSuccessor(l2, i);
2✔
467

468
                assert succ1 != null && succ2 != null;
2✔
469

470
                if (sevpa.isAcceptingLocation(succ1) != sevpa.isAcceptingLocation(succ2)) {
2✔
471
                    lastPair = pair;
2✔
472
                    break explore;
2✔
473
                }
474

475
                final int r1 = uf.find(sevpa.getLocationId(succ1)), r2 = uf.find(sevpa.getLocationId(succ2));
2✔
476

477
                if (r1 == r2) {
2✔
478
                    continue;
2✔
479
                }
480

481
                uf.link(r1, r2);
2✔
482

483
                queue.add(new Record<>(succ1, succ2, pair, current));
2✔
484
            }
2✔
485

486
            for (I c : alphabet.getCallAlphabet()) {
2✔
487
                final Word<I> cWord = Word.fromLetter(c);
2✔
488

489
                for (I r : alphabet.getReturnAlphabet()) {
2✔
490
                    final Word<I> rWord = Word.fromLetter(r);
2✔
491

492
                    // check l as source location for l1/l2
493
                    for (L l : sevpa.getLocations()) {
2✔
494
                        final int sym = sevpa.encodeStackSym(l, c);
2✔
495
                        final L rSucc1 = sevpa.getReturnSuccessor(l1, r, sym);
2✔
496
                        final L rSucc2 = sevpa.getReturnSuccessor(l2, r, sym);
2✔
497

498
                        assert rSucc1 != null && rSucc2 != null;
2✔
499

500
                        final Pair<Word<I>, Word<I>> pair =
2✔
501
                                Pair.of(Word.fromWords(as.get(sevpa.getLocationId(l)), cWord), rWord);
2✔
502

503
                        if (sevpa.isAcceptingLocation(rSucc1) != sevpa.isAcceptingLocation(rSucc2)) {
2✔
504
                            lastPair = pair;
2✔
505
                            break explore;
2✔
506
                        }
507

508
                        final int r1 = uf.find(sevpa.getLocationId(rSucc1)), r2 = uf.find(sevpa.getLocationId(rSucc2));
2✔
509

510
                        if (r1 == r2) {
2✔
511
                            continue;
2✔
512
                        }
513

514
                        uf.link(r1, r2);
2✔
515

516
                        queue.add(new Record<>(rSucc1, rSucc2, pair, current));
2✔
517
                    }
2✔
518

519
                    // check l1/l2 as source location for l
520
                    for (L l : sevpa.getLocations()) {
2✔
521
                        final int sym1 = sevpa.encodeStackSym(l1, c);
2✔
522
                        final int sym2 = sevpa.encodeStackSym(l2, c);
2✔
523
                        final L rSucc1 = sevpa.getReturnSuccessor(l, r, sym1);
2✔
524
                        final L rSucc2 = sevpa.getReturnSuccessor(l, r, sym2);
2✔
525

526
                        assert rSucc1 != null && rSucc2 != null;
2✔
527

528
                        final Pair<Word<I>, Word<I>> pair =
529
                                Pair.of(Word.epsilon(), Word.fromWords(cWord, as.get(sevpa.getLocationId(l)), rWord));
2✔
530

531
                        if (sevpa.isAcceptingLocation(rSucc1) != sevpa.isAcceptingLocation(rSucc2)) {
2✔
532
                            lastPair = pair;
2✔
533
                            break explore;
2✔
534
                        }
535

536
                        final int r1 = uf.find(sevpa.getLocationId(rSucc1)), r2 = uf.find(sevpa.getLocationId(rSucc2));
2✔
537

538
                        if (r1 == r2) {
2✔
539
                            continue;
2✔
540
                        }
541

542
                        uf.link(r1, r2);
2✔
543

544
                        queue.add(new Record<>(rSucc1, rSucc2, pair, current));
2✔
545
                    }
2✔
546
                }
2✔
547
            }
2✔
548
        }
2✔
549

550
        if (current == null) {
2✔
551
            return null;
2✔
552
        }
553

554
        final Deque<Word<I>> prefixBuilder = new ArrayDeque<>();
2✔
555
        final Deque<Word<I>> suffixBuilder = new ArrayDeque<>();
2✔
556

557
        prefixBuilder.add(lastPair.getFirst());
2✔
558
        suffixBuilder.add(lastPair.getSecond());
2✔
559

560
        while (current.reachedFrom != null) {
2✔
561
            final Pair<Word<I>, Word<I>> reachedBy = current.reachedBy;
2✔
562
            prefixBuilder.offerLast(reachedBy.getFirst());
2✔
563
            suffixBuilder.offerFirst(reachedBy.getSecond());
2✔
564
            current = current.reachedFrom;
2✔
565
        }
2✔
566

567
        return Pair.of(Word.fromWords(prefixBuilder), Word.fromWords(suffixBuilder));
2✔
568
    }
569

570
    /**
571
     * Computes a characterizing set for the given SEVPA. Note that the characterizing words consist of a prefix and a
572
     * suffix since locations are typically distinguished in regard to the syntactical
573
     *
574
     * @param <L>
575
     *         location type
576
     * @param <I>
577
     *         input symbol type
578
     * @param sevpa
579
     *         the SEVPA for which to determine the characterizing set
580
     * @param alphabet
581
     *         the input symbols to consider
582
     *
583
     * @return a list containing the characterizing words
584
     */
585
    public static <L, I> Collection<Pair<Word<I>, Word<I>>> findCharacterizingSet(OneSEVPA<L, I> sevpa,
586
                                                                                  VPAlphabet<I> alphabet) {
587

588
        final ArrayStorage<Word<I>> as = computeAccessSequences(sevpa, alphabet);
2✔
589
        final List<L> acceptingLocations = new ArrayList<>(sevpa.size());
2✔
590
        final List<L> rejectionLocations = new ArrayList<>(sevpa.size());
2✔
591

592
        for (L l : sevpa.getLocations()) {
2✔
593
            if (sevpa.isAcceptingLocation(l)) {
2✔
594
                acceptingLocations.add(l);
2✔
595
            } else {
596
                rejectionLocations.add(l);
2✔
597
            }
598
        }
2✔
599

600
        final Set<Pair<Word<I>, Word<I>>> result = Sets.newHashSetWithExpectedSize(sevpa.size());
2✔
601
        result.add(Pair.of(Word.epsilon(), Word.epsilon()));
2✔
602

603
        final Queue<List<L>> blockQueue = new ArrayDeque<>();
2✔
604
        blockQueue.add(acceptingLocations);
2✔
605
        blockQueue.add(rejectionLocations);
2✔
606

607
        while (!blockQueue.isEmpty()) {
2✔
608
            @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399
609
            final @NonNull List<L> block = blockQueue.poll();
2✔
610
            if (block.size() == 1) {
2✔
611
                continue;
2✔
612
            }
613

614
            final Iterator<L> blockIter = block.iterator();
2✔
615

616
            final L l1 = blockIter.next();
2✔
617
            final L l2 = blockIter.next();
2✔
618

619
            final Pair<Word<I>, Word<I>> sepWord = findSeparatingWord(sevpa, l1, l2, alphabet);
2✔
620
            assert sepWord != null;
2✔
621

622
            result.add(sepWord);
2✔
623

624
            final List<L> acceptingBucket = new ArrayList<>(block.size());
2✔
625
            final List<L> rejectingBucket = new ArrayList<>(block.size());
2✔
626

627
            if (sevpa.accepts(Word.fromWords(sepWord.getFirst(),
2✔
628
                                             as.get(sevpa.getLocationId(l1)),
2✔
629
                                             sepWord.getSecond()))) {
2✔
630
                acceptingBucket.add(l1);
2✔
631
                rejectingBucket.add(l2);
2✔
632
            } else {
633
                acceptingBucket.add(l2);
2✔
634
                rejectingBucket.add(l1);
2✔
635
            }
636

637
            while (blockIter.hasNext()) {
2✔
638
                final L next = blockIter.next();
2✔
639
                if (sevpa.accepts(Word.fromWords(sepWord.getFirst(),
2✔
640
                                                 as.get(sevpa.getLocationId(next)),
2✔
641
                                                 sepWord.getSecond()))) {
2✔
642
                    acceptingBucket.add(next);
2✔
643
                } else {
644
                    rejectingBucket.add(next);
2✔
645
                }
646
            }
2✔
647

648
            blockQueue.add(acceptingBucket);
2✔
649
            blockQueue.add(rejectingBucket);
2✔
650
        }
2✔
651

652
        return result;
2✔
653
    }
654

655
    /**
656
     * Converts a given SEVPA into an SPA by concretizing the (abstract) behavior of the SEVPA into the (concrete)
657
     * behavior of an SPA which can be described by the copy-rule semantics of (instrumented) context-free grammars.
658
     *
659
     * @param <AI>
660
     *         abstract input symbol type
661
     * @param <CI>
662
     *         concrete input symbol type
663
     * @param sevpa
664
     *         the SEVPA to consider
665
     * @param alphabet
666
     *         the input symbols to consider
667
     * @param mainProcedure
668
     *         the concrete input symbol that should be used to represent the main procedure of the constructed SPA
669
     * @param symbolMapper
670
     *         a symbol mapper to translate the abstract SEVPA symbols to concrete SPA symbols
671
     * @param minimize
672
     *         a flat indicating, whether the constructed SPA should be minimized
673
     *
674
     * @return a {@link ConversionResult} containing the relevant data of this conversion
675
     */
676
    public static <AI, CI> ConversionResult<AI, CI> toSPA(OneSEVPA<?, AI> sevpa,
677
                                                          VPAlphabet<AI> alphabet,
678
                                                          CI mainProcedure,
679
                                                          SymbolMapper<AI, CI> symbolMapper,
680
                                                          boolean minimize) {
681
        return SPAConverter.convert(sevpa, alphabet, mainProcedure, symbolMapper, minimize);
2✔
682
    }
683

684
    private static class ReachResult<L, I> {
685

686
        public final @Nullable L terminateLoc;
687
        public final List<L> reachableLocs;
688
        public final ArrayStorage<Word<I>> accessSequences;
689

690
        ReachResult(@Nullable L terminateLoc, List<L> reachableLocs, ArrayStorage<Word<I>> accessSequences) {
2✔
691
            this.terminateLoc = terminateLoc;
2✔
692
            this.reachableLocs = reachableLocs;
2✔
693
            this.accessSequences = accessSequences;
2✔
694
        }
2✔
695
    }
696

697
    private static final class Record<L, I> {
698

699
        private final L l1;
700
        private final L l2;
701
        private final @PolyNull Pair<Word<I>, Word<I>> reachedBy;
702
        private final @PolyNull Record<L, I> reachedFrom;
703

704
        Record(L l1, L l2) {
705
            this(l1, l2, null, null);
2✔
706
        }
2✔
707

708
        Record(L l1, L l2, @PolyNull Pair<Word<I>, Word<I>> reachedBy, @PolyNull Record<L, I> reachedFrom) {
2✔
709
            this.l1 = l1;
2✔
710
            this.l2 = l2;
2✔
711
            this.reachedBy = reachedBy;
2✔
712
            this.reachedFrom = reachedFrom;
2✔
713
        }
2✔
714
    }
715

716
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc