aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-05-01 23:53:01 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-05-03 15:51:36 +0200
commitf3e42e7a09140fb68af534aca3e8df02c6c46ec7 (patch)
tree29be3e8c8840b5bca10c8c15fc6673cc23fe6814 /subprojects/store-query
parentrefactor: avoid unnecessary Literal substitutions (diff)
downloadrefinery-f3e42e7a09140fb68af534aca3e8df02c6c46ec7.tar.gz
refinery-f3e42e7a09140fb68af534aca3e8df02c6c46ec7.tar.zst
refinery-f3e42e7a09140fb68af534aca3e8df02c6c46ec7.zip
feat: count and aggregation literal reduction
Diffstat (limited to 'subprojects/store-query')
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Reduction.java (renamed from subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java)6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBinderBuilder.java96
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSite.java (renamed from subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBinder.java)16
-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.java85
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java6
18 files changed, 281 insertions, 233 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
index 207c627c..e841da9e 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
@@ -24,8 +24,8 @@ public interface Constraint {
24 return i < 0 || i >= arity(); 24 return i < 0 || i >= arity();
25 } 25 }
26 26
27 default LiteralReduction getReduction() { 27 default Reduction getReduction() {
28 return LiteralReduction.NOT_REDUCIBLE; 28 return Reduction.NOT_REDUCIBLE;
29 } 29 }
30 30
31 default boolean equals(LiteralEqualityHelper helper, Constraint other) { 31 default boolean equals(LiteralEqualityHelper helper, Constraint other) {
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Reduction.java
index 9d8352ea..82c52b04 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Reduction.java
@@ -3,9 +3,9 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query;
7 7
8public enum LiteralReduction { 8public enum Reduction {
9 /** 9 /**
10 * Signifies that a literal should be preserved in the clause. 10 * Signifies that a literal should be preserved in the clause.
11 */ 11 */
@@ -21,7 +21,7 @@ public enum LiteralReduction {
21 */ 21 */
22 ALWAYS_FALSE; 22 ALWAYS_FALSE;
23 23
24 public LiteralReduction negate() { 24 public Reduction negate() {
25 return switch (this) { 25 return switch (this) {
26 case NOT_REDUCIBLE -> NOT_REDUCIBLE; 26 case NOT_REDUCIBLE -> NOT_REDUCIBLE;
27 case ALWAYS_TRUE -> ALWAYS_FALSE; 27 case ALWAYS_TRUE -> ALWAYS_FALSE;
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 72bc06a1..467b8d52 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import org.jetbrains.annotations.NotNull; 8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.query.literal.BooleanLiteral;
9import tools.refinery.store.query.literal.EquivalenceLiteral; 10import tools.refinery.store.query.literal.EquivalenceLiteral;
10import tools.refinery.store.query.literal.Literal; 11import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.literal.VariableDirection; 12import tools.refinery.store.query.literal.VariableDirection;
@@ -51,18 +52,11 @@ class ClausePostProcessor {
51 topologicallySortLiterals(); 52 topologicallySortLiterals();
52 var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size()); 53 var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size());
53 for (var literal : topologicallySortedLiterals) { 54 for (var literal : topologicallySortedLiterals) {
54 var reduction = literal.getReduction(); 55 var reducedLiteral = literal.reduce();
55 switch (reduction) { 56 if (BooleanLiteral.FALSE.equals(reducedLiteral)) {
56 case NOT_REDUCIBLE -> filteredLiterals.add(literal);
57 case ALWAYS_TRUE -> {
58 // Literals reducible to {@code true} can be omitted, because the model is always assumed to have at
59 // least on object.
60 }
61 case ALWAYS_FALSE -> {
62 // Clauses with {@code false} literals can be omitted entirely.
63 return ConstantResult.ALWAYS_FALSE; 57 return ConstantResult.ALWAYS_FALSE;
64 } 58 } else if (!BooleanLiteral.TRUE.equals(reducedLiteral)) {
65 default -> throw new IllegalArgumentException("Invalid reduction: " + reduction); 59 filteredLiterals.add(reducedLiteral);
66 } 60 }
67 } 61 }
68 if (filteredLiterals.isEmpty()) { 62 if (filteredLiterals.isEmpty()) {
@@ -156,7 +150,7 @@ class ClausePostProcessor {
156 150
157 private void computePositiveVariables() { 151 private void computePositiveVariables() {
158 for (var literal : substitutedLiterals) { 152 for (var literal : substitutedLiterals) {
159 var variableBinder = literal.getVariableBinder(); 153 var variableBinder = literal.getVariableBindingSite();
160 variableBinder.getVariablesWithDirection(VariableDirection.IN_OUT) 154 variableBinder.getVariablesWithDirection(VariableDirection.IN_OUT)
161 .forEach(existentiallyQuantifiedVariables::add); 155 .forEach(existentiallyQuantifiedVariables::add);
162 variableBinder.getVariablesWithDirection(VariableDirection.OUT).forEach(variable -> { 156 variableBinder.getVariablesWithDirection(VariableDirection.OUT).forEach(variable -> {
@@ -192,7 +186,7 @@ class ClausePostProcessor {
192 private void validateClosureVariables() { 186 private void validateClosureVariables() {
193 var negativeVariablesMap = new HashMap<Variable, Literal>(); 187 var negativeVariablesMap = new HashMap<Variable, Literal>();
194 for (var literal : substitutedLiterals) { 188 for (var literal : substitutedLiterals) {
195 var variableBinder = literal.getVariableBinder(); 189 var variableBinder = literal.getVariableBindingSite();
196 variableBinder.getVariablesWithDirection(VariableDirection.CLOSURE, positiveVariables) 190 variableBinder.getVariablesWithDirection(VariableDirection.CLOSURE, positiveVariables)
197 .forEach(variable -> { 191 .forEach(variable -> {
198 var oldLiteral = negativeVariablesMap.put(variable, literal); 192 var oldLiteral = negativeVariablesMap.put(variable, literal);
@@ -243,7 +237,7 @@ class ClausePostProcessor {
243 private SortableLiteral(int index, Literal literal) { 237 private SortableLiteral(int index, Literal literal) {
244 this.index = index; 238 this.index = index;
245 this.literal = literal; 239 this.literal = literal;
246 remainingInputs = literal.getVariableBinder() 240 remainingInputs = literal.getVariableBindingSite()
247 .getVariablesWithDirection(VariableDirection.IN, positiveVariables) 241 .getVariablesWithDirection(VariableDirection.IN, positiveVariables)
248 .collect(Collectors.toCollection(HashSet::new)); 242 .collect(Collectors.toCollection(HashSet::new));
249 remainingInputs.removeAll(inputParameters); 243 remainingInputs.removeAll(inputParameters);
@@ -288,7 +282,7 @@ class ClausePostProcessor {
288 } 282 }
289 // Add literal if we haven't yet added a duplicate of this literal. 283 // Add literal if we haven't yet added a duplicate of this literal.
290 topologicallySortedLiterals.add(literal); 284 topologicallySortedLiterals.add(literal);
291 var variableBinder = literal.getVariableBinder(); 285 var variableBinder = literal.getVariableBindingSite();
292 variableBinder.getVariablesWithDirection(VariableDirection.IN_OUT) 286 variableBinder.getVariablesWithDirection(VariableDirection.IN_OUT)
293 .forEach(ClausePostProcessor.this::topologicallySortVariable); 287 .forEach(ClausePostProcessor.this::topologicallySortVariable);
294 variableBinder.getVariablesWithDirection(VariableDirection.OUT) 288 variableBinder.getVariablesWithDirection(VariableDirection.OUT)
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
index 7e68d4a0..64790f42 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
@@ -6,9 +6,9 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.Reduction;
9import tools.refinery.store.query.equality.DnfEqualityChecker; 10import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.query.equality.LiteralEqualityHelper; 11import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.literal.LiteralReduction;
12import tools.refinery.store.query.term.Parameter; 12import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.term.Variable; 13import tools.refinery.store.query.term.Variable;
14 14
@@ -103,16 +103,16 @@ public final class Dnf implements Constraint {
103 } 103 }
104 104
105 @Override 105 @Override
106 public LiteralReduction getReduction() { 106 public Reduction getReduction() {
107 if (clauses.isEmpty()) { 107 if (clauses.isEmpty()) {
108 return LiteralReduction.ALWAYS_FALSE; 108 return Reduction.ALWAYS_FALSE;
109 } 109 }
110 for (var clause : clauses) { 110 for (var clause : clauses) {
111 if (clause.literals().isEmpty()) { 111 if (clause.literals().isEmpty()) {
112 return LiteralReduction.ALWAYS_TRUE; 112 return Reduction.ALWAYS_TRUE;
113 } 113 }
114 } 114 }
115 return LiteralReduction.NOT_REDUCIBLE; 115 return Reduction.NOT_REDUCIBLE;
116 } 116 }
117 117
118 public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) { 118 public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) {
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 2aa0a0d5..51a33c1f 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
@@ -9,6 +9,7 @@ import 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.Aggregator;
12import tools.refinery.store.query.term.ConstantTerm;
12import tools.refinery.store.query.term.DataVariable; 13import tools.refinery.store.query.term.DataVariable;
13import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
14 15
@@ -19,7 +20,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
19 private final DataVariable<R> resultVariable; 20 private final DataVariable<R> resultVariable;
20 private final DataVariable<T> inputVariable; 21 private final DataVariable<T> inputVariable;
21 private final Aggregator<R, T> aggregator; 22 private final Aggregator<R, T> aggregator;
22 private final VariableBinder variableBinder; 23 private final VariableBindingSite variableBindingSite;
23 24
24 public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator, 25 public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator,
25 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { 26 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
@@ -39,13 +40,13 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
39 this.resultVariable = resultVariable; 40 this.resultVariable = resultVariable;
40 this.inputVariable = inputVariable; 41 this.inputVariable = inputVariable;
41 this.aggregator = aggregator; 42 this.aggregator = aggregator;
42 variableBinder = VariableBinder.builder() 43 variableBindingSite = VariableBindingSite.builder()
43 .variable(resultVariable, VariableDirection.OUT) 44 .variable(resultVariable, VariableDirection.OUT)
44 .parameterList(false, target.getParameters(), arguments) 45 .parameterList(false, target.getParameters(), arguments)
45 .build(); 46 .build();
46 if (variableBinder.getDirection(inputVariable) != VariableDirection.CLOSURE) { 47 if (variableBindingSite.getDirection(inputVariable) != VariableDirection.CLOSURE) {
47 throw new IllegalArgumentException("Input variable %s must appear in the argument list".formatted( 48 throw new IllegalArgumentException(("Input variable %s must appear in the argument list as an output " +
48 inputVariable)); 49 "variable and should not be bound anywhere else").formatted(inputVariable));
49 } 50 }
50 } 51 }
51 52
@@ -62,8 +63,22 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
62 } 63 }
63 64
64 @Override 65 @Override
65 public VariableBinder getVariableBinder() { 66 public VariableBindingSite getVariableBindingSite() {
66 return variableBinder; 67 return variableBindingSite;
68 }
69
70 @Override
71 public Literal reduce() {
72 var reduction = getTarget().getReduction();
73 return switch (reduction) {
74 case ALWAYS_FALSE -> {
75 var emptyValue = aggregator.getEmptyResult();
76 yield emptyValue == null ? BooleanLiteral.FALSE :
77 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue));
78 }
79 case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set");
80 case NOT_REDUCIBLE -> this;
81 };
67 } 82 }
68 83
69 @Override 84 @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 4da92b90..079ba6bb 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
@@ -15,7 +15,7 @@ import java.util.Objects;
15public final class AssignLiteral<T> implements Literal { 15public final class AssignLiteral<T> implements Literal {
16 private final DataVariable<T> variable; 16 private final DataVariable<T> variable;
17 private final Term<T> term; 17 private final Term<T> term;
18 private final VariableBinder variableBinder; 18 private final VariableBindingSite variableBindingSite;
19 19
20 public AssignLiteral(DataVariable<T> variable, Term<T> term) { 20 public AssignLiteral(DataVariable<T> variable, Term<T> term) {
21 if (!term.getType().equals(variable.getType())) { 21 if (!term.getType().equals(variable.getType())) {
@@ -29,7 +29,7 @@ public final class AssignLiteral<T> implements Literal {
29 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( 29 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted(
30 variable, term)); 30 variable, term));
31 } 31 }
32 variableBinder = VariableBinder.builder() 32 variableBindingSite = VariableBindingSite.builder()
33 .variable(variable, VariableDirection.OUT) 33 .variable(variable, VariableDirection.OUT)
34 .variables(inputVariables, VariableDirection.IN) 34 .variables(inputVariables, VariableDirection.IN)
35 .build(); 35 .build();
@@ -44,8 +44,8 @@ public final class AssignLiteral<T> implements Literal {
44 } 44 }
45 45
46 @Override 46 @Override
47 public VariableBinder getVariableBinder() { 47 public VariableBindingSite getVariableBindingSite() {
48 return variableBinder; 48 return variableBindingSite;
49 } 49 }
50 50
51 @Override 51 @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 61486e4c..3dfb8902 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
@@ -14,7 +14,7 @@ import java.util.Objects;
14 14
15public final class AssumeLiteral implements Literal { 15public final class AssumeLiteral implements Literal {
16 private final Term<Boolean> term; 16 private final Term<Boolean> term;
17 private final VariableBinder variableBinder; 17 private final VariableBindingSite variableBindingSite;
18 18
19 public AssumeLiteral(Term<Boolean> term) { 19 public AssumeLiteral(Term<Boolean> term) {
20 if (!term.getType().equals(Boolean.class)) { 20 if (!term.getType().equals(Boolean.class)) {
@@ -22,7 +22,7 @@ public final class AssumeLiteral implements Literal {
22 term, Boolean.class.getName(), term.getType().getName())); 22 term, Boolean.class.getName(), term.getType().getName()));
23 } 23 }
24 this.term = term; 24 this.term = term;
25 variableBinder = VariableBinder.builder() 25 variableBindingSite = VariableBindingSite.builder()
26 .variables(term.getInputVariables(), VariableDirection.IN) 26 .variables(term.getInputVariables(), VariableDirection.IN)
27 .build(); 27 .build();
28 } 28 }
@@ -32,8 +32,8 @@ public final class AssumeLiteral implements Literal {
32 } 32 }
33 33
34 @Override 34 @Override
35 public VariableBinder getVariableBinder() { 35 public VariableBindingSite getVariableBindingSite() {
36 return variableBinder; 36 return variableBindingSite;
37 } 37 }
38 38
39 @Override 39 @Override
@@ -51,13 +51,13 @@ public final class AssumeLiteral implements Literal {
51 } 51 }
52 52
53 @Override 53 @Override
54 public LiteralReduction getReduction() { 54 public Literal reduce() {
55 if (term instanceof ConstantTerm<Boolean> constantTerm) { 55 if (term instanceof ConstantTerm<Boolean> constantTerm) {
56 // Return {@code ALWAYS_FALSE} for {@code false} or {@code null} literals. 56 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals.
57 return Boolean.TRUE.equals(constantTerm.getValue()) ? LiteralReduction.ALWAYS_TRUE : 57 return Boolean.TRUE.equals(constantTerm.getValue()) ? BooleanLiteral.TRUE :
58 LiteralReduction.ALWAYS_FALSE; 58 BooleanLiteral.FALSE;
59 } 59 }
60 return LiteralReduction.NOT_REDUCIBLE; 60 return this;
61 } 61 }
62 62
63 @Override 63 @Override
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 e61f0535..736b3537 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
@@ -19,8 +19,8 @@ public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
19 } 19 }
20 20
21 @Override 21 @Override
22 public VariableBinder getVariableBinder() { 22 public VariableBindingSite getVariableBindingSite() {
23 return VariableBinder.EMPTY; 23 return VariableBindingSite.EMPTY;
24 } 24 }
25 25
26 @Override 26 @Override
@@ -30,11 +30,6 @@ public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
30 } 30 }
31 31
32 @Override 32 @Override
33 public LiteralReduction getReduction() {
34 return value ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE;
35 }
36
37 @Override
38 public BooleanLiteral negate() { 33 public BooleanLiteral negate() {
39 return fromBoolean(!value); 34 return fromBoolean(!value);
40 } 35 }
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 dc6098a8..4f755e95 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
@@ -15,7 +15,7 @@ import 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 VariableBinder variableBinder; 18 private final VariableBindingSite variableBindingSite;
19 19
20 public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) { 20 public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) {
21 super(target, arguments); 21 super(target, arguments);
@@ -30,7 +30,7 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate<
30 } 30 }
31 } 31 }
32 this.polarity = polarity; 32 this.polarity = polarity;
33 variableBinder = VariableBinder.builder() 33 variableBindingSite = VariableBindingSite.builder()
34 .parameterList(polarity.isPositive(), parameters, arguments) 34 .parameterList(polarity.isPositive(), parameters, arguments)
35 .build(); 35 .build();
36 } 36 }
@@ -40,8 +40,8 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate<
40 } 40 }
41 41
42 @Override 42 @Override
43 public VariableBinder getVariableBinder() { 43 public VariableBindingSite getVariableBindingSite() {
44 return variableBinder; 44 return variableBindingSite;
45 } 45 }
46 46
47 @Override 47 @Override
@@ -50,9 +50,14 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate<
50 } 50 }
51 51
52 @Override 52 @Override
53 public LiteralReduction getReduction() { 53 public Literal reduce() {
54 var reduction = getTarget().getReduction(); 54 var reduction = getTarget().getReduction();
55 return polarity.isPositive() ? reduction : reduction.negate(); 55 var negatedReduction = polarity.isPositive() ? reduction : reduction.negate();
56 return switch (negatedReduction) {
57 case ALWAYS_TRUE -> BooleanLiteral.TRUE;
58 case ALWAYS_FALSE -> BooleanLiteral.FALSE;
59 default -> this;
60 };
56 } 61 }
57 62
58 @Override 63 @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 7466cb1d..1bc14bab 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
@@ -14,12 +14,12 @@ import java.util.Objects;
14public final class ConstantLiteral implements Literal { 14public final class ConstantLiteral implements Literal {
15 private final NodeVariable variable; 15 private final NodeVariable variable;
16 private final int nodeId; 16 private final int nodeId;
17 private final VariableBinder variableBinder; 17 private final VariableBindingSite variableBindingSite;
18 18
19 public ConstantLiteral(NodeVariable variable, int nodeId) { 19 public ConstantLiteral(NodeVariable variable, int nodeId) {
20 this.variable = variable; 20 this.variable = variable;
21 this.nodeId = nodeId; 21 this.nodeId = nodeId;
22 variableBinder = VariableBinder.builder().variable(variable, VariableDirection.IN_OUT).build(); 22 variableBindingSite = VariableBindingSite.builder().variable(variable, VariableDirection.IN_OUT).build();
23 } 23 }
24 24
25 public NodeVariable getVariable() { 25 public NodeVariable getVariable() {
@@ -31,8 +31,8 @@ public final class ConstantLiteral implements Literal {
31 } 31 }
32 32
33 @Override 33 @Override
34 public VariableBinder getVariableBinder() { 34 public VariableBindingSite getVariableBindingSite() {
35 return variableBinder; 35 return variableBindingSite;
36 } 36 }
37 37
38 @Override 38 @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 a602d982..0a47aa66 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
@@ -10,13 +10,14 @@ import 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.DataVariable; 11import tools.refinery.store.query.term.DataVariable;
12import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
13 14
14import java.util.List; 15import java.util.List;
15import java.util.Objects; 16import java.util.Objects;
16 17
17public class CountLiteral extends AbstractCallLiteral { 18public class CountLiteral extends AbstractCallLiteral {
18 private final DataVariable<Integer> resultVariable; 19 private final DataVariable<Integer> resultVariable;
19 private final VariableBinder variableBinder; 20 private final VariableBindingSite variableBindingSite;
20 21
21 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { 22 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) {
22 super(target, arguments); 23 super(target, arguments);
@@ -29,7 +30,7 @@ public class CountLiteral extends AbstractCallLiteral {
29 .formatted(resultVariable)); 30 .formatted(resultVariable));
30 } 31 }
31 this.resultVariable = resultVariable; 32 this.resultVariable = resultVariable;
32 variableBinder = VariableBinder.builder() 33 variableBindingSite = VariableBindingSite.builder()
33 .variable(resultVariable, VariableDirection.OUT) 34 .variable(resultVariable, VariableDirection.OUT)
34 .parameterList(false, target.getParameters(), arguments) 35 .parameterList(false, target.getParameters(), arguments)
35 .build(); 36 .build();
@@ -40,8 +41,20 @@ public class CountLiteral extends AbstractCallLiteral {
40 } 41 }
41 42
42 @Override 43 @Override
43 public VariableBinder getVariableBinder() { 44 public VariableBindingSite getVariableBindingSite() {
44 return variableBinder; 45 return variableBindingSite;
46 }
47
48 @Override
49 public Literal reduce() {
50 var reduction = getTarget().getReduction();
51 return switch (reduction) {
52 case ALWAYS_FALSE -> getResultVariable().assign(IntTerms.constant(0));
53 // The only way a constant {@code true} predicate can be called in a negative position is to have all of
54 // its arguments bound as input variables. Thus, there will only be a single match.
55 case ALWAYS_TRUE -> getResultVariable().assign(IntTerms.constant(1));
56 case NOT_REDUCIBLE -> this;
57 };
45 } 58 }
46 59
47 @Override 60 @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 794cee0d..b36c0e40 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
@@ -16,13 +16,13 @@ public final class EquivalenceLiteral
16 private final boolean positive; 16 private final boolean positive;
17 private final NodeVariable left; 17 private final NodeVariable left;
18 private final NodeVariable right; 18 private final NodeVariable right;
19 private final VariableBinder variableBinder; 19 private final VariableBindingSite variableBindingSite;
20 20
21 public EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) { 21 public EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) {
22 this.positive = positive; 22 this.positive = positive;
23 this.left = left; 23 this.left = left;
24 this.right = right; 24 this.right = right;
25 variableBinder = VariableBinder.builder() 25 variableBindingSite = VariableBindingSite.builder()
26 .variable(left, positive ? VariableDirection.IN_OUT : VariableDirection.IN) 26 .variable(left, positive ? VariableDirection.IN_OUT : VariableDirection.IN)
27 .variable(right, VariableDirection.IN) 27 .variable(right, VariableDirection.IN)
28 .build(); 28 .build();
@@ -41,8 +41,8 @@ public final class EquivalenceLiteral
41 } 41 }
42 42
43 @Override 43 @Override
44 public VariableBinder getVariableBinder() { 44 public VariableBindingSite getVariableBindingSite() {
45 return variableBinder; 45 return variableBindingSite;
46 } 46 }
47 47
48 @Override 48 @Override
@@ -57,11 +57,11 @@ public final class EquivalenceLiteral
57 } 57 }
58 58
59 @Override 59 @Override
60 public LiteralReduction getReduction() { 60 public Literal reduce() {
61 if (left.equals(right)) { 61 if (left.equals(right)) {
62 return positive ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE; 62 return positive ? BooleanLiteral.TRUE : BooleanLiteral.FALSE;
63 } 63 }
64 return LiteralReduction.NOT_REDUCIBLE; 64 return this;
65 } 65 }
66 66
67 @Override 67 @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 c3c111a4..10e4cffd 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
@@ -9,12 +9,12 @@ import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
10 10
11public interface Literal { 11public interface Literal {
12 VariableBinder getVariableBinder(); 12 VariableBindingSite getVariableBindingSite();
13 13
14 Literal substitute(Substitution substitution); 14 Literal substitute(Substitution substitution);
15 15
16 default LiteralReduction getReduction() { 16 default Literal reduce() {
17 return LiteralReduction.NOT_REDUCIBLE; 17 return this;
18 } 18 }
19 19
20 @SuppressWarnings("BooleanMethodIsAlwaysInverted") 20 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBinderBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBinderBuilder.java
deleted file mode 100644
index b0d47240..00000000
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBinderBuilder.java
+++ /dev/null
@@ -1,96 +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 VariableBinderBuilder {
14 private final Map<Variable, VariableDirection> directionMap = new LinkedHashMap<>();
15 private final Set<Variable> uniqueVariables = new HashSet<>();
16
17 VariableBinderBuilder() {
18 }
19
20 public VariableBinderBuilder variable(Variable variable, VariableDirection direction) {
21 return variable(variable, direction, direction == VariableDirection.OUT);
22 }
23
24 private VariableBinderBuilder variable(Variable variable, VariableDirection direction, boolean markAsUnique) {
25 validateDirection(variable, direction);
26 boolean unique = shouldBeUnique(variable, markAsUnique);
27 directionMap.compute(variable, (ignored, oldValue) -> {
28 if (oldValue == null) {
29 return direction;
30 }
31 if (unique) {
32 throw new IllegalArgumentException("Duplicate binding for variable " + variable);
33 }
34 try {
35 return oldValue.merge(direction);
36 } catch (IncompatibleParameterDirectionException e) {
37 var message = "%s for variable %s".formatted(e.getMessage(), variable);
38 throw new IncompatibleParameterDirectionException(message, e);
39 }
40 });
41 return this;
42 }
43
44 private static void validateDirection(Variable variable, VariableDirection direction) {
45 if (variable instanceof AnyDataVariable) {
46 if (direction == VariableDirection.IN_OUT) {
47 throw new IllegalArgumentException("%s direction is not supported for data variable %s"
48 .formatted(direction, variable));
49 }
50 } else if (variable instanceof NodeVariable) {
51 if (direction == VariableDirection.OUT) {
52 throw new IllegalArgumentException("%s direction is not supported for node variable %s"
53 .formatted(direction, variable));
54 }
55 } else {
56 throw new IllegalArgumentException("Unknown variable " + variable);
57 }
58 }
59
60 private boolean shouldBeUnique(Variable variable, boolean markAsUnique) {
61 if (markAsUnique) {
62 uniqueVariables.add(variable);
63 return true;
64 } else {
65 return uniqueVariables.contains(variable);
66 }
67 }
68
69 public VariableBinderBuilder variables(Collection<? extends Variable> variables, VariableDirection direction) {
70 for (var variable : variables) {
71 variable(variable, direction);
72 }
73 return this;
74 }
75
76 public VariableBinderBuilder parameterList(boolean positive, List<Parameter> parameters,
77 List<Variable> arguments) {
78 int arity = parameters.size();
79 if (arity != arguments.size()) {
80 throw new IllegalArgumentException("Got %d arguments for %d parameters"
81 .formatted(arguments.size(), arity));
82 }
83 for (int i = 0; i < arity; i++) {
84 var argument = arguments.get(i);
85 var parameter = parameters.get(i);
86 var parameterDirection = parameter.getDirection();
87 var direction = VariableDirection.from(positive, parameterDirection);
88 variable(argument, direction, parameterDirection == ParameterDirection.OUT);
89 }
90 return this;
91 }
92
93 public VariableBinder build() {
94 return new VariableBinder(directionMap);
95 }
96}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBinder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSite.java
index 5f43d07d..624af045 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBinder.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSite.java
@@ -11,12 +11,12 @@ import java.util.Map;
11import java.util.Set; 11import java.util.Set;
12import java.util.stream.Stream; 12import java.util.stream.Stream;
13 13
14public final class VariableBinder { 14public final class VariableBindingSite {
15 public static final VariableBinder EMPTY = new VariableBinder(Map.of()); 15 public static final VariableBindingSite EMPTY = new VariableBindingSite(Map.of());
16 16
17 private final Map<Variable, VariableDirection> directionMap; 17 private final Map<Variable, VariableDirection> directionMap;
18 18
19 VariableBinder(Map<Variable, VariableDirection> directionMap) { 19 VariableBindingSite(Map<Variable, VariableDirection> directionMap) {
20 this.directionMap = directionMap; 20 this.directionMap = directionMap;
21 } 21 }
22 22
@@ -41,9 +41,9 @@ public final class VariableBinder {
41 41
42 public Stream<Variable> getVariablesWithDirection(VariableDirection direction, 42 public Stream<Variable> getVariablesWithDirection(VariableDirection direction,
43 Set<? extends Variable> positiveVariables) { 43 Set<? extends Variable> positiveVariables) {
44 if (direction == VariableDirection.NEGATIVE) { 44 if (direction == VariableDirection.NEUTRAL) {
45 throw new IllegalArgumentException("Use #getVariablesWithDirection(VariableDirection) if disambiguation " + 45 throw new IllegalArgumentException("Use #getVariablesWithDirection(VariableDirection) if disambiguation " +
46 "of VariableDirection#NEGATIVE variables according to the containing DnfClose is not desired"); 46 "of VariableDirection#NEUTRAL variables according to the containing DnfClose is not desired");
47 } 47 }
48 return directionMap.entrySet().stream() 48 return directionMap.entrySet().stream()
49 .filter(pair -> disambiguateDirection(pair.getValue(), pair.getKey(), positiveVariables) == direction) 49 .filter(pair -> disambiguateDirection(pair.getValue(), pair.getKey(), positiveVariables) == direction)
@@ -52,13 +52,13 @@ public final class VariableBinder {
52 52
53 private VariableDirection disambiguateDirection(VariableDirection direction, Variable variable, 53 private VariableDirection disambiguateDirection(VariableDirection direction, Variable variable,
54 Set<? extends Variable> positiveVariables) { 54 Set<? extends Variable> positiveVariables) {
55 if (direction != VariableDirection.NEGATIVE) { 55 if (direction != VariableDirection.NEUTRAL) {
56 return direction; 56 return direction;
57 } 57 }
58 return positiveVariables.contains(variable) ? VariableDirection.IN : VariableDirection.CLOSURE; 58 return positiveVariables.contains(variable) ? VariableDirection.IN : VariableDirection.CLOSURE;
59 } 59 }
60 60
61 public static VariableBinderBuilder builder() { 61 public static VariableBindingSiteBuilder builder() {
62 return new VariableBinderBuilder(); 62 return new VariableBindingSiteBuilder();
63 } 63 }
64} 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
new file mode 100644
index 00000000..873db1ec
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/VariableBindingSiteBuilder.java
@@ -0,0 +1,137 @@
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
index 444dcbbf..0b7a2960 100644
--- 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
@@ -5,44 +5,63 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.exceptions.IncompatibleParameterDirectionException;
9import tools.refinery.store.query.term.ParameterDirection; 8import tools.refinery.store.query.term.ParameterDirection;
10 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 */
11public enum VariableDirection { 30public enum VariableDirection {
12 /** 31 /**
13 * Binds a node variable or check equality with a node variable. 32 * Binds a node variable or check equality with a node variable.
14 * <p> 33 * <p>
15 * This is the usual direction for positive constraints on nodes. A data variable may have multiple {@code InOut} 34 * This is the usual direction for positive constraints on nodes. A data variable may have multiple {@code IN_OUT}
16 * bindings, even on the same parameter list. 35 * bindings, even on the same parameter list.
17 * <p> 36 * <p>
18 * Cannot be used for data variables. 37 * Cannot be used for data variables.
19 */ 38 */
20 IN_OUT("@InOut"), 39 IN_OUT,
21 40
22 /** 41 /**
23 * Binds a data variable. 42 * Binds a data variable.
24 * <p> 43 * <p>
25 * A single variable must have at most one {@code @Out} binding. A variable with a {@code @Out} binding cannot 44 * A single variable must have at most one {@code OUT} binding. A variable with a {@code OUT} binding cannot
26 * appear in any other place in a parameter list. 45 * appear in any other place in a parameter list.
27 * <p> 46 * <p>
28 * Cannot be used for node variables. 47 * Cannot be used for node variables.
29 */ 48 */
30 OUT("@Out"), 49 OUT,
31 50
32 /** 51 /**
33 * Either takes a bound data variable or enumerates all possible data variable bindings. 52 * Takes an already bound variable.
34 * <p> 53 * <p>
35 * Cannot be used for data variables. 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.
36 */ 56 */
37 NEGATIVE("@Negative"), 57 IN,
38 58
39 /** 59 /**
40 * Takes an already bound variable. 60 * Either takes a bound data variable or enumerates all possible data variable bindings.
41 * <p> 61 * <p>
42 * May be used with node or data variables. An {@code @InOut} or {@code @Out} binding on the same parameter list 62 * Cannot be used for data variables.
43 * cannot satisfy the {@code @In} binding, because it might introduce a (non-monotonic) circular dependency.
44 */ 63 */
45 IN("@In"), 64 NEUTRAL,
46 65
47 /** 66 /**
48 * Enumerates over all possible data variable bindings. 67 * Enumerates over all possible data variable bindings.
@@ -51,49 +70,11 @@ public enum VariableDirection {
51 * variable may only appear once in the parameter list, but node variables can appear multiple times to form 70 * variable may only appear once in the parameter list, but node variables can appear multiple times to form
52 * diagonal constraints. 71 * diagonal constraints.
53 */ 72 */
54 CLOSURE("@Closure"); 73 CLOSURE;
55
56 private final String name;
57
58 VariableDirection(String name) {
59 this.name = name;
60 }
61
62 public VariableDirection merge(VariableDirection other) {
63 switch (this) {
64 case IN_OUT -> {
65 if (other == IN_OUT) {
66 return this;
67 }
68 }
69 case OUT -> {
70 if (other == OUT) {
71 throw new IncompatibleParameterDirectionException("Multiple %s bindings".formatted(this));
72 }
73 }
74 case NEGATIVE -> {
75 if (other == NEGATIVE || other == IN || other == CLOSURE) {
76 return other;
77 }
78 }
79 case IN, CLOSURE -> {
80 if (other == NEGATIVE || other == this) {
81 return this;
82 }
83 }
84 }
85 throw new IncompatibleParameterDirectionException("Incompatible variable directions %s and %s"
86 .formatted(this, other));
87 }
88
89 @Override
90 public String toString() {
91 return name;
92 }
93 74
94 public static VariableDirection from(boolean positive, ParameterDirection parameterDirection) { 75 public static VariableDirection from(boolean positive, ParameterDirection parameterDirection) {
95 return switch (parameterDirection) { 76 return switch (parameterDirection) {
96 case IN_OUT -> positive ? IN_OUT : NEGATIVE; 77 case IN_OUT -> positive ? IN_OUT : NEUTRAL;
97 case OUT -> positive ? OUT : CLOSURE; 78 case OUT -> positive ? OUT : CLOSURE;
98 case IN -> IN; 79 case IN -> IN;
99 }; 80 };
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 29aedbef..351a353d 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
@@ -26,8 +26,12 @@ public class Parameter {
26 } 26 }
27 } 27 }
28 28
29 public boolean isNodeVariable() {
30 return dataType == null;
31 }
32
29 public boolean isDataVariable() { 33 public boolean isDataVariable() {
30 return dataType != null; 34 return !isNodeVariable();
31 } 35 }
32 36
33 public Optional<Class<?>> tryGetType() { 37 public Optional<Class<?>> tryGetType() {