/*
* Copyright (C) 2021 Dominik Klumpp (klumpp@informatik.uni-freiburg.de)
* Copyright (C) 2021 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.partialorder;
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
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.VpAlphabet;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.transitions.OutgoingCallTransition;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.transitions.OutgoingInternalTransition;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.transitions.OutgoingReturnTransition;
import de.uni_freiburg.informatik.ultimate.automata.partialorder.independence.IIndependenceRelation;
import de.uni_freiburg.informatik.ultimate.automata.partialorder.independence.IIndependenceRelation.Dependence;
import de.uni_freiburg.informatik.ultimate.automata.statefactory.IStateFactory;
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.UnifyHash;
/**
* Implementation of the Sleep Set Reduction with new states. The reduction automaton partially unfolds and unrolls the
* input automaton, in order to guarantee a reduction that is minimal (in terms of the accepted language).
*
* @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de)
*
* @param
* The type of letters of the input automaton.
* @param
* The type of states of the input automaton.
* @param
* The type of states of the reduced automaton.
*/
public class MinimalSleepSetReduction implements INwaOutgoingLetterAndTransitionProvider {
private final INwaOutgoingLetterAndTransitionProvider mOperand;
private final ISleepSetStateFactory mStateFactory;
private final IDfsOrder mOrder;
private final IIndependenceRelation mIndependence;
private final R mInitial;
// Memory footprint optimization: Unify different but equal sleep sets (see #getSleepSet below).
// ---------------------------------------------------------------------------------------------
// This is useful because the number of states of the reduced automaton often far exceeds the number of different
// sleep sets. Hence, without this optimization, we create millions of sleep sets when only a few thousand of them
// are different from each other. Unification thus allows for significant memory savings.
//
// At the same time, we do not expect this unification to be particularly expensive in runtime, since
// (1) it only requires a newly created set to be compared to (hopefully few) other sets with the same hash code,
// (2) such comparisons likely take place anyway at other points in the code (e.g. to unify sleep set states), and
// (3) the unification also may speed up future comparisons, because in most cases sleep sets are either already
// reference-equal, or they have different hash codes (which is used in ImmutableSet::equals).
//
private final UnifyHash> mSleepSetUnifier = new UnifyHash<>();
/**
* Create a new reduction automaton.
*
* @param operand
* The input automaton
* @param stateFactory
* A state factory to create the reduction automaton's states
* @param independenceRelation
* The independence relation up to which a reduction is computed. The input automaton must be closed up
* to this independence relation.
* @param order
* The preference order for the reduction
*/
public MinimalSleepSetReduction(final INwaOutgoingLetterAndTransitionProvider operand,
final ISleepSetStateFactory stateFactory, final IIndependenceRelation independenceRelation,
final IDfsOrder order) {
assert NestedWordAutomataUtils.isFiniteAutomaton(operand) : "Sleep sets support only finite automata";
mOperand = operand;
mStateFactory = stateFactory;
mOrder = order;
mIndependence = independenceRelation;
final var initial =
DataStructureUtils.getOnly(operand.getInitialStates(), "There must only be one initial state");
if (initial.isPresent()) {
mInitial = mStateFactory.createSleepSetState(initial.get(), getSleepSet(ImmutableSet.empty()));
} else {
mInitial = null;
}
}
@Override
public IStateFactory getStateFactory() {
return mStateFactory;
}
@Override
public VpAlphabet getVpAlphabet() {
return mOperand.getVpAlphabet();
}
@Override
public R getEmptyStackState() {
return mStateFactory.createEmptyStackState();
}
@Override
public Iterable getInitialStates() {
return mInitial == null ? Collections.emptySet() : Set.of(mInitial);
}
@Override
public boolean isInitial(final R state) {
return Objects.equals(mInitial, state);
}
@Override
public boolean isFinal(final R state) {
return mOperand.isFinal(mStateFactory.getOriginalState(state));
}
@Override
public int size() {
return -1;
}
@Override
public String sizeInformation() {
return "currently " + size() + " states, but on-demand construction may add more states";
}
@Override
public Set lettersInternal(final R state) {
return DataStructureUtils.difference(mOperand.lettersInternal(mStateFactory.getOriginalState(state)),
mStateFactory.getSleepSet(state));
}
@Override
public Iterable> internalSuccessors(final R state, final L letter) {
final ImmutableSet currentSleepSet = mStateFactory.getSleepSet(state);
if (currentSleepSet.contains(letter)) {
return Collections.emptySet();
}
final S currentState = mStateFactory.getOriginalState(state);
final var currentTransitionOpt = DataStructureUtils.getOnly(mOperand.internalSuccessors(currentState, letter),
"Automaton must be deterministic");
if (currentTransitionOpt.isEmpty()) {
return Collections.emptySet();
}
final Comparator comp = mOrder.getOrder(state);
final Stream explored = mOperand.lettersInternal(currentState).stream()
.filter(x -> comp.compare(x, letter) < 0 && !currentSleepSet.contains(x));
// TODO factor out sleep set successor computation
final ImmutableSet succSleepSet =
getSleepSet(ImmutableSet.of((Set) Set.of(Stream.concat(currentSleepSet.stream(), explored)
.filter(l -> mIndependence.isIndependent(currentState, letter, l) == Dependence.INDEPENDENT)
.toArray())));
final R succSleepSetState =
mStateFactory.createSleepSetState(currentTransitionOpt.get().getSucc(), succSleepSet);
return Set.of(new OutgoingInternalTransition<>(letter, succSleepSetState));
}
private ImmutableSet getSleepSet(final ImmutableSet set) {
final int hash = set.hashCode();
for (final var candidate : mSleepSetUnifier.iterateHashCode(hash)) {
if (candidate.equals(set)) {
return candidate;
}
}
mSleepSetUnifier.put(hash, set);
return set;
}
@Override
public Iterable> callSuccessors(final R state, final L letter) {
return Collections.emptySet();
}
@Override
public Iterable> returnSuccessors(final R state, final R hier, final L letter) {
return Collections.emptySet();
}
}