/*
* 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);
}
}