aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter')
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java160
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java98
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java51
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 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.util.CycleDetectingMapper;
11
12public 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 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.literal.*;
11import tools.refinery.store.query.substitution.Substitution;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.*;
16
17class 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 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9
10import java.util.ArrayList;
11import java.util.List;
12
13public 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 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.AnyQuery;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.Query;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.equality.DnfEqualityChecker;
12import tools.refinery.store.query.literal.AbstractCallLiteral;
13import tools.refinery.store.query.literal.Literal;
14
15import java.util.ArrayList;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20public 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 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfBuilder;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.HashSet;
15import java.util.List;
16
17public 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}