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

LearnLib / automatalib / 13138848026

04 Feb 2025 02:53PM UTC coverage: 92.108% (+2.2%) from 89.877%
13138848026

push

github

mtf90
[maven-release-plugin] prepare release automatalib-0.12.0

16609 of 18032 relevant lines covered (92.11%)

1.7 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-2025 TU Dortmund University
2
 * This file is part of AutomataLib <https://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.HashSet;
23
import java.util.Iterator;
24
import java.util.List;
25
import java.util.Queue;
26
import java.util.Set;
27
import java.util.function.Predicate;
28

29
import net.automatalib.alphabet.VPAlphabet;
30
import net.automatalib.automaton.vpa.OneSEVPA;
31
import net.automatalib.automaton.vpa.impl.DefaultOneSEVPA;
32
import net.automatalib.common.util.HashUtil;
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.common.util.array.ArrayStorage;
37
import net.automatalib.util.automaton.minimizer.OneSEVPAMinimizer;
38
import net.automatalib.util.automaton.vpa.SPAConverter.ConversionResult;
39
import net.automatalib.util.ts.acceptor.AcceptanceCombiner;
40
import net.automatalib.word.Word;
41
import org.checkerframework.checker.nullness.qual.NonNull;
42
import org.checkerframework.checker.nullness.qual.Nullable;
43
import org.checkerframework.checker.nullness.qual.PolyNull;
44

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

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

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

78
    /**
79
     * Most general way of combining two {@link OneSEVPA}s. The {@link AcceptanceCombiner} specified via the
80
     * {@code combiner} parameter specifies how acceptance values of the {@link OneSEVPA}s will be combined to an
81
     * acceptance value in the resulting {@link ProductOneSEVPA}.
82
     *
83
     * @param sevpa1
84
     *         the first SEVPA
85
     * @param sevpa2
86
     *         the second SEVPA
87
     * @param alphabet
88
     *         the input alphabet
89
     * @param combiner
90
     *         combination method for acceptance values
91
     * @param <L1>
92
     *         location type of the first SEVPA
93
     * @param <L2>
94
     *         location type of the second SEVPA
95
     * @param <I>
96
     *         input symbol type
97
     *
98
     * @return a view representing the given combination of the specified {@link OneSEVPA}s
99
     */
100
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> combine(OneSEVPA<L1, I> sevpa1,
101
                                                                OneSEVPA<L2, I> sevpa2,
102
                                                                VPAlphabet<I> alphabet,
103
                                                                AcceptanceCombiner combiner) {
104
        return new ProductOneSEVPA<>(alphabet, sevpa1, sevpa2, combiner);
2✔
105
    }
106

107
    /**
108
     * Returns a view on the disjunction ("or") of two {@link OneSEVPA}s.
109
     *
110
     * @param sevpa1
111
     *         the first SEVPA
112
     * @param sevpa2
113
     *         the second SEVPA
114
     * @param alphabet
115
     *         the input alphabet
116
     * @param <L1>
117
     *         location type of the first SEVPA
118
     * @param <L2>
119
     *         location type of the second SEVPA
120
     * @param <I>
121
     *         input symbol type
122
     *
123
     * @return a view representing the disjunction of the specified {@link OneSEVPA}s
124
     */
125
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> or(OneSEVPA<L1, I> sevpa1,
126
                                                           OneSEVPA<L2, I> sevpa2,
127
                                                           VPAlphabet<I> alphabet) {
128
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.OR);
×
129
    }
130

131
    /**
132
     * Returns a view on the exclusive-or ("xor") of two {@link OneSEVPA}s.
133
     *
134
     * @param sevpa1
135
     *         the first SEVPA
136
     * @param sevpa2
137
     *         the second SEVPA
138
     * @param alphabet
139
     *         the input alphabet
140
     * @param <L1>
141
     *         location type of the first SEVPA
142
     * @param <L2>
143
     *         location type of the second SEVPA
144
     * @param <I>
145
     *         input symbol type
146
     *
147
     * @return a view representing the exclusive-or of the specified {@link OneSEVPA}s
148
     */
149
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> xor(OneSEVPA<L1, I> sevpa1,
150
                                                            OneSEVPA<L2, I> sevpa2,
151
                                                            VPAlphabet<I> alphabet) {
152
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.XOR);
2✔
153
    }
