/*
* Copyright (C) 2011-2015 Julian Jarecki (jareckij@informatik.uni-freiburg.de)
* Copyright (C) 2011-2015 Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* Copyright (C) 2009-2015 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.unfolding;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.IPetriNetTransitionProvider;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.Marking;
import de.uni_freiburg.informatik.ultimate.automata.petrinet.PetriNetNot1SafeException;
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.Transition;
import de.uni_freiburg.informatik.ultimate.util.datastructures.TreePriorityQueue;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.HashRelation;
/**
* Implementation of a possible extension.
*
* @author Julian Jarecki (jareckij@informatik.uni-freiburg.de)
* @author Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* @param
* symbol type
* @param
* place content type
*/
public class PossibleExtensions implements IPossibleExtensions {
/**
* Use the optimization that is outlined in observation B07 in the following issue.
* https://github.com/ultimate-pa/ultimate/issues/448
*/
private static final boolean USE_FORWARD_CHECKING = false;
private static final boolean USE_PQ = true;
private final static boolean LAZY_SUCCESSOR_COMPUTATION = true;
private final Queue> mPe;
private final Map, Event> mMarkingEventMap = new HashMap<>();
private int mMaximalSize;
private final boolean mUseFirstbornCutoffCheck;
private final boolean mUseB32Optimization;
private int mNumberOfGeneratedExtensions = 0;
/**
* If {@link Event} is known to be cut-off event we can move it immediately to front because it will not create
* descendants. This optimization keeps the queue smaller. TODO 2019-10-16 Matthias: Mehdi found out that this
* ArrayDeque is currently unused because the cut-off detection is only done later. We could to an additional
* cut-off check earlier but we have doubts that this will pay off.
*/
private final Comparator> mOrder;
private final ArrayDeque> mFastpathCutoffEventList;
private final BranchingProcess mBranchingProcess;
/**
* A candidate is useful if it lead to at least one new possible extension.
*/
private int mUsefulExtensionCandidates;
private int mUselessExtensionCandidates;
public PossibleExtensions(final BranchingProcess branchingProcess,
final ConfigurationOrder order, final boolean useFirstbornCutoffCheck,
final boolean useB32Optimization) {
mUseFirstbornCutoffCheck = useFirstbornCutoffCheck;
mBranchingProcess = branchingProcess;
if (USE_PQ) {
mPe = new PriorityQueue<>(order);
} else {
if (!order.isTotal()) {
throw new UnsupportedOperationException(TreePriorityQueue.class.getSimpleName()
+ " can only be used in combination with total orders.");
}
mPe = new TreePriorityQueue<>(order);
}
mFastpathCutoffEventList = new ArrayDeque<>();
mOrder = order;
mMarkingEventMap.put(mBranchingProcess.getDummyRoot().getMark(), mBranchingProcess.getDummyRoot());
mUseB32Optimization = useB32Optimization;
}
@Override
public Event remove() {
if (mFastpathCutoffEventList.isEmpty()) {
return mPe.remove();
}
return mFastpathCutoffEventList.removeFirst();
}
@Override
public void update(final Event event) throws PetriNetNot1SafeException {
final Collection> candidates = computeCandidates(event);
for (final Candidate candidate : candidates) {
if (candidate.getInstantiated().isEmpty()) {
throw new AssertionError("at least one place has to be instantiated");
}
final int possibleExtensionsBefore = size();
if (USE_FORWARD_CHECKING) {
evolveCandidateWithForwardChecking(candidate);
} else {
evolveCandidate(candidate);
}
if (size() > possibleExtensionsBefore) {
mUsefulExtensionCandidates++;
} else {
mUselessExtensionCandidates++;
}
}
}
private boolean firstbornCutoffCheck(final Event newEvent) {
final Event eventWithSameMarking = mMarkingEventMap.get(newEvent.getMark());
if (eventWithSameMarking == null) {
return false;
}
if (mOrder.compare(newEvent, eventWithSameMarking) > 0) {
newEvent.setCompanion(eventWithSameMarking);
return true;
}
final boolean eventWithSameMarkingWasInTheMainQueu = mPe.remove(eventWithSameMarking);
assert eventWithSameMarkingWasInTheMainQueu;
mFastpathCutoffEventList.add(eventWithSameMarking);
eventWithSameMarking.setCompanion(newEvent);
return false;
}
/**
* Evolves a {@code Candidate} for a new possible Event in all possible ways and, as a side-effect, adds valid
* extensions (ones whose predecessors are a co-set) to he possible extension set.
*/
private void addFullyInstantiatedCandidate(final Candidate cand) throws PetriNetNot1SafeException {
for (final Transition trans : cand.getTransition().getTransitions()) {
mNumberOfGeneratedExtensions++;
final Event newEvent =
new Event<>(cand.getInstantiated(), trans, mBranchingProcess, mNumberOfGeneratedExtensions);
if (mUseFirstbornCutoffCheck) {
if (firstbornCutoffCheck(newEvent)) {
mFastpathCutoffEventList.add(newEvent);
} else {
mMarkingEventMap.put(newEvent.getMark(), newEvent);
final boolean somethingWasAdded = mPe.add(newEvent);
mMaximalSize = Integer.max(mMaximalSize, mPe.size());
if (!somethingWasAdded) {
throw new AssertionError("Event was already in queue.");
}
}
} else {
final boolean somethingWasAdded = mPe.add(newEvent);
mMaximalSize = Integer.max(mMaximalSize, mPe.size());
if (!somethingWasAdded) {
throw new AssertionError("Event was already in queue.");
}
}
}
}
private void evolveCandidate(final Candidate cand) throws PetriNetNot1SafeException {
if (cand.isFullyInstantiated()) {
addFullyInstantiatedCandidate(cand);
return;
}
final PLACE nextUninstantiated = cand.getNextUninstantiatedPlace();
final ICoRelation coRelation = mBranchingProcess.getCoRelation();
final List> yetInstantiated = cand.getInstantiatedButNotInitially();
final Set> inCoRelationWithAllInstantiated =
cand.getPossibleInstantiations(nextUninstantiated).stream()
.filter(x -> coRelation.isCoset(yetInstantiated, x)).collect(Collectors.toSet());
for (final Condition c : inCoRelationWithAllInstantiated) {
assert cand.getTransition().getPredecessorPlaces().contains(c.getPlace());
// equality intended here
assert c.getPlace().equals(nextUninstantiated);
assert !cand.getInstantiated().contains(c);
cand.instantiateNext(c);
evolveCandidate(cand);
cand.undoOneInstantiation();
}
}
private void evolveCandidateWithForwardChecking(final Candidate cand)
throws PetriNetNot1SafeException {
if (cand.isFullyInstantiated()) {
addFullyInstantiatedCandidate(cand);
return;
}
final PLACE nextUninstantiated = cand.getNextUninstantiatedPlace();
final ICoRelation coRelation = mBranchingProcess.getCoRelation();
final Map>> possibleInstantiationsMap = cand.getPossibleInstantiationsMap();
final Set> possibleInstantiations = cand.getPossibleInstantiations(nextUninstantiated);
possibleInstantiationsMap.remove(nextUninstantiated);
for (final Condition condition : possibleInstantiations) {
final Map>> newPossibleInstantiations = new HashMap<>();
boolean uselessCandidate = false;
for (final PLACE p : possibleInstantiationsMap.keySet()) {
final Set> possibleInstantiationsforP = possibleInstantiationsMap.get(p)
.stream().filter(x -> coRelation.isInCoRelation(condition, x)).collect(Collectors.toSet());
if (possibleInstantiationsforP.isEmpty()) {
uselessCandidate = true;
break;
}
newPossibleInstantiations.put(p, possibleInstantiationsforP);
}
if (!uselessCandidate) {
final LinkedList> newInstantiated = new LinkedList<>(cand.getInstantiated());
newInstantiated.add(condition);
final LinkedList newNotInstantiated = new LinkedList<>(cand.getNotInstantiated());
newNotInstantiated.removeLast();
evolveCandidateWithForwardChecking(new Candidate<>(cand.getTransition(), newNotInstantiated,
newInstantiated, newPossibleInstantiations));
}
}
}
/**
* @return All {@code Candidate}s for possible extensions that are successors of the {@code Event}.
*/
private Collection> computeCandidates(final Event event) {
if (event.getSuccessorConditions().isEmpty()) {
return Collections.emptySet();
}
if (LAZY_SUCCESSOR_COMPUTATION) {
final Set> newConditions = event.getSuccessorConditions();
final ICoRelation coRelation = mBranchingProcess.getCoRelation();
final Set> coRelatedConditions;
final HashRelation> place2coRelatedConditions = new HashRelation<>();
if (mUseB32Optimization) {
coRelatedConditions = coRelation.computeNonCutoffCoRelatatedConditions(newConditions.iterator().next());
for (final Condition c : coRelatedConditions) {
place2coRelatedConditions.addPair(c.getPlace(), c);
}
} else {
coRelatedConditions = coRelation.computeCoRelatatedConditions(newConditions.iterator().next());
for (final Condition c : coRelatedConditions) {
if (!c.getPredecessorEvent().isCutoffEvent()) {
place2coRelatedConditions.addPair(c.getPlace(), c);
}
}
}
final Collection> successorTransitionProviders;
final Set placesOfNewConditions = new HashSet<>();
for (final Condition c : newConditions) {
placesOfNewConditions.add(c.getPlace());
}
successorTransitionProviders = mBranchingProcess.getNet()
.getSuccessorTransitionProviders(placesOfNewConditions, place2coRelatedConditions.getDomain());
final List> candidates = successorTransitionProviders.stream()
.map(x -> new Candidate<>(x, newConditions, place2coRelatedConditions))
.collect(Collectors.toList());
return candidates;
}
if (!(mBranchingProcess.getNet() instanceof IPetriNetTransitionProvider)) {
throw new AssertionError("non-lazy computation only available for fully constructed nets");
}
final IPetriNetTransitionProvider fullPetriNet =
(IPetriNetTransitionProvider) mBranchingProcess.getNet();
final Set> transitions = new HashSet<>();
for (final Condition cond : event.getSuccessorConditions()) {
for (final Transition t : fullPetriNet.getSuccessors(cond.getPlace())) {
transitions.add(t);
}
}
final List> candidates = new ArrayList<>();
for (final Transition transition : transitions) {
final Candidate candidate =
new Candidate<>(new SimpleSuccessorTransitionProvider<>(Collections.singleton(transition)),
event.getSuccessorConditions(), null);
candidates.add(candidate);
}
return candidates;
}
@Override
public boolean isEmpy() {
return mPe.isEmpty() && mFastpathCutoffEventList.isEmpty();
}
@Override
public int size() {
return mPe.size() + mFastpathCutoffEventList.size();
}
@Override
public int getUsefulExtensionCandidates() {
return mUsefulExtensionCandidates;
}
@Override
public int getUselessExtensionCandidates() {
return mUselessExtensionCandidates;
}
@Override
public int getMaximalSize() {
return mMaximalSize;
}
}