/*
* Copyright (C) 2018 Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* Copyright (C) 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.Iterator;
import java.util.Map;
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.GeneralOperation;
import de.uni_freiburg.informatik.ultimate.automata.StatisticsType;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.INestedWordAutomaton;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.INwaInclusionStateFactory;
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.operations.RemoveDeadEnds;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.transitions.OutgoingInternalTransition;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.IPetriNet;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.IPetriNetSuccessorProvider;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.IPetriNetTransitionProvider;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.PetriNetNot1SafeException;
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.FinitePrefix;
import de.uni_freiburg.informatik.ultimate.automata.statefactory.IPetriNet2FiniteAutomatonStateFactory;
/**
*
* @author Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* @author schaetzc@informatik.uni-freiburg.de
*
* @param
* Type of letters in the alphabet of minuend Petri net, subtrahend DFA, and difference Petri net
* @param
* Type of places in minuend and difference Petri net
* @param
* Type of factory needed to check the result of this operation in {@link #checkResult(CRSF)}
*/
public final class DifferencePairwiseOnDemand & INwaInclusionStateFactory>
extends GeneralOperation {
/**
* If set, we add the statistics of the finite prefix to the statistics of this operation. This is helpful for
* debugging and analyzing the results. The statistics of the finite prefix are however computed on demand and this
* computation comes with minor costs.
* If we want to evaluate the speed of this algorithm the generation of statistics can be switched off. The automata
* script interpreter calls the following method, which triggers the computation of the finite prefix's statistics.
* Applications, like our software verification, typically do not call this method.
* {@link DifferencePairwiseOnDemand#getAutomataOperationStatistics()} Hence, an evaluation without the finite
* prefix's statistics gives a better impression about the performance in practice. See
* https://github.com/ultimate-pa/ultimate/issues/448#issuecomment-603025477 and
* https://github.com/ultimate-pa/ultimate/issues/448#issuecomment-608669868
*/
private static final boolean ADD_FINITE_PREFIX_STATISTICS = true;
private final IPetriNetSuccessorProvider mMinuend;
private final INwaOutgoingLetterAndTransitionProvider mSubtrahend;
private final FinitePrefix mFinitePrefixOfDifference;
private final BoundedPetriNet mResult;
private final DifferenceSynchronizationInformation mDifferenceSynchronizationInformation;
private Map, Transition> mTransitionBacktranslation;
public DifferencePairwiseOnDemand(final AutomataLibraryServices services,
final IPetriNetSuccessorProvider minuendNet,
final INwaOutgoingLetterAndTransitionProvider subtrahendDfa,
final Set userProvidedUniversalSubtrahendLoopers)
throws AutomataOperationCanceledException, PetriNetNot1SafeException {
super(services);
mMinuend = minuendNet;
mSubtrahend = subtrahendDfa;
if (mLogger.isInfoEnabled()) {
mLogger.info(startMessage());
}
final Set universalSubtrahendLoopers;
if (userProvidedUniversalSubtrahendLoopers != null) {
universalSubtrahendLoopers = userProvidedUniversalSubtrahendLoopers;
if (mLogger.isInfoEnabled()) {
final int numberLoopers = universalSubtrahendLoopers.size();
final int allLetters = mSubtrahend.getAlphabet().size();
mLogger.info("Universal subtrahend loopers provided by user.");
mLogger.info("Number of universal subtrahend loopers: " + numberLoopers + " of " + allLetters);
}
} else if (mSubtrahend instanceof INestedWordAutomaton) {
universalSubtrahendLoopers = determineUniversalLoopers((INestedWordAutomaton) mSubtrahend);
if (mLogger.isInfoEnabled()) {
final int numberLoopers = universalSubtrahendLoopers.size();
final int allLetters = mSubtrahend.getAlphabet().size();
mLogger.info("Number of universal subtrahend loopers: " + numberLoopers + " of " + allLetters);
}
} else {
universalSubtrahendLoopers = null;
mLogger.info("Subtrahend is not yet constructed. Will not use universal subtrahend loopers optimization.");
}
final DifferencePetriNet difference =
new DifferencePetriNet<>(mServices, mMinuend, mSubtrahend, universalSubtrahendLoopers);
mFinitePrefixOfDifference = new FinitePrefix<>(mServices, difference);
mResult = difference.getYetConstructedPetriNet();
mTransitionBacktranslation = difference.getTransitionBacktranslation();
final Set> vitalTransitionsOfDifference =
mFinitePrefixOfDifference.getResult().computeVitalTransitions();
mDifferenceSynchronizationInformation =
difference.computeDifferenceSynchronizationInformation(vitalTransitionsOfDifference, true);
final int allTransitions = difference.getYetConstructedPetriNet().getTransitions().size();
final int deadTransitions = allTransitions - vitalTransitionsOfDifference.size();
final int looperLetters =
mMinuend.getAlphabet().size() - mDifferenceSynchronizationInformation.getChangerLetters().size();
mLogger.info(looperLetters + "/" + mMinuend.getAlphabet().size() + " looper letters, "
+ mDifferenceSynchronizationInformation.getSelfloops().size() + " selfloop transitions, "
+ mDifferenceSynchronizationInformation.getStateChangers().size() + " changer transitions "
+ deadTransitions + "/" + allTransitions + " dead transitions.");
if (mLogger.isInfoEnabled()) {
mLogger.info(exitMessage());
}
}
public DifferencePairwiseOnDemand(final AutomataLibraryServices services,
final IPetriNetSuccessorProvider minuendNet,
final INwaOutgoingLetterAndTransitionProvider subtrahendDfa)
throws AutomataOperationCanceledException, PetriNetNot1SafeException {
this(services, minuendNet, subtrahendDfa, null);
}
public Map, Transition> getTransitionBacktranslation() {
return mTransitionBacktranslation;
}
@Override
public String startMessage() {
return "Start " + getOperationName() + ". First operand " + mMinuend.sizeInformation() + ". Second operand "
+ mSubtrahend.sizeInformation();
}
@Override
public String exitMessage() {
return "Finished " + getOperationName() + ". Result " + mResult.sizeInformation();
}
@Override
public AutomataOperationStatistics getAutomataOperationStatistics() {
final AutomataOperationStatistics statistics = new AutomataOperationStatistics();
if (ADD_FINITE_PREFIX_STATISTICS) {
statistics.addAllStatistics(mFinitePrefixOfDifference.getAutomataOperationStatistics());
}
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());
if (mMinuend instanceof IPetriNet) {
final IPetriNet minuendPetriNet = (IPetriNet) mMinuend;
statistics.addKeyValuePair(StatisticsType.PETRI_DIFFERENCE_MINUEND_PLACES,
minuendPetriNet.getPlaces().size());
statistics.addKeyValuePair(StatisticsType.PETRI_DIFFERENCE_MINUEND_TRANSITIONS,
minuendPetriNet.getTransitions().size());
statistics.addKeyValuePair(StatisticsType.PETRI_DIFFERENCE_MINUEND_FLOW, minuendPetriNet.flowSize());
}
if (mSubtrahend instanceof INestedWordAutomaton) {
statistics.addKeyValuePair(StatisticsType.PETRI_DIFFERENCE_SUBTRAHEND_STATES,
((INestedWordAutomaton) mSubtrahend).getStates().size());
}
return statistics;
}
@Override
public BoundedPetriNet getResult() {
return mResult;
}
public FinitePrefix getFinitePrefixOfDifference() {
return mFinitePrefixOfDifference;
}
public DifferenceSynchronizationInformation getDifferenceSynchronizationInformation() {
return mDifferenceSynchronizationInformation;
}
@Override
public boolean checkResult(final CRSF stateFactory) throws AutomataLibraryException {
if (mLogger.isInfoEnabled()) {
mLogger.info("Testing correctness of " + getOperationName());
}
INestedWordAutomaton subtrahend;
if (mSubtrahend instanceof INestedWordAutomaton) {
subtrahend = (INestedWordAutomaton) mSubtrahend;
} else {
subtrahend = new RemoveDeadEnds<>(mServices, mSubtrahend).getResult();
}
if (!(mMinuend instanceof IPetriNetTransitionProvider)) {
throw new UnsupportedOperationException("Convert minuend to fully constructed net");
}
final boolean correct = PetriNetUtils.doDifferenceLanguageCheck(mServices, stateFactory,
(IPetriNetTransitionProvider) mMinuend, subtrahend, mResult);
if (mLogger.isInfoEnabled()) {
mLogger.info("Finished testing correctness of " + getOperationName());
}
return correct;
}
private static Set
determineUniversalLoopers(final INestedWordAutomaton nwa) {
if (!NestedWordAutomataUtils.isFiniteAutomaton(nwa)) {
throw new UnsupportedOperationException("call and return not implemented yet");
}
return nwa.getAlphabet().stream().filter(letter -> isUniversalLooper(letter, nwa)).collect(Collectors.toSet());
}
private static boolean isUniversalLooper(final LETTER letter,
final INestedWordAutomaton nwa) {
return nwa.getStates().stream().allMatch(state -> hasSelfloop(letter, state, nwa));
}
private static boolean hasSelfloop(final LETTER letter, final STATE state,
final INestedWordAutomaton nwa) {
final Iterator> it = nwa.internalSuccessors(state, letter).iterator();
if (!it.hasNext()) {
return false;
}
final OutgoingInternalTransition succTrans = it.next();
if (it.hasNext()) {
throw new IllegalArgumentException("automaton is nondeterministic");
}
return succTrans.getSucc().equals(state);
}
}