Commit 30809215 authored by Salomon Sickert's avatar Salomon Sickert

Improve performance of BDD-based equivalence class and retain representative.

parent 01321303
......@@ -35,6 +35,10 @@ API:
* OmegaAcceptanceCast enables casting and conversion of different types of
omega-acceptance.
* EquivalenceClass always maintains the representative. This is made
possible by major performance improvements in the EquivalenceClass
implementation.
Bugfixes:
* Fixed several bugs affecting the LD(G)BA, D(G)RA, and DPA constructions.
......
......@@ -3610,7 +3610,7 @@
{
"formula": "G(a | ((!a) U (Xa)))",
"properties": {
"size": 7,
"size": 8,
"initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1,
......
......@@ -3951,7 +3951,7 @@
{
"formula": "G(a | ((!a) U (Xa)))",
"properties": {
"size": 7,
"size": 8,
"initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1,
......
......@@ -3951,7 +3951,7 @@
{
"formula": "G(a | ((!a) U (Xa)))",
"properties": {
"size": 7,
"size": 8,
"initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1,
......
......@@ -3951,7 +3951,7 @@
{
"formula": "G(a | ((!a) U (Xa)))",
"properties": {
"size": 7,
"size": 8,
"initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1,
......
......@@ -3951,7 +3951,7 @@
{
"formula": "G(a | ((!a) U (Xa)))",
"properties": {
"size": 7,
"size": 8,
"initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1,
......
......@@ -60,6 +60,11 @@ public interface EdgeTreeAutomatonMixin<S, A extends OmegaAcceptance> extends Au
return edgeTree(state).get(valuation);
}
@Override
default Set<S> successors(S state) {
return edgeTree(state).values(Edge::successor);
}
@Override
default List<PreferredEdgeAccess> preferredEdgeAccess() {
return ACCESS_MODES;
......
......@@ -24,6 +24,7 @@ import static owl.ltl.SyntacticFragment.SINGLE_STEP;
import com.google.common.primitives.ImmutableIntArray;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
......@@ -41,10 +42,9 @@ import owl.ltl.Conjunction;
import owl.ltl.Disjunction;
import owl.ltl.Formula;
import owl.ltl.GOperator;
import owl.ltl.PropositionalFormula;
import owl.ltl.Literal;
import owl.ltl.SyntacticFragment;
import owl.ltl.SyntacticFragments;
import owl.ltl.UnaryModalOperator;
import owl.ltl.XOperator;
import owl.ltl.rewriter.LiteralMapper;
import owl.ltl.rewriter.PullUpXVisitor;
......@@ -118,7 +118,7 @@ public final class DecomposedDPA {
private static boolean isSingleStep(Formula formula) {
if (formula instanceof Conjunction) {
return ((Conjunction) formula).children.stream().allMatch(DecomposedDPA::isSingleStep);
return formula.children().stream().allMatch(DecomposedDPA::isSingleStep);
}
return formula instanceof GOperator && SINGLE_STEP.contains(((GOperator) formula).operand);
......@@ -199,7 +199,8 @@ public final class DecomposedDPA {
return new LabelledTree.Leaf<>(newReference);
}
private List<LabelledTree<Tag, Reference>> createLeaves(PropositionalFormula formula) {
private List<LabelledTree<Tag, Reference>> createLeaves(
Formula.NaryPropositionalFormula formula) {
// Partition elements.
var safety = new Clusters();
var safetySingleStep = new HashMap<Integer, Clusters>();
......@@ -208,7 +209,7 @@ public final class DecomposedDPA {
var weakOrBuchiOrCoBuchi = new TreeSet<Formula>();
var parity = new TreeSet<Formula>();
for (Formula x : formula.children) {
for (Formula x : formula.children()) {
switch (annotatedTree.get(x)) {
case SAFETY:
PullUpXVisitor.XFormula rewrittenX = x.accept(PullUpXVisitor.INSTANCE);
......@@ -240,7 +241,7 @@ public final class DecomposedDPA {
// Process elements.
List<LabelledTree<Tag, Reference>> children = new ArrayList<>();
Function<Iterable<Formula>, Formula> merger = formula instanceof Conjunction
Function<Collection<Formula>, Formula> merger = formula instanceof Conjunction
? Conjunction::of
: Disjunction::of;
......@@ -274,12 +275,17 @@ public final class DecomposedDPA {
}
@Override
protected LabelledTree<Tag, Reference> visit(Formula.TemporalOperator formula) {
protected LabelledTree<Tag, Reference> visit(Formula.Temporal formula) {
return createLeaf(formula);
}
@Override
public LabelledTree<Tag, Reference> visit(Literal literal) {
return createLeaf(literal);
}
private boolean keepTreeStructureBiconditional(Formula formula) {
if (formula instanceof PropositionalFormula) {
if (formula instanceof Conjunction || formula instanceof Disjunction) {
if (formula.children().stream()
.filter(x -> annotatedTree.get(x) == Acceptance.PARITY).count() > 1) {
return false;
......@@ -290,11 +296,10 @@ public final class DecomposedDPA {
return formula.accept(new PropositionalVisitor<Boolean>() {
@Override
protected Boolean visit(Formula.TemporalOperator formula) {
protected Boolean visit(Formula.Temporal formula) {
return (SyntacticFragments.isAlmostAll(formula)
|| SyntacticFragments.isInfinitelyOften(formula))
&& SyntacticFragment.SINGLE_STEP
.contains(((UnaryModalOperator) ((UnaryModalOperator) formula).operand).operand);
&& SyntacticFragment.SINGLE_STEP.contains(formula.children().get(0).children().get(0));
}
@Override
......@@ -304,12 +309,12 @@ public final class DecomposedDPA {
@Override
public Boolean visit(Conjunction conjunction) {
return conjunction.children.stream().allMatch(this::apply);
return conjunction.children().stream().allMatch(this::apply);
}
@Override
public Boolean visit(Disjunction disjunction) {
return disjunction.children.stream().allMatch(this::apply);
return disjunction.children().stream().allMatch(this::apply);
}
});
}
......@@ -355,7 +360,7 @@ public final class DecomposedDPA {
static class Clusters {
private static final Predicate<Formula> INTERESTING_OPERATOR =
o -> o instanceof Formula.ModalOperator && !(o instanceof XOperator);
o -> o instanceof Formula.Temporal && !(o instanceof XOperator);
List<Set<Formula>> clusterList = new ArrayList<>();
......@@ -364,9 +369,11 @@ public final class DecomposedDPA {
cluster.add(formula);
clusterList.removeIf(x -> {
Set<Formula.TemporalOperator> modalOperators1 = formula.subformulas(INTERESTING_OPERATOR);
Set<Formula.TemporalOperator> modalOperators2 = x.stream()
.flatMap(y -> y.subformulas(INTERESTING_OPERATOR).stream()).collect(Collectors.toSet());
Set<Formula.Temporal> modalOperators1 = formula.subformulas(
INTERESTING_OPERATOR, Formula.Temporal.class::cast);
Set<Formula.Temporal> modalOperators2 = x.stream()
.flatMap(y -> y.subformulas(INTERESTING_OPERATOR, Formula.Temporal.class::cast).stream())
.collect(Collectors.toSet());
if (!Collections.disjoint(modalOperators1, modalOperators2)) {
cluster.addAll(x);
......@@ -384,7 +391,7 @@ public final class DecomposedDPA {
static final Annotator INSTANCE = new Annotator();
@Override
protected Map<Formula, Acceptance> visit(Formula.TemporalOperator formula) {
protected Map<Formula, Acceptance> visit(Formula.Temporal formula) {
if (SyntacticFragments.isSafety(formula)) {
return Map.of(formula, Acceptance.SAFETY);
}
......@@ -440,11 +447,16 @@ public final class DecomposedDPA {
return visitPropositional(disjunction);
}
private Map<Formula, Acceptance> visitPropositional(PropositionalFormula formula) {
@Override
public Map<Formula, Acceptance> visit(Literal literal) {
return Map.of(literal, Acceptance.WEAK);
}
private Map<Formula, Acceptance> visitPropositional(Formula.NaryPropositionalFormula formula) {
Acceptance acceptance = Acceptance.BOTTOM;
Map<Formula, Acceptance> acceptanceMap = new HashMap<>();
for (Formula child : formula.children) {
for (Formula child : formula.children()) {
Map<Formula, Acceptance> childDecisions = child.accept(this);
acceptanceMap.putAll(childDecisions);
acceptance = acceptance.lub(acceptanceMap.get(child));
......
......@@ -168,7 +168,7 @@ public final class DeterministicAutomaton<S, T> {
if (SyntacticFragments.isGCoSafety(unwrapped)) {
return new DeterministicAutomaton<>(
GenericConstructions.delay(
DeterministicConstructionsPortfolio.gCoSafety(ENV, LabelledFormula.of(unwrapped, formula.variables())),
DeterministicConstructionsPortfolio.gCoSafety(ENV, LabelledFormula.of(unwrapped, formula.atomicPropositions())),
xCount),
BUCHI, BuchiAcceptance.class,
x -> false,
......
......@@ -22,8 +22,10 @@ package owl.collections;
import com.google.common.collect.Maps;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
......@@ -54,7 +56,15 @@ public abstract class ValuationTree<E> {
public abstract Set<E> get(BitSet valuation);
public abstract Set<E> values();
public final Set<E> values() {
return values(Function.identity());
}
public final <T> Set<T> values(Function<E, T> mapper) {
Set<T> values = new HashSet<>();
memoizedValues(values, Collections.newSetFromMap(new IdentityHashMap<>()), mapper);
return values;
}
public final Map<E, ValuationSet> inverse(ValuationSetFactory factory) {
return memoizedInverse(factory, new HashMap<>());
......@@ -73,6 +83,9 @@ public abstract class ValuationTree<E> {
ValuationSetFactory factory,
Map<ValuationTree<E>, Map<E, ValuationSet>> memoizedCalls);
protected abstract <T> void memoizedValues(
Set<T> values, Set<ValuationTree<E>> seenNodes, Function<E, T> mapper);
public static final class Leaf<E> extends ValuationTree<E> {
private static final ValuationTree<Object> EMPTY = new Leaf<>(Set.of());
......@@ -89,8 +102,11 @@ public abstract class ValuationTree<E> {
}
@Override
public Set<E> values() {
return new HashSet<>(value);
protected <T> void memoizedValues(Set<T> values,
Set<ValuationTree<E>> seenNodes, Function<E, T> mapper) {
for (E x : value) {
values.add(mapper.apply(x));
}
}
@Override
......@@ -153,10 +169,14 @@ public abstract class ValuationTree<E> {
}
@Override
public Set<E> values() {
Set<E> values = trueChild.values();
values.addAll(falseChild.values());
return values;
protected <T> void memoizedValues(Set<T> values, Set<ValuationTree<E>> seenNodes,
Function<E, T> mapper) {
if (!seenNodes.add(this)) {
return;
}
trueChild.memoizedValues(values, seenNodes, mapper);
falseChild.memoizedValues(values, seenNodes, mapper);
}
// Perfect for fork/join-parallesism
......
......@@ -19,14 +19,7 @@
package owl.factories;
import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import owl.collections.ValuationTree;
import owl.ltl.BooleanConstant;
import owl.ltl.EquivalenceClass;
import owl.ltl.Formula;
......@@ -35,62 +28,5 @@ import owl.ltl.Formula;
public interface EquivalenceClassFactory {
List<String> variables();
EquivalenceClass of(Formula formula);
default EquivalenceClass getFalse() {
return of(BooleanConstant.FALSE);
}
default EquivalenceClass getTrue() {
return of(BooleanConstant.TRUE);
}
/**
* Collects all literals used in the bdd and stores the corresponding atomic propositions in
* the BitSet.
*/
BitSet atomicPropositions(EquivalenceClass clazz, boolean includeNested);
/**
* Compute the support of the EquivalenceClass.
*
* @return All modal operators this equivalence class depends on.
*/
Set<Formula.ModalOperator> modalOperators(EquivalenceClass clazz);
boolean implies(EquivalenceClass clazz, EquivalenceClass other);
default EquivalenceClass conjunction(Collection<EquivalenceClass> classes) {
return conjunction(classes.iterator());
}
EquivalenceClass conjunction(Iterator<EquivalenceClass> classes);
default EquivalenceClass disjunction(Collection<EquivalenceClass> classes) {
return disjunction(classes.iterator());
}
EquivalenceClass disjunction(Iterator<EquivalenceClass> classes);
EquivalenceClass substitute(EquivalenceClass clazz,
Function<? super Formula.ModalOperator, ? extends Formula> substitution);
EquivalenceClass temporalStep(EquivalenceClass clazz, BitSet valuation);
EquivalenceClass temporalStepUnfold(EquivalenceClass clazz, BitSet valuation);
EquivalenceClass unfold(EquivalenceClass clazz);
EquivalenceClass unfoldTemporalStep(EquivalenceClass clazz, BitSet valuation);
String toString(EquivalenceClass clazz);
<T> ValuationTree<T> temporalStepTree(EquivalenceClass clazz,
Function<EquivalenceClass, Set<T>> mapper);
double trueness(EquivalenceClass clazz);
}
......@@ -26,14 +26,9 @@ public interface FactorySupplier {
EquivalenceClassFactory getEquivalenceClassFactory(List<String> alphabet);
EquivalenceClassFactory getEquivalenceClassFactory(List<String> alphabet,
boolean keepRepresentatives);
Factories getFactories(List<String> alphabet);
default Factories getFactories(List<String> alphabet, boolean keepRepresentatives) {
default Factories getFactories(List<String> alphabet) {
return new Factories(
getEquivalenceClassFactory(alphabet, keepRepresentatives),
getEquivalenceClassFactory(alphabet),
getValuationSetFactory(alphabet));
}
}
......@@ -19,28 +19,21 @@
package owl.factories.jbdd;
import com.google.common.collect.Lists;
import de.tum.in.jbdd.Bdd;
import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import owl.collections.ValuationTree;
import owl.factories.EquivalenceClassFactory;
......@@ -50,498 +43,362 @@ import owl.ltl.Disjunction;
import owl.ltl.EquivalenceClass;
import owl.ltl.Formula;
import owl.ltl.Literal;
import owl.ltl.XOperator;
import owl.ltl.visitors.PrintVisitor;
import owl.ltl.visitors.PropositionalIntVisitor;
final class EquivalenceFactory extends GcManagedFactory<EquivalenceFactory.BddEquivalenceClass>
implements EquivalenceClassFactory {
private final List<String> alphabet;
private final boolean keepRepresentatives;
private final BddVisitor visitor;
private final List<String> atomicPropositions;
private final BddEquivalenceClass falseClass;
private final BddEquivalenceClass trueClass;
private final BddVisitor visitor;
private Formula.TemporalOperator[] reverseMapping;
private final Object2IntMap<Formula.TemporalOperator> mapping;
// Compose maps.
private int[] temporalStepSubstitution;
private int[] unfoldSubstitution;
// Protect objects from the GC.
private EquivalenceClass[] temporalStepSubstitutes;
private EquivalenceClass[] unfoldSubstitutes;
private Formula.Temporal[] reverseMapping;
private final Object2IntMap<Formula.Temporal> mapping;
private boolean registerActive = false;
@Nullable
private Function<EquivalenceClass, Set<?>> temporalStepTreeCachedMapper;
@Nullable
private IdentityHashMap<EquivalenceClass, ValuationTree<?>> temporalStepTreeCache;
public EquivalenceFactory(Bdd bdd, List<String> alphabet, boolean keepRepresentatives) {
public EquivalenceFactory(Bdd bdd, List<String> atomicPropositions) {
super(bdd);
this.alphabet = List.copyOf(alphabet);
this.keepRepresentatives = keepRepresentatives;
this.atomicPropositions = List.copyOf(atomicPropositions);
int alphabetSize = this.alphabet.size();
int alphabetSize = this.atomicPropositions.size();
mapping = new Object2IntOpenHashMap<>();
mapping.defaultReturnValue(-1);
reverseMapping = new Formula.TemporalOperator[alphabetSize];
reverseMapping = new Formula.Temporal[alphabetSize];
visitor = new BddVisitor();
unfoldSubstitution = new int[alphabetSize];
unfoldSubstitutes = new EquivalenceClass[alphabetSize];
temporalStepSubstitution = new int[alphabetSize];
temporalStepSubstitutes = new EquivalenceClass[alphabetSize];
// Register literals.
for (int i = 0; i < alphabetSize; i++) {
Literal literal = Literal.of(i);
int node = this.bdd.createVariable();
assert this.bdd.variable(node) == i;
mapping.put(literal, i);
reverseMapping[i] = literal;
}
// Literals are not unfolded.
Arrays.fill(unfoldSubstitution, -1);
trueClass = new BddEquivalenceClass(this, this.bdd.trueNode(), BooleanConstant.TRUE);
falseClass = new BddEquivalenceClass(this, this.bdd.falseNode(), BooleanConstant.FALSE);
}
@Override
public List<String> variables() {
return alphabet;
return atomicPropositions;
}
@Override
public EquivalenceClass of(Formula formula) {
return create(formula, toBdd(formula));
return of(formula, true);
}
@Override
public EquivalenceClass getFalse() {
return falseClass;
}
@Override
public EquivalenceClass getTrue() {
return trueClass;
}
private BddEquivalenceClass of(Formula formula, boolean scanForUnknown) {
if (scanForUnknown) {
// Scan for unknown modal operators.
var newPropositions = formula.subformulas(Formula.Temporal.class)
.stream().filter(x -> !mapping.containsKey(x)).sorted().collect(Collectors.toList());
if (!newPropositions.isEmpty()) {
// Create variables.
int literalOffset = atomicPropositions.size();
int newSize = mapping.size() + newPropositions.size();
reverseMapping = Arrays.copyOf(reverseMapping, newSize);
@Override
public BitSet atomicPropositions(EquivalenceClass clazz, boolean includeNested) {
if (!includeNested) {
return bdd.support(getNode(clazz), alphabet.size());
for (Formula.Temporal proposition : newPropositions) {
int variable = bdd.variable(bdd.createVariable());
mapping.put(proposition, variable);
reverseMapping[variable - literalOffset] = proposition;
}
}
}
BitSet atomicPropositions = bdd.support(getNode(clazz));
int i = atomicPropositions.nextSetBit(alphabet.size());
for (; i >= 0; i = atomicPropositions.nextSetBit(i + 1)) {
atomicPropositions.or(reverseMapping[i].atomicPropositions(true));
return of(formula, bdd.dereference(formula.accept(visitor)));
}
if (atomicPropositions.length() >= alphabet.size()) {
atomicPropositions.clear(alphabet.size(), atomicPropositions.length());
private BddEquivalenceClass of(@Nullable Formula representative, int node) {
if (node == bdd.trueNode()) {
return trueClass;
}
return atomicPropositions;
if (node == bdd.falseNode()) {
return falseClass;
}
@Override
public Set<Formula.ModalOperator> modalOperators(EquivalenceClass clazz) {
BitSet support = bdd.support(getNode(clazz));
support.clear(0, alphabet.size());
var clazz = canonicalize(new BddEquivalenceClass(this, node, representative));
return new AbstractSet<>() {
@Override
public boolean contains(Object o) {
int i = mapping.getInt(o);
return i != -1 && support.get(i);
if (clazz.representative == null) {
clazz.representative = representative;
}