/* * Copyright (C) 2021 Dominik Klumpp (klumpp@informatik.uni-freiburg.de) * Copyright (C) 2021 University of Freiburg * * This file is part of the ULTIMATE Automata Library. * * The ULTIMATE Automata Library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE Automata Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE Automata Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE Automata Library, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE Automata Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.automata.partialorder; import java.util.ArrayDeque; import java.util.Comparator; import java.util.Deque; import java.util.stream.StreamSupport; import de.uni_freiburg.informatik.ultimate.automata.AutomataLibraryServices; import de.uni_freiburg.informatik.ultimate.automata.AutomataOperationCanceledException; import de.uni_freiburg.informatik.ultimate.automata.nestedword.INwaOutgoingLetterAndTransitionProvider; import de.uni_freiburg.informatik.ultimate.automata.nestedword.NestedWordAutomataUtils; import de.uni_freiburg.informatik.ultimate.automata.nestedword.transitions.OutgoingInternalTransition; import de.uni_freiburg.informatik.ultimate.automata.partialorder.visitors.IDfsVisitor; import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger; import de.uni_freiburg.informatik.ultimate.util.DfsBookkeeping; import de.uni_freiburg.informatik.ultimate.util.datastructures.DataStructureUtils; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair; /** * Implements a depth-first traversal of deterministic finite automata. * * @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de) * * @param * The type of letters in the traversed automaton * @param * The type of states in the traversed automaton */ public class DepthFirstTraversal { private static final String ABORT_MSG = "visitor aborted traversal"; private final AutomataLibraryServices mServices; private final ILogger mLogger; private final INwaOutgoingLetterAndTransitionProvider mOperand; private final S mStartState; private final IDfsOrder mOrder; private final IDfsVisitor mVisitor; private final Deque>> mWorklist = new ArrayDeque<>(); private final DfsBookkeeping mDfs = new DfsBookkeeping<>(); private int mIndentLevel = -1; /** * Performs a depth-first traversal. This constructor is called purely for its side-effects. * * @param services * automata services used for logging and timeout management * @param operand * The automaton to be traversed * @param order * The order in which transitions for each state should be explored * @param visitor * A visitor to traverse the automaton * @param startingState * A state from which the traversal starts. * @throws AutomataOperationCanceledException * in case of timeout or cancellation */ public DepthFirstTraversal(final AutomataLibraryServices services, final INwaOutgoingLetterAndTransitionProvider operand, final IDfsOrder order, final IDfsVisitor visitor, final S startingState) throws AutomataOperationCanceledException { assert NestedWordAutomataUtils.isFiniteAutomaton(operand) : "DFS supports only finite automata"; mServices = services; mLogger = services.getLoggingService().getLogger(DepthFirstTraversal.class); mOperand = operand; mStartState = startingState; mOrder = order; mVisitor = visitor; traverse(); } /** * Performs a depth-first traversal starting from the operand's initial state. This method is called purely for its * side-effects. * * @param services * automata services used for logging and timeout management * @param operand * The automaton to be traversed * @param order * The order in which transitions for each state should be explored * @param visitor * A visitor to traverse the automaton * @throws AutomataOperationCanceledException * in case of timeout or cancellation */ public static void traverse(final AutomataLibraryServices services, final INwaOutgoingLetterAndTransitionProvider operand, final IDfsOrder order, final IDfsVisitor visitor) throws AutomataOperationCanceledException { final var initial = DataStructureUtils.getOnly(operand.getInitialStates(), "There must only be one initial state"); if (initial.isPresent()) { new DepthFirstTraversal<>(services, operand, order, visitor, initial.get()); } else { final var logger = services.getLoggingService().getLogger(DepthFirstTraversal.class); logger.warn("Depth first traversal did not find any initial state. Returning directly."); } } private void traverse() throws AutomataOperationCanceledException { final boolean abortImmediately = visitState(mStartState); if (abortImmediately) { mLogger.debug(ABORT_MSG); return; } while (!mWorklist.isEmpty()) { if (!mServices.getProgressAwareTimer().continueProcessing()) { throw new AutomataOperationCanceledException(this.getClass()); } final var current = mWorklist.pop(); final S currentState = current.getFirst(); // Backtrack states still on the stack whose exploration has finished. final boolean abort = backtrackUntil(currentState); if (abort) { mLogger.debug(ABORT_MSG); return; } final OutgoingInternalTransition currentTransition = current.getSecond(); final S nextState = currentTransition.getSucc(); debugIndent("Now exploring transition %s --> %s (label: %s)", currentState, nextState, currentTransition.getLetter()); final boolean prune = mVisitor.discoverTransition(currentState, currentTransition.getLetter(), nextState); if (mVisitor.isFinished()) { mLogger.debug(ABORT_MSG); return; } final int stackIndex; if (prune) { debugIndent("-> visitor pruned transition"); } else if (!mDfs.isVisited(nextState)) { final boolean abortNow = visitState(nextState); if (abortNow) { mLogger.debug(ABORT_MSG); return; } } else if ((stackIndex = mDfs.stackIndexOf(nextState)) != -1) { debugIndent("-> state is on stack -- do not unroll loop"); mDfs.updateLoopHead(currentState, new Pair<>(stackIndex, nextState)); } else { debugIndent("-> state was visited before -- no re-exploration"); mDfs.backPropagateLoopHead(currentState, nextState); } } final boolean abort = backtrackUntil(mStartState); if (abort) { mLogger.debug(ABORT_MSG); return; } backtrack(); mLogger.debug("traversal completed"); } private boolean backtrackUntil(final S state) { while (!mDfs.peek().equals(state)) { final boolean abort = backtrack(); if (abort) { return true; } } return false; } private boolean backtrack() { final S oldState = mDfs.peek(); final boolean isComplete = mDfs.backtrack(); debugIndent("backtracking state %s (complete: %s)", oldState, isComplete); mIndentLevel--; mVisitor.backtrackState(oldState, isComplete); return mVisitor.isFinished(); } private boolean visitState(final S state) { assert !mDfs.isVisited(state) : "must never re-visit state"; mIndentLevel++; debugIndent("visiting state %s", state); final boolean pruneSuccessors; if (mStartState.equals(state)) { debugIndent("-> state is start state"); assert !mDfs.hasStarted() : "start state should be first visited state"; pruneSuccessors = mVisitor.addStartState(state); } else { assert mDfs.hasStarted() : "first visited state should be start state"; pruneSuccessors = mVisitor.discoverState(state); } if (mVisitor.isFinished()) { return true; } mDfs.push(state); if (pruneSuccessors) { debugIndent("-> visitor pruned all outgoing edges"); } else { final Comparator> comp = Comparator., L> comparing(OutgoingInternalTransition::getLetter, mOrder.getOrder(state)).reversed(); StreamSupport.stream(mOperand.internalSuccessors(state).spliterator(), false).sorted(comp) .forEachOrdered(out -> mWorklist.push(new Pair<>(state, out))); } return false; } private void debugIndent(final String msg, final Object... params) { mLogger.debug(" ".repeat(mIndentLevel) + msg, params); } }