diff options
Diffstat (limited to 'subprojects/store-query/src')
26 files changed, 383 insertions, 665 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java index 467b8d52..dd45ecd4 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java | |||
@@ -9,35 +9,30 @@ import org.jetbrains.annotations.NotNull; | |||
9 | import tools.refinery.store.query.literal.BooleanLiteral; | 9 | import tools.refinery.store.query.literal.BooleanLiteral; |
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | 10 | import tools.refinery.store.query.literal.EquivalenceLiteral; |
11 | import tools.refinery.store.query.literal.Literal; | 11 | import tools.refinery.store.query.literal.Literal; |
12 | import tools.refinery.store.query.literal.VariableDirection; | ||
13 | import tools.refinery.store.query.substitution.MapBasedSubstitution; | 12 | import tools.refinery.store.query.substitution.MapBasedSubstitution; |
14 | import tools.refinery.store.query.substitution.StatelessSubstitution; | 13 | import tools.refinery.store.query.substitution.StatelessSubstitution; |
15 | import tools.refinery.store.query.substitution.Substitution; | 14 | import tools.refinery.store.query.substitution.Substitution; |
16 | import tools.refinery.store.query.term.NodeVariable; | 15 | import tools.refinery.store.query.term.NodeVariable; |
16 | import tools.refinery.store.query.term.ParameterDirection; | ||
17 | import tools.refinery.store.query.term.Variable; | 17 | import tools.refinery.store.query.term.Variable; |
18 | 18 | ||
19 | import java.util.*; | 19 | import java.util.*; |
20 | import java.util.function.Function; | 20 | import java.util.function.Function; |
21 | import java.util.stream.Collectors; | ||
22 | 21 | ||
23 | class ClausePostProcessor { | 22 | class ClausePostProcessor { |
24 | private final Set<Variable> parameters; | 23 | private final Map<Variable, ParameterInfo> parameters; |
25 | private final Map<Variable, Integer> parameterWeights; | ||
26 | private final List<Literal> literals; | 24 | private final List<Literal> literals; |
27 | private final Map<NodeVariable, NodeVariable> representatives = new LinkedHashMap<>(); | 25 | private final Map<NodeVariable, NodeVariable> representatives = new LinkedHashMap<>(); |
28 | private final Map<NodeVariable, Set<NodeVariable>> equivalencePartition = new HashMap<>(); | 26 | private final Map<NodeVariable, Set<NodeVariable>> equivalencePartition = new HashMap<>(); |
29 | private List<Literal> substitutedLiterals; | 27 | private List<Literal> substitutedLiterals; |
30 | private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>(); | 28 | private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>(); |
31 | private Set<Variable> inputParameters; | ||
32 | private Set<Variable> positiveVariables; | 29 | private Set<Variable> positiveVariables; |
33 | private Map<Variable, Set<SortableLiteral>> variableToLiteralInputMap; | 30 | private Map<Variable, Set<SortableLiteral>> variableToLiteralInputMap; |
34 | private PriorityQueue<SortableLiteral> literalsWithAllInputsBound; | 31 | private PriorityQueue<SortableLiteral> literalsWithAllInputsBound; |
35 | private LinkedHashSet<Literal> topologicallySortedLiterals; | 32 | private LinkedHashSet<Literal> topologicallySortedLiterals; |
36 | 33 | ||
37 | public ClausePostProcessor(Set<Variable> parameters, Map<Variable, Integer> parameterWeights, | 34 | public ClausePostProcessor(Map<Variable, ParameterInfo> parameters, List<Literal> literals) { |
38 | List<Literal> literals) { | ||
39 | this.parameters = parameters; | 35 | this.parameters = parameters; |
40 | this.parameterWeights = parameterWeights; | ||
41 | this.literals = literals; | 36 | this.literals = literals; |
42 | } | 37 | } |
43 | 38 | ||
@@ -46,9 +41,10 @@ class ClausePostProcessor { | |||
46 | substitutedLiterals = new ArrayList<>(literals.size()); | 41 | substitutedLiterals = new ArrayList<>(literals.size()); |
47 | keepParameterEquivalences(); | 42 | keepParameterEquivalences(); |
48 | substituteLiterals(); | 43 | substituteLiterals(); |
44 | computeExistentiallyQuantifiedVariables(); | ||
49 | computePositiveVariables(); | 45 | computePositiveVariables(); |
50 | validatePositiveRepresentatives(); | 46 | validatePositiveRepresentatives(); |
51 | validateClosureVariables(); | 47 | validatePrivateVariables(); |
52 | topologicallySortLiterals(); | 48 | topologicallySortLiterals(); |
53 | var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size()); | 49 | var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size()); |
54 | for (var literal : topologicallySortedLiterals) { | 50 | for (var literal : topologicallySortedLiterals) { |
@@ -71,20 +67,21 @@ class ClausePostProcessor { | |||
71 | for (var literal : literals) { | 67 | for (var literal : literals) { |
72 | if (isPositiveEquivalence(literal)) { | 68 | if (isPositiveEquivalence(literal)) { |
73 | var equivalenceLiteral = (EquivalenceLiteral) literal; | 69 | var equivalenceLiteral = (EquivalenceLiteral) literal; |
74 | mergeVariables(equivalenceLiteral.getLeft(), equivalenceLiteral.getRight()); | 70 | mergeVariables(equivalenceLiteral.left(), equivalenceLiteral.right()); |
75 | } | 71 | } |
76 | } | 72 | } |
77 | } | 73 | } |
78 | 74 | ||
79 | private static boolean isPositiveEquivalence(Literal literal) { | 75 | private static boolean isPositiveEquivalence(Literal literal) { |
80 | return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.isPositive(); | 76 | return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.positive(); |
81 | } | 77 | } |
82 | 78 | ||
83 | private void mergeVariables(NodeVariable left, NodeVariable right) { | 79 | private void mergeVariables(NodeVariable left, NodeVariable right) { |
84 | var leftRepresentative = getRepresentative(left); | 80 | var leftRepresentative = getRepresentative(left); |
85 | var rightRepresentative = getRepresentative(right); | 81 | var rightRepresentative = getRepresentative(right); |
86 | if (parameters.contains(leftRepresentative) && (!parameters.contains(rightRepresentative) || | 82 | var leftInfo = parameters.get(leftRepresentative); |
87 | parameterWeights.get(leftRepresentative).compareTo(parameterWeights.get(rightRepresentative)) <= 0)) { | 83 | var rightInfo = parameters.get(rightRepresentative); |
84 | if (leftInfo != null && (rightInfo == null || leftInfo.index() <= rightInfo.index())) { | ||
88 | // Prefer the variable occurring earlier in the parameter list as a representative. | 85 | // Prefer the variable occurring earlier in the parameter list as a representative. |
89 | doMergeVariables(leftRepresentative, rightRepresentative); | 86 | doMergeVariables(leftRepresentative, rightRepresentative); |
90 | } else { | 87 | } else { |
@@ -123,7 +120,7 @@ class ClausePostProcessor { | |||
123 | for (var pair : representatives.entrySet()) { | 120 | for (var pair : representatives.entrySet()) { |
124 | var left = pair.getKey(); | 121 | var left = pair.getKey(); |
125 | var right = pair.getValue(); | 122 | var right = pair.getValue(); |
126 | if (!left.equals(right) && parameters.contains(left) && parameters.contains(right)) { | 123 | if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) { |
127 | substitutedLiterals.add(left.isEquivalent(right)); | 124 | substitutedLiterals.add(left.isEquivalent(right)); |
128 | } | 125 | } |
129 | } | 126 | } |
@@ -148,27 +145,37 @@ class ClausePostProcessor { | |||
148 | } | 145 | } |
149 | } | 146 | } |
150 | 147 | ||
151 | private void computePositiveVariables() { | 148 | private void computeExistentiallyQuantifiedVariables() { |
152 | for (var literal : substitutedLiterals) { | 149 | for (var literal : substitutedLiterals) { |
153 | var variableBinder = literal.getVariableBindingSite(); | 150 | for (var variable : literal.getOutputVariables()) { |
154 | variableBinder.getVariablesWithDirection(VariableDirection.IN_OUT) | ||
155 | .forEach(existentiallyQuantifiedVariables::add); | ||
156 | variableBinder.getVariablesWithDirection(VariableDirection.OUT).forEach(variable -> { | ||
157 | boolean added = existentiallyQuantifiedVariables.add(variable); | 151 | boolean added = existentiallyQuantifiedVariables.add(variable); |
158 | if (!added) { | 152 | if (!variable.isUnifiable()) { |
159 | throw new IllegalArgumentException("Variable %s has multiple %s bindings" | 153 | var parameterInfo = parameters.get(variable); |
160 | .formatted(variable, VariableDirection.OUT)); | 154 | if (parameterInfo != null && parameterInfo.direction() == ParameterDirection.IN) { |
155 | throw new IllegalArgumentException("Trying to bind %s parameter %s" | ||
156 | .formatted(ParameterDirection.IN, variable)); | ||
157 | } | ||
158 | if (!added) { | ||
159 | throw new IllegalArgumentException("Variable %s has multiple assigned values" | ||
160 | .formatted(variable)); | ||
161 | } | ||
161 | } | 162 | } |
162 | }); | 163 | } |
164 | } | ||
165 | } | ||
166 | |||
167 | private void computePositiveVariables() { | ||
168 | positiveVariables = new LinkedHashSet<>(); | ||
169 | for (var pair : parameters.entrySet()) { | ||
170 | var variable = pair.getKey(); | ||
171 | if (pair.getValue().direction() == ParameterDirection.IN) { | ||
172 | // Inputs count as positive, because they are already bound when we evaluate literals. | ||
173 | positiveVariables.add(variable); | ||
174 | } else if (!existentiallyQuantifiedVariables.contains(variable)) { | ||
175 | throw new IllegalArgumentException("Unbound %s parameter %s" | ||
176 | .formatted(ParameterDirection.OUT, variable)); | ||
177 | } | ||
163 | } | 178 | } |
164 | // Input parameters are parameters not bound by any positive literal. | ||
165 | inputParameters = new LinkedHashSet<>(parameters); | ||
166 | inputParameters.removeAll(existentiallyQuantifiedVariables); | ||
167 | // Existentially quantified variables are variables appearing in positive literals that aren't parameters. | ||
168 | existentiallyQuantifiedVariables.removeAll(parameters); | ||
169 | // Positive variables are parameters (including input parameters) and variables bound by positive literals. | ||
170 | positiveVariables = new LinkedHashSet<>(parameters.size() + existentiallyQuantifiedVariables.size()); | ||
171 | positiveVariables.addAll(parameters); | ||
172 | positiveVariables.addAll(existentiallyQuantifiedVariables); | 179 | positiveVariables.addAll(existentiallyQuantifiedVariables); |
173 | } | 180 | } |
174 | 181 | ||
@@ -183,19 +190,16 @@ class ClausePostProcessor { | |||
183 | } | 190 | } |
184 | } | 191 | } |
185 | 192 | ||
186 | private void validateClosureVariables() { | 193 | private void validatePrivateVariables() { |
187 | var negativeVariablesMap = new HashMap<Variable, Literal>(); | 194 | var negativeVariablesMap = new HashMap<Variable, Literal>(); |
188 | for (var literal : substitutedLiterals) { | 195 | for (var literal : substitutedLiterals) { |
189 | var variableBinder = literal.getVariableBindingSite(); | 196 | for (var variable : literal.getPrivateVariables(positiveVariables)) { |
190 | variableBinder.getVariablesWithDirection(VariableDirection.CLOSURE, positiveVariables) | 197 | var oldLiteral = negativeVariablesMap.put(variable, literal); |
191 | .forEach(variable -> { | 198 | if (oldLiteral != null) { |
192 | var oldLiteral = negativeVariablesMap.put(variable, literal); | 199 | throw new IllegalArgumentException("Unbound variable %s appears in multiple literals %s and %s" |
193 | if (oldLiteral != null) { | 200 | .formatted(variable, oldLiteral, literal)); |
194 | throw new IllegalArgumentException( | 201 | } |
195 | "Unbound variable %s appears in multiple literals %s and %s" | 202 | } |
196 | .formatted(variable, oldLiteral, literal)); | ||
197 | } | ||
198 | }); | ||
199 | } | 203 | } |
200 | } | 204 | } |
201 | 205 | ||
@@ -219,16 +223,6 @@ class ClausePostProcessor { | |||
219 | } | 223 | } |
220 | } | 224 | } |
221 | 225 | ||
222 | private void topologicallySortVariable(Variable variable) { | ||
223 | var literalSetForInput = variableToLiteralInputMap.remove(variable); | ||
224 | if (literalSetForInput == null) { | ||
225 | return; | ||
226 | } | ||
227 | for (var targetSortableLiteral : literalSetForInput) { | ||
228 | targetSortableLiteral.bindVariable(variable); | ||
229 | } | ||
230 | } | ||
231 | |||
232 | private class SortableLiteral implements Comparable<SortableLiteral> { | 226 | private class SortableLiteral implements Comparable<SortableLiteral> { |
233 | private final int index; | 227 | private final int index; |
234 | private final Literal literal; | 228 | private final Literal literal; |
@@ -237,10 +231,12 @@ class ClausePostProcessor { | |||
237 | private SortableLiteral(int index, Literal literal) { | 231 | private SortableLiteral(int index, Literal literal) { |
238 | this.index = index; | 232 | this.index = index; |
239 | this.literal = literal; | 233 | this.literal = literal; |
240 | remainingInputs = literal.getVariableBindingSite() | 234 | remainingInputs = new HashSet<>(literal.getInputVariables(positiveVariables)); |
241 | .getVariablesWithDirection(VariableDirection.IN, positiveVariables) | 235 | for (var pair : parameters.entrySet()) { |
242 | .collect(Collectors.toCollection(HashSet::new)); | 236 | if (pair.getValue().direction() == ParameterDirection.IN) { |
243 | remainingInputs.removeAll(inputParameters); | 237 | remainingInputs.remove(pair.getKey()); |
238 | } | ||
239 | } | ||
244 | } | 240 | } |
245 | 241 | ||
246 | public void enqueue() { | 242 | public void enqueue() { |
@@ -282,11 +278,15 @@ class ClausePostProcessor { | |||
282 | } | 278 | } |
283 | // Add literal if we haven't yet added a duplicate of this literal. | 279 | // Add literal if we haven't yet added a duplicate of this literal. |
284 | topologicallySortedLiterals.add(literal); | 280 | topologicallySortedLiterals.add(literal); |
285 | var variableBinder = literal.getVariableBindingSite(); | 281 | for (var variable : literal.getOutputVariables()) { |
286 | variableBinder.getVariablesWithDirection(VariableDirection.IN_OUT) | 282 | var literalSetForInput = variableToLiteralInputMap.remove(variable); |
287 | .forEach(ClausePostProcessor.this::topologicallySortVariable); | 283 | if (literalSetForInput == null) { |
288 | variableBinder.getVariablesWithDirection(VariableDirection.OUT) | 284 | continue; |
289 | .forEach(ClausePostProcessor.this::topologicallySortVariable); | 285 | } |
286 | for (var targetSortableLiteral : literalSetForInput) { | ||
287 | targetSortableLiteral.bindVariable(variable); | ||
288 | } | ||
289 | } | ||
290 | } | 290 | } |
291 | 291 | ||
292 | @Override | 292 | @Override |
@@ -318,4 +318,7 @@ class ClausePostProcessor { | |||
318 | ALWAYS_TRUE, | 318 | ALWAYS_TRUE, |
319 | ALWAYS_FALSE | 319 | ALWAYS_FALSE |
320 | } | 320 | } |
321 | |||
322 | public record ParameterInfo(ParameterDirection direction, int index) { | ||
323 | } | ||
321 | } | 324 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java index 3fac4627..dcf7611d 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java | |||
@@ -7,15 +7,18 @@ package tools.refinery.store.query.dnf; | |||
7 | 7 | ||
8 | import tools.refinery.store.query.dnf.callback.*; | 8 | import tools.refinery.store.query.dnf.callback.*; |
9 | import tools.refinery.store.query.literal.Literal; | 9 | import tools.refinery.store.query.literal.Literal; |
10 | import tools.refinery.store.query.term.*; | 10 | import tools.refinery.store.query.term.DataVariable; |
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
11 | 14 | ||
12 | import java.util.*; | 15 | import java.util.*; |
13 | 16 | ||
14 | @SuppressWarnings("UnusedReturnValue") | 17 | @SuppressWarnings("UnusedReturnValue") |
15 | public final class DnfBuilder { | 18 | public final class DnfBuilder { |
16 | private final String name; | 19 | private final String name; |
17 | private final List<Variable> parameters = new ArrayList<>(); | 20 | private final Set<Variable> parameterVariables = new LinkedHashSet<>(); |
18 | private final Map<Variable, ParameterDirection> directions = new HashMap<>(); | 21 | private final List<SymbolicParameter> parameters = new ArrayList<>(); |
19 | private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>(); | 22 | private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>(); |
20 | private final List<List<Literal>> clauses = new ArrayList<>(); | 23 | private final List<List<Literal>> clauses = new ArrayList<>(); |
21 | 24 | ||
@@ -28,20 +31,16 @@ public final class DnfBuilder { | |||
28 | } | 31 | } |
29 | 32 | ||
30 | public NodeVariable parameter(String name) { | 33 | public NodeVariable parameter(String name) { |
31 | var variable = Variable.of(name); | 34 | return parameter(name, ParameterDirection.OUT); |
32 | parameter(variable); | ||
33 | return variable; | ||
34 | } | 35 | } |
35 | 36 | ||
36 | public NodeVariable parameter(ParameterDirection direction) { | 37 | public NodeVariable parameter(ParameterDirection direction) { |
37 | var variable = parameter(); | 38 | return parameter((String) null, direction); |
38 | putDirection(variable, direction); | ||
39 | return variable; | ||
40 | } | 39 | } |
41 | 40 | ||
42 | public NodeVariable parameter(String name, ParameterDirection direction) { | 41 | public NodeVariable parameter(String name, ParameterDirection direction) { |
43 | var variable = parameter(name); | 42 | var variable = Variable.of(name); |
44 | putDirection(variable, direction); | 43 | parameter(variable, direction); |
45 | return variable; | 44 | return variable; |
46 | } | 45 | } |
47 | 46 | ||
@@ -50,50 +49,25 @@ public final class DnfBuilder { | |||
50 | } | 49 | } |
51 | 50 | ||
52 | public <T> DataVariable<T> parameter(String name, Class<T> type) { | 51 | public <T> DataVariable<T> parameter(String name, Class<T> type) { |
53 | var variable = Variable.of(name, type); | 52 | return parameter(name, type, ParameterDirection.OUT); |
54 | parameter(variable); | ||
55 | return variable; | ||
56 | } | 53 | } |
57 | 54 | ||
58 | public <T> DataVariable<T> parameter(Class<T> type, ParameterDirection direction) { | 55 | public <T> DataVariable<T> parameter(Class<T> type, ParameterDirection direction) { |
59 | var variable = parameter(type); | 56 | return parameter(null, type, direction); |
60 | putDirection(variable, direction); | ||
61 | return variable; | ||
62 | } | 57 | } |
63 | 58 | ||
64 | public <T> DataVariable<T> parameter(String name, Class<T> type, ParameterDirection direction) { | 59 | public <T> DataVariable<T> parameter(String name, Class<T> type, ParameterDirection direction) { |
65 | var variable = parameter(name, type); | 60 | var variable = Variable.of(name, type); |
66 | putDirection(variable, direction); | 61 | parameter(variable, direction); |
67 | return variable; | 62 | return variable; |
68 | } | 63 | } |
69 | 64 | ||
70 | public DnfBuilder parameter(Variable variable) { | 65 | public DnfBuilder parameter(Variable variable) { |
71 | if (parameters.contains(variable)) { | 66 | return parameter(variable, ParameterDirection.OUT); |
72 | throw new IllegalArgumentException("Duplicate parameter: " + variable); | ||
73 | } | ||
74 | parameters.add(variable); | ||
75 | return this; | ||
76 | } | 67 | } |
77 | 68 | ||
78 | public DnfBuilder parameter(Variable variable, ParameterDirection direction) { | 69 | public DnfBuilder parameter(Variable variable, ParameterDirection direction) { |
79 | parameter(variable); | 70 | return symbolicParameter(new SymbolicParameter(variable, direction)); |
80 | putDirection(variable, direction); | ||
81 | return this; | ||
82 | } | ||
83 | |||
84 | private void putDirection(Variable variable, ParameterDirection direction) { | ||
85 | if (variable.tryGetType().isPresent()) { | ||
86 | if (direction == ParameterDirection.IN_OUT) { | ||
87 | throw new IllegalArgumentException("%s direction is forbidden for data variable %s" | ||
88 | .formatted(direction, variable)); | ||
89 | } | ||
90 | } else { | ||
91 | if (direction == ParameterDirection.OUT) { | ||
92 | throw new IllegalArgumentException("%s direction is forbidden for node variable %s" | ||
93 | .formatted(direction, variable)); | ||
94 | } | ||
95 | } | ||
96 | directions.put(variable, direction); | ||
97 | } | 71 | } |
98 | 72 | ||
99 | public DnfBuilder parameters(Variable... variables) { | 73 | public DnfBuilder parameters(Variable... variables) { |
@@ -101,10 +75,7 @@ public final class DnfBuilder { | |||
101 | } | 75 | } |
102 | 76 | ||
103 | public DnfBuilder parameters(Collection<? extends Variable> variables) { | 77 | public DnfBuilder parameters(Collection<? extends Variable> variables) { |
104 | for (var variable : variables) { | 78 | return parameters(variables, ParameterDirection.OUT); |
105 | parameter(variable); | ||
106 | } | ||
107 | return this; | ||
108 | } | 79 | } |
109 | 80 | ||
110 | public DnfBuilder parameters(Collection<? extends Variable> variables, ParameterDirection direction) { | 81 | public DnfBuilder parameters(Collection<? extends Variable> variables, ParameterDirection direction) { |
@@ -114,9 +85,23 @@ public final class DnfBuilder { | |||
114 | return this; | 85 | return this; |
115 | } | 86 | } |
116 | 87 | ||
117 | public DnfBuilder symbolicParameters(Collection<SymbolicParameter> parameters) { | 88 | public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) { |
118 | for (var parameter : parameters) { | 89 | var variable = symbolicParameter.getVariable(); |
119 | parameter(parameter.getVariable(), parameter.getDirection()); | 90 | if (!parameterVariables.add(variable)) { |
91 | throw new IllegalArgumentException("Variable %s is already on the parameter list %s" | ||
92 | .formatted(variable, parameters)); | ||
93 | } | ||
94 | parameters.add(symbolicParameter); | ||
95 | return this; | ||
96 | } | ||
97 | |||
98 | public DnfBuilder symbolicParameters(SymbolicParameter... symbolicParameters) { | ||
99 | return symbolicParameters(List.of(symbolicParameters)); | ||
100 | } | ||
101 | |||
102 | public DnfBuilder symbolicParameters(Collection<SymbolicParameter> symbolicParameters) { | ||
103 | for (var symbolicParameter : symbolicParameters) { | ||
104 | symbolicParameter(symbolicParameter); | ||
120 | } | 105 | } |
121 | return this; | 106 | return this; |
122 | } | 107 | } |
@@ -214,24 +199,24 @@ public final class DnfBuilder { | |||
214 | } | 199 | } |
215 | 200 | ||
216 | <T> void output(DataVariable<T> outputVariable) { | 201 | <T> void output(DataVariable<T> outputVariable) { |
217 | var fromParameters = Set.copyOf(parameters); | 202 | // Copy parameter variables to exclude the newly added {@code outputVariable}. |
203 | var fromParameters = Set.copyOf(parameterVariables); | ||
218 | parameter(outputVariable, ParameterDirection.OUT); | 204 | parameter(outputVariable, ParameterDirection.OUT); |
219 | functionalDependency(fromParameters, Set.of(outputVariable)); | 205 | functionalDependency(fromParameters, Set.of(outputVariable)); |
220 | } | 206 | } |
221 | 207 | ||
222 | public Dnf build() { | 208 | public Dnf build() { |
223 | var postProcessedClauses = postProcessClauses(); | 209 | var postProcessedClauses = postProcessClauses(); |
224 | return new Dnf(name, createParameterList(postProcessedClauses), | 210 | return new Dnf(name, Collections.unmodifiableList(parameters), |
225 | Collections.unmodifiableList(functionalDependencies), | 211 | Collections.unmodifiableList(functionalDependencies), |
226 | Collections.unmodifiableList(postProcessedClauses)); | 212 | Collections.unmodifiableList(postProcessedClauses)); |
227 | } | 213 | } |
228 | 214 | ||
229 | private List<DnfClause> postProcessClauses() { | 215 | private List<DnfClause> postProcessClauses() { |
230 | var parameterSet = Collections.unmodifiableSet(new LinkedHashSet<>(parameters)); | 216 | var parameterInfoMap = getParameterInfoMap(); |
231 | var parameterWeights = getParameterWeights(); | ||
232 | var postProcessedClauses = new ArrayList<DnfClause>(clauses.size()); | 217 | var postProcessedClauses = new ArrayList<DnfClause>(clauses.size()); |
233 | for (var literals : clauses) { | 218 | for (var literals : clauses) { |
234 | var postProcessor = new ClausePostProcessor(parameterSet, parameterWeights, literals); | 219 | var postProcessor = new ClausePostProcessor(parameterInfoMap, literals); |
235 | var result = postProcessor.postProcessClause(); | 220 | var result = postProcessor.postProcessClause(); |
236 | if (result instanceof ClausePostProcessor.ClauseResult clauseResult) { | 221 | if (result instanceof ClausePostProcessor.ClauseResult clauseResult) { |
237 | postProcessedClauses.add(clauseResult.clause()); | 222 | postProcessedClauses.add(clauseResult.clause()); |
@@ -253,54 +238,14 @@ public final class DnfBuilder { | |||
253 | return postProcessedClauses; | 238 | return postProcessedClauses; |
254 | } | 239 | } |
255 | 240 | ||
256 | private Map<Variable, Integer> getParameterWeights() { | 241 | private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() { |
257 | var mutableParameterWeights = new HashMap<Variable, Integer>(); | 242 | var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>(); |
258 | int arity = parameters.size(); | 243 | int arity = parameters.size(); |
259 | for (int i = 0; i < arity; i++) { | 244 | for (int i = 0; i < arity; i++) { |
260 | mutableParameterWeights.put(parameters.get(i), i); | 245 | var parameter = parameters.get(i); |
261 | } | 246 | mutableParameterInfoMap.put(parameter.getVariable(), |
262 | return Collections.unmodifiableMap(mutableParameterWeights); | 247 | new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i)); |
263 | } | ||
264 | |||
265 | private List<SymbolicParameter> createParameterList(List<DnfClause> postProcessedClauses) { | ||
266 | var outputParameterVariables = new HashSet<>(parameters); | ||
267 | for (var clause : postProcessedClauses) { | ||
268 | outputParameterVariables.retainAll(clause.positiveVariables()); | ||
269 | } | ||
270 | var parameterList = new ArrayList<SymbolicParameter>(parameters.size()); | ||
271 | for (var parameter : parameters) { | ||
272 | ParameterDirection direction = getDirection(outputParameterVariables, parameter); | ||
273 | parameterList.add(new SymbolicParameter(parameter, direction)); | ||
274 | } | ||
275 | return Collections.unmodifiableList(parameterList); | ||
276 | } | ||
277 | |||
278 | private ParameterDirection getDirection(HashSet<Variable> outputParameterVariables, Variable parameter) { | ||
279 | var direction = getInferredDirection(outputParameterVariables, parameter); | ||
280 | var expectedDirection = directions.get(parameter); | ||
281 | if (expectedDirection == ParameterDirection.IN && direction == ParameterDirection.IN_OUT) { | ||
282 | // Parameters may be explicitly marked as {@code @In} even if they are bound in all clauses. | ||
283 | return expectedDirection; | ||
284 | } | ||
285 | if (expectedDirection != null && expectedDirection != direction) { | ||
286 | throw new IllegalArgumentException("Expected parameter %s to have direction %s, but got %s instead" | ||
287 | .formatted(parameter, expectedDirection, direction)); | ||
288 | } | ||
289 | return direction; | ||
290 | } | ||
291 | |||
292 | private static ParameterDirection getInferredDirection(HashSet<Variable> outputParameterVariables, | ||
293 | Variable parameter) { | ||
294 | if (outputParameterVariables.contains(parameter)) { | ||
295 | if (parameter instanceof NodeVariable) { | ||
296 | return ParameterDirection.IN_OUT; | ||
297 | } else if (parameter instanceof AnyDataVariable) { | ||
298 | return ParameterDirection.OUT; | ||
299 | } else { | ||
300 | throw new IllegalArgumentException("Unknown parameter: " + parameter); | ||
301 | } | ||
302 | } else { | ||
303 | return ParameterDirection.IN; | ||
304 | } | 248 | } |
249 | return Collections.unmodifiableMap(mutableParameterInfoMap); | ||
305 | } | 250 | } |
306 | } | 251 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java index 57cb8baf..6f253012 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java | |||
@@ -6,17 +6,20 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.literal.CallPolarity; | 8 | import tools.refinery.store.query.literal.CallPolarity; |
9 | import tools.refinery.store.query.term.*; | 9 | import tools.refinery.store.query.term.Aggregator; |
10 | import tools.refinery.store.query.term.AssignedValue; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
10 | 13 | ||
11 | import java.util.ArrayList; | 14 | import java.util.ArrayList; |
12 | import java.util.List; | 15 | import java.util.List; |
13 | import java.util.Objects; | 16 | import java.util.Objects; |
14 | 17 | ||
15 | public final class FunctionalQuery<T> implements Query<T> { | 18 | public final class FunctionalQuery<T> extends Query<T> { |
16 | private final Dnf dnf; | ||
17 | private final Class<T> type; | 19 | private final Class<T> type; |
18 | 20 | ||
19 | FunctionalQuery(Dnf dnf, Class<T> type) { | 21 | FunctionalQuery(Dnf dnf, Class<T> type) { |
22 | super(dnf); | ||
20 | var parameters = dnf.getSymbolicParameters(); | 23 | var parameters = dnf.getSymbolicParameters(); |
21 | int outputIndex = dnf.arity() - 1; | 24 | int outputIndex = dnf.arity() - 1; |
22 | for (int i = 0; i < outputIndex; i++) { | 25 | for (int i = 0; i < outputIndex; i++) { |
@@ -33,18 +36,12 @@ public final class FunctionalQuery<T> implements Query<T> { | |||
33 | throw new IllegalArgumentException("Expected parameter %s of %s to be %s, but got %s instead".formatted( | 36 | throw new IllegalArgumentException("Expected parameter %s of %s to be %s, but got %s instead".formatted( |
34 | outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); | 37 | outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); |
35 | } | 38 | } |
36 | this.dnf = dnf; | ||
37 | this.type = type; | 39 | this.type = type; |
38 | } | 40 | } |
39 | 41 | ||
40 | @Override | 42 | @Override |
41 | public String name() { | ||
42 | return dnf.name(); | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public int arity() { | 43 | public int arity() { |
47 | return dnf.arity() - 1; | 44 | return getDnf().arity() - 1; |
48 | } | 45 | } |
49 | 46 | ||
50 | @Override | 47 | @Override |
@@ -57,17 +54,12 @@ public final class FunctionalQuery<T> implements Query<T> { | |||
57 | return null; | 54 | return null; |
58 | } | 55 | } |
59 | 56 | ||
60 | @Override | ||
61 | public Dnf getDnf() { | ||
62 | return dnf; | ||
63 | } | ||
64 | |||
65 | public AssignedValue<T> call(List<NodeVariable> arguments) { | 57 | public AssignedValue<T> call(List<NodeVariable> arguments) { |
66 | return targetVariable -> { | 58 | return targetVariable -> { |
67 | var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1); | 59 | var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1); |
68 | argumentsWithTarget.addAll(arguments); | 60 | argumentsWithTarget.addAll(arguments); |
69 | argumentsWithTarget.add(targetVariable); | 61 | argumentsWithTarget.add(targetVariable); |
70 | return dnf.call(CallPolarity.POSITIVE, argumentsWithTarget); | 62 | return getDnf().call(CallPolarity.POSITIVE, argumentsWithTarget); |
71 | }; | 63 | }; |
72 | } | 64 | } |
73 | 65 | ||
@@ -81,7 +73,9 @@ public final class FunctionalQuery<T> implements Query<T> { | |||
81 | var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1); | 73 | var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1); |
82 | argumentsWithPlaceholder.addAll(arguments); | 74 | argumentsWithPlaceholder.addAll(arguments); |
83 | argumentsWithPlaceholder.add(placeholderVariable); | 75 | argumentsWithPlaceholder.add(placeholderVariable); |
84 | return dnf.aggregate(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable); | 76 | return getDnf() |
77 | .aggregate(placeholderVariable, aggregator, argumentsWithPlaceholder) | ||
78 | .toLiteral(targetVariable); | ||
85 | }; | 79 | }; |
86 | } | 80 | } |
87 | 81 | ||
@@ -93,17 +87,13 @@ public final class FunctionalQuery<T> implements Query<T> { | |||
93 | public boolean equals(Object o) { | 87 | public boolean equals(Object o) { |
94 | if (this == o) return true; | 88 | if (this == o) return true; |
95 | if (o == null || getClass() != o.getClass()) return false; | 89 | if (o == null || getClass() != o.getClass()) return false; |
90 | if (!super.equals(o)) return false; | ||
96 | FunctionalQuery<?> that = (FunctionalQuery<?>) o; | 91 | FunctionalQuery<?> that = (FunctionalQuery<?>) o; |
97 | return dnf.equals(that.dnf) && type.equals(that.type); | 92 | return Objects.equals(type, that.type); |
98 | } | 93 | } |
99 | 94 | ||
100 | @Override | 95 | @Override |
101 | public int hashCode() { | 96 | public int hashCode() { |
102 | return Objects.hash(dnf, type); | 97 | return Objects.hash(super.hashCode(), type); |
103 | } | ||
104 | |||
105 | @Override | ||
106 | public String toString() { | ||
107 | return dnf.toString(); | ||
108 | } | 98 | } |
109 | } | 99 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java index e94c0c6b..aaa52ce6 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java | |||
@@ -6,113 +6,158 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.dnf.callback.*; | 8 | import tools.refinery.store.query.dnf.callback.*; |
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
9 | import tools.refinery.store.query.term.Variable; | 10 | import tools.refinery.store.query.term.Variable; |
10 | 11 | ||
11 | public sealed interface Query<T> extends AnyQuery permits RelationalQuery, FunctionalQuery { | 12 | import java.util.Objects; |
12 | String OUTPUT_VARIABLE_NAME = "output"; | ||
13 | 13 | ||
14 | public abstract sealed class Query<T> implements AnyQuery permits FunctionalQuery, RelationalQuery { | ||
15 | private static final String OUTPUT_VARIABLE_NAME = "output"; | ||
16 | |||
17 | private final Dnf dnf; | ||
18 | |||
19 | protected Query(Dnf dnf) { | ||
20 | for (var parameter : dnf.getSymbolicParameters()) { | ||
21 | if (parameter.getDirection() != ParameterDirection.OUT) { | ||
22 | throw new IllegalArgumentException("Query parameter %s with direction %s is not allowed" | ||
23 | .formatted(parameter.getVariable(), parameter.getDirection())); | ||
24 | } | ||
25 | } | ||
26 | this.dnf = dnf; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public String name() { | ||
31 | return dnf.name(); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public Dnf getDnf() { | ||
36 | return dnf; | ||
37 | } | ||
38 | |||
39 | // Allow redeclaration of the method with refined return type. | ||
40 | @SuppressWarnings("squid:S3038") | ||
14 | @Override | 41 | @Override |
15 | Class<T> valueType(); | 42 | public abstract Class<T> valueType(); |
16 | 43 | ||
17 | T defaultValue(); | 44 | public abstract T defaultValue(); |
45 | |||
46 | @Override | ||
47 | public boolean equals(Object o) { | ||
48 | if (this == o) return true; | ||
49 | if (o == null || getClass() != o.getClass()) return false; | ||
50 | Query<?> that = (Query<?>) o; | ||
51 | return Objects.equals(dnf, that.dnf); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public int hashCode() { | ||
56 | return Objects.hash(dnf); | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public String toString() { | ||
61 | return dnf.toString(); | ||
62 | } | ||
18 | 63 | ||
19 | static QueryBuilder builder() { | 64 | public static QueryBuilder builder() { |
20 | return builder(null); | 65 | return builder(null); |
21 | } | 66 | } |
22 | 67 | ||
23 | static QueryBuilder builder(String name) { | 68 | public static QueryBuilder builder(String name) { |
24 | return new QueryBuilder(name); | 69 | return new QueryBuilder(name); |
25 | } | 70 | } |
26 | 71 | ||
27 | static RelationalQuery of(QueryCallback0 callback) { | 72 | public static RelationalQuery of(QueryCallback0 callback) { |
28 | return of(null, callback); | 73 | return of(null, callback); |
29 | } | 74 | } |
30 | 75 | ||
31 | static RelationalQuery of(String name, QueryCallback0 callback) { | 76 | public static RelationalQuery of(String name, QueryCallback0 callback) { |
32 | var builder = builder(name); | 77 | var builder = builder(name); |
33 | callback.accept(builder); | 78 | callback.accept(builder); |
34 | return builder.build(); | 79 | return builder.build(); |
35 | } | 80 | } |
36 | 81 | ||
37 | static RelationalQuery of(QueryCallback1 callback) { | 82 | public static RelationalQuery of(QueryCallback1 callback) { |
38 | return of(null, callback); | 83 | return of(null, callback); |
39 | } | 84 | } |
40 | 85 | ||
41 | static RelationalQuery of(String name, QueryCallback1 callback) { | 86 | public static RelationalQuery of(String name, QueryCallback1 callback) { |
42 | var builder = builder(name); | 87 | var builder = builder(name); |
43 | callback.accept(builder, builder.parameter("p1")); | 88 | callback.accept(builder, builder.parameter("p1")); |
44 | return builder.build(); | 89 | return builder.build(); |
45 | } | 90 | } |
46 | 91 | ||
47 | static RelationalQuery of(QueryCallback2 callback) { | 92 | public static RelationalQuery of(QueryCallback2 callback) { |
48 | return of(null, callback); | 93 | return of(null, callback); |
49 | } | 94 | } |
50 | 95 | ||
51 | static RelationalQuery of(String name, QueryCallback2 callback) { | 96 | public static RelationalQuery of(String name, QueryCallback2 callback) { |
52 | var builder = builder(name); | 97 | var builder = builder(name); |
53 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2")); | 98 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2")); |
54 | return builder.build(); | 99 | return builder.build(); |
55 | } | 100 | } |
56 | 101 | ||
57 | static RelationalQuery of(QueryCallback3 callback) { | 102 | public static RelationalQuery of(QueryCallback3 callback) { |
58 | return of(null, callback); | 103 | return of(null, callback); |
59 | } | 104 | } |
60 | 105 | ||
61 | static RelationalQuery of(String name, QueryCallback3 callback) { | 106 | public static RelationalQuery of(String name, QueryCallback3 callback) { |
62 | var builder = builder(name); | 107 | var builder = builder(name); |
63 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3")); | 108 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3")); |
64 | return builder.build(); | 109 | return builder.build(); |
65 | } | 110 | } |
66 | 111 | ||
67 | static RelationalQuery of(QueryCallback4 callback) { | 112 | public static RelationalQuery of(QueryCallback4 callback) { |
68 | return of(null, callback); | 113 | return of(null, callback); |
69 | } | 114 | } |
70 | 115 | ||
71 | static RelationalQuery of(String name, QueryCallback4 callback) { | 116 | public static RelationalQuery of(String name, QueryCallback4 callback) { |
72 | var builder = builder(name); | 117 | var builder = builder(name); |
73 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), | 118 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), |
74 | builder.parameter("p4")); | 119 | builder.parameter("p4")); |
75 | return builder.build(); | 120 | return builder.build(); |
76 | } | 121 | } |
77 | 122 | ||
78 | static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback0<T> callback) { | 123 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback0<T> callback) { |
79 | return of(null, type, callback); | 124 | return of(null, type, callback); |
80 | } | 125 | } |
81 | 126 | ||
82 | static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback0<T> callback) { | 127 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback0<T> callback) { |
83 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | 128 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); |
84 | var builder = builder(name).output(outputVariable); | 129 | var builder = builder(name).output(outputVariable); |
85 | callback.accept(builder, outputVariable); | 130 | callback.accept(builder, outputVariable); |
86 | return builder.build(); | 131 | return builder.build(); |
87 | } | 132 | } |
88 | 133 | ||
89 | static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback1<T> callback) { | 134 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback1<T> callback) { |
90 | return of(null, type, callback); | 135 | return of(null, type, callback); |
91 | } | 136 | } |
92 | 137 | ||
93 | static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback1<T> callback) { | 138 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback1<T> callback) { |
94 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | 139 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); |
95 | var builder = builder(name).output(outputVariable); | 140 | var builder = builder(name).output(outputVariable); |
96 | callback.accept(builder, builder.parameter("p1"), outputVariable); | 141 | callback.accept(builder, builder.parameter("p1"), outputVariable); |
97 | return builder.build(); | 142 | return builder.build(); |
98 | } | 143 | } |
99 | 144 | ||
100 | static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback2<T> callback) { | 145 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback2<T> callback) { |
101 | return of(null, type, callback); | 146 | return of(null, type, callback); |
102 | } | 147 | } |
103 | 148 | ||
104 | static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback2<T> callback) { | 149 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback2<T> callback) { |
105 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | 150 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); |
106 | var builder = builder(name).output(outputVariable); | 151 | var builder = builder(name).output(outputVariable); |
107 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), outputVariable); | 152 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), outputVariable); |
108 | return builder.build(); | 153 | return builder.build(); |
109 | } | 154 | } |
110 | 155 | ||
111 | static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback3<T> callback) { | 156 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback3<T> callback) { |
112 | return of(null, type, callback); | 157 | return of(null, type, callback); |
113 | } | 158 | } |
114 | 159 | ||
115 | static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback3<T> callback) { | 160 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback3<T> callback) { |
116 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | 161 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); |
117 | var builder = builder(name).output(outputVariable); | 162 | var builder = builder(name).output(outputVariable); |
118 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), | 163 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), |
@@ -120,11 +165,11 @@ public sealed interface Query<T> extends AnyQuery permits RelationalQuery, Funct | |||
120 | return builder.build(); | 165 | return builder.build(); |
121 | } | 166 | } |
122 | 167 | ||
123 | static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback4<T> callback) { | 168 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback4<T> callback) { |
124 | return of(null, type, callback); | 169 | return of(null, type, callback); |
125 | } | 170 | } |
126 | 171 | ||
127 | static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback4<T> callback) { | 172 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback4<T> callback) { |
128 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | 173 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); |
129 | var builder = builder(name).output(outputVariable); | 174 | var builder = builder(name).output(outputVariable); |
130 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), | 175 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java index c043a285..d34a7ace 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java | |||
@@ -12,12 +12,10 @@ import tools.refinery.store.query.term.NodeVariable; | |||
12 | 12 | ||
13 | import java.util.Collections; | 13 | import java.util.Collections; |
14 | import java.util.List; | 14 | import java.util.List; |
15 | import java.util.Objects; | ||
16 | |||
17 | public final class RelationalQuery implements Query<Boolean> { | ||
18 | private final Dnf dnf; | ||
19 | 15 | ||
16 | public final class RelationalQuery extends Query<Boolean> { | ||
20 | RelationalQuery(Dnf dnf) { | 17 | RelationalQuery(Dnf dnf) { |
18 | super(dnf); | ||
21 | for (var parameter : dnf.getSymbolicParameters()) { | 19 | for (var parameter : dnf.getSymbolicParameters()) { |
22 | var parameterType = parameter.tryGetType(); | 20 | var parameterType = parameter.tryGetType(); |
23 | if (parameterType.isPresent()) { | 21 | if (parameterType.isPresent()) { |
@@ -25,17 +23,11 @@ public final class RelationalQuery implements Query<Boolean> { | |||
25 | .formatted(parameter, dnf, parameterType.get().getName())); | 23 | .formatted(parameter, dnf, parameterType.get().getName())); |
26 | } | 24 | } |
27 | } | 25 | } |
28 | this.dnf = dnf; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public String name() { | ||
33 | return dnf.name(); | ||
34 | } | 26 | } |
35 | 27 | ||
36 | @Override | 28 | @Override |
37 | public int arity() { | 29 | public int arity() { |
38 | return dnf.arity(); | 30 | return getDnf().arity(); |
39 | } | 31 | } |
40 | 32 | ||
41 | @Override | 33 | @Override |
@@ -48,50 +40,27 @@ public final class RelationalQuery implements Query<Boolean> { | |||
48 | return false; | 40 | return false; |
49 | } | 41 | } |
50 | 42 | ||
51 | @Override | ||
52 | public Dnf getDnf() { | ||
53 | return dnf; | ||
54 | } | ||
55 | |||
56 | public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) { | 43 | public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) { |
57 | return dnf.call(polarity, Collections.unmodifiableList(arguments)); | 44 | return getDnf().call(polarity, Collections.unmodifiableList(arguments)); |
58 | } | 45 | } |
59 | 46 | ||
60 | public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) { | 47 | public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) { |
61 | return dnf.call(polarity, arguments); | 48 | return getDnf().call(polarity, arguments); |
62 | } | 49 | } |
63 | 50 | ||
64 | public CallLiteral call(NodeVariable... arguments) { | 51 | public CallLiteral call(NodeVariable... arguments) { |
65 | return dnf.call(arguments); | 52 | return getDnf().call(arguments); |
66 | } | 53 | } |
67 | 54 | ||
68 | public CallLiteral callTransitive(NodeVariable left, NodeVariable right) { | 55 | public CallLiteral callTransitive(NodeVariable left, NodeVariable right) { |
69 | return dnf.callTransitive(left, right); | 56 | return getDnf().callTransitive(left, right); |
70 | } | 57 | } |
71 | 58 | ||
72 | public AssignedValue<Integer> count(List<NodeVariable> arguments) { | 59 | public AssignedValue<Integer> count(List<NodeVariable> arguments) { |
73 | return dnf.count(Collections.unmodifiableList(arguments)); | 60 | return getDnf().count(Collections.unmodifiableList(arguments)); |
74 | } | 61 | } |
75 | 62 | ||
76 | public AssignedValue<Integer> count(NodeVariable... arguments) { | 63 | public AssignedValue<Integer> count(NodeVariable... arguments) { |
77 | return dnf.count(arguments); | 64 | return getDnf().count(arguments); |
78 | } | ||
79 | |||
80 | @Override | ||
81 | public boolean equals(Object o) { | ||
82 | if (this == o) return true; | ||
83 | if (o == null || getClass() != o.getClass()) return false; | ||
84 | RelationalQuery that = (RelationalQuery) o; | ||
85 | return dnf.equals(that.dnf); | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public int hashCode() { | ||
90 | return Objects.hash(dnf); | ||
91 | } | ||
92 | |||
93 | @Override | ||
94 | public String toString() { | ||
95 | return dnf.toString(); | ||
96 | } | 65 | } |
97 | } | 66 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java index e2f05bde..e0d3ba1f 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java | |||
@@ -23,10 +23,14 @@ public final class SymbolicParameter extends Parameter { | |||
23 | return variable; | 23 | return variable; |
24 | } | 24 | } |
25 | 25 | ||
26 | public boolean isUnifiable() { | ||
27 | return variable.isUnifiable(); | ||
28 | } | ||
29 | |||
26 | @Override | 30 | @Override |
27 | public String toString() { | 31 | public String toString() { |
28 | var direction = getDirection(); | 32 | var direction = getDirection(); |
29 | if (direction == ParameterDirection.IN_OUT) { | 33 | if (direction == ParameterDirection.OUT) { |
30 | return variable.toString(); | 34 | return variable.toString(); |
31 | } | 35 | } |
32 | return "%s %s".formatted(getDirection(), variable); | 36 | return "%s %s".formatted(getDirection(), variable); |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java index 55db04e0..ed7d3401 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java | |||
@@ -8,14 +8,16 @@ package tools.refinery.store.query.literal; | |||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
11 | import tools.refinery.store.query.term.Variable; | 12 | import tools.refinery.store.query.term.Variable; |
12 | 13 | ||
13 | import java.util.List; | 14 | import java.util.*; |
14 | import java.util.Objects; | ||
15 | 15 | ||
16 | public abstract class AbstractCallLiteral implements Literal { | 16 | public abstract class AbstractCallLiteral implements Literal { |
17 | private final Constraint target; | 17 | private final Constraint target; |
18 | private final List<Variable> arguments; | 18 | private final List<Variable> arguments; |
19 | private final Set<Variable> inArguments; | ||
20 | private final Set<Variable> outArguments; | ||
19 | 21 | ||
20 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { | 22 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { |
21 | int arity = target.arity(); | 23 | int arity = target.arity(); |
@@ -25,6 +27,8 @@ public abstract class AbstractCallLiteral implements Literal { | |||
25 | } | 27 | } |
26 | this.target = target; | 28 | this.target = target; |
27 | this.arguments = arguments; | 29 | this.arguments = arguments; |
30 | var mutableInArguments = new LinkedHashSet<Variable>(); | ||
31 | var mutableOutArguments = new LinkedHashSet<Variable>(); | ||
28 | var parameters = target.getParameters(); | 32 | var parameters = target.getParameters(); |
29 | for (int i = 0; i < arity; i++) { | 33 | for (int i = 0; i < arity; i++) { |
30 | var argument = arguments.get(i); | 34 | var argument = arguments.get(i); |
@@ -33,6 +37,36 @@ public abstract class AbstractCallLiteral implements Literal { | |||
33 | throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s" | 37 | throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s" |
34 | .formatted(i, target, parameter)); | 38 | .formatted(i, target, parameter)); |
35 | } | 39 | } |
40 | switch (parameter.getDirection()) { | ||
41 | case IN -> { | ||
42 | if (mutableOutArguments.remove(argument)) { | ||
43 | checkInOutUnifiable(argument); | ||
44 | } | ||
45 | mutableInArguments.add(argument); | ||
46 | } | ||
47 | case OUT -> { | ||
48 | if (mutableInArguments.contains(argument)) { | ||
49 | checkInOutUnifiable(argument); | ||
50 | } else if (!mutableOutArguments.add(argument)) { | ||
51 | checkDuplicateOutUnifiable(argument); | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | inArguments = Collections.unmodifiableSet(mutableInArguments); | ||
57 | outArguments = Collections.unmodifiableSet(mutableOutArguments); | ||
58 | } | ||
59 | |||
60 | private static void checkInOutUnifiable(Variable argument) { | ||
61 | if (!argument.isUnifiable()) { | ||
62 | throw new IllegalArgumentException("Arguments %s cannot appear with both %s and %s direction" | ||
63 | .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT)); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | private static void checkDuplicateOutUnifiable(Variable argument) { | ||
68 | if (!argument.isUnifiable()) { | ||
69 | throw new IllegalArgumentException("Arguments %s cannot be bound multiple times".formatted(argument)); | ||
36 | } | 70 | } |
37 | } | 71 | } |
38 | 72 | ||
@@ -44,6 +78,23 @@ public abstract class AbstractCallLiteral implements Literal { | |||
44 | return arguments; | 78 | return arguments; |
45 | } | 79 | } |
46 | 80 | ||
81 | protected Set<Variable> getArgumentsOfDirection(ParameterDirection direction) { | ||
82 | return switch (direction) { | ||
83 | case IN -> inArguments; | ||
84 | case OUT -> outArguments; | ||
85 | }; | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
90 | return getArgumentsOfDirection(ParameterDirection.IN); | ||
91 | } | ||
92 | |||
93 | @Override | ||
94 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
95 | return Set.of(); | ||
96 | } | ||
97 | |||
47 | @Override | 98 | @Override |
48 | public Literal substitute(Substitution substitution) { | 99 | public Literal substitute(Substitution substitution) { |
49 | var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList(); | 100 | var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList(); |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java index 51a33c1f..b2fec430 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java | |||
@@ -8,19 +8,16 @@ package tools.refinery.store.query.literal; | |||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.Aggregator; | 11 | import tools.refinery.store.query.term.*; |
12 | import tools.refinery.store.query.term.ConstantTerm; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | 12 | ||
16 | import java.util.List; | 13 | import java.util.List; |
17 | import java.util.Objects; | 14 | import java.util.Objects; |
15 | import java.util.Set; | ||
18 | 16 | ||
19 | public class AggregationLiteral<R, T> extends AbstractCallLiteral { | 17 | public class AggregationLiteral<R, T> extends AbstractCallLiteral { |
20 | private final DataVariable<R> resultVariable; | 18 | private final DataVariable<R> resultVariable; |
21 | private final DataVariable<T> inputVariable; | 19 | private final DataVariable<T> inputVariable; |
22 | private final Aggregator<R, T> aggregator; | 20 | private final Aggregator<R, T> aggregator; |
23 | private final VariableBindingSite variableBindingSite; | ||
24 | 21 | ||
25 | public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator, | 22 | public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator, |
26 | DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { | 23 | DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { |
@@ -29,6 +26,10 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
29 | throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( | 26 | throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( |
30 | inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); | 27 | inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); |
31 | } | 28 | } |
29 | if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { | ||
30 | throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list" | ||
31 | .formatted(inputVariable, ParameterDirection.OUT)); | ||
32 | } | ||
32 | if (!resultVariable.getType().equals(aggregator.getResultType())) { | 33 | if (!resultVariable.getType().equals(aggregator.getResultType())) { |
33 | throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( | 34 | throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( |
34 | resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); | 35 | resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); |
@@ -40,14 +41,6 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
40 | this.resultVariable = resultVariable; | 41 | this.resultVariable = resultVariable; |
41 | this.inputVariable = inputVariable; | 42 | this.inputVariable = inputVariable; |
42 | this.aggregator = aggregator; | 43 | this.aggregator = aggregator; |
43 | variableBindingSite = VariableBindingSite.builder() | ||
44 | .variable(resultVariable, VariableDirection.OUT) | ||
45 | .parameterList(false, target.getParameters(), arguments) | ||
46 | .build(); | ||
47 | if (variableBindingSite.getDirection(inputVariable) != VariableDirection.CLOSURE) { | ||
48 | throw new IllegalArgumentException(("Input variable %s must appear in the argument list as an output " + | ||
49 | "variable and should not be bound anywhere else").formatted(inputVariable)); | ||
50 | } | ||
51 | } | 44 | } |
52 | 45 | ||
53 | public DataVariable<R> getResultVariable() { | 46 | public DataVariable<R> getResultVariable() { |
@@ -63,8 +56,8 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
63 | } | 56 | } |
64 | 57 | ||
65 | @Override | 58 | @Override |
66 | public VariableBindingSite getVariableBindingSite() { | 59 | public Set<Variable> getOutputVariables() { |
67 | return variableBindingSite; | 60 | return Set.of(resultVariable); |
68 | } | 61 | } |
69 | 62 | ||
70 | @Override | 63 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java index 079ba6bb..dbf999a2 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java | |||
@@ -9,43 +9,38 @@ import tools.refinery.store.query.equality.LiteralEqualityHelper; | |||
9 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.DataVariable; | 10 | import tools.refinery.store.query.term.DataVariable; |
11 | import tools.refinery.store.query.term.Term; | 11 | import tools.refinery.store.query.term.Term; |
12 | import tools.refinery.store.query.term.Variable; | ||
12 | 13 | ||
14 | import java.util.Collections; | ||
13 | import java.util.Objects; | 15 | import java.util.Objects; |
16 | import java.util.Set; | ||
14 | 17 | ||
15 | public final class AssignLiteral<T> implements Literal { | 18 | public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal { |
16 | private final DataVariable<T> variable; | 19 | public AssignLiteral { |
17 | private final Term<T> term; | ||
18 | private final VariableBindingSite variableBindingSite; | ||
19 | |||
20 | public AssignLiteral(DataVariable<T> variable, Term<T> term) { | ||
21 | if (!term.getType().equals(variable.getType())) { | 20 | if (!term.getType().equals(variable.getType())) { |
22 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | 21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( |
23 | term, variable.getType().getName(), term.getType().getName())); | 22 | term, variable.getType().getName(), term.getType().getName())); |
24 | } | 23 | } |
25 | this.variable = variable; | ||
26 | this.term = term; | ||
27 | var inputVariables = term.getInputVariables(); | 24 | var inputVariables = term.getInputVariables(); |
28 | if (inputVariables.contains(variable)) { | 25 | if (inputVariables.contains(variable)) { |
29 | throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( | 26 | throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( |
30 | variable, term)); | 27 | variable, term)); |
31 | } | 28 | } |
32 | variableBindingSite = VariableBindingSite.builder() | ||
33 | .variable(variable, VariableDirection.OUT) | ||
34 | .variables(inputVariables, VariableDirection.IN) | ||
35 | .build(); | ||
36 | } | 29 | } |
37 | 30 | ||
38 | public DataVariable<T> getTargetVariable() { | 31 | @Override |
39 | return variable; | 32 | public Set<Variable> getOutputVariables() { |
33 | return Set.of(variable); | ||
40 | } | 34 | } |
41 | 35 | ||
42 | public Term<T> getTerm() { | 36 | @Override |
43 | return term; | 37 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { |
38 | return Collections.unmodifiableSet(term.getInputVariables()); | ||
44 | } | 39 | } |
45 | 40 | ||
46 | @Override | 41 | @Override |
47 | public VariableBindingSite getVariableBindingSite() { | 42 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { |
48 | return variableBindingSite; | 43 | return Set.of(); |
49 | } | 44 | } |
50 | 45 | ||
51 | @Override | 46 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java index 3dfb8902..1ca04c77 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java | |||
@@ -9,33 +9,36 @@ import tools.refinery.store.query.equality.LiteralEqualityHelper; | |||
9 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.ConstantTerm; | 10 | import tools.refinery.store.query.term.ConstantTerm; |
11 | import tools.refinery.store.query.term.Term; | 11 | import tools.refinery.store.query.term.Term; |
12 | import tools.refinery.store.query.term.Variable; | ||
12 | 13 | ||
14 | import java.util.Collections; | ||
13 | import java.util.Objects; | 15 | import java.util.Objects; |
16 | import java.util.Set; | ||
14 | 17 | ||
15 | public final class AssumeLiteral implements Literal { | 18 | public record AssumeLiteral(Term<Boolean> term) implements Literal { |
16 | private final Term<Boolean> term; | 19 | public AssumeLiteral { |
17 | private final VariableBindingSite variableBindingSite; | ||
18 | |||
19 | public AssumeLiteral(Term<Boolean> term) { | ||
20 | if (!term.getType().equals(Boolean.class)) { | 20 | if (!term.getType().equals(Boolean.class)) { |
21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | 21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( |
22 | term, Boolean.class.getName(), term.getType().getName())); | 22 | term, Boolean.class.getName(), term.getType().getName())); |
23 | } | 23 | } |
24 | this.term = term; | ||
25 | variableBindingSite = VariableBindingSite.builder() | ||
26 | .variables(term.getInputVariables(), VariableDirection.IN) | ||
27 | .build(); | ||
28 | } | 24 | } |
29 | 25 | ||
30 | public Term<Boolean> getTerm() { | 26 | @Override |
31 | return term; | 27 | public Set<Variable> getOutputVariables() { |
28 | return Set.of(); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
33 | return Collections.unmodifiableSet(term.getInputVariables()); | ||
32 | } | 34 | } |
33 | 35 | ||
34 | @Override | 36 | @Override |
35 | public VariableBindingSite getVariableBindingSite() { | 37 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { |
36 | return variableBindingSite; | 38 | return Set.of(); |
37 | } | 39 | } |
38 | 40 | ||
41 | |||
39 | @Override | 42 | @Override |
40 | public Literal substitute(Substitution substitution) { | 43 | public Literal substitute(Substitution substitution) { |
41 | return new AssumeLiteral(term.substitute(substitution)); | 44 | return new AssumeLiteral(term.substitute(substitution)); |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java index 736b3537..f312d202 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java | |||
@@ -7,6 +7,9 @@ package tools.refinery.store.query.literal; | |||
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Set; | ||
10 | 13 | ||
11 | public enum BooleanLiteral implements CanNegate<BooleanLiteral> { | 14 | public enum BooleanLiteral implements CanNegate<BooleanLiteral> { |
12 | TRUE(true), | 15 | TRUE(true), |
@@ -19,8 +22,18 @@ public enum BooleanLiteral implements CanNegate<BooleanLiteral> { | |||
19 | } | 22 | } |
20 | 23 | ||
21 | @Override | 24 | @Override |
22 | public VariableBindingSite getVariableBindingSite() { | 25 | public Set<Variable> getOutputVariables() { |
23 | return VariableBindingSite.EMPTY; | 26 | return Set.of(); |
27 | } | ||
28 | |||
29 | @Override | ||
30 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
31 | return Set.of(); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
36 | return Set.of(); | ||
24 | } | 37 | } |
25 | 38 | ||
26 | @Override | 39 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java index 4f755e95..27d8ad60 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java | |||
@@ -8,14 +8,13 @@ package tools.refinery.store.query.literal; | |||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
11 | import tools.refinery.store.query.term.Variable; | 12 | import tools.refinery.store.query.term.Variable; |
12 | 13 | ||
13 | import java.util.List; | 14 | import java.util.*; |
14 | import java.util.Objects; | ||
15 | 15 | ||
16 | public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { | 16 | public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { |
17 | private final CallPolarity polarity; | 17 | private final CallPolarity polarity; |
18 | private final VariableBindingSite variableBindingSite; | ||
19 | 18 | ||
20 | public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) { | 19 | public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) { |
21 | super(target, arguments); | 20 | super(target, arguments); |
@@ -30,9 +29,6 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate< | |||
30 | } | 29 | } |
31 | } | 30 | } |
32 | this.polarity = polarity; | 31 | this.polarity = polarity; |
33 | variableBindingSite = VariableBindingSite.builder() | ||
34 | .parameterList(polarity.isPositive(), parameters, arguments) | ||
35 | .build(); | ||
36 | } | 32 | } |
37 | 33 | ||
38 | public CallPolarity getPolarity() { | 34 | public CallPolarity getPolarity() { |
@@ -40,13 +36,16 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate< | |||
40 | } | 36 | } |
41 | 37 | ||
42 | @Override | 38 | @Override |
43 | public VariableBindingSite getVariableBindingSite() { | 39 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { |
44 | return variableBindingSite; | 40 | return new CallLiteral(polarity, getTarget(), substitutedArguments); |
45 | } | 41 | } |
46 | 42 | ||
47 | @Override | 43 | @Override |
48 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | 44 | public Set<Variable> getOutputVariables() { |
49 | return new CallLiteral(polarity, getTarget(), substitutedArguments); | 45 | if (polarity.isPositive()) { |
46 | return getArgumentsOfDirection(ParameterDirection.OUT); | ||
47 | } | ||
48 | return Set.of(); | ||
50 | } | 49 | } |
51 | 50 | ||
52 | @Override | 51 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java index 1bc14bab..73545620 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java | |||
@@ -8,31 +8,25 @@ package tools.refinery.store.query.literal; | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.NodeVariable; | 10 | import tools.refinery.store.query.term.NodeVariable; |
11 | import tools.refinery.store.query.term.Variable; | ||
11 | 12 | ||
12 | import java.util.Objects; | 13 | import java.util.Objects; |
14 | import java.util.Set; | ||
13 | 15 | ||
14 | public final class ConstantLiteral implements Literal { | 16 | public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal { |
15 | private final NodeVariable variable; | 17 | @Override |
16 | private final int nodeId; | 18 | public Set<Variable> getOutputVariables() { |
17 | private final VariableBindingSite variableBindingSite; | 19 | return Set.of(variable); |
18 | |||
19 | public ConstantLiteral(NodeVariable variable, int nodeId) { | ||
20 | this.variable = variable; | ||
21 | this.nodeId = nodeId; | ||
22 | variableBindingSite = VariableBindingSite.builder().variable(variable, VariableDirection.IN_OUT).build(); | ||
23 | } | ||
24 | |||
25 | public NodeVariable getVariable() { | ||
26 | return variable; | ||
27 | } | 20 | } |
28 | 21 | ||
29 | public int getNodeId() { | 22 | @Override |
30 | return nodeId; | 23 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { |
24 | return Set.of(); | ||
31 | } | 25 | } |
32 | 26 | ||
33 | @Override | 27 | @Override |
34 | public VariableBindingSite getVariableBindingSite() { | 28 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { |
35 | return variableBindingSite; | 29 | return Set.of(); |
36 | } | 30 | } |
37 | 31 | ||
38 | @Override | 32 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java index 0a47aa66..4d4749c8 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java | |||
@@ -14,10 +14,10 @@ import tools.refinery.store.query.term.int_.IntTerms; | |||
14 | 14 | ||
15 | import java.util.List; | 15 | import java.util.List; |
16 | import java.util.Objects; | 16 | import java.util.Objects; |
17 | import java.util.Set; | ||
17 | 18 | ||
18 | public class CountLiteral extends AbstractCallLiteral { | 19 | public class CountLiteral extends AbstractCallLiteral { |
19 | private final DataVariable<Integer> resultVariable; | 20 | private final DataVariable<Integer> resultVariable; |
20 | private final VariableBindingSite variableBindingSite; | ||
21 | 21 | ||
22 | public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { | 22 | public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { |
23 | super(target, arguments); | 23 | super(target, arguments); |
@@ -30,10 +30,6 @@ public class CountLiteral extends AbstractCallLiteral { | |||
30 | .formatted(resultVariable)); | 30 | .formatted(resultVariable)); |
31 | } | 31 | } |
32 | this.resultVariable = resultVariable; | 32 | this.resultVariable = resultVariable; |
33 | variableBindingSite = VariableBindingSite.builder() | ||
34 | .variable(resultVariable, VariableDirection.OUT) | ||
35 | .parameterList(false, target.getParameters(), arguments) | ||
36 | .build(); | ||
37 | } | 33 | } |
38 | 34 | ||
39 | public DataVariable<Integer> getResultVariable() { | 35 | public DataVariable<Integer> getResultVariable() { |
@@ -41,8 +37,8 @@ public class CountLiteral extends AbstractCallLiteral { | |||
41 | } | 37 | } |
42 | 38 | ||
43 | @Override | 39 | @Override |
44 | public VariableBindingSite getVariableBindingSite() { | 40 | public Set<Variable> getOutputVariables() { |
45 | return variableBindingSite; | 41 | return Set.of(resultVariable); |
46 | } | 42 | } |
47 | 43 | ||
48 | @Override | 44 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java index b36c0e40..28ba7625 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java | |||
@@ -8,41 +8,26 @@ package tools.refinery.store.query.literal; | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.NodeVariable; | 10 | import tools.refinery.store.query.term.NodeVariable; |
11 | import tools.refinery.store.query.term.Variable; | ||
11 | 12 | ||
12 | import java.util.Objects; | 13 | import java.util.Objects; |
14 | import java.util.Set; | ||
13 | 15 | ||
14 | public final class EquivalenceLiteral | 16 | public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) |
15 | implements CanNegate<EquivalenceLiteral> { | 17 | implements CanNegate<EquivalenceLiteral> { |
16 | private final boolean positive; | 18 | @Override |
17 | private final NodeVariable left; | 19 | public Set<Variable> getOutputVariables() { |
18 | private final NodeVariable right; | 20 | return Set.of(left); |
19 | private final VariableBindingSite variableBindingSite; | ||
20 | |||
21 | public EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) { | ||
22 | this.positive = positive; | ||
23 | this.left = left; | ||
24 | this.right = right; | ||
25 | variableBindingSite = VariableBindingSite.builder() | ||
26 | .variable(left, positive ? VariableDirection.IN_OUT : VariableDirection.IN) | ||
27 | .variable(right, VariableDirection.IN) | ||
28 | .build(); | ||
29 | } | ||
30 | |||
31 | public boolean isPositive() { | ||
32 | return positive; | ||
33 | } | ||
34 | |||
35 | public NodeVariable getLeft() { | ||
36 | return left; | ||
37 | } | 21 | } |
38 | 22 | ||
39 | public NodeVariable getRight() { | 23 | @Override |
40 | return right; | 24 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { |
25 | return Set.of(right); | ||
41 | } | 26 | } |
42 | 27 | ||
43 | @Override | 28 | @Override |
44 | public VariableBindingSite getVariableBindingSite() { | 29 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { |
45 | return variableBindingSite; | 30 | return Set.of(); |
46 | } | 31 | } |
47 | 32 | ||
48 | @Override | 33 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java index 10e4cffd..ce6c11fe 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java | |||
@@ -7,9 +7,16 @@ package tools.refinery.store.query.literal; | |||
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Set; | ||
10 | 13 | ||
11 | public interface Literal { | 14 | public interface Literal { |
12 | VariableBindingSite getVariableBindingSite(); | 15 | Set<Variable> getOutputVariables(); |
16 | |||
17 | Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause); | ||
18 | |||
19 | Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause); | ||
13 | 20 | ||
14 | Literal substitute(Substitution substitution); | 21 | Literal substitute(Substitution substitution); |
15 | 22 | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSite.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSite.java deleted file mode 100644 index 624af045..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSite.java +++ /dev/null | |||
@@ -1,64 +0,0 @@ | |||
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.literal; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | import java.util.Map; | ||
11 | import java.util.Set; | ||
12 | import java.util.stream.Stream; | ||
13 | |||
14 | public final class VariableBindingSite { | ||
15 | public static final VariableBindingSite EMPTY = new VariableBindingSite(Map.of()); | ||
16 | |||
17 | private final Map<Variable, VariableDirection> directionMap; | ||
18 | |||
19 | VariableBindingSite(Map<Variable, VariableDirection> directionMap) { | ||
20 | this.directionMap = directionMap; | ||
21 | } | ||
22 | |||
23 | public VariableDirection getDirection(Variable variable) { | ||
24 | var direction = directionMap.get(variable); | ||
25 | if (direction == null) { | ||
26 | throw new IllegalArgumentException("No such variable " + variable); | ||
27 | } | ||
28 | return direction; | ||
29 | } | ||
30 | |||
31 | public VariableDirection getDirection(Variable variable, Set<? extends Variable> positiveVariables) { | ||
32 | var direction = getDirection(variable); | ||
33 | return disambiguateDirection(direction, variable, positiveVariables); | ||
34 | } | ||
35 | |||
36 | public Stream<Variable> getVariablesWithDirection(VariableDirection direction) { | ||
37 | return directionMap.entrySet().stream() | ||
38 | .filter(pair -> pair.getValue() == direction) | ||
39 | .map(Map.Entry::getKey); | ||
40 | } | ||
41 | |||
42 | public Stream<Variable> getVariablesWithDirection(VariableDirection direction, | ||
43 | Set<? extends Variable> positiveVariables) { | ||
44 | if (direction == VariableDirection.NEUTRAL) { | ||
45 | throw new IllegalArgumentException("Use #getVariablesWithDirection(VariableDirection) if disambiguation " + | ||
46 | "of VariableDirection#NEUTRAL variables according to the containing DnfClose is not desired"); | ||
47 | } | ||
48 | return directionMap.entrySet().stream() | ||
49 | .filter(pair -> disambiguateDirection(pair.getValue(), pair.getKey(), positiveVariables) == direction) | ||
50 | .map(Map.Entry::getKey); | ||
51 | } | ||
52 | |||
53 | private VariableDirection disambiguateDirection(VariableDirection direction, Variable variable, | ||
54 | Set<? extends Variable> positiveVariables) { | ||
55 | if (direction != VariableDirection.NEUTRAL) { | ||
56 | return direction; | ||
57 | } | ||
58 | return positiveVariables.contains(variable) ? VariableDirection.IN : VariableDirection.CLOSURE; | ||
59 | } | ||
60 | |||
61 | public static VariableBindingSiteBuilder builder() { | ||
62 | return new VariableBindingSiteBuilder(); | ||
63 | } | ||
64 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSiteBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSiteBuilder.java deleted file mode 100644 index 873db1ec..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSiteBuilder.java +++ /dev/null | |||
@@ -1,137 +0,0 @@ | |||
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.literal; | ||
7 | |||
8 | import tools.refinery.store.query.exceptions.IncompatibleParameterDirectionException; | ||
9 | import tools.refinery.store.query.term.*; | ||
10 | |||
11 | import java.util.*; | ||
12 | |||
13 | public final class VariableBindingSiteBuilder { | ||
14 | private final Map<Variable, VariableDirection> directionMap = new LinkedHashMap<>(); | ||
15 | private final Set<Variable> nonUnifiableVariables = new HashSet<>(); | ||
16 | |||
17 | VariableBindingSiteBuilder() { | ||
18 | } | ||
19 | |||
20 | public VariableBindingSiteBuilder variable(Variable variable, VariableDirection direction) { | ||
21 | return variable(variable, direction, direction == VariableDirection.OUT); | ||
22 | } | ||
23 | |||
24 | public VariableBindingSiteBuilder variable(Variable variable, VariableDirection direction, boolean markAsUnique) { | ||
25 | validateUnique(direction, markAsUnique); | ||
26 | validateDirection(variable, direction); | ||
27 | boolean unique; | ||
28 | if (markAsUnique) { | ||
29 | nonUnifiableVariables.add(variable); | ||
30 | unique = true; | ||
31 | } else { | ||
32 | unique = nonUnifiableVariables.contains(variable); | ||
33 | } | ||
34 | directionMap.compute(variable, (ignored, oldValue) -> { | ||
35 | if (oldValue == null) { | ||
36 | return direction; | ||
37 | } | ||
38 | if (unique) { | ||
39 | throw new IllegalArgumentException("Duplicate binding for variable " + variable); | ||
40 | } | ||
41 | try { | ||
42 | return merge(oldValue, direction); | ||
43 | } catch (IncompatibleParameterDirectionException e) { | ||
44 | var message = "%s for variable %s".formatted(e.getMessage(), variable); | ||
45 | throw new IncompatibleParameterDirectionException(message, e); | ||
46 | } | ||
47 | }); | ||
48 | return this; | ||
49 | } | ||
50 | |||
51 | private static void validateUnique(VariableDirection direction, boolean markAsUnique) { | ||
52 | if (direction == VariableDirection.OUT && !markAsUnique) { | ||
53 | throw new IllegalArgumentException("OUT binding must be marked as non-unifiable"); | ||
54 | } | ||
55 | if (markAsUnique && (direction != VariableDirection.OUT && direction != VariableDirection.CLOSURE)) { | ||
56 | throw new IllegalArgumentException("Only OUT or CLOSURE binding may be marked as non-unifiable"); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | private static void validateDirection(Variable variable, VariableDirection direction) { | ||
61 | if (variable instanceof AnyDataVariable) { | ||
62 | if (direction == VariableDirection.IN_OUT) { | ||
63 | throw new IllegalArgumentException("%s direction is not supported for data variable %s" | ||
64 | .formatted(direction, variable)); | ||
65 | } | ||
66 | } else if (variable instanceof NodeVariable) { | ||
67 | if (direction == VariableDirection.OUT) { | ||
68 | throw new IllegalArgumentException("%s direction is not supported for node variable %s" | ||
69 | .formatted(direction, variable)); | ||
70 | } | ||
71 | } else { | ||
72 | throw new IllegalArgumentException("Unknown variable " + variable); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | private static VariableDirection merge(VariableDirection left, VariableDirection right) { | ||
77 | return switch (left) { | ||
78 | case IN_OUT -> switch (right) { | ||
79 | case IN_OUT -> VariableDirection.IN_OUT; | ||
80 | case OUT -> VariableDirection.OUT; | ||
81 | case IN, NEUTRAL -> VariableDirection.IN; | ||
82 | case CLOSURE -> throw incompatibleDirections(left, right); | ||
83 | }; | ||
84 | case OUT -> switch (right) { | ||
85 | case IN_OUT, OUT -> VariableDirection.OUT; | ||
86 | case IN, NEUTRAL, CLOSURE -> throw incompatibleDirections(left, right); | ||
87 | }; | ||
88 | case IN -> switch (right) { | ||
89 | case IN_OUT, NEUTRAL, IN -> VariableDirection.IN; | ||
90 | case OUT, CLOSURE -> throw incompatibleDirections(left, right); | ||
91 | }; | ||
92 | case NEUTRAL -> switch (right) { | ||
93 | case IN_OUT, IN -> VariableDirection.IN; | ||
94 | case NEUTRAL -> VariableDirection.NEUTRAL; | ||
95 | case CLOSURE -> VariableDirection.CLOSURE; | ||
96 | case OUT -> throw incompatibleDirections(left, right); | ||
97 | }; | ||
98 | case CLOSURE -> switch (right) { | ||
99 | case NEUTRAL, CLOSURE -> VariableDirection.CLOSURE; | ||
100 | case IN_OUT, IN, OUT -> throw incompatibleDirections(left, right); | ||
101 | }; | ||
102 | }; | ||
103 | } | ||
104 | |||
105 | private static RuntimeException incompatibleDirections(VariableDirection left, VariableDirection right) { | ||
106 | return new IncompatibleParameterDirectionException("Incompatible variable directions %s and %s" | ||
107 | .formatted(left, right)); | ||
108 | } | ||
109 | |||
110 | public VariableBindingSiteBuilder variables(Collection<? extends Variable> variables, VariableDirection direction) { | ||
111 | for (var variable : variables) { | ||
112 | variable(variable, direction); | ||
113 | } | ||
114 | return this; | ||
115 | } | ||
116 | |||
117 | public VariableBindingSiteBuilder parameterList(boolean positive, List<Parameter> parameters, | ||
118 | List<Variable> arguments) { | ||
119 | int arity = parameters.size(); | ||
120 | if (arity != arguments.size()) { | ||
121 | throw new IllegalArgumentException("Got %d arguments for %d parameters" | ||
122 | .formatted(arguments.size(), arity)); | ||
123 | } | ||
124 | for (int i = 0; i < arity; i++) { | ||
125 | var argument = arguments.get(i); | ||
126 | var parameter = parameters.get(i); | ||
127 | var parameterDirection = parameter.getDirection(); | ||
128 | var direction = VariableDirection.from(positive, parameterDirection); | ||
129 | variable(argument, direction, parameterDirection == ParameterDirection.OUT); | ||
130 | } | ||
131 | return this; | ||
132 | } | ||
133 | |||
134 | public VariableBindingSite build() { | ||
135 | return new VariableBindingSite(directionMap); | ||
136 | } | ||
137 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableDirection.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableDirection.java deleted file mode 100644 index 0b7a2960..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableDirection.java +++ /dev/null | |||
@@ -1,82 +0,0 @@ | |||
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.literal; | ||
7 | |||
8 | import tools.refinery.store.query.term.ParameterDirection; | ||
9 | |||
10 | /** | ||
11 | * Directions of the flow of a variable to ro from a clause. | ||
12 | * <p> | ||
13 | * During the evaluation of a clause | ||
14 | * <ol> | ||
15 | * <li>reads already bound {@code IN} variables,</li> | ||
16 | * <li>enumerates over all possible bindings of {@code CLOSURE} variables, and</li> | ||
17 | * <li> | ||
18 | * produces bindings for | ||
19 | * <ul> | ||
20 | * <li>{@code IN_OUT} variables that may already be bound in clause (if they already have a binding, | ||
21 | * the existing values are compared to the new binding by {@link Object#equals(Object)}), and</li> | ||
22 | * <li>{@code OUT} variables that must not be already bound in the clause (because comparison by | ||
23 | * equality wouldn't produce an appropriate join).</li> | ||
24 | * </ul> | ||
25 | * </li> | ||
26 | * </ol> | ||
27 | * Variables marked as {@code NEUTRAL} may act as {@code IN} or {@code CLOSURE} depending on whether they have an | ||
28 | * existing binding that can be read. | ||
29 | */ | ||
30 | public enum VariableDirection { | ||
31 | /** | ||
32 | * Binds a node variable or check equality with a node variable. | ||
33 | * <p> | ||
34 | * This is the usual direction for positive constraints on nodes. A data variable may have multiple {@code IN_OUT} | ||
35 | * bindings, even on the same parameter list. | ||
36 | * <p> | ||
37 | * Cannot be used for data variables. | ||
38 | */ | ||
39 | IN_OUT, | ||
40 | |||
41 | /** | ||
42 | * Binds a data variable. | ||
43 | * <p> | ||
44 | * A single variable must have at most one {@code OUT} binding. A variable with a {@code OUT} binding cannot | ||
45 | * appear in any other place in a parameter list. | ||
46 | * <p> | ||
47 | * Cannot be used for node variables. | ||
48 | */ | ||
49 | OUT, | ||
50 | |||
51 | /** | ||
52 | * Takes an already bound variable. | ||
53 | * <p> | ||
54 | * May be used with node or data variables. An {@code IN_OUT} or {@code OUT} binding on the same parameter list | ||
55 | * cannot satisfy the {@code IN} binding, because it might introduce a (non-monotonic) circular dependency. | ||
56 | */ | ||
57 | IN, | ||
58 | |||
59 | /** | ||
60 | * Either takes a bound data variable or enumerates all possible data variable bindings. | ||
61 | * <p> | ||
62 | * Cannot be used for data variables. | ||
63 | */ | ||
64 | NEUTRAL, | ||
65 | |||
66 | /** | ||
67 | * Enumerates over all possible data variable bindings. | ||
68 | * <p> | ||
69 | * May be used with node or data variables. The variable may not appear in any other parameter list. A data | ||
70 | * variable may only appear once in the parameter list, but node variables can appear multiple times to form | ||
71 | * diagonal constraints. | ||
72 | */ | ||
73 | CLOSURE; | ||
74 | |||
75 | public static VariableDirection from(boolean positive, ParameterDirection parameterDirection) { | ||
76 | return switch (parameterDirection) { | ||
77 | case IN_OUT -> positive ? IN_OUT : NEUTRAL; | ||
78 | case OUT -> positive ? OUT : CLOSURE; | ||
79 | case IN -> IN; | ||
80 | }; | ||
81 | } | ||
82 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java index 5864ee56..192c39c5 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java | |||
@@ -37,6 +37,11 @@ public abstract sealed class AnyDataVariable extends Variable implements AnyTerm | |||
37 | } | 37 | } |
38 | 38 | ||
39 | @Override | 39 | @Override |
40 | public boolean isUnifiable() { | ||
41 | return false; | ||
42 | } | ||
43 | |||
44 | @Override | ||
40 | public abstract AnyDataVariable renew(@Nullable String name); | 45 | public abstract AnyDataVariable renew(@Nullable String name); |
41 | 46 | ||
42 | @Override | 47 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java index 37f9d477..a2f3261f 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java | |||
@@ -22,6 +22,11 @@ public final class NodeVariable extends Variable { | |||
22 | } | 22 | } |
23 | 23 | ||
24 | @Override | 24 | @Override |
25 | public boolean isUnifiable() { | ||
26 | return true; | ||
27 | } | ||
28 | |||
29 | @Override | ||
25 | public NodeVariable renew(@Nullable String name) { | 30 | public NodeVariable renew(@Nullable String name) { |
26 | return Variable.of(name); | 31 | return Variable.of(name); |
27 | } | 32 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java index 351a353d..0fe297ab 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java | |||
@@ -9,7 +9,7 @@ import java.util.Objects; | |||
9 | import java.util.Optional; | 9 | import java.util.Optional; |
10 | 10 | ||
11 | public class Parameter { | 11 | public class Parameter { |
12 | public static final Parameter NODE_IN_OUT = new Parameter(null, ParameterDirection.IN_OUT); | 12 | public static final Parameter NODE_IN_OUT = new Parameter(null, ParameterDirection.OUT); |
13 | 13 | ||
14 | private final Class<?> dataType; | 14 | private final Class<?> dataType; |
15 | private final ParameterDirection direction; | 15 | private final ParameterDirection direction; |
@@ -17,13 +17,6 @@ public class Parameter { | |||
17 | public Parameter(Class<?> dataType, ParameterDirection direction) { | 17 | public Parameter(Class<?> dataType, ParameterDirection direction) { |
18 | this.dataType = dataType; | 18 | this.dataType = dataType; |
19 | this.direction = direction; | 19 | this.direction = direction; |
20 | if (isDataVariable()) { | ||
21 | if (direction == ParameterDirection.IN_OUT) { | ||
22 | throw new IllegalArgumentException("IN_OUT direction is not supported for data parameters"); | ||
23 | } | ||
24 | } else if (direction == ParameterDirection.OUT) { | ||
25 | throw new IllegalArgumentException("OUT direction is not supported for node parameters"); | ||
26 | } | ||
27 | } | 20 | } |
28 | 21 | ||
29 | public boolean isNodeVariable() { | 22 | public boolean isNodeVariable() { |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java index 652208aa..cd0739be 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java | |||
@@ -6,7 +6,6 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | public enum ParameterDirection { | 8 | public enum ParameterDirection { |
9 | IN_OUT("@InOut"), | ||
10 | OUT("@Out"), | 9 | OUT("@Out"), |
11 | IN("@In"); | 10 | IN("@In"); |
12 | 11 | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java index 869120fa..a0268c8e 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java | |||
@@ -38,6 +38,8 @@ public abstract sealed class Variable permits AnyDataVariable, NodeVariable { | |||
38 | return uniqueName; | 38 | return uniqueName; |
39 | } | 39 | } |
40 | 40 | ||
41 | public abstract boolean isUnifiable(); | ||
42 | |||
41 | public abstract Variable renew(@Nullable String name); | 43 | public abstract Variable renew(@Nullable String name); |
42 | 44 | ||
43 | public abstract Variable renew(); | 45 | public abstract Variable renew(); |
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java index 4edea401..687b06db 100644 --- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java | |||
@@ -10,6 +10,7 @@ import org.junit.jupiter.params.ParameterizedTest; | |||
10 | import org.junit.jupiter.params.provider.CsvSource; | 10 | import org.junit.jupiter.params.provider.CsvSource; |
11 | import tools.refinery.store.query.literal.BooleanLiteral; | 11 | import tools.refinery.store.query.literal.BooleanLiteral; |
12 | import tools.refinery.store.query.term.NodeVariable; | 12 | import tools.refinery.store.query.term.NodeVariable; |
13 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | 14 | import tools.refinery.store.query.term.Variable; |
14 | import tools.refinery.store.query.term.bool.BoolTerms; | 15 | import tools.refinery.store.query.term.bool.BoolTerms; |
15 | import tools.refinery.store.query.view.KeyOnlyView; | 16 | import tools.refinery.store.query.view.KeyOnlyView; |
@@ -28,7 +29,7 @@ class DnfBuilderLiteralEliminationTest { | |||
28 | private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend); | 29 | private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend); |
29 | private final NodeVariable p = Variable.of("p"); | 30 | private final NodeVariable p = Variable.of("p"); |
30 | private final NodeVariable q = Variable.of("q"); | 31 | private final NodeVariable q = Variable.of("q"); |
31 | private final Dnf trueDnf = Dnf.builder().parameter(p).clause().build(); | 32 | private final Dnf trueDnf = Dnf.builder().parameter(p, ParameterDirection.IN).clause().build(); |
32 | private final Dnf falseDnf = Dnf.builder().parameter(p).build(); | 33 | private final Dnf falseDnf = Dnf.builder().parameter(p).build(); |
33 | 34 | ||
34 | @Test | 35 | @Test |
@@ -84,11 +85,11 @@ class DnfBuilderLiteralEliminationTest { | |||
84 | @Test | 85 | @Test |
85 | void alwaysTrueTest() { | 86 | void alwaysTrueTest() { |
86 | var actual = Dnf.builder() | 87 | var actual = Dnf.builder() |
87 | .parameters(p, q) | 88 | .parameters(List.of(p, q), ParameterDirection.IN) |
88 | .clause(friendView.call(p, q)) | 89 | .clause(friendView.call(p, q)) |
89 | .clause(BooleanLiteral.TRUE) | 90 | .clause(BooleanLiteral.TRUE) |
90 | .build(); | 91 | .build(); |
91 | var expected = Dnf.builder().parameters(p, q).clause().build(); | 92 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); |
92 | 93 | ||
93 | assertThat(actual, structurallyEqualTo(expected)); | 94 | assertThat(actual, structurallyEqualTo(expected)); |
94 | } | 95 | } |
@@ -130,11 +131,11 @@ class DnfBuilderLiteralEliminationTest { | |||
130 | @Test | 131 | @Test |
131 | void alwaysTrueDnfTest() { | 132 | void alwaysTrueDnfTest() { |
132 | var actual = Dnf.builder() | 133 | var actual = Dnf.builder() |
133 | .parameters(p, q) | 134 | .parameters(List.of(p, q), ParameterDirection.IN) |
134 | .clause(friendView.call(p, q)) | 135 | .clause(friendView.call(p, q)) |
135 | .clause(trueDnf.call(q)) | 136 | .clause(trueDnf.call(q)) |
136 | .build(); | 137 | .build(); |
137 | var expected = Dnf.builder().parameters(p, q).clause().build(); | 138 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); |
138 | 139 | ||
139 | assertThat(actual, structurallyEqualTo(expected)); | 140 | assertThat(actual, structurallyEqualTo(expected)); |
140 | } | 141 | } |
@@ -176,11 +177,11 @@ class DnfBuilderLiteralEliminationTest { | |||
176 | @Test | 177 | @Test |
177 | void alwaysNotFalseDnfTest() { | 178 | void alwaysNotFalseDnfTest() { |
178 | var actual = Dnf.builder() | 179 | var actual = Dnf.builder() |
179 | .parameters(p, q) | 180 | .parameters(List.of(p, q), ParameterDirection.IN) |
180 | .clause(friendView.call(p, q)) | 181 | .clause(friendView.call(p, q)) |
181 | .clause(not(falseDnf.call(q))) | 182 | .clause(not(falseDnf.call(q))) |
182 | .build(); | 183 | .build(); |
183 | var expected = Dnf.builder().parameters(p, q).clause().build(); | 184 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); |
184 | 185 | ||
185 | assertThat(actual, structurallyEqualTo(expected)); | 186 | assertThat(actual, structurallyEqualTo(expected)); |
186 | } | 187 | } |
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java index 2e93d78a..63310a78 100644 --- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
9 | import tools.refinery.store.query.term.Variable; | 10 | import tools.refinery.store.query.term.Variable; |
10 | import tools.refinery.store.query.view.KeyOnlyView; | 11 | import tools.refinery.store.query.view.KeyOnlyView; |
11 | import tools.refinery.store.representation.Symbol; | 12 | import tools.refinery.store.representation.Symbol; |
@@ -39,7 +40,7 @@ class DnfToDefinitionStringTest { | |||
39 | @Test | 40 | @Test |
40 | void emptyClauseTest() { | 41 | void emptyClauseTest() { |
41 | var p = Variable.of("p"); | 42 | var p = Variable.of("p"); |
42 | var dnf = Dnf.builder("Example").parameter(p).clause().build(); | 43 | var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build(); |
43 | 44 | ||
44 | assertThat(dnf.toDefinitionString(), is(""" | 45 | assertThat(dnf.toDefinitionString(), is(""" |
45 | pred Example(@In p) <-> | 46 | pred Example(@In p) <-> |
@@ -67,10 +68,13 @@ class DnfToDefinitionStringTest { | |||
67 | var q = Variable.of("q"); | 68 | var q = Variable.of("q"); |
68 | var friend = new Symbol<>("friend", 2, Boolean.class, false); | 69 | var friend = new Symbol<>("friend", 2, Boolean.class, false); |
69 | var friendView = new KeyOnlyView<>(friend); | 70 | var friendView = new KeyOnlyView<>(friend); |
70 | var dnf = Dnf.builder("Example").parameter(p).clause(not(friendView.call(p, q))).build(); | 71 | var dnf = Dnf.builder("Example") |
72 | .parameter(p, ParameterDirection.IN) | ||
73 | .clause(not(friendView.call(p, q))) | ||
74 | .build(); | ||
71 | 75 | ||
72 | assertThat(dnf.toDefinitionString(), is(""" | 76 | assertThat(dnf.toDefinitionString(), is(""" |
73 | pred Example(p) <-> | 77 | pred Example(@In p) <-> |
74 | !(@RelationView("key") friend(p, q)). | 78 | !(@RelationView("key") friend(p, q)). |
75 | """)); | 79 | """)); |
76 | } | 80 | } |