154

155
    /**
156
     * Returns a view on  the equivalence ("&lt;=&gt;") of two {@link OneSEVPA}s.
157
     *
158
     * @param sevpa1
159
     *         the first SEVPA
160
     * @param sevpa2
161
     *         the second SEVPA
162
     * @param alphabet
163
     *         the input alphabet
164
     * @param <L1>
165
     *         location type of the first SEVPA
166
     * @param <L2>
167
     *         location type of the second SEVPA
168
     * @param <I>
169
     *         input symbol type
170
     *
171
     * @return a view representing the equivalence of the specified {@link OneSEVPA}s
172
     */
173
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> equiv(OneSEVPA<L1, I> sevpa1,
174
                                                              OneSEVPA<L2, I> sevpa2,
175
                                                              VPAlphabet<I> alphabet) {
176
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.EQUIV);
×
177
    }
178

179
    /**
180
     * Returns a view on  the implication ("=&gt;") of two {@link OneSEVPA}s.
181
     *
182
     * @param sevpa1
183
     *         the first SEVPA
184
     * @param sevpa2
185
     *         the second SEVPA
186
     * @param alphabet
187
     *         the input alphabet
188
     * @param <L1>
189
     *         location type of the first SEVPA
190
     * @param <L2>
191
     *         location type of the second SEVPA
192
     * @param <I>
193
     *         input symbol type
194
     *
195
     * @return a view representing the implication of the specified {@link OneSEVPA}s
196
     */
197
    public static <L1, L2, I> OneSEVPA<Pair<L1, L2>, I> impl(OneSEVPA<L1, I> sevpa1,
198
                                                             OneSEVPA<L2, I> sevpa2,
199
                                                             VPAlphabet<I> alphabet) {
200
        return combine(sevpa1, sevpa2, alphabet, AcceptanceCombiner.IMPL);
×
201
    }
202

203
    /**
204
     * Minimizes the given {@link OneSEVPA} over the given alphabet. This method does not modify the given
205
     * {@link OneSEVPA}, but returns the minimized version as a new instance.
206
     * <p>
207
     * <b>Note:</b> the method currently does not support partial {@link OneSEVPA}s.
208
     *
209
     * @param sevpa
210
     *         the SEVPA to be minimized
211
     * @param alphabet
212
     *         the input alphabet to consider for minimization (this will also be the input alphabet of the resulting
213
     *         automaton)
214
     * @param <I>
215
     *         input symbol type
216
     *
217
     * @return a minimized version of the specified {@link OneSEVPA}
218
     */
219
    public static <I> DefaultOneSEVPA<I> minimize(OneSEVPA<?, I> sevpa, VPAlphabet<I> alphabet) {
220
        return OneSEVPAMinimizer.minimize(sevpa, alphabet);
2✔
221
    }
222

223
    /**
224
     * Computes an access sequence over the given alphabet (if existent) such that it reaches a location of the given
225
     * SEVPA that satisfies the given predicate.
226
     *
227
     * @param sevpa
228
     *         the SEVPA
229
     * @param alphabet
230
     *         the alphabet symbols to consider
231
     * @param predicate
232
     *         the predicate to satisfy
233
     * @param <L>
234
     *         the location type
235
     * @param <I>
236
     *         input symbol type
237
     *
238
     * @return an access sequence {@code as} such that {@code predicate.test(sevpa.getState(as).getLocation()) == true},
239
     * or {@code null} if such a sequence does not exist.
240
     */
241
    public static <L, I> @Nullable Word<I> computeAccessSequence(OneSEVPA<L, I> sevpa,
242
                                                                 VPAlphabet<I> alphabet,
243
                                                                 Predicate<? super L> predicate) {
244
        final ReachResult<L, I> result = computeAccessSequences(sevpa, alphabet, true, predicate);
2✔
245
        L resultLoc = result.terminateLoc;
2✔
246
        if (resultLoc != null) {
2✔
247
            return result.accessSequences.get(sevpa.getLocationId(resultLoc));
×
248
        }
249
        return null;
2✔
250
    }
251

252
    /**
253
     * Computes all access sequences over the given alphabet (if existent) to the locations of the given SEVPA.
254
     *
255
     * @param sevpa
256
     *         the SEVPA
257
     * @param alphabet
258
     *         the alphabet symbols to consider
259
     * @param <L>
260
     *         the location type
261
     * @param <I>
262
     *         input symbol type
263
     *
264
     * @return a list of access sequences, indexed by their respective {@link OneSEVPA#getLocationId(Object) id}.
265
     */
