aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-05-26 22:24:40 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-05-26 22:24:40 +0200
commitd2348a15846ad861fc58b018f50a502a288bfcec (patch)
tree3a03b9eb35e9f90fd7f0e669ed00bf3aa8636982
parentfeat: count and aggregation literal reduction (diff)
downloadrefinery-d2348a15846ad861fc58b018f50a502a288bfcec.tar.gz
refinery-d2348a15846ad861fc58b018f50a502a288bfcec.tar.zst
refinery-d2348a15846ad861fc58b018f50a502a288bfcec.zip
refactor: simplified Dnf parameter directions
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java19
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java125
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java145
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java38
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java97
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java55
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java37
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSite.java64
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSiteBuilder.java137
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableDirection.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java1
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java2
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java15
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java10
28 files changed, 392 insertions, 686 deletions
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
index ec880435..5b0ea61d 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
@@ -88,8 +88,7 @@ public class Dnf2PQuery {
88 List<PParameter> parameterList = new ArrayList<>(); 88 List<PParameter> parameterList = new ArrayList<>();
89 for (var parameter : dnfQuery.getSymbolicParameters()) { 89 for (var parameter : dnfQuery.getSymbolicParameters()) {
90 var direction = switch (parameter.getDirection()) { 90 var direction = switch (parameter.getDirection()) {
91 case IN_OUT -> PParameterDirection.INOUT; 91 case OUT -> parameter.isUnifiable() ? PParameterDirection.INOUT : PParameterDirection.OUT;
92 case OUT -> PParameterDirection.OUT;
93 case IN -> throw new IllegalArgumentException("Query %s with input parameter %s is not supported" 92 case IN -> throw new IllegalArgumentException("Query %s with input parameter %s is not supported"
94 .formatted(dnfQuery, parameter.getVariable())); 93 .formatted(dnfQuery, parameter.getVariable()));
95 }; 94 };
@@ -154,9 +153,9 @@ public class Dnf2PQuery {
154 } 153 }
155 154
156 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) { 155 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) {
157 PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.getLeft().getUniqueName()); 156 PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.left().getUniqueName());
158 PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.getRight().getUniqueName()); 157 PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.right().getUniqueName());
159 if (equivalenceLiteral.isPositive()) { 158 if (equivalenceLiteral.positive()) {
160 new Equality(body, varSource, varTarget); 159 new Equality(body, varSource, varTarget);
161 } else { 160 } else {
162 new Inequality(body, varSource, varTarget); 161 new Inequality(body, varSource, varTarget);
@@ -213,13 +212,13 @@ public class Dnf2PQuery {
213 } 212 }
214 213
215 private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) { 214 private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) {
216 var variable = body.getOrCreateVariableByName(constantLiteral.getVariable().getUniqueName()); 215 var variable = body.getOrCreateVariableByName(constantLiteral.variable().getUniqueName());
217 new ConstantValue(body, variable, constantLiteral.getNodeId()); 216 new ConstantValue(body, variable, constantLiteral.nodeId());
218 } 217 }
219 218
220 private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) { 219 private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) {
221 var variable = body.getOrCreateVariableByName(assignLiteral.getTargetVariable().getUniqueName()); 220 var variable = body.getOrCreateVariableByName(assignLiteral.variable().getUniqueName());
222 var term = assignLiteral.getTerm(); 221 var term = assignLiteral.term();
223 if (term instanceof ConstantTerm<T> constantTerm) { 222 if (term instanceof ConstantTerm<T> constantTerm) {
224 new ConstantValue(body, variable, constantTerm.getValue()); 223 new ConstantValue(body, variable, constantTerm.getValue());
225 } else { 224 } else {
@@ -229,7 +228,7 @@ public class Dnf2PQuery {
229 } 228 }
230 229
231 private void translateAssumeLiteral(AssumeLiteral assumeLiteral, PBody body) { 230 private void translateAssumeLiteral(AssumeLiteral assumeLiteral, PBody body) {
232 var evaluator = new AssumptionEvaluator(assumeLiteral.getTerm()); 231 var evaluator = new AssumptionEvaluator(assumeLiteral.term());
233 new ExpressionEvaluation(body, evaluator, null); 232 new ExpressionEvaluation(body, evaluator, null);
234 } 233 }
235 234
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
index 46ce37b4..a9a2f71c 100644
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
@@ -23,7 +23,6 @@ import tools.refinery.store.tuple.Tuple;
23import java.util.List; 23import java.util.List;
24import java.util.Map; 24import java.util.Map;
25 25
26import static org.junit.jupiter.api.Assertions.assertThrows;
27import static tools.refinery.store.query.literal.Literals.assume; 26import static tools.refinery.store.query.literal.Literals.assume;
28import static tools.refinery.store.query.literal.Literals.not; 27import static tools.refinery.store.query.literal.Literals.not;
29import static tools.refinery.store.query.term.int_.IntTerms.constant; 28import static tools.refinery.store.query.term.int_.IntTerms.constant;
@@ -707,14 +706,4 @@ class QueryTest {
707 queryEngine.flushChanges(); 706 queryEngine.flushChanges();
708 assertResults(Map.of(), predicateResultSet); 707 assertResults(Map.of(), predicateResultSet);
709 } 708 }
710
711 @Test
712 void alwaysTrueTest() {
713 var p1 = Variable.of("p1");
714 var predicate = Query.builder("AlwaysTrue").parameters(p1).clause().build();
715
716 var queryBuilder = ViatraModelQueryAdapter.builder();
717
718 assertThrows(IllegalArgumentException.class, () -> queryBuilder.queries(predicate));
719 }
720} 709}
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;
9import tools.refinery.store.query.literal.BooleanLiteral; 9import tools.refinery.store.query.literal.BooleanLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral; 10import tools.refinery.store.query.literal.EquivalenceLiteral;
11import tools.refinery.store.query.literal.Literal; 11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.literal.VariableDirection;
13import tools.refinery.store.query.substitution.MapBasedSubstitution; 12import tools.refinery.store.query.substitution.MapBasedSubstitution;
14import tools.refinery.store.query.substitution.StatelessSubstitution; 13import tools.refinery.store.query.substitution.StatelessSubstitution;
15import tools.refinery.store.query.substitution.Substitution; 14import tools.refinery.store.query.substitution.Substitution;
16import tools.refinery.store.query.term.NodeVariable; 15import tools.refinery.store.query.term.NodeVariable;
16import tools.refinery.store.query.term.ParameterDirection;
17import tools.refinery.store.query.term.Variable; 17import tools.refinery.store.query.term.Variable;
18 18
19import java.util.*; 19import java.util.*;
20import java.util.function.Function; 20import java.util.function.Function;
21import java.util.stream.Collectors;
22 21
23class ClausePostProcessor { 22class 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
8import tools.refinery.store.query.dnf.callback.*; 8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal; 9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.*; 10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
11 14
12import java.util.*; 15import java.util.*;
13 16
14@SuppressWarnings("UnusedReturnValue") 17@SuppressWarnings("UnusedReturnValue")
15public final class DnfBuilder { 18public 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 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.literal.CallPolarity; 8import tools.refinery.store.query.literal.CallPolarity;
9import tools.refinery.store.query.term.*; 9import tools.refinery.store.query.term.Aggregator;
10import tools.refinery.store.query.term.AssignedValue;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.Variable;
10 13
11import java.util.ArrayList; 14import java.util.ArrayList;
12import java.util.List; 15import java.util.List;
13import java.util.Objects; 16import java.util.Objects;
14 17
15public final class FunctionalQuery<T> implements Query<T> { 18public 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 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.dnf.callback.*; 8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.term.ParameterDirection;
9import tools.refinery.store.query.term.Variable; 10import tools.refinery.store.query.term.Variable;
10 11
11public sealed interface Query<T> extends AnyQuery permits RelationalQuery, FunctionalQuery { 12import java.util.Objects;
12 String OUTPUT_VARIABLE_NAME = "output";
13 13
14public 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
13import java.util.Collections; 13import java.util.Collections;
14import java.util.List; 14import java.util.List;
15import java.util.Objects;
16
17public final class RelationalQuery implements Query<Boolean> {
18 private final Dnf dnf;
19 15
16public 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;
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
12 13
13import java.util.List; 14import java.util.*;
14import java.util.Objects;
15 15
16public abstract class AbstractCallLiteral implements Literal { 16public 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;
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.Aggregator; 11import tools.refinery.store.query.term.*;
12import tools.refinery.store.query.term.ConstantTerm;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15 12
16import java.util.List; 13import java.util.List;
17import java.util.Objects; 14import java.util.Objects;
15import java.util.Set;
18 16
19public class AggregationLiteral<R, T> extends AbstractCallLiteral { 17public 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;
9import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.DataVariable; 10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Term; 11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
12 13
14import java.util.Collections;
13import java.util.Objects; 15import java.util.Objects;
16import java.util.Set;
14 17
15public final class AssignLiteral<T> implements Literal { 18public 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;
9import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.ConstantTerm; 10import tools.refinery.store.query.term.ConstantTerm;
11import tools.refinery.store.query.term.Term; 11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
12 13
14import java.util.Collections;
13import java.util.Objects; 15import java.util.Objects;
16import java.util.Set;
14 17
15public final class AssumeLiteral implements Literal { 18public 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
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
10 13
11public enum BooleanLiteral implements CanNegate<BooleanLiteral> { 14public 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;
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
12 13
13import java.util.List; 14import java.util.*;
14import java.util.Objects;
15 15
16public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { 16public 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;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable; 10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
11 12
12import java.util.Objects; 13import java.util.Objects;
14import java.util.Set;
13 15
14public final class ConstantLiteral implements Literal { 16public 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
15import java.util.List; 15import java.util.List;
16import java.util.Objects; 16import java.util.Objects;
17import java.util.Set;
17 18
18public class CountLiteral extends AbstractCallLiteral { 19public 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;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable; 10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
11 12
12import java.util.Objects; 13import java.util.Objects;
14import java.util.Set;
13 15
14public final class EquivalenceLiteral 16public 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
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
10 13
11public interface Literal { 14public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Map;
11import java.util.Set;
12import java.util.stream.Stream;
13
14public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.exceptions.IncompatibleParameterDirectionException;
9import tools.refinery.store.query.term.*;
10
11import java.util.*;
12
13public 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 */
6package tools.refinery.store.query.literal;
7
8import 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 */
30public 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;
9import java.util.Optional; 9import java.util.Optional;
10 10
11public class Parameter { 11public 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 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8public enum ParameterDirection { 8public 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;
10import org.junit.jupiter.params.provider.CsvSource; 10import org.junit.jupiter.params.provider.CsvSource;
11import tools.refinery.store.query.literal.BooleanLiteral; 11import tools.refinery.store.query.literal.BooleanLiteral;
12import tools.refinery.store.query.term.NodeVariable; 12import tools.refinery.store.query.term.NodeVariable;
13import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.term.bool.BoolTerms; 15import tools.refinery.store.query.term.bool.BoolTerms;
15import tools.refinery.store.query.view.KeyOnlyView; 16import 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 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.ParameterDirection;
9import tools.refinery.store.query.term.Variable; 10import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.view.KeyOnlyView; 11import tools.refinery.store.query.view.KeyOnlyView;
11import tools.refinery.store.representation.Symbol; 12import 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 }