diff options
Diffstat (limited to 'subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter')
6 files changed, 388 insertions, 0 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java new file mode 100644 index 00000000..fb4c14a7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java | |||
@@ -0,0 +1,26 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
10 | import tools.refinery.store.util.CycleDetectingMapper; | ||
11 | |||
12 | public abstract class AbstractRecursiveRewriter implements DnfRewriter { | ||
13 | private final CycleDetectingMapper<Dnf, Dnf> mapper = new CycleDetectingMapper<>(Dnf::name, this::map); | ||
14 | |||
15 | @Override | ||
16 | public Dnf rewrite(Dnf dnf) { | ||
17 | return mapper.map(dnf); | ||
18 | } | ||
19 | |||
20 | protected Dnf map(Dnf dnf) { | ||
21 | var result = doRewrite(dnf); | ||
22 | return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, result) ? dnf : result; | ||
23 | } | ||
24 | |||
25 | protected abstract Dnf doRewrite(Dnf dnf); | ||
26 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java new file mode 100644 index 00000000..aa06a05a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java | |||
@@ -0,0 +1,160 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfClause; | ||
10 | import tools.refinery.store.query.literal.*; | ||
11 | import tools.refinery.store.query.substitution.Substitution; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.*; | ||
16 | |||
17 | class ClauseInputParameterResolver { | ||
18 | private final InputParameterResolver rewriter; | ||
19 | private final String dnfName; | ||
20 | private final int clauseIndex; | ||
21 | private final Set<Variable> positiveVariables = new LinkedHashSet<>(); | ||
22 | private final List<Literal> inlinedLiterals = new ArrayList<>(); | ||
23 | private final Deque<Literal> workList; | ||
24 | private int helperIndex = 0; | ||
25 | |||
26 | public ClauseInputParameterResolver(InputParameterResolver rewriter, List<Literal> context, DnfClause clause, | ||
27 | String dnfName, int clauseIndex) { | ||
28 | this.rewriter = rewriter; | ||
29 | this.dnfName = dnfName; | ||
30 | this.clauseIndex = clauseIndex; | ||
31 | workList = new ArrayDeque<>(clause.literals().size() + context.size()); | ||
32 | for (var literal : context) { | ||
33 | workList.addLast(literal); | ||
34 | } | ||
35 | for (var literal : clause.literals()) { | ||
36 | workList.addLast(literal); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | public List<Literal> rewriteClause() { | ||
41 | while (!workList.isEmpty()) { | ||
42 | var literal = workList.removeFirst(); | ||
43 | processLiteral(literal); | ||
44 | } | ||
45 | return inlinedLiterals; | ||
46 | } | ||
47 | |||
48 | private void processLiteral(Literal literal) { | ||
49 | if (!(literal instanceof AbstractCallLiteral abstractCallLiteral) || | ||
50 | !(abstractCallLiteral.getTarget() instanceof Dnf targetDnf)) { | ||
51 | markAsDone(literal); | ||
52 | return; | ||
53 | } | ||
54 | boolean hasInputParameter = hasInputParameter(targetDnf); | ||
55 | if (!hasInputParameter) { | ||
56 | targetDnf = rewriter.rewrite(targetDnf); | ||
57 | } | ||
58 | if (inlinePositiveClause(abstractCallLiteral, targetDnf)) { | ||
59 | return; | ||
60 | } | ||
61 | if (eliminateDoubleNegation(abstractCallLiteral, targetDnf)) { | ||
62 | return; | ||
63 | } | ||
64 | if (hasInputParameter) { | ||
65 | rewriteWithCurrentContext(abstractCallLiteral, targetDnf); | ||
66 | return; | ||
67 | } | ||
68 | markAsDone(abstractCallLiteral.withTarget(targetDnf)); | ||
69 | } | ||
70 | |||
71 | private void markAsDone(Literal literal) { | ||
72 | positiveVariables.addAll(literal.getOutputVariables()); | ||
73 | inlinedLiterals.add(literal); | ||
74 | } | ||
75 | |||
76 | private boolean inlinePositiveClause(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { | ||
77 | var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.POSITIVE); | ||
78 | if (targetLiteral == null) { | ||
79 | return false; | ||
80 | } | ||
81 | var substitution = asSubstitution(abstractCallLiteral, targetDnf); | ||
82 | var substitutedLiteral = targetLiteral.substitute(substitution); | ||
83 | workList.addFirst(substitutedLiteral); | ||
84 | return true; | ||
85 | } | ||
86 | |||
87 | private boolean eliminateDoubleNegation(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { | ||
88 | var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.NEGATIVE); | ||
89 | if (!(targetLiteral instanceof CallLiteral targetCallLiteral) || | ||
90 | targetCallLiteral.getPolarity() != CallPolarity.NEGATIVE) { | ||
91 | return false; | ||
92 | } | ||
93 | var substitution = asSubstitution(abstractCallLiteral, targetDnf); | ||
94 | var substitutedLiteral = (CallLiteral) targetCallLiteral.substitute(substitution); | ||
95 | workList.addFirst(substitutedLiteral.negate()); | ||
96 | return true; | ||
97 | } | ||
98 | |||
99 | private void rewriteWithCurrentContext(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { | ||
100 | var contextBuilder = Dnf.builder("%s#clause%d#helper%d".formatted(dnfName, clauseIndex, helperIndex)); | ||
101 | helperIndex++; | ||
102 | contextBuilder.parameters(positiveVariables, ParameterDirection.OUT); | ||
103 | contextBuilder.clause(inlinedLiterals); | ||
104 | var contextDnf = contextBuilder.build(); | ||
105 | var contextCall = new CallLiteral(CallPolarity.POSITIVE, contextDnf, List.copyOf(positiveVariables)); | ||
106 | inlinedLiterals.clear(); | ||
107 | var substitution = Substitution.builder().renewing().build(); | ||
108 | var context = new ArrayList<Literal>(); | ||
109 | context.add(contextCall.substitute(substitution)); | ||
110 | int arity = targetDnf.arity(); | ||
111 | for (int i = 0; i < arity; i++) { | ||
112 | var parameter = targetDnf.getSymbolicParameters().get(i).getVariable(); | ||
113 | var argument = abstractCallLiteral.getArguments().get(i); | ||
114 | context.add(new EquivalenceLiteral(true, parameter, substitution.getSubstitute(argument))); | ||
115 | } | ||
116 | var rewrittenDnf = rewriter.rewriteWithContext(context, targetDnf); | ||
117 | workList.addFirst(abstractCallLiteral.withTarget(rewrittenDnf)); | ||
118 | workList.addFirst(contextCall); | ||
119 | } | ||
120 | |||
121 | private static boolean hasInputParameter(Dnf targetDnf) { | ||
122 | for (var parameter : targetDnf.getParameters()) { | ||
123 | if (parameter.getDirection() != ParameterDirection.OUT) { | ||
124 | return true; | ||
125 | } | ||
126 | } | ||
127 | return false; | ||
128 | } | ||
129 | |||
130 | private static Literal getSingleLiteral(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf, | ||
131 | CallPolarity polarity) { | ||
132 | if (!(abstractCallLiteral instanceof CallLiteral callLiteral) || | ||
133 | callLiteral.getPolarity() != polarity) { | ||
134 | return null; | ||
135 | } | ||
136 | var clauses = targetDnf.getClauses(); | ||
137 | if (clauses.size() != 1) { | ||
138 | return null; | ||
139 | } | ||
140 | var targetLiterals = clauses.get(0).literals(); | ||
141 | if (targetLiterals.size() != 1) { | ||
142 | return null; | ||
143 | } | ||
144 | return targetLiterals.get(0); | ||
145 | } | ||
146 | |||
147 | private static Substitution asSubstitution(AbstractCallLiteral callLiteral, Dnf targetDnf) { | ||
148 | var builder = Substitution.builder().renewing(); | ||
149 | var arguments = callLiteral.getArguments(); | ||
150 | var parameters = targetDnf.getSymbolicParameters(); | ||
151 | int arity = arguments.size(); | ||
152 | if (parameters.size() != arity) { | ||
153 | throw new IllegalArgumentException("Call %s of %s arity mismatch".formatted(callLiteral, targetDnf)); | ||
154 | } | ||
155 | for (int i = 0; i < arity; i++) { | ||
156 | builder.putChecked(parameters.get(i).getVariable(), arguments.get(i)); | ||
157 | } | ||
158 | return builder.build(); | ||
159 | } | ||
160 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java new file mode 100644 index 00000000..5b4f65e5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java | |||
@@ -0,0 +1,29 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | |||
10 | import java.util.ArrayList; | ||
11 | import java.util.List; | ||
12 | |||
13 | public class CompositeRewriter implements DnfRewriter { | ||
14 | private final List<DnfRewriter> rewriterList = new ArrayList<>(); | ||
15 | |||
16 | public void addFirst(DnfRewriter rewriter) { | ||
17 | rewriterList.add(rewriter); | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public Dnf rewrite(Dnf dnf) { | ||
22 | Dnf rewrittenDnf = dnf; | ||
23 | for (int i = rewriterList.size() - 1; i >= 0; i--) { | ||
24 | var rewriter = rewriterList.get(i); | ||
25 | rewrittenDnf = rewriter.rewrite(rewrittenDnf); | ||
26 | } | ||
27 | return rewrittenDnf; | ||
28 | } | ||
29 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java new file mode 100644 index 00000000..5d8359d1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java | |||
@@ -0,0 +1,24 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.AnyQuery; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface DnfRewriter { | ||
14 | Dnf rewrite(Dnf dnf); | ||
15 | |||
16 | default AnyQuery rewrite(AnyQuery query) { | ||
17 | return rewrite((Query<?>) query); | ||
18 | } | ||
19 | |||
20 | default <T> Query<T> rewrite(Query<T> query) { | ||
21 | var rewrittenDnf = rewrite(query.getDnf()); | ||
22 | return query.withDnf(rewrittenDnf); | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java new file mode 100644 index 00000000..0c786470 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java | |||
@@ -0,0 +1,98 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfClause; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
12 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
13 | import tools.refinery.store.query.literal.Literal; | ||
14 | |||
15 | import java.util.ArrayList; | ||
16 | import java.util.HashMap; | ||
17 | import java.util.List; | ||
18 | import java.util.Map; | ||
19 | |||
20 | public class DuplicateDnfRemover extends AbstractRecursiveRewriter { | ||
21 | private final Map<CanonicalDnf, Dnf> dnfCache = new HashMap<>(); | ||
22 | private final Map<Dnf, Query<?>> queryCache = new HashMap<>(); | ||
23 | |||
24 | @Override | ||
25 | protected Dnf map(Dnf dnf) { | ||
26 | var result = super.map(dnf); | ||
27 | return dnfCache.computeIfAbsent(new CanonicalDnf(result), CanonicalDnf::getDnf); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | protected Dnf doRewrite(Dnf dnf) { | ||
32 | var builder = Dnf.builderFrom(dnf); | ||
33 | for (var clause : dnf.getClauses()) { | ||
34 | builder.clause(rewriteClause(clause)); | ||
35 | } | ||
36 | return builder.build(); | ||
37 | } | ||
38 | |||
39 | private List<Literal> rewriteClause(DnfClause clause) { | ||
40 | var originalLiterals = clause.literals(); | ||
41 | var literals = new ArrayList<Literal>(originalLiterals.size()); | ||
42 | for (var literal : originalLiterals) { | ||
43 | var rewrittenLiteral = literal; | ||
44 | if (literal instanceof AbstractCallLiteral abstractCallLiteral && | ||
45 | abstractCallLiteral.getTarget() instanceof Dnf targetDnf) { | ||
46 | var rewrittenTarget = rewrite(targetDnf); | ||
47 | rewrittenLiteral = abstractCallLiteral.withTarget(rewrittenTarget); | ||
48 | } | ||
49 | literals.add(rewrittenLiteral); | ||
50 | } | ||
51 | return literals; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public <T> Query<T> rewrite(Query<T> query) { | ||
56 | var rewrittenDnf = rewrite(query.getDnf()); | ||
57 | // {@code withDnf} will always return the appropriate type. | ||
58 | @SuppressWarnings("unchecked") | ||
59 | var rewrittenQuery = (Query<T>) queryCache.computeIfAbsent(rewrittenDnf, query::withDnf); | ||
60 | return rewrittenQuery; | ||
61 | } | ||
62 | |||
63 | private static class CanonicalDnf { | ||
64 | private final Dnf dnf; | ||
65 | private final int hash; | ||
66 | |||
67 | public CanonicalDnf(Dnf dnf) { | ||
68 | this.dnf = dnf; | ||
69 | hash = dnf.hashCodeWithSubstitution(); | ||
70 | } | ||
71 | |||
72 | public Dnf getDnf() { | ||
73 | return dnf; | ||
74 | } | ||
75 | |||
76 | @Override | ||
77 | public boolean equals(Object obj) { | ||
78 | if (this == obj) { | ||
79 | return true; | ||
80 | } | ||
81 | if (obj == null || getClass() != obj.getClass()) { | ||
82 | return false; | ||
83 | } | ||
84 | var otherCanonicalDnf = (CanonicalDnf) obj; | ||
85 | return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, otherCanonicalDnf.dnf); | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public int hashCode() { | ||
90 | return hash; | ||
91 | } | ||
92 | |||
93 | @Override | ||
94 | public String toString() { | ||
95 | return dnf.name(); | ||
96 | } | ||
97 | } | ||
98 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java new file mode 100644 index 00000000..cd8a2e7d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfBuilder; | ||
10 | import tools.refinery.store.query.literal.Literal; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.HashSet; | ||
15 | import java.util.List; | ||
16 | |||
17 | public class InputParameterResolver extends AbstractRecursiveRewriter { | ||
18 | @Override | ||
19 | protected Dnf doRewrite(Dnf dnf) { | ||
20 | return rewriteWithContext(List.of(), dnf); | ||
21 | } | ||
22 | |||
23 | Dnf rewriteWithContext(List<Literal> context, Dnf dnf) { | ||
24 | var dnfName = dnf.name(); | ||
25 | var builder = Dnf.builder(dnfName); | ||
26 | createSymbolicParameters(context, dnf, builder); | ||
27 | builder.functionalDependencies(dnf.getFunctionalDependencies()); | ||
28 | var clauses = dnf.getClauses(); | ||
29 | int clauseCount = clauses.size(); | ||
30 | for (int i = 0; i < clauseCount; i++) { | ||
31 | var clause = clauses.get(i); | ||
32 | var clauseRewriter = new ClauseInputParameterResolver(this, context, clause, dnfName, i); | ||
33 | builder.clause(clauseRewriter.rewriteClause()); | ||
34 | } | ||
35 | return builder.build(); | ||
36 | } | ||
37 | |||
38 | private static void createSymbolicParameters(List<Literal> context, Dnf dnf, DnfBuilder builder) { | ||
39 | var positiveInContext = new HashSet<Variable>(); | ||
40 | for (var literal : context) { | ||
41 | positiveInContext.addAll(literal.getOutputVariables()); | ||
42 | } | ||
43 | for (var symbolicParameter : dnf.getSymbolicParameters()) { | ||
44 | var variable = symbolicParameter.getVariable(); | ||
45 | var isOutput = symbolicParameter.getDirection() == ParameterDirection.OUT || | ||
46 | positiveInContext.contains(variable); | ||
47 | var direction = isOutput ? ParameterDirection.OUT : ParameterDirection.IN; | ||
48 | builder.parameter(variable, direction); | ||
49 | } | ||
50 | } | ||
51 | } | ||