266
    public static <L, I> ArrayStorage<Word<I>> computeAccessSequences(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
267
        return computeAccessSequences(sevpa, alphabet, true, l -> false).accessSequences;
2✔
268
    }
269

270
    private static <L, I> ReachResult<L, I> computeAccessSequences(OneSEVPA<L, I> sevpa,
271
                                                                   VPAlphabet<I> alphabet,
272
                                                                   boolean computeAs,
273
                                                                   Predicate<? super L> terminatePred) {
274
        final ArrayStorage<Word<I>> result = new ArrayStorage<>(sevpa.size());
2✔
275

276
        final L initLoc = sevpa.getInitialLocation();
2✔
277
        final List<L> reachable = new ArrayList<>();
2✔
278
        reachable.add(initLoc);
2✔
279
        result.set(sevpa.getLocationId(initLoc), Word.epsilon());
2✔
280

281
        if (terminatePred.test(initLoc)) {
2✔
282
            return new ReachResult<>(initLoc, reachable, result);
×
283
        }
284

285
        int queuePtr = 0;
2✔
286
        while (queuePtr < reachable.size()) {
2✔
287
            final L curr = reachable.get(queuePtr++);
2✔
288
            final Word<I> currAs = result.get(sevpa.getLocationId(curr));
2✔
289

290
            for (I intSym : alphabet.getInternalAlphabet()) {
2✔
291
                final L succ = sevpa.getInternalSuccessor(curr, intSym);
2✔
292
                if (succ == null) {
2✔
293
                    continue;
×
294
                }
295
                final int succIdx = sevpa.getLocationId(succ);
2✔
296
                if (result.get(succIdx) != null) {
2✔
297
                    continue;
2✔
298
                }
299
                final Word<I> succAs = computeAs ? currAs.append(intSym) : Word.epsilon();
2✔
300
                result.set(succIdx, succAs);
2✔
301
                if (terminatePred.test(succ)) {
2✔
302
                    return new ReachResult<>(succ, reachable, result);
×
303
                }
304
                reachable.add(succ);
2✔
305
            }
2✔
306

307
            for (I callSym : alphabet.getCallAlphabet()) {
2✔
308
                for (I returnSym : alphabet.getReturnAlphabet()) {
2✔
309
                    for (int i = 0; i < queuePtr; i++) {
2✔
310
                        final L src = reachable.get(i);
2✔
311
                        int stackSym = sevpa.encodeStackSym(src, callSym);
2✔
312
                        L succ = sevpa.getReturnSuccessor(curr, returnSym, stackSym);
2✔
313
                        if (succ == null) {
2✔
314
                            continue;
×
315
                        }
316
                        int succIdx = sevpa.getLocationId(succ);
2✔
317
                        if (result.get(succIdx) == null) {
2✔
318
                            Word<I> succAs = computeAs ?
2✔
319
                                    result.get(sevpa.getLocationId(src))
2✔
320
                                          .append(callSym)
2✔
321
                                          .concat(currAs.append(returnSym)) :
2✔
322
                                    Word.epsilon();
2✔
323
                            result.set(succIdx, succAs);
2✔
324
                            if (terminatePred.test(succ)) {
2✔
325
                                return new ReachResult<>(succ, reachable, result);
×
326
                            }
327
                            reachable.add(succ);
2✔
328
                        }
329

330
                        if (src != curr) {
2✔
331
                            stackSym = sevpa.encodeStackSym(curr, callSym);
2✔
332
                            succ = sevpa.getReturnSuccessor(src, returnSym, stackSym);
2✔
333
                            if (succ == null) {
2✔
334
                                continue;
×
335
                            }
336
                            succIdx = sevpa.getLocationId(succ);
2✔
337
                            if (result.get(succIdx) == null) {
2✔
338
                                final Word<I> succAs = computeAs ?
2✔
339
                                        currAs.append(callSym)
2✔
340
                                              .concat(result.get(sevpa.getLocationId(src)).append(returnSym)) :
2✔
341
                                        Word.epsilon();
2✔
342
                                result.set(succIdx, succAs);
2✔
343
                                if (terminatePred.test(succ)) {
2✔
344
                                    return new ReachResult<>(succ, reachable, result);
×
345
                                }
346
                                reachable.add(succ);
2✔
347
                            }
348
                        }
349
                    }
350
                }
2✔
351
            }
2✔
352
        }
2✔
353

354
        return new ReachResult<>(null, reachable, result);
2✔
355
    }
