/* * Copyright (C) 2018 schaetzc@tf.uni-freiburg.de * Copyright (C) 2009-2018 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.petrinet.operations; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; import de.uni_freiburg.informatik.ultimate.automata.AutomataLibraryException; import de.uni_freiburg.informatik.ultimate.automata.AutomataLibraryServices; import de.uni_freiburg.informatik.ultimate.automata.AutomataOperationCanceledException; import de.uni_freiburg.informatik.ultimate.automata.AutomataOperationStatistics; import de.uni_freiburg.informatik.ultimate.automata.StatisticsType; import de.uni_freiburg.informatik.ultimate.automata.nestedword.INwaInclusionStateFactory; import de.uni_freiburg.informatik.ultimate.automata.petrinet.IPetriNet; import de.uni_freiburg.informatik.ultimate.automata.petrinet.PetriNetNot1SafeException; import de.uni_freiburg.informatik.ultimate.automata.petrinet.UnaryNetOperation; import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.BoundedPetriNet; import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.PetriNetUtils; import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.Transition; import de.uni_freiburg.informatik.ultimate.automata.petrinet.unfolding.BranchingProcess; import de.uni_freiburg.informatik.ultimate.automata.petrinet.unfolding.Condition; import de.uni_freiburg.informatik.ultimate.automata.petrinet.unfolding.Event; import de.uni_freiburg.informatik.ultimate.automata.petrinet.unfolding.FinitePrefix; import de.uni_freiburg.informatik.ultimate.automata.statefactory.IPetriNet2FiniteAutomatonStateFactory; import de.uni_freiburg.informatik.ultimate.automata.statefactory.IStateFactory; /** * Removes dead transitions in a Petri Net preserving its language. A transition t is dead iff there is no firing * sequence containing t and ending in an accepting marking. In other words: Dead transitions do not contribute to the * accepted language. Unreachable transitions are a subset of the dead transitions. *

* This operation assumes that unreachable transitions were already removed. Call {@link RemoveUnreachable} before * {@link RemoveDead} or some dead (but not necessarily unreachable) transitions might not be removed. *

* This operation also removes some places that do not contribute to the accepted language. *

* TODO 20200126 Matthias: Does not detect dead transitions that have some vital successor places like the transition * ({pb pc} sync {p3}) in the DivergeAndSync.ats example. Solution: Enable COMPUTE_VITAL_TRANSITIONS_IN_UNFOLDING which * (bug or feature?) removes also unreachable transitions. * * @author schaetzc@tf.uni-freiburg.de * @author heizmann@informatik.uni-freiburg.de * * @param * Type of letters in alphabet of Petri net * @param * Type of places in Petri net * @param * Type of factory needed to check the result of this operation in {@link #checkResult(CRSF)} */ public class RemoveDead & IPetriNet2FiniteAutomatonStateFactory & INwaInclusionStateFactory> extends UnaryNetOperation { private static final boolean DEBUG_COMPUTE_REMOVED_TRANSITIONS = false; /** * If set to false we use an outdated algorithm that does not always remove all dead transitions. TODO Matthias * 20200204: If someone does further modification in this class he or she should remove the old algorithm. */ private static final boolean COMPUTE_VITAL_TRANSITIONS_VIA_UNFOLDING = true; private final IPetriNet mOperand; private BranchingProcess mFinPre; private Collection> mAcceptingConditions; private final Set> mVitalTransitions; private final BoundedPetriNet mResult; public RemoveDead(final AutomataLibraryServices services, final IPetriNet operand) throws AutomataOperationCanceledException, PetriNetNot1SafeException { this(services, operand, null, false); } public RemoveDead(final AutomataLibraryServices services, final IPetriNet operand, final BranchingProcess finPre, final boolean keepUselessSuccessorPlaces) throws AutomataOperationCanceledException, PetriNetNot1SafeException { super(services); mOperand = operand; if (finPre != null) { mFinPre = finPre; } else { mFinPre = new FinitePrefix<>(services, operand).getResult(); } printStartMessage(); if (COMPUTE_VITAL_TRANSITIONS_VIA_UNFOLDING) { mVitalTransitions = mFinPre.computeVitalTransitions(); } else { mVitalTransitions = vitalTransitions(); } if (DEBUG_COMPUTE_REMOVED_TRANSITIONS) { final Set> removedTransitions = operand.getTransitions().stream() .filter(x -> !mVitalTransitions.contains(x)).collect(Collectors.toSet()); if (!removedTransitions.isEmpty()) { mLogger.info("Removed transitions: " + removedTransitions); } } mResult = CopySubnet.copy(services, mOperand, mVitalTransitions, mOperand.getAlphabet(), keepUselessSuccessorPlaces); printExitMessage(); } private Set> vitalTransitions() throws AutomataOperationCanceledException, PetriNetNot1SafeException { final Set> vitalTransitions = transitivePredecessors(mOperand.getAcceptingPlaces()); if (vitalTransitions.size() == mOperand.getTransitions().size()) { mLogger.debug("Skipping co-relation queries. All transitions lead to accepting places."); } else { ensureFinPreExists(); mAcceptingConditions = acceptingConditions(); mFinPre.getEvents().stream().filter(event -> event != mFinPre.getDummyRoot()) // optimization to reduce number of co-relation queries .filter(event -> !vitalTransitions.contains(event.getTransition())).filter(event -> !timeout()) .filter(this::coRelatedToAnyAccCond).map(Event::getTransition).forEach(vitalTransitions::add); if (timeout()) { throw new AutomataOperationCanceledException(this.getClass()); } } return vitalTransitions; } private void ensureFinPreExists() throws AutomataOperationCanceledException, PetriNetNot1SafeException { if (mFinPre == null) { mLogger.info("Computing finite prefix for " + getOperationName()); mFinPre = new FinitePrefix<>(mServices, mOperand).getResult(); } } private boolean timeout() { return !mServices.getProgressAwareTimer().continueProcessing(); } private boolean coRelatedToAnyAccCond(final Event event) { return mAcceptingConditions.stream() .anyMatch(condition -> mFinPre.getCoRelation().isInCoRelation(condition, event)); } private Set> transitivePredecessors(final Collection places) { final Set> transitivePredecessors = new HashSet<>(); final Set visited = new HashSet<>(); final Queue work = new LinkedList<>(places); while (!work.isEmpty()) { final PLACE place = work.poll(); for (final Transition predTransition : mOperand.getPredecessors(place)) { transitivePredecessors.add(predTransition); predTransition.getPredecessors().stream().filter(visited::add).forEach(work::add); } } return transitivePredecessors; } private Collection> acceptingConditions() { assert mFinPre != null : "Finite prefix not computed yet."; return mOperand.getAcceptingPlaces().stream().map(mFinPre::place2cond).flatMap(Collection::stream) .collect(Collectors.toList()); } @Override public BoundedPetriNet getResult() { return mResult; } @Override protected IPetriNet getOperand() { return mOperand; } @Override public boolean checkResult(final CRSF stateFactory) throws AutomataLibraryException { if (mLogger.isInfoEnabled()) { mLogger.info("Testing correctness of " + getOperationName()); } final boolean correct = PetriNetUtils.isEquivalent(mServices, stateFactory, mOperand, mResult); if (mLogger.isInfoEnabled()) { mLogger.info("Finished testing correctness of " + getOperationName()); } return correct; } @Override public String exitMessage() { return "Finished " + this.getClass().getSimpleName() + ", result has " + mResult.sizeInformation(); } @Override public AutomataOperationStatistics getAutomataOperationStatistics() { final AutomataOperationStatistics statistics = new AutomataOperationStatistics(); statistics.addKeyValuePair(StatisticsType.PETRI_REMOVED_PLACES, mOperand.getPlaces().size() - mResult.getPlaces().size()); statistics.addKeyValuePair(StatisticsType.PETRI_REMOVED_TRANSITIONS, mOperand.getTransitions().size() - mResult.getTransitions().size()); statistics.addKeyValuePair(StatisticsType.PETRI_REMOVED_FLOW, mOperand.flowSize() - mResult.flowSize()); statistics.addKeyValuePair(StatisticsType.PETRI_ALPHABET, mResult.getAlphabet().size()); statistics.addKeyValuePair(StatisticsType.PETRI_PLACES, mResult.getPlaces().size()); statistics.addKeyValuePair(StatisticsType.PETRI_TRANSITIONS, mResult.getTransitions().size()); statistics.addKeyValuePair(StatisticsType.PETRI_FLOW, mResult.flowSize()); return statistics; } }