aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/logic/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/logic/src/main')
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/AbstractDomain.java37
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/AnyAbstractDomain.java12
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/Constraint.java82
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/InvalidQueryException.java23
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/AbstractQueryBuilder.java175
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/AnyQuery.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/ClausePostProcessor.java362
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/Dnf.java235
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfBuilder.java225
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfClause.java37
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfPostProcessor.java112
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfUtils.java24
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalDependency.java22
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQuery.java126
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQueryBuilder.java29
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/InvalidClauseException.java35
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/Query.java202
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/QueryBuilder.java27
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/RelationalQuery.java77
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/SymbolicParameter.java53
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback0.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data0.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data1.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data0.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data1.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data2.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data0.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data1.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data2.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data3.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data0.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data1.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data2.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data3.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data4.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback0.java14
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback1.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback2.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback3.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback4.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback0.java13
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback1.java14
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback2.java14
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback3.java14
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback4.java14
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/equality/DeepDnfEqualityChecker.java77
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/equality/DnfEqualityChecker.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralEqualityHelper.java27
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralHashCodeHelper.java17
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralEqualityHelper.java59
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralHashCodeHelper.java42
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCallLiteral.java135
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCountLiteral.java107
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractLiteral.java34
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/AggregationLiteral.java143
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/AssignLiteral.java88
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/BooleanLiteral.java69
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/CallLiteral.java134
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/CallPolarity.java39
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/CanNegate.java10
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/CheckLiteral.java95
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/Connectivity.java18
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/ConstantLiteral.java73
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/CountLiteral.java45
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/EquivalenceLiteral.java100
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/LeftJoinLiteral.java140
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/Literal.java32
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/Literals.java22
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/Reduction.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/literal/RepresentativeElectionLiteral.java119
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/rewriter/AbstractRecursiveRewriter.java26
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/rewriter/ClauseInputParameterResolver.java160
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/rewriter/CompositeRewriter.java29
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DnfRewriter.java24
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DuplicateDnfRemover.java98
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/rewriter/InputParameterResolver.java51
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/substitution/MapBasedSubstitution.java18
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/substitution/RenewingSubstitution.java20
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/substitution/StatelessSubstitution.java23
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/substitution/Substitution.java29
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/substitution/SubstitutionBuilder.java79
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/AbstractTerm.java47
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/Aggregator.java18
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/AnyDataVariable.java55
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/AnyTerm.java24
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/AssignedValue.java13
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/BinaryTerm.java106
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/ConstantTerm.java67
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/DataVariable.java103
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/ExtremeValueAggregator.java108
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/NodeVariable.java71
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/Parameter.java68
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/ParameterDirection.java22
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregate.java22
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregator.java28
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/StatelessAggregator.java25
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/Term.java26
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/UnaryTerm.java74
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/Variable.java88
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolAndTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolBinaryTerm.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolNotTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolOrTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolTerms.java35
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolXorTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityDomain.java69
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityInterval.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervals.java54
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityInterval.java74
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/NonEmptyCardinalityInterval.java109
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/ComparisonTerm.java19
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/EqTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterEqTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessEqTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/NotEqTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntAddTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntBinaryTerm.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntDivTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMaxTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinusTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMulTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPlusTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPowTerm.java43
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSubTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSumAggregator.java40
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntTerms.java95
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntUnaryTerm.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/int_/RealToIntTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/IntToRealTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealAddTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealBinaryTerm.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealDivTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMaxTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinusTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMulTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPlusTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPowTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSubTerm.java31
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSumAggregator.java90
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealTerms.java95
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealUnaryTerm.java15
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValue.java76
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValueDomain.java69
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinality.java83
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UnboundedUpperCardinality.java66
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalities.java38
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinality.java32
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityAddTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityBinaryTerm.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMaxTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMinTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMulTerm.java30
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregator.java84
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTerms.java71
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/util/CycleDetectingMapper.java61
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/valuation/MapBasedValuation.java22
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/valuation/RestrictedValuation.java21
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/valuation/SubstitutedValuation.java16
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/valuation/Valuation.java37
-rw-r--r--subprojects/logic/src/main/java/tools/refinery/logic/valuation/ValuationBuilder.java40
164 files changed, 8065 insertions, 0 deletions
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/AbstractDomain.java b/subprojects/logic/src/main/java/tools/refinery/logic/AbstractDomain.java
new file mode 100644
index 00000000..607caa48
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/AbstractDomain.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic;
7
8import java.util.Objects;
9import java.util.Optional;
10
11public non-sealed interface AbstractDomain<A, C> extends AnyAbstractDomain {
12 @Override
13 Class<A> abstractType();
14
15 @Override
16 Class<C> concreteType();
17
18 A toAbstract(C concreteValue);
19
20 Optional<C> toConcrete(A abstractValue);
21
22 default boolean isConcrete(A abstractValue) {
23 return toConcrete(abstractValue).isPresent();
24 }
25
26 default boolean isRefinement(A originalValue, A refinedValue) {
27 return Objects.equals(commonRefinement(originalValue, refinedValue), refinedValue);
28 }
29
30 A commonRefinement(A leftValue, A rightValue);
31
32 A commonAncestor(A leftValue, A rightValue);
33
34 A unknown();
35
36 boolean isError(A abstractValue);
37}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/AnyAbstractDomain.java b/subprojects/logic/src/main/java/tools/refinery/logic/AnyAbstractDomain.java
new file mode 100644
index 00000000..a296f4b2
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/AnyAbstractDomain.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic;
7
8public sealed interface AnyAbstractDomain permits AbstractDomain {
9 Class<?> abstractType();
10
11 Class<?> concreteType();
12}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/Constraint.java b/subprojects/logic/src/main/java/tools/refinery/logic/Constraint.java
new file mode 100644
index 00000000..89c8760b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/Constraint.java
@@ -0,0 +1,82 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.literal.*;
10import tools.refinery.logic.term.*;
11
12import java.util.List;
13
14public interface Constraint {
15 String name();
16
17 List<Parameter> getParameters();
18
19 default int arity() {
20 return getParameters().size();
21 }
22
23 default boolean invalidIndex(int i) {
24 return i < 0 || i >= arity();
25 }
26
27 default Reduction getReduction() {
28 return Reduction.NOT_REDUCIBLE;
29 }
30
31 default boolean equals(LiteralEqualityHelper helper, Constraint other) {
32 return equals(other);
33 }
34
35 default String toReferenceString() {
36 return name();
37 }
38
39 default CallLiteral call(CallPolarity polarity, List<Variable> arguments) {
40 return new CallLiteral(polarity, this, arguments);
41 }
42
43 default CallLiteral call(CallPolarity polarity, Variable... arguments) {
44 return call(polarity, List.of(arguments));
45 }
46
47 default CallLiteral call(Variable... arguments) {
48 return call(CallPolarity.POSITIVE, arguments);
49 }
50
51 default CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
52 return call(CallPolarity.TRANSITIVE, List.of(left, right));
53 }
54
55 default AssignedValue<Integer> count(List<Variable> arguments) {
56 return targetVariable -> new CountLiteral(targetVariable, this, arguments);
57 }
58
59 default AssignedValue<Integer> count(Variable... arguments) {
60 return count(List.of(arguments));
61 }
62
63 default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
64 List<Variable> arguments) {
65 return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments);
66 }
67
68 default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
69 Variable... arguments) {
70 return aggregateBy(inputVariable, aggregator, List.of(arguments));
71 }
72
73 default <T> AssignedValue<T> leftJoinBy(DataVariable<T> placeholderVariable, T defaultValue,
74 List<Variable> arguments) {
75 return targetVariable -> new LeftJoinLiteral<>(targetVariable, placeholderVariable, defaultValue, this,
76 arguments);
77 }
78
79 default <T> AssignedValue<T> leftJoinBy(DataVariable<T> inputVariable, T defaultValue, Variable... arguments) {
80 return leftJoinBy(inputVariable, defaultValue, List.of(arguments));
81 }
82}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/InvalidQueryException.java b/subprojects/logic/src/main/java/tools/refinery/logic/InvalidQueryException.java
new file mode 100644
index 00000000..b5460e0d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/InvalidQueryException.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic;
7
8public class InvalidQueryException extends RuntimeException {
9 public InvalidQueryException() {
10 }
11
12 public InvalidQueryException(String message) {
13 super(message);
14 }
15
16 public InvalidQueryException(String message, Throwable cause) {
17 super(message, cause);
18 }
19
20 public InvalidQueryException(Throwable cause) {
21 super(cause);
22 }
23}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/AbstractQueryBuilder.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/AbstractQueryBuilder.java
new file mode 100644
index 00000000..68712b98
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/AbstractQueryBuilder.java
@@ -0,0 +1,175 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.dnf.callback.*;
9import tools.refinery.logic.literal.Literal;
10import tools.refinery.logic.term.NodeVariable;
11import tools.refinery.logic.term.ParameterDirection;
12import tools.refinery.logic.term.Variable;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Set;
17
18public abstract class AbstractQueryBuilder<T extends AbstractQueryBuilder<T>> {
19 protected final DnfBuilder dnfBuilder;
20
21 protected AbstractQueryBuilder(DnfBuilder dnfBuilder) {
22 this.dnfBuilder = dnfBuilder;
23 }
24
25 protected abstract T self();
26
27 public NodeVariable parameter() {
28 return dnfBuilder.parameter();
29 }
30
31 public NodeVariable parameter(String name) {
32 return dnfBuilder.parameter(name);
33 }
34
35 public NodeVariable parameter(ParameterDirection direction) {
36 return dnfBuilder.parameter(direction);
37 }
38
39 public NodeVariable parameter(String name, ParameterDirection direction) {
40 return dnfBuilder.parameter(name, direction);
41 }
42
43 public T parameter(NodeVariable variable) {
44 dnfBuilder.parameter(variable);
45 return self();
46 }
47
48 public T parameter(NodeVariable variable, ParameterDirection direction) {
49 dnfBuilder.parameter(variable, direction);
50 return self();
51 }
52
53 public T parameters(NodeVariable... variables) {
54 dnfBuilder.parameters(variables);
55 return self();
56 }
57
58 public T parameters(List<NodeVariable> variables) {
59 dnfBuilder.parameters(variables);
60 return self();
61 }
62
63 public T parameters(List<NodeVariable> variables, ParameterDirection direction) {
64 dnfBuilder.parameters(variables, direction);
65 return self();
66 }
67
68 public T symbolicParameters(List<SymbolicParameter> parameters) {
69 dnfBuilder.symbolicParameters(parameters);
70 return self();
71 }
72
73 public T functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
74 dnfBuilder.functionalDependencies(functionalDependencies);
75 return self();
76 }
77
78 public T functionalDependency(FunctionalDependency<Variable> functionalDependency) {
79 dnfBuilder.functionalDependency(functionalDependency);
80 return self();
81 }
82
83 public T functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
84 dnfBuilder.functionalDependency(forEach, unique);
85 return self();
86 }
87
88 public T clause(ClauseCallback0 callback) {
89 dnfBuilder.clause(callback);
90 return self();
91 }
92
93 public T clause(ClauseCallback1Data0 callback) {
94 dnfBuilder.clause(callback);
95 return self();
96 }
97
98 public <U1> T clause(Class<U1> type1, ClauseCallback1Data1<U1> callback) {
99 dnfBuilder.clause(type1, callback);
100 return self();
101 }
102
103 public T clause(ClauseCallback2Data0 callback) {
104 dnfBuilder.clause(callback);
105 return self();
106 }
107
108 public <U1> T clause(Class<U1> type1, ClauseCallback2Data1<U1> callback) {
109 dnfBuilder.clause(type1, callback);
110 return self();
111 }
112
113 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback2Data2<U1, U2> callback) {
114 dnfBuilder.clause(type1, type2, callback);
115 return self();
116 }
117
118 public T clause(ClauseCallback3Data0 callback) {
119 dnfBuilder.clause(callback);
120 return self();
121 }
122
123 public <U1> T clause(Class<U1> type1, ClauseCallback3Data1<U1> callback) {
124 dnfBuilder.clause(type1, callback);
125 return self();
126 }
127
128 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback3Data2<U1, U2> callback) {
129 dnfBuilder.clause(type1, type2, callback);
130 return self();
131 }
132
133 public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3,
134 ClauseCallback3Data3<U1, U2, U3> callback) {
135 dnfBuilder.clause(type1, type2, type3, callback);
136 return self();
137 }
138
139 public T clause(ClauseCallback4Data0 callback) {
140 dnfBuilder.clause(callback);
141 return self();
142 }
143
144 public <U1> T clause(Class<U1> type1, ClauseCallback4Data1<U1> callback) {
145 dnfBuilder.clause(type1, callback);
146 return self();
147 }
148
149 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback4Data2<U1, U2> callback) {
150 dnfBuilder.clause(type1, type2, callback);
151 return self();
152 }
153
154 public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3,
155 ClauseCallback4Data3<U1, U2, U3> callback) {
156 dnfBuilder.clause(type1, type2, type3, callback);
157 return self();
158 }
159
160 public <U1, U2, U3, U4> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3, Class<U4> type4,
161 ClauseCallback4Data4<U1, U2, U3, U4> callback) {
162 dnfBuilder.clause(type1, type2, type3, type4, callback);
163 return self();
164 }
165
166 public T clause(Literal... literals) {
167 dnfBuilder.clause(literals);
168 return self();
169 }
170
171 public T clause(Collection<? extends Literal> literals) {
172 dnfBuilder.clause(literals);
173 return self();
174 }
175}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/AnyQuery.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/AnyQuery.java
new file mode 100644
index 00000000..aebfd73f
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/AnyQuery.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8public sealed interface AnyQuery permits Query {
9 String name();
10
11 int arity();
12
13 Class<?> valueType();
14
15 Dnf getDnf();
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/ClausePostProcessor.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/ClausePostProcessor.java
new file mode 100644
index 00000000..00d15a0c
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/ClausePostProcessor.java
@@ -0,0 +1,362 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.InvalidQueryException;
11import tools.refinery.logic.literal.*;
12import tools.refinery.logic.substitution.MapBasedSubstitution;
13import tools.refinery.logic.substitution.StatelessSubstitution;
14import tools.refinery.logic.substitution.Substitution;
15import tools.refinery.logic.term.ParameterDirection;
16import tools.refinery.logic.term.Variable;
17
18import java.util.*;
19import java.util.function.Function;
20
21class ClausePostProcessor {
22 private final Map<Variable, ParameterInfo> parameters;
23 private final List<Literal> literals;
24 private final Map<Variable, Variable> representatives = new LinkedHashMap<>();
25 private final Map<Variable, Set<Variable>> equivalencePartition = new HashMap<>();
26 private List<Literal> substitutedLiterals;
27 private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>();
28 private Set<Variable> positiveVariables;
29 private Map<Variable, Set<SortableLiteral>> variableToLiteralInputMap;
30 private PriorityQueue<SortableLiteral> literalsWithAllInputsBound;
31 private LinkedHashSet<Literal> topologicallySortedLiterals;
32
33 public ClausePostProcessor(Map<Variable, ParameterInfo> parameters, List<Literal> literals) {
34 this.parameters = parameters;
35 this.literals = literals;
36 }
37
38 public Result postProcessClause() {
39 mergeEquivalentNodeVariables();
40 substitutedLiterals = new ArrayList<>(literals.size());
41 keepParameterEquivalences();
42 substituteLiterals();
43 computeExistentiallyQuantifiedVariables();
44 computePositiveVariables();
45 validatePositiveRepresentatives();
46 validatePrivateVariables();
47 topologicallySortLiterals();
48 var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size());
49 for (var literal : topologicallySortedLiterals) {
50 var reducedLiteral = literal.reduce();
51 if (BooleanLiteral.FALSE.equals(reducedLiteral)) {
52 return ConstantResult.ALWAYS_FALSE;
53 } else if (!BooleanLiteral.TRUE.equals(reducedLiteral)) {
54 filteredLiterals.add(reducedLiteral);
55 }
56 }
57 if (filteredLiterals.isEmpty()) {
58 return ConstantResult.ALWAYS_TRUE;
59 }
60 if (hasContradictoryCall(filteredLiterals)) {
61 return ConstantResult.ALWAYS_FALSE;
62 }
63 var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables),
64 Collections.unmodifiableList(filteredLiterals));
65 return new ClauseResult(clause);
66 }
67
68 private void mergeEquivalentNodeVariables() {
69 for (var literal : literals) {
70 if (isPositiveEquivalence(literal)) {
71 var equivalenceLiteral = (EquivalenceLiteral) literal;
72 mergeVariables(equivalenceLiteral.getLeft(), equivalenceLiteral.getRight());
73 }
74 }
75 }
76
77 private static boolean isPositiveEquivalence(Literal literal) {
78 return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.isPositive();
79 }
80
81 private void mergeVariables(Variable left, Variable right) {
82 var leftRepresentative = getRepresentative(left);
83 var rightRepresentative = getRepresentative(right);
84 var leftInfo = parameters.get(leftRepresentative);
85 var rightInfo = parameters.get(rightRepresentative);
86 if (leftInfo != null && (rightInfo == null || leftInfo.index() <= rightInfo.index())) {
87 // Prefer the variable occurring earlier in the parameter list as a representative.
88 doMergeVariables(leftRepresentative, rightRepresentative);
89 } else {
90 doMergeVariables(rightRepresentative, leftRepresentative);
91 }
92 }
93
94 private void doMergeVariables(Variable parentRepresentative, Variable newChildRepresentative) {
95 var parentSet = getEquivalentVariables(parentRepresentative);
96 var childSet = getEquivalentVariables(newChildRepresentative);
97 parentSet.addAll(childSet);
98 equivalencePartition.remove(newChildRepresentative);
99 for (var childEquivalentNodeVariable : childSet) {
100 representatives.put(childEquivalentNodeVariable, parentRepresentative);
101 }
102 }
103
104 private Variable getRepresentative(Variable variable) {
105 return representatives.computeIfAbsent(variable, Function.identity());
106 }
107
108 private Set<Variable> getEquivalentVariables(Variable variable) {
109 var representative = getRepresentative(variable);
110 if (!representative.equals(variable)) {
111 throw new AssertionError("NodeVariable %s already has a representative %s"
112 .formatted(variable, representative));
113 }
114 return equivalencePartition.computeIfAbsent(variable, key -> {
115 var set = HashSet.<Variable>newHashSet(1);
116 set.add(key);
117 return set;
118 });
119 }
120
121 private void keepParameterEquivalences() {
122 for (var pair : representatives.entrySet()) {
123 var left = pair.getKey();
124 var right = pair.getValue();
125 if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) {
126 substitutedLiterals.add(new EquivalenceLiteral(true, left, right));
127 }
128 }
129 }
130
131 private void substituteLiterals() {
132 Substitution substitution;
133 if (representatives.isEmpty()) {
134 substitution = null;
135 } else {
136 substitution = new MapBasedSubstitution(Collections.unmodifiableMap(representatives),
137 StatelessSubstitution.IDENTITY);
138 }
139 for (var literal : literals) {
140 if (isPositiveEquivalence(literal)) {
141 // We already retained all equivalences that cannot be replaced with substitutions in
142 // {@link#keepParameterEquivalences()}.
143 continue;
144 }
145 var substitutedLiteral = substitution == null ? literal : literal.substitute(substitution);
146 substitutedLiterals.add(substitutedLiteral);
147 }
148 }
149
150 private void computeExistentiallyQuantifiedVariables() {
151 for (var literal : substitutedLiterals) {
152 existentiallyQuantifiedVariables.addAll(literal.getOutputVariables());
153 }
154 }
155
156 private void computePositiveVariables() {
157 positiveVariables = new LinkedHashSet<>();
158 for (var pair : parameters.entrySet()) {
159 var variable = pair.getKey();
160 if (pair.getValue().direction() == ParameterDirection.IN) {
161 // Inputs count as positive, because they are already bound when we evaluate literals.
162 positiveVariables.add(variable);
163 } else if (!existentiallyQuantifiedVariables.contains(variable)) {
164 throw new InvalidQueryException("Unbound %s parameter %s"
165 .formatted(ParameterDirection.OUT, variable));
166 }
167 }
168 positiveVariables.addAll(existentiallyQuantifiedVariables);
169 }
170
171 private void validatePositiveRepresentatives() {
172 for (var pair : equivalencePartition.entrySet()) {
173 var representative = pair.getKey();
174 if (!positiveVariables.contains(representative)) {
175 var variableSet = pair.getValue();
176 throw new InvalidQueryException("Variables %s were merged by equivalence but are not bound"
177 .formatted(variableSet));
178 }
179 }
180 }
181
182 private void validatePrivateVariables() {
183 var negativeVariablesMap = new HashMap<Variable, Literal>();
184 for (var literal : substitutedLiterals) {
185 for (var variable : literal.getPrivateVariables(positiveVariables)) {
186 var oldLiteral = negativeVariablesMap.put(variable, literal);
187 if (oldLiteral != null) {
188 throw new InvalidQueryException("Unbound variable %s appears in multiple literals %s and %s"
189 .formatted(variable, oldLiteral, literal));
190 }
191 }
192 }
193 }
194
195 private void topologicallySortLiterals() {
196 topologicallySortedLiterals = LinkedHashSet.newLinkedHashSet(substitutedLiterals.size());
197 variableToLiteralInputMap = new HashMap<>();
198 literalsWithAllInputsBound = new PriorityQueue<>();
199 int size = substitutedLiterals.size();
200 for (int i = 0; i < size; i++) {
201 var literal = substitutedLiterals.get(i);
202 var sortableLiteral = new SortableLiteral(i, literal);
203 sortableLiteral.enqueue();
204 }
205 while (!literalsWithAllInputsBound.isEmpty()) {
206 var variable = literalsWithAllInputsBound.remove();
207 variable.addToSortedLiterals();
208 }
209 if (!variableToLiteralInputMap.isEmpty()) {
210 throw new InvalidQueryException("Unbound input variables %s"
211 .formatted(variableToLiteralInputMap.keySet()));
212 }
213 }
214
215 private boolean hasContradictoryCall(Collection<Literal> filteredLiterals) {
216 var positiveCalls = new HashMap<Constraint, Set<CallLiteral>>();
217 for (var literal : filteredLiterals) {
218 if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.POSITIVE) {
219 var callsOfTarget = positiveCalls.computeIfAbsent(callLiteral.getTarget(), key -> new HashSet<>());
220 callsOfTarget.add(callLiteral);
221 }
222 }
223 for (var literal : filteredLiterals) {
224 if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.NEGATIVE) {
225 var callsOfTarget = positiveCalls.get(callLiteral.getTarget());
226 if (contradicts(callLiteral, callsOfTarget)) {
227 return true;
228 }
229 }
230 }
231 return false;
232 }
233
234 private boolean contradicts(CallLiteral negativeCall, Collection<CallLiteral> positiveCalls) {
235 if (positiveCalls == null) {
236 return false;
237 }
238 for (var positiveCall : positiveCalls) {
239 if (contradicts(negativeCall, positiveCall)) {
240 return true;
241 }
242 }
243 return false;
244 }
245
246 private boolean contradicts(CallLiteral negativeCall, CallLiteral positiveCall) {
247 var privateVariables = negativeCall.getPrivateVariables(positiveVariables);
248 var negativeArguments = negativeCall.getArguments();
249 var positiveArguments = positiveCall.getArguments();
250 int arity = negativeArguments.size();
251 for (int i = 0; i < arity; i++) {
252 var negativeArgument = negativeArguments.get(i);
253 if (privateVariables.contains(negativeArgument)) {
254 continue;
255 }
256 var positiveArgument = positiveArguments.get(i);
257 if (!negativeArgument.equals(positiveArgument)) {
258 return false;
259 }
260 }
261 return true;
262 }
263
264 private class SortableLiteral implements Comparable<SortableLiteral> {
265 private final int index;
266 private final Literal literal;
267 private final Set<Variable> remainingInputs;
268
269 private SortableLiteral(int index, Literal literal) {
270 this.index = index;
271 this.literal = literal;
272 remainingInputs = new HashSet<>(literal.getInputVariables(positiveVariables));
273 for (var pair : parameters.entrySet()) {
274 if (pair.getValue().direction() == ParameterDirection.IN) {
275 remainingInputs.remove(pair.getKey());
276 }
277 }
278 }
279
280 public void enqueue() {
281 if (allInputsBound()) {
282 addToAllInputsBoundQueue();
283 } else {
284 addToVariableToLiteralInputMap();
285 }
286 }
287
288 private void bindVariable(Variable input) {
289 if (!remainingInputs.remove(input)) {
290 throw new AssertionError("Already processed input %s of literal %s".formatted(input, literal));
291 }
292 if (allInputsBound()) {
293 addToAllInputsBoundQueue();
294 }
295 }
296
297 private boolean allInputsBound() {
298 return remainingInputs.isEmpty();
299 }
300
301 private void addToVariableToLiteralInputMap() {
302 for (var inputVariable : remainingInputs) {
303 var literalSetForInput = variableToLiteralInputMap.computeIfAbsent(
304 inputVariable, key -> new HashSet<>());
305 literalSetForInput.add(this);
306 }
307 }
308
309 private void addToAllInputsBoundQueue() {
310 literalsWithAllInputsBound.add(this);
311 }
312
313 public void addToSortedLiterals() {
314 if (!allInputsBound()) {
315 throw new AssertionError("Inputs %s of %s are not yet bound".formatted(remainingInputs, literal));
316 }
317 // Add literal if we haven't yet added a duplicate of this literal.
318 topologicallySortedLiterals.add(literal);
319 for (var variable : literal.getOutputVariables()) {
320 var literalSetForInput = variableToLiteralInputMap.remove(variable);
321 if (literalSetForInput == null) {
322 continue;
323 }
324 for (var targetSortableLiteral : literalSetForInput) {
325 targetSortableLiteral.bindVariable(variable);
326 }
327 }
328 }
329
330 @Override
331 public int compareTo(@NotNull ClausePostProcessor.SortableLiteral other) {
332 return Integer.compare(index, other.index);
333 }
334
335 @Override
336 public boolean equals(Object o) {
337 if (this == o) return true;
338 if (o == null || getClass() != o.getClass()) return false;
339 SortableLiteral that = (SortableLiteral) o;
340 return index == that.index && Objects.equals(literal, that.literal);
341 }
342
343 @Override
344 public int hashCode() {
345 return Objects.hash(index, literal);
346 }
347 }
348
349 public sealed interface Result permits ClauseResult, ConstantResult {
350 }
351
352 public record ClauseResult(DnfClause clause) implements Result {
353 }
354
355 public enum ConstantResult implements Result {
356 ALWAYS_TRUE,
357 ALWAYS_FALSE
358 }
359
360 public record ParameterInfo(ParameterDirection direction, int index) {
361 }
362}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/Dnf.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/Dnf.java
new file mode 100644
index 00000000..0fc2a1cc
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/Dnf.java
@@ -0,0 +1,235 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.DnfEqualityChecker;
11import tools.refinery.logic.equality.LiteralEqualityHelper;
12import tools.refinery.logic.equality.SubstitutingLiteralEqualityHelper;
13import tools.refinery.logic.equality.SubstitutingLiteralHashCodeHelper;
14import tools.refinery.logic.literal.Reduction;
15import tools.refinery.logic.term.Parameter;
16import tools.refinery.logic.term.Variable;
17
18import java.util.Collection;
19import java.util.Collections;
20import java.util.List;
21import java.util.Set;
22import java.util.function.Consumer;
23import java.util.stream.Collectors;
24
25public final class Dnf implements Constraint {
26 private static final String INDENTATION = " ";
27
28 private final String name;
29 private final String uniqueName;
30 private final List<SymbolicParameter> symbolicParameters;
31 private final List<FunctionalDependency<Variable>> functionalDependencies;
32 private final List<DnfClause> clauses;
33
34 Dnf(String name, List<SymbolicParameter> symbolicParameters,
35 List<FunctionalDependency<Variable>> functionalDependencies, List<DnfClause> clauses) {
36 validateFunctionalDependencies(symbolicParameters, functionalDependencies);
37 this.name = name;
38 this.uniqueName = DnfUtils.generateUniqueName(name);
39 this.symbolicParameters = symbolicParameters;
40 this.functionalDependencies = functionalDependencies;
41 this.clauses = clauses;
42 }
43
44 private static void validateFunctionalDependencies(
45 Collection<SymbolicParameter> symbolicParameters,
46 Collection<FunctionalDependency<Variable>> functionalDependencies) {
47 var parameterSet = symbolicParameters.stream().map(SymbolicParameter::getVariable).collect(Collectors.toSet());
48 for (var functionalDependency : functionalDependencies) {
49 validateParameters(symbolicParameters, parameterSet, functionalDependency.forEach(), functionalDependency);
50 validateParameters(symbolicParameters, parameterSet, functionalDependency.unique(), functionalDependency);
51 }
52 }
53
54 private static void validateParameters(Collection<SymbolicParameter> symbolicParameters,
55 Set<Variable> parameterSet, Collection<Variable> toValidate,
56 FunctionalDependency<Variable> functionalDependency) {
57 for (var variable : toValidate) {
58 if (!parameterSet.contains(variable)) {
59 throw new InvalidQueryException(
60 "Variable %s of functional dependency %s does not appear in the parameter list %s"
61 .formatted(variable, functionalDependency, symbolicParameters));
62 }
63 }
64 }
65
66 @Override
67 public String name() {
68 return name == null ? uniqueName : name;
69 }
70
71 public boolean isExplicitlyNamed() {
72 return name != null;
73 }
74
75 public String getUniqueName() {
76 return uniqueName;
77 }
78
79 public List<SymbolicParameter> getSymbolicParameters() {
80 return symbolicParameters;
81 }
82
83 public List<Parameter> getParameters() {
84 return Collections.unmodifiableList(symbolicParameters);
85 }
86
87 public List<FunctionalDependency<Variable>> getFunctionalDependencies() {
88 return functionalDependencies;
89 }
90
91 @Override
92 public int arity() {
93 return symbolicParameters.size();
94 }
95
96 public List<DnfClause> getClauses() {
97 return clauses;
98 }
99
100 public RelationalQuery asRelation() {
101 return new RelationalQuery(this);
102 }
103
104 public <T> FunctionalQuery<T> asFunction(Class<T> type) {
105 return new FunctionalQuery<>(this, type);
106 }
107
108 @Override
109 public Reduction getReduction() {
110 if (clauses.isEmpty()) {
111 return Reduction.ALWAYS_FALSE;
112 }
113 for (var clause : clauses) {
114 if (clause.literals().isEmpty()) {
115 return Reduction.ALWAYS_TRUE;
116 }
117 }
118 return Reduction.NOT_REDUCIBLE;
119 }
120
121 public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) {
122 if (arity() != other.arity()) {
123 return false;
124 }
125 for (int i = 0; i < arity(); i++) {
126 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
127 return false;
128 }
129 }
130 int numClauses = clauses.size();
131 if (numClauses != other.clauses.size()) {
132 return false;
133 }
134 for (int i = 0; i < numClauses; i++) {
135 var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(callEqualityChecker, symbolicParameters,
136 other.symbolicParameters);
137 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) {
138 return false;
139 }
140 }
141 return true;
142 }
143
144 @Override
145 public boolean equals(LiteralEqualityHelper helper, Constraint other) {
146 if (other instanceof Dnf otherDnf) {
147 return helper.dnfEqual(this, otherDnf);
148 }
149 return false;
150 }
151
152 public int hashCodeWithSubstitution() {
153 var helper = new SubstitutingLiteralHashCodeHelper();
154 int result = 0;
155 for (var symbolicParameter : symbolicParameters) {
156 result = result * 31 + symbolicParameter.hashCodeWithSubstitution(helper);
157 }
158 for (var clause : clauses) {
159 result = result * 31 + clause.hashCodeWithSubstitution(helper);
160 }
161 return result;
162 }
163
164 @Override
165 public String toString() {
166 return "%s/%d".formatted(name(), arity());
167 }
168
169 @Override
170 public String toReferenceString() {
171 return "@Dnf " + name();
172 }
173
174 public String toDefinitionString() {
175 var builder = new StringBuilder();
176 builder.append("pred ").append(name()).append("(");
177 var parameterIterator = symbolicParameters.iterator();
178 if (parameterIterator.hasNext()) {
179 builder.append(parameterIterator.next());
180 while (parameterIterator.hasNext()) {
181 builder.append(", ").append(parameterIterator.next());
182 }
183 }
184 builder.append(") <->");
185 var clauseIterator = clauses.iterator();
186 if (clauseIterator.hasNext()) {
187 appendClause(clauseIterator.next(), builder);
188 while (clauseIterator.hasNext()) {
189 builder.append("\n;");
190 appendClause(clauseIterator.next(), builder);
191 }
192 } else {
193 builder.append("\n").append(INDENTATION).append("<no clauses>");
194 }
195 builder.append(".\n");
196 return builder.toString();
197 }
198
199 private static void appendClause(DnfClause clause, StringBuilder builder) {
200 var iterator = clause.literals().iterator();
201 if (!iterator.hasNext()) {
202 builder.append("\n").append(INDENTATION).append("<empty>");
203 return;
204 }
205 builder.append("\n").append(INDENTATION).append(iterator.next());
206 while (iterator.hasNext()) {
207 builder.append(",\n").append(INDENTATION).append(iterator.next());
208 }
209 }
210
211 public static DnfBuilder builder() {
212 return builder(null);
213 }
214
215 public static DnfBuilder builder(String name) {
216 return new DnfBuilder(name);
217 }
218
219 public static DnfBuilder builderFrom(Dnf original) {
220 var builder = builder(original.name());
221 builder.symbolicParameters(original.getSymbolicParameters());
222 builder.functionalDependencies(original.getFunctionalDependencies());
223 return builder;
224 }
225
226 public static Dnf of(Consumer<DnfBuilder> callback) {
227 return of(null, callback);
228 }
229
230 public static Dnf of(String name, Consumer<DnfBuilder> callback) {
231 var builder = builder(name);
232 callback.accept(builder);
233 return builder.build();
234 }
235}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfBuilder.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfBuilder.java
new file mode 100644
index 00000000..b58c5c45
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfBuilder.java
@@ -0,0 +1,225 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.dnf.callback.*;
10import tools.refinery.logic.literal.Literal;
11import tools.refinery.logic.term.*;
12
13import java.util.*;
14
15@SuppressWarnings("UnusedReturnValue")
16public final class DnfBuilder {
17 private final String name;
18 private final Set<Variable> parameterVariables = new LinkedHashSet<>();
19 private final List<SymbolicParameter> parameters = new ArrayList<>();
20 private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>();
21 private final List<List<Literal>> clauses = new ArrayList<>();
22
23 DnfBuilder(String name) {
24 this.name = name;
25 }
26
27 public NodeVariable parameter() {
28 return parameter((String) null);
29 }
30
31 public NodeVariable parameter(String name) {
32 return parameter(name, ParameterDirection.OUT);
33 }
34
35 public NodeVariable parameter(ParameterDirection direction) {
36 return parameter((String) null, direction);
37 }
38
39 public NodeVariable parameter(String name, ParameterDirection direction) {
40 var variable = Variable.of(name);
41 parameter(variable, direction);
42 return variable;
43 }
44
45 public <T> DataVariable<T> parameter(Class<T> type) {
46 return parameter(null, type);
47 }
48
49 public <T> DataVariable<T> parameter(String name, Class<T> type) {
50 return parameter(name, type, ParameterDirection.OUT);
51 }
52
53 public <T> DataVariable<T> parameter(Class<T> type, ParameterDirection direction) {
54 return parameter(null, type, direction);
55 }
56
57 public <T> DataVariable<T> parameter(String name, Class<T> type, ParameterDirection direction) {
58 var variable = Variable.of(name, type);
59 parameter(variable, direction);
60 return variable;
61 }
62
63 public Variable parameter(Parameter parameter) {
64 return parameter(null, parameter);
65 }
66
67 public Variable parameter(String name, Parameter parameter) {
68 var type = parameter.tryGetType();
69 if (type.isPresent()) {
70 return parameter(name, type.get(), parameter.getDirection());
71 }
72 return parameter(name, parameter.getDirection());
73 }
74
75 public DnfBuilder parameter(Variable variable) {
76 return parameter(variable, ParameterDirection.OUT);
77 }
78
79 public DnfBuilder parameter(Variable variable, ParameterDirection direction) {
80 return symbolicParameter(new SymbolicParameter(variable, direction));
81 }
82
83 public DnfBuilder parameters(Variable... variables) {
84 return parameters(List.of(variables));
85 }
86
87 public DnfBuilder parameters(Collection<? extends Variable> variables) {
88 return parameters(variables, ParameterDirection.OUT);
89 }
90
91 public DnfBuilder parameters(Collection<? extends Variable> variables, ParameterDirection direction) {
92 for (var variable : variables) {
93 parameter(variable, direction);
94 }
95 return this;
96 }
97
98 public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) {
99 var variable = symbolicParameter.getVariable();
100 if (!parameterVariables.add(variable)) {
101 throw new InvalidQueryException("Variable %s is already on the parameter list %s"
102 .formatted(variable, parameters));
103 }
104 parameters.add(symbolicParameter);
105 return this;
106 }
107
108 public DnfBuilder symbolicParameters(SymbolicParameter... symbolicParameters) {
109 return symbolicParameters(List.of(symbolicParameters));
110 }
111
112 public DnfBuilder symbolicParameters(Collection<SymbolicParameter> symbolicParameters) {
113 for (var symbolicParameter : symbolicParameters) {
114 symbolicParameter(symbolicParameter);
115 }
116 return this;
117 }
118
119 public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
120 this.functionalDependencies.addAll(functionalDependencies);
121 return this;
122 }
123
124 public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
125 functionalDependencies.add(functionalDependency);
126 return this;
127 }
128
129 public DnfBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
130 return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique)));
131 }
132
133 public DnfBuilder clause(ClauseCallback0 callback) {
134 return clause(callback.toLiterals());
135 }
136
137 public DnfBuilder clause(ClauseCallback1Data0 callback) {
138 return clause(callback.toLiterals(Variable.of("v1")));
139 }
140
141 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) {
142 return clause(callback.toLiterals(Variable.of("d1", type1)));
143 }
144
145 public DnfBuilder clause(ClauseCallback2Data0 callback) {
146 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2")));
147 }
148
149 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback2Data1<T> callback) {
150 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1)));
151 }
152
153 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback2Data2<T1, T2> callback) {
154 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2)));
155 }
156
157 public DnfBuilder clause(ClauseCallback3Data0 callback) {
158 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3")));
159 }
160
161 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback3Data1<T> callback) {
162 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1)));
163 }
164
165 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback3Data2<T1, T2> callback) {
166 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2)));
167 }
168
169 public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3,
170 ClauseCallback3Data3<T1, T2, T3> callback) {
171 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2),
172 Variable.of("d3", type3)));
173 }
174
175 public DnfBuilder clause(ClauseCallback4Data0 callback) {
176 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("v4")));
177 }
178
179 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback4Data1<T> callback) {
180 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("d1",
181 type1)));
182 }
183
184 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback4Data2<T1, T2> callback) {
185 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1),
186 Variable.of("d2", type2)));
187 }
188
189 public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3,
190 ClauseCallback4Data3<T1, T2, T3> callback) {
191 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2),
192 Variable.of("d3", type3)));
193 }
194
195 public <T1, T2, T3, T4> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3, Class<T4> type4,
196 ClauseCallback4Data4<T1, T2, T3, T4> callback) {
197 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2),
198 Variable.of("d3", type3), Variable.of("d4", type4)));
199 }
200
201 public DnfBuilder clause(Literal... literals) {
202 clause(List.of(literals));
203 return this;
204 }
205
206 public DnfBuilder clause(Collection<? extends Literal> literals) {
207 clauses.add(List.copyOf(literals));
208 return this;
209 }
210
211 <T> void output(DataVariable<T> outputVariable) {
212 // Copy parameter variables to exclude the newly added {@code outputVariable}.
213 var fromParameters = Set.copyOf(parameterVariables);
214 parameter(outputVariable, ParameterDirection.OUT);
215 functionalDependency(fromParameters, Set.of(outputVariable));
216 }
217
218 public Dnf build() {
219 var postProcessor = new DnfPostProcessor(parameters, clauses);
220 var postProcessedClauses = postProcessor.postProcessClauses();
221 return new Dnf(name, Collections.unmodifiableList(parameters),
222 Collections.unmodifiableList(functionalDependencies),
223 Collections.unmodifiableList(postProcessedClauses));
224 }
225}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfClause.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfClause.java
new file mode 100644
index 00000000..92755d4d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfClause.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.equality.LiteralHashCodeHelper;
10import tools.refinery.logic.literal.Literal;
11import tools.refinery.logic.term.Variable;
12
13import java.util.List;
14import java.util.Set;
15
16public record DnfClause(Set<Variable> positiveVariables, List<Literal> literals) {
17 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) {
18 int size = literals.size();
19 if (size != other.literals.size()) {
20 return false;
21 }
22 for (int i = 0; i < size; i++) {
23 if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) {
24 return false;
25 }
26 }
27 return true;
28 }
29
30 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
31 int result = 0;
32 for (var literal : literals) {
33 result = result * 31 + literal.hashCodeWithSubstitution(helper);
34 }
35 return result;
36 }
37}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfPostProcessor.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfPostProcessor.java
new file mode 100644
index 00000000..87d07187
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfPostProcessor.java
@@ -0,0 +1,112 @@
1/*
2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.equality.DnfEqualityChecker;
10import tools.refinery.logic.equality.SubstitutingLiteralEqualityHelper;
11import tools.refinery.logic.equality.SubstitutingLiteralHashCodeHelper;
12import tools.refinery.logic.literal.Literal;
13import tools.refinery.logic.term.ParameterDirection;
14import tools.refinery.logic.term.Variable;
15
16import java.util.*;
17
18class DnfPostProcessor {
19 private final List<SymbolicParameter> parameters;
20 private final List<List<Literal>> clauses;
21
22 public DnfPostProcessor(List<SymbolicParameter> parameters, List<List<Literal>> clauses) {
23 this.parameters = parameters;
24 this.clauses = clauses;
25 }
26
27 public List<DnfClause> postProcessClauses() {
28 var parameterInfoMap = getParameterInfoMap();
29 var postProcessedClauses = LinkedHashSet.<CanonicalClause>newLinkedHashSet(clauses.size());
30 int index = 0;
31 for (var literals : clauses) {
32 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
33 ClausePostProcessor.Result result;
34 try {
35 result = postProcessor.postProcessClause();
36 } catch (InvalidQueryException e) {
37 throw new InvalidClauseException(index, e);
38 }
39 if (result instanceof ClausePostProcessor.ClauseResult clauseResult) {
40 postProcessedClauses.add(new CanonicalClause(clauseResult.clause()));
41 } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) {
42 switch (constantResult) {
43 case ALWAYS_TRUE -> {
44 var inputVariables = getInputVariables();
45 return List.of(new DnfClause(inputVariables, List.of()));
46 }
47 case ALWAYS_FALSE -> {
48 // Skip this clause because it can never match.
49 }
50 default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " +
51 constantResult);
52 }
53 } else {
54 throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result);
55 }
56 index++;
57 }
58 return postProcessedClauses.stream().map(CanonicalClause::getDnfClause).toList();
59 }
60
61 private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() {
62 var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>();
63 int arity = parameters.size();
64 for (int i = 0; i < arity; i++) {
65 var parameter = parameters.get(i);
66 mutableParameterInfoMap.put(parameter.getVariable(),
67 new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i));
68 }
69 return Collections.unmodifiableMap(mutableParameterInfoMap);
70 }
71
72 private Set<Variable> getInputVariables() {
73 var inputParameters = new LinkedHashSet<Variable>();
74 for (var parameter : parameters) {
75 if (parameter.getDirection() == ParameterDirection.IN) {
76 inputParameters.add(parameter.getVariable());
77 }
78 }
79 return Collections.unmodifiableSet(inputParameters);
80 }
81
82 private class CanonicalClause {
83 private final DnfClause dnfClause;
84
85 public CanonicalClause(DnfClause dnfClause) {
86 this.dnfClause = dnfClause;
87 }
88
89 public DnfClause getDnfClause() {
90 return dnfClause;
91 }
92
93 @Override
94 public boolean equals(Object obj) {
95 if (this == obj) {
96 return true;
97 }
98 if (obj == null || getClass() != obj.getClass()) {
99 return false;
100 }
101 var otherCanonicalClause = (CanonicalClause) obj;
102 var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, parameters, parameters);
103 return dnfClause.equalsWithSubstitution(helper, otherCanonicalClause.dnfClause);
104 }
105
106 @Override
107 public int hashCode() {
108 var helper = new SubstitutingLiteralHashCodeHelper(parameters);
109 return dnfClause.hashCodeWithSubstitution(helper);
110 }
111 }
112}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfUtils.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfUtils.java
new file mode 100644
index 00000000..02a619a6
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfUtils.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import java.util.UUID;
9
10public final class DnfUtils {
11 private DnfUtils() {
12 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
13 }
14
15 public static String generateUniqueName(String originalName) {
16 UUID uuid = UUID.randomUUID();
17 String uniqueString = "_" + uuid.toString().replace('-', '_');
18 if (originalName == null) {
19 return uniqueString;
20 } else {
21 return originalName + uniqueString;
22 }
23 }
24}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalDependency.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalDependency.java
new file mode 100644
index 00000000..c3fc87ab
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalDependency.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.InvalidQueryException;
9
10import java.util.HashSet;
11import java.util.Set;
12
13public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) {
14 public FunctionalDependency {
15 var uniqueForEach = new HashSet<>(unique);
16 uniqueForEach.retainAll(forEach);
17 if (!uniqueForEach.isEmpty()) {
18 throw new InvalidQueryException("Variables %s appear on both sides of the functional dependency"
19 .formatted(uniqueForEach));
20 }
21 }
22}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQuery.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQuery.java
new file mode 100644
index 00000000..1df63fbd
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQuery.java
@@ -0,0 +1,126 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.literal.CallPolarity;
10import tools.refinery.logic.term.Aggregator;
11import tools.refinery.logic.term.AssignedValue;
12import tools.refinery.logic.term.NodeVariable;
13import tools.refinery.logic.term.Variable;
14
15import java.util.ArrayList;
16import java.util.List;
17import java.util.Objects;
18
19public final class FunctionalQuery<T> extends Query<T> {
20 private final Class<T> type;
21
22 FunctionalQuery(Dnf dnf, Class<T> type) {
23 super(dnf);
24 var parameters = dnf.getSymbolicParameters();
25 int outputIndex = dnf.arity() - 1;
26 for (int i = 0; i < outputIndex; i++) {
27 var parameter = parameters.get(i);
28 var parameterType = parameter.tryGetType();
29 if (parameterType.isPresent()) {
30 throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead"
31 .formatted(parameter, dnf, parameterType.get().getName()));
32 }
33 }
34 var outputParameter = parameters.get(outputIndex);
35 var outputParameterType = outputParameter.tryGetType();
36 if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) {
37 throw new InvalidQueryException("Expected parameter %s of %s to be %s, but got %s instead".formatted(
38 outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node")));
39 }
40 this.type = type;
41 }
42
43 @Override
44 public int arity() {
45 return getDnf().arity() - 1;
46 }
47
48 @Override
49 public Class<T> valueType() {
50 return type;
51 }
52
53 @Override
54 public T defaultValue() {
55 return null;
56 }
57
58 @Override
59 protected FunctionalQuery<T> withDnfInternal(Dnf newDnf) {
60 return newDnf.asFunction(type);
61 }
62
63 @Override
64 public FunctionalQuery<T> withDnf(Dnf newDnf) {
65 return (FunctionalQuery<T>) super.withDnf(newDnf);
66 }
67
68 public AssignedValue<T> call(List<NodeVariable> arguments) {
69 return targetVariable -> {
70 var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1);
71 argumentsWithTarget.addAll(arguments);
72 argumentsWithTarget.add(targetVariable);
73 return getDnf().call(CallPolarity.POSITIVE, argumentsWithTarget);
74 };
75 }
76
77 public AssignedValue<T> call(NodeVariable... arguments) {
78 return call(List.of(arguments));
79 }
80
81 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
82 return targetVariable -> {
83 var placeholderVariable = Variable.of(type);
84 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
85 argumentsWithPlaceholder.addAll(arguments);
86 argumentsWithPlaceholder.add(placeholderVariable);
87 return getDnf()
88 .aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder)
89 .toLiteral(targetVariable);
90 };
91 }
92
93 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
94 return aggregate(aggregator, List.of(arguments));
95 }
96
97 public AssignedValue<T> leftJoin(T defaultValue, List<NodeVariable> arguments) {
98 return targetVariable -> {
99 var placeholderVariable = Variable.of(type);
100 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
101 argumentsWithPlaceholder.addAll(arguments);
102 argumentsWithPlaceholder.add(placeholderVariable);
103 return getDnf()
104 .leftJoinBy(placeholderVariable, defaultValue, argumentsWithPlaceholder)
105 .toLiteral(targetVariable);
106 };
107 }
108
109 public AssignedValue<T> leftJoin(T defaultValue, NodeVariable... arguments) {
110 return leftJoin(defaultValue, List.of(arguments));
111 }
112
113 @Override
114 public boolean equals(Object o) {
115 if (this == o) return true;
116 if (o == null || getClass() != o.getClass()) return false;
117 if (!super.equals(o)) return false;
118 FunctionalQuery<?> that = (FunctionalQuery<?>) o;
119 return Objects.equals(type, that.type);
120 }
121
122 @Override
123 public int hashCode() {
124 return Objects.hash(super.hashCode(), type);
125 }
126}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQueryBuilder.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQueryBuilder.java
new file mode 100644
index 00000000..476f3c83
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQueryBuilder.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.term.DataVariable;
9
10public final class FunctionalQueryBuilder<T> extends AbstractQueryBuilder<FunctionalQueryBuilder<T>> {
11 private final DataVariable<T> outputVariable;
12 private final Class<T> type;
13
14 FunctionalQueryBuilder(DataVariable<T> outputVariable, DnfBuilder dnfBuilder, Class<T> type) {
15 super(dnfBuilder);
16 this.outputVariable = outputVariable;
17 this.type = type;
18 }
19
20 @Override
21 protected FunctionalQueryBuilder<T> self() {
22 return this;
23 }
24
25 public FunctionalQuery<T> build() {
26 dnfBuilder.output(outputVariable);
27 return dnfBuilder.build().asFunction(type);
28 }
29}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/InvalidClauseException.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/InvalidClauseException.java
new file mode 100644
index 00000000..51a42d02
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/InvalidClauseException.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.InvalidQueryException;
9
10public class InvalidClauseException extends InvalidQueryException {
11 private final int clauseIndex;
12
13 public InvalidClauseException(int clauseIndex) {
14 this.clauseIndex = clauseIndex;
15 }
16
17 public InvalidClauseException(int clauseIndex, String message) {
18 super(message);
19 this.clauseIndex = clauseIndex;
20 }
21
22 public InvalidClauseException(int clauseIndex, String message, Throwable cause) {
23 super(message, cause);
24 this.clauseIndex = clauseIndex;
25 }
26
27 public InvalidClauseException(int clauseIndex, Throwable cause) {
28 super(cause);
29 this.clauseIndex = clauseIndex;
30 }
31
32 public int getClauseIndex() {
33 return clauseIndex;
34 }
35}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/Query.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/Query.java
new file mode 100644
index 00000000..1f913ea0
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/Query.java
@@ -0,0 +1,202 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.dnf.callback.*;
9import tools.refinery.logic.term.ParameterDirection;
10import tools.refinery.logic.term.Variable;
11
12import java.util.Objects;
13
14public abstract sealed class Query<T> implements AnyQuery permits FunctionalQuery, RelationalQuery {
15 private static final String OUTPUT_VARIABLE_NAME = "output";
16
17 private final Dnf dnf;
18
19 protected Query(Dnf dnf) {
20 for (var parameter : dnf.getSymbolicParameters()) {
21 if (parameter.getDirection() != ParameterDirection.OUT) {
22 throw new IllegalArgumentException("Query parameter %s with direction %s is not allowed"
23 .formatted(parameter.getVariable(), parameter.getDirection()));
24 }
25 }
26 this.dnf = dnf;
27 }
28
29 @Override
30 public String name() {
31 return dnf.name();
32 }
33
34 @Override
35 public Dnf getDnf() {
36 return dnf;
37 }
38
39 // Allow redeclaration of the method with refined return type.
40 @SuppressWarnings("squid:S3038")
41 @Override
42 public abstract Class<T> valueType();
43
44 public abstract T defaultValue();
45
46 public Query<T> withDnf(Dnf newDnf) {
47 if (dnf.equals(newDnf)) {
48 return this;
49 }
50 int arity = dnf.arity();
51 if (newDnf.arity() != arity) {
52 throw new IllegalArgumentException("Arity of %s and %s do not match".formatted(dnf, newDnf));
53 }
54 var parameters = dnf.getParameters();
55 var newParameters = newDnf.getParameters();
56 for (int i = 0; i < arity; i++) {
57 var parameter = parameters.get(i);
58 var newParameter = newParameters.get(i);
59 if (!parameter.matches(newParameter)) {
60 throw new IllegalArgumentException("Parameter #%d mismatch: %s does not match %s"
61 .formatted(i, parameter, newParameter));
62 }
63 }
64 return withDnfInternal(newDnf);
65 }
66
67 protected abstract Query<T> withDnfInternal(Dnf newDnf);
68
69 @Override
70 public boolean equals(Object o) {
71 if (this == o) return true;
72 if (o == null || getClass() != o.getClass()) return false;
73 Query<?> that = (Query<?>) o;
74 return Objects.equals(dnf, that.dnf);
75 }
76
77 @Override
78 public int hashCode() {
79 return Objects.hash(dnf);
80 }
81
82 @Override
83 public String toString() {
84 return dnf.toString();
85 }
86
87 public static QueryBuilder builder() {
88 return builder(null);
89 }
90
91 public static QueryBuilder builder(String name) {
92 return new QueryBuilder(name);
93 }
94
95 public static RelationalQuery of(QueryCallback0 callback) {
96 return of(null, callback);
97 }
98
99 public static RelationalQuery of(String name, QueryCallback0 callback) {
100 var builder = builder(name);
101 callback.accept(builder);
102 return builder.build();
103 }
104
105 public static RelationalQuery of(QueryCallback1 callback) {
106 return of(null, callback);
107 }
108
109 public static RelationalQuery of(String name, QueryCallback1 callback) {
110 var builder = builder(name);
111 callback.accept(builder, builder.parameter("p1"));
112 return builder.build();
113 }
114
115 public static RelationalQuery of(QueryCallback2 callback) {
116 return of(null, callback);
117 }
118
119 public static RelationalQuery of(String name, QueryCallback2 callback) {
120 var builder = builder(name);
121 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"));
122 return builder.build();
123 }
124
125 public static RelationalQuery of(QueryCallback3 callback) {
126 return of(null, callback);
127 }
128
129 public static RelationalQuery of(String name, QueryCallback3 callback) {
130 var builder = builder(name);
131 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"));
132 return builder.build();
133 }
134
135 public static RelationalQuery of(QueryCallback4 callback) {
136 return of(null, callback);
137 }
138
139 public static RelationalQuery of(String name, QueryCallback4 callback) {
140 var builder = builder(name);
141 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
142 builder.parameter("p4"));
143 return builder.build();
144 }
145
146 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback0<T> callback) {
147 return of(null, type, callback);
148 }
149
150 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback0<T> callback) {
151 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
152 var builder = builder(name).output(outputVariable);
153 callback.accept(builder, outputVariable);
154 return builder.build();
155 }
156
157 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback1<T> callback) {
158 return of(null, type, callback);
159 }
160
161 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback1<T> callback) {
162 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
163 var builder = builder(name).output(outputVariable);
164 callback.accept(builder, builder.parameter("p1"), outputVariable);
165 return builder.build();
166 }
167
168 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback2<T> callback) {
169 return of(null, type, callback);
170 }
171
172 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback2<T> callback) {
173 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
174 var builder = builder(name).output(outputVariable);
175 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), outputVariable);
176 return builder.build();
177 }
178
179 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback3<T> callback) {
180 return of(null, type, callback);
181 }
182
183 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback3<T> callback) {
184 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
185 var builder = builder(name).output(outputVariable);
186 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
187 outputVariable);
188 return builder.build();
189 }
190
191 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback4<T> callback) {
192 return of(null, type, callback);
193 }
194
195 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback4<T> callback) {
196 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
197 var builder = builder(name).output(outputVariable);
198 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
199 builder.parameter("p4"), outputVariable);
200 return builder.build();
201 }
202}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/QueryBuilder.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/QueryBuilder.java
new file mode 100644
index 00000000..a74295ba
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/QueryBuilder.java
@@ -0,0 +1,27 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.term.DataVariable;
9
10public final class QueryBuilder extends AbstractQueryBuilder<QueryBuilder> {
11 QueryBuilder(String name) {
12 super(Dnf.builder(name));
13 }
14
15 @Override
16 protected QueryBuilder self() {
17 return this;
18 }
19
20 public <T> FunctionalQueryBuilder<T> output(DataVariable<T> outputVariable) {
21 return new FunctionalQueryBuilder<>(outputVariable, dnfBuilder, outputVariable.getType());
22 }
23
24 public RelationalQuery build() {
25 return dnfBuilder.build().asRelation();
26 }
27}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/RelationalQuery.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/RelationalQuery.java
new file mode 100644
index 00000000..dc2b8eb6
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/RelationalQuery.java
@@ -0,0 +1,77 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.literal.CallLiteral;
10import tools.refinery.logic.literal.CallPolarity;
11import tools.refinery.logic.term.AssignedValue;
12import tools.refinery.logic.term.NodeVariable;
13
14import java.util.Collections;
15import java.util.List;
16
17public final class RelationalQuery extends Query<Boolean> {
18 RelationalQuery(Dnf dnf) {
19 super(dnf);
20 for (var parameter : dnf.getSymbolicParameters()) {
21 var parameterType = parameter.tryGetType();
22 if (parameterType.isPresent()) {
23 throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead"
24 .formatted(parameter, dnf, parameterType.get().getName()));
25 }
26 }
27 }
28
29 @Override
30 public int arity() {
31 return getDnf().arity();
32 }
33
34 @Override
35 public Class<Boolean> valueType() {
36 return Boolean.class;
37 }
38
39 @Override
40 public Boolean defaultValue() {
41 return false;
42 }
43
44 @Override
45 protected RelationalQuery withDnfInternal(Dnf newDnf) {
46 return newDnf.asRelation();
47 }
48
49 @Override
50 public RelationalQuery withDnf(Dnf newDnf) {
51 return (RelationalQuery) super.withDnf(newDnf);
52 }
53
54 public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) {
55 return getDnf().call(polarity, Collections.unmodifiableList(arguments));
56 }
57
58 public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) {
59 return getDnf().call(polarity, arguments);
60 }
61
62 public CallLiteral call(NodeVariable... arguments) {
63 return getDnf().call(arguments);
64 }
65
66 public CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
67 return getDnf().callTransitive(left, right);
68 }
69
70 public AssignedValue<Integer> count(List<NodeVariable> arguments) {
71 return getDnf().count(Collections.unmodifiableList(arguments));
72 }
73
74 public AssignedValue<Integer> count(NodeVariable... arguments) {
75 return getDnf().count(arguments);
76 }
77}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/SymbolicParameter.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/SymbolicParameter.java
new file mode 100644
index 00000000..acc775a7
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/SymbolicParameter.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf;
7
8import tools.refinery.logic.equality.LiteralHashCodeHelper;
9import tools.refinery.logic.term.Parameter;
10import tools.refinery.logic.term.ParameterDirection;
11import tools.refinery.logic.term.Variable;
12
13import java.util.Objects;
14
15public final class SymbolicParameter extends Parameter {
16 private final Variable variable;
17
18 public SymbolicParameter(Variable variable, ParameterDirection direction) {
19 super(variable.tryGetType().orElse(null), direction);
20 this.variable = variable;
21 }
22
23 public Variable getVariable() {
24 return variable;
25 }
26
27 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
28 return Objects.hash(super.hashCode(), helper.getVariableHashCode(variable));
29 }
30
31 @Override
32 public String toString() {
33 var direction = getDirection();
34 if (direction == ParameterDirection.OUT) {
35 return variable.toString();
36 }
37 return "%s %s".formatted(getDirection(), variable);
38 }
39
40 @Override
41 public boolean equals(Object o) {
42 if (this == o) return true;
43 if (o == null || getClass() != o.getClass()) return false;
44 if (!super.equals(o)) return false;
45 SymbolicParameter that = (SymbolicParameter) o;
46 return Objects.equals(variable, that.variable);
47 }
48
49 @Override
50 public int hashCode() {
51 return Objects.hash(super.hashCode(), variable);
52 }
53}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback0.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback0.java
new file mode 100644
index 00000000..2e276030
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback0.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9
10import java.util.Collection;
11
12@FunctionalInterface
13public interface ClauseCallback0 {
14 Collection<Literal> toLiterals();
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data0.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data0.java
new file mode 100644
index 00000000..f2e174b0
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback1Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data1.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data1.java
new file mode 100644
index 00000000..4e6b5a06
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data1.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback1Data1<T> {
15 Collection<Literal> toLiterals(DataVariable<T> d1);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data0.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data0.java
new file mode 100644
index 00000000..42b7cb08
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback2Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data1.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data1.java
new file mode 100644
index 00000000..59e53744
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback2Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T> x1);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data2.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data2.java
new file mode 100644
index 00000000..d9550d49
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data2.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback2Data2<T1, T2> {
15 Collection<Literal> toLiterals(DataVariable<T1> x1, DataVariable<T2> x2);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data0.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data0.java
new file mode 100644
index 00000000..e36f05db
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback3Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data1.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data1.java
new file mode 100644
index 00000000..7227bada
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback3Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T> d1);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data2.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data2.java
new file mode 100644
index 00000000..7d842655
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data2.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback3Data2<T1, T2> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data3.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data3.java
new file mode 100644
index 00000000..3bc895d5
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data3.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback3Data3<T1, T2, T3> {
15 Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data0.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data0.java
new file mode 100644
index 00000000..164c3208
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback4Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, NodeVariable v4);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data1.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data1.java
new file mode 100644
index 00000000..d7454135
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, DataVariable<T> d1);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data2.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data2.java
new file mode 100644
index 00000000..829dbcf8
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data2.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data2<T1, T2> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T1> d1, DataVariable<T2> d2);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data3.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data3.java
new file mode 100644
index 00000000..50da829b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data3.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data3<T1, T2, T3> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data4.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data4.java
new file mode 100644
index 00000000..46f8fbe9
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data4.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.literal.Literal;
9import tools.refinery.logic.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback4Data4<T1, T2, T3, T4> {
15 Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3, DataVariable<T4> d4);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback0.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback0.java
new file mode 100644
index 00000000..689a5b53
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback0.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.FunctionalQueryBuilder;
9import tools.refinery.logic.term.DataVariable;
10
11@FunctionalInterface
12public interface FunctionalQueryCallback0<T> {
13 void accept(FunctionalQueryBuilder<T> builder, DataVariable<T> output);
14}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback1.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback1.java
new file mode 100644
index 00000000..b7d69a50
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback1.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.FunctionalQueryBuilder;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback1<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, DataVariable<T> output);
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback2.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback2.java
new file mode 100644
index 00000000..38fb265b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback2.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.FunctionalQueryBuilder;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback2<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, DataVariable<T> output);
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback3.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback3.java
new file mode 100644
index 00000000..04451657
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback3.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.FunctionalQueryBuilder;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback3<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3,
15 DataVariable<T> output);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback4.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback4.java
new file mode 100644
index 00000000..a39c389d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback4.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.FunctionalQueryBuilder;
9import tools.refinery.logic.term.DataVariable;
10import tools.refinery.logic.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback4<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4,
15 DataVariable<T> output);
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback0.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback0.java
new file mode 100644
index 00000000..bc9a6ee4
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback0.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.QueryBuilder;
9
10@FunctionalInterface
11public interface QueryCallback0 {
12 void accept(QueryBuilder builder);
13}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback1.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback1.java
new file mode 100644
index 00000000..80763171
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback1.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.QueryBuilder;
9import tools.refinery.logic.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback1 {
13 void accept(QueryBuilder builder, NodeVariable p1);
14}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback2.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback2.java
new file mode 100644
index 00000000..54423104
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback2.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.QueryBuilder;
9import tools.refinery.logic.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback2 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2);
14}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback3.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback3.java
new file mode 100644
index 00000000..eb68d493
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback3.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.QueryBuilder;
9import tools.refinery.logic.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback3 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3);
14}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback4.java b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback4.java
new file mode 100644
index 00000000..59690af9
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback4.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.dnf.callback;
7
8import tools.refinery.logic.dnf.QueryBuilder;
9import tools.refinery.logic.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback4 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4);
14}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/equality/DeepDnfEqualityChecker.java b/subprojects/logic/src/main/java/tools/refinery/logic/equality/DeepDnfEqualityChecker.java
new file mode 100644
index 00000000..a49ef080
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/equality/DeepDnfEqualityChecker.java
@@ -0,0 +1,77 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.equality;
7
8import tools.refinery.logic.dnf.Dnf;
9import tools.refinery.logic.dnf.DnfClause;
10import tools.refinery.logic.dnf.SymbolicParameter;
11import tools.refinery.logic.literal.Literal;
12import tools.refinery.logic.util.CycleDetectingMapper;
13
14import java.util.List;
15
16public class DeepDnfEqualityChecker implements DnfEqualityChecker {
17 private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(this::doCheckEqual);
18
19 @Override
20 public boolean dnfEqual(Dnf left, Dnf right) {
21 return mapper.map(new Pair(left, right));
22 }
23
24 public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters,
25 List<? extends List<? extends Literal>> clauses, Dnf other) {
26 int arity = symbolicParameters.size();
27 if (arity != other.arity()) {
28 return false;
29 }
30 for (int i = 0; i < arity; i++) {
31 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
32 return false;
33 }
34 }
35 int numClauses = clauses.size();
36 if (numClauses != other.getClauses().size()) {
37 return false;
38 }
39 for (int i = 0; i < numClauses; i++) {
40 var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(this, symbolicParameters,
41 other.getSymbolicParameters());
42 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) {
43 return false;
44 }
45 }
46 return true;
47 }
48
49 private boolean equalsWithSubstitutionRaw(LiteralEqualityHelper helper, List<? extends Literal> literals,
50 DnfClause other) {
51 int size = literals.size();
52 if (size != other.literals().size()) {
53 return false;
54 }
55 for (int i = 0; i < size; i++) {
56 if (!literals.get(i).equalsWithSubstitution(helper, other.literals().get(i))) {
57 return false;
58 }
59 }
60 return true;
61 }
62
63 protected boolean doCheckEqual(Pair pair) {
64 return pair.left.equalsWithSubstitution(this, pair.right);
65 }
66
67 protected List<Pair> getInProgress() {
68 return mapper.getInProgress();
69 }
70
71 protected record Pair(Dnf left, Dnf right) {
72 @Override
73 public String toString() {
74 return "(%s, %s)".formatted(left.name(), right.name());
75 }
76 }
77}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/equality/DnfEqualityChecker.java b/subprojects/logic/src/main/java/tools/refinery/logic/equality/DnfEqualityChecker.java
new file mode 100644
index 00000000..3162b019
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/equality/DnfEqualityChecker.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.equality;
7
8import tools.refinery.logic.dnf.Dnf;
9
10import java.util.Objects;
11
12@FunctionalInterface
13public interface DnfEqualityChecker {
14 DnfEqualityChecker DEFAULT = Objects::equals;
15
16 boolean dnfEqual(Dnf left, Dnf right);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralEqualityHelper.java b/subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralEqualityHelper.java
new file mode 100644
index 00000000..ea62061e
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralEqualityHelper.java
@@ -0,0 +1,27 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.equality;
7
8import tools.refinery.logic.dnf.Dnf;
9import tools.refinery.logic.term.Variable;
10
11import java.util.Objects;
12
13public interface LiteralEqualityHelper extends DnfEqualityChecker {
14 LiteralEqualityHelper DEFAULT = new LiteralEqualityHelper() {
15 @Override
16 public boolean variableEqual(Variable left, Variable right) {
17 return Objects.equals(left, right);
18 }
19
20 @Override
21 public boolean dnfEqual(Dnf left, Dnf right) {
22 return DnfEqualityChecker.DEFAULT.dnfEqual(left, right);
23 }
24 };
25
26 boolean variableEqual(Variable left, Variable right);
27}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralHashCodeHelper.java b/subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralHashCodeHelper.java
new file mode 100644
index 00000000..fccadd08
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralHashCodeHelper.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.equality;
7
8import tools.refinery.logic.term.Variable;
9
10import java.util.Objects;
11
12@FunctionalInterface
13public interface LiteralHashCodeHelper {
14 LiteralHashCodeHelper DEFAULT = Objects::hashCode;
15
16 int getVariableHashCode(Variable variable);
17}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralEqualityHelper.java b/subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralEqualityHelper.java
new file mode 100644
index 00000000..7a16b5c9
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralEqualityHelper.java
@@ -0,0 +1,59 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.equality;
7
8import tools.refinery.logic.dnf.Dnf;
9import tools.refinery.logic.dnf.SymbolicParameter;
10import tools.refinery.logic.term.Variable;
11
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15
16public class SubstitutingLiteralEqualityHelper implements LiteralEqualityHelper {
17 private final DnfEqualityChecker dnfEqualityChecker;
18 private final Map<Variable, Variable> leftToRight;
19 private final Map<Variable, Variable> rightToLeft;
20
21 public SubstitutingLiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker,
22 List<SymbolicParameter> leftParameters,
23 List<SymbolicParameter> rightParameters) {
24 this.dnfEqualityChecker = dnfEqualityChecker;
25 var arity = leftParameters.size();
26 if (arity != rightParameters.size()) {
27 throw new IllegalArgumentException("Parameter lists have unequal length");
28 }
29 leftToRight = new HashMap<>(arity);
30 rightToLeft = new HashMap<>(arity);
31 for (int i = 0; i < arity; i++) {
32 if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) {
33 throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i);
34 }
35 }
36 }
37
38 @Override
39 public boolean dnfEqual(Dnf left, Dnf right) {
40 return dnfEqualityChecker.dnfEqual(left, right);
41 }
42
43 @Override
44 public boolean variableEqual(Variable left, Variable right) {
45 if (left.tryGetType().equals(right.tryGetType()) &&
46 checkMapping(leftToRight, left, right) &&
47 checkMapping(rightToLeft, right, left)) {
48 leftToRight.put(left, right);
49 rightToLeft.put(right, left);
50 return true;
51 }
52 return false;
53 }
54
55 private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) {
56 var currentValue = map.get(key);
57 return currentValue == null || currentValue.equals(expectedValue);
58 }
59}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralHashCodeHelper.java b/subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralHashCodeHelper.java
new file mode 100644
index 00000000..64bd9784
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralHashCodeHelper.java
@@ -0,0 +1,42 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.equality;
7
8import tools.refinery.logic.dnf.SymbolicParameter;
9import tools.refinery.logic.term.Variable;
10
11import java.util.LinkedHashMap;
12import java.util.List;
13import java.util.Map;
14
15public class SubstitutingLiteralHashCodeHelper implements LiteralHashCodeHelper {
16 private final Map<Variable, Integer> assignedHashCodes = new LinkedHashMap<>();
17
18 // 0 is for {@code null}, so we start with 1.
19 private int next = 1;
20
21 public SubstitutingLiteralHashCodeHelper() {
22 this(List.of());
23 }
24
25 public SubstitutingLiteralHashCodeHelper(List<SymbolicParameter> parameters) {
26 for (var parameter : parameters) {
27 getVariableHashCode(parameter.getVariable());
28 }
29 }
30
31 @Override
32 public int getVariableHashCode(Variable variable) {
33 if (variable == null) {
34 return 0;
35 }
36 return assignedHashCodes.computeIfAbsent(variable, key -> {
37 int sequenceNumber = next;
38 next++;
39 return variable.hashCodeWithSubstitution(sequenceNumber);
40 });
41 }
42}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCallLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCallLiteral.java
new file mode 100644
index 00000000..9ae84547
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCallLiteral.java
@@ -0,0 +1,135 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.equality.LiteralHashCodeHelper;
12import tools.refinery.logic.substitution.Substitution;
13import tools.refinery.logic.term.ParameterDirection;
14import tools.refinery.logic.term.Variable;
15
16import java.util.*;
17
18// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19@SuppressWarnings("squid:S2160")
20public abstract class AbstractCallLiteral extends tools.refinery.logic.literal.AbstractLiteral {
21 private final Constraint target;
22 private final List<Variable> arguments;
23 private final Set<Variable> inArguments;
24 private final Set<Variable> outArguments;
25
26 // Use exhaustive switch over enums.
27 @SuppressWarnings("squid:S1301")
28 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) {
29 int arity = target.arity();
30 if (arguments.size() != arity) {
31 throw new InvalidQueryException("%s needs %d arguments, but got %s".formatted(target.name(),
32 target.arity(), arguments.size()));
33 }
34 this.target = target;
35 this.arguments = arguments;
36 var mutableInArguments = new LinkedHashSet<Variable>();
37 var mutableOutArguments = new LinkedHashSet<Variable>();
38 var parameters = target.getParameters();
39 for (int i = 0; i < arity; i++) {
40 var argument = arguments.get(i);
41 var parameter = parameters.get(i);
42 if (!parameter.isAssignable(argument)) {
43 throw new InvalidQueryException("Argument %d of %s is not assignable to parameter %s"
44 .formatted(i, target, parameter));
45 }
46 switch (parameter.getDirection()) {
47 case IN -> {
48 mutableOutArguments.remove(argument);
49 mutableInArguments.add(argument);
50 }
51 case OUT -> {
52 if (!mutableInArguments.contains(argument)) {
53 mutableOutArguments.add(argument);
54 }
55 }
56 }
57 }
58 inArguments = Collections.unmodifiableSet(mutableInArguments);
59 outArguments = Collections.unmodifiableSet(mutableOutArguments);
60 }
61
62 public Constraint getTarget() {
63 return target;
64 }
65
66 public List<Variable> getArguments() {
67 return arguments;
68 }
69
70 protected Set<Variable> getArgumentsOfDirection(ParameterDirection direction) {
71 return switch (direction) {
72 case IN -> inArguments;
73 case OUT -> outArguments;
74 };
75 }
76
77 @Override
78 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
79 var inputVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT));
80 inputVariables.retainAll(positiveVariablesInClause);
81 inputVariables.addAll(getArgumentsOfDirection(ParameterDirection.IN));
82 return Collections.unmodifiableSet(inputVariables);
83 }
84
85 @Override
86 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
87 var privateVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT));
88 privateVariables.removeAll(positiveVariablesInClause);
89 return Collections.unmodifiableSet(privateVariables);
90 }
91
92 @Override
93 public tools.refinery.logic.literal.Literal substitute(Substitution substitution) {
94 var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList();
95 return doSubstitute(substitution, substitutedArguments);
96 }
97
98 protected abstract tools.refinery.logic.literal.Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments);
99
100 public AbstractCallLiteral withTarget(Constraint newTarget) {
101 if (Objects.equals(target, newTarget)) {
102 return this;
103 }
104 return withArguments(newTarget, arguments);
105 }
106
107 public abstract AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments);
108
109 @Override
110 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, tools.refinery.logic.literal.Literal other) {
111 if (!super.equalsWithSubstitution(helper, other)) {
112 return false;
113 }
114 var otherCallLiteral = (AbstractCallLiteral) other;
115 var arity = arguments.size();
116 if (arity != otherCallLiteral.arguments.size()) {
117 return false;
118 }
119 for (int i = 0; i < arity; i++) {
120 if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) {
121 return false;
122 }
123 }
124 return target.equals(helper, otherCallLiteral.target);
125 }
126
127 @Override
128 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
129 int result = super.hashCodeWithSubstitution(helper) * 31 + target.hashCode();
130 for (var argument : arguments) {
131 result = result * 31 + helper.getVariableHashCode(argument);
132 }
133 return result;
134 }
135}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCountLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCountLiteral.java
new file mode 100644
index 00000000..ee932598
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCountLiteral.java
@@ -0,0 +1,107 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.equality.LiteralHashCodeHelper;
12import tools.refinery.logic.term.ConstantTerm;
13import tools.refinery.logic.term.DataVariable;
14import tools.refinery.logic.term.Variable;
15
16import java.util.List;
17import java.util.Objects;
18import java.util.Set;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public abstract class AbstractCountLiteral<T> extends AbstractCallLiteral {
23 private final Class<T> resultType;
24 private final DataVariable<T> resultVariable;
25
26 protected AbstractCountLiteral(Class<T> resultType, DataVariable<T> resultVariable, Constraint target,
27 List<Variable> arguments) {
28 super(target, arguments);
29 if (!resultVariable.getType().equals(resultType)) {
30 throw new InvalidQueryException("Count result variable %s must be of type %s, got %s instead".formatted(
31 resultVariable, resultType, resultVariable.getType().getName()));
32 }
33 if (arguments.contains(resultVariable)) {
34 throw new InvalidQueryException("Count result variable %s must not appear in the argument list"
35 .formatted(resultVariable));
36 }
37 this.resultType = resultType;
38 this.resultVariable = resultVariable;
39 }
40
41 public Class<T> getResultType() {
42 return resultType;
43 }
44
45 public DataVariable<T> getResultVariable() {
46 return resultVariable;
47 }
48
49 @Override
50 public Set<Variable> getOutputVariables() {
51 return Set.of(resultVariable);
52 }
53
54 protected abstract T zero();
55
56 protected abstract T one();
57
58 @Override
59 public Literal reduce() {
60 var reduction = getTarget().getReduction();
61 return switch (reduction) {
62 case ALWAYS_FALSE -> getResultVariable().assign(new ConstantTerm<>(resultType, zero()));
63 // The only way a constant {@code true} predicate can be called in a negative position is to have all of
64 // its arguments bound as input variables. Thus, there will only be a single match.
65 case ALWAYS_TRUE -> getResultVariable().assign(new ConstantTerm<>(resultType, one()));
66 case NOT_REDUCIBLE -> this;
67 };
68 }
69
70 @Override
71 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
72 if (!super.equalsWithSubstitution(helper, other)) {
73 return false;
74 }
75 var otherCountLiteral = (AbstractCountLiteral<?>) other;
76 return Objects.equals(resultType, otherCountLiteral.resultType) &&
77 helper.variableEqual(resultVariable, otherCountLiteral.resultVariable);
78 }
79
80 @Override
81 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
82 return Objects.hash(super.hashCodeWithSubstitution(helper), resultType,
83 helper.getVariableHashCode(resultVariable));
84 }
85
86 protected abstract String operatorName();
87
88 @Override
89 public String toString() {
90 var builder = new StringBuilder();
91 builder.append(resultVariable);
92 builder.append(" is ");
93 builder.append(operatorName());
94 builder.append(' ');
95 builder.append(getTarget().toReferenceString());
96 builder.append('(');
97 var argumentIterator = getArguments().iterator();
98 if (argumentIterator.hasNext()) {
99 builder.append(argumentIterator.next());
100 while (argumentIterator.hasNext()) {
101 builder.append(", ").append(argumentIterator.next());
102 }
103 }
104 builder.append(')');
105 return builder.toString();
106 }
107}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractLiteral.java
new file mode 100644
index 00000000..79100e40
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractLiteral.java
@@ -0,0 +1,34 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.equality.LiteralHashCodeHelper;
10
11public abstract class AbstractLiteral implements Literal {
12 @Override
13 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
14 return other != null && getClass() == other.getClass();
15 }
16
17 @Override
18 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
19 return getClass().hashCode();
20 }
21
22 @Override
23 public boolean equals(Object o) {
24 if (this == o) return true;
25 if (o == null || getClass() != o.getClass()) return false;
26 AbstractLiteral that = (AbstractLiteral) o;
27 return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that);
28 }
29
30 @Override
31 public int hashCode() {
32 return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT);
33 }
34}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/AggregationLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AggregationLiteral.java
new file mode 100644
index 00000000..d2cc23f9
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AggregationLiteral.java
@@ -0,0 +1,143 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.equality.LiteralHashCodeHelper;
12import tools.refinery.logic.substitution.Substitution;
13import tools.refinery.logic.term.*;
14
15import java.util.List;
16import java.util.Objects;
17import java.util.Set;
18
19// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
20@SuppressWarnings("squid:S2160")
21public class AggregationLiteral<R, T> extends AbstractCallLiteral {
22 private final DataVariable<R> resultVariable;
23 private final DataVariable<T> inputVariable;
24 private final Aggregator<R, T> aggregator;
25
26 public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator,
27 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
28 super(target, arguments);
29 if (!inputVariable.getType().equals(aggregator.getInputType())) {
30 throw new InvalidQueryException("Input variable %s must of type %s, got %s instead".formatted(
31 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName()));
32 }
33 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) {
34 throw new InvalidQueryException("Input variable %s must be bound with direction %s in the argument list"
35 .formatted(inputVariable, ParameterDirection.OUT));
36 }
37 if (!resultVariable.getType().equals(aggregator.getResultType())) {
38 throw new InvalidQueryException("Result variable %s must of type %s, got %s instead".formatted(
39 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName()));
40 }
41 if (arguments.contains(resultVariable)) {
42 throw new InvalidQueryException("Result variable %s must not appear in the argument list".formatted(
43 resultVariable));
44 }
45 this.resultVariable = resultVariable;
46 this.inputVariable = inputVariable;
47 this.aggregator = aggregator;
48 }
49
50 public DataVariable<R> getResultVariable() {
51 return resultVariable;
52 }
53
54 public DataVariable<T> getInputVariable() {
55 return inputVariable;
56 }
57
58 public Aggregator<R, T> getAggregator() {
59 return aggregator;
60 }
61
62 @Override
63 public Set<Variable> getOutputVariables() {
64 return Set.of(resultVariable);
65 }
66
67 @Override
68 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
69 if (positiveVariablesInClause.contains(inputVariable)) {
70 throw new InvalidQueryException("Aggregation variable %s must not be bound".formatted(inputVariable));
71 }
72 return super.getInputVariables(positiveVariablesInClause);
73 }
74
75 @Override
76 public Literal reduce() {
77 var reduction = getTarget().getReduction();
78 return switch (reduction) {
79 case ALWAYS_FALSE -> {
80 var emptyValue = aggregator.getEmptyResult();
81 yield emptyValue == null ? BooleanLiteral.FALSE :
82 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue));
83 }
84 case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to aggregate over an infinite set");
85 case NOT_REDUCIBLE -> this;
86 };
87 }
88
89 @Override
90 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
91 return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator,
92 substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments);
93 }
94
95 @Override
96 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
97 return new AggregationLiteral<>(resultVariable, aggregator, inputVariable, newTarget, newArguments);
98 }
99
100 @Override
101 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
102 if (!super.equalsWithSubstitution(helper, other)) {
103 return false;
104 }
105 var otherAggregationLiteral = (AggregationLiteral<?, ?>) other;
106 return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) &&
107 aggregator.equals(otherAggregationLiteral.aggregator) &&
108 helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable);
109 }
110
111 @Override
112 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
113 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable),
114 helper.getVariableHashCode(inputVariable), aggregator);
115 }
116
117 @Override
118 public String toString() {
119 var builder = new StringBuilder();
120 builder.append(resultVariable);
121 builder.append(" is ");
122 builder.append(getTarget().toReferenceString());
123 builder.append("(");
124 var argumentIterator = getArguments().iterator();
125 if (argumentIterator.hasNext()) {
126 var argument = argumentIterator.next();
127 if (inputVariable.equals(argument)) {
128 builder.append("@Aggregate(\"").append(aggregator).append("\") ");
129 }
130 builder.append(argument);
131 while (argumentIterator.hasNext()) {
132 builder.append(", ");
133 argument = argumentIterator.next();
134 if (inputVariable.equals(argument)) {
135 builder.append("@Aggregate(\"").append(aggregator).append("\") ");
136 }
137 builder.append(argument);
138 }
139 }
140 builder.append(")");
141 return builder.toString();
142 }
143}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/AssignLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AssignLiteral.java
new file mode 100644
index 00000000..4929e74c
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/AssignLiteral.java
@@ -0,0 +1,88 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.equality.LiteralEqualityHelper;
10import tools.refinery.logic.equality.LiteralHashCodeHelper;
11import tools.refinery.logic.substitution.Substitution;
12import tools.refinery.logic.term.DataVariable;
13import tools.refinery.logic.term.Term;
14import tools.refinery.logic.term.Variable;
15
16import java.util.Collections;
17import java.util.Objects;
18import java.util.Set;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public class AssignLiteral<T> extends AbstractLiteral {
23 private final DataVariable<T> variable;
24 private final Term<T> term;
25
26 public AssignLiteral(DataVariable<T> variable, Term<T> term) {
27 if (!term.getType().equals(variable.getType())) {
28 throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted(
29 term, variable.getType().getName(), term.getType().getName()));
30 }
31 var inputVariables = term.getInputVariables();
32 if (inputVariables.contains(variable)) {
33 throw new InvalidQueryException("Result variable %s must not appear in the term %s".formatted(
34 variable, term));
35 }
36 this.variable = variable;
37 this.term = term;
38 }
39
40 public DataVariable<T> getVariable() {
41 return variable;
42 }
43
44 public Term<T> getTerm() {
45 return term;
46 }
47
48 @Override
49 public Set<Variable> getOutputVariables() {
50 return Set.of(variable);
51 }
52
53 @Override
54 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
55 return Collections.unmodifiableSet(term.getInputVariables());
56 }
57
58 @Override
59 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
60 return Set.of();
61 }
62
63 @Override
64 public Literal substitute(Substitution substitution) {
65 return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution));
66 }
67
68 @Override
69 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
70 if (other == null || getClass() != other.getClass()) {
71 return false;
72 }
73 var otherAssignLiteral = (AssignLiteral<?>) other;
74 return helper.variableEqual(variable, otherAssignLiteral.variable) &&
75 term.equalsWithSubstitution(helper, otherAssignLiteral.term);
76 }
77
78 @Override
79 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
80 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable),
81 term.hashCodeWithSubstitution(helper));
82 }
83
84 @Override
85 public String toString() {
86 return "%s is (%s)".formatted(variable, term);
87 }
88}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/BooleanLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/BooleanLiteral.java
new file mode 100644
index 00000000..fd1dbf91
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/BooleanLiteral.java
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.equality.LiteralHashCodeHelper;
10import tools.refinery.logic.substitution.Substitution;
11import tools.refinery.logic.term.Variable;
12
13import java.util.Set;
14
15public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
16 TRUE(true),
17 FALSE(false);
18
19 private final boolean value;
20
21 BooleanLiteral(boolean value) {
22 this.value = value;
23 }
24
25 @Override
26 public Set<Variable> getOutputVariables() {
27 return Set.of();
28 }
29
30 @Override
31 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
32 return Set.of();
33 }
34
35 @Override
36 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
37 return Set.of();
38 }
39
40 @Override
41 public Literal substitute(Substitution substitution) {
42 // No variables to substitute.
43 return this;
44 }
45
46 @Override
47 public BooleanLiteral negate() {
48 return fromBoolean(!value);
49 }
50
51 @Override
52 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
53 return equals(other);
54 }
55
56 @Override
57 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
58 return hashCode();
59 }
60
61 @Override
62 public String toString() {
63 return Boolean.toString(value);
64 }
65
66 public static BooleanLiteral fromBoolean(boolean value) {
67 return value ? TRUE : FALSE;
68 }
69}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/CallLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CallLiteral.java
new file mode 100644
index 00000000..d93afdc7
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CallLiteral.java
@@ -0,0 +1,134 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.equality.LiteralHashCodeHelper;
12import tools.refinery.logic.substitution.Substitution;
13import tools.refinery.logic.term.ParameterDirection;
14import tools.refinery.logic.term.Variable;
15
16import java.util.*;
17
18// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19@SuppressWarnings("squid:S2160")
20public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> {
21 private final CallPolarity polarity;
22
23 public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) {
24 super(target, arguments);
25 var parameters = target.getParameters();
26 int arity = target.arity();
27 if (polarity.isTransitive()) {
28 if (arity != 2) {
29 throw new InvalidQueryException("Transitive closures can only take binary relations");
30 }
31 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
32 throw new InvalidQueryException("Transitive closures can only be computed over nodes");
33 }
34 if (parameters.get(0).getDirection() != ParameterDirection.OUT ||
35 parameters.get(1).getDirection() != ParameterDirection.OUT) {
36 throw new InvalidQueryException("Transitive closures cannot take input parameters");
37 }
38 }
39 this.polarity = polarity;
40 }
41
42 public CallPolarity getPolarity() {
43 return polarity;
44 }
45
46 @Override
47 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
48 return new CallLiteral(polarity, getTarget(), substitutedArguments);
49 }
50
51 @Override
52 public Set<Variable> getOutputVariables() {
53 if (polarity.isPositive()) {
54 return getArgumentsOfDirection(ParameterDirection.OUT);
55 }
56 return Set.of();
57 }
58
59 @Override
60 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
61 if (polarity.isPositive()) {
62 return getArgumentsOfDirection(ParameterDirection.IN);
63 }
64 return super.getInputVariables(positiveVariablesInClause);
65 }
66
67 @Override
68 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
69 if (polarity.isPositive()) {
70 return Set.of();
71 }
72 return super.getPrivateVariables(positiveVariablesInClause);
73 }
74
75 @Override
76 public Literal reduce() {
77 var reduction = getTarget().getReduction();
78 var negatedReduction = polarity.isPositive() ? reduction : reduction.negate();
79 return switch (negatedReduction) {
80 case ALWAYS_TRUE -> BooleanLiteral.TRUE;
81 case ALWAYS_FALSE -> BooleanLiteral.FALSE;
82 default -> this;
83 };
84 }
85
86 @Override
87 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
88 if (!super.equalsWithSubstitution(helper, other)) {
89 return false;
90 }
91 var otherCallLiteral = (CallLiteral) other;
92 return polarity.equals(otherCallLiteral.polarity);
93 }
94
95 @Override
96 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
97 return Objects.hash(super.hashCodeWithSubstitution(helper), polarity);
98 }
99
100 @Override
101 public CallLiteral negate() {
102 return new CallLiteral(polarity.negate(), getTarget(), getArguments());
103 }
104
105 @Override
106 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
107 return new CallLiteral(polarity, newTarget, newArguments);
108 }
109
110 @Override
111 public String toString() {
112 var builder = new StringBuilder();
113 if (!polarity.isPositive()) {
114 builder.append("!(");
115 }
116 builder.append(getTarget().toReferenceString());
117 if (polarity.isTransitive()) {
118 builder.append("+");
119 }
120 builder.append("(");
121 var argumentIterator = getArguments().iterator();
122 if (argumentIterator.hasNext()) {
123 builder.append(argumentIterator.next());
124 while (argumentIterator.hasNext()) {
125 builder.append(", ").append(argumentIterator.next());
126 }
127 }
128 builder.append(")");
129 if (!polarity.isPositive()) {
130 builder.append(")");
131 }
132 return builder.toString();
133 }
134}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/CallPolarity.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CallPolarity.java
new file mode 100644
index 00000000..4f3bca15
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CallPolarity.java
@@ -0,0 +1,39 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.InvalidQueryException;
9
10public enum CallPolarity {
11 POSITIVE(true, false),
12 NEGATIVE(false, false),
13 TRANSITIVE(true, true);
14
15 private final boolean positive;
16
17 private final boolean transitive;
18
19 CallPolarity(boolean positive, boolean transitive) {
20 this.positive = positive;
21 this.transitive = transitive;
22 }
23
24 public boolean isPositive() {
25 return positive;
26 }
27
28 public boolean isTransitive() {
29 return transitive;
30 }
31
32 public CallPolarity negate() {
33 return switch (this) {
34 case POSITIVE -> NEGATIVE;
35 case NEGATIVE -> POSITIVE;
36 case TRANSITIVE -> throw new InvalidQueryException("Transitive polarity cannot be negated");
37 };
38 }
39}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/CanNegate.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CanNegate.java
new file mode 100644
index 00000000..649f0505
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CanNegate.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8public interface CanNegate<T extends CanNegate<T>> extends Literal {
9 T negate();
10}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/CheckLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CheckLiteral.java
new file mode 100644
index 00000000..86bf7eba
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CheckLiteral.java
@@ -0,0 +1,95 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.equality.LiteralEqualityHelper;
10import tools.refinery.logic.equality.LiteralHashCodeHelper;
11import tools.refinery.logic.substitution.Substitution;
12import tools.refinery.logic.term.ConstantTerm;
13import tools.refinery.logic.term.Term;
14import tools.refinery.logic.term.Variable;
15import tools.refinery.logic.term.bool.BoolNotTerm;
16import tools.refinery.logic.term.bool.BoolTerms;
17
18import java.util.Collections;
19import java.util.Objects;
20import java.util.Set;
21
22// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
23@SuppressWarnings("squid:S2160")
24public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> {
25 private final Term<Boolean> term;
26
27 public CheckLiteral(Term<Boolean> term) {
28 if (!term.getType().equals(Boolean.class)) {
29 throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted(
30 term, Boolean.class.getName(), term.getType().getName()));
31 }
32 this.term = term;
33 }
34
35 public Term<Boolean> getTerm() {
36 return term;
37 }
38
39 @Override
40 public Set<Variable> getOutputVariables() {
41 return Set.of();
42 }
43
44 @Override
45 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
46 return Collections.unmodifiableSet(term.getInputVariables());
47 }
48
49 @Override
50 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
51 return Set.of();
52 }
53
54 @Override
55 public Literal substitute(Substitution substitution) {
56 return new CheckLiteral(term.substitute(substitution));
57 }
58
59 @Override
60 public CheckLiteral negate() {
61 if (term instanceof BoolNotTerm notTerm) {
62 return new CheckLiteral(notTerm.getBody());
63 }
64 return new CheckLiteral(BoolTerms.not(term));
65 }
66
67 @Override
68 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
69 if (other == null || getClass() != other.getClass()) {
70 return false;
71 }
72 var otherAssumeLiteral = (CheckLiteral) other;
73 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term);
74 }
75
76 @Override
77 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
78 return Objects.hash(super.hashCodeWithSubstitution(helper), term.hashCodeWithSubstitution(helper));
79 }
80
81 @Override
82 public Literal reduce() {
83 if (term instanceof ConstantTerm<Boolean> constantTerm) {
84 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals.
85 return Boolean.TRUE.equals(constantTerm.getValue()) ? BooleanLiteral.TRUE :
86 BooleanLiteral.FALSE;
87 }
88 return this;
89 }
90
91 @Override
92 public String toString() {
93 return "(%s)".formatted(term);
94 }
95}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/Connectivity.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Connectivity.java
new file mode 100644
index 00000000..c0465716
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Connectivity.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import java.util.Locale;
9
10public enum Connectivity {
11 WEAK,
12 STRONG;
13
14 @Override
15 public String toString() {
16 return name().toLowerCase(Locale.ROOT);
17 }
18}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/ConstantLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/ConstantLiteral.java
new file mode 100644
index 00000000..688ddfa0
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/ConstantLiteral.java
@@ -0,0 +1,73 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.equality.LiteralHashCodeHelper;
10import tools.refinery.logic.substitution.Substitution;
11import tools.refinery.logic.term.NodeVariable;
12import tools.refinery.logic.term.Variable;
13
14import java.util.Objects;
15import java.util.Set;
16
17public class ConstantLiteral extends AbstractLiteral {
18 private final NodeVariable variable;
19 private final int nodeId;
20
21 public ConstantLiteral(NodeVariable variable, int nodeId) {
22 this.variable = variable;
23 this.nodeId = nodeId;
24 }
25
26 public NodeVariable getVariable() {
27 return variable;
28 }
29
30 public int getNodeId() {
31 return nodeId;
32 }
33
34
35 @Override
36 public Set<Variable> getOutputVariables() {
37 return Set.of(variable);
38 }
39
40 @Override
41 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
42 return Set.of();
43 }
44
45 @Override
46 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
47 return Set.of();
48 }
49
50 @Override
51 public ConstantLiteral substitute(Substitution substitution) {
52 return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId);
53 }
54
55 @Override
56 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
57 if (other.getClass() != getClass()) {
58 return false;
59 }
60 var otherConstantLiteral = (ConstantLiteral) other;
61 return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId;
62 }
63
64 @Override
65 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
66 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), nodeId);
67 }
68
69 @Override
70 public String toString() {
71 return "%s === @Constant %d".formatted(variable, nodeId);
72 }
73}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/CountLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CountLiteral.java
new file mode 100644
index 00000000..19879692
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/CountLiteral.java
@@ -0,0 +1,45 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.substitution.Substitution;
10import tools.refinery.logic.term.DataVariable;
11import tools.refinery.logic.term.Variable;
12
13import java.util.List;
14
15public class CountLiteral extends AbstractCountLiteral<Integer> {
16 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) {
17 super(Integer.class, resultVariable, target, arguments);
18 }
19
20 @Override
21 protected Integer zero() {
22 return 0;
23 }
24
25 @Override
26 protected Integer one() {
27 return 1;
28 }
29
30 @Override
31 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
32 return new CountLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(),
33 substitutedArguments);
34 }
35
36 @Override
37 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
38 return new CountLiteral(getResultVariable(), newTarget, newArguments);
39 }
40
41 @Override
42 protected String operatorName() {
43 return "count";
44 }
45}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/EquivalenceLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/EquivalenceLiteral.java
new file mode 100644
index 00000000..48e85b38
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/EquivalenceLiteral.java
@@ -0,0 +1,100 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.equality.LiteralEqualityHelper;
10import tools.refinery.logic.equality.LiteralHashCodeHelper;
11import tools.refinery.logic.substitution.Substitution;
12import tools.refinery.logic.term.Variable;
13
14import java.util.Objects;
15import java.util.Set;
16
17// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
18@SuppressWarnings("squid:S2160")
19public final class EquivalenceLiteral extends AbstractLiteral implements CanNegate<EquivalenceLiteral> {
20 private final boolean positive;
21 private final Variable left;
22 private final Variable right;
23
24 public EquivalenceLiteral(boolean positive, Variable left, Variable right) {
25 if (!left.tryGetType().equals(right.tryGetType())) {
26 throw new InvalidQueryException("Variables %s and %s of different type cannot be equivalent"
27 .formatted(left, right));
28 }
29 this.positive = positive;
30 this.left = left;
31 this.right = right;
32 }
33
34 public boolean isPositive() {
35 return positive;
36 }
37
38 public Variable getLeft() {
39 return left;
40 }
41
42 public Variable getRight() {
43 return right;
44 }
45
46 @Override
47 public Set<Variable> getOutputVariables() {
48 return Set.of(left);
49 }
50
51 @Override
52 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
53 return Set.of(right);
54 }
55
56 @Override
57 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
58 return Set.of();
59 }
60
61 @Override
62 public EquivalenceLiteral negate() {
63 return new EquivalenceLiteral(!positive, left, right);
64 }
65
66 @Override
67 public EquivalenceLiteral substitute(Substitution substitution) {
68 return new EquivalenceLiteral(positive, substitution.getSubstitute(left),
69 substitution.getSubstitute(right));
70 }
71
72 @Override
73 public Literal reduce() {
74 if (left.equals(right)) {
75 return positive ? BooleanLiteral.TRUE : BooleanLiteral.FALSE;
76 }
77 return this;
78 }
79
80 @Override
81 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
82 if (other.getClass() != getClass()) {
83 return false;
84 }
85 var otherEquivalenceLiteral = (EquivalenceLiteral) other;
86 return helper.variableEqual(left, otherEquivalenceLiteral.left) &&
87 helper.variableEqual(right, otherEquivalenceLiteral.right);
88 }
89
90 @Override
91 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
92 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(left),
93 helper.getVariableHashCode(right));
94 }
95
96 @Override
97 public String toString() {
98 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
99 }
100}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/LeftJoinLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/LeftJoinLiteral.java
new file mode 100644
index 00000000..593904c5
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/LeftJoinLiteral.java
@@ -0,0 +1,140 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.equality.LiteralHashCodeHelper;
12import tools.refinery.logic.substitution.Substitution;
13import tools.refinery.logic.term.ConstantTerm;
14import tools.refinery.logic.term.DataVariable;
15import tools.refinery.logic.term.ParameterDirection;
16import tools.refinery.logic.term.Variable;
17
18import java.util.*;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public class LeftJoinLiteral<T> extends AbstractCallLiteral {
23 private final DataVariable<T> resultVariable;
24 private final DataVariable<T> placeholderVariable;
25 private final T defaultValue;
26
27 public LeftJoinLiteral(DataVariable<T> resultVariable, DataVariable<T> placeholderVariable,
28 T defaultValue, Constraint target, List<Variable> arguments) {
29 super(target, arguments);
30 this.resultVariable = resultVariable;
31 this.placeholderVariable = placeholderVariable;
32 this.defaultValue = defaultValue;
33 if (defaultValue == null) {
34 throw new InvalidQueryException("Default value must not be null");
35 }
36 if (!resultVariable.getType().isInstance(defaultValue)) {
37 throw new InvalidQueryException("Default value %s must be assignable to result variable %s type %s"
38 .formatted(defaultValue, resultVariable, resultVariable.getType().getName()));
39 }
40 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(placeholderVariable)) {
41 throw new InvalidQueryException(
42 "Placeholder variable %s must be bound with direction %s in the argument list"
43 .formatted(resultVariable, ParameterDirection.OUT));
44 }
45 if (arguments.contains(resultVariable)) {
46 throw new InvalidQueryException("Result variable must not appear in the argument list");
47 }
48 }
49
50 public DataVariable<T> getResultVariable() {
51 return resultVariable;
52 }
53
54 public DataVariable<T> getPlaceholderVariable() {
55 return placeholderVariable;
56 }
57
58 public T getDefaultValue() {
59 return defaultValue;
60 }
61
62 @Override
63 public Set<Variable> getOutputVariables() {
64 return Set.of(resultVariable);
65 }
66
67 @Override
68 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
69 var inputVariables = new LinkedHashSet<>(getArguments());
70 inputVariables.remove(placeholderVariable);
71 return Collections.unmodifiableSet(inputVariables);
72 }
73
74 @Override
75 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
76 return Set.of(placeholderVariable);
77 }
78
79 @Override
80 public Literal reduce() {
81 var reduction = getTarget().getReduction();
82 return switch (reduction) {
83 case ALWAYS_FALSE -> resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), defaultValue));
84 case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to left join an infinite set");
85 case NOT_REDUCIBLE -> this;
86 };
87 }
88
89 @Override
90 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
91 return new LeftJoinLiteral<>(substitution.getTypeSafeSubstitute(resultVariable),
92 substitution.getTypeSafeSubstitute(placeholderVariable), defaultValue, getTarget(),
93 substitutedArguments);
94 }
95
96 @Override
97 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
98 return new LeftJoinLiteral<>(resultVariable, placeholderVariable, defaultValue, newTarget, newArguments);
99 }
100
101 @Override
102 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
103 if (!super.equalsWithSubstitution(helper, other)) {
104 return false;
105 }
106 var otherLeftJoinLiteral = (LeftJoinLiteral<?>) other;
107 return helper.variableEqual(resultVariable, otherLeftJoinLiteral.resultVariable) &&
108 helper.variableEqual(placeholderVariable, otherLeftJoinLiteral.placeholderVariable) &&
109 Objects.equals(defaultValue, otherLeftJoinLiteral.defaultValue);
110 }
111
112 @Override
113 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
114 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable),
115 helper.getVariableHashCode(placeholderVariable), defaultValue);
116 }
117
118 @Override
119 public String toString() {
120 var builder = new StringBuilder();
121 var argumentIterator = getArguments().iterator();
122 if (argumentIterator.hasNext()) {
123 appendArgument(builder, argumentIterator.next());
124 while (argumentIterator.hasNext()) {
125 builder.append(", ");
126 appendArgument(builder, argumentIterator.next());
127 }
128 }
129 builder.append(")");
130 return builder.toString();
131 }
132
133 private void appendArgument(StringBuilder builder, Variable argument) {
134 if (placeholderVariable.equals(argument)) {
135 builder.append("@Default(").append(defaultValue).append(") ");
136 argument = resultVariable;
137 }
138 builder.append(argument);
139 }
140}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/Literal.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Literal.java
new file mode 100644
index 00000000..08a0241c
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Literal.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.equality.LiteralHashCodeHelper;
10import tools.refinery.logic.substitution.Substitution;
11import tools.refinery.logic.term.Variable;
12
13import java.util.Set;
14
15public interface Literal {
16 Set<Variable> getOutputVariables();
17
18 Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause);
19
20 Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause);
21
22 Literal substitute(Substitution substitution);
23
24 default Literal reduce() {
25 return this;
26 }
27
28 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
29 boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other);
30
31 int hashCodeWithSubstitution(LiteralHashCodeHelper helper);
32}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/Literals.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Literals.java
new file mode 100644
index 00000000..2f7ddb5d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Literals.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.term.Term;
9
10public final class Literals {
11 private Literals() {
12 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
13 }
14
15 public static <T extends CanNegate<T>> T not(CanNegate<T> literal) {
16 return literal.negate();
17 }
18
19 public static CheckLiteral check(Term<Boolean> term) {
20 return new CheckLiteral(term);
21 }
22}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/Reduction.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Reduction.java
new file mode 100644
index 00000000..1f7d9a2f
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/Reduction.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8public enum Reduction {
9 /**
10 * Signifies that a literal should be preserved in the clause.
11 */
12 NOT_REDUCIBLE,
13
14 /**
15 * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty).
16 */
17 ALWAYS_TRUE,
18
19 /**
20 * Signifies that the clause with the literal may be omitted entirely.
21 */
22 ALWAYS_FALSE;
23
24 public Reduction negate() {
25 return switch (this) {
26 case NOT_REDUCIBLE -> NOT_REDUCIBLE;
27 case ALWAYS_TRUE -> ALWAYS_FALSE;
28 case ALWAYS_FALSE -> ALWAYS_TRUE;
29 };
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/literal/RepresentativeElectionLiteral.java b/subprojects/logic/src/main/java/tools/refinery/logic/literal/RepresentativeElectionLiteral.java
new file mode 100644
index 00000000..ff9a6bed
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/literal/RepresentativeElectionLiteral.java
@@ -0,0 +1,119 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.literal;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.equality.LiteralHashCodeHelper;
12import tools.refinery.logic.substitution.Substitution;
13import tools.refinery.logic.term.NodeVariable;
14import tools.refinery.logic.term.ParameterDirection;
15import tools.refinery.logic.term.Variable;
16
17import java.util.List;
18import java.util.Set;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public class RepresentativeElectionLiteral extends AbstractCallLiteral {
23 private final Connectivity connectivity;
24
25 public RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, NodeVariable specific,
26 NodeVariable representative) {
27 this(connectivity, target, List.of(specific, representative));
28 }
29
30 private RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, List<Variable> arguments) {
31 super(target, arguments);
32 this.connectivity = connectivity;
33 var parameters = target.getParameters();
34 int arity = target.arity();
35 if (arity != 2) {
36 throw new InvalidQueryException("SCCs can only take binary relations");
37 }
38 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
39 throw new InvalidQueryException("SCCs can only be computed over nodes");
40 }
41 if (parameters.get(0).getDirection() != ParameterDirection.OUT ||
42 parameters.get(1).getDirection() != ParameterDirection.OUT) {
43 throw new InvalidQueryException("SCCs cannot take input parameters");
44 }
45 }
46
47 public Connectivity getConnectivity() {
48 return connectivity;
49 }
50
51 @Override
52 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
53 return new RepresentativeElectionLiteral(connectivity, getTarget(), substitutedArguments);
54 }
55
56 @Override
57 public Set<Variable> getOutputVariables() {
58 return getArgumentsOfDirection(ParameterDirection.OUT);
59 }
60
61 @Override
62 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
63 return Set.of();
64 }
65
66 @Override
67 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
68 return Set.of();
69 }
70
71 @Override
72 public Literal reduce() {
73 var reduction = getTarget().getReduction();
74 return switch (reduction) {
75 case ALWAYS_FALSE -> BooleanLiteral.FALSE;
76 case ALWAYS_TRUE -> throw new InvalidQueryException(
77 "Trying to elect representatives over an infinite set");
78 case NOT_REDUCIBLE -> this;
79 };
80 }
81
82 @Override
83 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
84 return new RepresentativeElectionLiteral(connectivity, newTarget, newArguments);
85 }
86
87 @Override
88 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
89 if (!super.equalsWithSubstitution(helper, other)) {
90 return false;
91 }
92 var otherRepresentativeElectionLiteral = (RepresentativeElectionLiteral) other;
93 return connectivity.equals(otherRepresentativeElectionLiteral.connectivity);
94 }
95
96 @Override
97 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
98 return super.hashCodeWithSubstitution(helper) * 31 + connectivity.hashCode();
99 }
100
101 @Override
102 public String toString() {
103 var builder = new StringBuilder();
104 builder.append("@Representative(\"");
105 builder.append(connectivity);
106 builder.append("\") ");
107 builder.append(getTarget().toReferenceString());
108 builder.append("(");
109 var argumentIterator = getArguments().iterator();
110 if (argumentIterator.hasNext()) {
111 builder.append(argumentIterator.next());
112 while (argumentIterator.hasNext()) {
113 builder.append(", ").append(argumentIterator.next());
114 }
115 }
116 builder.append(")");
117 return builder.toString();
118 }
119}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/AbstractRecursiveRewriter.java b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/AbstractRecursiveRewriter.java
new file mode 100644
index 00000000..073705f8
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/AbstractRecursiveRewriter.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.rewriter;
7
8import tools.refinery.logic.dnf.Dnf;
9import tools.refinery.logic.equality.DnfEqualityChecker;
10import tools.refinery.logic.util.CycleDetectingMapper;
11
12public abstract class AbstractRecursiveRewriter implements DnfRewriter {
13 private final CycleDetectingMapper<Dnf, Dnf> mapper = new CycleDetectingMapper<>(Dnf::name, this::map);
14
15 @Override
16 public Dnf rewrite(Dnf dnf) {
17 return mapper.map(dnf);
18 }
19
20 protected Dnf map(Dnf dnf) {
21 var result = doRewrite(dnf);
22 return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, result) ? dnf : result;
23 }
24
25 protected abstract Dnf doRewrite(Dnf dnf);
26}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/ClauseInputParameterResolver.java b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/ClauseInputParameterResolver.java
new file mode 100644
index 00000000..83fd44c5
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/ClauseInputParameterResolver.java
@@ -0,0 +1,160 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.rewriter;
7
8import tools.refinery.logic.dnf.Dnf;
9import tools.refinery.logic.dnf.DnfClause;
10import tools.refinery.logic.substitution.Substitution;
11import tools.refinery.logic.term.Variable;
12import tools.refinery.logic.literal.*;
13import tools.refinery.logic.term.ParameterDirection;
14
15import java.util.*;
16
17class ClauseInputParameterResolver {
18 private final InputParameterResolver rewriter;
19 private final String dnfName;
20 private final int clauseIndex;
21 private final Set<Variable> positiveVariables = new LinkedHashSet<>();
22 private final List<Literal> inlinedLiterals = new ArrayList<>();
23 private final Deque<Literal> workList;
24 private int helperIndex = 0;
25
26 public ClauseInputParameterResolver(InputParameterResolver rewriter, List<Literal> context, DnfClause clause,
27 String dnfName, int clauseIndex) {
28 this.rewriter = rewriter;
29 this.dnfName = dnfName;
30 this.clauseIndex = clauseIndex;
31 workList = new ArrayDeque<>(clause.literals().size() + context.size());
32 for (var literal : context) {
33 workList.addLast(literal);
34 }
35 for (var literal : clause.literals()) {
36 workList.addLast(literal);
37 }
38 }
39
40 public List<Literal> rewriteClause() {
41 while (!workList.isEmpty()) {
42 var literal = workList.removeFirst();
43 processLiteral(literal);
44 }
45 return inlinedLiterals;
46 }
47
48 private void processLiteral(Literal literal) {
49 if (!(literal instanceof AbstractCallLiteral abstractCallLiteral) ||
50 !(abstractCallLiteral.getTarget() instanceof Dnf targetDnf)) {
51 markAsDone(literal);
52 return;
53 }
54 boolean hasInputParameter = hasInputParameter(targetDnf);
55 if (!hasInputParameter) {
56 targetDnf = rewriter.rewrite(targetDnf);
57 }
58 if (inlinePositiveClause(abstractCallLiteral, targetDnf)) {
59 return;
60 }
61 if (eliminateDoubleNegation(abstractCallLiteral, targetDnf)) {
62 return;
63 }
64 if (hasInputParameter) {
65 rewriteWithCurrentContext(abstractCallLiteral, targetDnf);
66 return;
67 }
68 markAsDone(abstractCallLiteral.withTarget(targetDnf));
69 }
70
71 private void markAsDone(Literal literal) {
72 positiveVariables.addAll(literal.getOutputVariables());
73 inlinedLiterals.add(literal);
74 }
75
76 private boolean inlinePositiveClause(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) {
77 var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.POSITIVE);
78 if (targetLiteral == null) {
79 return false;
80 }
81 var substitution = asSubstitution(abstractCallLiteral, targetDnf);
82 var substitutedLiteral = targetLiteral.substitute(substitution);
83 workList.addFirst(substitutedLiteral);
84 return true;
85 }
86
87 private boolean eliminateDoubleNegation(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) {
88 var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.NEGATIVE);
89 if (!(targetLiteral instanceof CallLiteral targetCallLiteral) ||
90 targetCallLiteral.getPolarity() != CallPolarity.NEGATIVE) {
91 return false;
92 }
93 var substitution = asSubstitution(abstractCallLiteral, targetDnf);
94 var substitutedLiteral = (CallLiteral) targetCallLiteral.substitute(substitution);
95 workList.addFirst(substitutedLiteral.negate());
96 return true;
97 }
98
99 private void rewriteWithCurrentContext(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) {
100 var contextBuilder = Dnf.builder("%s#clause%d#helper%d".formatted(dnfName, clauseIndex, helperIndex));
101 helperIndex++;
102 contextBuilder.parameters(positiveVariables, ParameterDirection.OUT);
103 contextBuilder.clause(inlinedLiterals);
104 var contextDnf = contextBuilder.build();
105 var contextCall = new CallLiteral(CallPolarity.POSITIVE, contextDnf, List.copyOf(positiveVariables));
106 inlinedLiterals.clear();
107 var substitution = Substitution.builder().renewing().build();
108 var context = new ArrayList<Literal>();
109 context.add(contextCall.substitute(substitution));
110 int arity = targetDnf.arity();
111 for (int i = 0; i < arity; i++) {
112 var parameter = targetDnf.getSymbolicParameters().get(i).getVariable();
113 var argument = abstractCallLiteral.getArguments().get(i);
114 context.add(new EquivalenceLiteral(true, parameter, substitution.getSubstitute(argument)));
115 }
116 var rewrittenDnf = rewriter.rewriteWithContext(context, targetDnf);
117 workList.addFirst(abstractCallLiteral.withTarget(rewrittenDnf));
118 workList.addFirst(contextCall);
119 }
120
121 private static boolean hasInputParameter(Dnf targetDnf) {
122 for (var parameter : targetDnf.getParameters()) {
123 if (parameter.getDirection() != ParameterDirection.OUT) {
124 return true;
125 }
126 }
127 return false;
128 }
129
130 private static Literal getSingleLiteral(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf,
131 CallPolarity polarity) {
132 if (!(abstractCallLiteral instanceof CallLiteral callLiteral) ||
133 callLiteral.getPolarity() != polarity) {
134 return null;
135 }
136 var clauses = targetDnf.getClauses();
137 if (clauses.size() != 1) {
138 return null;
139 }
140 var targetLiterals = clauses.get(0).literals();
141 if (targetLiterals.size() != 1) {
142 return null;
143 }
144 return targetLiterals.get(0);
145 }
146
147 private static Substitution asSubstitution(AbstractCallLiteral callLiteral, Dnf targetDnf) {
148 var builder = Substitution.builder().renewing();
149 var arguments = callLiteral.getArguments();
150 var parameters = targetDnf.getSymbolicParameters();
151 int arity = arguments.size();
152 if (parameters.size() != arity) {
153 throw new IllegalArgumentException("Call %s of %s arity mismatch".formatted(callLiteral, targetDnf));
154 }
155 for (int i = 0; i < arity; i++) {
156 builder.putChecked(parameters.get(i).getVariable(), arguments.get(i));
157 }
158 return builder.build();
159 }
160}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/CompositeRewriter.java b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/CompositeRewriter.java
new file mode 100644
index 00000000..2e681093
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/CompositeRewriter.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.rewriter;
7
8import tools.refinery.logic.dnf.Dnf;
9
10import java.util.ArrayList;
11import java.util.List;
12
13public class CompositeRewriter implements DnfRewriter {
14 private final List<DnfRewriter> rewriterList = new ArrayList<>();
15
16 public void addFirst(DnfRewriter rewriter) {
17 rewriterList.add(rewriter);
18 }
19
20 @Override
21 public Dnf rewrite(Dnf dnf) {
22 Dnf rewrittenDnf = dnf;
23 for (int i = rewriterList.size() - 1; i >= 0; i--) {
24 var rewriter = rewriterList.get(i);
25 rewrittenDnf = rewriter.rewrite(rewrittenDnf);
26 }
27 return rewrittenDnf;
28 }
29}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DnfRewriter.java b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DnfRewriter.java
new file mode 100644
index 00000000..f4605895
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DnfRewriter.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.rewriter;
7
8import tools.refinery.logic.dnf.AnyQuery;
9import tools.refinery.logic.dnf.Dnf;
10import tools.refinery.logic.dnf.Query;
11
12@FunctionalInterface
13public interface DnfRewriter {
14 Dnf rewrite(Dnf dnf);
15
16 default AnyQuery rewrite(AnyQuery query) {
17 return rewrite((Query<?>) query);
18 }
19
20 default <T> Query<T> rewrite(Query<T> query) {
21 var rewrittenDnf = rewrite(query.getDnf());
22 return query.withDnf(rewrittenDnf);
23 }
24}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DuplicateDnfRemover.java b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DuplicateDnfRemover.java
new file mode 100644
index 00000000..43317338
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DuplicateDnfRemover.java
@@ -0,0 +1,98 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.rewriter;
7
8import tools.refinery.logic.dnf.Dnf;
9import tools.refinery.logic.dnf.DnfClause;
10import tools.refinery.logic.dnf.Query;
11import tools.refinery.logic.equality.DnfEqualityChecker;
12import tools.refinery.logic.literal.AbstractCallLiteral;
13import tools.refinery.logic.literal.Literal;
14
15import java.util.ArrayList;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20public class DuplicateDnfRemover extends AbstractRecursiveRewriter {
21 private final Map<CanonicalDnf, Dnf> dnfCache = new HashMap<>();
22 private final Map<Dnf, Query<?>> queryCache = new HashMap<>();
23
24 @Override
25 protected Dnf map(Dnf dnf) {
26 var result = super.map(dnf);
27 return dnfCache.computeIfAbsent(new CanonicalDnf(result), CanonicalDnf::getDnf);
28 }
29
30 @Override
31 protected Dnf doRewrite(Dnf dnf) {
32 var builder = Dnf.builderFrom(dnf);
33 for (var clause : dnf.getClauses()) {
34 builder.clause(rewriteClause(clause));
35 }
36 return builder.build();
37 }
38
39 private List<Literal> rewriteClause(DnfClause clause) {
40 var originalLiterals = clause.literals();
41 var literals = new ArrayList<Literal>(originalLiterals.size());
42 for (var literal : originalLiterals) {
43 var rewrittenLiteral = literal;
44 if (literal instanceof AbstractCallLiteral abstractCallLiteral &&
45 abstractCallLiteral.getTarget() instanceof Dnf targetDnf) {
46 var rewrittenTarget = rewrite(targetDnf);
47 rewrittenLiteral = abstractCallLiteral.withTarget(rewrittenTarget);
48 }
49 literals.add(rewrittenLiteral);
50 }
51 return literals;
52 }
53
54 @Override
55 public <T> Query<T> rewrite(Query<T> query) {
56 var rewrittenDnf = rewrite(query.getDnf());
57 // {@code withDnf} will always return the appropriate type.
58 @SuppressWarnings("unchecked")
59 var rewrittenQuery = (Query<T>) queryCache.computeIfAbsent(rewrittenDnf, query::withDnf);
60 return rewrittenQuery;
61 }
62
63 private static class CanonicalDnf {
64 private final Dnf dnf;
65 private final int hash;
66
67 public CanonicalDnf(Dnf dnf) {
68 this.dnf = dnf;
69 hash = dnf.hashCodeWithSubstitution();
70 }
71
72 public Dnf getDnf() {
73 return dnf;
74 }
75
76 @Override
77 public boolean equals(Object obj) {
78 if (this == obj) {
79 return true;
80 }
81 if (obj == null || getClass() != obj.getClass()) {
82 return false;
83 }
84 var otherCanonicalDnf = (CanonicalDnf) obj;
85 return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, otherCanonicalDnf.dnf);
86 }
87
88 @Override
89 public int hashCode() {
90 return hash;
91 }
92
93 @Override
94 public String toString() {
95 return dnf.name();
96 }
97 }
98}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/InputParameterResolver.java b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/InputParameterResolver.java
new file mode 100644
index 00000000..89aa43c7
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/rewriter/InputParameterResolver.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.rewriter;
7
8import tools.refinery.logic.dnf.Dnf;
9import tools.refinery.logic.dnf.DnfBuilder;
10import tools.refinery.logic.term.Variable;
11import tools.refinery.logic.literal.Literal;
12import tools.refinery.logic.term.ParameterDirection;
13
14import java.util.HashSet;
15import java.util.List;
16
17public class InputParameterResolver extends AbstractRecursiveRewriter {
18 @Override
19 protected Dnf doRewrite(Dnf dnf) {
20 return rewriteWithContext(List.of(), dnf);
21 }
22
23 Dnf rewriteWithContext(List<Literal> context, Dnf dnf) {
24 var dnfName = dnf.name();
25 var builder = Dnf.builder(dnfName);
26 createSymbolicParameters(context, dnf, builder);
27 builder.functionalDependencies(dnf.getFunctionalDependencies());
28 var clauses = dnf.getClauses();
29 int clauseCount = clauses.size();
30 for (int i = 0; i < clauseCount; i++) {
31 var clause = clauses.get(i);
32 var clauseRewriter = new ClauseInputParameterResolver(this, context, clause, dnfName, i);
33 builder.clause(clauseRewriter.rewriteClause());
34 }
35 return builder.build();
36 }
37
38 private static void createSymbolicParameters(List<Literal> context, Dnf dnf, DnfBuilder builder) {
39 var positiveInContext = new HashSet<Variable>();
40 for (var literal : context) {
41 positiveInContext.addAll(literal.getOutputVariables());
42 }
43 for (var symbolicParameter : dnf.getSymbolicParameters()) {
44 var variable = symbolicParameter.getVariable();
45 var isOutput = symbolicParameter.getDirection() == ParameterDirection.OUT ||
46 positiveInContext.contains(variable);
47 var direction = isOutput ? ParameterDirection.OUT : ParameterDirection.IN;
48 builder.parameter(variable, direction);
49 }
50 }
51}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/substitution/MapBasedSubstitution.java b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/MapBasedSubstitution.java
new file mode 100644
index 00000000..2977bd57
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/MapBasedSubstitution.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.substitution;
7
8import tools.refinery.logic.term.Variable;
9
10import java.util.Map;
11
12public record MapBasedSubstitution(Map<Variable, Variable> map, Substitution fallback) implements Substitution {
13 @Override
14 public Variable getSubstitute(Variable variable) {
15 var value = map.get(variable);
16 return value == null ? fallback.getSubstitute(variable) : value;
17 }
18}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/substitution/RenewingSubstitution.java b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/RenewingSubstitution.java
new file mode 100644
index 00000000..ca40ee50
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/RenewingSubstitution.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.substitution;
7
8import tools.refinery.logic.term.Variable;
9
10import java.util.HashMap;
11import java.util.Map;
12
13public class RenewingSubstitution implements Substitution {
14 private final Map<Variable, Variable> alreadyRenewed = new HashMap<>();
15
16 @Override
17 public Variable getSubstitute(Variable variable) {
18 return alreadyRenewed.computeIfAbsent(variable, Variable::renew);
19 }
20}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/substitution/StatelessSubstitution.java b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/StatelessSubstitution.java
new file mode 100644
index 00000000..ab36d3db
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/StatelessSubstitution.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.substitution;
7
8import tools.refinery.logic.term.Variable;
9
10public enum StatelessSubstitution implements Substitution {
11 FAILING {
12 @Override
13 public Variable getSubstitute(Variable variable) {
14 throw new IllegalArgumentException("No substitute for " + variable);
15 }
16 },
17 IDENTITY {
18 @Override
19 public Variable getSubstitute(Variable variable) {
20 return variable;
21 }
22 }
23}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/substitution/Substitution.java b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/Substitution.java
new file mode 100644
index 00000000..8fc700bc
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/Substitution.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.substitution;
7
8import tools.refinery.logic.term.DataVariable;
9import tools.refinery.logic.term.NodeVariable;
10import tools.refinery.logic.term.Variable;
11
12@FunctionalInterface
13public interface Substitution {
14 Variable getSubstitute(Variable variable);
15
16 default NodeVariable getTypeSafeSubstitute(NodeVariable variable) {
17 var substitute = getSubstitute(variable);
18 return substitute.asNodeVariable();
19 }
20
21 default <T> DataVariable<T> getTypeSafeSubstitute(DataVariable<T> variable) {
22 var substitute = getSubstitute(variable);
23 return substitute.asDataVariable(variable.getType());
24 }
25
26 static SubstitutionBuilder builder() {
27 return new SubstitutionBuilder();
28 }
29}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/substitution/SubstitutionBuilder.java b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/SubstitutionBuilder.java
new file mode 100644
index 00000000..f30b1a5d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/substitution/SubstitutionBuilder.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.substitution;
7
8import tools.refinery.logic.term.DataVariable;
9import tools.refinery.logic.term.NodeVariable;
10import tools.refinery.logic.term.Variable;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17@SuppressWarnings("UnusedReturnValue")
18public class SubstitutionBuilder {
19 private final Map<Variable, Variable> map = new HashMap<>();
20 private Substitution fallback;
21
22 SubstitutionBuilder() {
23 total();
24 }
25
26 public SubstitutionBuilder put(NodeVariable original, NodeVariable substitute) {
27 return putChecked(original, substitute);
28 }
29
30 public <T> SubstitutionBuilder put(DataVariable<T> original, DataVariable<T> substitute) {
31 return putChecked(original, substitute);
32 }
33
34 public SubstitutionBuilder putChecked(Variable original, Variable substitute) {
35 if (!original.tryGetType().equals(substitute.tryGetType())) {
36 throw new IllegalArgumentException("Cannot substitute variable %s of sort %s with variable %s of sort %s"
37 .formatted(original, original.tryGetType().map(Class::getName).orElse("node"), substitute,
38 substitute.tryGetType().map(Class::getName).orElse("node")));
39 }
40 if (map.containsKey(original)) {
41 throw new IllegalArgumentException("Already has substitution for variable %s".formatted(original));
42 }
43 map.put(original, substitute);
44 return this;
45 }
46
47 public SubstitutionBuilder putManyChecked(List<Variable> originals, List<Variable> substitutes) {
48 int size = originals.size();
49 if (size != substitutes.size()) {
50 throw new IllegalArgumentException("Cannot substitute %d variables %s with %d variables %s"
51 .formatted(size, originals, substitutes.size(), substitutes));
52 }
53 for (int i = 0; i < size; i++) {
54 putChecked(originals.get(i), substitutes.get(i));
55 }
56 return this;
57 }
58
59 public SubstitutionBuilder fallback(Substitution newFallback) {
60 fallback = newFallback;
61 return this;
62 }
63
64 public SubstitutionBuilder total() {
65 return fallback(StatelessSubstitution.FAILING);
66 }
67
68 public SubstitutionBuilder partial() {
69 return fallback(StatelessSubstitution.IDENTITY);
70 }
71
72 public SubstitutionBuilder renewing() {
73 return fallback(new RenewingSubstitution());
74 }
75
76 public Substitution build() {
77 return map.isEmpty() ? fallback : new MapBasedSubstitution(Collections.unmodifiableMap(map), fallback);
78 }
79}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/AbstractTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/AbstractTerm.java
new file mode 100644
index 00000000..293f7e9e
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/AbstractTerm.java
@@ -0,0 +1,47 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.equality.LiteralHashCodeHelper;
10
11import java.util.Objects;
12
13public abstract class AbstractTerm<T> implements Term<T> {
14 private final Class<T> type;
15
16 protected AbstractTerm(Class<T> type) {
17 this.type = type;
18 }
19
20 @Override
21 public Class<T> getType() {
22 return type;
23 }
24
25 @Override
26 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
27 return other != null && getClass() == other.getClass() && type.equals(other.getType());
28 }
29
30 @Override
31 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
32 return Objects.hash(getClass(), type);
33 }
34
35 @Override
36 public boolean equals(Object o) {
37 if (this == o) return true;
38 if (o == null || getClass() != o.getClass()) return false;
39 AbstractTerm<?> that = (AbstractTerm<?>) o;
40 return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that);
41 }
42
43 @Override
44 public int hashCode() {
45 return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT);
46 }
47}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/Aggregator.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/Aggregator.java
new file mode 100644
index 00000000..40189ebc
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/Aggregator.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import java.util.stream.Stream;
9
10public interface Aggregator<R, T> {
11 Class<R> getResultType();
12
13 Class<T> getInputType();
14
15 R aggregateStream(Stream<T> stream);
16
17 R getEmptyResult();
18}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/AnyDataVariable.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/AnyDataVariable.java
new file mode 100644
index 00000000..3376bdc9
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/AnyDataVariable.java
@@ -0,0 +1,55 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11
12import java.util.Optional;
13import java.util.Set;
14
15public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable {
16 protected AnyDataVariable(String name) {
17 super(name);
18 }
19
20 @Override
21 public Optional<Class<?>> tryGetType() {
22 return Optional.of(getType());
23 }
24
25 @Override
26 public boolean isNodeVariable() {
27 return false;
28 }
29
30 @Override
31 public boolean isDataVariable() {
32 return true;
33 }
34
35 @Override
36 public NodeVariable asNodeVariable() {
37 throw new InvalidQueryException("%s is a data variable".formatted(this));
38 }
39
40 @Override
41 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
42 return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable);
43 }
44
45 @Override
46 public Set<AnyDataVariable> getInputVariables() {
47 return Set.of(this);
48 }
49
50 @Override
51 public abstract AnyDataVariable renew(@Nullable String name);
52
53 @Override
54 public abstract AnyDataVariable renew();
55}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/AnyTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/AnyTerm.java
new file mode 100644
index 00000000..58b4cb1d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/AnyTerm.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import tools.refinery.logic.equality.LiteralEqualityHelper;
9import tools.refinery.logic.equality.LiteralHashCodeHelper;
10import tools.refinery.logic.substitution.Substitution;
11
12import java.util.Set;
13
14public sealed interface AnyTerm permits AnyDataVariable, Term {
15 Class<?> getType();
16
17 AnyTerm substitute(Substitution substitution);
18
19 boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other);
20
21 int hashCodeWithSubstitution(LiteralHashCodeHelper helper);
22
23 Set<AnyDataVariable> getInputVariables();
24}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/AssignedValue.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/AssignedValue.java
new file mode 100644
index 00000000..78900cd6
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/AssignedValue.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import tools.refinery.logic.literal.Literal;
9
10@FunctionalInterface
11public interface AssignedValue<T> {
12 Literal toLiteral(DataVariable<T> targetVariable);
13}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/BinaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/BinaryTerm.java
new file mode 100644
index 00000000..356fd807
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/BinaryTerm.java
@@ -0,0 +1,106 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import tools.refinery.logic.equality.LiteralHashCodeHelper;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.substitution.Substitution;
12import tools.refinery.logic.valuation.Valuation;
13
14import java.util.Collections;
15import java.util.HashSet;
16import java.util.Objects;
17import java.util.Set;
18
19// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
20@SuppressWarnings("squid:S2160")
21public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
22 private final Class<T1> leftType;
23 private final Class<T2> rightType;
24 private final Term<T1> left;
25 private final Term<T2> right;
26
27 protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) {
28 super(type);
29 if (!left.getType().equals(leftType)) {
30 throw new InvalidQueryException("Expected left %s to be of type %s, got %s instead".formatted(
31 left, leftType.getName(), left.getType().getName()));
32 }
33 if (!right.getType().equals(rightType)) {
34 throw new InvalidQueryException("Expected right %s to be of type %s, got %s instead".formatted(
35 right, rightType.getName(), right.getType().getName()));
36 }
37 this.leftType = leftType;
38 this.rightType = rightType;
39 this.left = left;
40 this.right = right;
41 }
42
43 public Class<T1> getLeftType() {
44 return leftType;
45 }
46
47 public Class<T2> getRightType() {
48 return rightType;
49 }
50
51 public Term<T1> getLeft() {
52 return left;
53 }
54
55 public Term<T2> getRight() {
56 return right;
57 }
58
59 @Override
60 public R evaluate(Valuation valuation) {
61 var leftValue = left.evaluate(valuation);
62 if (leftValue == null) {
63 return null;
64 }
65 var rightValue = right.evaluate(valuation);
66 if (rightValue == null) {
67 return null;
68 }
69 return doEvaluate(leftValue, rightValue);
70 }
71
72 protected abstract R doEvaluate(T1 leftValue, T2 rightValue);
73
74 @Override
75 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
76 if (!super.equalsWithSubstitution(helper, other)) {
77 return false;
78 }
79 var otherBinaryTerm = (BinaryTerm<?, ?, ?>) other;
80 return leftType.equals(otherBinaryTerm.leftType) &&
81 rightType.equals(otherBinaryTerm.rightType) &&
82 left.equalsWithSubstitution(helper, otherBinaryTerm.left) &&
83 right.equalsWithSubstitution(helper, otherBinaryTerm.right);
84 }
85
86 @Override
87 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
88 return Objects.hash(super.hashCodeWithSubstitution(helper), leftType.hashCode(), rightType.hashCode(),
89 left.hashCodeWithSubstitution(helper), right.hashCodeWithSubstitution(helper));
90 }
91
92 @Override
93 public Term<R> substitute(Substitution substitution) {
94 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution));
95 }
96
97 public abstract Term<R> doSubstitute(Substitution substitution, Term<T1> substitutedLeft,
98 Term<T2> substitutedRight);
99
100 @Override
101 public Set<AnyDataVariable> getInputVariables() {
102 var inputVariables = new HashSet<>(left.getInputVariables());
103 inputVariables.addAll(right.getInputVariables());
104 return Collections.unmodifiableSet(inputVariables);
105 }
106}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/ConstantTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/ConstantTerm.java
new file mode 100644
index 00000000..69c7e5f6
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/ConstantTerm.java
@@ -0,0 +1,67 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import tools.refinery.logic.equality.LiteralHashCodeHelper;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.substitution.Substitution;
12import tools.refinery.logic.valuation.Valuation;
13
14import java.util.Objects;
15import java.util.Set;
16
17// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
18@SuppressWarnings("squid:S2160")
19public final class ConstantTerm<T> extends AbstractTerm<T> {
20 private final T value;
21
22 public ConstantTerm(Class<T> type, T value) {
23 super(type);
24 if (value != null && !type.isInstance(value)) {
25 throw new InvalidQueryException("Value %s is not an instance of %s".formatted(value, type.getName()));
26 }
27 this.value = value;
28 }
29
30 public T getValue() {
31 return value;
32 }
33
34 @Override
35 public T evaluate(Valuation valuation) {
36 return getValue();
37 }
38
39 @Override
40 public Term<T> substitute(Substitution substitution) {
41 return this;
42 }
43
44 @Override
45 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
46 if (!super.equalsWithSubstitution(helper, other)) {
47 return false;
48 }
49 var otherConstantTerm = (ConstantTerm<?>) other;
50 return Objects.equals(value, otherConstantTerm.value);
51 }
52
53 @Override
54 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
55 return Objects.hash(super.hashCodeWithSubstitution(helper), Objects.hash(value));
56 }
57
58 @Override
59 public Set<AnyDataVariable> getInputVariables() {
60 return Set.of();
61 }
62
63 @Override
64 public String toString() {
65 return value.toString();
66 }
67}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/DataVariable.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/DataVariable.java
new file mode 100644
index 00000000..7ef69d05
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/DataVariable.java
@@ -0,0 +1,103 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.equality.LiteralEqualityHelper;
11import tools.refinery.logic.equality.LiteralHashCodeHelper;
12import tools.refinery.logic.literal.EquivalenceLiteral;
13import tools.refinery.logic.literal.Literal;
14import tools.refinery.logic.substitution.Substitution;
15import tools.refinery.logic.valuation.Valuation;
16
17import java.util.Objects;
18
19public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
20 private final Class<T> type;
21
22 DataVariable(String name, Class<T> type) {
23 super(name);
24 this.type = type;
25 }
26
27 @Override
28 public Class<T> getType() {
29 return type;
30 }
31
32 @Override
33 public DataVariable<T> renew(@Nullable String name) {
34 return new DataVariable<>(name, type);
35 }
36
37 @Override
38 public DataVariable<T> renew() {
39 return renew(getExplicitName());
40 }
41
42 @Override
43 public <U> DataVariable<U> asDataVariable(Class<U> newType) {
44 if (!getType().equals(newType)) {
45 throw new InvalidQueryException("%s is not of type %s but of type %s"
46 .formatted(this, newType.getName(), getType().getName()));
47 }
48 @SuppressWarnings("unchecked")
49 var result = (DataVariable<U>) this;
50 return result;
51 }
52
53 @Override
54 public T evaluate(Valuation valuation) {
55 return valuation.getValue(this);
56 }
57
58 @Override
59 public Term<T> substitute(Substitution substitution) {
60 return substitution.getTypeSafeSubstitute(this);
61 }
62
63 @Override
64 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
65 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable);
66 }
67
68 @Override
69 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
70 return helper.getVariableHashCode(this);
71 }
72
73 @Override
74 public int hashCodeWithSubstitution(int sequenceNumber) {
75 return Objects.hash(type, sequenceNumber);
76 }
77
78 public Literal assign(AssignedValue<T> value) {
79 return value.toLiteral(this);
80 }
81
82 @Override
83 public boolean equals(Object o) {
84 if (this == o) return true;
85 if (o == null || getClass() != o.getClass()) return false;
86 if (!super.equals(o)) return false;
87 DataVariable<?> that = (DataVariable<?>) o;
88 return type.equals(that.type);
89 }
90
91 @Override
92 public int hashCode() {
93 return Objects.hash(super.hashCode(), type);
94 }
95
96 public EquivalenceLiteral isEquivalent(DataVariable<T> other) {
97 return new EquivalenceLiteral(true, this, other);
98 }
99
100 public EquivalenceLiteral notEquivalent(DataVariable<T> other) {
101 return new EquivalenceLiteral(false, this, other);
102 }
103}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/ExtremeValueAggregator.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/ExtremeValueAggregator.java
new file mode 100644
index 00000000..31c61b6a
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/ExtremeValueAggregator.java
@@ -0,0 +1,108 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import java.util.Comparator;
9import java.util.Objects;
10import java.util.SortedMap;
11import java.util.TreeMap;
12
13public class ExtremeValueAggregator<T> implements StatefulAggregator<T, T> {
14 private final Class<T> type;
15 private final T emptyResult;
16 private final Comparator<T> comparator;
17
18 public ExtremeValueAggregator(Class<T> type, T emptyResult) {
19 this(type, emptyResult, null);
20 }
21
22 public ExtremeValueAggregator(Class<T> type, T emptyResult, Comparator<T> comparator) {
23 this.type = type;
24 this.emptyResult = emptyResult;
25 this.comparator = comparator;
26 }
27
28 @Override
29 public Class<T> getResultType() {
30 return getInputType();
31 }
32
33 @Override
34 public Class<T> getInputType() {
35 return type;
36 }
37
38 @Override
39 public StatefulAggregate<T, T> createEmptyAggregate() {
40 return new Aggregate();
41 }
42
43 @Override
44 public T getEmptyResult() {
45 return emptyResult;
46 }
47
48 @Override
49 public boolean equals(Object o) {
50 if (this == o) return true;
51 if (o == null || getClass() != o.getClass()) return false;
52 ExtremeValueAggregator<?> that = (ExtremeValueAggregator<?>) o;
53 return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator,
54 that.comparator);
55 }
56
57 @Override
58 public int hashCode() {
59 return Objects.hash(type, emptyResult, comparator);
60 }
61
62 private class Aggregate implements StatefulAggregate<T, T> {
63 private final SortedMap<T, Integer> values;
64
65 private Aggregate() {
66 values = new TreeMap<>(comparator);
67 }
68
69 private Aggregate(Aggregate other) {
70 values = new TreeMap<>(other.values);
71 }
72
73 @Override
74 public void add(T value) {
75 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
76 }
77
78 @Override
79 public void remove(T value) {
80 values.compute(value, (theValue, currentCount) -> {
81 if (currentCount == null || currentCount <= 0) {
82 throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue));
83 }
84 return currentCount.equals(1) ? null : currentCount - 1;
85 });
86 }
87
88 @Override
89 public T getResult() {
90 return isEmpty() ? emptyResult : values.firstKey();
91 }
92
93 @Override
94 public boolean isEmpty() {
95 return values.isEmpty();
96 }
97
98 @Override
99 public StatefulAggregate<T, T> deepCopy() {
100 return new Aggregate(this);
101 }
102
103 @Override
104 public boolean contains(T value) {
105 return StatefulAggregate.super.contains(value);
106 }
107 }
108}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/NodeVariable.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/NodeVariable.java
new file mode 100644
index 00000000..1a5f2657
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/NodeVariable.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.logic.InvalidQueryException;
10import tools.refinery.logic.literal.ConstantLiteral;
11import tools.refinery.logic.literal.EquivalenceLiteral;
12
13import java.util.Optional;
14
15public final class NodeVariable extends Variable {
16 NodeVariable(@Nullable String name) {
17 super(name);
18 }
19
20 @Override
21 public Optional<Class<?>> tryGetType() {
22 return Optional.empty();
23 }
24
25 @Override
26 public NodeVariable renew(@Nullable String name) {
27 return of(name);
28 }
29
30 @Override
31 public NodeVariable renew() {
32 return renew(getExplicitName());
33 }
34
35 @Override
36 public boolean isNodeVariable() {
37 return true;
38 }
39
40 @Override
41 public boolean isDataVariable() {
42 return false;
43 }
44
45 @Override
46 public NodeVariable asNodeVariable() {
47 return this;
48 }
49
50 @Override
51 public <T> DataVariable<T> asDataVariable(Class<T> type) {
52 throw new InvalidQueryException("%s is a node variable".formatted(this));
53 }
54
55 @Override
56 public int hashCodeWithSubstitution(int sequenceNumber) {
57 return sequenceNumber;
58 }
59
60 public ConstantLiteral isConstant(int value) {
61 return new ConstantLiteral(this, value);
62 }
63
64 public EquivalenceLiteral isEquivalent(NodeVariable other) {
65 return new EquivalenceLiteral(true, this, other);
66 }
67
68 public EquivalenceLiteral notEquivalent(NodeVariable other) {
69 return new EquivalenceLiteral(false, this, other);
70 }
71}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/Parameter.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/Parameter.java
new file mode 100644
index 00000000..d4a651e1
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/Parameter.java
@@ -0,0 +1,68 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import java.util.Objects;
9import java.util.Optional;
10
11public class Parameter {
12 public static final Parameter NODE_OUT = new Parameter(null);
13
14 private final Class<?> dataType;
15 private final ParameterDirection direction;
16
17 public Parameter(Class<?> dataType) {
18 this(dataType, ParameterDirection.OUT);
19 }
20
21 public Parameter(Class<?> dataType, ParameterDirection direction) {
22 this.dataType = dataType;
23 this.direction = direction;
24 }
25
26 public boolean isNodeVariable() {
27 return dataType == null;
28 }
29
30 public boolean isDataVariable() {
31 return !isNodeVariable();
32 }
33
34 public Optional<Class<?>> tryGetType() {
35 return Optional.ofNullable(dataType);
36 }
37
38 public ParameterDirection getDirection() {
39 return direction;
40 }
41
42 public boolean matches(Parameter other) {
43 return Objects.equals(dataType, other.dataType) && direction == other.direction;
44 }
45
46 public boolean isAssignable(Variable variable) {
47 if (variable instanceof AnyDataVariable dataVariable) {
48 return dataVariable.getType().equals(dataType);
49 } else if (variable instanceof NodeVariable) {
50 return !isDataVariable();
51 } else {
52 throw new IllegalArgumentException("Unknown variable " + variable);
53 }
54 }
55
56 @Override
57 public boolean equals(Object o) {
58 if (this == o) return true;
59 if (o == null || getClass() != o.getClass()) return false;
60 Parameter parameter = (Parameter) o;
61 return matches(parameter);
62 }
63
64 @Override
65 public int hashCode() {
66 return Objects.hash(dataType, direction);
67 }
68}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/ParameterDirection.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/ParameterDirection.java
new file mode 100644
index 00000000..75ee06e6
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/ParameterDirection.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8public enum ParameterDirection {
9 OUT("out"),
10 IN("in");
11
12 private final String name;
13
14 ParameterDirection(String name) {
15 this.name = name;
16 }
17
18 @Override
19 public String toString() {
20 return name;
21 }
22}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregate.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregate.java
new file mode 100644
index 00000000..75e7ecff
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregate.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8public interface StatefulAggregate<R, T> {
9 void add(T value);
10
11 void remove(T value);
12
13 R getResult();
14
15 boolean isEmpty();
16
17 StatefulAggregate<R, T> deepCopy();
18
19 default boolean contains(T value) {
20 throw new UnsupportedOperationException();
21 }
22}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregator.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregator.java
new file mode 100644
index 00000000..79d32535
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregator.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import java.util.stream.Stream;
9
10public interface StatefulAggregator<R, T> extends Aggregator<R, T> {
11 StatefulAggregate<R, T> createEmptyAggregate();
12
13 @Override
14 default R aggregateStream(Stream<T> stream) {
15 var accumulator = createEmptyAggregate();
16 var iterator = stream.iterator();
17 while (iterator.hasNext()) {
18 var value = iterator.next();
19 accumulator.add(value);
20 }
21 return accumulator.getResult();
22 }
23
24 @Override
25 default R getEmptyResult() {
26 return createEmptyAggregate().getResult();
27 }
28}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/StatelessAggregator.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/StatelessAggregator.java
new file mode 100644
index 00000000..37db262e
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/StatelessAggregator.java
@@ -0,0 +1,25 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import java.util.stream.Stream;
9
10public interface StatelessAggregator<R, T> extends Aggregator<R, T> {
11 R add(R current, T value);
12
13 R remove(R current, T value);
14
15 @Override
16 default R aggregateStream(Stream<T> stream) {
17 var accumulator = getEmptyResult();
18 var iterator = stream.iterator();
19 while (iterator.hasNext()) {
20 var value = iterator.next();
21 accumulator = add(accumulator, value);
22 }
23 return accumulator;
24 }
25}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/Term.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/Term.java
new file mode 100644
index 00000000..0eaa36d8
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/Term.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import tools.refinery.logic.literal.AssignLiteral;
9import tools.refinery.logic.literal.Literal;
10import tools.refinery.logic.substitution.Substitution;
11import tools.refinery.logic.valuation.Valuation;
12
13public non-sealed interface Term<T> extends AnyTerm, AssignedValue<T> {
14 @Override
15 Class<T> getType();
16
17 T evaluate(Valuation valuation);
18
19 @Override
20 Term<T> substitute(Substitution substitution);
21
22 @Override
23 default Literal toLiteral(DataVariable<T> targetVariable) {
24 return new AssignLiteral<>(targetVariable, this);
25 }
26}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/UnaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/UnaryTerm.java
new file mode 100644
index 00000000..e173de3e
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/UnaryTerm.java
@@ -0,0 +1,74 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import tools.refinery.logic.InvalidQueryException;
9import tools.refinery.logic.equality.LiteralEqualityHelper;
10import tools.refinery.logic.equality.LiteralHashCodeHelper;
11import tools.refinery.logic.substitution.Substitution;
12import tools.refinery.logic.valuation.Valuation;
13
14import java.util.Objects;
15import java.util.Set;
16
17// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
18@SuppressWarnings("squid:S2160")
19public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
20 private final Class<T> bodyType;
21 private final Term<T> body;
22
23 protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) {
24 super(type);
25 if (!body.getType().equals(bodyType)) {
26 throw new InvalidQueryException("Expected body %s to be of type %s, got %s instead".formatted(body,
27 bodyType.getName(), body.getType().getName()));
28 }
29 this.bodyType = bodyType;
30 this.body = body;
31 }
32
33 public Class<T> getBodyType() {
34 return bodyType;
35 }
36
37 public Term<T> getBody() {
38 return body;
39 }
40
41 @Override
42 public R evaluate(Valuation valuation) {
43 var bodyValue = body.evaluate(valuation);
44 return bodyValue == null ? null : doEvaluate(bodyValue);
45 }
46
47 protected abstract R doEvaluate(T bodyValue);
48
49 @Override
50 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
51 if (!super.equalsWithSubstitution(helper, other)) {
52 return false;
53 }
54 var otherUnaryTerm = (UnaryTerm<?, ?>) other;
55 return bodyType.equals(otherUnaryTerm.bodyType) && body.equalsWithSubstitution(helper, otherUnaryTerm.body);
56 }
57
58 @Override
59 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
60 return Objects.hash(super.hashCodeWithSubstitution(helper), bodyType, body.hashCodeWithSubstitution(helper));
61 }
62
63 @Override
64 public Term<R> substitute(Substitution substitution) {
65 return doSubstitute(substitution, body.substitute(substitution));
66 }
67
68 protected abstract Term<R> doSubstitute(Substitution substitution, Term<T> substitutedBody);
69
70 @Override
71 public Set<AnyDataVariable> getInputVariables() {
72 return body.getInputVariables();
73 }
74}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/Variable.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/Variable.java
new file mode 100644
index 00000000..28aad21b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/Variable.java
@@ -0,0 +1,88 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.logic.dnf.DnfUtils;
10
11import java.util.Objects;
12import java.util.Optional;
13
14public abstract sealed class Variable permits AnyDataVariable, NodeVariable {
15 private final String explicitName;
16 private final String uniqueName;
17
18 protected Variable(String name) {
19 this.explicitName = name;
20 uniqueName = DnfUtils.generateUniqueName(name);
21 }
22
23 public abstract Optional<Class<?>> tryGetType();
24
25 public String getName() {
26 return explicitName == null ? uniqueName : explicitName;
27 }
28
29 protected String getExplicitName() {
30 return explicitName;
31 }
32
33 public boolean isExplicitlyNamed() {
34 return explicitName != null;
35 }
36
37 public String getUniqueName() {
38 return uniqueName;
39 }
40
41 public abstract Variable renew(@Nullable String name);
42
43 public abstract Variable renew();
44
45 public abstract boolean isNodeVariable();
46
47 public abstract boolean isDataVariable();
48
49 public abstract NodeVariable asNodeVariable();
50
51 public abstract <T> DataVariable<T> asDataVariable(Class<T> type);
52
53 public abstract int hashCodeWithSubstitution(int sequenceNumber);
54
55 @Override
56 public String toString() {
57 return getName();
58 }
59
60 @Override
61 public boolean equals(Object o) {
62 if (this == o) return true;
63 if (o == null || getClass() != o.getClass()) return false;
64 Variable variable = (Variable) o;
65 return Objects.equals(uniqueName, variable.uniqueName);
66 }
67
68 @Override
69 public int hashCode() {
70 return Objects.hash(uniqueName);
71 }
72
73 public static NodeVariable of(@Nullable String name) {
74 return new NodeVariable(name);
75 }
76
77 public static NodeVariable of() {
78 return of((String) null);
79 }
80
81 public static <T> DataVariable<T> of(@Nullable String name, Class<T> type) {
82 return new DataVariable<>(name, type);
83 }
84
85 public static <T> DataVariable<T> of(Class<T> type) {
86 return of(null, type);
87 }
88}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolAndTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolAndTerm.java
new file mode 100644
index 00000000..88c2081b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolAndTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.bool;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class BoolAndTerm extends BoolBinaryTerm {
12 public BoolAndTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolAndTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue && rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s && %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolBinaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolBinaryTerm.java
new file mode 100644
index 00000000..6d53fb43
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.bool;
7
8import tools.refinery.logic.term.BinaryTerm;
9import tools.refinery.logic.term.Term;
10
11public abstract class BoolBinaryTerm extends BinaryTerm<Boolean, Boolean, Boolean> {
12 protected BoolBinaryTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(Boolean.class, Boolean.class, Boolean.class, left, right);
14 }
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolNotTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolNotTerm.java
new file mode 100644
index 00000000..ec80484f
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolNotTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.bool;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10import tools.refinery.logic.term.UnaryTerm;
11
12public class BoolNotTerm extends UnaryTerm<Boolean, Boolean> {
13 protected BoolNotTerm(Term<Boolean> body) {
14 super(Boolean.class, Boolean.class, body);
15 }
16
17 @Override
18 protected Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedBody) {
19 return new BoolNotTerm(substitutedBody);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean bodyValue) {
24 return !bodyValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(!%s)".formatted(getBody());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolOrTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolOrTerm.java
new file mode 100644
index 00000000..10c61bf2
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolOrTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.bool;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class BoolOrTerm extends BoolBinaryTerm {
12 public BoolOrTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolOrTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue || rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s || %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolTerms.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolTerms.java
new file mode 100644
index 00000000..5bdc3207
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolTerms.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.bool;
7
8import tools.refinery.logic.term.ConstantTerm;
9import tools.refinery.logic.term.Term;
10
11public final class BoolTerms {
12 private BoolTerms() {
13 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
14 }
15
16 public static Term<Boolean> constant(Boolean value) {
17 return new ConstantTerm<>(Boolean.class, value);
18 }
19
20 public static Term<Boolean> not(Term<Boolean> body) {
21 return new BoolNotTerm(body);
22 }
23
24 public static Term<Boolean> and(Term<Boolean> left, Term<Boolean> right) {
25 return new BoolAndTerm(left, right);
26 }
27
28 public static Term<Boolean> or(Term<Boolean> left, Term<Boolean> right) {
29 return new BoolOrTerm(left, right);
30 }
31
32 public static Term<Boolean> xor(Term<Boolean> left, Term<Boolean> right) {
33 return new BoolXorTerm(left, right);
34 }
35}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolXorTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolXorTerm.java
new file mode 100644
index 00000000..3d5247a7
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolXorTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.bool;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class BoolXorTerm extends BoolBinaryTerm {
12 public BoolXorTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolXorTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue ^ rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s ^^ %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityDomain.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityDomain.java
new file mode 100644
index 00000000..508a454b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityDomain.java
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.cardinalityinterval;
7
8import tools.refinery.logic.term.uppercardinality.FiniteUpperCardinality;
9import tools.refinery.logic.AbstractDomain;
10
11import java.util.Optional;
12
13// Singleton pattern, because there is only one domain for truth values.
14@SuppressWarnings("squid:S6548")
15public class CardinalityDomain implements AbstractDomain<CardinalityInterval, Integer> {
16 public static final CardinalityDomain INSTANCE = new CardinalityDomain();
17
18 private CardinalityDomain() {
19 }
20
21 @Override
22 public Class<CardinalityInterval> abstractType() {
23 return CardinalityInterval.class;
24 }
25
26 @Override
27 public Class<Integer> concreteType() {
28 return Integer.class;
29 }
30
31 @Override
32 public CardinalityInterval toAbstract(Integer concreteValue) {
33 return CardinalityIntervals.exactly(concreteValue);
34 }
35
36 @Override
37 public Optional<Integer> toConcrete(CardinalityInterval abstractValue) {
38 return isConcrete(abstractValue) ? Optional.of(abstractValue.lowerBound()) : Optional.empty();
39 }
40
41 @Override
42 public boolean isConcrete(CardinalityInterval abstractValue) {
43 if (!(abstractValue instanceof NonEmptyCardinalityInterval nonEmptyValue) ||
44 !((nonEmptyValue.upperBound()) instanceof FiniteUpperCardinality finiteUpperCardinality)) {
45 return false;
46 }
47 return nonEmptyValue.lowerBound() == finiteUpperCardinality.finiteUpperBound();
48 }
49
50 @Override
51 public CardinalityInterval commonRefinement(CardinalityInterval leftValue, CardinalityInterval rightValue) {
52 return leftValue.meet(rightValue);
53 }
54
55 @Override
56 public CardinalityInterval commonAncestor(CardinalityInterval leftValue, CardinalityInterval rightValue) {
57 return leftValue.join(rightValue);
58 }
59
60 @Override
61 public CardinalityInterval unknown() {
62 return CardinalityIntervals.SET;
63 }
64
65 @Override
66 public boolean isError(CardinalityInterval abstractValue) {
67 return abstractValue.isEmpty();
68 }
69}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityInterval.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityInterval.java
new file mode 100644
index 00000000..dbf30def
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityInterval.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.cardinalityinterval;
7
8import tools.refinery.logic.term.uppercardinality.UpperCardinality;
9
10public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval {
11 int lowerBound();
12
13 UpperCardinality upperBound();
14
15 boolean isEmpty();
16
17 CardinalityInterval min(CardinalityInterval other);
18
19 CardinalityInterval max(CardinalityInterval other);
20
21 CardinalityInterval add(CardinalityInterval other);
22
23 CardinalityInterval take(int count);
24
25 CardinalityInterval multiply(CardinalityInterval other);
26
27 CardinalityInterval meet(CardinalityInterval other);
28
29 CardinalityInterval join(CardinalityInterval other);
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervals.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervals.java
new file mode 100644
index 00000000..cb64cc0d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervals.java
@@ -0,0 +1,54 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.cardinalityinterval;
7
8import tools.refinery.logic.term.uppercardinality.UpperCardinalities;
9import tools.refinery.logic.term.uppercardinality.UpperCardinality;
10
11public final class CardinalityIntervals {
12 public static final CardinalityInterval NONE = exactly(0);
13
14 public static final CardinalityInterval ONE = exactly(1);
15
16 public static final CardinalityInterval LONE = atMost(1);
17
18 public static final CardinalityInterval SET = atLeast(0);
19
20 public static final CardinalityInterval SOME = atLeast(1);
21
22 public static final CardinalityInterval ERROR = EmptyCardinalityInterval.INSTANCE;
23
24 private CardinalityIntervals() {
25 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
26 }
27
28 public static CardinalityInterval between(int lowerBound, UpperCardinality upperBound) {
29 if (upperBound.compareToInt(lowerBound) < 0) {
30 return ERROR;
31 }
32 return new NonEmptyCardinalityInterval(lowerBound, upperBound);
33 }
34
35 public static CardinalityInterval between(int lowerBound, int upperBound) {
36 return between(lowerBound, UpperCardinalities.atMost(upperBound));
37 }
38
39 public static CardinalityInterval atMost(UpperCardinality upperBound) {
40 return new NonEmptyCardinalityInterval(0, upperBound);
41 }
42
43 public static CardinalityInterval atMost(int upperBound) {
44 return atMost(UpperCardinalities.atMost(upperBound));
45 }
46
47 public static CardinalityInterval atLeast(int lowerBound) {
48 return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.UNBOUNDED);
49 }
50
51 public static CardinalityInterval exactly(int lowerBound) {
52 return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.atMost(lowerBound));
53 }
54}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityInterval.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityInterval.java
new file mode 100644
index 00000000..29a7f69d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityInterval.java
@@ -0,0 +1,74 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.cardinalityinterval;
7
8import tools.refinery.logic.term.uppercardinality.UpperCardinalities;
9import tools.refinery.logic.term.uppercardinality.UpperCardinality;
10
11// Singleton implementation, because there is only a single empty interval.
12@SuppressWarnings("squid:S6548")
13public final class EmptyCardinalityInterval implements CardinalityInterval {
14 static final EmptyCardinalityInterval INSTANCE = new EmptyCardinalityInterval();
15
16 private EmptyCardinalityInterval() {
17 // Singleton constructor.
18 }
19
20 @Override
21 public int lowerBound() {
22 return 1;
23 }
24
25 @Override
26 public boolean isEmpty() {
27 return true;
28 }
29
30 @Override
31 public UpperCardinality upperBound() {
32 return UpperCardinalities.ZERO;
33 }
34
35 @Override
36 public CardinalityInterval min(CardinalityInterval other) {
37 return this;
38 }
39
40 @Override
41 public CardinalityInterval max(CardinalityInterval other) {
42 return this;
43 }
44
45 @Override
46 public CardinalityInterval add(CardinalityInterval other) {
47 return this;
48 }
49
50 @Override
51 public CardinalityInterval take(int count) {
52 return this;
53 }
54
55 @Override
56 public CardinalityInterval multiply(CardinalityInterval other) {
57 return this;
58 }
59
60 @Override
61 public CardinalityInterval meet(CardinalityInterval other) {
62 return this;
63 }
64
65 @Override
66 public CardinalityInterval join(CardinalityInterval other) {
67 return other;
68 }
69
70 @Override
71 public String toString() {
72 return "error";
73 }
74}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/NonEmptyCardinalityInterval.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/NonEmptyCardinalityInterval.java
new file mode 100644
index 00000000..0919fc36
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/NonEmptyCardinalityInterval.java
@@ -0,0 +1,109 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.cardinalityinterval;
7
8import tools.refinery.logic.term.uppercardinality.FiniteUpperCardinality;
9import tools.refinery.logic.term.uppercardinality.UpperCardinality;
10
11import java.util.Objects;
12import java.util.function.BinaryOperator;
13import java.util.function.IntBinaryOperator;
14
15public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upperBound) implements CardinalityInterval {
16 public NonEmptyCardinalityInterval {
17 if (lowerBound < 0) {
18 throw new IllegalArgumentException("lowerBound must not be negative");
19 }
20 if (upperBound.compareToInt(lowerBound) < 0) {
21 throw new IllegalArgumentException("lowerBound must not be larger than upperBound");
22 }
23 }
24
25 @Override
26 public boolean isEmpty() {
27 return false;
28 }
29
30 @Override
31 public CardinalityInterval min(CardinalityInterval other) {
32 return lift(other, Math::min, UpperCardinality::min);
33 }
34
35 @Override
36 public CardinalityInterval max(CardinalityInterval other) {
37 return lift(other, Math::max, UpperCardinality::max);
38 }
39
40 @Override
41 public CardinalityInterval add(CardinalityInterval other) {
42 return lift(other, Integer::sum, UpperCardinality::add);
43 }
44
45 @Override
46 public CardinalityInterval multiply(CardinalityInterval other) {
47 return lift(other, (a, b) -> a * b, UpperCardinality::multiply);
48 }
49
50 @Override
51 public CardinalityInterval meet(CardinalityInterval other) {
52 return lift(other, Math::max, UpperCardinality::min);
53 }
54
55 @Override
56 public CardinalityInterval join(CardinalityInterval other) {
57 return lift(other, Math::min, UpperCardinality::max, this);
58 }
59
60 @Override
61 public CardinalityInterval take(int count) {
62 int newLowerBound = Math.max(lowerBound - count, 0);
63 var newUpperBound = upperBound.take(count);
64 if (newUpperBound == null) {
65 return CardinalityIntervals.ERROR;
66 }
67 return CardinalityIntervals.between(newLowerBound, newUpperBound);
68 }
69
70 private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator,
71 BinaryOperator<UpperCardinality> upperOperator,
72 CardinalityInterval whenEmpty) {
73 if (other instanceof NonEmptyCardinalityInterval nonEmptyOther) {
74 return CardinalityIntervals.between(lowerOperator.applyAsInt(lowerBound, nonEmptyOther.lowerBound),
75 upperOperator.apply(upperBound, nonEmptyOther.upperBound));
76 }
77 if (other instanceof EmptyCardinalityInterval) {
78 return whenEmpty;
79 }
80 throw new IllegalArgumentException("Unknown CardinalityInterval: " + other);
81 }
82
83 private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator,
84 BinaryOperator<UpperCardinality> upperOperator) {
85 return lift(other, lowerOperator, upperOperator, CardinalityIntervals.ERROR);
86 }
87
88 @Override
89 public String toString() {
90 if (upperBound instanceof FiniteUpperCardinality finiteUpperCardinality &&
91 finiteUpperCardinality.finiteUpperBound() == lowerBound) {
92 return "[%d]".formatted(lowerBound);
93 }
94 return "[%d..%s]".formatted(lowerBound, upperBound);
95 }
96
97 @Override
98 public boolean equals(Object o) {
99 if (this == o) return true;
100 if (o == null || getClass() != o.getClass()) return false;
101 NonEmptyCardinalityInterval that = (NonEmptyCardinalityInterval) o;
102 return lowerBound == that.lowerBound && Objects.equals(upperBound, that.upperBound);
103 }
104
105 @Override
106 public int hashCode() {
107 return lowerBound * 31 + upperBound.hashCode();
108 }
109}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/ComparisonTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/ComparisonTerm.java
new file mode 100644
index 00000000..0401f984
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/ComparisonTerm.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.comparable;
7
8import tools.refinery.logic.term.BinaryTerm;
9import tools.refinery.logic.term.Term;
10
11public abstract class ComparisonTerm<T> extends BinaryTerm<Boolean, T, T> {
12 protected ComparisonTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(Boolean.class, argumentType, argumentType, left, right);
14 }
15
16 public Class<T> getArgumentType() {
17 return getLeftType();
18 }
19}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/EqTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/EqTerm.java
new file mode 100644
index 00000000..7a1315e6
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/EqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.comparable;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class EqTerm<T> extends ComparisonTerm<T> {
12 public EqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.equals(rightValue);
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new EqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s == %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterEqTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterEqTerm.java
new file mode 100644
index 00000000..38323999
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.comparable;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class GreaterEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public GreaterEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) >= 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new GreaterEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s >= %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterTerm.java
new file mode 100644
index 00000000..7174372e
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.comparable;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class GreaterTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public GreaterTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) > 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new GreaterTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s > %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessEqTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessEqTerm.java
new file mode 100644
index 00000000..7a07b7b2
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.comparable;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class LessEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public LessEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) <= 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new LessEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s <= %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessTerm.java
new file mode 100644
index 00000000..b402f54d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.comparable;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class LessTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public LessTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) < 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new LessTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s < %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/NotEqTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/NotEqTerm.java
new file mode 100644
index 00000000..d2c697f4
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/NotEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.comparable;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class NotEqTerm<T> extends ComparisonTerm<T> {
12 public NotEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return !leftValue.equals(rightValue);
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new NotEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s != %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntAddTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntAddTerm.java
new file mode 100644
index 00000000..73533d9b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntAddTerm extends IntBinaryTerm {
12 public IntAddTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntAddTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue + rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntBinaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntBinaryTerm.java
new file mode 100644
index 00000000..9543c21f
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.term.BinaryTerm;
9import tools.refinery.logic.term.Term;
10
11public abstract class IntBinaryTerm extends BinaryTerm<Integer, Integer, Integer> {
12 protected IntBinaryTerm(Term<Integer> left, Term<Integer> right) {
13 super(Integer.class, Integer.class, Integer.class, left, right);
14 }
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntDivTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntDivTerm.java
new file mode 100644
index 00000000..f9fa70ef
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntDivTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntDivTerm extends IntBinaryTerm {
12 public IntDivTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntDivTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return rightValue == 0 ? null : leftValue / rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s / %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMaxTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMaxTerm.java
new file mode 100644
index 00000000..f7b315df
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntMaxTerm extends IntBinaryTerm {
12 public IntMaxTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMaxTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return Math.max(rightValue, leftValue);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinTerm.java
new file mode 100644
index 00000000..73247db1
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntMinTerm extends IntBinaryTerm {
12 public IntMinTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMinTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return Math.min(rightValue, leftValue);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinusTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinusTerm.java
new file mode 100644
index 00000000..006e1977
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntMinusTerm extends IntUnaryTerm {
12 public IntMinusTerm(Term<Integer> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
18 return new IntMinusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Integer doEvaluate(Integer bodyValue) {
23 return -bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(-%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMulTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMulTerm.java
new file mode 100644
index 00000000..18fdcb45
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntMulTerm extends IntBinaryTerm {
12 public IntMulTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMulTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue * rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPlusTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPlusTerm.java
new file mode 100644
index 00000000..1a905293
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPlusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntPlusTerm extends IntUnaryTerm {
12 public IntPlusTerm(Term<Integer> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
18 return new IntPlusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Integer doEvaluate(Integer bodyValue) {
23 return bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(+%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPowTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPowTerm.java
new file mode 100644
index 00000000..dac490b0
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPowTerm.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntPowTerm extends IntBinaryTerm {
12 public IntPowTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntPowTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return rightValue < 0 ? null : power(leftValue, rightValue);
25 }
26
27 private static int power(int base, int exponent) {
28 int accum = 1;
29 while (exponent > 0) {
30 if (exponent % 2 == 1) {
31 accum = accum * base;
32 }
33 base = base * base;
34 exponent = exponent / 2;
35 }
36 return accum;
37 }
38
39 @Override
40 public String toString() {
41 return "(%s ** %s)".formatted(getLeft(), getRight());
42 }
43}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSubTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSubTerm.java
new file mode 100644
index 00000000..94326677
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSubTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class IntSubTerm extends IntBinaryTerm {
12 public IntSubTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntSubTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue - rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s - %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSumAggregator.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSumAggregator.java
new file mode 100644
index 00000000..b42038ab
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSumAggregator.java
@@ -0,0 +1,40 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.term.StatelessAggregator;
9
10public final class IntSumAggregator implements StatelessAggregator<Integer, Integer> {
11 public static final IntSumAggregator INSTANCE = new IntSumAggregator();
12
13 private IntSumAggregator() {
14 }
15
16 @Override
17 public Class<Integer> getResultType() {
18 return Integer.class;
19 }
20
21 @Override
22 public Class<Integer> getInputType() {
23 return Integer.class;
24 }
25
26 @Override
27 public Integer getEmptyResult() {
28 return 0;
29 }
30
31 @Override
32 public Integer add(Integer current, Integer value) {
33 return current + value;
34 }
35
36 @Override
37 public Integer remove(Integer current, Integer value) {
38 return current - value;
39 }
40}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntTerms.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntTerms.java
new file mode 100644
index 00000000..b152a138
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntTerms.java
@@ -0,0 +1,95 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.term.Aggregator;
9import tools.refinery.logic.term.ConstantTerm;
10import tools.refinery.logic.term.ExtremeValueAggregator;
11import tools.refinery.logic.term.Term;
12import tools.refinery.logic.term.comparable.*;
13import tools.refinery.logic.term.comparable.*;
14
15import java.util.Comparator;
16
17public final class IntTerms {
18 public static final Aggregator<Integer, Integer> INT_SUM = IntSumAggregator.INSTANCE;
19 public static final Aggregator<Integer, Integer> INT_MIN = new ExtremeValueAggregator<>(Integer.class,
20 Integer.MAX_VALUE);
21 public static final Aggregator<Integer, Integer> INT_MAX = new ExtremeValueAggregator<>(Integer.class,
22 Integer.MIN_VALUE, Comparator.reverseOrder());
23
24 private IntTerms() {
25 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
26 }
27
28 public static Term<Integer> constant(Integer value) {
29 return new ConstantTerm<>(Integer.class, value);
30 }
31
32 public static Term<Integer> plus(Term<Integer> body) {
33 return new IntPlusTerm(body);
34 }
35
36 public static Term<Integer> minus(Term<Integer> body) {
37 return new IntMinusTerm(body);
38 }
39
40 public static Term<Integer> add(Term<Integer> left, Term<Integer> right) {
41 return new IntAddTerm(left, right);
42 }
43
44 public static Term<Integer> sub(Term<Integer> left, Term<Integer> right) {
45 return new IntSubTerm(left, right);
46 }
47
48 public static Term<Integer> mul(Term<Integer> left, Term<Integer> right) {
49 return new IntMulTerm(left, right);
50 }
51
52 public static Term<Integer> div(Term<Integer> left, Term<Integer> right) {
53 return new IntDivTerm(left, right);
54 }
55
56 public static Term<Integer> pow(Term<Integer> left, Term<Integer> right) {
57 return new IntPowTerm(left, right);
58 }
59
60 public static Term<Integer> min(Term<Integer> left, Term<Integer> right) {
61 return new IntMinTerm(left, right);
62 }
63
64 public static Term<Integer> max(Term<Integer> left, Term<Integer> right) {
65 return new IntMaxTerm(left, right);
66 }
67
68 public static Term<Boolean> eq(Term<Integer> left, Term<Integer> right) {
69 return new EqTerm<>(Integer.class, left, right);
70 }
71
72 public static Term<Boolean> notEq(Term<Integer> left, Term<Integer> right) {
73 return new NotEqTerm<>(Integer.class, left, right);
74 }
75
76 public static Term<Boolean> less(Term<Integer> left, Term<Integer> right) {
77 return new LessTerm<>(Integer.class, left, right);
78 }
79
80 public static Term<Boolean> lessEq(Term<Integer> left, Term<Integer> right) {
81 return new LessEqTerm<>(Integer.class, left, right);
82 }
83
84 public static Term<Boolean> greater(Term<Integer> left, Term<Integer> right) {
85 return new GreaterTerm<>(Integer.class, left, right);
86 }
87
88 public static Term<Boolean> greaterEq(Term<Integer> left, Term<Integer> right) {
89 return new GreaterEqTerm<>(Integer.class, left, right);
90 }
91
92 public static Term<Integer> asInt(Term<Double> body) {
93 return new RealToIntTerm(body);
94 }
95}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntUnaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntUnaryTerm.java
new file mode 100644
index 00000000..20449e67
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntUnaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.term.UnaryTerm;
9import tools.refinery.logic.term.Term;
10
11public abstract class IntUnaryTerm extends UnaryTerm<Integer, Integer> {
12 protected IntUnaryTerm(Term<Integer> body) {
13 super(Integer.class, Integer.class, body);
14 }
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/RealToIntTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/RealToIntTerm.java
new file mode 100644
index 00000000..7611af90
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/int_/RealToIntTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.int_;
7
8import tools.refinery.logic.term.Term;
9import tools.refinery.logic.term.UnaryTerm;
10import tools.refinery.logic.substitution.Substitution;
11
12public class RealToIntTerm extends UnaryTerm<Integer, Double> {
13 protected RealToIntTerm(Term<Double> body) {
14 super(Integer.class, Double.class, body);
15 }
16
17 @Override
18 protected Integer doEvaluate(Double bodyValue) {
19 return bodyValue.isNaN() ? null : bodyValue.intValue();
20 }
21
22 @Override
23 protected Term<Integer> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
24 return new RealToIntTerm(substitutedBody);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s as int)".formatted(getBody());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/IntToRealTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/IntToRealTerm.java
new file mode 100644
index 00000000..23c6893c
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/IntToRealTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.term.UnaryTerm;
9import tools.refinery.logic.substitution.Substitution;
10import tools.refinery.logic.term.Term;
11
12public class IntToRealTerm extends UnaryTerm<Double, Integer> {
13 protected IntToRealTerm(Term<Integer> body) {
14 super(Double.class, Integer.class, body);
15 }
16
17 @Override
18 protected Term<Double> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
19 return new IntToRealTerm(substitutedBody);
20 }
21
22 @Override
23 protected Double doEvaluate(Integer bodyValue) {
24 return bodyValue.doubleValue();
25 }
26
27 @Override
28 public String toString() {
29 return "(%s as real)".formatted(getBody());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealAddTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealAddTerm.java
new file mode 100644
index 00000000..82b97228
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealAddTerm extends RealBinaryTerm {
12 public RealAddTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealAddTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue + rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealBinaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealBinaryTerm.java
new file mode 100644
index 00000000..3c23e767
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.term.BinaryTerm;
9import tools.refinery.logic.term.Term;
10
11public abstract class RealBinaryTerm extends BinaryTerm<Double, Double, Double> {
12 protected RealBinaryTerm(Term<Double> left, Term<Double> right) {
13 super(Double.class, Double.class, Double.class, left, right);
14 }
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealDivTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealDivTerm.java
new file mode 100644
index 00000000..c22399e3
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealDivTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealDivTerm extends RealBinaryTerm {
12 public RealDivTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealDivTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue / rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s / %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMaxTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMaxTerm.java
new file mode 100644
index 00000000..0c609c6d
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealMaxTerm extends RealBinaryTerm {
12 public RealMaxTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMaxTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.max(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinTerm.java
new file mode 100644
index 00000000..af2c3a52
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealMinTerm extends RealBinaryTerm {
12 public RealMinTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMinTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.min(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinusTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinusTerm.java
new file mode 100644
index 00000000..e3e371c2
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealMinusTerm extends RealUnaryTerm {
12 public RealMinusTerm(Term<Double> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
18 return new RealMinusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Double doEvaluate(Double bodyValue) {
23 return -bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(-%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMulTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMulTerm.java
new file mode 100644
index 00000000..ce8f2cd4
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealMulTerm extends RealBinaryTerm {
12 public RealMulTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMulTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue * rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPlusTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPlusTerm.java
new file mode 100644
index 00000000..60efc5fd
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPlusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealPlusTerm extends RealUnaryTerm {
12 public RealPlusTerm(Term<Double> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
18 return new RealPlusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Double doEvaluate(Double bodyValue) {
23 return bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(+%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPowTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPowTerm.java
new file mode 100644
index 00000000..6cb50362
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPowTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealPowTerm extends RealBinaryTerm {
12 public RealPowTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealPowTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.pow(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s ** %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSubTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSubTerm.java
new file mode 100644
index 00000000..32315e8e
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSubTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class RealSubTerm extends RealBinaryTerm {
12 public RealSubTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealSubTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue - rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s - %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSumAggregator.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSumAggregator.java
new file mode 100644
index 00000000..4b090188
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSumAggregator.java
@@ -0,0 +1,90 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.term.StatefulAggregate;
9import tools.refinery.logic.term.StatefulAggregator;
10
11import java.util.Map;
12import java.util.TreeMap;
13
14public final class RealSumAggregator implements StatefulAggregator<Double, Double> {
15 public static final RealSumAggregator INSTANCE = new RealSumAggregator();
16
17 private RealSumAggregator() {
18 }
19
20 @Override
21 public Class<Double> getResultType() {
22 return Double.class;
23 }
24
25 @Override
26 public Class<Double> getInputType() {
27 return Double.class;
28 }
29
30 @Override
31 public StatefulAggregate<Double, Double> createEmptyAggregate() {
32 return new Aggregate();
33 }
34
35 @Override
36 public Double getEmptyResult() {
37 return 0d;
38 }
39
40 private static class Aggregate implements StatefulAggregate<Double, Double> {
41 private final Map<Double, Integer> values;
42
43 public Aggregate() {
44 values = new TreeMap<>();
45 }
46
47 private Aggregate(Aggregate other) {
48 values = new TreeMap<>(other.values);
49 }
50
51 @Override
52 public void add(Double value) {
53 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
54 }
55
56 @Override
57 public void remove(Double value) {
58 values.compute(value, (theValue, currentCount) -> {
59 if (currentCount == null || currentCount <= 0) {
60 throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue));
61 }
62 return currentCount.equals(1) ? null : currentCount - 1;
63 });
64 }
65
66 @Override
67 public Double getResult() {
68 return values.entrySet()
69 .stream()
70 .mapToDouble(entry -> entry.getKey() * entry.getValue())
71 .reduce(Double::sum)
72 .orElse(0d);
73 }
74
75 @Override
76 public boolean isEmpty() {
77 return values.isEmpty();
78 }
79
80 @Override
81 public StatefulAggregate<Double, Double> deepCopy() {
82 return new Aggregate(this);
83 }
84
85 @Override
86 public boolean contains(Double value) {
87 return values.containsKey(value);
88 }
89 }
90}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealTerms.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealTerms.java
new file mode 100644
index 00000000..07dfa96b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealTerms.java
@@ -0,0 +1,95 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.term.Aggregator;
9import tools.refinery.logic.term.ConstantTerm;
10import tools.refinery.logic.term.ExtremeValueAggregator;
11import tools.refinery.logic.term.Term;
12import tools.refinery.logic.term.comparable.*;
13import tools.refinery.logic.term.comparable.*;
14
15import java.util.Comparator;
16
17public final class RealTerms {
18 public static final Aggregator<Double, Double> REAL_SUM = RealSumAggregator.INSTANCE;
19 public static final Aggregator<Double, Double> REAL_MIN = new ExtremeValueAggregator<>(Double.class,
20 Double.POSITIVE_INFINITY);
21 public static final Aggregator<Double, Double> REAL_MAX = new ExtremeValueAggregator<>(Double.class,
22 Double.NEGATIVE_INFINITY, Comparator.reverseOrder());
23
24 private RealTerms() {
25 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
26 }
27
28 public static Term<Double> constant(Double value) {
29 return new ConstantTerm<>(Double.class, value);
30 }
31
32 public static Term<Double> plus(Term<Double> body) {
33 return new RealPlusTerm(body);
34 }
35
36 public static Term<Double> minus(Term<Double> body) {
37 return new RealMinusTerm(body);
38 }
39
40 public static Term<Double> add(Term<Double> left, Term<Double> right) {
41 return new RealAddTerm(left, right);
42 }
43
44 public static Term<Double> sub(Term<Double> left, Term<Double> right) {
45 return new RealSubTerm(left, right);
46 }
47
48 public static Term<Double> mul(Term<Double> left, Term<Double> right) {
49 return new RealMulTerm(left, right);
50 }
51
52 public static Term<Double> div(Term<Double> left, Term<Double> right) {
53 return new RealDivTerm(left, right);
54 }
55
56 public static Term<Double> pow(Term<Double> left, Term<Double> right) {
57 return new RealPowTerm(left, right);
58 }
59
60 public static Term<Double> min(Term<Double> left, Term<Double> right) {
61 return new RealMinTerm(left, right);
62 }
63
64 public static Term<Double> max(Term<Double> left, Term<Double> right) {
65 return new RealMaxTerm(left, right);
66 }
67
68 public static Term<Boolean> eq(Term<Double> left, Term<Double> right) {
69 return new EqTerm<>(Double.class, left, right);
70 }
71
72 public static Term<Boolean> notEq(Term<Double> left, Term<Double> right) {
73 return new NotEqTerm<>(Double.class, left, right);
74 }
75
76 public static Term<Boolean> less(Term<Double> left, Term<Double> right) {
77 return new LessTerm<>(Double.class, left, right);
78 }
79
80 public static Term<Boolean> lessEq(Term<Double> left, Term<Double> right) {
81 return new LessEqTerm<>(Double.class, left, right);
82 }
83
84 public static Term<Boolean> greater(Term<Double> left, Term<Double> right) {
85 return new GreaterTerm<>(Double.class, left, right);
86 }
87
88 public static Term<Boolean> greaterEq(Term<Double> left, Term<Double> right) {
89 return new GreaterEqTerm<>(Double.class, left, right);
90 }
91
92 public static Term<Double> asReal(Term<Integer> body) {
93 return new IntToRealTerm(body);
94 }
95}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealUnaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealUnaryTerm.java
new file mode 100644
index 00000000..14772411
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealUnaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.real;
7
8import tools.refinery.logic.term.UnaryTerm;
9import tools.refinery.logic.term.Term;
10
11public abstract class RealUnaryTerm extends UnaryTerm<Double, Double> {
12 protected RealUnaryTerm(Term<Double> body) {
13 super(Double.class, Double.class, body);
14 }
15}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValue.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValue.java
new file mode 100644
index 00000000..bbdd3f97
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValue.java
@@ -0,0 +1,76 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.truthvalue;
7
8public enum TruthValue {
9 TRUE("true"),
10
11 FALSE("false"),
12
13 UNKNOWN("unknown"),
14
15 ERROR("error");
16
17 private final String name;
18
19 TruthValue(String name) {
20 this.name = name;
21 }
22
23 public String getName() {
24 return name;
25 }
26
27 public static TruthValue toTruthValue(boolean value) {
28 return value ? TRUE : FALSE;
29 }
30
31 public boolean isConsistent() {
32 return this != ERROR;
33 }
34
35 public boolean isComplete() {
36 return this != UNKNOWN;
37 }
38
39 public boolean isConcrete() {
40 return this == TRUE || this == FALSE;
41 }
42
43 public boolean must() {
44 return this == TRUE || this == ERROR;
45 }
46
47 public boolean may() {
48 return this == TRUE || this == UNKNOWN;
49 }
50
51 public TruthValue not() {
52 return switch (this) {
53 case TRUE -> FALSE;
54 case FALSE -> TRUE;
55 default -> this;
56 };
57 }
58
59 public TruthValue merge(TruthValue other) {
60 return switch (this) {
61 case TRUE -> other == UNKNOWN || other == TRUE ? TRUE : ERROR;
62 case FALSE -> other == UNKNOWN || other == FALSE ? FALSE : ERROR;
63 case UNKNOWN -> other;
64 case ERROR -> ERROR;
65 };
66 }
67
68 public TruthValue join(TruthValue other) {
69 return switch (this) {
70 case TRUE -> other == ERROR || other == TRUE ? TRUE : UNKNOWN;
71 case FALSE -> other == ERROR || other == FALSE ? FALSE : UNKNOWN;
72 case UNKNOWN -> UNKNOWN;
73 case ERROR -> other;
74 };
75 }
76}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValueDomain.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValueDomain.java
new file mode 100644
index 00000000..443f744f
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValueDomain.java
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.truthvalue;
7
8import tools.refinery.logic.AbstractDomain;
9
10import java.util.Optional;
11
12// Singleton pattern, because there is only one domain for truth values.
13@SuppressWarnings("squid:S6548")
14public final class TruthValueDomain implements AbstractDomain<TruthValue, Boolean> {
15 public static final TruthValueDomain INSTANCE = new TruthValueDomain();
16
17 private TruthValueDomain() {
18 }
19
20 @Override
21 public Class<TruthValue> abstractType() {
22 return TruthValue.class;
23 }
24
25 @Override
26 public Class<Boolean> concreteType() {
27 return Boolean.class;
28 }
29
30 @Override
31 public TruthValue toAbstract(Boolean concreteValue) {
32 return TruthValue.toTruthValue(concreteValue);
33 }
34
35 @Override
36 public Optional<Boolean> toConcrete(TruthValue abstractValue) {
37 return switch (abstractValue) {
38 case TRUE -> Optional.of(true);
39 case FALSE -> Optional.of(false);
40 default -> Optional.empty();
41 };
42 }
43
44 @Override
45 public boolean isConcrete(TruthValue abstractValue) {
46 return abstractValue.isConcrete();
47 }
48
49 @Override
50 public TruthValue commonRefinement(TruthValue leftValue, TruthValue rightValue) {
51 return leftValue.merge(rightValue);
52 }
53
54 @Override
55 public TruthValue commonAncestor(TruthValue leftValue, TruthValue rightValue) {
56 return leftValue.join(rightValue);
57 }
58
59 @Override
60 public TruthValue unknown() {
61 return TruthValue.UNKNOWN;
62 }
63
64 @Override
65 public boolean isError(TruthValue abstractValue) {
66 return !abstractValue.isConsistent();
67 }
68}
69
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinality.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinality.java
new file mode 100644
index 00000000..478d456b
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinality.java
@@ -0,0 +1,83 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import org.jetbrains.annotations.NotNull;
9import org.jetbrains.annotations.Nullable;
10
11import java.util.function.IntBinaryOperator;
12
13public record FiniteUpperCardinality(int finiteUpperBound) implements UpperCardinality {
14 public FiniteUpperCardinality {
15 if (finiteUpperBound < 0) {
16 throw new IllegalArgumentException("finiteUpperBound must not be negative");
17 }
18 }
19
20 @Override
21 public UpperCardinality add(UpperCardinality other) {
22 return lift(other, Integer::sum);
23 }
24
25 @Override
26 @Nullable
27 public UpperCardinality take(int count) {
28 if (finiteUpperBound < count) {
29 return null;
30 }
31 return new FiniteUpperCardinality(finiteUpperBound - count);
32 }
33
34 @Override
35 public UpperCardinality multiply(UpperCardinality other) {
36 return lift(other, (a, b) -> a * b);
37 }
38
39 @Override
40 public int compareTo(@NotNull UpperCardinality upperCardinality) {
41 if (upperCardinality instanceof FiniteUpperCardinality finiteUpperCardinality) {
42 return compareToInt(finiteUpperCardinality.finiteUpperBound);
43 }
44 if (upperCardinality instanceof UnboundedUpperCardinality) {
45 return -1;
46 }
47 throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality);
48 }
49
50 @Override
51 public int compareToInt(int value) {
52 return Integer.compare(finiteUpperBound, value);
53 }
54
55 @Override
56 public String toString() {
57 return Integer.toString(finiteUpperBound);
58 }
59
60 private UpperCardinality lift(@NotNull UpperCardinality other, IntBinaryOperator operator) {
61 if (other instanceof FiniteUpperCardinality finiteUpperCardinality) {
62 return UpperCardinalities.atMost(operator.applyAsInt(finiteUpperBound,
63 finiteUpperCardinality.finiteUpperBound));
64 }
65 if (other instanceof UnboundedUpperCardinality) {
66 return UpperCardinalities.UNBOUNDED;
67 }
68 throw new IllegalArgumentException("Unknown UpperCardinality: " + other);
69 }
70
71 @Override
72 public boolean equals(Object o) {
73 if (this == o) return true;
74 if (o == null || getClass() != o.getClass()) return false;
75 FiniteUpperCardinality that = (FiniteUpperCardinality) o;
76 return finiteUpperBound == that.finiteUpperBound;
77 }
78
79 @Override
80 public int hashCode() {
81 return finiteUpperBound;
82 }
83}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UnboundedUpperCardinality.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UnboundedUpperCardinality.java
new file mode 100644
index 00000000..b6ecc1bd
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UnboundedUpperCardinality.java
@@ -0,0 +1,66 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import org.jetbrains.annotations.NotNull;
9
10// Singleton implementation, because there is only a single countable infinity.
11@SuppressWarnings("squid:S6548")
12public final class UnboundedUpperCardinality implements UpperCardinality {
13 static final UnboundedUpperCardinality INSTANCE = new UnboundedUpperCardinality();
14
15 private UnboundedUpperCardinality() {
16 // Singleton constructor.
17 }
18
19 @Override
20 public UpperCardinality add(UpperCardinality other) {
21 return this;
22 }
23
24 @Override
25 public UpperCardinality take(int count) {
26 return this;
27 }
28
29 @Override
30 public UpperCardinality multiply(UpperCardinality other) {
31 return this;
32 }
33
34 // This should always be greater than any finite cardinality.
35 @SuppressWarnings("ComparatorMethodParameterNotUsed")
36 @Override
37 public int compareTo(@NotNull UpperCardinality upperCardinality) {
38 if (upperCardinality instanceof FiniteUpperCardinality) {
39 return 1;
40 }
41 if (upperCardinality instanceof UnboundedUpperCardinality) {
42 return 0;
43 }
44 throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality);
45 }
46
47 @Override
48 public int compareToInt(int value) {
49 return 1;
50 }
51
52 @Override
53 public String toString() {
54 return "*";
55 }
56
57 @Override
58 public boolean equals(Object obj) {
59 return this == obj || (obj != null && getClass() == obj.getClass());
60 }
61
62 @Override
63 public int hashCode() {
64 return -1;
65 }
66}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalities.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalities.java
new file mode 100644
index 00000000..edf0afd2
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalities.java
@@ -0,0 +1,38 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8public final class UpperCardinalities {
9 public static final UpperCardinality UNBOUNDED = UnboundedUpperCardinality.INSTANCE;
10
11 public static final UpperCardinality ZERO;
12
13 public static final UpperCardinality ONE;
14
15 private static final FiniteUpperCardinality[] cache = new FiniteUpperCardinality[256];
16
17 static {
18 for (int i = 0; i < cache.length; i++) {
19 cache[i] = new FiniteUpperCardinality(i);
20 }
21 ZERO = cache[0];
22 ONE = cache[1];
23 }
24
25 private UpperCardinalities() {
26 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
27 }
28
29 public static UpperCardinality atMost(int upperBound) {
30 if (upperBound < 0) {
31 return UNBOUNDED;
32 }
33 if (upperBound < cache.length) {
34 return cache[upperBound];
35 }
36 return new FiniteUpperCardinality(upperBound);
37 }
38}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinality.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinality.java
new file mode 100644
index 00000000..5a3dcfb9
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinality.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import org.jetbrains.annotations.Nullable;
9
10public sealed interface UpperCardinality extends Comparable<UpperCardinality> permits FiniteUpperCardinality,
11 UnboundedUpperCardinality {
12 default UpperCardinality min(UpperCardinality other) {
13 return this.compareTo(other) <= 0 ? this : other;
14 }
15
16 default UpperCardinality max(UpperCardinality other) {
17 return this.compareTo(other) >= 0 ? this : other;
18 }
19
20 UpperCardinality add(UpperCardinality other);
21
22 @Nullable
23 UpperCardinality take(int count);
24
25 UpperCardinality multiply(UpperCardinality other);
26
27 int compareToInt(int value);
28
29 static UpperCardinality of(int upperBound) {
30 return UpperCardinalities.atMost(upperBound);
31 }
32}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityAddTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityAddTerm.java
new file mode 100644
index 00000000..5c849b0a
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityAddTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class UpperCardinalityAddTerm extends UpperCardinalityBinaryTerm {
12 protected UpperCardinalityAddTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
13 super(left, right);
14 }
15
16 @Override
17 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
18 return leftValue.add(rightValue);
19 }
20
21 @Override
22 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
23 return new UpperCardinalityAddTerm(substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s + %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityBinaryTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityBinaryTerm.java
new file mode 100644
index 00000000..976d6115
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityBinaryTerm.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import tools.refinery.logic.term.BinaryTerm;
9import tools.refinery.logic.term.Term;
10
11public abstract class UpperCardinalityBinaryTerm extends BinaryTerm<UpperCardinality, UpperCardinality,
12 UpperCardinality> {
13 protected UpperCardinalityBinaryTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(UpperCardinality.class, UpperCardinality.class, UpperCardinality.class, left, right);
15 }
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMaxTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMaxTerm.java
new file mode 100644
index 00000000..0a0c5ee4
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMaxTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class UpperCardinalityMaxTerm extends UpperCardinalityBinaryTerm {
12 protected UpperCardinalityMaxTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
13 super(left, right);
14 }
15
16 @Override
17 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
18 return leftValue.max(rightValue);
19 }
20
21 @Override
22 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
23 return new UpperCardinalityMaxTerm(substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "max(%s, %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMinTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMinTerm.java
new file mode 100644
index 00000000..d6879595
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMinTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class UpperCardinalityMinTerm extends UpperCardinalityBinaryTerm {
12 protected UpperCardinalityMinTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
13 super(left, right);
14 }
15
16 @Override
17 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
18 return leftValue.min(rightValue);
19 }
20
21 @Override
22 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
23 return new UpperCardinalityMinTerm(substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "min(%s, %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMulTerm.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMulTerm.java
new file mode 100644
index 00000000..cb0d685f
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMulTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.Term;
10
11public class UpperCardinalityMulTerm extends UpperCardinalityBinaryTerm {
12 protected UpperCardinalityMulTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
13 super(left, right);
14 }
15
16 @Override
17 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
18 return leftValue.multiply(rightValue);
19 }
20
21 @Override
22 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
23 return new UpperCardinalityMulTerm(substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s * %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregator.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregator.java
new file mode 100644
index 00000000..7c45e6ef
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregator.java
@@ -0,0 +1,84 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import tools.refinery.logic.term.StatefulAggregate;
9import tools.refinery.logic.term.StatefulAggregator;
10
11// Singleton implementation, since there is only one way to aggregate upper cardinalities.
12@SuppressWarnings("squid:S6548")
13public class UpperCardinalitySumAggregator implements StatefulAggregator<UpperCardinality, UpperCardinality> {
14 public static final UpperCardinalitySumAggregator INSTANCE = new UpperCardinalitySumAggregator();
15
16 private UpperCardinalitySumAggregator() {
17 }
18
19 @Override
20 public Class<UpperCardinality> getResultType() {
21 return UpperCardinality.class;
22 }
23
24 @Override
25 public Class<UpperCardinality> getInputType() {
26 return UpperCardinality.class;
27 }
28
29 @Override
30 public StatefulAggregate<UpperCardinality, UpperCardinality> createEmptyAggregate() {
31 return new Aggregate();
32 }
33
34 private static class Aggregate implements StatefulAggregate<UpperCardinality, UpperCardinality> {
35 private int sumFiniteUpperBounds;
36 private int countUnbounded;
37
38 public Aggregate() {
39 this(0, 0);
40 }
41
42 private Aggregate(int sumFiniteUpperBounds, int countUnbounded) {
43 this.sumFiniteUpperBounds = sumFiniteUpperBounds;
44 this.countUnbounded = countUnbounded;
45 }
46
47 @Override
48 public void add(UpperCardinality value) {
49 if (value instanceof FiniteUpperCardinality finiteUpperCardinality) {
50 sumFiniteUpperBounds += finiteUpperCardinality.finiteUpperBound();
51 } else if (value instanceof UnboundedUpperCardinality) {
52 countUnbounded += 1;
53 } else {
54 throw new IllegalArgumentException("Unknown UpperCardinality: " + value);
55 }
56 }
57
58 @Override
59 public void remove(UpperCardinality value) {
60 if (value instanceof FiniteUpperCardinality finiteUpperCardinality) {
61 sumFiniteUpperBounds -= finiteUpperCardinality.finiteUpperBound();
62 } else if (value instanceof UnboundedUpperCardinality) {
63 countUnbounded -= 1;
64 } else {
65 throw new IllegalArgumentException("Unknown UpperCardinality: " + value);
66 }
67 }
68
69 @Override
70 public UpperCardinality getResult() {
71 return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(sumFiniteUpperBounds);
72 }
73
74 @Override
75 public boolean isEmpty() {
76 return sumFiniteUpperBounds == 0 && countUnbounded == 0;
77 }
78
79 @Override
80 public StatefulAggregate<UpperCardinality, UpperCardinality> deepCopy() {
81 return new Aggregate(sumFiniteUpperBounds, countUnbounded);
82 }
83 }
84}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTerms.java b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTerms.java
new file mode 100644
index 00000000..7ec61cac
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTerms.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.term.uppercardinality;
7
8import tools.refinery.logic.term.Aggregator;
9import tools.refinery.logic.term.ConstantTerm;
10import tools.refinery.logic.term.ExtremeValueAggregator;
11import tools.refinery.logic.term.Term;
12import tools.refinery.logic.term.comparable.*;
13
14import java.util.Comparator;
15
16public final class UpperCardinalityTerms {
17 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_SUM =
18 UpperCardinalitySumAggregator.INSTANCE;
19 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MIN =
20 new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.UNBOUNDED);
21 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MAX =
22 new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.ZERO, Comparator.reverseOrder());
23
24 private UpperCardinalityTerms() {
25 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
26 }
27
28 public static Term<UpperCardinality> constant(UpperCardinality value) {
29 return new ConstantTerm<>(UpperCardinality.class, value);
30 }
31
32 public static Term<UpperCardinality> add(Term<UpperCardinality> left, Term<UpperCardinality> right) {
33 return new UpperCardinalityAddTerm(left, right);
34 }
35
36 public static Term<UpperCardinality> mul(Term<UpperCardinality> left, Term<UpperCardinality> right) {
37 return new UpperCardinalityMulTerm(left, right);
38 }
39
40 public static Term<UpperCardinality> min(Term<UpperCardinality> left, Term<UpperCardinality> right) {
41 return new UpperCardinalityMinTerm(left, right);
42 }
43
44 public static Term<UpperCardinality> max(Term<UpperCardinality> left, Term<UpperCardinality> right) {
45 return new UpperCardinalityMaxTerm(left, right);
46 }
47
48 public static Term<Boolean> eq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
49 return new EqTerm<>(UpperCardinality.class, left, right);
50 }
51
52 public static Term<Boolean> notEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
53 return new NotEqTerm<>(UpperCardinality.class, left, right);
54 }
55
56 public static Term<Boolean> less(Term<UpperCardinality> left, Term<UpperCardinality> right) {
57 return new LessTerm<>(UpperCardinality.class, left, right);
58 }
59
60 public static Term<Boolean> lessEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
61 return new LessEqTerm<>(UpperCardinality.class, left, right);
62 }
63
64 public static Term<Boolean> greater(Term<UpperCardinality> left, Term<UpperCardinality> right) {
65 return new GreaterTerm<>(UpperCardinality.class, left, right);
66 }
67
68 public static Term<Boolean> greaterEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
69 return new GreaterEqTerm<>(UpperCardinality.class, left, right);
70 }
71}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/util/CycleDetectingMapper.java b/subprojects/logic/src/main/java/tools/refinery/logic/util/CycleDetectingMapper.java
new file mode 100644
index 00000000..8a9efdd6
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/util/CycleDetectingMapper.java
@@ -0,0 +1,61 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.util;
7
8import java.util.*;
9import java.util.function.Function;
10import java.util.stream.Collectors;
11
12public class CycleDetectingMapper<T, R> {
13 private static final String SEPARATOR = " -> ";
14
15 private final Function<T, String> getName;
16
17 private final Function<T, R> doMap;
18
19 private final Set<T> inProgress = new LinkedHashSet<>();
20
21 private final Map<T, R> results = new HashMap<>();
22
23 public CycleDetectingMapper(Function<T, R> doMap) {
24 this(Objects::toString, doMap);
25 }
26
27 public CycleDetectingMapper(Function<T, String> getName, Function<T, R> doMap) {
28 this.getName = getName;
29 this.doMap = doMap;
30 }
31
32 public R map(T input) {
33 if (inProgress.contains(input)) {
34 var path = inProgress.stream().map(getName).collect(Collectors.joining(SEPARATOR));
35 throw new IllegalArgumentException("Circular reference %s%s%s detected".formatted(path, SEPARATOR,
36 getName.apply(input)));
37 }
38 // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant
39 // way, which would cause a ConcurrentModificationException with computeIfAbsent.
40 @SuppressWarnings("squid:S3824")
41 var result = results.get(input);
42 if (result == null) {
43 inProgress.add(input);
44 try {
45 result = doMap.apply(input);
46 results.put(input, result);
47 } finally {
48 inProgress.remove(input);
49 }
50 }
51 return result;
52 }
53
54 public List<T> getInProgress() {
55 return List.copyOf(inProgress);
56 }
57
58 public R getAlreadyMapped(T input) {
59 return results.get(input);
60 }
61}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/valuation/MapBasedValuation.java b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/MapBasedValuation.java
new file mode 100644
index 00000000..be1862d2
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/MapBasedValuation.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.valuation;
7
8import tools.refinery.logic.term.AnyDataVariable;
9import tools.refinery.logic.term.DataVariable;
10
11import java.util.Map;
12
13record MapBasedValuation(Map<AnyDataVariable, Object> values) implements Valuation {
14 @Override
15 public <T> T getValue(DataVariable<T> variable) {
16 if (!values.containsKey(variable)) {
17 throw new IllegalArgumentException("No value for variable %s".formatted(variable));
18 }
19 var value = values.get(variable);
20 return variable.getType().cast(value);
21 }
22}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/valuation/RestrictedValuation.java b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/RestrictedValuation.java
new file mode 100644
index 00000000..b6ac7cc9
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/RestrictedValuation.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.valuation;
7
8import tools.refinery.logic.term.AnyDataVariable;
9import tools.refinery.logic.term.DataVariable;
10
11import java.util.Set;
12
13public record RestrictedValuation(Valuation valuation, Set<AnyDataVariable> allowedVariables) implements Valuation {
14 @Override
15 public <T> T getValue(DataVariable<T> variable) {
16 if (!allowedVariables.contains(variable)) {
17 throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable));
18 }
19 return valuation.getValue(variable);
20 }
21}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/valuation/SubstitutedValuation.java b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/SubstitutedValuation.java
new file mode 100644
index 00000000..38491481
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/SubstitutedValuation.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.valuation;
7
8import tools.refinery.logic.substitution.Substitution;
9import tools.refinery.logic.term.DataVariable;
10
11public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation {
12 @Override
13 public <T> T getValue(DataVariable<T> variable) {
14 return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable));
15 }
16}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/valuation/Valuation.java b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/Valuation.java
new file mode 100644
index 00000000..95ee887e
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/Valuation.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.valuation;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.logic.substitution.Substitution;
10import tools.refinery.logic.term.AnyDataVariable;
11import tools.refinery.logic.term.DataVariable;
12
13import java.util.Map;
14import java.util.Set;
15
16public interface Valuation {
17 <T> T getValue(DataVariable<T> variable);
18
19 default Valuation substitute(@Nullable Substitution substitution) {
20 if (substitution == null) {
21 return this;
22 }
23 return new SubstitutedValuation(this, substitution);
24 }
25
26 default Valuation restrict(Set<? extends AnyDataVariable> allowedVariables) {
27 return new RestrictedValuation(this, Set.copyOf(allowedVariables));
28 }
29
30 static ValuationBuilder builder() {
31 return new ValuationBuilder();
32 }
33
34 static Valuation empty() {
35 return new MapBasedValuation(Map.of());
36 }
37}
diff --git a/subprojects/logic/src/main/java/tools/refinery/logic/valuation/ValuationBuilder.java b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/ValuationBuilder.java
new file mode 100644
index 00000000..46459ac7
--- /dev/null
+++ b/subprojects/logic/src/main/java/tools/refinery/logic/valuation/ValuationBuilder.java
@@ -0,0 +1,40 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.valuation;
7
8import tools.refinery.logic.term.AnyDataVariable;
9import tools.refinery.logic.term.DataVariable;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.Map;
14
15public class ValuationBuilder {
16 private final Map<AnyDataVariable, Object> values = new HashMap<>();
17
18 ValuationBuilder() {
19 }
20
21 public <T> ValuationBuilder put(DataVariable<T> variable, T value) {
22 return putChecked(variable, value);
23 }
24
25 public ValuationBuilder putChecked(AnyDataVariable variable, Object value) {
26 if (value != null && !variable.getType().isInstance(value)) {
27 throw new IllegalArgumentException("Value %s is not an instance of %s"
28 .formatted(value, variable.getType().getName()));
29 }
30 if (values.containsKey(variable)) {
31 throw new IllegalArgumentException("Already has value for variable %s".formatted(variable));
32 }
33 values.put(variable, value);
34 return this;
35 }
36
37 public Valuation build() {
38 return new MapBasedValuation(Collections.unmodifiableMap(values));
39 }
40}