356

357
    /**
358
     * Tests whether two SEVPAs are equivalent, i.e. whether there exists a
359
     * {@link #findSeparatingWord(OneSEVPA, OneSEVPA, VPAlphabet) separating word} for the two given SEVPAs.
360
     *
361
     * @param <I>
362
     *         input symbol type
363
     * @param sevpa1
364
     *         the one SEVPA to consider
365
     * @param sevpa2
366
     *         the other SEVPA to consider
367
     * @param alphabet
368
     *         the input symbols to consider
369
     *
370
     * @return {@code true} if the SEVPA are equivalent, {@code false} otherwise.
371
     *
372
     * @see #findSeparatingWord(OneSEVPA, OneSEVPA, VPAlphabet)
373
     */
374
    public static <I> boolean testEquivalence(OneSEVPA<?, I> sevpa1, OneSEVPA<?, I> sevpa2, VPAlphabet<I> alphabet) {
375
        return findSeparatingWord(sevpa1, sevpa2, alphabet) == null;
2✔
376
    }
377

378
    /**
379
     * Returns a well-matched word (over the given alphabet) that is accepted by the given SEVPA, if existent.
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 {@link VPAlphabet#isWellMatched(Word) well-matched} word {@code w} such that
391
     * {@code sevpa.accepts(w) == true}, {@code null} if such a word does not exist.
392
     */
393
    public static <L, I> @Nullable Word<I> findAcceptedWord(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
394
        return computeAccessSequence(sevpa, alphabet, sevpa::isAcceptingLocation);
2✔
395
    }
396

397
    /**
398
     * Returns a well-matched word (over the given alphabet) that is rejected by the given SEVPA, if existent.
399
     *
400
     * @param <L>
401
     *         location type
402
     * @param <I>
403
     *         input symbol type
404
     * @param sevpa
405
     *         the SEVPA to consider
406
     * @param alphabet
407
     *         the input symbols to consider
408
     *
409
     * @return a {@link VPAlphabet#isWellMatched(Word) well-matched} word {@code w} such that
410
     * {@code sevpa.accepts(w) == false}, {@code null} if such a word does not exist.
411
     */
412
    public static <L, I> @Nullable Word<I> findRejectedWord(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
413
        return computeAccessSequence(sevpa, alphabet, l -> !sevpa.isAcceptingLocation(l));
×
414
    }
415

416
    /**
417
     * Returns a list of locations that are reachable from the initial location of the given SEVPA via symbols of the
418
     * given alphabet.
419
     *
420
     * @param <L>
421
     *         location type
422
     * @param <I>
423
     *         input symbol type
424
     * @param sevpa
425
     *         the SEVPA to consider
426
     * @param alphabet
427
     *         the input symbols to consider
428
     *
429
     * @return a list of locations that are reachable via symbols of the given alphabet
430
     */
431
    public static <L, I> List<L> findReachableLocations(OneSEVPA<L, I> sevpa, VPAlphabet<I> alphabet) {
432
        return computeAccessSequences(sevpa, alphabet, false, l -> false).reachableLocs;
2✔
433
    }
434

435
    /**
436
     * Finds a separating word for two SEVPAs, if existent. A separating word is a word that exposes a different
437
     * acceptance behavior between the two SEVPAs.
438
     *
439
     * @param <I>
440
     *         input symbol type
441
     * @param sevpa1
442
     *         the one SEVPA to consider
443
     * @param sevpa2
444
     *         the other SEVPA to consider
445
     * @param alphabet
446
     *         the input symbols to consider
447
     *
448
     * @return a separating word, or {@code null} if no such word could be found.
449
     */
450
    public static <I> @Nullable Word<I> findSeparatingWord(OneSEVPA<?, I> sevpa1,
451
                                                           OneSEVPA<?, I> sevpa2,
452
                                                           VPAlphabet<I> alphabet) {
453
        final OneSEVPA<?, I> prod = OneSEVPAs.xor(sevpa1, sevpa2, alphabet);
2✔
454
        return findAcceptedWord(prod, alphabet);
2✔
455
    }
