/*
* Copyright (C) 2020 heizmann@informatik.uni-freiburg.de
* Copyright (C) 2020 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.Map;
import java.util.Optional;
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.FinitePrefix;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.unfolding.ICoRelation;
import de.uni_freiburg.informatik.ultimate.automata.statefactory.IPetriNet2FiniteAutomatonStateFactory;
import de.uni_freiburg.informatik.ultimate.automata.statefactory.IStateFactory;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.HashRelation;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.NestedMap2;
/**
* @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 RemoveRedundantFlow & IPetriNet2FiniteAutomatonStateFactory & INwaInclusionStateFactory>
extends UnaryNetOperation {
private static final boolean DEBUG_LOG_RESTRICTOR_INFORMATION = false;
/**
* Idea: If true we only check if (p,p') is a restrictor-redundancy pair if the restrictor candidate p does not have
* more conditions than the redundancy candidate p'
*/
private static final boolean MOUNTAIN_COCK_HEURISTIC = false;
private final IPetriNet mOperand;
private final FinitePrefix mFinitePrefixOperation;
private final BranchingProcess mFinPre;
private final HashRelation, PLACE> mRedundantSelfloopFlow = new HashRelation<>();
private final BoundedPetriNet mResult;
private final Set mRedundantPlaces;
private final Set mEligibleRedundancyCandidates;
private final Set mEligibleRestrictorCandidates;
private int mRestrictorConditionChecks = 0;
private final NestedMap2 mRestrictorPlaceCache = new NestedMap2<>();
private final Map, Transition> mInput2projected;
public RemoveRedundantFlow(final AutomataLibraryServices services, final IPetriNet operand)
throws AutomataOperationCanceledException, PetriNetNot1SafeException {
this(services, operand, null, null, null);
}
public RemoveRedundantFlow(final AutomataLibraryServices services, final IPetriNet operand,
final BranchingProcess finPre, final Set eligibleRedundancyCandidates,
final Set eligibleRestrictorCandidates)
throws AutomataOperationCanceledException, PetriNetNot1SafeException {
super(services);
mOperand = operand;
mEligibleRedundancyCandidates = eligibleRedundancyCandidates;
mEligibleRestrictorCandidates = eligibleRestrictorCandidates;
printStartMessage();
if (finPre != null) {
mFinitePrefixOperation = null;
mFinPre = finPre;
} else {
mFinitePrefixOperation = new FinitePrefix<>(services, operand);
mFinPre = mFinitePrefixOperation.getResult();
}
final HashRelation, PLACE> redundantFlow = new HashRelation<>();
for (final Transition t : operand.getTransitions()) {
for (final PLACE p : t.getPredecessors()) {
if (isEligibleRedundancyCandidate(p)) {
final boolean isFlowRedundant = isFlowRedundant(t, p, redundantFlow);
if (isFlowRedundant) {
redundantFlow.addPair(t, p);
if (t.getSuccessors().contains(p)) {
mRedundantSelfloopFlow.addPair(t, p);
}
}
}
}
}
mRedundantPlaces = operand.getPlaces().stream().filter(x -> isRedundantPlace(x, operand, redundantFlow))
.collect(Collectors.toSet());
final ProjectToSubnet pts =
new ProjectToSubnet<>(services, operand, mRedundantSelfloopFlow, mRedundantPlaces);
mInput2projected = pts.getTransitionMapping();
mResult = pts.getResult();
printExitMessage();
}
private boolean isEligibleRedundancyCandidate(final PLACE p) {
return mEligibleRedundancyCandidates == null || mEligibleRedundancyCandidates.contains(p);
}
private boolean isEligibleRestrictorCandidate(final PLACE p) {
return mEligibleRestrictorCandidates == null || mEligibleRestrictorCandidates.contains(p);
}
private boolean isRedundantPlace(final PLACE p, final IPetriNet operand,
final HashRelation, PLACE> redundantFlow) {
if (operand.isAccepting(p)) {
return false;
}
final Set> succTrans = operand.getSuccessors(p);
if (succTrans.isEmpty()) {
// TODO 20200225 Matthias: At the moment places without successor
// transitions are not considered redundant. Otherwise we would
// produce transitions without successor which are not yet supported
// by the unfolding.
return false;
}
for (final Transition t : succTrans) {
if (!redundantFlow.containsPair(t, p)) {
return false;
}
}
return true;
}
private boolean isFlowRedundant(final Transition t, final PLACE redundancyCandidate,
final HashRelation, PLACE> redundantFlow)
throws AutomataOperationCanceledException {
for (final PLACE p : t.getPredecessors()) {
if (isEligibleRestrictorCandidate(p) && !p.equals(redundancyCandidate) && (!MOUNTAIN_COCK_HEURISTIC
|| mFinPre.getConditions(p).size() <= mFinPre.getConditions(redundancyCandidate).size())) {
if (redundantFlow.containsPair(t, p)) {
// do nothing
// must not use flow that is already marked for removal
} else {
if (!mServices.getProgressAwareTimer().continueProcessing()) {
throw new AutomataOperationCanceledException(getClass());
}
final boolean isRestrictorPlace = isRestrictorPlace(redundancyCandidate, p);
if (isRestrictorPlace) {
if (DEBUG_LOG_RESTRICTOR_INFORMATION) {
final int restrictorConditions = mFinPre.getConditions(p).size();
final int redundancyConditions = mFinPre.getConditions(redundancyCandidate).size();
mLogger.info("RestrictorPredecessor:" + restrictorConditions + " RendundantPredecessor:"
+ redundancyConditions + " " + p + " " + redundancyCandidate);
}
return true;
}
}
}
}
return false;
}
private boolean isRestrictorPlace(final PLACE redundancyCandidate, final PLACE restrictorCandidate) {
Boolean isRestrictor = mRestrictorPlaceCache.get(redundancyCandidate, restrictorCandidate);
if (isRestrictor == null) {
isRestrictor = checkRestrictorPlace(redundancyCandidate, restrictorCandidate);
mRestrictorPlaceCache.put(redundancyCandidate, restrictorCandidate, isRestrictor);
}
return isRestrictor;
}
private boolean checkRestrictorPlace(final PLACE redundancyCandidate, final PLACE restrictorCandidate) {
for (final Condition restrictorCandidateCondition : mFinPre.place2cond(restrictorCandidate)) {
if (restrictorCandidateCondition.getPredecessorEvent().isCutoffEvent()) {
// we may omit the check because if the the candidate is not a
// restrictor condition there is also another condition of the
// same place that is not a restrictor condition and not
// successor of a cut-off event.
} else {
final boolean isRestrictorCondition = isRestrictorCondition(restrictorCandidateCondition,
redundancyCandidate, mFinPre.getCoRelation());
if (!isRestrictorCondition) {
return false;
}
}
}
return true;
}
private boolean isRestrictorCondition(final Condition restrictorCandidateCondition,
final PLACE redundancyCandidate, final ICoRelation coRelation) {
mRestrictorConditionChecks++;
final Optional> redundancyCandidateCondition =
restrictorCandidateCondition.getPredecessorEvent().getConditionMark().stream()
.filter(x -> x.getPlace().equals(redundancyCandidate)).findAny();
if (!redundancyCandidateCondition.isPresent()) {
return false;
}
final boolean isRestrictorCondition = redundancyCandidateCondition.get().getSuccessorEvents().stream()
.allMatch(x -> !coRelation.isInCoRelation(restrictorCandidateCondition, x));
return isRestrictorCondition;
}
@Override
public BoundedPetriNet getResult() {
return mResult;
}
@Override
protected IPetriNet getOperand() {
return mOperand;
}
public HashRelation, PLACE> getRedundantSelfloopFlow() {
return mRedundantSelfloopFlow;
}
public Set getRedundantPlaces() {
return mRedundantPlaces;
}
public Map, Transition> getOld2projected() {
return mInput2projected;
}
@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 " + getOperationName() + ", result has " + mResult.sizeInformation() + ", removed "
+ mRedundantSelfloopFlow.size() + " selfloop flow, removed " + mRedundantPlaces.size()
+ " redundant places.";
}
@Override
public AutomataOperationStatistics getAutomataOperationStatistics() {
final AutomataOperationStatistics statistics = new AutomataOperationStatistics();
if (mFinitePrefixOperation != null) {
statistics.addAllStatistics(mFinitePrefixOperation.getAutomataOperationStatistics());
}
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.RESTRICTOR_CONDITION_CHECKS, mRestrictorConditionChecks);
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;
}
}