001/* Copyright (C) 2013 TU Dortmund
002 * This file is part of AutomataLib, http://www.automatalib.net/.
003 * 
004 * AutomataLib is free software; you can redistribute it and/or
005 * modify it under the terms of the GNU Lesser General Public
006 * License version 3.0 as published by the Free Software Foundation.
007 * 
008 * AutomataLib is distributed in the hope that it will be useful,
009 * but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
011 * Lesser General Public License for more details.
012 * 
013 * You should have received a copy of the GNU Lesser General Public
014 * License along with AutomataLib; if not, see
015 * http://www.gnu.de/documents/lgpl.en.html.
016 */
017package net.automatalib.incremental.mealy;
018
019import java.util.ArrayDeque;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.Objects;
027import java.util.Queue;
028
029import net.automatalib.automata.abstractimpl.AbstractDeterministicAutomaton;
030import net.automatalib.automata.concepts.StateIDs;
031import net.automatalib.automata.concepts.TransitionOutput;
032import net.automatalib.automata.graphs.AbstractAutomatonGraph;
033import net.automatalib.automata.transout.MealyMachine;
034import net.automatalib.automata.transout.impl.compact.CompactMealy;
035import net.automatalib.commons.util.Pair;
036import net.automatalib.commons.util.UnionFind;
037import net.automatalib.commons.util.mappings.MutableMapping;
038import net.automatalib.graphs.UniversalGraph;
039import net.automatalib.graphs.concepts.NodeIDs;
040import net.automatalib.graphs.dot.DOTPlottableGraph;
041import net.automatalib.graphs.dot.GraphDOTHelper;
042import net.automatalib.incremental.ConflictException;
043import net.automatalib.incremental.IncrementalConstruction;
044import net.automatalib.words.Alphabet;
045import net.automatalib.words.Word;
046import net.automatalib.words.WordBuilder;
047
048/**
049 * Incrementally builds an (acyclic) Mealy machine, from a set of input and corresponding
050 * output words.
051 * 
052 * @author Malte Isberner <malte.isberner@gmail.com>
053 *
054 * @param <I> input symbol class
055 * @param <O> output symbol class
056 */
057public class IncrementalMealyBuilder<I, O> extends
058        AbstractDeterministicAutomaton<State, I, TransitionRecord> implements
059        TransitionOutput<TransitionRecord, O>,
060        UniversalGraph<State, TransitionRecord, Void, Pair<I,O>>,
061        DOTPlottableGraph<State, TransitionRecord>,
062        IncrementalConstruction<MealyMachine<?,I,?,O>, I> {
063        
064        
065        private final Map<StateSignature, State> register = new HashMap<>();
066
067        private final Alphabet<I> inputAlphabet;
068        private final int alphabetSize;
069        private final State init;
070
071        /**
072         * Constructor.
073         * @param inputAlphabet the input alphabet to use
074         */
075        public IncrementalMealyBuilder(Alphabet<I> inputAlphabet) {
076                this.inputAlphabet = inputAlphabet;
077                this.alphabetSize = inputAlphabet.size();
078                StateSignature initSig = new StateSignature(alphabetSize);
079                this.init = new State(initSig);
080                register.put(null, init);
081        }
082
083        /*
084         * (non-Javadoc)
085         * @see net.automatalib.automata.abstractimpl.AbstractDeterministicAutomaton#size()
086         */
087        @Override
088        public int size() {
089                return register.size();
090        }
091
092        /**
093         * Retrieves the (internal) state reached by the given input word,
094         * or <tt>null</tt> if no information about the input word is present.
095         * @param word the input word
096         * @return the corresponding state
097         */
098        private State getState(Word<I> word) {
099                State s = init;
100
101                for (I sym : word) {
102                        int idx = inputAlphabet.getSymbolIndex(sym);
103                        s = s.getSuccessor(idx);
104                        if (s == null)
105                                return null;
106                }
107                return s;
108        }
109
110        /**
111         * Checks whether there exists secured information about the output
112         * for the given word.
113         * @param word the input word
114         * @return a boolean indicating whether information about the output for the
115         * given input word exists.
116         */
117        public boolean isComplete(Word<I> word) {
118                State s = getState(word);
119                return (s != null);
120        }
121        
122        /**
123         * Retrieves the output word for the given input word. If no definitive information
124         * for the input word exists, the output for the longest known prefix will be returned.
125         * @param word the input word
126         * @param output a {@link WordBuilder} for constructing the output word
127         * @return <tt>true</tt> if the information contained was complete (in this case,
128         * <code>word.length() == output.size()</code> will hold), <tt>false</tt> otherwise. 
129         */
130        @SuppressWarnings("unchecked")
131        public boolean lookup(Word<I> word, WordBuilder<O> output) {
132                State curr = init;
133                for(I sym : word) {
134                        int idx = inputAlphabet.getSymbolIndex(sym);
135                        State succ = curr.getSuccessor(idx);
136                        if(succ == null)
137                                return false;
138                        output.append((O)curr.getOutput(idx));
139                        curr = succ;
140                }
141                
142                return true;
143        }
144        
145
146        /**
147         * Incorporates a pair of input/output words into the stored information.
148         * @param word the input word
149         * @param outputWord the corresponding output word
150         * @throws ConflictException if this information conflicts with information already stored
151         */
152        public void insert(Word<I> word, Word<O> outputWord) {
153                int len = word.length();
154
155                State curr = init;
156                State conf = null;
157
158                int confIndex = -1;
159
160                int prefixLen = 0;
161                // Find the internal state in the automaton that can be reached by a
162                // maximal prefix of the word (i.e., a path of secured information)
163                for (I sym : word) {
164                        // During this, store the *first* confluence state (i.e., state with
165                        // multiple incoming edges).
166                        if (conf == null && curr.isConfluence()) {
167                                conf = curr;
168                                confIndex = prefixLen;
169                        }
170
171                        int idx = inputAlphabet.getSymbolIndex(sym);
172                        State succ = curr.getSuccessor(idx);
173                        if (succ == null)
174                                break;
175                        // If a transition exists for the input symbol, it also has an output symbol.
176                        // Check if this matches the provided one, otherwise there is a conflict
177                        O outSym = outputWord.getSymbol(prefixLen);
178                        if(!Objects.equals(outSym, curr.getOutput(idx)))
179                                throw new ConflictException("Incompatible output symbols: " + outSym + " vs " + curr.getOutput(idx));
180                        curr = succ;
181                        prefixLen++;
182                }
183
184                // The information was already present - we do not need to continue
185                if (prefixLen == len)
186                        return;
187
188
189                // We then create a suffix path, i.e., a linear sequence of states corresponding to
190                // the suffix (more precisely: the suffix minus the first symbol, since this is the
191                // transition which is used for gluing the suffix path to the existing automaton).
192                Word<I> suffix = word.subWord(prefixLen);
193                Word<O> suffixOut = outputWord.subWord(prefixLen);
194
195                State last;
196
197                State suffixState;
198                State endpoint = null;
199                if(conf != null) {
200                        // If we encountered a confluence state on a way, the whole path including
201                        // the confluence state will have to be duplicated to separate it from
202                        // other prefixes
203                        suffixState = createSuffix(suffix.subWord(1), suffixOut.subWord(1));
204                }
205                else {
206                        // This is a dangerous corner case: If NO confluence state was found, it can happen
207                        // that the last state of the suffix path is merged with the end of the prefix path
208                        // (i.e., when both have no outgoing transitions - note that this is ALWAYS the case
209                        // upon the first insert() call). Because there is no confluence we resolve by cloning
210                        // part of the prefix path, we might accidentally introduce a cycle here.
211                        // Storing the endpoint of the suffix path allows avoiding this later on.
212                        Pair<State,State> suffixRes = createSuffix2(suffix.subWord(1), suffixOut.subWord(1));
213                        suffixState = suffixRes.getFirst();
214                        endpoint = suffixRes.getSecond();
215                }
216                
217                // Here we create the "gluing" transition
218                I sym = suffix.getSymbol(0);
219                int suffTransIdx = inputAlphabet.getSymbolIndex(sym);
220                O suffTransOut = suffixOut.getSymbol(0);
221                
222
223                int currentIndex;
224                if (conf != null) {
225                        // If there was a confluence state, we have to clone all nodes on
226                        // the prefix path up to this state, in order to separate it from other
227                        // prefixes reaching the confluence state (we do not now anything about them
228                        // plus the suffix).
229                        last = clone(curr, suffTransIdx, suffixState, suffTransOut);
230
231                        for (int i = prefixLen - 1; i >= confIndex; i--) {
232                                State s = getState(word.prefix(i));
233                                sym = word.getSymbol(i);
234                                int idx = inputAlphabet.getSymbolIndex(sym);
235                                last = clone(s, idx, last);
236                        }
237
238                        currentIndex = confIndex;
239                } else {
240                        // Otherwise, we have to check for the above-mentioned corner case, and possibly
241                        // also duplicate the last state on the prefix path
242                        if(endpoint == curr)
243                                last = clone(curr, suffTransIdx, suffixState, suffTransOut);
244                        else if(curr != init)
245                                last = updateSignature(curr, suffTransIdx, suffixState, suffTransOut);
246                        else {
247                                // The last state on the prefix path is the initial state. After updating
248                                // its signature, we are done since we cannot backtrack any further.
249                                updateInitSignature(suffTransIdx, suffixState, suffTransOut);
250                                return;
251                        }
252                        currentIndex = prefixLen;
253                }
254                
255                // Finally, we have to refresh all the signatures, iterating backwards
256                // until the updating becomes stable.
257                while (--currentIndex > 0) {
258                        State state = getState(word.prefix(currentIndex));
259                        sym = word.getSymbol(currentIndex);
260                        int idx = inputAlphabet.getSymbolIndex(sym);
261                        last = updateSignature(state, idx, last);
262
263                        if (state == last)
264                                return;
265                }
266                
267                sym = word.getSymbol(0);
268                int idx = inputAlphabet.getSymbolIndex(sym);
269                updateInitSignature(idx, last);
270        }
271
272        
273        /**
274         * Update the signature of the initial state. This requires special handling, as the
275         * initial state is not stored in the register (since it can never legally act as a predecessor).
276         * @param idx the transition index being changed
277         * @param succ the new successor state
278         */
279        private void updateInitSignature(int idx, State succ) {
280                StateSignature sig = init.getSignature();
281                State oldSucc = sig.successors[idx];
282                if(oldSucc == succ)
283                        return;
284                if(oldSucc != null)
285                        oldSucc.decreaseIncoming();
286                sig.successors[idx] = succ;
287                succ.increaseIncoming();
288        }
289        
290        /**
291         * Update the signature of a state, changing only the successor state of a single transition
292         * index.
293         * @param state the state which's signature to update
294         * @param idx the transition index to modify
295         * @param succ the new successor state
296         * @return the resulting state, which can either be the same as the input state (if the new
297         * signature is unique), or the result of merging with another state.
298         */
299        private State updateSignature(State state, int idx, State succ) {
300                StateSignature sig = state.getSignature();
301                if (sig.successors[idx] == succ)
302                        return state;
303                
304                register.remove(sig);
305                if(sig.successors[idx] != null)
306                        sig.successors[idx].decreaseIncoming();
307                sig.successors[idx] = succ;
308                succ.increaseIncoming();
309                sig.updateHashCode();
310                return replaceOrRegister(state);
311        }
312        
313        /**
314         * Updates the signature of the initial state, changing both the successor state
315         * and the output symbol.
316         * @param idx the transition index to change
317         * @param succ the new successor state
318         * @param out the output symbol
319         */
320        private void updateInitSignature(int idx, State succ, O out) {
321                StateSignature sig = init.getSignature();
322                State oldSucc = sig.successors[idx];
323                if(oldSucc == succ && Objects.equals(out, sig.outputs[idx]))
324                        return;
325                if(oldSucc != null)
326                        oldSucc.decreaseIncoming();
327                sig.successors[idx] = succ;
328                sig.outputs[idx] = out;
329                succ.increaseIncoming();
330        }
331        
332        /**
333         * Updates the signature of a state, changing both the successor state and the output
334         * symbol for a single transition index.
335         * @param state the state which's signature to change
336         * @param idx the transition index to change
337         * @param succ the new successor state
338         * @param out the output symbol
339         * @return the resulting state, which can either be the same as the input state (if the new
340         * signature is unique), or the result of merging with another state.
341         */
342        private State updateSignature(State state, int idx, State succ, O out) {
343                StateSignature sig = state.getSignature();
344                if (sig.successors[idx] == succ && Objects.equals(out, sig.outputs[idx]))
345                        return state;
346                
347                register.remove(sig);
348                if(sig.successors[idx] != null)
349                        sig.successors[idx].decreaseIncoming();
350                sig.successors[idx] = succ;
351                succ.increaseIncoming();
352                sig.outputs[idx] = out;
353                sig.updateHashCode();
354                return replaceOrRegister(state);
355        }
356
357
358        private State clone(State other, int idx, State succ) {
359                StateSignature sig = other.getSignature();
360                if (sig.successors[idx] == succ)
361                        return other;
362                sig = sig.clone();
363                sig.successors[idx] = succ;
364                sig.updateHashCode();
365                return replaceOrRegister(sig);
366        }
367        
368        private State clone(State other, int idx, State succ, O out) {
369                StateSignature sig = other.getSignature();
370                if (sig.successors[idx] == succ && Objects.equals(out, sig.outputs[idx]))
371                        return other;
372                sig = sig.clone();
373                sig.successors[idx] = succ;
374                sig.outputs[idx] = out;
375                sig.updateHashCode();
376                return replaceOrRegister(sig);
377        }
378
379        private State replaceOrRegister(StateSignature sig) {
380                State state = register.get(sig);
381                if (state != null)
382                        return state;
383
384                register.put(sig, state = new State(sig));
385                for (int i = 0; i < sig.successors.length; i++) {
386                        State succ = sig.successors[i];
387                        if (succ != null)
388                                succ.increaseIncoming();
389                }
390                return state;
391        }
392
393        private State replaceOrRegister(State state) {
394                StateSignature sig = state.getSignature();
395                State other = register.get(sig);
396                if (other != null) {
397                        if (state != other) {
398                                for (int i = 0; i < sig.successors.length; i++) {
399                                        State succ = sig.successors[i];
400                                        if(succ != null)
401                                                succ.decreaseIncoming();
402                                }
403                        }
404                        return other;
405                }
406
407                register.put(sig, state);
408                return state;
409        }
410
411        private State createSuffix(Word<I> suffix, Word<O> suffixOut) {
412                StateSignature sig = new StateSignature(alphabetSize);
413                sig.updateHashCode();
414                State last = replaceOrRegister(sig);
415                
416                int len = suffix.length();
417                for (int i = len - 1; i >= 0; i--) {
418                        sig = new StateSignature(alphabetSize);
419                        I sym = suffix.getSymbol(i);
420                        O outsym = suffixOut.getSymbol(i);
421                        int idx = inputAlphabet.getSymbolIndex(sym);
422                        sig.successors[idx] = last;
423                        sig.outputs[idx] = outsym;
424                        sig.updateHashCode();
425                        last = replaceOrRegister(sig);
426                }
427
428                return last;
429        }
430        
431        private Pair<State,State> createSuffix2(Word<I> suffix, Word<O> suffixOut) {
432                StateSignature sig = new StateSignature(alphabetSize);
433                sig.updateHashCode();
434                State last = replaceOrRegister(sig);
435                State end = last;
436                
437                int len = suffix.length();
438                for (int i = len - 1; i >= 0; i--) {
439                        sig = new StateSignature(alphabetSize);
440                        I sym = suffix.getSymbol(i);
441                        O outsym = suffixOut.getSymbol(i);
442                        int idx = inputAlphabet.getSymbolIndex(sym);
443                        sig.successors[idx] = last;
444                        sig.outputs[idx] = outsym;
445                        sig.updateHashCode();
446                        last = replaceOrRegister(sig);
447                }
448
449                return Pair.make(last, end);
450        }
451        
452
453        
454        /*
455         * (non-Javadoc)
456         * @see net.automatalib.ts.TransitionSystem#getSuccessor(java.lang.Object)
457         */
458        @Override
459        public State getSuccessor(TransitionRecord transition) {
460                return transition.source.getSuccessor(transition.transIdx);
461        }
462
463        /*
464         * (non-Javadoc)
465         * @see net.automatalib.ts.SimpleDTS#getInitialState()
466         */
467        @Override
468        public State getInitialState() {
469                return init;
470        }
471
472        /*
473         * (non-Javadoc)
474         * @see net.automatalib.graphs.UniversalGraph#getNodeProperties(java.lang.Object)
475         */
476        @Override
477        public Void getNodeProperty(State node) {
478                return null;
479        }
480
481        /*
482         * (non-Javadoc)
483         * @see net.automatalib.graphs.UniversalGraph#getEdgeProperties(java.lang.Object)
484         */
485        @Override
486        @SuppressWarnings("unchecked")
487        public Pair<I, O> getEdgeProperty(TransitionRecord edge) {
488                I input = inputAlphabet.getSymbol(edge.transIdx);
489                O out = (O)edge.source.getOutput(edge.transIdx);
490                return Pair.make(input, out);
491        }
492
493        /*
494         * (non-Javadoc)
495         * @see net.automatalib.graphs.IndefiniteGraph#getOutgoingEdges(java.lang.Object)
496         */
497        @Override
498        public Collection<TransitionRecord> getOutgoingEdges(State node) {
499                List<TransitionRecord> edges = new ArrayList<TransitionRecord>();
500                for(int i = 0; i < alphabetSize; i++) {
501                        if(node.getSuccessor(i) != null)
502                                edges.add(new TransitionRecord(node, i));
503                }
504                return edges;
505        }
506
507        /*
508         * (non-Javadoc)
509         * @see net.automatalib.graphs.IndefiniteGraph#getTarget(java.lang.Object)
510         */
511        @Override
512        public State getTarget(TransitionRecord edge) {
513                return edge.source.getSuccessor(edge.transIdx);
514        }
515
516        /*
517         * (non-Javadoc)
518         * @see net.automatalib.automata.concepts.TransitionOutput#getTransitionOutput(java.lang.Object)
519         */
520        @Override
521        @SuppressWarnings("unchecked")
522        public O getTransitionOutput(TransitionRecord transition) {
523                return (O)transition.source.getOutput(transition.transIdx);
524        }
525
526        /*
527         * (non-Javadoc)
528         * @see net.automatalib.ts.DeterministicTransitionSystem#getTransition(java.lang.Object, java.lang.Object)
529         */
530        @Override
531        public TransitionRecord getTransition(State state, I input) {
532                int idx = inputAlphabet.getSymbolIndex(input);
533                if(state.getSuccessor(idx) != null)
534                        return new TransitionRecord(state, idx);
535                return null;
536        }
537        
538        /*
539         * (non-Javadoc)
540         * @see net.automatalib.graphs.dot.DOTPlottableGraph#getHelper()
541         */
542        @Override
543        public GraphDOTHelper<State, TransitionRecord> getGraphDOTHelper() {
544                return new DOTHelper(inputAlphabet, init);
545        }
546
547        /*
548         * (non-Javadoc)
549         * @see net.automatalib.ts.SimpleFiniteTS#getStates()
550         */
551        @Override
552        public Collection<State> getStates() {
553                return Collections.unmodifiableCollection(register.values());
554        }
555
556        /*
557         * (non-Javadoc)
558         * @see net.automatalib.graphs.FiniteGraph#getNodes()
559         */
560        @Override
561        public Collection<State> getNodes() {
562                return Collections.unmodifiableCollection(register.values());
563        }
564
565        /*
566         * (non-Javadoc)
567         * @see net.automatalib.incremental.IncrementalConstruction#getInputAlphabet()
568         */
569        @Override
570        public Alphabet<I> getInputAlphabet() {
571                return inputAlphabet;
572        }
573
574        /*
575         * (non-Javadoc)
576         * @see net.automatalib.incremental.IncrementalConstruction#findSeparatingWord(java.lang.Object, java.util.Collection, boolean)
577         */
578        @Override
579        public Word<I> findSeparatingWord(MealyMachine<?, I, ?, O> target,
580                        Collection<? extends I> inputs, boolean omitUndefined) {
581                return doFindSeparatingWord(target, inputs, omitUndefined);
582        }
583
584        /*
585         * (non-Javadoc)
586         * @see net.automatalib.incremental.IncrementalConstruction#toAutomaton()
587         */
588        @Override
589        @SuppressWarnings("unchecked")
590        public CompactMealy<I, O> toAutomaton() {
591                CompactMealy<I,O> result = new CompactMealy<I,O>(inputAlphabet, register.size());
592                
593                Map<State,Integer> stateMap = new HashMap<>();
594                
595                for(State s : register.values()) {
596                        Integer id;
597                        if(s == init)
598                                id = result.addInitialState();
599                        else
600                                id = result.addState();
601                        stateMap.put(s, id);
602                }
603                
604                for(Map.Entry<State,Integer> e : stateMap.entrySet()) {
605                        State s = e.getKey();
606                        Integer id = e.getValue();
607                        
608                        for(int i = 0; i < alphabetSize; i++) {
609                                State succ = s.getSuccessor(i);
610                                if(succ == null)
611                                        continue;
612                                I sym = inputAlphabet.getSymbol(i);
613                                O out = (O)s.getOutput(i);
614                                Integer succId = stateMap.get(succ);
615                                result.addTransition(id, sym, succId, out);
616                        }
617                }
618                
619                return result;
620        }
621
622        /*
623         * (non-Javadoc)
624         * @see net.automatalib.incremental.IncrementalConstruction#hasDefinitiveInformation(net.automatalib.words.Word)
625         */
626        @Override
627        public boolean hasDefinitiveInformation(Word<I> word) {
628                State s = getState(word);
629                return (s != null);
630        }
631        
632        ///////////////////////////////////////////////////////////////////////
633        // Equivalence test                                                  //
634        ///////////////////////////////////////////////////////////////////////
635        
636        private static int getStateId(State state, Map<State,Integer> ids) {
637                Integer id = ids.get(state);
638                if(id == null) {
639                        id = ids.size();
640                        ids.put(state, id);
641                }
642                return id.intValue();
643        }
644        
645        private static final class Record<S,I> {
646                private final State state1;
647                private final S state2;
648                private final I reachedVia;
649                private final Record<S,I> reachedFrom;
650                private final int depth;
651                
652                public Record(State state1, S state2, Record<S,I> reachedFrom, I reachedVia) {
653                        this.state1 = state1;
654                        this.state2 = state2;
655                        this.reachedFrom = reachedFrom;
656                        this.reachedVia = reachedVia;
657                        this.depth = (reachedFrom != null) ? reachedFrom.depth + 1 : 0;
658                }
659                
660                public Record(State state1, S state2) {
661                        this(state1, state2, null, null);
662                }
663        }
664        
665        private <S,T> Word<I> doFindSeparatingWord(MealyMachine<S,I,T,O> mealy, Collection<? extends I> inputs, boolean omitUndefined) {
666                int thisStates = register.size();
667                
668                UnionFind uf = new UnionFind(thisStates + mealy.size());
669                
670                Map<State,Integer> ids = new HashMap<State,Integer>();
671                
672                State init1 = init;
673                S init2 = mealy.getInitialState();
674                
675                if(init2 == null)
676                        return omitUndefined ? null : Word.<I>epsilon();
677                
678                StateIDs<S> mealyIds = mealy.stateIDs();
679                
680                int id1 = getStateId(init1, ids), id2 = mealyIds.getStateId(init2) + thisStates;
681                
682                uf.link(id1, id2);
683                
684                Queue<Record<S,I>> queue = new ArrayDeque<Record<S,I>>();
685                
686                queue.offer(new Record<S,I>(init1, init2));
687                
688                I lastSym = null;
689                
690                Record<S,I> current;
691                
692explore:while((current = queue.poll()) != null) {
693                        State state1 = current.state1;
694                        S state2 = current.state2;
695                        
696                        for(I sym : inputs) {
697                                int idx = inputAlphabet.getSymbolIndex(sym);
698                                State succ1 = state1.getSuccessor(idx);
699                                if(succ1 == null)
700                                        continue;
701                                
702                                T trans2 = mealy.getTransition(state2, sym);
703                                if(trans2 == null) {
704                                        if(omitUndefined)
705                                                continue;
706                                        lastSym = sym;
707                                        break explore;
708                                }
709                                
710                                Object out1 = state1.getOutput(idx);
711                                Object out2 = mealy.getTransitionOutput(trans2);
712                                if(!Objects.equals(out1, out2)) {
713                                        lastSym = sym;
714                                        break explore;
715                                }
716                                
717                                S succ2 = mealy.getSuccessor(trans2);
718                                
719                                id1 = getStateId(succ1, ids);
720                                id2 = mealyIds.getStateId(succ2) + thisStates;
721                                
722                                int r1 = uf.find(id1), r2 = uf.find(id2);
723                                
724                                if(r1 == r2)
725                                        continue;
726                                
727                                uf.link(r1, r2);
728                                
729                                queue.offer(new Record<>(succ1, succ2, current, sym));
730                        }
731        }
732                
733                if(current == null)
734                        return null;
735                
736                int ceLength = current.depth;
737                if(lastSym != null)
738                        ceLength++;
739                
740                WordBuilder<I> wb = new WordBuilder<I>(null, ceLength);
741                
742                int index = ceLength;
743                
744                if(lastSym != null)
745                        wb.setSymbol(--index, lastSym);
746                
747                while(current.reachedFrom != null) {
748                        wb.setSymbol(--index, current.reachedVia);
749                        current = current.reachedFrom;
750                }
751                
752                return wb.toWord();
753        }
754
755        @Override
756        public NodeIDs<State> nodeIDs() {
757                return AbstractAutomatonGraph.nodeIDs(this);
758        }
759
760        @Override
761        public <V> MutableMapping<State, V> createStaticNodeMapping() {
762                return AbstractAutomatonGraph.createDynamicNodeMapping(this);
763        }
764
765        @Override
766        public <V> MutableMapping<State, V> createDynamicNodeMapping() {
767                return AbstractAutomatonGraph.createDynamicNodeMapping(this);
768        }
769
770
771}