/* * Copyright (C) 2023 Philipp Müller (pm251@venus.uni-freiburg.de) * Copyright (C) 2023 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.rabin; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import de.uni_freiburg.informatik.ultimate.automata.AutomataLibraryServices; import de.uni_freiburg.informatik.ultimate.automata.AutomataOperationCanceledException; import de.uni_freiburg.informatik.ultimate.automata.GeneralOperation; import de.uni_freiburg.informatik.ultimate.automata.nestedword.buchi.NestedLassoWord; import de.uni_freiburg.informatik.ultimate.automata.nestedword.transitions.OutgoingInternalTransition; import de.uni_freiburg.informatik.ultimate.automata.statefactory.IStateFactory; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair; /** * Computes the acceptance of an infinite word for a Rabin automaton * * @author Philipp Müller (pm251@venus.uni-freiburg.de) * * @param * letter * @param * state * @param * crsf */ public class Accepts> extends GeneralOperation { private final boolean mResult; /** * Checks acceptance of a NestedLassoWord on IRabinAutomaton * * @param services * services * @param automaton * The Rabin automaton which is simulated for acceptance purposes * @param word * The word that is evaluated on automaton * @throws AutomataOperationCanceledException * exception if isCancellationRequested */ public Accepts(final AutomataLibraryServices services, final IRabinAutomaton automaton, final NestedLassoWord word) throws AutomataOperationCanceledException { super(services); // TODO: Could we use another type of lasso-words here instead? if (!word.getStem().hasEmptyNestingRelation() || !word.getLoop().hasEmptyNestingRelation()) { throw new AssertionError("Rabin automata cannot handle calls/returns."); } mResult = computeResult(automaton, word.getStem().asList(), word.getLoop().asList()); } /** * Checks acceptance of a Word split into stem and loop on IRabinAutomaton * * @param services * services * @param automaton * The Rabin automaton which is simulated for acceptance purposes * @param stem * The stem of the word that is evaluated on automaton * @param loop * The loop of the word that is evaluated on automaton * @throws AutomataOperationCanceledException * exception if isCancellationRequested */ public Accepts(final AutomataLibraryServices services, final IRabinAutomaton automaton, final List stem, final List loop) throws AutomataOperationCanceledException { super(services); mResult = computeResult(automaton, stem, loop); } /* * computes paths to accepting states with iterative letter evaluation and returns true iff if one of them has a * valid infinite loop returns false if no new situations with letter state pairs are found iff no true value was * returned first */ private boolean computeResult(final IRabinAutomaton automaton, final List stem, final List loop) throws AutomataOperationCanceledException { // computes all states which the automaton can be in after the stem is consumed final ArrayList currentStateSet = stemEvaluation(automaton, stem); // a index referencing the current letter/position in loop which is to be consumed in this step int loopIndex = 0; // situations refer to the combination of a state and a letter to be consumed(referenced by loopIndex), if one // situation is encountered twice it is not to be evaluated again since its effects/successors are already // considered final HashSet> uniqueSituations = new HashSet<>(); final HashSet> visitedSituations = new HashSet<>(); currentStateSet.forEach(x -> uniqueSituations.add(new Pair<>(0, x))); do { if (isCancellationRequested()) { throw new AutomataOperationCanceledException(getClass()); } // if there are no transitions for an input letter the automaton does not accept if (currentStateSet.isEmpty()) { return false; } final ArrayList temp = new ArrayList<>(); for (final STATE state : currentStateSet) { // if there is a loop on a accepting, nonFinite state the automaton accepts, since visited situations // already encountered the test for hasLoop we don't have to test them again if (automaton.isAccepting(state) && !automaton.isFinite(state) && !visitedSituations.contains(new Pair<>(loopIndex, state)) && hasLoop(automaton, state, loop, loopIndex)) { return true; } visitedSituations.add(new Pair<>(loopIndex, state)); for (final OutgoingInternalTransition possibleTransition : automaton.getSuccessors(state, loop.get(loopIndex))) { final STATE sucessor = possibleTransition.getSucc(); if (!visitedSituations.contains(new Pair<>((loopIndex + 1) % loop.size(), sucessor))) { temp.add(sucessor); } } } currentStateSet.clear(); currentStateSet.addAll(temp); // for the next step we consider the next letter, if we consumed loop once the next letter will therefore be // the one at index 0 of loop, so it can be consumed "infinitely" often loopIndex = (loopIndex + 1) % loop.size(); for (final STATE state : currentStateSet) { uniqueSituations.add(new Pair<>(loopIndex, state)); } } while (!uniqueSituations.equals(visitedSituations)); // if there are only non-accepting self loops(no new situations are produced) the automaton does not accept return false; } /** * Produces a new set of starting states that is valid after stem is read. Works similar to a NFA interpreter. */ private ArrayList stemEvaluation(final IRabinAutomaton automaton, final List stem) { ArrayList currentStateSet = new ArrayList<>(automaton.getInitialStates()); // we consume each letter from the stem in a step and derive from it a new situation with a respective stateSet for (final LETTER letter : stem) { if (currentStateSet.isEmpty()) { break; } final ArrayList temp = new ArrayList<>(); for (final STATE currentState : currentStateSet) { for (final OutgoingInternalTransition possibleTransition : automaton .getSuccessors(currentState, letter)) { temp.add(possibleTransition.getSucc()); } } currentStateSet = temp; } return currentStateSet; } /** * Tests if a loop exists from state to itself with input loop */ private boolean hasLoop(final IRabinAutomaton automaton, final STATE start, final List loop, final int loopIndex) throws AutomataOperationCanceledException { ArrayList currentStateSet = new ArrayList<>(); int localLoopIndex = loopIndex; final HashSet> uniqueSituations = new HashSet<>(); final HashSet> visitedSituations = new HashSet<>(); currentStateSet.add(start); uniqueSituations.add(new Pair<>(loopIndex, start)); do { if (isCancellationRequested()) { throw new AutomataOperationCanceledException(getClass()); } if (currentStateSet.isEmpty()) { return false; } final ArrayList temp = new ArrayList<>(); for (final STATE state : currentStateSet) { for (final OutgoingInternalTransition possibleTransition : automaton.getSuccessors(state, loop.get(localLoopIndex))) { final STATE sucessor = possibleTransition.getSucc(); // we only consider nonFinite states in the loop evaluation, all finite properties are taken care of // by other methods if (!automaton.isFinite(sucessor)) { // if we reach a situation where a nonzero multiple of loop has been consumed and reach the // original state we found a loop if (loopIndex == (localLoopIndex + 1) % loop.size() && sucessor.equals(start)) { return true; } if (!visitedSituations.contains(new Pair<>((localLoopIndex + 1) % loop.size(), sucessor))) { temp.add(sucessor); } } } visitedSituations.add(new Pair<>(localLoopIndex, state)); } currentStateSet = temp; // for the next step we consider the next letter, if we consumed loop once the next letter will therefore be // the one at index 0 of loop, so it can be consumed "infinitely" often localLoopIndex = (localLoopIndex + 1) % loop.size(); for (final STATE state : currentStateSet) { uniqueSituations.add(new Pair<>(localLoopIndex, state)); } } while (!uniqueSituations.equals(visitedSituations)); // if there are only self loops for other states(no new situations are produced) the automaton has no loop at // start return false; } @Override public Boolean getResult() { return mResult; } }