Commit 30809215 authored by Salomon Sickert's avatar Salomon Sickert

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

parent 01321303
...@@ -34,6 +34,10 @@ API: ...@@ -34,6 +34,10 @@ API:
* OmegaAcceptanceCast enables casting and conversion of different types of * OmegaAcceptanceCast enables casting and conversion of different types of
omega-acceptance. omega-acceptance.
* EquivalenceClass always maintains the representative. This is made
possible by major performance improvements in the EquivalenceClass
implementation.
Bugfixes: Bugfixes:
......
...@@ -3610,7 +3610,7 @@ ...@@ -3610,7 +3610,7 @@
{ {
"formula": "G(a | ((!a) U (Xa)))", "formula": "G(a | ((!a) U (Xa)))",
"properties": { "properties": {
"size": 7, "size": 8,
"initialStatesSize": 1, "initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance", "acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1, "acceptanceSets": 1,
......
...@@ -3951,7 +3951,7 @@ ...@@ -3951,7 +3951,7 @@
{ {
"formula": "G(a | ((!a) U (Xa)))", "formula": "G(a | ((!a) U (Xa)))",
"properties": { "properties": {
"size": 7, "size": 8,
"initialStatesSize": 1, "initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance", "acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1, "acceptanceSets": 1,
......
...@@ -3951,7 +3951,7 @@ ...@@ -3951,7 +3951,7 @@
{ {
"formula": "G(a | ((!a) U (Xa)))", "formula": "G(a | ((!a) U (Xa)))",
"properties": { "properties": {
"size": 7, "size": 8,
"initialStatesSize": 1, "initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance", "acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1, "acceptanceSets": 1,
......
...@@ -3951,7 +3951,7 @@ ...@@ -3951,7 +3951,7 @@
{ {
"formula": "G(a | ((!a) U (Xa)))", "formula": "G(a | ((!a) U (Xa)))",
"properties": { "properties": {
"size": 7, "size": 8,
"initialStatesSize": 1, "initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance", "acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1, "acceptanceSets": 1,
......
...@@ -3951,7 +3951,7 @@ ...@@ -3951,7 +3951,7 @@
{ {
"formula": "G(a | ((!a) U (Xa)))", "formula": "G(a | ((!a) U (Xa)))",
"properties": { "properties": {
"size": 7, "size": 8,
"initialStatesSize": 1, "initialStatesSize": 1,
"acceptanceName": "BuchiAcceptance", "acceptanceName": "BuchiAcceptance",
"acceptanceSets": 1, "acceptanceSets": 1,
......
...@@ -60,6 +60,11 @@ public interface EdgeTreeAutomatonMixin<S, A extends OmegaAcceptance> extends Au ...@@ -60,6 +60,11 @@ public interface EdgeTreeAutomatonMixin<S, A extends OmegaAcceptance> extends Au
return edgeTree(state).get(valuation); return edgeTree(state).get(valuation);
} }
@Override
default Set<S> successors(S state) {
return edgeTree(state).values(Edge::successor);
}
@Override @Override
default List<PreferredEdgeAccess> preferredEdgeAccess() { default List<PreferredEdgeAccess> preferredEdgeAccess() {
return ACCESS_MODES; return ACCESS_MODES;
......
...@@ -24,6 +24,7 @@ import static owl.ltl.SyntacticFragment.SINGLE_STEP; ...@@ -24,6 +24,7 @@ import static owl.ltl.SyntacticFragment.SINGLE_STEP;
import com.google.common.primitives.ImmutableIntArray; import com.google.common.primitives.ImmutableIntArray;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
...@@ -41,10 +42,9 @@ import owl.ltl.Conjunction; ...@@ -41,10 +42,9 @@ import owl.ltl.Conjunction;
import owl.ltl.Disjunction; import owl.ltl.Disjunction;
import owl.ltl.Formula; import owl.ltl.Formula;
import owl.ltl.GOperator; import owl.ltl.GOperator;
import owl.ltl.PropositionalFormula; import owl.ltl.Literal;
import owl.ltl.SyntacticFragment; import owl.ltl.SyntacticFragment;
import owl.ltl.SyntacticFragments; import owl.ltl.SyntacticFragments;
import owl.ltl.UnaryModalOperator;
import owl.ltl.XOperator; import owl.ltl.XOperator;
import owl.ltl.rewriter.LiteralMapper; import owl.ltl.rewriter.LiteralMapper;
import owl.ltl.rewriter.PullUpXVisitor; import owl.ltl.rewriter.PullUpXVisitor;
...@@ -118,7 +118,7 @@ public final class DecomposedDPA { ...@@ -118,7 +118,7 @@ public final class DecomposedDPA {
private static boolean isSingleStep(Formula formula) { private static boolean isSingleStep(Formula formula) {
if (formula instanceof Conjunction) { 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); return formula instanceof GOperator && SINGLE_STEP.contains(((GOperator) formula).operand);
...@@ -199,7 +199,8 @@ public final class DecomposedDPA { ...@@ -199,7 +199,8 @@ public final class DecomposedDPA {
return new LabelledTree.Leaf<>(newReference); return new LabelledTree.Leaf<>(newReference);
} }
private List<LabelledTree<Tag, Reference>> createLeaves(PropositionalFormula formula) { private List<LabelledTree<Tag, Reference>> createLeaves(
Formula.NaryPropositionalFormula formula) {
// Partition elements. // Partition elements.
var safety = new Clusters(); var safety = new Clusters();
var safetySingleStep = new HashMap<Integer, Clusters>(); var safetySingleStep = new HashMap<Integer, Clusters>();
...@@ -208,7 +209,7 @@ public final class DecomposedDPA { ...@@ -208,7 +209,7 @@ public final class DecomposedDPA {
var weakOrBuchiOrCoBuchi = new TreeSet<Formula>(); var weakOrBuchiOrCoBuchi = new TreeSet<Formula>();
var parity = new TreeSet<Formula>(); var parity = new TreeSet<Formula>();
for (Formula x : formula.children) { for (Formula x : formula.children()) {
switch (annotatedTree.get(x)) { switch (annotatedTree.get(x)) {
case SAFETY: case SAFETY:
PullUpXVisitor.XFormula rewrittenX = x.accept(PullUpXVisitor.INSTANCE); PullUpXVisitor.XFormula rewrittenX = x.accept(PullUpXVisitor.INSTANCE);
...@@ -240,7 +241,7 @@ public final class DecomposedDPA { ...@@ -240,7 +241,7 @@ public final class DecomposedDPA {
// Process elements. // Process elements.
List<LabelledTree<Tag, Reference>> children = new ArrayList<>(); 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 ? Conjunction::of
: Disjunction::of; : Disjunction::of;
...@@ -274,12 +275,17 @@ public final class DecomposedDPA { ...@@ -274,12 +275,17 @@ public final class DecomposedDPA {
} }
@Override @Override
protected LabelledTree<Tag, Reference> visit(Formula.TemporalOperator formula) { protected LabelledTree<Tag, Reference> visit(Formula.Temporal formula) {
return createLeaf(formula); return createLeaf(formula);
} }
@Override
public LabelledTree<Tag, Reference> visit(Literal literal) {
return createLeaf(literal);
}
private boolean keepTreeStructureBiconditional(Formula formula) { private boolean keepTreeStructureBiconditional(Formula formula) {
if (formula instanceof PropositionalFormula) { if (formula instanceof Conjunction || formula instanceof Disjunction) {
if (formula.children().stream() if (formula.children().stream()
.filter(x -> annotatedTree.get(x) == Acceptance.PARITY).count() > 1) { .filter(x -> annotatedTree.get(x) == Acceptance.PARITY).count() > 1) {
return false; return false;
...@@ -290,11 +296,10 @@ public final class DecomposedDPA { ...@@ -290,11 +296,10 @@ public final class DecomposedDPA {
return formula.accept(new PropositionalVisitor<Boolean>() { return formula.accept(new PropositionalVisitor<Boolean>() {
@Override @Override
protected Boolean visit(Formula.TemporalOperator formula) { protected Boolean visit(Formula.Temporal formula) {
return (SyntacticFragments.isAlmostAll(formula) return (SyntacticFragments.isAlmostAll(formula)
|| SyntacticFragments.isInfinitelyOften(formula)) || SyntacticFragments.isInfinitelyOften(formula))
&& SyntacticFragment.SINGLE_STEP && SyntacticFragment.SINGLE_STEP.contains(formula.children().get(0).children().get(0));
.contains(((UnaryModalOperator) ((UnaryModalOperator) formula).operand).operand);
} }
@Override @Override
...@@ -304,12 +309,12 @@ public final class DecomposedDPA { ...@@ -304,12 +309,12 @@ public final class DecomposedDPA {
@Override @Override
public Boolean visit(Conjunction conjunction) { public Boolean visit(Conjunction conjunction) {
return conjunction.children.stream().allMatch(this::apply); return conjunction.children().stream().allMatch(this::apply);
} }
@Override @Override
public Boolean visit(Disjunction disjunction) { 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 { ...@@ -355,7 +360,7 @@ public final class DecomposedDPA {
static class Clusters { static class Clusters {
private static final Predicate<Formula> INTERESTING_OPERATOR = 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<>(); List<Set<Formula>> clusterList = new ArrayList<>();
...@@ -364,9 +369,11 @@ public final class DecomposedDPA { ...@@ -364,9 +369,11 @@ public final class DecomposedDPA {
cluster.add(formula); cluster.add(formula);
clusterList.removeIf(x -> { clusterList.removeIf(x -> {
Set<Formula.TemporalOperator> modalOperators1 = formula.subformulas(INTERESTING_OPERATOR); Set<Formula.Temporal> modalOperators1 = formula.subformulas(
Set<Formula.TemporalOperator> modalOperators2 = x.stream() INTERESTING_OPERATOR, Formula.Temporal.class::cast);
.flatMap(y -> y.subformulas(INTERESTING_OPERATOR).stream()).collect(Collectors.toSet()); 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)) { if (!Collections.disjoint(modalOperators1, modalOperators2)) {
cluster.addAll(x); cluster.addAll(x);
...@@ -384,7 +391,7 @@ public final class DecomposedDPA { ...@@ -384,7 +391,7 @@ public final class DecomposedDPA {
static final Annotator INSTANCE = new Annotator(); static final Annotator INSTANCE = new Annotator();
@Override @Override
protected Map<Formula, Acceptance> visit(Formula.TemporalOperator formula) { protected Map<Formula, Acceptance> visit(Formula.Temporal formula) {
if (SyntacticFragments.isSafety(formula)) { if (SyntacticFragments.isSafety(formula)) {
return Map.of(formula, Acceptance.SAFETY); return Map.of(formula, Acceptance.SAFETY);
} }
...@@ -440,11 +447,16 @@ public final class DecomposedDPA { ...@@ -440,11 +447,16 @@ public final class DecomposedDPA {
return visitPropositional(disjunction); 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; Acceptance acceptance = Acceptance.BOTTOM;
Map<Formula, Acceptance> acceptanceMap = new HashMap<>(); Map<Formula, Acceptance> acceptanceMap = new HashMap<>();
for (Formula child : formula.children) { for (Formula child : formula.children()) {
Map<Formula, Acceptance> childDecisions = child.accept(this); Map<Formula, Acceptance> childDecisions = child.accept(this);
acceptanceMap.putAll(childDecisions); acceptanceMap.putAll(childDecisions);
acceptance = acceptance.lub(acceptanceMap.get(child)); acceptance = acceptance.lub(acceptanceMap.get(child));
......
...@@ -168,7 +168,7 @@ public final class DeterministicAutomaton<S, T> { ...@@ -168,7 +168,7 @@ public final class DeterministicAutomaton<S, T> {
if (SyntacticFragments.isGCoSafety(unwrapped)) { if (SyntacticFragments.isGCoSafety(unwrapped)) {
return new DeterministicAutomaton<>( return new DeterministicAutomaton<>(
GenericConstructions.delay( GenericConstructions.delay(
DeterministicConstructionsPortfolio.gCoSafety(ENV, LabelledFormula.of(unwrapped, formula.variables())), DeterministicConstructionsPortfolio.gCoSafety(ENV, LabelledFormula.of(unwrapped, formula.atomicPropositions())),
xCount), xCount),
BUCHI, BuchiAcceptance.class, BUCHI, BuchiAcceptance.class,
x -> false, x -> false,
...@@ -201,7 +201,7 @@ public final class DeterministicAutomaton<S, T> { ...@@ -201,7 +201,7 @@ public final class DeterministicAutomaton<S, T> {
x -> x.inSet(0) ? 0.0d : 0.5d x -> x.inSet(0) ? 0.0d : 0.5d
); );
} }
if (SyntacticFragments.isCoSafetySafety(formula.formula())) { if (SyntacticFragments.isCoSafetySafety(formula.formula())) {
return new DeterministicAutomaton<>( return new DeterministicAutomaton<>(
DeterministicConstructionsPortfolio.coSafetySafety(ENV, formula), DeterministicConstructionsPortfolio.coSafetySafety(ENV, formula),
......
...@@ -22,8 +22,10 @@ package owl.collections; ...@@ -22,8 +22,10 @@ package owl.collections;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
...@@ -54,7 +56,15 @@ public abstract class ValuationTree<E> { ...@@ -54,7 +56,15 @@ public abstract class ValuationTree<E> {
public abstract Set<E> get(BitSet valuation); 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) { public final Map<E, ValuationSet> inverse(ValuationSetFactory factory) {
return memoizedInverse(factory, new HashMap<>()); return memoizedInverse(factory, new HashMap<>());
...@@ -73,6 +83,9 @@ public abstract class ValuationTree<E> { ...@@ -73,6 +83,9 @@ public abstract class ValuationTree<E> {
ValuationSetFactory factory, ValuationSetFactory factory,
Map<ValuationTree<E>, Map<E, ValuationSet>> memoizedCalls); 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> { public static final class Leaf<E> extends ValuationTree<E> {
private static final ValuationTree<Object> EMPTY = new Leaf<>(Set.of()); private static final ValuationTree<Object> EMPTY = new Leaf<>(Set.of());
...@@ -89,8 +102,11 @@ public abstract class ValuationTree<E> { ...@@ -89,8 +102,11 @@ public abstract class ValuationTree<E> {
} }
@Override @Override
public Set<E> values() { protected <T> void memoizedValues(Set<T> values,
return new HashSet<>(value); Set<ValuationTree<E>> seenNodes, Function<E, T> mapper) {
for (E x : value) {
values.add(mapper.apply(x));
}
} }
@Override @Override
...@@ -153,10 +169,14 @@ public abstract class ValuationTree<E> { ...@@ -153,10 +169,14 @@ public abstract class ValuationTree<E> {
} }
@Override @Override
public Set<E> values() { protected <T> void memoizedValues(Set<T> values, Set<ValuationTree<E>> seenNodes,
Set<E> values = trueChild.values(); Function<E, T> mapper) {
values.addAll(falseChild.values()); if (!seenNodes.add(this)) {
return values; return;
}
trueChild.memoizedValues(values, seenNodes, mapper);
falseChild.memoizedValues(values, seenNodes, mapper);
} }
// Perfect for fork/join-parallesism // Perfect for fork/join-parallesism
......
...@@ -19,14 +19,7 @@ ...@@ -19,14 +19,7 @@
package owl.factories; package owl.factories;
import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List; 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.EquivalenceClass;
import owl.ltl.Formula; import owl.ltl.Formula;
...@@ -35,62 +28,5 @@ import owl.ltl.Formula; ...@@ -35,62 +28,5 @@ import owl.ltl.Formula;
public interface EquivalenceClassFactory { public interface EquivalenceClassFactory {
List<String> variables(); List<String> variables();
EquivalenceClass of(Formula formula); 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 { ...@@ -26,14 +26,9 @@ public interface FactorySupplier {
EquivalenceClassFactory getEquivalenceClassFactory(List<String> alphabet); EquivalenceClassFactory getEquivalenceClassFactory(List<String> alphabet);
EquivalenceClassFactory getEquivalenceClassFactory(List<String> alphabet, default Factories getFactories(List<String> alphabet) {
boolean keepRepresentatives);
Factories getFactories(List<String> alphabet);
default Factories getFactories(List<String> alphabet, boolean keepRepresentatives) {
return new Factories( return new Factories(
getEquivalenceClassFactory(alphabet, keepRepresentatives), getEquivalenceClassFactory(alphabet),
getValuationSetFactory(alphabet)); getValuationSetFactory(alphabet));
} }
} }
...@@ -29,17 +29,12 @@ import owl.factories.FactorySupplier; ...@@ -29,17 +29,12 @@ import owl.factories.FactorySupplier;
import owl.factories.ValuationSetFactory; import owl.factories.ValuationSetFactory;
public final class JBddSupplier implements FactorySupplier { public final class JBddSupplier implements FactorySupplier {
private static final JBddSupplier PLAIN = new JBddSupplier(false); private static final JBddSupplier INSTANCE = new JBddSupplier();
private static final JBddSupplier ANNOTATED = new JBddSupplier(true);
private final boolean keepRepresentativesDefault; private JBddSupplier() {}
private JBddSupplier(boolean keepRepresentativesDefault) { public static FactorySupplier async() {
this.keepRepresentativesDefault = keepRepresentativesDefault; return INSTANCE;
}
public static FactorySupplier async(boolean keepRepresentativesDefault) {