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