456

457
    /**
458
     * Finds a separating word for two locations of a SEVPAs, if existent. A separating word is a word that exposes a
459
     * different acceptance behavior between the two SEVPAs. Note that the separating word for two explicit locations
460
     * consists of a prefix and a suffix since locations are typically distinguished in regard to the syntactical
461
     * congruence.
462
     *
463
     * @param <L>
464
     *         location type
465
     * @param <I>
466
     *         input symbol type
467
     * @param sevpa
468
     *         the SEVPA to consider
469
     * @param init1
470
     *         the one location to consider
471
     * @param init2
472
     *         the other location to consider
473
     * @param alphabet
474
     *         the input symbols to consider
475
     *
476
     * @return a separating word for the two locations, or {@code null} if no such word could be found.
477
     */
478
    public static <L, I> @Nullable Pair<Word<I>, Word<I>> findSeparatingWord(OneSEVPA<L, I> sevpa,
479
                                                                             L init1,
480
                                                                             L init2,
481
                                                                             VPAlphabet<I> alphabet) {
482
        if (sevpa.isAcceptingLocation(init1) != sevpa.isAcceptingLocation(init2)) {
2✔
483
            return Pair.of(Word.epsilon(), Word.epsilon());
2✔
484
        }
485

486
        final ArrayStorage<Word<I>> as = computeAccessSequences(sevpa, alphabet);
2✔
487
        final IntDisjointSets uf = new UnionFindRemSP(sevpa.size());
2✔
488
        uf.link(sevpa.getLocationId(init1), sevpa.getLocationId(init2));
2✔
489

490
        final Queue<Record<L, I>> queue = new ArrayDeque<>();
2✔
491
        queue.add(new Record<>(init1, init2));
2✔
492

493
        Pair<Word<I>, Word<I>> lastPair = null;
2✔
494
        Record<L, I> current;
495

496
        explore:
497
        while ((current = queue.poll()) != null) {
2✔
498
            final L l1 = current.l1;
2✔
499
            final L l2 = current.l2;
2✔
500

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

504
                final L succ1 = sevpa.getInternalSuccessor(l1, i);
2✔
505
                final L succ2 = sevpa.getInternalSuccessor(l2, i);
2✔
506

507
                assert succ1 != null && succ2 != null;
2✔
508

509
                if (sevpa.isAcceptingLocation(succ1) != sevpa.isAcceptingLocation(succ2)) {
2✔
510
                    lastPair = pair;
2✔
511
                    break explore;
2✔
512
                }
513

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

516
                if (r1 == r2) {
2✔
517
                    continue;
2✔
518
                }
519

520
                uf.link(r1, r2);
2✔
521

522
                queue.add(new Record<>(succ1, succ2, pair, current));
2✔
523
            }
2✔
524

525
            for (I c : alphabet.getCallAlphabet()) {
2✔
526
                final Word<I> cWord = Word.fromLetter(c);
2✔
527

528
                for (I r : alphabet.getReturnAlphabet()) {
2✔
529
                    final Word<I> rWord = Word.fromLetter(r);
2✔
530

531
                    // check l as source location for l1/l2
532
                    for (L l : sevpa.getLocations()) {
2✔
533
                        final int sym = sevpa.encodeStackSym(l, c);
2✔
534
                        final L rSucc1 = sevpa.getReturnSuccessor(l1, r, sym);
2✔
535
                        final L rSucc2 = sevpa.getReturnSuccessor(l2, r, sym);
2✔
536

537
                        assert rSucc1 != null && rSucc2 != null;
2✔
538

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

542
                        if (sevpa.isAcceptingLocation(rSucc1) != sevpa.isAcceptingLocation(rSucc2)) {
2✔
543
                            lastPair = pair;
2✔
544
                            break explore;
2✔
545
                        }
546

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

549
                        if (r1 == r2) {
2✔
550
                            continue;
2✔
551
                        }
552

553
                        uf.link(r1, r2);
2✔
554

555
                        queue.add(new Record<>(rSucc1, rSucc2, pair, current));
2✔
556
                    }
2✔
557

558
                    // check l1/l2 as source location for l
559
                    for (L l : sevpa.getLocations()) {
2✔
560
                        final int sym1 = sevpa.encodeStackSym(l1, c);
2✔
561
                        final int sym2 = sevpa.encodeStackSym(l2, c);
2✔
562
                        final L rSucc1 = sevpa.getReturnSuccessor(l, r, sym1);
2✔
563
                        final L rSucc2 = sevpa.getReturnSuccessor(l, r, sym2);
2✔
564

565
                        assert rSucc1 != null && rSucc2 != null;
2✔
566

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

570
                        if (sevpa.isAcceptingLocation(rSucc1) != sevpa.isAcceptingLocation(rSucc2)) {
2✔
571
                            lastPair = pair;
2✔
572
                            break explore;
2✔
573
                        }
574

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

577
                        if (r1 == r2) {
2✔
578
                            continue;
2✔
579
                        }
580

581
                        uf.link(r1, r2);
2✔
582

583
                        queue.add(new Record<>(rSucc1, rSucc2, pair, current));
2✔
584
                    }
2✔
585
                }
2✔
586
            }
2✔
587
        }
