aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query/src/main/java
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-02-20 20:23:27 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-02-20 20:23:27 +0100
commitb3f7c4d7707435803921c4fec2c4d95b3dd45c53 (patch)
tree18a90112efe3ece8678709db322dddcefafaace1 /subprojects/store-query/src/main/java
parentfeat: type inference for class hierarchies (diff)
downloadrefinery-b3f7c4d7707435803921c4fec2c4d95b3dd45c53.tar.gz
refinery-b3f7c4d7707435803921c4fec2c4d95b3dd45c53.tar.zst
refinery-b3f7c4d7707435803921c4fec2c4d95b3dd45c53.zip
refactor: split query and partial from store
Allows more complicated dependency hiearchies (e.g., use store-query-viatra for testing store-partial) and better separation of test fixtures.
Diffstat (limited to 'subprojects/store-query/src/main/java')
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java112
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java108
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java25
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java58
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java37
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java66
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java32
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java71
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java44
30 files changed, 1050 insertions, 0 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java
new file mode 100644
index 00000000..760b264b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java
@@ -0,0 +1,112 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.literal.CallPolarity;
4import tools.refinery.store.query.literal.DnfCallLiteral;
5import tools.refinery.store.query.literal.LiteralReduction;
6
7import java.util.*;
8
9public final class Dnf implements RelationLike {
10 private final String name;
11
12 private final String uniqueName;
13
14 private final List<Variable> parameters;
15
16 private final List<FunctionalDependency<Variable>> functionalDependencies;
17
18 private final List<DnfClause> clauses;
19
20 Dnf(String name, List<Variable> parameters, List<FunctionalDependency<Variable>> functionalDependencies,
21 List<DnfClause> clauses) {
22 validateFunctionalDependencies(parameters, functionalDependencies);
23 this.name = name;
24 this.uniqueName = DnfUtils.generateUniqueName(name);
25 this.parameters = parameters;
26 this.functionalDependencies = functionalDependencies;
27 this.clauses = clauses;
28 }
29
30 private static void validateFunctionalDependencies(
31 Collection<Variable> parameters, Collection<FunctionalDependency<Variable>> functionalDependencies) {
32 var parameterSet = new HashSet<>(parameters);
33 for (var functionalDependency : functionalDependencies) {
34 validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency);
35 validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency);
36 }
37 }
38
39 private static void validateParameters(Collection<Variable> parameters, Set<Variable> parameterSet,
40 Collection<Variable> toValidate,
41 FunctionalDependency<Variable> functionalDependency) {
42 for (var variable : toValidate) {
43 if (!parameterSet.contains(variable)) {
44 throw new IllegalArgumentException(
45 "Variable %s of functional dependency %s does not appear in the parameter list %s"
46 .formatted(variable, functionalDependency, parameters));
47 }
48 }
49 }
50
51 @Override
52 public String name() {
53 return name;
54 }
55
56 public String getUniqueName() {
57 return uniqueName;
58 }
59
60 public List<Variable> getParameters() {
61 return parameters;
62 }
63
64 public List<FunctionalDependency<Variable>> getFunctionalDependencies() {
65 return functionalDependencies;
66 }
67
68 @Override
69 public int arity() {
70 return parameters.size();
71 }
72
73 public List<DnfClause> getClauses() {
74 return clauses;
75 }
76
77 public LiteralReduction getReduction() {
78 if (clauses.isEmpty()) {
79 return LiteralReduction.ALWAYS_FALSE;
80 }
81 for (var clause : clauses) {
82 if (clause.literals().isEmpty()) {
83 return LiteralReduction.ALWAYS_TRUE;
84 }
85 }
86 return LiteralReduction.NOT_REDUCIBLE;
87 }
88
89 public DnfCallLiteral call(CallPolarity polarity, List<Variable> arguments) {
90 return new DnfCallLiteral(polarity, this, arguments);
91 }
92
93 public DnfCallLiteral call(CallPolarity polarity, Variable... arguments) {
94 return call(polarity, List.of(arguments));
95 }
96
97 public DnfCallLiteral call(Variable... arguments) {
98 return call(CallPolarity.POSITIVE, arguments);
99 }
100
101 public DnfCallLiteral callTransitive(Variable left, Variable right) {
102 return call(CallPolarity.TRANSITIVE, List.of(left, right));
103 }
104
105 public static DnfBuilder builder() {
106 return builder(null);
107 }
108
109 public static DnfBuilder builder(String name) {
110 return new DnfBuilder(name);
111 }
112}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java
new file mode 100644
index 00000000..b18b5177
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java
@@ -0,0 +1,108 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.literal.Literal;
4
5import java.util.*;
6
7@SuppressWarnings("UnusedReturnValue")
8public class DnfBuilder {
9 private final String name;
10
11 private final List<Variable> parameters = new ArrayList<>();
12
13 private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>();
14
15 private final List<List<Literal>> clauses = new ArrayList<>();
16
17 DnfBuilder(String name) {
18 this.name = name;
19 }
20
21 public DnfBuilder parameter(Variable variable) {
22 parameters.add(variable);
23 return this;
24 }
25
26 public DnfBuilder parameters(Variable... variables) {
27 return parameters(List.of(variables));
28 }
29
30 public DnfBuilder parameters(Collection<Variable> variables) {
31 parameters.addAll(variables);
32 return this;
33 }
34
35 public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
36 this.functionalDependencies.addAll(functionalDependencies);
37 return this;
38 }
39
40 public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
41 functionalDependencies.add(functionalDependency);
42 return this;
43 }
44
45 public DnfBuilder functionalDependency(Set<Variable> forEach, Set<Variable> unique) {
46 return functionalDependency(new FunctionalDependency<>(forEach, unique));
47 }
48
49 public DnfBuilder clause(Literal... literals) {
50 clause(List.of(literals));
51 return this;
52 }
53
54 public DnfBuilder clause(Collection<Literal> literals) {
55 var filteredLiterals = new ArrayList<Literal>(literals.size());
56 for (var literal : literals) {
57 var reduction = literal.getReduction();
58 switch (reduction) {
59 case NOT_REDUCIBLE -> filteredLiterals.add(literal);
60 case ALWAYS_TRUE -> {
61 // Literals reducible to {@code true} can be omitted, because the model is always assumed to have at
62 // least on object.
63 }
64 case ALWAYS_FALSE -> {
65 // Clauses with {@code false} literals can be omitted entirely.
66 return this;
67 }
68 default -> throw new IllegalStateException("Invalid reduction %s".formatted(reduction));
69 }
70 }
71 clauses.add(Collections.unmodifiableList(filteredLiterals));
72 return this;
73 }
74
75 public DnfBuilder clause(DnfClause clause) {
76 return clause(clause.literals());
77 }
78
79 public DnfBuilder clauses(DnfClause... clauses) {
80 for (var clause : clauses) {
81 this.clause(clause);
82 }
83 return this;
84 }
85
86 public DnfBuilder clauses(Collection<DnfClause> clauses) {
87 for (var clause : clauses) {
88 this.clause(clause);
89 }
90 return this;
91 }
92
93 public Dnf build() {
94 var postProcessedClauses = new ArrayList<DnfClause>(clauses.size());
95 for (var constraints : clauses) {
96 var variables = new HashSet<Variable>();
97 for (var constraint : constraints) {
98 constraint.collectAllVariables(variables);
99 }
100 parameters.forEach(variables::remove);
101 postProcessedClauses.add(new DnfClause(Collections.unmodifiableSet(variables),
102 Collections.unmodifiableList(constraints)));
103 }
104 return new Dnf(name, Collections.unmodifiableList(parameters),
105 Collections.unmodifiableList(functionalDependencies),
106 Collections.unmodifiableList(postProcessedClauses));
107 }
108}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java
new file mode 100644
index 00000000..2ba6becc
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java
@@ -0,0 +1,9 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.literal.Literal;
4
5import java.util.List;
6import java.util.Set;
7
8public record DnfClause(Set<Variable> quantifiedVariables, List<Literal> literals) {
9}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java
new file mode 100644
index 00000000..17564d43
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java
@@ -0,0 +1,24 @@
1package tools.refinery.store.query;
2
3import java.util.Map;
4import java.util.UUID;
5
6public final class DnfUtils {
7 private DnfUtils() {
8 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
9 }
10
11 public static String generateUniqueName(String originalName) {
12 UUID uuid = UUID.randomUUID();
13 String uniqueString = "_" + uuid.toString().replace('-', '_');
14 if (originalName == null) {
15 return uniqueString;
16 } else {
17 return originalName + uniqueString;
18 }
19 }
20
21 public static Variable maybeSubstitute(Variable variable, Map<Variable, Variable> substitution) {
22 return substitution.getOrDefault(variable, variable);
23 }
24}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java
new file mode 100644
index 00000000..63a81713
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java
@@ -0,0 +1,15 @@
1package tools.refinery.store.query;
2
3import java.util.HashSet;
4import java.util.Set;
5
6public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) {
7 public FunctionalDependency {
8 var uniqueForEach = new HashSet<>(unique);
9 uniqueForEach.retainAll(forEach);
10 if (!uniqueForEach.isEmpty()) {
11 throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency"
12 .formatted(uniqueForEach));
13 }
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java
new file mode 100644
index 00000000..6a1aeabb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapterType;
4
5public final class ModelQuery extends ModelAdapterType<ModelQueryAdapter, ModelQueryStoreAdapter, ModelQueryBuilder> {
6 public static final ModelQuery ADAPTER = new ModelQuery();
7
8 private ModelQuery() {
9 super(ModelQueryAdapter.class, ModelQueryStoreAdapter.class, ModelQueryBuilder.class);
10 }
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
new file mode 100644
index 00000000..f7762444
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
@@ -0,0 +1,13 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapter;
4
5public interface ModelQueryAdapter extends ModelAdapter {
6 ModelQueryStoreAdapter getStoreAdapter();
7
8 ResultSet getResultSet(Dnf query);
9
10 boolean hasPendingChanges();
11
12 void flushChanges();
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
new file mode 100644
index 00000000..b3cfb4b4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
@@ -0,0 +1,23 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5
6import java.util.Collection;
7import java.util.List;
8
9public interface ModelQueryBuilder extends ModelAdapterBuilder {
10 default ModelQueryBuilder queries(Dnf... queries) {
11 return queries(List.of(queries));
12 }
13
14 default ModelQueryBuilder queries(Collection<Dnf> queries) {
15 queries.forEach(this::query);
16 return this;
17 }
18
19 ModelQueryBuilder query(Dnf query);
20
21 @Override
22 ModelQueryStoreAdapter createStoreAdapter(ModelStore store);
23}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
new file mode 100644
index 00000000..091d6d06
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.view.AnyRelationView;
4import tools.refinery.store.adapter.ModelStoreAdapter;
5import tools.refinery.store.model.Model;
6
7import java.util.Collection;
8
9public interface ModelQueryStoreAdapter extends ModelStoreAdapter {
10 Collection<AnyRelationView> getRelationViews();
11
12 Collection<Dnf> getQueries();
13
14 @Override
15 ModelQueryAdapter createModelAdapter(Model model);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java
new file mode 100644
index 00000000..8c784d8b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query;
2
3public interface RelationLike {
4 String name();
5
6 int arity();
7
8 default boolean invalidIndex(int i) {
9 return i < 0 || i >= arity();
10 }
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java
new file mode 100644
index 00000000..3542e252
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java
@@ -0,0 +1,25 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.tuple.Tuple;
4import tools.refinery.store.tuple.TupleLike;
5
6import java.util.Optional;
7import java.util.stream.Stream;
8
9public interface ResultSet {
10 boolean hasResult();
11
12 boolean hasResult(Tuple parameters);
13
14 Optional<TupleLike> oneResult();
15
16 Optional<TupleLike> oneResult(Tuple parameters);
17
18 Stream<TupleLike> allResults();
19
20 Stream<TupleLike> allResults(Tuple parameters);
21
22 int countResults();
23
24 int countResults(Tuple parameters);
25}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java
new file mode 100644
index 00000000..2eb87649
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java
@@ -0,0 +1,58 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.literal.ConstantLiteral;
4import tools.refinery.store.query.literal.EquivalenceLiteral;
5
6import java.util.Objects;
7
8public class Variable {
9 private final String name;
10 private final String uniqueName;
11
12 public Variable() {
13 this(null);
14 }
15
16 public Variable(String name) {
17 super();
18 this.name = name;
19 this.uniqueName = DnfUtils.generateUniqueName(name);
20
21 }
22 public String getName() {
23 return name;
24 }
25
26 public String getUniqueName() {
27 return uniqueName;
28 }
29
30 public boolean isNamed() {
31 return name != null;
32 }
33
34 public ConstantLiteral isConstant(int value) {
35 return new ConstantLiteral(this, value);
36 }
37
38 public EquivalenceLiteral isEquivalent(Variable other) {
39 return new EquivalenceLiteral(true, this, other);
40 }
41
42 public EquivalenceLiteral notEquivalent(Variable other) {
43 return new EquivalenceLiteral(false, this, other);
44 }
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 Variable variable = (Variable) o;
51 return Objects.equals(uniqueName, variable.uniqueName);
52 }
53
54 @Override
55 public int hashCode() {
56 return Objects.hash(uniqueName);
57 }
58}
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
new file mode 100644
index 00000000..fd2f1eec
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
@@ -0,0 +1,37 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Map;
6import java.util.Set;
7
8public class BooleanLiteral implements Literal {
9 public static final BooleanLiteral TRUE = new BooleanLiteral(LiteralReduction.ALWAYS_TRUE);
10 public static final BooleanLiteral FALSE = new BooleanLiteral(LiteralReduction.ALWAYS_FALSE);
11
12 private final LiteralReduction reduction;
13
14 private BooleanLiteral(LiteralReduction reduction) {
15 this.reduction = reduction;
16 }
17
18 @Override
19 public void collectAllVariables(Set<Variable> variables) {
20 // No variables to collect.
21 }
22
23 @Override
24 public Literal substitute(Map<Variable, Variable> substitution) {
25 // No variables to substitute.
26 return this;
27 }
28
29 @Override
30 public LiteralReduction getReduction() {
31 return reduction;
32 }
33
34 public static BooleanLiteral fromBoolean(boolean value) {
35 return value ? TRUE : FALSE;
36 }
37}
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
new file mode 100644
index 00000000..5e1ae94d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
@@ -0,0 +1,66 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.DnfUtils;
4import tools.refinery.store.query.RelationLike;
5import tools.refinery.store.query.Variable;
6
7import java.util.List;
8import java.util.Map;
9import java.util.Objects;
10import java.util.Set;
11
12public abstract class CallLiteral<T extends RelationLike> implements Literal {
13 private final CallPolarity polarity;
14 private final T target;
15 private final List<Variable> arguments;
16
17 protected CallLiteral(CallPolarity polarity, T target, List<Variable> arguments) {
18 if (arguments.size() != target.arity()) {
19 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(),
20 target.arity(), arguments.size()));
21 }
22 if (polarity.isTransitive() && target.arity() != 2) {
23 throw new IllegalArgumentException("Transitive closures can only take binary relations");
24 }
25 this.polarity = polarity;
26 this.target = target;
27 this.arguments = arguments;
28 }
29
30 public CallPolarity getPolarity() {
31 return polarity;
32 }
33
34 public T getTarget() {
35 return target;
36 }
37
38 public List<Variable> getArguments() {
39 return arguments;
40 }
41
42 @Override
43 public void collectAllVariables(Set<Variable> variables) {
44 if (polarity.isPositive()) {
45 variables.addAll(arguments);
46 }
47 }
48
49 protected List<Variable> substituteArguments(Map<Variable, Variable> substitution) {
50 return arguments.stream().map(variable -> DnfUtils.maybeSubstitute(variable, substitution)).toList();
51 }
52
53 @Override
54 public boolean equals(Object o) {
55 if (this == o) return true;
56 if (o == null || getClass() != o.getClass()) return false;
57 CallLiteral<?> callAtom = (CallLiteral<?>) o;
58 return polarity == callAtom.polarity && Objects.equals(target, callAtom.target) &&
59 Objects.equals(arguments, callAtom.arguments);
60 }
61
62 @Override
63 public int hashCode() {
64 return Objects.hash(polarity, target, arguments);
65 }
66}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
new file mode 100644
index 00000000..84b4b771
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
@@ -0,0 +1,32 @@
1package tools.refinery.store.query.literal;
2
3public enum CallPolarity {
4 POSITIVE(true, false),
5 NEGATIVE(false, false),
6 TRANSITIVE(true, true);
7
8 private final boolean positive;
9
10 private final boolean transitive;
11
12 CallPolarity(boolean positive, boolean transitive) {
13 this.positive = positive;
14 this.transitive = transitive;
15 }
16
17 public boolean isPositive() {
18 return positive;
19 }
20
21 public boolean isTransitive() {
22 return transitive;
23 }
24
25 public CallPolarity negate() {
26 return switch (this) {
27 case POSITIVE -> NEGATIVE;
28 case NEGATIVE -> POSITIVE;
29 case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated");
30 };
31 }
32}
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
new file mode 100644
index 00000000..746d23af
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
@@ -0,0 +1,19 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.DnfUtils;
4import tools.refinery.store.query.Variable;
5
6import java.util.Map;
7import java.util.Set;
8
9public record ConstantLiteral(Variable variable, int nodeId) implements Literal {
10 @Override
11 public void collectAllVariables(Set<Variable> variables) {
12 variables.add(variable);
13 }
14
15 @Override
16 public ConstantLiteral substitute(Map<Variable, Variable> substitution) {
17 return new ConstantLiteral(DnfUtils.maybeSubstitute(variable, substitution), nodeId);
18 }
19}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java
new file mode 100644
index 00000000..de6c6005
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java
@@ -0,0 +1,29 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Dnf;
4import tools.refinery.store.query.Variable;
5
6import java.util.List;
7import java.util.Map;
8
9public final class DnfCallLiteral extends CallLiteral<Dnf> implements PolarLiteral<DnfCallLiteral> {
10 public DnfCallLiteral(CallPolarity polarity, Dnf target, List<Variable> arguments) {
11 super(polarity, target, arguments);
12 }
13
14 @Override
15 public DnfCallLiteral substitute(Map<Variable, Variable> substitution) {
16 return new DnfCallLiteral(getPolarity(), getTarget(), substituteArguments(substitution));
17 }
18
19 @Override
20 public DnfCallLiteral negate() {
21 return new DnfCallLiteral(getPolarity().negate(), getTarget(), getArguments());
22 }
23
24 @Override
25 public LiteralReduction getReduction() {
26 var dnfReduction = getTarget().getReduction();
27 return getPolarity().isPositive() ? dnfReduction : dnfReduction.negate();
28 }
29}
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
new file mode 100644
index 00000000..f30179b2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
@@ -0,0 +1,35 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.DnfUtils;
4import tools.refinery.store.query.Variable;
5
6import java.util.Map;
7import java.util.Set;
8
9public record EquivalenceLiteral(boolean positive, Variable left, Variable right)
10 implements PolarLiteral<EquivalenceLiteral> {
11 @Override
12 public void collectAllVariables(Set<Variable> variables) {
13 variables.add(left);
14 variables.add(right);
15 }
16
17 @Override
18 public EquivalenceLiteral negate() {
19 return new EquivalenceLiteral(!positive, left, right);
20 }
21
22 @Override
23 public EquivalenceLiteral substitute(Map<Variable, Variable> substitution) {
24 return new EquivalenceLiteral(positive, DnfUtils.maybeSubstitute(left, substitution),
25 DnfUtils.maybeSubstitute(right, substitution));
26 }
27
28 @Override
29 public LiteralReduction getReduction() {
30 if (left.equals(right)) {
31 return positive ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE;
32 }
33 return LiteralReduction.NOT_REDUCIBLE;
34 }
35}
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
new file mode 100644
index 00000000..a6893acf
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Map;
6import java.util.Set;
7
8public interface Literal {
9 void collectAllVariables(Set<Variable> variables);
10
11 Literal substitute(Map<Variable, Variable> substitution);
12
13 default LiteralReduction getReduction() {
14 return LiteralReduction.NOT_REDUCIBLE;
15 }
16}
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/literal/LiteralReduction.java
new file mode 100644
index 00000000..146089f6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java
@@ -0,0 +1,26 @@
1package tools.refinery.store.query.literal;
2
3public enum LiteralReduction {
4 /**
5 * Signifies that a literal should be preserved in the clause.
6 */
7 NOT_REDUCIBLE,
8
9 /**
10 * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty).
11 */
12 ALWAYS_TRUE,
13
14 /**
15 * Signifies that the clause with the literal may be omitted entirely.
16 */
17 ALWAYS_FALSE;
18
19 public LiteralReduction negate() {
20 return switch (this) {
21 case NOT_REDUCIBLE -> NOT_REDUCIBLE;
22 case ALWAYS_TRUE -> ALWAYS_FALSE;
23 case ALWAYS_FALSE -> ALWAYS_TRUE;
24 };
25 }
26}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
new file mode 100644
index 00000000..2c7e893f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query.literal;
2
3public final class Literals {
4 private Literals() {
5 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
6 }
7
8 public static <T extends PolarLiteral<T>> T not(PolarLiteral<T> literal) {
9 return literal.negate();
10 }
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java
new file mode 100644
index 00000000..32523675
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java
@@ -0,0 +1,5 @@
1package tools.refinery.store.query.literal;
2
3public interface PolarLiteral<T extends PolarLiteral<T>> extends Literal {
4 T negate();
5}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java
new file mode 100644
index 00000000..4718b550
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java
@@ -0,0 +1,24 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Variable;
4import tools.refinery.store.query.view.AnyRelationView;
5
6import java.util.List;
7import java.util.Map;
8
9public final class RelationViewLiteral extends CallLiteral<AnyRelationView>
10 implements PolarLiteral<RelationViewLiteral> {
11 public RelationViewLiteral(CallPolarity polarity, AnyRelationView target, List<Variable> arguments) {
12 super(polarity, target, arguments);
13 }
14
15 @Override
16 public RelationViewLiteral substitute(Map<Variable, Variable> substitution) {
17 return new RelationViewLiteral(getPolarity(), getTarget(), substituteArguments(substitution));
18 }
19
20 @Override
21 public RelationViewLiteral negate() {
22 return new RelationViewLiteral(getPolarity().negate(), getTarget(), getArguments());
23 }
24}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java
new file mode 100644
index 00000000..328cde3a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java
@@ -0,0 +1,24 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.FunctionalDependency;
5import tools.refinery.store.representation.AnySymbol;
6import tools.refinery.store.query.RelationLike;
7
8import java.util.Set;
9
10public sealed interface AnyRelationView extends RelationLike permits RelationView {
11 AnySymbol getSymbol();
12
13 default Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
14 return Set.of();
15 }
16
17 default Set<RelationViewImplication> getImpliedRelationViews() {
18 return Set.of();
19 }
20
21 boolean get(Model model, Object[] tuple);
22
23 Iterable<Object[]> getAll(Model model);
24}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java
new file mode 100644
index 00000000..64c601bb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java
@@ -0,0 +1,49 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.tuple.Tuple;
4import tools.refinery.store.representation.Symbol;
5
6import java.util.Objects;
7import java.util.function.BiPredicate;
8import java.util.function.Predicate;
9
10public class FilteredRelationView<T> extends TuplePreservingRelationView<T> {
11 private final BiPredicate<Tuple, T> predicate;
12
13 public FilteredRelationView(Symbol<T> symbol, String name, BiPredicate<Tuple, T> predicate) {
14 super(symbol, name);
15 this.predicate = predicate;
16 }
17
18 public FilteredRelationView(Symbol<T> symbol, BiPredicate<Tuple, T> predicate) {
19 super(symbol);
20 this.predicate = predicate;
21 }
22
23 public FilteredRelationView(Symbol<T> symbol, String name, Predicate<T> predicate) {
24 this(symbol, name, (k, v) -> predicate.test(v));
25 }
26
27 public FilteredRelationView(Symbol<T> symbol, Predicate<T> predicate) {
28 this(symbol, (k, v) -> predicate.test(v));
29 }
30
31 @Override
32 public boolean filter(Tuple key, T value) {
33 return this.predicate.test(key, value);
34 }
35
36 @Override
37 public boolean equals(Object o) {
38 if (this == o) return true;
39 if (o == null || getClass() != o.getClass()) return false;
40 if (!super.equals(o)) return false;
41 FilteredRelationView<?> that = (FilteredRelationView<?>) o;
42 return Objects.equals(predicate, that.predicate);
43 }
44
45 @Override
46 public int hashCode() {
47 return Objects.hash(super.hashCode(), predicate);
48 }
49}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java
new file mode 100644
index 00000000..3d278a8b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java
@@ -0,0 +1,71 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.FunctionalDependency;
5import tools.refinery.store.representation.Symbol;
6import tools.refinery.store.tuple.Tuple;
7import tools.refinery.store.tuple.Tuple1;
8
9import java.util.Set;
10import java.util.stream.Collectors;
11import java.util.stream.IntStream;
12
13public final class FunctionalRelationView<T> extends RelationView<T> {
14 public FunctionalRelationView(Symbol<T> symbol, String name) {
15 super(symbol, name);
16 }
17
18 public FunctionalRelationView(Symbol<T> symbol) {
19 super(symbol);
20 }
21
22 @Override
23 public Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
24 var arity = getSymbol().arity();
25 var forEach = IntStream.range(0, arity).boxed().collect(Collectors.toUnmodifiableSet());
26 var unique = Set.of(arity);
27 return Set.of(new FunctionalDependency<>(forEach, unique));
28 }
29
30 @Override
31 public Set<RelationViewImplication> getImpliedRelationViews() {
32 var symbol = getSymbol();
33 var impliedIndices = IntStream.range(0, symbol.arity()).boxed().toList();
34 var keyOnlyRelationView = new KeyOnlyRelationView<>(symbol);
35 return Set.of(new RelationViewImplication(this, keyOnlyRelationView, impliedIndices));
36 }
37
38 @Override
39 public boolean filter(Tuple key, T value) {
40 return true;
41 }
42
43 @Override
44 public Object[] forwardMap(Tuple key, T value) {
45 int size = key.getSize();
46 Object[] result = new Object[size + 1];
47 for (int i = 0; i < size; i++) {
48 result[i] = Tuple.of(key.get(i));
49 }
50 result[key.getSize()] = value;
51 return result;
52 }
53
54 @Override
55 public boolean get(Model model, Object[] tuple) {
56 int[] content = new int[tuple.length - 1];
57 for (int i = 0; i < tuple.length - 1; i++) {
58 content[i] = ((Tuple1) tuple[i]).value0();
59 }
60 Tuple key = Tuple.of(content);
61 @SuppressWarnings("unchecked")
62 T valueInTuple = (T) tuple[tuple.length - 1];
63 T valueInMap = model.getInterpretation(getSymbol()).get(key);
64 return valueInTuple.equals(valueInMap);
65 }
66
67 @Override
68 public int arity() {
69 return getSymbol().arity() + 1;
70 }
71}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java
new file mode 100644
index 00000000..e1b2e45b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.tuple.Tuple;
5
6import java.util.Objects;
7
8public final class KeyOnlyRelationView<T> extends TuplePreservingRelationView<T> {
9 public static final String VIEW_NAME = "key";
10
11 private final T defaultValue;
12
13 public KeyOnlyRelationView(Symbol<T> symbol) {
14 super(symbol, VIEW_NAME);
15 defaultValue = symbol.defaultValue();
16 }
17
18 @Override
19 public boolean filter(Tuple key, T value) {
20 return !Objects.equals(value, defaultValue);
21 }
22
23 @Override
24 public boolean equals(Object o) {
25 if (this == o) return true;
26 if (o == null || getClass() != o.getClass()) return false;
27 if (!super.equals(o)) return false;
28 KeyOnlyRelationView<?> that = (KeyOnlyRelationView<?>) o;
29 return Objects.equals(defaultValue, that.defaultValue);
30 }
31
32 @Override
33 public int hashCode() {
34 return Objects.hash(super.hashCode(), defaultValue);
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java
new file mode 100644
index 00000000..2714a8c5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java
@@ -0,0 +1,82 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.query.Variable;
4import tools.refinery.store.map.CursorAsIterator;
5import tools.refinery.store.model.Model;
6import tools.refinery.store.query.literal.CallPolarity;
7import tools.refinery.store.query.literal.RelationViewLiteral;
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.tuple.Tuple;
10
11import java.util.List;
12import java.util.Objects;
13import java.util.UUID;
14
15/**
16 * Represents a view of a {@link Symbol} that can be queried.
17 *
18 * @param <T>
19 * @author Oszkar Semerath
20 */
21public abstract non-sealed class RelationView<T> implements AnyRelationView {
22 private final Symbol<T> symbol;
23
24 private final String name;
25
26 protected RelationView(Symbol<T> symbol, String name) {
27 this.symbol = symbol;
28 this.name = name;
29 }
30
31 protected RelationView(Symbol<T> representation) {
32 this(representation, UUID.randomUUID().toString());
33 }
34
35 @Override
36 public Symbol<T> getSymbol() {
37 return symbol;
38 }
39
40 @Override
41 public String name() {
42 return symbol.name() + "#" + name;
43 }
44
45 public abstract boolean filter(Tuple key, T value);
46
47 public abstract Object[] forwardMap(Tuple key, T value);
48
49 @Override
50 public Iterable<Object[]> getAll(Model model) {
51 return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter));
52 }
53
54 public RelationViewLiteral call(CallPolarity polarity, List<Variable> arguments) {
55 return new RelationViewLiteral(polarity, this, arguments);
56 }
57
58 public RelationViewLiteral call(CallPolarity polarity, Variable... arguments) {
59 return call(polarity, List.of(arguments));
60 }
61
62 public RelationViewLiteral call(Variable... arguments) {
63 return call(CallPolarity.POSITIVE, arguments);
64 }
65
66 public RelationViewLiteral callTransitive(Variable left, Variable right) {
67 return call(CallPolarity.TRANSITIVE, List.of(left, right));
68 }
69
70 @Override
71 public boolean equals(Object o) {
72 if (this == o) return true;
73 if (o == null || getClass() != o.getClass()) return false;
74 RelationView<?> that = (RelationView<?>) o;
75 return Objects.equals(symbol, that.symbol) && Objects.equals(name, that.name);
76 }
77
78 @Override
79 public int hashCode() {
80 return Objects.hash(symbol, name);
81 }
82}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java
new file mode 100644
index 00000000..2ba1fcc4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java
@@ -0,0 +1,19 @@
1package tools.refinery.store.query.view;
2
3import java.util.List;
4
5public record RelationViewImplication(AnyRelationView implyingRelationView, AnyRelationView impliedRelationView,
6 List<Integer> impliedIndices) {
7 public RelationViewImplication {
8 if (impliedIndices.size() != impliedRelationView.arity()) {
9 throw new IllegalArgumentException("Expected %d implied indices for %s, but %d are provided"
10 .formatted(impliedRelationView.arity(), impliedRelationView, impliedIndices.size()));
11 }
12 for (var index : impliedIndices) {
13 if (impliedRelationView.invalidIndex(index)) {
14 throw new IllegalArgumentException("%d is not a valid index for %s".formatted(index,
15 implyingRelationView));
16 }
17 }
18 }
19}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java
new file mode 100644
index 00000000..8cc4986e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java
@@ -0,0 +1,44 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.tuple.Tuple;
5import tools.refinery.store.tuple.Tuple1;
6import tools.refinery.store.representation.Symbol;
7
8public abstract class TuplePreservingRelationView<T> extends RelationView<T> {
9 protected TuplePreservingRelationView(Symbol<T> symbol, String name) {
10 super(symbol, name);
11 }
12
13 protected TuplePreservingRelationView(Symbol<T> symbol) {
14 super(symbol);
15 }
16
17 public Object[] forwardMap(Tuple key) {
18 Object[] result = new Object[key.getSize()];
19 for (int i = 0; i < key.getSize(); i++) {
20 result[i] = Tuple.of(key.get(i));
21 }
22 return result;
23 }
24
25 @Override
26 public Object[] forwardMap(Tuple key, T value) {
27 return forwardMap(key);
28 }
29
30 @Override
31 public boolean get(Model model, Object[] tuple) {
32 int[] content = new int[tuple.length];
33 for (int i = 0; i < tuple.length; i++) {
34 content[i] = ((Tuple1) tuple[i]).value0();
35 }
36 Tuple key = Tuple.of(content);
37 T value = model.getInterpretation(getSymbol()).get(key);
38 return filter(key, value);
39 }
40
41 public int arity() {
42 return this.getSymbol().arity();
43 }
44}