/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.uni_freiburg.informatik.ultimate.automata.AutomataLibraryServices;
import de.uni_freiburg.informatik.ultimate.automata.AutomataOperationCanceledException;
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.petrinet.IPetriNetSuccessorProvider;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.Marking;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.BoundedPetriNet;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.ISuccessorTransitionProvider;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.SimpleSuccessorTransitionProvider;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.SuccessorTransitionProviderSplit;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.netdatastructures.Transition;
import de.uni_freiburg.informatik.ultimate.core.model.models.IElement;
import de.uni_freiburg.informatik.ultimate.util.datastructures.DataStructureUtils;
import de.uni_freiburg.informatik.ultimate.util.datastructures.ImmutableSet;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.HashRelation;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.NestedMap2;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair;
/**
* Petri net for on-demand construction of difference.
*
* @author Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
*/
public class DifferencePetriNet implements IPetriNetSuccessorProvider {
private final AutomataLibraryServices mServices;
private final IPetriNetSuccessorProvider mMinuend;
private final INwaOutgoingLetterAndTransitionProvider mSubtrahend;
private final Map, Transition> mNew2Old = new HashMap<>();
private final Map, PLACE> mNewTransition2AutomatonPredecessorState = new HashMap<>();
private final HashRelation, PLACE> mBlockingConfigurations = new HashRelation<>();
private final Set mMinuendPlaces = new HashSet<>();
private final Set mSubtrahendStates = new HashSet<>();
private final NestedMap2, PLACE, Transition> mInputTransition2State2OutputTransition =
new NestedMap2<>();
private int mNumberOfConstructedTransitions;
/**
* Letters for which the subtrahend DFA has a selfloop in every state. This set is provided by the user of
* {@link DifferencePetriNet} it can be an underapproximation of the letters that have a selfloop, we do not check
* if this set does really contain only universal loopers (i.e., we do not check if what the user provided was
* correct).
*/
private final Set mUniversalLoopers;
private final BoundedPetriNet mYetConstructedResult;
public DifferencePetriNet(final AutomataLibraryServices services,
final IPetriNetSuccessorProvider minued,
final INwaOutgoingLetterAndTransitionProvider subtrahend,
final Set universalLoopers) {
super();
mServices = services;
mMinuend = minued;
mSubtrahend = subtrahend;
mUniversalLoopers = universalLoopers;
mYetConstructedResult = new BoundedPetriNet<>(mServices, mMinuend.getAlphabet(), false);
}
@Override
public Set getInitialPlaces() {
final Iterator it = mSubtrahend.getInitialStates().iterator();
if (!it.hasNext()) {
throw new UnsupportedOperationException(
"Subtrahend has no initial states! We could soundly return the minuend as result (implement this if required). "
+ "However we presume that in most cases, such a subtrahend was passed accidentally");
}
final Set result = new HashSet<>(mMinuend.getInitialPlaces());
final PLACE automatonInitialState = it.next();
result.add(automatonInitialState);
if (it.hasNext()) {
throw new IllegalArgumentException("subtrahend not deterministic");
}
if (mSubtrahend.isFinal(automatonInitialState)) {
return Collections.emptySet();
}
addSubtrahendState(automatonInitialState);
for (final PLACE initialPlace : mMinuend.getInitialPlaces()) {
addMinuendPlace(initialPlace);
}
return result;
}
private void addMinuendPlace(final PLACE newMinuendPlace) {
final boolean newlyAdded = mMinuendPlaces.add(newMinuendPlace);
if (newlyAdded) {
final boolean isInitial = mMinuend.getInitialPlaces().contains(newMinuendPlace);
final boolean isAccepting = mMinuend.isAccepting(newMinuendPlace);
final boolean newlyAddedToYcr = mYetConstructedResult.addPlace(newMinuendPlace, isInitial, isAccepting);
if (!newlyAddedToYcr) {
throw new AssertionError("Must not add place twice: " + newMinuendPlace);
}
}
}
private void addSubtrahendState(final PLACE newSubtrahendState) {
final boolean newlyAdded = mSubtrahendStates.add(newSubtrahendState);
if (newlyAdded) {
final boolean isInitial = mSubtrahend.isInitial(newSubtrahendState);
final boolean newlyAddedToYcr = mYetConstructedResult.addPlace(newSubtrahendState, isInitial, false);
if (!newlyAddedToYcr) {
throw new AssertionError("Must not add place twice: " + newSubtrahendState);
}
}
}
@Override
public Collection>
getSuccessorTransitionProviders(final Set mustPlaces, final Set mayPlaces) {
if (mustPlaces.isEmpty()) {
return Collections.emptySet();
}
assert (mayPlaces.containsAll(mustPlaces)) : "some must place is not may place";
final Pair, Set> splitMust = split(mustPlaces);
final Set minuendMustPlaces = splitMust.getFirst();
final Set subtrahendMustStates = splitMust.getSecond();
final Pair, Set> splitMay = split(mayPlaces);
final Set minuendMayPlaces = splitMay.getFirst();
final Set subtrahendMayStates = splitMay.getSecond();
final Collection> minuendStps;
if (subtrahendMustStates.isEmpty()) {
minuendStps = mMinuend.getSuccessorTransitionProviders(minuendMustPlaces, minuendMayPlaces);
} else {
minuendStps = mMinuend.getSuccessorTransitionProviders(minuendMayPlaces, minuendMayPlaces);
}
final boolean useUniversalLoopersOptimization = (mUniversalLoopers != null);
final List> result = new ArrayList<>();
for (final ISuccessorTransitionProvider minuendStp : minuendStps) {
// TODO: Compute on-demand
final boolean emtpyIntersectionWithMinuendMustPlaces =
DataStructureUtils.haveEmptyIntersection(minuendStp.getPredecessorPlaces(), minuendMustPlaces);
if (useUniversalLoopersOptimization) {
final SuccessorTransitionProviderSplit split =
new SuccessorTransitionProviderSplit<>(minuendStp, mUniversalLoopers);
if (split.getSuccTransProviderLetterInSet() != null) {
// copy from minuend, no need to synchronize
if (emtpyIntersectionWithMinuendMustPlaces) {
// do nothing
// must not add, no corresponding place
// transition will be considered if one of the
// predecessor places is considered
} else {
result.add(new SimpleSuccessorTransitionProviderWithUsageInformation(
split.getSuccTransProviderLetterInSet().getTransitions()));
}
}
if (split.getSuccTransProviderLetterNotInSet() != null) {
Set automatonPredecessorsToConsider;
if (emtpyIntersectionWithMinuendMustPlaces) {
automatonPredecessorsToConsider = subtrahendMustStates;
} else {
automatonPredecessorsToConsider = subtrahendMayStates;
}
for (final PLACE automatonPredecessor : automatonPredecessorsToConsider) {
result.add(new DifferenceSuccessorTransitionProvider(split.getSuccTransProviderLetterNotInSet(),
automatonPredecessor));
}
}
} else {
Set automatonPredecessorsToConsider;
if (emtpyIntersectionWithMinuendMustPlaces) {
automatonPredecessorsToConsider = subtrahendMustStates;
} else {
automatonPredecessorsToConsider = subtrahendMayStates;
}
for (final PLACE automatonPredecessor : automatonPredecessorsToConsider) {
result.add(new DifferenceSuccessorTransitionProvider(minuendStp, automatonPredecessor));
}
}
}
return result;
}
private Pair, Set> split(final Set places) {
final Pair, Set> result = new Pair<>(new HashSet<>(), new HashSet<>());
for (final PLACE place : places) {
if (mMinuendPlaces.contains(place)) {
result.getFirst().add(place);
} else if (mSubtrahendStates.contains(place)) {
result.getSecond().add(place);
}
}
return result;
}
@Override
public boolean isAccepting(final Marking marking) {
final Set petriNetPlaces = new HashSet<>();
for (final PLACE place : marking) {
if (mMinuendPlaces.contains(place)) {
petriNetPlaces.add(place);
}
}
final Marking filteredMarking = new Marking<>(ImmutableSet.of(petriNetPlaces));
return mMinuend.isAccepting(filteredMarking);
}
private class DifferenceSuccessorTransitionProvider implements ISuccessorTransitionProvider {
private final ISuccessorTransitionProvider mPetriNetPredecessors;
private final PLACE mAutomatonPredecessor;
private final ImmutableSet mAllPredecessors;
public DifferenceSuccessorTransitionProvider(
final ISuccessorTransitionProvider petriNetPredecessors,
final PLACE automatonPredecessor) {
super();
mPetriNetPredecessors = petriNetPredecessors;
mAutomatonPredecessor = automatonPredecessor;
final Set predecessors = new LinkedHashSet<>(petriNetPredecessors.getPredecessorPlaces());
predecessors.add(automatonPredecessor);
mAllPredecessors = ImmutableSet.of(predecessors);
}
@Override
public Set getPredecessorPlaces() {
return mAllPredecessors;
}
@Override
public Collection> getTransitions() {
final List> result = new ArrayList<>();
for (final Transition inputTransition : mPetriNetPredecessors.getTransitions()) {
final Transition outputTransition =
getOrConstructTransition(inputTransition, mAutomatonPredecessor);
if (outputTransition != null) {
result.add(outputTransition);
}
}
return result;
}
/**
*
* @return null iff subtrahend successor is accepting which means that we do not want such a transition in our
* resulting Petri net.
*/
private Transition getOrConstructTransition(final Transition inputTransition,
final PLACE automatonPredecessor) {
Transition result =
mInputTransition2State2OutputTransition.get(inputTransition, automatonPredecessor);
if (result == null) {
final PLACE automatonSuccessor = NestedWordAutomataUtils.getSuccessorState(mSubtrahend,
automatonPredecessor, inputTransition.getSymbol());
if (mSubtrahend.isFinal(automatonSuccessor)) {
mBlockingConfigurations.addPair(inputTransition, automatonPredecessor);
return null;
}
final Set successors = new LinkedHashSet<>();
for (final PLACE petriNetSuccessor : inputTransition.getSuccessors()) {
// possibly first time that we saw this place, add
addMinuendPlace(petriNetSuccessor);
successors.add(petriNetSuccessor);
}
addSubtrahendState(automatonSuccessor);
successors.add(automatonSuccessor);
final int totalOrderId = mNumberOfConstructedTransitions;
mNumberOfConstructedTransitions++;
result = mYetConstructedResult.addTransition(inputTransition.getSymbol(), mAllPredecessors,
ImmutableSet.of(successors), totalOrderId);
mInputTransition2State2OutputTransition.put(inputTransition, automatonPredecessor, result);
mNewTransition2AutomatonPredecessorState.put(result, automatonPredecessor);
final Transition valueBefore = mNew2Old.put(result, inputTransition);
assert valueBefore == null : "Cannot add transition twice.";
}
return result;
}
}
public BoundedPetriNet getYetConstructedPetriNet() {
return mYetConstructedResult;
}
/**
* @return Mapping from new transitions (i.e., transitions of the resulting difference) to old transitions (i.e.,
* transitions of the minuend).
*/
public Map, Transition> getTransitionBacktranslation() {
return Collections.unmodifiableMap(mNew2Old);
}
@Override
public Set getAlphabet() {
return mMinuend.getAlphabet();
}
@Override
public int size() {
// we do not provide size during on-demand construction
return -1;
}
@Override
public String sizeInformation() {
return "will be constructed on-demand";
}
@Override
public IElement transformToUltimateModel(final AutomataLibraryServices services)
throws AutomataOperationCanceledException {
throw new UnsupportedOperationException();
}
@Override
public boolean isAccepting(final PLACE place) {
if (mSubtrahendStates.contains(place)) {
return false;
}
return mMinuend.isAccepting(place);
}
/**
* DifferenceSynchronizationInformation for all reachable transitions.
*/
public DifferenceSynchronizationInformation computeDifferenceSynchronizationInformation() {
return computeDifferenceSynchronizationInformation(mNew2Old.keySet(), false);
}
/**
* DifferenceSynchronizationInformation for a restricted set of transitions. This is useful if you want to restrict
* the result of an {@link Difference} operation to the set of vital transitions. TODO 2020-02-01 Matthias: Warning
* this information is not yet optimal. Assume we have a transition-state pair that is blocking but its preset is
* never reachable in the result of the Difference operation, then the entry in "blockingTransitions" is useless and
* might prevent an efficient synchronization of selfloops. However, I expect that the costs for a corresponding
* optimization are probably high and the gain is low.
*/
public DifferenceSynchronizationInformation computeDifferenceSynchronizationInformation(
final Set> transitionSubset, final boolean vitalityPreserved) {
final Set changerLetters = new HashSet<>();
final HashRelation, PLACE> selfloops = new HashRelation<>();
final HashRelation, PLACE> stateChangers = new HashRelation<>();
final HashRelation, PLACE> blockingTransitions = new HashRelation<>();
final Set> contributingTransitions = new HashSet<>();
for (final Transition transition : mNew2Old.keySet()) {
final Transition minuendTransition = mNew2Old.get(transition);
assert minuendTransition != null : "Unknown transition: " + transition;
final PLACE automatonPredecessor = mNewTransition2AutomatonPredecessorState.get(transition);
if (automatonPredecessor == null) {
// this transition is not synchronized with the automaton
if (transitionSubset.contains(transition)) {
contributingTransitions.add(minuendTransition);
}
continue;
}
final PLACE automatonSuccessor = NestedWordAutomataUtils.getSuccessorState(mSubtrahend,
automatonPredecessor, transition.getSymbol());
final boolean isSelfloop = (automatonPredecessor.equals(automatonSuccessor));
if (!transitionSubset.contains(transition)) {
if (!isSelfloop) {
blockingTransitions.addPair(minuendTransition, automatonPredecessor);
changerLetters.add(minuendTransition.getSymbol());
} else {
// do nothing
}
} else {
contributingTransitions.add(minuendTransition);
if (isSelfloop) {
selfloops.addPair(minuendTransition, automatonPredecessor);
} else {
stateChangers.addPair(minuendTransition, automatonPredecessor);
changerLetters.add(minuendTransition.getSymbol());
}
}
}
blockingTransitions.addAll(mBlockingConfigurations);
for (final Transition trans : mBlockingConfigurations.getDomain()) {
changerLetters.add(trans.getSymbol());
}
return new DifferenceSynchronizationInformation<>(changerLetters, selfloops, stateChangers,
contributingTransitions, blockingTransitions, true, vitalityPreserved);
}
private class SimpleSuccessorTransitionProviderWithUsageInformation
extends SimpleSuccessorTransitionProvider {
public SimpleSuccessorTransitionProviderWithUsageInformation(
final Collection> transitions) {
super(transitions);
}
@Override
public Collection> getTransitions() {
final Collection> transitions = new ArrayList<>();
for (final Transition inputTransition : super.getTransitions()) {
final Transition resultTransition = getOrConstructTransitionCopy(inputTransition);
transitions.add(resultTransition);
}
return transitions;
}
private Transition
getOrConstructTransitionCopy(final Transition inputTransition) {
Transition result = mInputTransition2State2OutputTransition.get(inputTransition, null);
if (result == null) {
final Set successors = new LinkedHashSet<>();
for (final PLACE petriNetSuccessor : inputTransition.getSuccessors()) {
// possibly first time that we saw this place, add
addMinuendPlace(petriNetSuccessor);
successors.add(petriNetSuccessor);
}
final int totalOrderId = mNumberOfConstructedTransitions;
mNumberOfConstructedTransitions++;
result = mYetConstructedResult.addTransition(inputTransition.getSymbol(),
inputTransition.getPredecessors(), ImmutableSet.of(successors), totalOrderId);
mInputTransition2State2OutputTransition.put(inputTransition, null, result);
final Transition valueBefore = mNew2Old.put(result, inputTransition);
assert valueBefore == null : "Cannot add transition twice.";
}
return result;
}
}
}