2✔
588

589
        if (current == null) {
2✔
590
            return null;
2✔
591
        }
592

593
        final Deque<Word<I>> prefixBuilder = new ArrayDeque<>();
2✔
594
        final Deque<Word<I>> suffixBuilder = new ArrayDeque<>();
2✔
595

596
        prefixBuilder.add(lastPair.getFirst());
2✔
597
        suffixBuilder.add(lastPair.getSecond());
2✔
598

599
        while (current.reachedFrom != null) {
2✔
600
            final Pair<Word<I>, Word<I>> reachedBy = current.reachedBy;
2✔
601
            prefixBuilder.offerLast(reachedBy.getFirst());
2✔
602
            suffixBuilder.offerFirst(reachedBy.getSecond());
2✔
603
            current = current.reachedFrom;
2✔
604
        }
2✔
605

606
        return Pair.of(Word.fromWords(prefixBuilder), Word.fromWords(suffixBuilder));
2✔
607
    }
608

609
    /**
610
     * Computes a characterizing set for the given SEVPA. Note that the characterizing words consist of a prefix and a
611
     * suffix since locations are typically distinguished in regard to the syntactical
612
     *
613
     * @param <L>
614
     *         location type
615
     * @param <I>
616
     *         input symbol type
617
     * @param sevpa
618
     *         the SEVPA for which to determine the characterizing set
619
     * @param alphabet
620
     *         the input symbols to consider
621
     *
622
     * @return a list containing the characterizing words
623
     */
624
    public static <L, I> Collection<Pair<Word<I>, Word<I>>> findCharacterizingSet(OneSEVPA<L, I> sevpa,
625
                                                                                  VPAlphabet<I> alphabet) {
626

627
        final ArrayStorage<Word<I>> as = computeAccessSequences(sevpa, alphabet);
2✔
628
        final List<L> acceptingLocations = new ArrayList<>(sevpa.size());
2✔
629
        final List<L> rejectionLocations = new ArrayList<>(sevpa.size());
2✔
630

631
        for (L l : sevpa.getLocations()) {
2✔
632
            if (sevpa.isAcceptingLocation(l)) {
2✔
633
                acceptingLocations.add(l);
2✔
634
            } else {
635
                rejectionLocations.add(l);
2✔
636
            }
637
        }
2✔
638

639
        final Set<Pair<Word<I>, Word<I>>> result = new HashSet<>(HashUtil.capacity(sevpa.size()));
2✔
640
        result.add(Pair.of(Word.epsilon(), Word.epsilon()));
2✔
641

642
        final Queue<List<L>> blockQueue = new ArrayDeque<>();
2✔
643
        blockQueue.add(acceptingLocations);
2✔
644
        blockQueue.add(rejectionLocations);
2✔
645

646
        while (!blockQueue.isEmpty()) {
2✔
647
            @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399
648
            final @NonNull List<L> block = blockQueue.poll();
2✔
649
            if (block.size() == 1) {
2✔
650
                continue;
2✔
651
            }
652

653
            final Iterator<L> blockIter = block.iterator();
2✔
654

655
            final L l1 = blockIter.next();
2✔
656
            final L l2 = blockIter.next();
2✔
657

658
            final Pair<Word<I>, Word<I>> sepWord = findSeparatingWord(sevpa, l1, l2, alphabet);
2✔
659
            assert sepWord != null;
2✔
660

661
            result.add(sepWord);
2✔
662

663
            final List<L> acceptingBucket = new ArrayList<>(block.size());
2✔
664
            final List<L> rejectingBucket = new ArrayList<>(block.size());
2✔
665

666
            if (sevpa.accepts(Word.fromWords(sepWord.getFirst(),
2✔
667
                                             as.get(sevpa.getLocationId(l1)),
2✔
668
                                             sepWord.getSecond()))) {
2✔
669
                acceptingBucket.add(l1);
2✔
670
                rejectingBucket.add(l2);
2✔
671
            } else {
672
                acceptingBucket.add(l2);
2✔
673
                rejectingBucket.add(l1);
2✔
674
            }
675

676
            while (blockIter.hasNext()) {
2✔
677
                final L next = blockIter.next();
2✔
678
                if (sevpa.accepts(Word.fromWords(sepWord.getFirst(),
2✔
679
                                                 as.get(sevpa.getLocationId(next)),
2✔
680
                                                 sepWord.getSecond()))) {
2✔
681
                    acceptingBucket.add(next);
2✔
682
                } else {
683
                    rejectingBucket.add(next);
2✔
684
                }
685
            }
2✔
686

687
            blockQueue.add(acceptingBucket);
2✔
688
            blockQueue.add(rejectingBucket);
2✔
689
        }
2✔
690

691
        return result;
2✔
692
    }
