aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/logic/src
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/logic/src')
-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
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java257
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java322
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java154
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java64
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java111
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java247
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java89
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java94
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java162
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java225
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java97
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.java76
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java127
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.java27
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.java19
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.java27
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java260
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java239
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.java17
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.java30
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java54
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.java79
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java103
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java115
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java57
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java21
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java155
-rw-r--r--subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java123
-rw-r--r--subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/MismatchDescribingDnfEqualityChecker.java68
-rw-r--r--subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/QueryMatchers.java46
-rw-r--r--subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualTo.java41
-rw-r--r--subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualToRaw.java51
196 files changed, 11622 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}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java
new file mode 100644
index 00000000..d5a9ccad
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderLiteralEliminationTest.java
@@ -0,0 +1,257 @@
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 org.junit.jupiter.api.Test;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.CsvSource;
11import tools.refinery.logic.Constraint;
12import tools.refinery.logic.literal.BooleanLiteral;
13import tools.refinery.logic.term.NodeVariable;
14import tools.refinery.logic.term.ParameterDirection;
15import tools.refinery.logic.term.Variable;
16import tools.refinery.logic.term.bool.BoolTerms;
17import tools.refinery.logic.tests.FakeKeyOnlyView;
18
19import java.util.List;
20
21import static org.hamcrest.MatcherAssert.assertThat;
22import static tools.refinery.logic.literal.Literals.check;
23import static tools.refinery.logic.literal.Literals.not;
24import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo;
25
26class DnfBuilderLiteralEliminationTest {
27 private final Constraint friendView = new FakeKeyOnlyView("friend", 2);
28 private final NodeVariable p = Variable.of("p");
29 private final NodeVariable q = Variable.of("q");
30 private final Dnf trueDnf = Dnf.builder().parameter(p, ParameterDirection.IN).clause().build();
31 private final Dnf falseDnf = Dnf.builder().parameter(p).build();
32
33 @Test
34 void eliminateTrueTest() {
35 var actual = Dnf.builder()
36 .parameters(p, q)
37 .clause(BooleanLiteral.TRUE, friendView.call(p, q))
38 .build();
39 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
40
41 assertThat(actual, structurallyEqualTo(expected));
42 }
43
44 @Test
45 void eliminateTrueAssumptionTest() {
46 var actual = Dnf.builder()
47 .parameters(p, q)
48 .clause(check(BoolTerms.constant(true)), friendView.call(p, q))
49 .build();
50 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
51
52 assertThat(actual, structurallyEqualTo(expected));
53 }
54
55 @Test
56 void eliminateFalseTest() {
57 var actual = Dnf.builder()
58 .parameters(p, q)
59 .clause(friendView.call(p, q))
60 .clause(friendView.call(q, p), BooleanLiteral.FALSE)
61 .build();
62 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
63
64 assertThat(actual, structurallyEqualTo(expected));
65 }
66
67 @ParameterizedTest
68 @CsvSource(value = {
69 "false",
70 "null"
71 }, nullValues = "null")
72 void eliminateFalseAssumptionTest(Boolean value) {
73 var actual = Dnf.builder()
74 .parameters(p, q)
75 .clause(friendView.call(p, q))
76 .clause(friendView.call(q, p), check(BoolTerms.constant(value)))
77 .build();
78 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
79
80 assertThat(actual, structurallyEqualTo(expected));
81 }
82
83 @Test
84 void alwaysTrueTest() {
85 var actual = Dnf.builder()
86 .parameters(List.of(p, q), ParameterDirection.IN)
87 .clause(friendView.call(p, q))
88 .clause(BooleanLiteral.TRUE)
89 .build();
90 var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build();
91
92 assertThat(actual, structurallyEqualTo(expected));
93 }
94
95 @Test
96 void alwaysFalseTest() {
97 var actual = Dnf.builder()
98 .parameters(p, q)
99 .clause(friendView.call(p, q), BooleanLiteral.FALSE)
100 .build();
101 var expected = Dnf.builder().parameters(p, q).build();
102
103 assertThat(actual, structurallyEqualTo(expected));
104 }
105
106 @Test
107 void eliminateTrueDnfTest() {
108 var actual = Dnf.builder()
109 .parameters(p, q)
110 .clause(trueDnf.call(q), friendView.call(p, q))
111 .build();
112 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
113
114 assertThat(actual, structurallyEqualTo(expected));
115 }
116
117 @Test
118 void eliminateFalseDnfTest() {
119 var actual = Dnf.builder()
120 .parameters(p, q)
121 .clause(friendView.call(p, q))
122 .clause(friendView.call(q, p), falseDnf.call(q))
123 .build();
124 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
125
126 assertThat(actual, structurallyEqualTo(expected));
127 }
128
129 @Test
130 void alwaysTrueDnfTest() {
131 var actual = Dnf.builder()
132 .parameters(List.of(p, q), ParameterDirection.IN)
133 .clause(friendView.call(p, q))
134 .clause(trueDnf.call(q))
135 .build();
136 var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build();
137
138 assertThat(actual, structurallyEqualTo(expected));
139 }
140
141 @Test
142 void alwaysFalseDnfTest() {
143 var actual = Dnf.builder()
144 .parameters(p, q)
145 .clause(friendView.call(p, q), falseDnf.call(q))
146 .build();
147 var expected = Dnf.builder().parameters(p, q).build();
148
149 assertThat(actual, structurallyEqualTo(expected));
150 }
151
152 @Test
153 void eliminateNotFalseDnfTest() {
154 var actual = Dnf.builder()
155 .parameters(p, q)
156 .clause(not(falseDnf.call(q)), friendView.call(p, q))
157 .build();
158 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
159
160 assertThat(actual, structurallyEqualTo(expected));
161 }
162
163 @Test
164 void eliminateNotTrueDnfTest() {
165 var actual = Dnf.builder()
166 .parameters(p, q)
167 .clause(friendView.call(p, q))
168 .clause(friendView.call(q, p), not(trueDnf.call(q)))
169 .build();
170 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
171
172 assertThat(actual, structurallyEqualTo(expected));
173 }
174
175 @Test
176 void alwaysNotFalseDnfTest() {
177 var actual = Dnf.builder()
178 .parameters(List.of(p, q), ParameterDirection.IN)
179 .clause(friendView.call(p, q))
180 .clause(not(falseDnf.call(q)))
181 .build();
182 var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build();
183
184 assertThat(actual, structurallyEqualTo(expected));
185 }
186
187 @Test
188 void alwaysNotTrueDnfTest() {
189 var actual = Dnf.builder()
190 .parameters(p, q)
191 .clause(friendView.call(p, q), not(trueDnf.call(q)))
192 .build();
193 var expected = Dnf.builder().parameters(p, q).build();
194
195 assertThat(actual, structurallyEqualTo(expected));
196 }
197
198 @Test
199 void removeDuplicateTest() {
200 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
201 friendView.call(p, q),
202 friendView.call(p, q)
203 )));
204 var expected = Dnf.of(builder -> builder.clause((p, q) -> List.of(friendView.call(p, q))));
205
206 assertThat(actual, structurallyEqualTo(expected));
207 }
208
209 @Test
210 void removeContradictoryTest() {
211 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
212 friendView.call(p, q),
213 not(friendView.call(p, q))
214 )));
215 var expected = Dnf.builder().build();
216
217 assertThat(actual, structurallyEqualTo(expected));
218 }
219
220 @Test
221 void removeContradictoryUniversalTest() {
222 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
223 friendView.call(q, q),
224 friendView.call(p, q),
225 not(friendView.call(p, Variable.of()))
226 )));
227 var expected = Dnf.builder().build();
228
229 assertThat(actual, structurallyEqualTo(expected));
230 }
231
232 @Test
233 void removeContradictoryExistentialUniversalTest() {
234 var actual = Dnf.of(builder -> builder.clause((p) -> List.of(
235 friendView.call(p, Variable.of()),
236 not(friendView.call(p, Variable.of()))
237 )));
238 var expected = Dnf.builder().build();
239
240 assertThat(actual, structurallyEqualTo(expected));
241 }
242
243 @Test
244 void removeContradictoryUniversalParameterTest() {
245 var actual = Dnf.of(builder -> {
246 var p = builder.parameter("p");
247 builder.clause((q) -> List.of(
248 friendView.call(q, q),
249 friendView.call(p, q),
250 not(friendView.call(p, Variable.of()))
251 ));
252 });
253 var expected = Dnf.builder().parameter(p).build();
254
255 assertThat(actual, structurallyEqualTo(expected));
256 }
257}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java
new file mode 100644
index 00000000..0e1f77e2
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfBuilderVariableUnificationTest.java
@@ -0,0 +1,322 @@
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 org.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.term.ParameterDirection;
11import tools.refinery.logic.term.Variable;
12import tools.refinery.logic.tests.FakeKeyOnlyView;
13
14import java.util.List;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo;
18
19class DnfBuilderVariableUnificationTest {
20 private final Constraint friendView = new FakeKeyOnlyView("friend", 2);
21 private final Constraint childrenView = new FakeKeyOnlyView("children", 2);
22
23 @Test
24 void equalToParameterTest() {
25 var actual = Dnf.of(builder -> {
26 var p = builder.parameter("p");
27 builder.clause(q -> List.of(
28 friendView.call(p, q),
29 p.isEquivalent(q)
30 ));
31 });
32
33 var expectedP = Variable.of("p");
34 assertThat(actual, structurallyEqualTo(
35 List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)),
36 List.of(
37 List.of(friendView.call(expectedP, expectedP))
38 )
39 ));
40 }
41
42 @Test
43 void equalToParameterReverseTest() {
44 var actual = Dnf.of(builder -> {
45 var p = builder.parameter("p");
46 builder.clause(q -> List.of(
47 friendView.call(p, q),
48 q.isEquivalent(p)
49 ));
50 });
51
52 var expectedP = Variable.of("p");
53 assertThat(actual, structurallyEqualTo(
54 List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)),
55 List.of(
56 List.of(friendView.call(expectedP, expectedP))
57 )
58 ));
59 }
60
61 @Test
62 void equalQuantifiedTest() {
63 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
64 friendView.call(p, q),
65 p.isEquivalent(q)
66 )));
67
68 var expectedP = Variable.of("p");
69 assertThat(actual, structurallyEqualTo(
70 List.of(),
71 List.of(
72 List.of(friendView.call(expectedP, expectedP))
73 )
74 ));
75 }
76
77 @Test
78 void equalQuantifiedTransitiveTest() {
79 var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of(
80 friendView.call(p, q),
81 p.isEquivalent(q),
82 childrenView.call(p, r),
83 q.isEquivalent(r)
84 )));
85
86 var expectedP = Variable.of("p");
87 assertThat(actual, structurallyEqualTo(
88 List.of(),
89 List.of(
90 List.of(friendView.call(expectedP, expectedP), childrenView.call(expectedP, expectedP))
91 )
92 ));
93 }
94
95 @Test
96 void equalQuantifiedTransitiveRemoveDuplicateTest() {
97 var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of(
98 friendView.call(p, q),
99 p.isEquivalent(q),
100 friendView.call(p, r),
101 q.isEquivalent(r)
102 )));
103
104 var expectedP = Variable.of("p");
105 assertThat(actual, structurallyEqualTo(
106 List.of(),
107 List.of(
108 List.of(friendView.call(expectedP, expectedP))
109 )
110 ));
111 }
112
113 @Test
114 void parametersEqualTest() {
115 var actual = Dnf.of(builder -> {
116 var p = builder.parameter("p");
117 var q = builder.parameter("q");
118 builder.clause(
119 friendView.call(p, q),
120 p.isEquivalent(q)
121 );
122 });
123
124 var expectedP = Variable.of("p");
125 var expectedQ = Variable.of("q");
126 assertThat(actual, structurallyEqualTo(
127 List.of(
128 new SymbolicParameter(expectedP, ParameterDirection.OUT),
129 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
130 ),
131 List.of(
132 List.of(friendView.call(expectedP, expectedP), expectedQ.isEquivalent(expectedP))
133 )
134 ));
135 }
136
137 @Test
138 void parametersEqualTransitiveTest() {
139 var actual = Dnf.of(builder -> {
140 var p = builder.parameter("p");
141 var q = builder.parameter("q");
142 var r = builder.parameter("r");
143 builder.clause(
144 friendView.call(p, q),
145 childrenView.call(p, r),
146 p.isEquivalent(q),
147 r.isEquivalent(q)
148 );
149 });
150
151 var expectedP = Variable.of("p");
152 var expectedQ = Variable.of("q");
153 var expectedR = Variable.of("r");
154 assertThat(actual, structurallyEqualTo(
155 List.of(
156 new SymbolicParameter(expectedP, ParameterDirection.OUT),
157 new SymbolicParameter(expectedQ, ParameterDirection.OUT),
158 new SymbolicParameter(expectedR, ParameterDirection.OUT)
159 ),
160 List.of(
161 List.of(
162 friendView.call(expectedP, expectedP),
163 expectedQ.isEquivalent(expectedP),
164 expectedR.isEquivalent(expectedP),
165 childrenView.call(expectedP, expectedP)
166 )
167 )
168 ));
169 }
170
171 @Test
172 void parameterAndQuantifiedEqualsTest() {
173 var actual = Dnf.of(builder -> {
174 var p = builder.parameter("p");
175 var q = builder.parameter("q");
176 builder.clause((r) -> List.of(
177 friendView.call(p, r),
178 p.isEquivalent(r),
179 childrenView.call(q, r),
180 q.isEquivalent(r)
181 ));
182 });
183
184
185 var expectedP = Variable.of("p");
186 var expectedQ = Variable.of("q");
187 assertThat(actual, structurallyEqualTo(
188 List.of(
189 new SymbolicParameter(expectedP, ParameterDirection.OUT),
190 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
191 ),
192 List.of(
193 List.of(
194 friendView.call(expectedP, expectedP),
195 expectedQ.isEquivalent(expectedP),
196 childrenView.call(expectedP, expectedP)
197 )
198 )
199 ));
200 }
201
202 @Test
203 void parameterAndQuantifiedEqualsReverseFirstTest() {
204 var actual = Dnf.of(builder -> {
205 var p = builder.parameter("p");
206 var q = builder.parameter("q");
207 builder.clause((r) -> List.of(
208 friendView.call(p, r),
209 r.isEquivalent(p),
210 childrenView.call(q, r),
211 q.isEquivalent(r)
212 ));
213 });
214
215 var expectedP = Variable.of("p");
216 var expectedQ = Variable.of("q");
217 assertThat(actual, structurallyEqualTo(
218 List.of(
219 new SymbolicParameter(expectedP, ParameterDirection.OUT),
220 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
221 ),
222 List.of(
223 List.of(
224 friendView.call(expectedP, expectedP),
225 expectedQ.isEquivalent(expectedP),
226 childrenView.call(expectedP, expectedP)
227 )
228 )
229 ));
230 }
231
232 @Test
233 void parameterAndQuantifiedEqualsReverseSecondTest() {
234 var actual = Dnf.of(builder -> {
235 var p = builder.parameter("p");
236 var q = builder.parameter("q");
237 builder.clause((r) -> List.of(
238 friendView.call(p, r),
239 p.isEquivalent(r),
240 childrenView.call(q, r),
241 r.isEquivalent(q)
242 ));
243 });
244
245 var expectedP = Variable.of("p");
246 var expectedQ = Variable.of("q");
247 assertThat(actual, structurallyEqualTo(
248 List.of(
249 new SymbolicParameter(expectedP, ParameterDirection.OUT),
250 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
251 ),
252 List.of(
253 List.of(
254 friendView.call(expectedP, expectedP),
255 expectedQ.isEquivalent(expectedP),
256 childrenView.call(expectedP, expectedP)
257 )
258 )
259 ));
260 }
261
262 @Test
263 void parameterAndQuantifiedEqualsReverseBoth() {
264 var actual = Dnf.of(builder -> {
265 var p = builder.parameter("p");
266 var q = builder.parameter("q");
267 builder.clause((r) -> List.of(
268 friendView.call(p, r),
269 p.isEquivalent(r),
270 childrenView.call(q, r),
271 r.isEquivalent(q)
272 ));
273 });
274
275 var expectedP = Variable.of("p");
276 var expectedQ = Variable.of("q");
277 assertThat(actual, structurallyEqualTo(
278 List.of(
279 new SymbolicParameter(expectedP, ParameterDirection.OUT),
280 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
281 ),
282 List.of(
283 List.of(
284 friendView.call(expectedP, expectedP),
285 expectedQ.isEquivalent(expectedP),
286 childrenView.call(expectedP, expectedP)
287 )
288 )
289 ));
290 }
291
292 @Test
293 void parameterAndTwoQuantifiedEqualsTest() {
294 var actual = Dnf.of(builder -> {
295 var p = builder.parameter("p");
296 var q = builder.parameter("q");
297 builder.clause((r, s) -> List.of(
298 r.isEquivalent(s),
299 friendView.call(p, r),
300 p.isEquivalent(r),
301 childrenView.call(q, s),
302 q.isEquivalent(s)
303 ));
304 });
305
306 var expectedP = Variable.of("p");
307 var expectedQ = Variable.of("q");
308 assertThat(actual, structurallyEqualTo(
309 List.of(
310 new SymbolicParameter(expectedP, ParameterDirection.OUT),
311 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
312 ),
313 List.of(
314 List.of(
315 friendView.call(expectedP, expectedP),
316 expectedQ.isEquivalent(expectedP),
317 childrenView.call(expectedP, expectedP)
318 )
319 )
320 ));
321 }
322}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java
new file mode 100644
index 00000000..dd624548
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/DnfToDefinitionStringTest.java
@@ -0,0 +1,154 @@
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.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.term.NodeVariable;
11import tools.refinery.logic.term.ParameterDirection;
12import tools.refinery.logic.term.Variable;
13import tools.refinery.logic.tests.FakeKeyOnlyView;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.is;
17import static tools.refinery.logic.literal.Literals.not;
18
19class DnfToDefinitionStringTest {
20 private static final Constraint personView = new FakeKeyOnlyView("person", 1);
21 private static final Constraint friendView = new FakeKeyOnlyView("friend", 2);
22 private static final NodeVariable p = Variable.of("p");
23 private static final NodeVariable q = Variable.of("q");
24
25 @Test
26 void noClausesTest() {
27 var dnf = Dnf.builder("Example").parameter(p).build();
28
29 assertThat(dnf.toDefinitionString(), is("""
30 pred Example(p) <->
31 <no clauses>.
32 """));
33 }
34
35 @Test
36 void noParametersTest() {
37 var dnf = Dnf.builder("Example").build();
38
39 assertThat(dnf.toDefinitionString(), is("""
40 pred Example() <->
41 <no clauses>.
42 """));
43 }
44
45 @Test
46 void emptyClauseTest() {
47 var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build();
48
49 assertThat(dnf.toDefinitionString(), is("""
50 pred Example(in p) <->
51 <empty>.
52 """));
53 }
54
55 @Test
56 void relationViewPositiveTest() {
57 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build();
58
59 assertThat(dnf.toDefinitionString(), is("""
60 pred Example(p) <->
61 friend(p, q).
62 """));
63 }
64
65 @Test
66 void relationViewNegativeTest() {
67 var dnf = Dnf.builder("Example")
68 .parameter(p, ParameterDirection.IN)
69 .clause(not(friendView.call(p, q)))
70 .build();
71
72 assertThat(dnf.toDefinitionString(), is("""
73 pred Example(in p) <->
74 !(friend(p, q)).
75 """));
76 }
77
78 @Test
79 void relationViewTransitiveTest() {
80 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build();
81
82 assertThat(dnf.toDefinitionString(), is("""
83 pred Example(p) <->
84 friend+(p, q).
85 """));
86 }
87
88 @Test
89 void multipleParametersTest() {
90 var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build();
91
92 assertThat(dnf.toDefinitionString(), is("""
93 pred Example(p, q) <->
94 friend(p, q).
95 """));
96 }
97
98 @Test
99 void multipleLiteralsTest() {
100 var dnf = Dnf.builder("Example")
101 .parameter(p)
102 .clause(
103 personView.call(p),
104 personView.call(q),
105 friendView.call(p, q)
106 )
107 .build();
108
109 assertThat(dnf.toDefinitionString(), is("""
110 pred Example(p) <->
111 person(p),
112 person(q),
113 friend(p, q).
114 """));
115 }
116
117 @Test
118 void multipleClausesTest() {
119 var dnf = Dnf.builder("Example")
120 .parameter(p)
121 .clause(friendView.call(p, q))
122 .clause(friendView.call(q, p))
123 .build();
124
125 assertThat(dnf.toDefinitionString(), is("""
126 pred Example(p) <->
127 friend(p, q)
128 ;
129 friend(q, p).
130 """));
131 }
132
133 @Test
134 void dnfTest() {
135 var r = Variable.of("r");
136 var s = Variable.of("s");
137 var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build();
138 var dnf = Dnf.builder("Example")
139 .parameter(p)
140 .clause(
141 personView.call(p),
142 personView.call(q),
143 not(called.call(p, q))
144 )
145 .build();
146
147 assertThat(dnf.toDefinitionString(), is("""
148 pred Example(p) <->
149 person(p),
150 person(q),
151 !(@Dnf Called(p, q)).
152 """));
153 }
154}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java
new file mode 100644
index 00000000..e140be1e
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/HashCodeTest.java
@@ -0,0 +1,64 @@
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 org.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.term.NodeVariable;
11import tools.refinery.logic.term.Variable;
12import tools.refinery.logic.tests.FakeKeyOnlyView;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16import static org.hamcrest.Matchers.not;
17
18class HashCodeTest {
19 private static final Constraint personView = new FakeKeyOnlyView("Person", 1);
20 private static final Constraint friendView = new FakeKeyOnlyView("friend", 2);
21 private static final NodeVariable p = Variable.of("p");
22 private static final NodeVariable q = Variable.of("q");
23
24 @Test
25 void flatEqualsTest() {
26 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
27 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
28
29 assertThat(actual.hashCodeWithSubstitution(), is(expected.hashCodeWithSubstitution()));
30 }
31
32 @Test
33 void flatNotEqualsTest() {
34 var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build();
35 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
36
37 assertThat(actual.hashCodeWithSubstitution(), not(expected.hashCodeWithSubstitution()));
38 }
39
40 @Test
41 void deepEqualsTest() {
42 var expected2 = Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build();
43 var expected = Dnf.builder("Expected").parameters(q).clause(
44 expected2.call(q)
45 ).build();
46 var actual = Dnf.builder("Actual").parameters(q).clause(
47 expected2.call(q)
48 ).build();
49
50 assertThat(actual.hashCodeWithSubstitution(), is(expected.hashCodeWithSubstitution()));
51 }
52
53 @Test
54 void deepNotEqualsTest() {
55 var expected = Dnf.builder("Expected").parameters(q).clause(
56 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
57 ).build();
58 var actual = Dnf.builder("Actual").parameters(q).clause(
59 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
60 ).build();
61
62 assertThat(actual.hashCodeWithSubstitution(), not(expected.hashCodeWithSubstitution()));
63 }
64}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java
new file mode 100644
index 00000000..8ea27cc9
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/TopologicalSortTest.java
@@ -0,0 +1,111 @@
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 org.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.InvalidQueryException;
11import tools.refinery.logic.term.NodeVariable;
12import tools.refinery.logic.term.ParameterDirection;
13import tools.refinery.logic.term.Variable;
14import tools.refinery.logic.tests.FakeKeyOnlyView;
15
16import java.util.List;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.junit.jupiter.api.Assertions.assertThrows;
20import static tools.refinery.logic.literal.Literals.not;
21import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo;
22
23class TopologicalSortTest {
24 private static final Constraint friendView = new FakeKeyOnlyView("friend", 2);
25 private static final Dnf example = Dnf.of("example", builder -> {
26 var a = builder.parameter("a", ParameterDirection.IN);
27 var b = builder.parameter("b", ParameterDirection.IN);
28 var c = builder.parameter("c", ParameterDirection.OUT);
29 var d = builder.parameter("d", ParameterDirection.OUT);
30 builder.clause(
31 friendView.call(a, b),
32 friendView.call(b, c),
33 friendView.call(c, d)
34 );
35 });
36 private static final NodeVariable p = Variable.of("p");
37 private static final NodeVariable q = Variable.of("q");
38 private static final NodeVariable r = Variable.of("r");
39 private static final NodeVariable s = Variable.of("s");
40 private static final NodeVariable t = Variable.of("t");
41
42 @Test
43 void topologicalSortTest() {
44 var actual = Dnf.builder("Actual")
45 .parameter(p, ParameterDirection.IN)
46 .parameter(q, ParameterDirection.OUT)
47 .clause(
48 not(friendView.call(p, q)),
49 example.call(p, q, r, s),
50 example.call(r, t, q, s),
51 friendView.call(r, t)
52 )
53 .build();
54
55 assertThat(actual, structurallyEqualTo(
56 List.of(
57 new SymbolicParameter(p, ParameterDirection.IN),
58 new SymbolicParameter(q, ParameterDirection.OUT)
59 ),
60 List.of(
61 List.of(
62 friendView.call(r, t),
63 example.call(r, t, q, s),
64 not(friendView.call(p, q)),
65 example.call(p, q, r, s)
66 )
67 )
68 ));
69 }
70
71 @Test
72 void missingInputTest() {
73 var builder = Dnf.builder("Actual")
74 .parameter(p, ParameterDirection.OUT)
75 .parameter(q, ParameterDirection.OUT)
76 .clause(
77 not(friendView.call(p, q)),
78 example.call(p, q, r, s),
79 example.call(r, t, q, s),
80 friendView.call(r, t)
81 );
82 assertThrows(InvalidQueryException.class, builder::build);
83 }
84
85 @Test
86 void missingVariableTest() {
87 var builder = Dnf.builder("Actual")
88 .parameter(p, ParameterDirection.IN)
89 .parameter(q, ParameterDirection.OUT)
90 .clause(
91 not(friendView.call(p, q)),
92 example.call(p, q, r, s),
93 example.call(r, t, q, s)
94 );
95 assertThrows(InvalidQueryException.class, builder::build);
96 }
97
98 @Test
99 void circularDependencyTest() {
100 var builder = Dnf.builder("Actual")
101 .parameter(p, ParameterDirection.IN)
102 .parameter(q, ParameterDirection.OUT)
103 .clause(
104 not(friendView.call(p, q)),
105 example.call(p, q, r, s),
106 example.call(r, t, q, s),
107 example.call(p, q, r, t)
108 );
109 assertThrows(InvalidQueryException.class, builder::build);
110 }
111}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java
new file mode 100644
index 00000000..f9f39b8a
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/dnf/VariableDirectionTest.java
@@ -0,0 +1,247 @@
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 org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11import tools.refinery.logic.Constraint;
12import tools.refinery.logic.literal.BooleanLiteral;
13import tools.refinery.logic.literal.Literal;
14import tools.refinery.logic.term.DataVariable;
15import tools.refinery.logic.term.NodeVariable;
16import tools.refinery.logic.term.ParameterDirection;
17import tools.refinery.logic.term.Variable;
18import tools.refinery.logic.tests.FakeFunctionView;
19import tools.refinery.logic.tests.FakeKeyOnlyView;
20
21import java.util.ArrayList;
22import java.util.List;
23import java.util.stream.Stream;
24
25import static org.hamcrest.MatcherAssert.assertThat;
26import static org.hamcrest.Matchers.hasItem;
27import static org.hamcrest.Matchers.not;
28import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
29import static org.junit.jupiter.api.Assertions.assertThrows;
30import static tools.refinery.logic.literal.Literals.not;
31import static tools.refinery.logic.term.int_.IntTerms.INT_SUM;
32
33class VariableDirectionTest {
34 private static final Constraint personView = new FakeKeyOnlyView("Person", 1);
35 private static final Constraint friendView = new FakeKeyOnlyView("friend", 2);
36 private static final FakeFunctionView<Integer> ageView = new FakeFunctionView<>("age", 1, Integer.class);
37 private static final NodeVariable p = Variable.of("p");
38 private static final NodeVariable q = Variable.of("q");
39 private static final DataVariable<Integer> x = Variable.of("x", Integer.class);
40 private static final DataVariable<Integer> y = Variable.of("y", Integer.class);
41 private static final DataVariable<Integer> z = Variable.of("z", Integer.class);
42
43 @ParameterizedTest
44 @MethodSource("clausesWithVariableInput")
45 void unboundOutVariableTest(List<? extends Literal> clause) {
46 var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause);
47 assertThrows(InvalidClauseException.class, builder::build);
48 }
49
50 @ParameterizedTest
51 @MethodSource("clausesWithVariableInput")
52 void unboundInVariableTest(List<? extends Literal> clause) {
53 var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(clause);
54 var dnf = assertDoesNotThrow(builder::build);
55 var clauses = dnf.getClauses();
56 if (!clauses.isEmpty()) {
57 assertThat(clauses.getFirst().positiveVariables(), hasItem(p));
58 }
59 }
60
61 @ParameterizedTest
62 @MethodSource("clausesWithVariableInput")
63 void boundPrivateVariableTest(List<? extends Literal> clause) {
64 var clauseWithBinding = new ArrayList<Literal>(clause);
65 clauseWithBinding.add(personView.call(p));
66 var builder = Dnf.builder().clause(clauseWithBinding);
67 var dnf = assertDoesNotThrow(builder::build);
68 var clauses = dnf.getClauses();
69 if (!clauses.isEmpty()) {
70 assertThat(clauses.getFirst().positiveVariables(), hasItem(p));
71 }
72 }
73
74 static Stream<Arguments> clausesWithVariableInput() {
75 return Stream.concat(
76 clausesNotBindingVariable(),
77 literalToClauseArgumentStream(literalsWithRequiredVariableInput())
78 );
79 }
80
81 @ParameterizedTest
82 @MethodSource("clausesNotBindingVariable")
83 void unboundPrivateVariableTest(List<? extends Literal> clause) {
84 var builder = Dnf.builder().clause(clause);
85 var dnf = assertDoesNotThrow(builder::build);
86 var clauses = dnf.getClauses();
87 if (!clauses.isEmpty()) {
88 assertThat(clauses.getFirst().positiveVariables(), not(hasItem(p)));
89 }
90 }
91
92 @ParameterizedTest
93 @MethodSource("clausesNotBindingVariable")
94 void unboundByEquivalencePrivateVariableTest(List<? extends Literal> clause) {
95 var r = Variable.of("r");
96 var clauseWithEquivalence = new ArrayList<Literal>(clause);
97 clauseWithEquivalence.add(r.isEquivalent(p));
98 var builder = Dnf.builder().clause(clauseWithEquivalence);
99 assertThrows(InvalidClauseException.class, builder::build);
100 }
101
102 static Stream<Arguments> clausesNotBindingVariable() {
103 return Stream.concat(
104 Stream.of(
105 Arguments.of(List.of()),
106 Arguments.of(List.of(BooleanLiteral.TRUE)),
107 Arguments.of(List.of(BooleanLiteral.FALSE))
108 ),
109 literalToClauseArgumentStream(literalsWithPrivateVariable())
110 );
111 }
112
113 @ParameterizedTest
114 @MethodSource("literalsWithPrivateVariable")
115 void unboundTwicePrivateVariableTest(Literal literal) {
116 var builder = Dnf.builder().clause(not(personView.call(p)), literal);
117 assertThrows(InvalidClauseException.class, builder::build);
118 }
119
120 @ParameterizedTest
121 @MethodSource("literalsWithPrivateVariable")
122 void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) {
123 var r = Variable.of("r");
124 var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal);
125 assertThrows(InvalidClauseException.class, builder::build);
126 }
127
128 static Stream<Arguments> literalsWithPrivateVariable() {
129 var dnfWithOutput = Dnf.builder("WithOutput")
130 .parameter(p, ParameterDirection.OUT)
131 .parameter(q, ParameterDirection.OUT)
132 .clause(friendView.call(p, q))
133 .build();
134 var dnfWithOutputToAggregate = Dnf.builder("WithOutputToAggregate")
135 .parameter(p, ParameterDirection.OUT)
136 .parameter(q, ParameterDirection.OUT)
137 .parameter(x, ParameterDirection.OUT)
138 .clause(
139 friendView.call(p, q),
140 ageView.call(q, x)
141 )
142 .build();
143
144 return Stream.of(
145 Arguments.of(not(friendView.call(p, q))),
146 Arguments.of(y.assign(friendView.count(p, q))),
147 Arguments.of(y.assign(ageView.aggregate(INT_SUM, p))),
148 Arguments.of(not(dnfWithOutput.call(p, q))),
149 Arguments.of(y.assign(dnfWithOutput.count(p, q))),
150 Arguments.of(y.assign(dnfWithOutputToAggregate.aggregateBy(z, INT_SUM, p, q, z)))
151 );
152 }
153
154 @ParameterizedTest
155 @MethodSource("literalsWithRequiredVariableInput")
156 void unboundPrivateVariableTest(Literal literal) {
157 var builder = Dnf.builder().clause(literal);
158 assertThrows(InvalidClauseException.class, builder::build);
159 }
160
161 @ParameterizedTest
162 @MethodSource("literalsWithRequiredVariableInput")
163 void boundPrivateVariableInputTest(Literal literal) {
164 var builder = Dnf.builder().clause(personView.call(p), literal);
165 var dnf = assertDoesNotThrow(builder::build);
166 assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p));
167 }
168
169 static Stream<Arguments> literalsWithRequiredVariableInput() {
170 var dnfWithInput = Dnf.builder("WithInput")
171 .parameter(p, ParameterDirection.IN)
172 .parameter(q, ParameterDirection.OUT)
173 .clause(friendView.call(p, q)).build();
174 var dnfWithInputToAggregate = Dnf.builder("WithInputToAggregate")
175 .parameter(p, ParameterDirection.IN)
176 .parameter(q, ParameterDirection.OUT)
177 .parameter(x, ParameterDirection.OUT)
178 .clause(
179 friendView.call(p, q),
180 ageView.call(q, x)
181 ).build();
182
183 return Stream.of(
184 Arguments.of(dnfWithInput.call(p, q)),
185 Arguments.of(dnfWithInput.call(p, p)),
186 Arguments.of(not(dnfWithInput.call(p, q))),
187 Arguments.of(not(dnfWithInput.call(p, p))),
188 Arguments.of(y.assign(dnfWithInput.count(p, q))),
189 Arguments.of(y.assign(dnfWithInput.count(p, p))),
190 Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, q, z))),
191 Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, p, z)))
192 );
193 }
194
195 @ParameterizedTest
196 @MethodSource("literalsWithVariableOutput")
197 void boundParameterTest(Literal literal) {
198 var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(literal);
199 var dnf = assertDoesNotThrow(builder::build);
200 assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p));
201 }
202
203 @ParameterizedTest
204 @MethodSource("literalsWithVariableOutput")
205 void boundTwiceParameterTest(Literal literal) {
206 var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(literal);
207 var dnf = assertDoesNotThrow(builder::build);
208 assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p));
209 }
210
211 @ParameterizedTest
212 @MethodSource("literalsWithVariableOutput")
213 void boundPrivateVariableOutputTest(Literal literal) {
214 var dnfWithInput = Dnf.builder("WithInput")
215 .parameter(p, ParameterDirection.IN)
216 .clause(personView.call(p))
217 .build();
218 var builder = Dnf.builder().clause(dnfWithInput.call(p), literal);
219 var dnf = assertDoesNotThrow(builder::build);
220 assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p));
221 }
222
223 @ParameterizedTest
224 @MethodSource("literalsWithVariableOutput")
225 void boundTwicePrivateVariableOutputTest(Literal literal) {
226 var builder = Dnf.builder().clause(personView.call(p), literal);
227 var dnf = assertDoesNotThrow(builder::build);
228 assertThat(dnf.getClauses().getFirst().positiveVariables(), hasItem(p));
229 }
230
231 static Stream<Arguments> literalsWithVariableOutput() {
232 var dnfWithOutput = Dnf.builder("WithOutput")
233 .parameter(p, ParameterDirection.OUT)
234 .parameter(q, ParameterDirection.OUT)
235 .clause(friendView.call(p, q))
236 .build();
237
238 return Stream.of(
239 Arguments.of(friendView.call(p, q)),
240 Arguments.of(dnfWithOutput.call(p, q))
241 );
242 }
243
244 private static Stream<Arguments> literalToClauseArgumentStream(Stream<Arguments> literalArgumentsStream) {
245 return literalArgumentsStream.map(arguments -> Arguments.of(List.of(arguments.get()[0])));
246 }
247}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java
new file mode 100644
index 00000000..76639e18
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/literal/AggregationLiteralTest.java
@@ -0,0 +1,89 @@
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 org.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.InvalidQueryException;
11import tools.refinery.logic.dnf.Dnf;
12import tools.refinery.logic.dnf.InvalidClauseException;
13import tools.refinery.logic.term.*;
14
15import java.util.List;
16import java.util.Set;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.hamcrest.Matchers.*;
20import static org.junit.jupiter.api.Assertions.assertAll;
21import static org.junit.jupiter.api.Assertions.assertThrows;
22import static tools.refinery.logic.literal.Literals.not;
23import static tools.refinery.logic.term.int_.IntTerms.INT_SUM;
24import static tools.refinery.logic.term.int_.IntTerms.constant;
25
26class AggregationLiteralTest {
27 private static final NodeVariable p = Variable.of("p");
28 private static final DataVariable<Integer> x = Variable.of("x", Integer.class);
29 private static final DataVariable<Integer> y = Variable.of("y", Integer.class);
30 private static final DataVariable<Integer> z = Variable.of("z", Integer.class);
31 private static final Constraint fakeConstraint = new Constraint() {
32 @Override
33 public String name() {
34 return getClass().getName();
35 }
36
37 @Override
38 public List<Parameter> getParameters() {
39 return List.of(
40 new Parameter(null, ParameterDirection.OUT),
41 new Parameter(Integer.class, ParameterDirection.OUT)
42 );
43 }
44 };
45
46 @Test
47 void parameterDirectionTest() {
48 var literal = x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y));
49 assertAll(
50 () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(x)),
51 () -> assertThat(literal.getInputVariables(Set.of()), empty()),
52 () -> assertThat(literal.getInputVariables(Set.of(p)), containsInAnyOrder(p)),
53 () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(p, y)),
54 () -> assertThat(literal.getPrivateVariables(Set.of(p)), containsInAnyOrder(y))
55 );
56 }
57
58 @Test
59 void missingAggregationVariableTest() {
60 var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z);
61 assertThrows(InvalidQueryException.class, () -> x.assign(aggregation));
62 }
63
64 @Test
65 void circularAggregationVariableTest() {
66 var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x);
67 assertThrows(InvalidQueryException.class, () -> x.assign(aggregation));
68 }
69
70 @Test
71 void unboundTwiceVariableTest() {
72 var builder = Dnf.builder()
73 .clause(
74 not(fakeConstraint.call(p, y)),
75 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y))
76 );
77 assertThrows(InvalidClauseException.class, builder::build);
78 }
79
80 @Test
81 void unboundBoundVariableTest() {
82 var builder = Dnf.builder()
83 .clause(
84 y.assign(constant(27)),
85 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y))
86 );
87 assertThrows(InvalidClauseException.class, builder::build);
88 }
89}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java
new file mode 100644
index 00000000..0fb2e7c9
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/literal/CallLiteralTest.java
@@ -0,0 +1,94 @@
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 org.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.term.NodeVariable;
11import tools.refinery.logic.term.Parameter;
12import tools.refinery.logic.term.ParameterDirection;
13import tools.refinery.logic.term.Variable;
14
15import java.util.List;
16import java.util.Set;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.hamcrest.Matchers.containsInAnyOrder;
20import static org.hamcrest.Matchers.empty;
21import static org.junit.jupiter.api.Assertions.assertAll;
22import static tools.refinery.logic.literal.Literals.not;
23
24class CallLiteralTest {
25 private static final NodeVariable p = Variable.of("p");
26 private static final NodeVariable q = Variable.of("q");
27 private static final NodeVariable r = Variable.of("r");
28 private static final NodeVariable s = Variable.of("s");
29
30 private static final Constraint fakeConstraint = new Constraint() {
31 @Override
32 public String name() {
33 return getClass().getName();
34 }
35
36 @Override
37 public List<Parameter> getParameters() {
38 return List.of(
39 new Parameter(null, ParameterDirection.IN),
40 new Parameter(null, ParameterDirection.IN),
41 new Parameter(null, ParameterDirection.OUT),
42 new Parameter(null, ParameterDirection.OUT)
43 );
44 }
45 };
46
47 @Test
48 void notRepeatedPositiveDirectionTest() {
49 var literal = fakeConstraint.call(p, q, r, s);
50 assertAll(
51 () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(r, s)),
52 () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)),
53 () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q)),
54 () -> assertThat(literal.getPrivateVariables(Set.of()), empty()),
55 () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), empty())
56 );
57 }
58
59 @Test
60 void notRepeatedNegativeDirectionTest() {
61 var literal = not(fakeConstraint.call(p, q, r, s));
62 assertAll(
63 () -> assertThat(literal.getOutputVariables(), empty()),
64 () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)),
65 () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q, r)),
66 () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(r, s)),
67 () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), containsInAnyOrder(s))
68 );
69 }
70
71 @Test
72 void repeatedPositiveDirectionTest() {
73 var literal = fakeConstraint.call(p, p, q, q);
74 assertAll(
75 () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(q)),
76 () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)),
77 () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p)),
78 () -> assertThat(literal.getPrivateVariables(Set.of()), empty()),
79 () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty())
80 );
81 }
82
83 @Test
84 void repeatedNegativeDirectionTest() {
85 var literal = not(fakeConstraint.call(p, p, q, q));
86 assertAll(
87 () -> assertThat(literal.getOutputVariables(), empty()),
88 () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)),
89 () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p, q)),
90 () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(q)),
91 () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty())
92 );
93 }
94}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java
new file mode 100644
index 00000000..7b2ce8b2
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/DuplicateDnfRemoverTest.java
@@ -0,0 +1,162 @@
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 org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.logic.Constraint;
11import tools.refinery.logic.dnf.Query;
12import tools.refinery.logic.literal.AbstractCallLiteral;
13import tools.refinery.logic.literal.Reduction;
14import tools.refinery.logic.term.Variable;
15import tools.refinery.logic.tests.FakeKeyOnlyView;
16
17import java.util.List;
18
19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.is;
21import static org.hamcrest.Matchers.not;
22import static tools.refinery.logic.literal.Literals.not;
23
24class DuplicateDnfRemoverTest {
25 private final static Constraint friendView = new FakeKeyOnlyView("friend", 2);
26
27 private DuplicateDnfRemover sut;
28
29 @BeforeEach
30 void beforeEach() {
31 sut = new DuplicateDnfRemover();
32 }
33
34 @Test
35 void removeDuplicateSimpleTest() {
36 var one = Query.of("One", (builder, x, y) -> builder.clause(
37 friendView.call(x, y),
38 friendView.call(y, x)
39 ));
40 var two = Query.of("Two", (builder, x, y) -> builder.clause(
41 friendView.call(x, y),
42 friendView.call(y, x)
43 ));
44
45 var oneResult = sut.rewrite(one);
46 var twoResult = sut.rewrite(two);
47
48 assertThat(oneResult, is(twoResult));
49 assertThat(one, is(oneResult));
50 }
51
52 @Test
53 void notDuplicateSimpleTest() {
54 var one = Query.of("One", (builder, x, y) -> builder.clause(
55 friendView.call(x, y),
56 friendView.call(y, x)
57 ));
58 var two = Query.of("Two", (builder, x, y) -> builder.clause((z) -> List.of(
59 friendView.call(x, y),
60 friendView.call(y, z)
61 )));
62
63 var oneResult = sut.rewrite(one);
64 var twoResult = sut.rewrite(two);
65
66 assertThat(one, is(oneResult));
67 assertThat(two, is(twoResult));
68 }
69
70 @Test
71 void removeDuplicateRecursiveTest() {
72 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
73 friendView.call(x, y),
74 friendView.call(y, x)
75 ));
76 var one = Query.of("One", (builder, x) -> builder.clause(
77 oneSubQuery.call(x, Variable.of())
78 ));
79 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
80 friendView.call(x, y),
81 friendView.call(y, x)
82 ));
83 var two = Query.of("Two", (builder, x) -> builder.clause(
84 twoSubQuery.call(x, Variable.of())
85 ));
86
87 var oneResult = sut.rewrite(one);
88 var twoResult = sut.rewrite(two);
89
90 assertThat(oneResult, is(twoResult));
91 assertThat(one, is(oneResult));
92 }
93
94 @Test
95 void notDuplicateRecursiveTest() {
96 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
97 friendView.call(x, y),
98 friendView.call(y, x)
99 ));
100 var one = Query.of("One", (builder, x) -> builder.clause(
101 oneSubQuery.call(x, Variable.of())
102 ));
103 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
104 friendView.call(x, y),
105 friendView.call(y, x)
106 ));
107 var two = Query.of("Two", (builder, x) -> builder.clause(
108 twoSubQuery.call(Variable.of(), x)
109 ));
110
111 var oneResult = sut.rewrite(one);
112 var twoResult = sut.rewrite(two);
113
114 assertThat(one, is(oneResult));
115 assertThat(oneResult, is(not(twoResult)));
116
117 var oneCall = (AbstractCallLiteral) oneResult.getDnf().getClauses().getFirst().literals().getFirst();
118 var twoCall = (AbstractCallLiteral) twoResult.getDnf().getClauses().getFirst().literals().getFirst();
119
120 assertThat(oneCall.getTarget(), is(twoCall.getTarget()));
121 }
122
123 @Test
124 void removeContradictionTest() {
125 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
126 friendView.call(x, y),
127 friendView.call(y, x)
128 ));
129 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
130 friendView.call(x, y),
131 friendView.call(y, x)
132 ));
133 var query = Query.of("Contradiction", (builder, x, y) -> builder.clause(
134 oneSubQuery.call(x, y),
135 not(twoSubQuery.call(x, y))
136 ));
137
138 var result = sut.rewrite(query);
139
140 assertThat(result.getDnf().getReduction(), is(Reduction.ALWAYS_FALSE));
141 }
142
143 @Test
144 void removeQuantifiedContradictionTest() {
145 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
146 friendView.call(x, y),
147 friendView.call(y, x)
148 ));
149 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
150 friendView.call(x, y),
151 friendView.call(y, x)
152 ));
153 var query = Query.of("Contradiction", (builder, x) -> builder.clause(
154 oneSubQuery.call(x, Variable.of()),
155 not(twoSubQuery.call(x, Variable.of()))
156 ));
157
158 var result = sut.rewrite(query);
159
160 assertThat(result.getDnf().getReduction(), is(Reduction.ALWAYS_FALSE));
161 }
162}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java
new file mode 100644
index 00000000..5e5fdb64
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/rewriter/InputParameterResolverTest.java
@@ -0,0 +1,225 @@
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 org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.logic.Constraint;
11import tools.refinery.logic.dnf.Dnf;
12import tools.refinery.logic.dnf.Query;
13import tools.refinery.logic.term.ParameterDirection;
14import tools.refinery.logic.term.Variable;
15import tools.refinery.logic.tests.FakeKeyOnlyView;
16
17import java.util.List;
18
19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.is;
21import static tools.refinery.logic.literal.Literals.not;
22import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo;
23
24class InputParameterResolverTest {
25 private final static Constraint personView = new FakeKeyOnlyView("Person", 1);
26 private final static Constraint friendView = new FakeKeyOnlyView("friend", 2);
27
28 private InputParameterResolver sut;
29
30 @BeforeEach
31 void beforeEach() {
32 sut = new InputParameterResolver();
33 }
34
35 @Test
36 void inlineSingleClauseTest() {
37 var dnf = Dnf.of("SubQuery", builder -> {
38 var x = builder.parameter("x", ParameterDirection.OUT);
39 builder.clause(friendView.call(x, Variable.of()));
40 });
41 var query = Query.of("Actual", (builder, x) -> builder.clause(
42 dnf.call(x),
43 personView.call(x)
44 ));
45
46 var actual = sut.rewrite(query);
47
48 var expected = Query.of("Expected", (builder, x) -> builder.clause(
49 friendView.call(x, Variable.of()),
50 personView.call(x)
51 ));
52
53 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
54 }
55
56 @Test
57 void inlineSingleClauseWIthInputTest() {
58 var dnf = Dnf.of("SubQuery", builder -> {
59 var x = builder.parameter("x", ParameterDirection.IN);
60 builder.clause(not(friendView.call(x, Variable.of())));
61 });
62 var query = Query.of("Actual", (builder, x) -> builder.clause(
63 dnf.call(x),
64 personView.call(x)
65 ));
66
67 var actual = sut.rewrite(query);
68
69 var expected = Query.of("Expected", (builder, x) -> builder.clause(
70 personView.call(x),
71 not(friendView.call(x, Variable.of()))
72 ));
73
74 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
75 }
76
77 @Test
78 void singleLiteralDemandSetTest() {
79 var dnf = Dnf.of("SubQuery", builder -> {
80 var x = builder.parameter("x", ParameterDirection.IN);
81 builder.clause(not(friendView.call(x, Variable.of())));
82 builder.clause(not(friendView.call(Variable.of(), x)));
83 });
84 var query = Query.of("Actual", (builder, x) -> builder.clause(
85 dnf.call(x),
86 personView.call(x)
87 ));
88
89 var actual = sut.rewrite(query);
90
91 var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> {
92 var x = builder.parameter("x", ParameterDirection.OUT);
93 builder.clause(
94 personView.call(x),
95 not(friendView.call(x, Variable.of()))
96 );
97 builder.clause(
98 personView.call(x),
99 not(friendView.call(Variable.of(), x))
100 );
101 });
102 var expected = Query.of("Expected", (builder, x) -> builder.clause(
103 personView.call(x),
104 expectedSubQuery.call(x)
105 ));
106
107 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
108 }
109
110 @Test
111 void multipleLiteralDemandSetTest() {
112 var dnf = Dnf.of("SubQuery", builder -> {
113 var x = builder.parameter("x", ParameterDirection.IN);
114 var y = builder.parameter("y", ParameterDirection.IN);
115 builder.clause(not(friendView.call(x, y)));
116 builder.clause(not(friendView.call(y, x)));
117 });
118 var query = Query.of("Actual", (builder, p1) -> builder.clause(p2 -> List.of(
119 not(dnf.call(p1, p2)),
120 personView.call(p1),
121 personView.call(p2)
122 )));
123
124 var actual = sut.rewrite(query);
125
126 var context = Dnf.of("Context", builder -> {
127 var x = builder.parameter("x", ParameterDirection.OUT);
128 var y = builder.parameter("y", ParameterDirection.OUT);
129 builder.clause(
130 personView.call(x),
131 personView.call(y)
132 );
133 });
134 var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> {
135 var x = builder.parameter("x", ParameterDirection.OUT);
136 var y = builder.parameter("x", ParameterDirection.OUT);
137 builder.clause(
138 context.call(x, y),
139 not(friendView.call(x, y))
140 );
141 builder.clause(
142 context.call(x, y),
143 not(friendView.call(y, x))
144 );
145 });
146 var expected = Query.of("Expected", (builder, p1) -> builder.clause(p2 -> List.of(
147 context.call(p1, p2),
148 not(expectedSubQuery.call(p1, p2))
149 )));
150
151 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
152 }
153
154 @Test
155 void multipleParameterDemandSetTest() {
156 var dnf = Dnf.of("SubQuery", builder -> {
157 var x = builder.parameter("x", ParameterDirection.IN);
158 var y = builder.parameter("y", ParameterDirection.IN);
159 builder.clause(not(friendView.call(x, y)));
160 builder.clause(not(friendView.call(y, x)));
161 });
162 var query = Query.of("Actual", (builder, p1) -> builder.clause(
163 not(dnf.call(p1, p1)),
164 personView.call(p1)
165 ));
166
167 var actual = sut.rewrite(query);
168
169 var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> {
170 var x = builder.parameter("x", ParameterDirection.OUT);
171 var y = builder.parameter("y", ParameterDirection.OUT);
172 builder.clause(
173 y.isEquivalent(x),
174 personView.call(x),
175 not(friendView.call(x, x))
176 );
177 });
178 var expected = Query.of("Expected", (builder, p1) -> builder.clause(
179 personView.call(p1),
180 not(expectedSubQuery.call(p1, p1))
181 ));
182
183 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
184 }
185
186 @Test
187 void eliminateDoubleNegationTest() {
188 var dnf = Dnf.of("SubQuery", builder -> {
189 var x = builder.parameter("x", ParameterDirection.IN);
190 builder.clause(not(friendView.call(x, Variable.of())));
191 });
192 var query = Query.of("Actual", (builder, p1) -> builder.clause(
193 personView.call(p1),
194 not(dnf.call(p1))
195 ));
196
197 var actual = sut.rewrite(query);
198
199 var expected = Query.of("Actual", (builder, p1) -> builder.clause(
200 personView.call(p1),
201 friendView.call(p1, Variable.of())
202 ));
203
204 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
205 }
206
207 @Test
208 void identityWhenNoWorkToDoTest() {
209 var dnf = Dnf.of("SubQuery", builder -> {
210 var x = builder.parameter("x", ParameterDirection.OUT);
211 builder.clause(
212 personView.call(x),
213 not(friendView.call(x, Variable.of()))
214 );
215 });
216 var query = Query.of("Actual", (builder, p1) -> builder.clause(
217 personView.call(p1),
218 not(dnf.call(p1))
219 ));
220
221 var actual = sut.rewrite(query);
222
223 assertThat(actual, is(query));
224 }
225}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java
new file mode 100644
index 00000000..52b21692
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/TermSubstitutionTest.java
@@ -0,0 +1,97 @@
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.junit.jupiter.api.Assertions;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.logic.equality.DnfEqualityChecker;
13import tools.refinery.logic.equality.SubstitutingLiteralEqualityHelper;
14import tools.refinery.logic.substitution.Substitution;
15import tools.refinery.logic.term.bool.BoolTerms;
16import tools.refinery.logic.term.int_.IntTerms;
17import tools.refinery.logic.term.real.RealTerms;
18import tools.refinery.logic.term.uppercardinality.UpperCardinality;
19import tools.refinery.logic.term.uppercardinality.UpperCardinalityTerms;
20
21import java.util.List;
22import java.util.stream.Stream;
23
24class TermSubstitutionTest {
25 private final static DataVariable<Integer> intA = Variable.of("intA", Integer.class);
26 private final static DataVariable<Integer> intB = Variable.of("intB", Integer.class);
27 private final static DataVariable<Double> realA = Variable.of("realA", Double.class);
28 private final static DataVariable<Double> realB = Variable.of("realB", Double.class);
29 private final static DataVariable<Boolean> boolA = Variable.of("boolA", Boolean.class);
30 private final static DataVariable<Boolean> boolB = Variable.of("boolB", Boolean.class);
31 private final static DataVariable<UpperCardinality> upperCardinalityA = Variable.of("upperCardinalityA",
32 UpperCardinality.class);
33 private final static DataVariable<UpperCardinality> upperCardinalityB = Variable.of("upperCardinalityB",
34 UpperCardinality.class);
35 private final static Substitution substitution = Substitution.builder()
36 .put(intA, intB)
37 .put(intB, intA)
38 .put(realA, realB)
39 .put(realB, realA)
40 .put(boolA, boolB)
41 .put(boolB, boolA)
42 .put(upperCardinalityA, upperCardinalityB)
43 .put(upperCardinalityB, upperCardinalityA)
44 .build();
45
46 @ParameterizedTest
47 @MethodSource
48 void substitutionTest(AnyTerm term) {
49 var substitutedTerm1 = term.substitute(substitution);
50 Assertions.assertNotEquals(term, substitutedTerm1, "Original term is not equal to substituted term");
51 var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, List.of(), List.of());
52 Assertions.assertTrue(term.equalsWithSubstitution(helper, substitutedTerm1), "Terms are equal by helper");
53 // The {@link #substitution} is its own inverse.
54 var substitutedTerm2 = substitutedTerm1.substitute(substitution);
55 Assertions.assertEquals(term, substitutedTerm2, "Original term is not equal to back-substituted term");
56 }
57
58 static Stream<Arguments> substitutionTest() {
59 return Stream.of(
60 Arguments.of(IntTerms.plus(intA)),
61 Arguments.of(IntTerms.minus(intA)),
62 Arguments.of(IntTerms.add(intA, intB)),
63 Arguments.of(IntTerms.sub(intA, intB)),
64 Arguments.of(IntTerms.mul(intA, intB)),
65 Arguments.of(IntTerms.div(intA, intB)),
66 Arguments.of(IntTerms.pow(intA, intB)),
67 Arguments.of(IntTerms.min(intA, intB)),
68 Arguments.of(IntTerms.max(intA, intB)),
69 Arguments.of(IntTerms.eq(intA, intB)),
70 Arguments.of(IntTerms.notEq(intA, intB)),
71 Arguments.of(IntTerms.less(intA, intB)),
72 Arguments.of(IntTerms.lessEq(intA, intB)),
73 Arguments.of(IntTerms.greater(intA, intB)),
74 Arguments.of(IntTerms.greaterEq(intA, intB)),
75 Arguments.of(IntTerms.asInt(realA)),
76 Arguments.of(RealTerms.plus(realA)),
77 Arguments.of(RealTerms.minus(realA)),
78 Arguments.of(RealTerms.add(realA, realB)),
79 Arguments.of(RealTerms.sub(realA, realB)),
80 Arguments.of(RealTerms.mul(realA, realB)),
81 Arguments.of(RealTerms.div(realA, realB)),
82 Arguments.of(RealTerms.pow(realA, realB)),
83 Arguments.of(RealTerms.min(realA, realB)),
84 Arguments.of(RealTerms.max(realA, realB)),
85 Arguments.of(RealTerms.asReal(intA)),
86 Arguments.of(BoolTerms.not(boolA)),
87 Arguments.of(BoolTerms.and(boolA, boolB)),
88 Arguments.of(BoolTerms.or(boolA, boolB)),
89 Arguments.of(BoolTerms.xor(boolA, boolB)),
90 Arguments.of(RealTerms.eq(realA, realB)),
91 Arguments.of(UpperCardinalityTerms.add(upperCardinalityA, upperCardinalityB)),
92 Arguments.of(UpperCardinalityTerms.mul(upperCardinalityA, upperCardinalityB)),
93 Arguments.of(UpperCardinalityTerms.min(upperCardinalityA, upperCardinalityB)),
94 Arguments.of(UpperCardinalityTerms.max(upperCardinalityA, upperCardinalityB))
95 );
96 }
97}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.java
new file mode 100644
index 00000000..7f65591f
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/bool/BoolTermsEvaluateTest.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.bool;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.CsvSource;
10import tools.refinery.logic.term.bool.BoolTerms;
11import tools.refinery.logic.valuation.Valuation;
12
13import static org.hamcrest.MatcherAssert.assertThat;
14import static org.hamcrest.Matchers.is;
15
16class BoolTermsEvaluateTest {
17 @ParameterizedTest(name = "!{0} == {1}")
18 @CsvSource(value = {
19 "false, true",
20 "true, false",
21 "null, null"
22 }, nullValues = "null")
23 void notTest(Boolean a, Boolean result) {
24 var term = BoolTerms.not(BoolTerms.constant(a));
25 assertThat(term.getType(), is(Boolean.class));
26 assertThat(term.evaluate(Valuation.empty()), is(result));
27 }
28
29 @ParameterizedTest(name = "{0} && {1} == {2}")
30 @CsvSource(value = {
31 "false, false, false",
32 "false, true, false",
33 "true, false, false",
34 "true, true, true",
35 "false, null, null",
36 "null, false, null",
37 "null, null, null"
38 }, nullValues = "null")
39 void andTest(Boolean a, Boolean b, Boolean result) {
40 var term = BoolTerms.and(BoolTerms.constant(a), BoolTerms.constant(b));
41 assertThat(term.getType(), is(Boolean.class));
42 assertThat(term.evaluate(Valuation.empty()), is(result));
43 }
44
45 @ParameterizedTest(name = "{0} || {1} == {2}")
46 @CsvSource(value = {
47 "false, false, false",
48 "false, true, true",
49 "true, false, true",
50 "true, true, true",
51 "true, null, null",
52 "null, true, null",
53 "null, null, null"
54 }, nullValues = "null")
55 void orTest(Boolean a, Boolean b, Boolean result) {
56 var term = BoolTerms.or(BoolTerms.constant(a), BoolTerms.constant(b));
57 assertThat(term.getType(), is(Boolean.class));
58 assertThat(term.evaluate(Valuation.empty()), is(result));
59 }
60
61 @ParameterizedTest(name = "{0} ^^ {1} == {2}")
62 @CsvSource(value = {
63 "false, false, false",
64 "false, true, true",
65 "true, false, true",
66 "true, true, false",
67 "false, null, null",
68 "null, false, null",
69 "null, null, null"
70 }, nullValues = "null")
71 void xorTest(Boolean a, Boolean b, Boolean result) {
72 var term = BoolTerms.xor(BoolTerms.constant(a), BoolTerms.constant(b));
73 assertThat(term.getType(), is(Boolean.class));
74 assertThat(term.evaluate(Valuation.empty()), is(result));
75 }
76}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java
new file mode 100644
index 00000000..ee2dd61c
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalTest.java
@@ -0,0 +1,127 @@
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 org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11
12import java.util.stream.Stream;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.equalTo;
16import static tools.refinery.logic.term.cardinalityinterval.CardinalityIntervals.*;
17
18class CardinalityIntervalTest {
19 @ParameterizedTest(name = "min({0}, {1}) == {2}")
20 @MethodSource
21 void minTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
22 assertThat(a.min(b), equalTo(expected));
23 }
24
25 static Stream<Arguments> minTest() {
26 return Stream.of(
27 Arguments.of(atMost(1), atMost(1), atMost(1)),
28 Arguments.of(atMost(1), between(2, 3), atMost(1)),
29 Arguments.of(atMost(1), atLeast(2), atMost(1)),
30 Arguments.of(atMost(1), ERROR, ERROR),
31 Arguments.of(atLeast(1), atLeast(2), atLeast(1)),
32 Arguments.of(atLeast(1), ERROR, ERROR),
33 Arguments.of(ERROR, atLeast(2), ERROR),
34 Arguments.of(ERROR, ERROR, ERROR)
35 );
36 }
37
38 @ParameterizedTest(name = "max({0}, {1}) == {2}")
39 @MethodSource
40 void maxTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
41 assertThat(a.max(b), equalTo(expected));
42 }
43
44 static Stream<Arguments> maxTest() {
45 return Stream.of(
46 Arguments.of(atMost(1), atMost(1), atMost(1)),
47 Arguments.of(atMost(1), between(2, 3), between(2, 3)),
48 Arguments.of(atMost(1), atLeast(2), atLeast(2)),
49 Arguments.of(atMost(1), ERROR, ERROR),
50 Arguments.of(atLeast(1), atLeast(2), atLeast(2)),
51 Arguments.of(atLeast(1), ERROR, ERROR),
52 Arguments.of(ERROR, atLeast(2), ERROR),
53 Arguments.of(ERROR, ERROR, ERROR)
54 );
55 }
56
57 @ParameterizedTest(name = "{0} + {1} == {2}")
58 @MethodSource
59 void addTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
60 assertThat(a.add(b), equalTo(expected));
61 }
62
63 static Stream<Arguments> addTest() {
64 return Stream.of(
65 Arguments.of(atMost(1), atMost(1), atMost(2)),
66 Arguments.of(atMost(1), between(2, 3), between(2, 4)),
67 Arguments.of(atMost(1), atLeast(2), atLeast(2)),
68 Arguments.of(atMost(1), ERROR, ERROR),
69 Arguments.of(atLeast(1), atLeast(2), atLeast(3)),
70 Arguments.of(atLeast(1), ERROR, ERROR),
71 Arguments.of(ERROR, atLeast(2), ERROR),
72 Arguments.of(ERROR, ERROR, ERROR)
73 );
74 }
75
76 @ParameterizedTest(name = "{0} * {1} == {2}")
77 @MethodSource
78 void multiplyTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
79 assertThat(a.multiply(b), equalTo(expected));
80 }
81
82 static Stream<Arguments> multiplyTest() {
83 return Stream.of(
84 Arguments.of(between(2, 3), between(4, 5), between(8, 15)),
85 Arguments.of(atLeast(2), between(4, 5), atLeast(8)),
86 Arguments.of(between(2, 3), atLeast(4), atLeast(8)),
87 Arguments.of(between(2, 3), ERROR, ERROR),
88 Arguments.of(ERROR, between(4, 5), ERROR),
89 Arguments.of(ERROR, ERROR, ERROR)
90 );
91 }
92
93 @ParameterizedTest(name = "{0} /\\ {1} == {2}")
94 @MethodSource
95 void meetTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
96 assertThat(a.meet(b), equalTo(expected));
97 }
98
99 static Stream<Arguments> meetTest() {
100 return Stream.of(
101 Arguments.of(atMost(1), atMost(2), atMost(1)),
102 Arguments.of(atMost(2), between(1, 3), between(1, 2)),
103 Arguments.of(atMost(1), between(1, 3), exactly(1)),
104 Arguments.of(atMost(1), between(2, 3), ERROR),
105 Arguments.of(atMost(1), ERROR, ERROR),
106 Arguments.of(ERROR, atMost(1), ERROR),
107 Arguments.of(ERROR, ERROR, ERROR)
108 );
109 }
110
111 @ParameterizedTest(name = "{0} \\/ {1} == {2}")
112 @MethodSource
113 void joinTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
114 assertThat(a.join(b), equalTo(expected));
115 }
116
117 static Stream<Arguments> joinTest() {
118 return Stream.of(
119 Arguments.of(atMost(1), atMost(2), atMost(2)),
120 Arguments.of(atMost(2), between(1, 3), atMost(3)),
121 Arguments.of(atMost(1), between(2, 3), atMost(3)),
122 Arguments.of(atMost(1), ERROR, atMost(1)),
123 Arguments.of(ERROR, atMost(1), atMost(1)),
124 Arguments.of(ERROR, ERROR, ERROR)
125 );
126 }
127}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.java
new file mode 100644
index 00000000..5441c837
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervalsTest.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.term.cardinalityinterval;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.logic.term.uppercardinality.UpperCardinalities;
10
11import static org.hamcrest.MatcherAssert.assertThat;
12import static org.hamcrest.Matchers.equalTo;
13
14class CardinalityIntervalsTest {
15 @Test
16 void betweenEmptyTest() {
17 var interval = CardinalityIntervals.between(2, 1);
18 assertThat(interval.isEmpty(), equalTo(true));
19 }
20
21 @Test
22 void betweenNegativeUpperBoundTest() {
23 var interval = CardinalityIntervals.between(0, -1);
24 assertThat(interval.upperBound(), equalTo(UpperCardinalities.UNBOUNDED));
25 assertThat(interval.isEmpty(), equalTo(false));
26 }
27}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.java
new file mode 100644
index 00000000..0dbc7f61
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityIntervalTest.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.cardinalityinterval;
7
8import org.junit.jupiter.api.Test;
9
10import static org.hamcrest.MatcherAssert.assertThat;
11import static org.hamcrest.Matchers.lessThan;
12
13class EmptyCardinalityIntervalTest {
14 @Test
15 void inconsistentBoundsTest() {
16 assertThat(CardinalityIntervals.ERROR.upperBound().compareToInt(CardinalityIntervals.ERROR.lowerBound()),
17 lessThan(0));
18 }
19}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.java
new file mode 100644
index 00000000..588b25ab
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/cardinalityinterval/FiniteCardinalityIntervalTest.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.term.cardinalityinterval;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.logic.term.uppercardinality.UpperCardinality;
10import tools.refinery.logic.term.uppercardinality.UpperCardinalities;
11
12import static org.junit.jupiter.api.Assertions.assertThrows;
13
14class FiniteCardinalityIntervalTest {
15 @Test
16 void invalidLowerBoundConstructorTest() {
17 assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(-1,
18 UpperCardinalities.UNBOUNDED));
19 }
20
21 @Test
22 void invalidUpperBoundConstructorTest() {
23 var upperCardinality = UpperCardinality.of(1);
24 assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(2,
25 upperCardinality));
26 }
27}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java
new file mode 100644
index 00000000..55d9b740
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/int_/IntTermsEvaluateTest.java
@@ -0,0 +1,260 @@
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 org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.CsvSource;
11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.logic.term.int_.IntTerms;
13import tools.refinery.logic.term.real.RealTerms;
14import tools.refinery.logic.valuation.Valuation;
15
16import java.util.stream.Stream;
17
18import static org.hamcrest.Matchers.is;
19import static org.hamcrest.MatcherAssert.assertThat;
20
21class IntTermsEvaluateTest {
22 @ParameterizedTest(name = "+{0} == {1}")
23 @CsvSource(value = {
24 "2, 2",
25 "null, null"
26 }, nullValues = "null")
27 void plusTest(Integer a, Integer result) {
28 var term = IntTerms.plus(IntTerms.constant(a));
29 assertThat(term.getType(), is(Integer.class));
30 assertThat(term.evaluate(Valuation.empty()), is(result));
31 }
32
33 @ParameterizedTest(name = "-{0} == {1}")
34 @CsvSource(value = {
35 "2, -2",
36 "null, null"
37 }, nullValues = "null")
38 void minusTest(Integer a, Integer result) {
39 var term = IntTerms.minus(IntTerms.constant(a));
40 assertThat(term.getType(), is(Integer.class));
41 assertThat(term.evaluate(Valuation.empty()), is(result));
42 }
43
44 @ParameterizedTest(name = "{0} + {1} == {2}")
45 @CsvSource(value = {
46 "1, 2, 3",
47 "null, 2, null",
48 "1, null, null",
49 "null, null, null"
50 }, nullValues = "null")
51 void addTest(Integer a, Integer b, Integer result) {
52 var term = IntTerms.add(IntTerms.constant(a), IntTerms.constant(b));
53 assertThat(term.getType(), is(Integer.class));
54 assertThat(term.evaluate(Valuation.empty()), is(result));
55 }
56
57 @ParameterizedTest(name = "{0} - {1} == {2}")
58 @CsvSource(value = {
59 "1, 3, -2",
60 "null, 3, null",
61 "1, null, null",
62 "null, null, null"
63 }, nullValues = "null")
64 void subTest(Integer a, Integer b, Integer result) {
65 var term = IntTerms.sub(IntTerms.constant(a), IntTerms.constant(b));
66 assertThat(term.getType(), is(Integer.class));
67 assertThat(term.evaluate(Valuation.empty()), is(result));
68 }
69
70 @ParameterizedTest(name = "{0} * {1} == {2}")
71 @CsvSource(value = {
72 "2, 3, 6",
73 "null, 3, null",
74 "2, null, null",
75 "null, null, null"
76 }, nullValues = "null")
77 void mulTest(Integer a, Integer b, Integer result) {
78 var term = IntTerms.mul(IntTerms.constant(a), IntTerms.constant(b));
79 assertThat(term.getType(), is(Integer.class));
80 assertThat(term.evaluate(Valuation.empty()), is(result));
81 }
82
83 @ParameterizedTest(name = "{0} * {1} == {2}")
84 @CsvSource(value = {
85 "6, 3, 2",
86 "7, 3, 2",
87 "6, 0, null",
88 "null, 3, null",
89 "6, null, null",
90 "null, null, null"
91 }, nullValues = "null")
92 void divTest(Integer a, Integer b, Integer result) {
93 var term = IntTerms.div(IntTerms.constant(a), IntTerms.constant(b));
94 assertThat(term.getType(), is(Integer.class));
95 assertThat(term.evaluate(Valuation.empty()), is(result));
96 }
97
98 @ParameterizedTest(name = "{0} ** {1} == {2}")
99 @CsvSource(value = {
100 "1, 0, 1",
101 "1, 3, 1",
102 "1, -3, null",
103 "2, 0, 1",
104 "2, 2, 4",
105 "2, 3, 8",
106 "2, 4, 16",
107 "2, 5, 32",
108 "2, 6, 64",
109 "2, -3, null",
110 "null, 3, null",
111 "2, null, null",
112 "null, null, null"
113 }, nullValues = "null")
114 void powTest(Integer a, Integer b, Integer result) {
115 var term = IntTerms.pow(IntTerms.constant(a), IntTerms.constant(b));
116 assertThat(term.getType(), is(Integer.class));
117 assertThat(term.evaluate(Valuation.empty()), is(result));
118 }
119
120 @ParameterizedTest(name = "min({0}, {1}) == {2}")
121 @CsvSource(value = {
122 "1, 2, 1",
123 "2, 1, 1",
124 "null, 2, null",
125 "1, null, null",
126 "null, null, null"
127 }, nullValues = "null")
128 void minTest(Integer a, Integer b, Integer result) {
129 var term = IntTerms.min(IntTerms.constant(a), IntTerms.constant(b));
130 assertThat(term.getType(), is(Integer.class));
131 assertThat(term.evaluate(Valuation.empty()), is(result));
132 }
133
134 @ParameterizedTest(name = "max({0}, {1}) == {2}")
135 @CsvSource(value = {
136 "1, 2, 2",
137 "2, 1, 2",
138 "null, 2, null",
139 "1, null, null",
140 "null, null, null"
141 }, nullValues = "null")
142 void maxTest(Integer a, Integer b, Integer result) {
143 var term = IntTerms.max(IntTerms.constant(a), IntTerms.constant(b));
144 assertThat(term.getType(), is(Integer.class));
145 assertThat(term.evaluate(Valuation.empty()), is(result));
146 }
147
148 @ParameterizedTest(name = "({0} == {1}) == {2}")
149 @CsvSource(value = {
150 "1, 1, true",
151 "1, 2, false",
152 "null, 1, null",
153 "1, null, null",
154 "null, null, null"
155 }, nullValues = "null")
156 void eqTest(Integer a, Integer b, Boolean result) {
157 var term = IntTerms.eq(IntTerms.constant(a), IntTerms.constant(b));
158 assertThat(term.getType(), is(Boolean.class));
159 assertThat(term.evaluate(Valuation.empty()), is(result));
160 }
161
162 @ParameterizedTest(name = "({0} != {1}) == {2}")
163 @CsvSource(value = {
164 "1, 1, false",
165 "1, 2, true",
166 "null, 1, null",
167 "1, null, null",
168 "null, null, null"
169 }, nullValues = "null")
170 void notEqTest(Integer a, Integer b, Boolean result) {
171 var term = IntTerms.notEq(IntTerms.constant(a), IntTerms.constant(b));
172 assertThat(term.getType(), is(Boolean.class));
173 assertThat(term.evaluate(Valuation.empty()), is(result));
174 }
175
176 @ParameterizedTest(name = "({0} < {1}) == {2}")
177 @CsvSource(value = {
178 "1, -2, false",
179 "1, 1, false",
180 "1, 2, true",
181 "null, 1, null",
182 "1, null, null",
183 "null, null, null"
184 }, nullValues = "null")
185 void lessTest(Integer a, Integer b, Boolean result) {
186 var term = IntTerms.less(IntTerms.constant(a), IntTerms.constant(b));
187 assertThat(term.getType(), is(Boolean.class));
188 assertThat(term.evaluate(Valuation.empty()), is(result));
189 }
190
191 @ParameterizedTest(name = "({0} <= {1}) == {2}")
192 @CsvSource(value = {
193 "1, -2, false",
194 "1, 1, true",
195 "1, 2, true",
196 "null, 1, null",
197 "1, null, null",
198 "null, null, null"
199 }, nullValues = "null")
200 void lessEqTest(Integer a, Integer b, Boolean result) {
201 var term = IntTerms.lessEq(IntTerms.constant(a), IntTerms.constant(b));
202 assertThat(term.getType(), is(Boolean.class));
203 assertThat(term.evaluate(Valuation.empty()), is(result));
204 }
205
206 @ParameterizedTest(name = "({0} > {1}) == {2}")
207 @CsvSource(value = {
208 "1, -2, true",
209 "1, 1, false",
210 "1, 2, false",
211 "null, 1, null",
212 "1, null, null",
213 "null, null, null"
214 }, nullValues = "null")
215 void greaterTest(Integer a, Integer b, Boolean result) {
216 var term = IntTerms.greater(IntTerms.constant(a), IntTerms.constant(b));
217 assertThat(term.getType(), is(Boolean.class));
218 assertThat(term.evaluate(Valuation.empty()), is(result));
219 }
220
221 @ParameterizedTest(name = "({0} >= {1}) == {2}")
222 @CsvSource(value = {
223 "1, -2, true",
224 "1, 1, true",
225 "1, 2, false",
226 "null, 1, null",
227 "1, null, null",
228 "null, null, null"
229 }, nullValues = "null")
230 void greaterEqTest(Integer a, Integer b, Boolean result) {
231 var term = IntTerms.greaterEq(IntTerms.constant(a), IntTerms.constant(b));
232 assertThat(term.getType(), is(Boolean.class));
233 assertThat(term.evaluate(Valuation.empty()), is(result));
234 }
235
236 @ParameterizedTest(name = "{0} as int == {1}")
237 @MethodSource
238 void asIntTest(Double a, Integer result) {
239 var term = IntTerms.asInt(RealTerms.constant(a));
240 assertThat(term.getType(), is(Integer.class));
241 assertThat(term.evaluate(Valuation.empty()), is(result));
242 }
243
244 static Stream<Arguments> asIntTest() {
245 return Stream.of(
246 Arguments.of(2.0, 2),
247 Arguments.of(2.1, 2),
248 Arguments.of(2.9, 2),
249 Arguments.of(-2.0, -2),
250 Arguments.of(-2.1, -2),
251 Arguments.of(-2.9, -2),
252 Arguments.of(0.0, 0),
253 Arguments.of(-0.0, 0),
254 Arguments.of(Double.POSITIVE_INFINITY, Integer.MAX_VALUE),
255 Arguments.of(Double.NEGATIVE_INFINITY, Integer.MIN_VALUE),
256 Arguments.of(Double.NaN, null),
257 Arguments.of(null, null)
258 );
259 }
260}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java
new file mode 100644
index 00000000..042d1807
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/real/RealTermEvaluateTest.java
@@ -0,0 +1,239 @@
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 org.hamcrest.Matcher;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.CsvSource;
11import tools.refinery.logic.term.int_.IntTerms;
12import tools.refinery.logic.term.real.RealTerms;
13import tools.refinery.logic.valuation.Valuation;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.*;
17
18class RealTermEvaluateTest {
19 public static final double TOLERANCE = 1e-6;
20
21 private static Matcher<Double> closeToOrNull(Double expected) {
22 return expected == null ? nullValue(Double.class) : closeTo(expected, TOLERANCE);
23 }
24
25 @ParameterizedTest(name = "+{0} == {1}")
26 @CsvSource(value = {
27 "2.5, 2.5",
28 "null, null"
29 }, nullValues = "null")
30 void plusTest(Double a, Double result) {
31 var term = RealTerms.plus(RealTerms.constant(a));
32 assertThat(term.getType(), is(Double.class));
33 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
34 }
35
36 @ParameterizedTest(name = "-{0} == {1}")
37 @CsvSource(value = {
38 "2.5, -2.5",
39 "null, null"
40 }, nullValues = "null")
41 void minusTest(Double a, Double result) {
42 var term = RealTerms.minus(RealTerms.constant(a));
43 assertThat(term.getType(), is(Double.class));
44 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
45 }
46
47 @ParameterizedTest(name = "{0} + {1} == {2}")
48 @CsvSource(value = {
49 "1.2, 2.3, 3.5",
50 "null, 2.3, null",
51 "1.2, null, null",
52 "null, null, null"
53 }, nullValues = "null")
54 void addTest(Double a, Double b, Double result) {
55 var term = RealTerms.add(RealTerms.constant(a), RealTerms.constant(b));
56 assertThat(term.getType(), is(Double.class));
57 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
58 }
59
60 @ParameterizedTest(name = "{0} - {1} == {2}")
61 @CsvSource(value = {
62 "1.2, 3.4, -2.2",
63 "null, 3.4, null",
64 "1.2, null, null",
65 "null, null, null"
66 }, nullValues = "null")
67 void subTest(Double a, Double b, Double result) {
68 var term = RealTerms.sub(RealTerms.constant(a), RealTerms.constant(b));
69 assertThat(term.getType(), is(Double.class));
70 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
71 }
72
73 @ParameterizedTest(name = "{0} * {1} == {2}")
74 @CsvSource(value = {
75 "2.3, 3.4, 7.82",
76 "null, 3.4, null",
77 "2.3, null, null",
78 "null, null, null"
79 }, nullValues = "null")
80 void mulTest(Double a, Double b, Double result) {
81 var term = RealTerms.mul(RealTerms.constant(a), RealTerms.constant(b));
82 assertThat(term.getType(), is(Double.class));
83 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
84 }
85
86 @ParameterizedTest(name = "{0} * {1} == {2}")
87 @CsvSource(value = {
88 "7.82, 3.4, 2.3",
89 "null, 3.4, null",
90 "7.82, null, null",
91 "null, null, null"
92 }, nullValues = "null")
93 void divTest(Double a, Double b, Double result) {
94 var term = RealTerms.div(RealTerms.constant(a), RealTerms.constant(b));
95 assertThat(term.getType(), is(Double.class));
96 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
97 }
98
99 @ParameterizedTest(name = "{0} ** {1} == {2}")
100 @CsvSource(value = {
101 "2.0, 6.0, 64.0",
102 "null, 6.0, null",
103 "2.0, null, null",
104 "null, null, null"
105 }, nullValues = "null")
106 void powTest(Double a, Double b, Double result) {
107 var term = RealTerms.pow(RealTerms.constant(a), RealTerms.constant(b));
108 assertThat(term.getType(), is(Double.class));
109 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
110 }
111
112 @ParameterizedTest(name = "min({0}, {1}) == {2}")
113 @CsvSource(value = {
114 "1.5, 2.7, 1.5",
115 "2.7, 1.5, 1.5",
116 "null, 2.7, null",
117 "1.5, null, null",
118 "null, null, null"
119 }, nullValues = "null")
120 void minTest(Double a, Double b, Double result) {
121 var term = RealTerms.min(RealTerms.constant(a), RealTerms.constant(b));
122 assertThat(term.getType(), is(Double.class));
123 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
124 }
125
126 @ParameterizedTest(name = "max({0}, {1}) == {2}")
127 @CsvSource(value = {
128 "1.5, 2.7, 2.7",
129 "2.7, 1.7, 2.7",
130 "null, 2.7, null",
131 "1.5, null, null",
132 "null, null, null"
133 }, nullValues = "null")
134 void maxTest(Double a, Double b, Double result) {
135 var term = RealTerms.max(RealTerms.constant(a), RealTerms.constant(b));
136 assertThat(term.getType(), is(Double.class));
137 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
138 }
139
140 @ParameterizedTest(name = "({0} == {1}) == {2}")
141 @CsvSource(value = {
142 "1.5, 1.5, true",
143 "1.5, 2.7, false",
144 "null, 1.5, null",
145 "1.5, null, null",
146 "null, null, null"
147 }, nullValues = "null")
148 void eqTest(Double a, Double b, Boolean result) {
149 var term = RealTerms.eq(RealTerms.constant(a), RealTerms.constant(b));
150 assertThat(term.getType(), is(Boolean.class));
151 assertThat(term.evaluate(Valuation.empty()), is(result));
152 }
153
154 @ParameterizedTest(name = "({0} != {1}) == {2}")
155 @CsvSource(value = {
156 "1.5, 1.5, false",
157 "1.5, 2.7, true",
158 "null, 1.5, null",
159 "1.5, null, null",
160 "null, null, null"
161 }, nullValues = "null")
162 void notEqTest(Double a, Double b, Boolean result) {
163 var term = RealTerms.notEq(RealTerms.constant(a), RealTerms.constant(b));
164 assertThat(term.getType(), is(Boolean.class));
165 assertThat(term.evaluate(Valuation.empty()), is(result));
166 }
167
168 @ParameterizedTest(name = "({0} < {1}) == {2}")
169 @CsvSource(value = {
170 "1.5, -2.7, false",
171 "1.5, 1.5, false",
172 "1.5, 2.7, true",
173 "null, 1.5, null",
174 "1.5, null, null",
175 "null, null, null"
176 }, nullValues = "null")
177 void lessTest(Double a, Double b, Boolean result) {
178 var term = RealTerms.less(RealTerms.constant(a), RealTerms.constant(b));
179 assertThat(term.getType(), is(Boolean.class));
180 assertThat(term.evaluate(Valuation.empty()), is(result));
181 }
182
183 @ParameterizedTest(name = "({0} <= {1}) == {2}")
184 @CsvSource(value = {
185 "1.5, -2.7, false",
186 "1.5, 1.5, true",
187 "1.5, 2.7, true",
188 "null, 1.5, null",
189 "1.5, null, null",
190 "null, null, null"
191 }, nullValues = "null")
192 void lessEqTest(Double a, Double b, Boolean result) {
193 var term = RealTerms.lessEq(RealTerms.constant(a), RealTerms.constant(b));
194 assertThat(term.getType(), is(Boolean.class));
195 assertThat(term.evaluate(Valuation.empty()), is(result));
196 }
197
198 @ParameterizedTest(name = "({0} > {1}) == {2}")
199 @CsvSource(value = {
200 "1.5, -2.7, true",
201 "1.5, 1.5, false",
202 "1.5, 2.7, false",
203 "null, 1.5, null",
204 "1.5, null, null",
205 "null, null, null"
206 }, nullValues = "null")
207 void greaterTest(Double a, Double b, Boolean result) {
208 var term = RealTerms.greater(RealTerms.constant(a), RealTerms.constant(b));
209 assertThat(term.getType(), is(Boolean.class));
210 assertThat(term.evaluate(Valuation.empty()), is(result));
211 }
212
213 @ParameterizedTest(name = "({0} >= {1}) == {2}")
214 @CsvSource(value = {
215 "1.5, -2.7, true",
216 "1.5, 1.5, true",
217 "1.5, 2.7, false",
218 "null, 1.5, null",
219 "1.5, null, null",
220 "null, null, null"
221 }, nullValues = "null")
222 void greaterEqTest(Double a, Double b, Boolean result) {
223 var term = RealTerms.greaterEq(RealTerms.constant(a), RealTerms.constant(b));
224 assertThat(term.getType(), is(Boolean.class));
225 assertThat(term.evaluate(Valuation.empty()), is(result));
226 }
227
228 @ParameterizedTest(name = "{0} as real == {1}")
229 @CsvSource(value = {
230 "0, 0.0",
231 "5, 5.0",
232 "null, null"
233 }, nullValues = "null")
234 void asRealTest(Integer a, Double result) {
235 var term = RealTerms.asReal(IntTerms.constant(a));
236 assertThat(term.getType(), is(Double.class));
237 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
238 }
239}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.java
new file mode 100644
index 00000000..8a57f029
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinalityTest.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.term.uppercardinality;
7
8import org.junit.jupiter.api.Test;
9
10import static org.junit.jupiter.api.Assertions.assertThrows;
11
12class FiniteUpperCardinalityTest {
13 @Test
14 void invalidConstructorTest() {
15 assertThrows(IllegalArgumentException.class, () -> new FiniteUpperCardinality(-1));
16 }
17}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.java
new file mode 100644
index 00000000..bdb6a833
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitiesTest.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 org.junit.jupiter.api.Test;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.ValueSource;
11
12import static org.hamcrest.MatcherAssert.assertThat;
13import static org.hamcrest.Matchers.equalTo;
14import static org.hamcrest.Matchers.instanceOf;
15
16class UpperCardinalitiesTest {
17 @ParameterizedTest
18 @ValueSource(ints = {0, 1, 255, 256, 1000, Integer.MAX_VALUE})
19 void valueOfBoundedTest(int value) {
20 var upperCardinality = UpperCardinalities.atMost(value);
21 assertThat(upperCardinality, instanceOf(FiniteUpperCardinality.class));
22 assertThat(((FiniteUpperCardinality) upperCardinality).finiteUpperBound(), equalTo(value));
23 }
24
25 @Test
26 void valueOfUnboundedTest() {
27 var upperCardinality = UpperCardinalities.atMost(-1);
28 assertThat(upperCardinality, instanceOf(UnboundedUpperCardinality.class));
29 }
30}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java
new file mode 100644
index 00000000..fc8522d4
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.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.uppercardinality;
7
8import org.hamcrest.Matchers;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.MethodSource;
12
13import java.util.List;
14import java.util.stream.Stream;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17
18class UpperCardinalitySumAggregatorStreamTest {
19 @ParameterizedTest
20 @MethodSource
21 void testStream(List<UpperCardinality> list, UpperCardinality expected) {
22 var result = UpperCardinalitySumAggregator.INSTANCE.aggregateStream(list.stream());
23 assertThat(result, Matchers.is(expected));
24 }
25
26 static Stream<Arguments> testStream() {
27 return Stream.of(
28 Arguments.of(List.of(), UpperCardinalities.ZERO),
29 Arguments.of(List.of(UpperCardinality.of(3)), UpperCardinality.of(3)),
30 Arguments.of(
31 List.of(
32 UpperCardinality.of(2),
33 UpperCardinality.of(3)
34 ),
35 UpperCardinality.of(5)
36 ),
37 Arguments.of(List.of(UpperCardinalities.UNBOUNDED), UpperCardinalities.UNBOUNDED),
38 Arguments.of(
39 List.of(
40 UpperCardinalities.UNBOUNDED,
41 UpperCardinalities.UNBOUNDED
42 ),
43 UpperCardinalities.UNBOUNDED
44 ),
45 Arguments.of(
46 List.of(
47 UpperCardinalities.UNBOUNDED,
48 UpperCardinality.of(3)
49 ),
50 UpperCardinalities.UNBOUNDED
51 )
52 );
53 }
54}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.java
new file mode 100644
index 00000000..e252b097
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregatorTest.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.term.uppercardinality;
7
8import org.hamcrest.MatcherAssert;
9import org.hamcrest.Matchers;
10import org.junit.jupiter.api.BeforeEach;
11import org.junit.jupiter.api.Test;
12import tools.refinery.logic.term.StatefulAggregate;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15
16class UpperCardinalitySumAggregatorTest {
17 private StatefulAggregate<UpperCardinality, UpperCardinality> accumulator;
18
19 @BeforeEach
20 void beforeEach() {
21 accumulator = UpperCardinalitySumAggregator.INSTANCE.createEmptyAggregate();
22 }
23
24 @Test
25 void emptyAggregationTest() {
26 MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(0)));
27 }
28
29 @Test
30 void singleBoundedTest() {
31 accumulator.add(UpperCardinality.of(3));
32 MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(3)));
33 }
34
35 @Test
36 void multipleBoundedTest() {
37 accumulator.add(UpperCardinality.of(2));
38 accumulator.add(UpperCardinality.of(3));
39 MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(5)));
40 }
41
42 @Test
43 void singleUnboundedTest() {
44 accumulator.add(UpperCardinalities.UNBOUNDED);
45 assertThat(accumulator.getResult(), Matchers.is(UpperCardinalities.UNBOUNDED));
46 }
47
48 @Test
49 void multipleUnboundedTest() {
50 accumulator.add(UpperCardinalities.UNBOUNDED);
51 accumulator.add(UpperCardinalities.UNBOUNDED);
52 assertThat(accumulator.getResult(), Matchers.is(UpperCardinalities.UNBOUNDED));
53 }
54
55 @Test
56 void removeBoundedTest() {
57 accumulator.add(UpperCardinality.of(2));
58 accumulator.add(UpperCardinality.of(3));
59 accumulator.remove(UpperCardinality.of(2));
60 MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(3)));
61 }
62
63 @Test
64 void removeAllUnboundedTest() {
65 accumulator.add(UpperCardinalities.UNBOUNDED);
66 accumulator.add(UpperCardinality.of(3));
67 accumulator.remove(UpperCardinalities.UNBOUNDED);
68 MatcherAssert.assertThat(accumulator.getResult(), Matchers.is(UpperCardinality.of(3)));
69 }
70
71 @Test
72 void removeSomeUnboundedTest() {
73 accumulator.add(UpperCardinalities.UNBOUNDED);
74 accumulator.add(UpperCardinalities.UNBOUNDED);
75 accumulator.add(UpperCardinality.of(3));
76 accumulator.remove(UpperCardinalities.UNBOUNDED);
77 assertThat(accumulator.getResult(), Matchers.is(UpperCardinalities.UNBOUNDED));
78 }
79}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java
new file mode 100644
index 00000000..ab71b716
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTermsEvaluateTest.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.uppercardinality;
7
8import org.hamcrest.Matchers;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.logic.valuation.Valuation;
13
14import java.util.stream.Stream;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.is;
18
19class UpperCardinalityTermsEvaluateTest {
20 @ParameterizedTest(name = "min({0}, {1}) == {2}")
21 @MethodSource
22 void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
23 var term = UpperCardinalityTerms.min(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
24 assertThat(term.getType(), is(UpperCardinality.class));
25 assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected));
26 }
27
28 static Stream<Arguments> minTest() {
29 return Stream.of(
30 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
31 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)),
32 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)),
33 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)),
34 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)),
35 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
36 Arguments.of(UpperCardinality.of(1), null, null),
37 Arguments.of(null, UpperCardinality.of(1), null),
38 Arguments.of(null, null, null)
39 );
40 }
41
42 @ParameterizedTest(name = "max({0}, {1}) == {2}")
43 @MethodSource
44 void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
45 var term = UpperCardinalityTerms.max(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
46 assertThat(term.getType(), is(UpperCardinality.class));
47 assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected));
48 }
49
50 static Stream<Arguments> maxTest() {
51 return Stream.of(
52 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
53 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)),
54 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)),
55 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
56 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED),
57 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
58 Arguments.of(UpperCardinality.of(1), null, null),
59 Arguments.of(null, UpperCardinality.of(1), null),
60 Arguments.of(null, null, null)
61 );
62 }
63
64 @ParameterizedTest(name = "{0} + {1} == {2}")
65 @MethodSource
66 void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
67 var term = UpperCardinalityTerms.add(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
68 assertThat(term.getType(), is(UpperCardinality.class));
69 assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected));
70 }
71
72 static Stream<Arguments> addTest() {
73 return Stream.of(
74 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)),
75 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
76 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
77 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
78 Arguments.of(UpperCardinality.of(1), null, null),
79 Arguments.of(null, UpperCardinality.of(1), null),
80 Arguments.of(null, null, null)
81 );
82 }
83
84 @ParameterizedTest(name = "{0} * {1} == {2}")
85 @MethodSource
86 void mulTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
87 var term = UpperCardinalityTerms.mul(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
88 assertThat(term.getType(), is(UpperCardinality.class));
89 assertThat(term.evaluate(Valuation.empty()), Matchers.is(expected));
90 }
91
92 static Stream<Arguments> mulTest() {
93 return Stream.of(
94 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)),
95 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
96 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
97 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
98 Arguments.of(UpperCardinality.of(1), null, null),
99 Arguments.of(null, UpperCardinality.of(1), null),
100 Arguments.of(null, null, null)
101 );
102 }
103}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java
new file mode 100644
index 00000000..70cb6695
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTest.java
@@ -0,0 +1,115 @@
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.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11
12import java.util.stream.Stream;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.equalTo;
16
17class UpperCardinalityTest {
18 @ParameterizedTest(name = "min({0}, {1}) == {2}")
19 @MethodSource
20 void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
21 assertThat(a.min(b), equalTo(expected));
22 }
23
24 static Stream<Arguments> minTest() {
25 return Stream.of(
26 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
27 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)),
28 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)),
29 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)),
30 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)),
31 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
32 );
33 }
34
35 @ParameterizedTest(name = "max({0}, {1}) == {2}")
36 @MethodSource
37 void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
38 assertThat(a.max(b), equalTo(expected));
39 }
40
41 static Stream<Arguments> maxTest() {
42 return Stream.of(
43 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
44 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)),
45 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)),
46 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
47 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED),
48 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
49 );
50 }
51
52 @ParameterizedTest(name = "{0} + {1} == {2}")
53 @MethodSource
54 void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
55 assertThat(a.add(b), equalTo(expected));
56 }
57
58 static Stream<Arguments> addTest() {
59 return Stream.of(
60 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)),
61 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
62 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
63 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
64 );
65 }
66
67 @ParameterizedTest(name = "{0} * {1} == {2}")
68 @MethodSource
69 void multiplyTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
70 assertThat(a.multiply(b), equalTo(expected));
71 }
72
73 static Stream<Arguments> multiplyTest() {
74 return Stream.of(
75 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)),
76 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
77 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
78 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
79 );
80 }
81
82 @ParameterizedTest(name = "{0}.compareTo({1}) == {2}")
83 @MethodSource
84 void compareToTest(UpperCardinality a, UpperCardinality b, int expected) {
85 assertThat(a.compareTo(b), equalTo(expected));
86 }
87
88 static Stream<Arguments> compareToTest() {
89 return Stream.of(
90 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), 0),
91 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), -1),
92 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), 1),
93 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, -1),
94 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), 1),
95 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, 0)
96 );
97 }
98
99 @ParameterizedTest(name = "{0}.compareToInt({1}) == {2}")
100 @MethodSource
101 void compareToIntTest(UpperCardinality a, int b, int expected) {
102 assertThat(a.compareToInt(b), equalTo(expected));
103 }
104
105 static Stream<Arguments> compareToIntTest() {
106 return Stream.of(
107 Arguments.of(UpperCardinality.of(3), -1, 1),
108 Arguments.of(UpperCardinality.of(3), 2, 1),
109 Arguments.of(UpperCardinality.of(3), 3, 0),
110 Arguments.of(UpperCardinality.of(3), 4, -1),
111 Arguments.of(UpperCardinalities.UNBOUNDED, -1, 1),
112 Arguments.of(UpperCardinalities.UNBOUNDED, 3, 1)
113 );
114 }
115}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java
new file mode 100644
index 00000000..4a55f561
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeFunctionView.java
@@ -0,0 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.tests;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.term.*;
10
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.List;
14
15public record FakeFunctionView<T>(String name, int keyArity, Class<T> valueType) implements Constraint {
16 @Override
17 public int arity() {
18 return keyArity + 1;
19 }
20
21 @Override
22 public List<Parameter> getParameters() {
23 var parameters = new Parameter[keyArity + 1];
24 Arrays.fill(parameters, Parameter.NODE_OUT);
25 parameters[keyArity] = new Parameter(valueType, ParameterDirection.OUT);
26 return List.of(parameters);
27 }
28
29 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
30 return targetVariable -> {
31 var placeholderVariable = Variable.of(valueType);
32 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
33 argumentsWithPlaceholder.addAll(arguments);
34 argumentsWithPlaceholder.add(placeholderVariable);
35 return aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable);
36 };
37 }
38
39 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
40 return aggregate(aggregator, List.of(arguments));
41 }
42
43 public AssignedValue<T> leftJoin(T defaultValue, List<NodeVariable> arguments) {
44 return targetVariable -> {
45 var placeholderVariable = Variable.of(valueType);
46 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
47 argumentsWithPlaceholder.addAll(arguments);
48 argumentsWithPlaceholder.add(placeholderVariable);
49 return leftJoinBy(placeholderVariable, defaultValue, argumentsWithPlaceholder).toLiteral(targetVariable);
50 };
51 }
52
53 public AssignedValue<T> leftJoin(T defaultValue, NodeVariable... arguments) {
54 return leftJoin(defaultValue, List.of(arguments));
55
56 }
57}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java
new file mode 100644
index 00000000..7e09ddab
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/FakeKeyOnlyView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.logic.tests;
7
8import tools.refinery.logic.Constraint;
9import tools.refinery.logic.term.Parameter;
10
11import java.util.Arrays;
12import java.util.List;
13
14public record FakeKeyOnlyView(String name, int arity) implements Constraint {
15 @Override
16 public List<Parameter> getParameters() {
17 var parameters = new Parameter[arity];
18 Arrays.fill(parameters, Parameter.NODE_OUT);
19 return List.of(parameters);
20 }
21}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java
new file mode 100644
index 00000000..52a22ce1
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToRawTest.java
@@ -0,0 +1,155 @@
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.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.dnf.Dnf;
11import tools.refinery.logic.dnf.SymbolicParameter;
12import tools.refinery.logic.term.NodeVariable;
13import tools.refinery.logic.term.ParameterDirection;
14import tools.refinery.logic.term.Variable;
15
16import java.util.List;
17
18import static org.hamcrest.CoreMatchers.containsString;
19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.allOf;
21import static org.junit.jupiter.api.Assertions.assertThrows;
22import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo;
23
24class StructurallyEqualToRawTest {
25 private static final Constraint personView = new FakeKeyOnlyView("Person", 1);
26 private static final Constraint friendView = new FakeKeyOnlyView("friend", 2);
27 private static final NodeVariable p = Variable.of("p");
28 private static final NodeVariable q = Variable.of("q");
29
30 @Test
31 void flatEqualsTest() {
32 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
33
34 assertThat(actual, structurallyEqualTo(
35 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
36 List.of(List.of(personView.call(q)))
37 ));
38 }
39
40 @Test
41 void flatNotEqualsTest() {
42 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
43
44 var assertion = structurallyEqualTo(
45 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
46 List.of(List.of(friendView.call(q, q)))
47 );
48 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
49 }
50
51 @Test
52 void deepEqualsTest() {
53 var actual = Dnf.builder("Actual").parameters(q).clause(
54 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
55 ).build();
56
57 assertThat(actual, structurallyEqualTo(
58 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
59 List.of(
60 List.of(
61 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
62 )
63 )
64 ));
65 }
66
67 @Test
68 void deepNotEqualsTest() {
69 var actual = Dnf.builder("Actual").parameter(q).clause(
70 Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q)
71 ).build();
72
73 var assertion = structurallyEqualTo(
74 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
75 List.of(
76 List.of(
77 Dnf.builder("Expected2")
78 .parameters(p)
79 .clause(friendView.call(p, p))
80 .build()
81 .call(q)
82 )
83 )
84 );
85 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
86 assertThat(error.getMessage(), allOf(containsString("Expected2"), containsString("Actual2")));
87 }
88
89 @Test
90 void parameterListLengthMismatchTest() {
91 var actual = Dnf.builder("Actual").parameters(p, q).clause(
92 friendView.call(p, q)
93 ).build();
94
95 var assertion = structurallyEqualTo(
96 List.of(new SymbolicParameter(p, ParameterDirection.OUT)),
97 List.of(List.of(friendView.call(p, p)))
98 );
99
100 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
101 }
102
103 @Test
104 void parameterDirectionMismatchTest() {
105 var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause(
106 personView.call(p)
107 ).build();
108
109 var assertion = structurallyEqualTo(
110 List.of(new SymbolicParameter(p, ParameterDirection.OUT)),
111 List.of(List.of(personView.call(p)))
112 );
113
114 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
115 }
116
117 @Test
118 void clauseCountMismatchTest() {
119 var actual = Dnf.builder("Actual").parameters(p, q).clause(
120 friendView.call(p, q)
121 ).build();
122
123 var assertion = structurallyEqualTo(
124 List.of(
125 new SymbolicParameter(p, ParameterDirection.OUT),
126 new SymbolicParameter(q, ParameterDirection.OUT)
127 ),
128 List.of(
129 List.of(friendView.call(p, q)),
130 List.of(friendView.call(q, p))
131 )
132 );
133
134 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
135 }
136
137 @Test
138 void literalCountMismatchTest() {
139 var actual = Dnf.builder("Actual").parameters(p, q).clause(
140 friendView.call(p, q)
141 ).build();
142
143 var assertion = structurallyEqualTo(
144 List.of(
145 new SymbolicParameter(p, ParameterDirection.OUT),
146 new SymbolicParameter(q, ParameterDirection.OUT)
147 ),
148 List.of(
149 List.of(friendView.call(p, q), friendView.call(q, p))
150 )
151 );
152
153 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
154 }
155}
diff --git a/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java
new file mode 100644
index 00000000..663b115a
--- /dev/null
+++ b/subprojects/logic/src/test/java/tools/refinery/logic/tests/StructurallyEqualToTest.java
@@ -0,0 +1,123 @@
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.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.logic.Constraint;
10import tools.refinery.logic.dnf.Dnf;
11import tools.refinery.logic.term.NodeVariable;
12import tools.refinery.logic.term.ParameterDirection;
13import tools.refinery.logic.term.Variable;
14
15import static org.hamcrest.CoreMatchers.containsString;
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.junit.jupiter.api.Assertions.assertThrows;
18import static tools.refinery.logic.tests.QueryMatchers.structurallyEqualTo;
19
20class StructurallyEqualToTest {
21 private static final Constraint personView = new FakeKeyOnlyView("Person", 1);
22 private static final Constraint friendView = new FakeKeyOnlyView("friend", 2);
23 private static final NodeVariable p = Variable.of("p");
24 private static final NodeVariable q = Variable.of("q");
25
26 @Test
27 void flatEqualsTest() {
28 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
29 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
30
31 assertThat(actual, structurallyEqualTo(expected));
32 }
33
34 @Test
35 void flatNotEqualsTest() {
36 var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build();
37 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
38
39 var assertion = structurallyEqualTo(expected);
40 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
41 }
42
43 @Test
44 void deepEqualsTest() {
45 var expected = Dnf.builder("Expected").parameters(q).clause(
46 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
47 ).build();
48 var actual = Dnf.builder("Actual").parameters(q).clause(
49 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
50 ).build();
51
52 assertThat(actual, structurallyEqualTo(expected));
53 }
54
55 @Test
56 void deepNotEqualsTest() {
57 var expected = Dnf.builder("Expected").parameters(q).clause(
58 Dnf.builder("Expected2").parameters(p).clause(friendView.call(p, p)).build().call(q)
59 ).build();
60 var actual = Dnf.builder("Actual").parameter(q).clause(
61 Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q)
62 ).build();
63
64 var assertion = structurallyEqualTo(expected);
65 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
66 assertThat(error.getMessage(), containsString(" called from Expected/1 "));
67 }
68
69 @Test
70 void parameterListLengthMismatchTest() {
71 var expected = Dnf.builder("Expected").parameter(p).clause(
72 friendView.call(p, p)
73 ).build();
74 var actual = Dnf.builder("Actual").parameters(p, q).clause(
75 friendView.call(p, q)
76 ).build();
77
78 var assertion = structurallyEqualTo(expected);
79 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
80 }
81
82 @Test
83 void parameterDirectionMismatchTest() {
84 var expected = Dnf.builder("Expected").parameter(p, ParameterDirection.OUT).clause(
85 personView.call(p)
86 ).build();
87 var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause(
88 personView.call(p)
89 ).build();
90
91 var assertion = structurallyEqualTo(expected);
92 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
93 }
94
95 @Test
96 void clauseCountMismatchTest() {
97 var expected = Dnf.builder("Expected")
98 .parameters(p, q)
99 .clause(friendView.call(p, q))
100 .clause(friendView.call(q, p))
101 .build();
102 var actual = Dnf.builder("Actual").parameters(p, q).clause(
103 friendView.call(p, q)
104 ).build();
105
106 var assertion = structurallyEqualTo(expected);
107 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
108 }
109
110 @Test
111 void literalCountMismatchTest() {
112 var expected = Dnf.builder("Expected").parameters(p, q).clause(
113 friendView.call(p, q),
114 friendView.call(q, p)
115 ).build();
116 var actual = Dnf.builder("Actual").parameters(p, q).clause(
117 friendView.call(p, q)
118 ).build();
119
120 var assertion = structurallyEqualTo(expected);
121 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
122 }
123}
diff --git a/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/MismatchDescribingDnfEqualityChecker.java b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/MismatchDescribingDnfEqualityChecker.java
new file mode 100644
index 00000000..aa73baec
--- /dev/null
+++ b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/MismatchDescribingDnfEqualityChecker.java
@@ -0,0 +1,68 @@
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.tests;
7
8import org.hamcrest.Description;
9import tools.refinery.logic.dnf.Dnf;
10import tools.refinery.logic.dnf.SymbolicParameter;
11import tools.refinery.logic.equality.DeepDnfEqualityChecker;
12import tools.refinery.logic.literal.Literal;
13
14import java.util.List;
15
16class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker {
17 private final Description description;
18 private boolean raw;
19 private boolean needsDescription = true;
20
21 MismatchDescribingDnfEqualityChecker(Description description) {
22 this.description = description;
23 }
24
25 public boolean needsDescription() {
26 return needsDescription;
27 }
28
29 @Override
30 public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters, List<? extends List<? extends Literal>> clauses, Dnf other) {
31 try {
32 raw = true;
33 boolean result = super.dnfEqualRaw(symbolicParameters, clauses, other);
34 if (!result && needsDescription) {
35 description.appendText("was ").appendText(other.toDefinitionString());
36 }
37 return false;
38 } finally {
39 raw = false;
40 }
41 }
42
43 @Override
44 protected boolean doCheckEqual(Pair pair) {
45 boolean result = super.doCheckEqual(pair);
46 if (!result && needsDescription) {
47 describeMismatch(pair);
48 // Only describe the first found (innermost) mismatch.
49 needsDescription = false;
50 }
51 return result;
52 }
53
54 private void describeMismatch(Pair pair) {
55 var inProgress = getInProgress();
56 int size = inProgress.size();
57 if (size <= 1 && !raw) {
58 description.appendText("was ").appendText(pair.right().toDefinitionString());
59 return;
60 }
61 var last = inProgress.get(size - 1);
62 description.appendText("expected ").appendText(last.left().toDefinitionString());
63 for (int i = size - 2; i >= 0; i--) {
64 description.appendText(" called from ").appendText(inProgress.get(i).left().toString());
65 }
66 description.appendText(" was not structurally equal to ").appendText(last.right().toDefinitionString());
67 }
68}
diff --git a/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/QueryMatchers.java b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/QueryMatchers.java
new file mode 100644
index 00000000..40332a8c
--- /dev/null
+++ b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/QueryMatchers.java
@@ -0,0 +1,46 @@
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.tests;
7
8import org.hamcrest.Matcher;
9import tools.refinery.logic.dnf.Dnf;
10import tools.refinery.logic.dnf.SymbolicParameter;
11import tools.refinery.logic.literal.Literal;
12
13import java.util.List;
14
15public final class QueryMatchers {
16 private QueryMatchers() {
17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
18 }
19
20 /**
21 * Compare two {@link Dnf} instances up to renaming of variables.
22 *
23 * @param expected The expected {@link Dnf} instance.
24 * @return A Hamcrest matcher for equality up to renaming of variables.
25 */
26 public static Matcher<Dnf> structurallyEqualTo(Dnf expected) {
27 return new StructurallyEqualTo(expected);
28 }
29
30 /**
31 * Compare a {@link Dnf} instance to another predicate in DNF form without constructing it.
32 * <p>
33 * This matcher should be used instead of {@link #structurallyEqualTo(Dnf)} when the validation and
34 * pre-processing associated with the {@link Dnf} constructor, i.e., validation of parameter directions,
35 * topological sorting of literals, and the reduction of trivial predicates is not desired. In particular, this
36 * matcher can be used to test for exact order of literal after pre-processing.
37 *
38 * @param expectedSymbolicParameters The expected list of symbolic parameters.
39 * @param expectedLiterals The expected clauses. Each clause is represented by a list of literals.
40 * @return A Hamcrest matcher for equality up to renaming of variables.
41 */
42 public static Matcher<Dnf> structurallyEqualTo(List<SymbolicParameter> expectedSymbolicParameters,
43 List<? extends List<? extends Literal>> expectedLiterals) {
44 return new StructurallyEqualToRaw(expectedSymbolicParameters, expectedLiterals);
45 }
46}
diff --git a/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualTo.java b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualTo.java
new file mode 100644
index 00000000..257e6850
--- /dev/null
+++ b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualTo.java
@@ -0,0 +1,41 @@
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.tests;
7
8import org.hamcrest.Description;
9import org.hamcrest.TypeSafeMatcher;
10import tools.refinery.logic.dnf.Dnf;
11import tools.refinery.logic.equality.DeepDnfEqualityChecker;
12
13public class StructurallyEqualTo extends TypeSafeMatcher<Dnf> {
14 private final Dnf expected;
15
16 public StructurallyEqualTo(Dnf expected) {
17 this.expected = expected;
18 }
19
20 @Override
21 protected boolean matchesSafely(Dnf item) {
22 var checker = new DeepDnfEqualityChecker();
23 return checker.dnfEqual(expected, item);
24 }
25
26 @Override
27 protected void describeMismatchSafely(Dnf item, Description mismatchDescription) {
28 var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription);
29 if (describingChecker.dnfEqual(expected, item)) {
30 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
31 }
32 if (describingChecker.needsDescription()) {
33 super.describeMismatchSafely(item, mismatchDescription);
34 }
35 }
36
37 @Override
38 public void describeTo(Description description) {
39 description.appendText("structurally equal to ").appendText(expected.toDefinitionString());
40 }
41}
diff --git a/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualToRaw.java b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualToRaw.java
new file mode 100644
index 00000000..944ab1ac
--- /dev/null
+++ b/subprojects/logic/src/testFixtures/java/tools/refinery/logic/tests/StructurallyEqualToRaw.java
@@ -0,0 +1,51 @@
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.tests;
7
8import org.hamcrest.Description;
9import org.hamcrest.TypeSafeMatcher;
10import tools.refinery.logic.dnf.Dnf;
11import tools.refinery.logic.dnf.SymbolicParameter;
12import tools.refinery.logic.equality.DeepDnfEqualityChecker;
13import tools.refinery.logic.literal.Literal;
14
15import java.util.List;
16
17public class StructurallyEqualToRaw extends TypeSafeMatcher<Dnf> {
18 private final List<SymbolicParameter> expectedSymbolicParameters;
19 private final List<? extends List<? extends Literal>> expectedClauses;
20
21 public StructurallyEqualToRaw(List<SymbolicParameter> expectedSymbolicParameters,
22 List<? extends List<? extends Literal>> expectedClauses) {
23 this.expectedSymbolicParameters = expectedSymbolicParameters;
24 this.expectedClauses = expectedClauses;
25 }
26
27 @Override
28 protected boolean matchesSafely(Dnf item) {
29 var checker = new DeepDnfEqualityChecker();
30 return checker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item);
31 }
32
33 @Override
34 protected void describeMismatchSafely(Dnf item, Description mismatchDescription) {
35 var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription);
36 if (describingChecker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item)) {
37 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
38 }
39 if (describingChecker.needsDescription()) {
40 super.describeMismatchSafely(item, mismatchDescription);
41 }
42 }
43
44 @Override
45 public void describeTo(Description description) {
46 description.appendText("structurally equal to ")
47 .appendValueList("(", ", ", ")", expectedSymbolicParameters)
48 .appendText(" <-> ")
49 .appendValueList("", ", ", ".", expectedClauses);
50 }
51}