693

694
    /**
695
     * Converts a given SEVPA into an SPA by concretizing the (abstract) behavior of the SEVPA into the (concrete)
696
     * behavior of an SPA which can be described by the copy-rule semantics of (instrumented) context-free grammars.
697
     *
698
     * @param <AI>
699
     *         abstract input symbol type
700
     * @param <CI>
701
     *         concrete input symbol type
702
     * @param sevpa
703
     *         the SEVPA to consider
704
     * @param alphabet
705
     *         the input symbols to consider
706
     * @param mainProcedure
707
     *         the concrete input symbol that should be used to represent the main procedure of the constructed SPA
708
     * @param symbolMapper
709
     *         a symbol mapper to translate the abstract SEVPA symbols to concrete SPA symbols
710
     * @param minimize
711
     *         a flat indicating, whether the constructed SPA should be minimized
712
     *
713
     * @return a {@link ConversionResult} containing the relevant data of this conversion
714
     */
715
    public static <AI, CI> ConversionResult<AI, CI> toSPA(OneSEVPA<?, AI> sevpa,
716
                                                          VPAlphabet<AI> alphabet,
717
                                                          CI mainProcedure,
718
                                                          SymbolMapper<AI, CI> symbolMapper,
719
                                                          boolean minimize) {
720
        return SPAConverter.convert(sevpa, alphabet, mainProcedure, symbolMapper, minimize);
2✔
721
    }
722

723
    private static class ReachResult<L, I> {
724

725
        public final @Nullable L terminateLoc;
726
        public final List<L> reachableLocs;
727
        public final ArrayStorage<Word<I>> accessSequences;
728

729
        ReachResult(@Nullable L terminateLoc, List<L> reachableLocs, ArrayStorage<Word<I>> accessSequences) {
2✔
730
            this.terminateLoc = terminateLoc;
2✔
731
            this.reachableLocs = reachableLocs;
2✔
732
            this.accessSequences = accessSequences;
2✔
733
        }
2✔
734
    }
735

736
    private static final class Record<L, I> {
737

738
        private final L l1;
739
        private final L l2;
740
        private final @PolyNull Pair<Word<I>, Word<I>> reachedBy;
741
        private final @PolyNull Record<L, I> reachedFrom;
742

743
        Record(L l1, L l2) {
744
            this(l1, l2, null, null);
2✔
745
        }
2✔
746

747
        Record(L l1, L l2, @PolyNull Pair<Word<I>, Word<I>> reachedBy, @PolyNull Record<L, I> reachedFrom) {
2✔
748
            this.l1 = l1;
2✔
749
            this.l2 = l2;
2✔
750
            this.reachedBy = reachedBy;
2✔
751
            this.reachedFrom = reachedFrom;
2✔
752
        }
2✔
753
    }
754

755
}
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