From 16a9b534adec2c53b50f92a43c1623918b1c59c0 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 7 Mar 2024 22:10:42 +0100 Subject: refactor: move terms and DNF into logic subproject --- .../java/tools/refinery/logic/AbstractDomain.java | 37 +++ .../tools/refinery/logic/AnyAbstractDomain.java | 12 + .../main/java/tools/refinery/logic/Constraint.java | 82 +++++ .../refinery/logic/InvalidQueryException.java | 23 ++ .../refinery/logic/dnf/AbstractQueryBuilder.java | 175 ++++++++++ .../java/tools/refinery/logic/dnf/AnyQuery.java | 16 + .../refinery/logic/dnf/ClausePostProcessor.java | 362 +++++++++++++++++++++ .../main/java/tools/refinery/logic/dnf/Dnf.java | 235 +++++++++++++ .../java/tools/refinery/logic/dnf/DnfBuilder.java | 225 +++++++++++++ .../java/tools/refinery/logic/dnf/DnfClause.java | 37 +++ .../tools/refinery/logic/dnf/DnfPostProcessor.java | 112 +++++++ .../java/tools/refinery/logic/dnf/DnfUtils.java | 24 ++ .../refinery/logic/dnf/FunctionalDependency.java | 22 ++ .../tools/refinery/logic/dnf/FunctionalQuery.java | 126 +++++++ .../refinery/logic/dnf/FunctionalQueryBuilder.java | 29 ++ .../refinery/logic/dnf/InvalidClauseException.java | 35 ++ .../main/java/tools/refinery/logic/dnf/Query.java | 202 ++++++++++++ .../tools/refinery/logic/dnf/QueryBuilder.java | 27 ++ .../tools/refinery/logic/dnf/RelationalQuery.java | 77 +++++ .../refinery/logic/dnf/SymbolicParameter.java | 53 +++ .../logic/dnf/callback/ClauseCallback0.java | 15 + .../logic/dnf/callback/ClauseCallback1Data0.java | 16 + .../logic/dnf/callback/ClauseCallback1Data1.java | 16 + .../logic/dnf/callback/ClauseCallback2Data0.java | 16 + .../logic/dnf/callback/ClauseCallback2Data1.java | 17 + .../logic/dnf/callback/ClauseCallback2Data2.java | 16 + .../logic/dnf/callback/ClauseCallback3Data0.java | 16 + .../logic/dnf/callback/ClauseCallback3Data1.java | 17 + .../logic/dnf/callback/ClauseCallback3Data2.java | 17 + .../logic/dnf/callback/ClauseCallback3Data3.java | 16 + .../logic/dnf/callback/ClauseCallback4Data0.java | 16 + .../logic/dnf/callback/ClauseCallback4Data1.java | 17 + .../logic/dnf/callback/ClauseCallback4Data2.java | 17 + .../logic/dnf/callback/ClauseCallback4Data3.java | 17 + .../logic/dnf/callback/ClauseCallback4Data4.java | 16 + .../dnf/callback/FunctionalQueryCallback0.java | 14 + .../dnf/callback/FunctionalQueryCallback1.java | 15 + .../dnf/callback/FunctionalQueryCallback2.java | 15 + .../dnf/callback/FunctionalQueryCallback3.java | 16 + .../dnf/callback/FunctionalQueryCallback4.java | 16 + .../logic/dnf/callback/QueryCallback0.java | 13 + .../logic/dnf/callback/QueryCallback1.java | 14 + .../logic/dnf/callback/QueryCallback2.java | 14 + .../logic/dnf/callback/QueryCallback3.java | 14 + .../logic/dnf/callback/QueryCallback4.java | 14 + .../logic/equality/DeepDnfEqualityChecker.java | 77 +++++ .../logic/equality/DnfEqualityChecker.java | 17 + .../logic/equality/LiteralEqualityHelper.java | 27 ++ .../logic/equality/LiteralHashCodeHelper.java | 17 + .../SubstitutingLiteralEqualityHelper.java | 59 ++++ .../SubstitutingLiteralHashCodeHelper.java | 42 +++ .../logic/literal/AbstractCallLiteral.java | 135 ++++++++ .../logic/literal/AbstractCountLiteral.java | 107 ++++++ .../refinery/logic/literal/AbstractLiteral.java | 34 ++ .../refinery/logic/literal/AggregationLiteral.java | 143 ++++++++ .../refinery/logic/literal/AssignLiteral.java | 88 +++++ .../refinery/logic/literal/BooleanLiteral.java | 69 ++++ .../tools/refinery/logic/literal/CallLiteral.java | 134 ++++++++ .../tools/refinery/logic/literal/CallPolarity.java | 39 +++ .../tools/refinery/logic/literal/CanNegate.java | 10 + .../tools/refinery/logic/literal/CheckLiteral.java | 95 ++++++ .../tools/refinery/logic/literal/Connectivity.java | 18 + .../refinery/logic/literal/ConstantLiteral.java | 73 +++++ .../tools/refinery/logic/literal/CountLiteral.java | 45 +++ .../refinery/logic/literal/EquivalenceLiteral.java | 100 ++++++ .../refinery/logic/literal/LeftJoinLiteral.java | 140 ++++++++ .../java/tools/refinery/logic/literal/Literal.java | 32 ++ .../tools/refinery/logic/literal/Literals.java | 22 ++ .../tools/refinery/logic/literal/Reduction.java | 31 ++ .../literal/RepresentativeElectionLiteral.java | 119 +++++++ .../logic/rewriter/AbstractRecursiveRewriter.java | 26 ++ .../rewriter/ClauseInputParameterResolver.java | 160 +++++++++ .../refinery/logic/rewriter/CompositeRewriter.java | 29 ++ .../tools/refinery/logic/rewriter/DnfRewriter.java | 24 ++ .../logic/rewriter/DuplicateDnfRemover.java | 98 ++++++ .../logic/rewriter/InputParameterResolver.java | 51 +++ .../logic/substitution/MapBasedSubstitution.java | 18 + .../logic/substitution/RenewingSubstitution.java | 20 ++ .../logic/substitution/StatelessSubstitution.java | 23 ++ .../refinery/logic/substitution/Substitution.java | 29 ++ .../logic/substitution/SubstitutionBuilder.java | 79 +++++ .../tools/refinery/logic/term/AbstractTerm.java | 47 +++ .../java/tools/refinery/logic/term/Aggregator.java | 18 + .../tools/refinery/logic/term/AnyDataVariable.java | 55 ++++ .../java/tools/refinery/logic/term/AnyTerm.java | 24 ++ .../tools/refinery/logic/term/AssignedValue.java | 13 + .../java/tools/refinery/logic/term/BinaryTerm.java | 106 ++++++ .../tools/refinery/logic/term/ConstantTerm.java | 67 ++++ .../tools/refinery/logic/term/DataVariable.java | 103 ++++++ .../logic/term/ExtremeValueAggregator.java | 108 ++++++ .../tools/refinery/logic/term/NodeVariable.java | 71 ++++ .../java/tools/refinery/logic/term/Parameter.java | 68 ++++ .../refinery/logic/term/ParameterDirection.java | 22 ++ .../refinery/logic/term/StatefulAggregate.java | 22 ++ .../refinery/logic/term/StatefulAggregator.java | 28 ++ .../refinery/logic/term/StatelessAggregator.java | 25 ++ .../main/java/tools/refinery/logic/term/Term.java | 26 ++ .../java/tools/refinery/logic/term/UnaryTerm.java | 74 +++++ .../java/tools/refinery/logic/term/Variable.java | 88 +++++ .../refinery/logic/term/bool/BoolAndTerm.java | 31 ++ .../refinery/logic/term/bool/BoolBinaryTerm.java | 15 + .../refinery/logic/term/bool/BoolNotTerm.java | 31 ++ .../tools/refinery/logic/term/bool/BoolOrTerm.java | 31 ++ .../tools/refinery/logic/term/bool/BoolTerms.java | 35 ++ .../refinery/logic/term/bool/BoolXorTerm.java | 31 ++ .../cardinalityinterval/CardinalityDomain.java | 69 ++++ .../cardinalityinterval/CardinalityInterval.java | 30 ++ .../cardinalityinterval/CardinalityIntervals.java | 54 +++ .../EmptyCardinalityInterval.java | 74 +++++ .../NonEmptyCardinalityInterval.java | 109 +++++++ .../logic/term/comparable/ComparisonTerm.java | 19 ++ .../refinery/logic/term/comparable/EqTerm.java | 30 ++ .../logic/term/comparable/GreaterEqTerm.java | 30 ++ .../logic/term/comparable/GreaterTerm.java | 30 ++ .../refinery/logic/term/comparable/LessEqTerm.java | 30 ++ .../refinery/logic/term/comparable/LessTerm.java | 30 ++ .../refinery/logic/term/comparable/NotEqTerm.java | 30 ++ .../tools/refinery/logic/term/int_/IntAddTerm.java | 31 ++ .../refinery/logic/term/int_/IntBinaryTerm.java | 15 + .../tools/refinery/logic/term/int_/IntDivTerm.java | 31 ++ .../tools/refinery/logic/term/int_/IntMaxTerm.java | 31 ++ .../tools/refinery/logic/term/int_/IntMinTerm.java | 31 ++ .../refinery/logic/term/int_/IntMinusTerm.java | 30 ++ .../tools/refinery/logic/term/int_/IntMulTerm.java | 31 ++ .../refinery/logic/term/int_/IntPlusTerm.java | 30 ++ .../tools/refinery/logic/term/int_/IntPowTerm.java | 43 +++ .../tools/refinery/logic/term/int_/IntSubTerm.java | 31 ++ .../refinery/logic/term/int_/IntSumAggregator.java | 40 +++ .../tools/refinery/logic/term/int_/IntTerms.java | 95 ++++++ .../refinery/logic/term/int_/IntUnaryTerm.java | 15 + .../refinery/logic/term/int_/RealToIntTerm.java | 31 ++ .../refinery/logic/term/real/IntToRealTerm.java | 31 ++ .../refinery/logic/term/real/RealAddTerm.java | 31 ++ .../refinery/logic/term/real/RealBinaryTerm.java | 15 + .../refinery/logic/term/real/RealDivTerm.java | 31 ++ .../refinery/logic/term/real/RealMaxTerm.java | 31 ++ .../refinery/logic/term/real/RealMinTerm.java | 31 ++ .../refinery/logic/term/real/RealMinusTerm.java | 30 ++ .../refinery/logic/term/real/RealMulTerm.java | 31 ++ .../refinery/logic/term/real/RealPlusTerm.java | 30 ++ .../refinery/logic/term/real/RealPowTerm.java | 31 ++ .../refinery/logic/term/real/RealSubTerm.java | 31 ++ .../logic/term/real/RealSumAggregator.java | 90 +++++ .../tools/refinery/logic/term/real/RealTerms.java | 95 ++++++ .../refinery/logic/term/real/RealUnaryTerm.java | 15 + .../refinery/logic/term/truthvalue/TruthValue.java | 76 +++++ .../logic/term/truthvalue/TruthValueDomain.java | 69 ++++ .../uppercardinality/FiniteUpperCardinality.java | 83 +++++ .../UnboundedUpperCardinality.java | 66 ++++ .../term/uppercardinality/UpperCardinalities.java | 38 +++ .../term/uppercardinality/UpperCardinality.java | 32 ++ .../uppercardinality/UpperCardinalityAddTerm.java | 30 ++ .../UpperCardinalityBinaryTerm.java | 16 + .../uppercardinality/UpperCardinalityMaxTerm.java | 30 ++ .../uppercardinality/UpperCardinalityMinTerm.java | 30 ++ .../uppercardinality/UpperCardinalityMulTerm.java | 30 ++ .../UpperCardinalitySumAggregator.java | 84 +++++ .../uppercardinality/UpperCardinalityTerms.java | 71 ++++ .../refinery/logic/util/CycleDetectingMapper.java | 61 ++++ .../logic/valuation/MapBasedValuation.java | 22 ++ .../logic/valuation/RestrictedValuation.java | 21 ++ .../logic/valuation/SubstitutedValuation.java | 16 + .../tools/refinery/logic/valuation/Valuation.java | 37 +++ .../refinery/logic/valuation/ValuationBuilder.java | 40 +++ 164 files changed, 8065 insertions(+) create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/AbstractDomain.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/AnyAbstractDomain.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/Constraint.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/InvalidQueryException.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/AbstractQueryBuilder.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/AnyQuery.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/ClausePostProcessor.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/Dnf.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfBuilder.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfClause.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfPostProcessor.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/DnfUtils.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalDependency.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQuery.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/FunctionalQueryBuilder.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/InvalidClauseException.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/Query.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/QueryBuilder.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/RelationalQuery.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/SymbolicParameter.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback0.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data0.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback1Data1.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data0.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data1.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback2Data2.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data0.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data1.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data2.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback3Data3.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data0.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data1.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data2.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data3.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/ClauseCallback4Data4.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback0.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback1.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback2.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback3.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/FunctionalQueryCallback4.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback0.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback1.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback2.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback3.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/dnf/callback/QueryCallback4.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/equality/DeepDnfEqualityChecker.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/equality/DnfEqualityChecker.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralEqualityHelper.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/equality/LiteralHashCodeHelper.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralEqualityHelper.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/equality/SubstitutingLiteralHashCodeHelper.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCallLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractCountLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/AbstractLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/AggregationLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/AssignLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/BooleanLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/CallLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/CallPolarity.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/CanNegate.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/CheckLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/Connectivity.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/ConstantLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/CountLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/EquivalenceLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/LeftJoinLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/Literal.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/Literals.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/Reduction.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/literal/RepresentativeElectionLiteral.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/rewriter/AbstractRecursiveRewriter.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/rewriter/ClauseInputParameterResolver.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/rewriter/CompositeRewriter.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DnfRewriter.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/rewriter/DuplicateDnfRemover.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/rewriter/InputParameterResolver.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/substitution/MapBasedSubstitution.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/substitution/RenewingSubstitution.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/substitution/StatelessSubstitution.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/substitution/Substitution.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/substitution/SubstitutionBuilder.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/AbstractTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/Aggregator.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/AnyDataVariable.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/AnyTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/AssignedValue.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/BinaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/ConstantTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/DataVariable.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/ExtremeValueAggregator.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/NodeVariable.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/Parameter.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/ParameterDirection.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregate.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/StatefulAggregator.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/StatelessAggregator.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/Term.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/UnaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/Variable.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolAndTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolBinaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolNotTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolOrTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolTerms.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/bool/BoolXorTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityDomain.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityInterval.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/CardinalityIntervals.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/EmptyCardinalityInterval.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/cardinalityinterval/NonEmptyCardinalityInterval.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/ComparisonTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/EqTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterEqTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/GreaterTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessEqTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/LessTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/comparable/NotEqTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntAddTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntBinaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntDivTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMaxTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMinusTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntMulTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPlusTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntPowTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSubTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntSumAggregator.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntTerms.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/IntUnaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/int_/RealToIntTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/IntToRealTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealAddTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealBinaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealDivTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMaxTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMinusTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealMulTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPlusTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealPowTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSubTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealSumAggregator.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealTerms.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/real/RealUnaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValue.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/truthvalue/TruthValueDomain.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/FiniteUpperCardinality.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UnboundedUpperCardinality.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalities.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinality.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityAddTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityBinaryTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMaxTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMinTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityMulTerm.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalitySumAggregator.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/term/uppercardinality/UpperCardinalityTerms.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/util/CycleDetectingMapper.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/valuation/MapBasedValuation.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/valuation/RestrictedValuation.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/valuation/SubstitutedValuation.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/valuation/Valuation.java create mode 100644 subprojects/logic/src/main/java/tools/refinery/logic/valuation/ValuationBuilder.java (limited to 'subprojects/logic/src/main/java/tools') 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic; + +import java.util.Objects; +import java.util.Optional; + +public non-sealed interface AbstractDomain extends AnyAbstractDomain { + @Override + Class abstractType(); + + @Override + Class concreteType(); + + A toAbstract(C concreteValue); + + Optional toConcrete(A abstractValue); + + default boolean isConcrete(A abstractValue) { + return toConcrete(abstractValue).isPresent(); + } + + default boolean isRefinement(A originalValue, A refinedValue) { + return Objects.equals(commonRefinement(originalValue, refinedValue), refinedValue); + } + + A commonRefinement(A leftValue, A rightValue); + + A commonAncestor(A leftValue, A rightValue); + + A unknown(); + + boolean isError(A abstractValue); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic; + +public sealed interface AnyAbstractDomain permits AbstractDomain { + Class abstractType(); + + Class concreteType(); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.literal.*; +import tools.refinery.logic.term.*; + +import java.util.List; + +public interface Constraint { + String name(); + + List getParameters(); + + default int arity() { + return getParameters().size(); + } + + default boolean invalidIndex(int i) { + return i < 0 || i >= arity(); + } + + default Reduction getReduction() { + return Reduction.NOT_REDUCIBLE; + } + + default boolean equals(LiteralEqualityHelper helper, Constraint other) { + return equals(other); + } + + default String toReferenceString() { + return name(); + } + + default CallLiteral call(CallPolarity polarity, List arguments) { + return new CallLiteral(polarity, this, arguments); + } + + default CallLiteral call(CallPolarity polarity, Variable... arguments) { + return call(polarity, List.of(arguments)); + } + + default CallLiteral call(Variable... arguments) { + return call(CallPolarity.POSITIVE, arguments); + } + + default CallLiteral callTransitive(NodeVariable left, NodeVariable right) { + return call(CallPolarity.TRANSITIVE, List.of(left, right)); + } + + default AssignedValue count(List arguments) { + return targetVariable -> new CountLiteral(targetVariable, this, arguments); + } + + default AssignedValue count(Variable... arguments) { + return count(List.of(arguments)); + } + + default AssignedValue aggregateBy(DataVariable inputVariable, Aggregator aggregator, + List arguments) { + return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments); + } + + default AssignedValue aggregateBy(DataVariable inputVariable, Aggregator aggregator, + Variable... arguments) { + return aggregateBy(inputVariable, aggregator, List.of(arguments)); + } + + default AssignedValue leftJoinBy(DataVariable placeholderVariable, T defaultValue, + List arguments) { + return targetVariable -> new LeftJoinLiteral<>(targetVariable, placeholderVariable, defaultValue, this, + arguments); + } + + default AssignedValue leftJoinBy(DataVariable inputVariable, T defaultValue, Variable... arguments) { + return leftJoinBy(inputVariable, defaultValue, List.of(arguments)); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic; + +public class InvalidQueryException extends RuntimeException { + public InvalidQueryException() { + } + + public InvalidQueryException(String message) { + super(message); + } + + public InvalidQueryException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidQueryException(Throwable cause) { + super(cause); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.dnf.callback.*; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public abstract class AbstractQueryBuilder> { + protected final DnfBuilder dnfBuilder; + + protected AbstractQueryBuilder(DnfBuilder dnfBuilder) { + this.dnfBuilder = dnfBuilder; + } + + protected abstract T self(); + + public NodeVariable parameter() { + return dnfBuilder.parameter(); + } + + public NodeVariable parameter(String name) { + return dnfBuilder.parameter(name); + } + + public NodeVariable parameter(ParameterDirection direction) { + return dnfBuilder.parameter(direction); + } + + public NodeVariable parameter(String name, ParameterDirection direction) { + return dnfBuilder.parameter(name, direction); + } + + public T parameter(NodeVariable variable) { + dnfBuilder.parameter(variable); + return self(); + } + + public T parameter(NodeVariable variable, ParameterDirection direction) { + dnfBuilder.parameter(variable, direction); + return self(); + } + + public T parameters(NodeVariable... variables) { + dnfBuilder.parameters(variables); + return self(); + } + + public T parameters(List variables) { + dnfBuilder.parameters(variables); + return self(); + } + + public T parameters(List variables, ParameterDirection direction) { + dnfBuilder.parameters(variables, direction); + return self(); + } + + public T symbolicParameters(List parameters) { + dnfBuilder.symbolicParameters(parameters); + return self(); + } + + public T functionalDependencies(Collection> functionalDependencies) { + dnfBuilder.functionalDependencies(functionalDependencies); + return self(); + } + + public T functionalDependency(FunctionalDependency functionalDependency) { + dnfBuilder.functionalDependency(functionalDependency); + return self(); + } + + public T functionalDependency(Set forEach, Set unique) { + dnfBuilder.functionalDependency(forEach, unique); + return self(); + } + + public T clause(ClauseCallback0 callback) { + dnfBuilder.clause(callback); + return self(); + } + + public T clause(ClauseCallback1Data0 callback) { + dnfBuilder.clause(callback); + return self(); + } + + public T clause(Class type1, ClauseCallback1Data1 callback) { + dnfBuilder.clause(type1, callback); + return self(); + } + + public T clause(ClauseCallback2Data0 callback) { + dnfBuilder.clause(callback); + return self(); + } + + public T clause(Class type1, ClauseCallback2Data1 callback) { + dnfBuilder.clause(type1, callback); + return self(); + } + + public T clause(Class type1, Class type2, ClauseCallback2Data2 callback) { + dnfBuilder.clause(type1, type2, callback); + return self(); + } + + public T clause(ClauseCallback3Data0 callback) { + dnfBuilder.clause(callback); + return self(); + } + + public T clause(Class type1, ClauseCallback3Data1 callback) { + dnfBuilder.clause(type1, callback); + return self(); + } + + public T clause(Class type1, Class type2, ClauseCallback3Data2 callback) { + dnfBuilder.clause(type1, type2, callback); + return self(); + } + + public T clause(Class type1, Class type2, Class type3, + ClauseCallback3Data3 callback) { + dnfBuilder.clause(type1, type2, type3, callback); + return self(); + } + + public T clause(ClauseCallback4Data0 callback) { + dnfBuilder.clause(callback); + return self(); + } + + public T clause(Class type1, ClauseCallback4Data1 callback) { + dnfBuilder.clause(type1, callback); + return self(); + } + + public T clause(Class type1, Class type2, ClauseCallback4Data2 callback) { + dnfBuilder.clause(type1, type2, callback); + return self(); + } + + public T clause(Class type1, Class type2, Class type3, + ClauseCallback4Data3 callback) { + dnfBuilder.clause(type1, type2, type3, callback); + return self(); + } + + public T clause(Class type1, Class type2, Class type3, Class type4, + ClauseCallback4Data4 callback) { + dnfBuilder.clause(type1, type2, type3, type4, callback); + return self(); + } + + public T clause(Literal... literals) { + dnfBuilder.clause(literals); + return self(); + } + + public T clause(Collection literals) { + dnfBuilder.clause(literals); + return self(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +public sealed interface AnyQuery permits Query { + String name(); + + int arity(); + + Class valueType(); + + Dnf getDnf(); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import org.jetbrains.annotations.NotNull; +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.literal.*; +import tools.refinery.logic.substitution.MapBasedSubstitution; +import tools.refinery.logic.substitution.StatelessSubstitution; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.*; +import java.util.function.Function; + +class ClausePostProcessor { + private final Map parameters; + private final List literals; + private final Map representatives = new LinkedHashMap<>(); + private final Map> equivalencePartition = new HashMap<>(); + private List substitutedLiterals; + private final Set existentiallyQuantifiedVariables = new LinkedHashSet<>(); + private Set positiveVariables; + private Map> variableToLiteralInputMap; + private PriorityQueue literalsWithAllInputsBound; + private LinkedHashSet topologicallySortedLiterals; + + public ClausePostProcessor(Map parameters, List literals) { + this.parameters = parameters; + this.literals = literals; + } + + public Result postProcessClause() { + mergeEquivalentNodeVariables(); + substitutedLiterals = new ArrayList<>(literals.size()); + keepParameterEquivalences(); + substituteLiterals(); + computeExistentiallyQuantifiedVariables(); + computePositiveVariables(); + validatePositiveRepresentatives(); + validatePrivateVariables(); + topologicallySortLiterals(); + var filteredLiterals = new ArrayList(topologicallySortedLiterals.size()); + for (var literal : topologicallySortedLiterals) { + var reducedLiteral = literal.reduce(); + if (BooleanLiteral.FALSE.equals(reducedLiteral)) { + return ConstantResult.ALWAYS_FALSE; + } else if (!BooleanLiteral.TRUE.equals(reducedLiteral)) { + filteredLiterals.add(reducedLiteral); + } + } + if (filteredLiterals.isEmpty()) { + return ConstantResult.ALWAYS_TRUE; + } + if (hasContradictoryCall(filteredLiterals)) { + return ConstantResult.ALWAYS_FALSE; + } + var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables), + Collections.unmodifiableList(filteredLiterals)); + return new ClauseResult(clause); + } + + private void mergeEquivalentNodeVariables() { + for (var literal : literals) { + if (isPositiveEquivalence(literal)) { + var equivalenceLiteral = (EquivalenceLiteral) literal; + mergeVariables(equivalenceLiteral.getLeft(), equivalenceLiteral.getRight()); + } + } + } + + private static boolean isPositiveEquivalence(Literal literal) { + return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.isPositive(); + } + + private void mergeVariables(Variable left, Variable right) { + var leftRepresentative = getRepresentative(left); + var rightRepresentative = getRepresentative(right); + var leftInfo = parameters.get(leftRepresentative); + var rightInfo = parameters.get(rightRepresentative); + if (leftInfo != null && (rightInfo == null || leftInfo.index() <= rightInfo.index())) { + // Prefer the variable occurring earlier in the parameter list as a representative. + doMergeVariables(leftRepresentative, rightRepresentative); + } else { + doMergeVariables(rightRepresentative, leftRepresentative); + } + } + + private void doMergeVariables(Variable parentRepresentative, Variable newChildRepresentative) { + var parentSet = getEquivalentVariables(parentRepresentative); + var childSet = getEquivalentVariables(newChildRepresentative); + parentSet.addAll(childSet); + equivalencePartition.remove(newChildRepresentative); + for (var childEquivalentNodeVariable : childSet) { + representatives.put(childEquivalentNodeVariable, parentRepresentative); + } + } + + private Variable getRepresentative(Variable variable) { + return representatives.computeIfAbsent(variable, Function.identity()); + } + + private Set getEquivalentVariables(Variable variable) { + var representative = getRepresentative(variable); + if (!representative.equals(variable)) { + throw new AssertionError("NodeVariable %s already has a representative %s" + .formatted(variable, representative)); + } + return equivalencePartition.computeIfAbsent(variable, key -> { + var set = HashSet.newHashSet(1); + set.add(key); + return set; + }); + } + + private void keepParameterEquivalences() { + for (var pair : representatives.entrySet()) { + var left = pair.getKey(); + var right = pair.getValue(); + if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) { + substitutedLiterals.add(new EquivalenceLiteral(true, left, right)); + } + } + } + + private void substituteLiterals() { + Substitution substitution; + if (representatives.isEmpty()) { + substitution = null; + } else { + substitution = new MapBasedSubstitution(Collections.unmodifiableMap(representatives), + StatelessSubstitution.IDENTITY); + } + for (var literal : literals) { + if (isPositiveEquivalence(literal)) { + // We already retained all equivalences that cannot be replaced with substitutions in + // {@link#keepParameterEquivalences()}. + continue; + } + var substitutedLiteral = substitution == null ? literal : literal.substitute(substitution); + substitutedLiterals.add(substitutedLiteral); + } + } + + private void computeExistentiallyQuantifiedVariables() { + for (var literal : substitutedLiterals) { + existentiallyQuantifiedVariables.addAll(literal.getOutputVariables()); + } + } + + private void computePositiveVariables() { + positiveVariables = new LinkedHashSet<>(); + for (var pair : parameters.entrySet()) { + var variable = pair.getKey(); + if (pair.getValue().direction() == ParameterDirection.IN) { + // Inputs count as positive, because they are already bound when we evaluate literals. + positiveVariables.add(variable); + } else if (!existentiallyQuantifiedVariables.contains(variable)) { + throw new InvalidQueryException("Unbound %s parameter %s" + .formatted(ParameterDirection.OUT, variable)); + } + } + positiveVariables.addAll(existentiallyQuantifiedVariables); + } + + private void validatePositiveRepresentatives() { + for (var pair : equivalencePartition.entrySet()) { + var representative = pair.getKey(); + if (!positiveVariables.contains(representative)) { + var variableSet = pair.getValue(); + throw new InvalidQueryException("Variables %s were merged by equivalence but are not bound" + .formatted(variableSet)); + } + } + } + + private void validatePrivateVariables() { + var negativeVariablesMap = new HashMap(); + for (var literal : substitutedLiterals) { + for (var variable : literal.getPrivateVariables(positiveVariables)) { + var oldLiteral = negativeVariablesMap.put(variable, literal); + if (oldLiteral != null) { + throw new InvalidQueryException("Unbound variable %s appears in multiple literals %s and %s" + .formatted(variable, oldLiteral, literal)); + } + } + } + } + + private void topologicallySortLiterals() { + topologicallySortedLiterals = LinkedHashSet.newLinkedHashSet(substitutedLiterals.size()); + variableToLiteralInputMap = new HashMap<>(); + literalsWithAllInputsBound = new PriorityQueue<>(); + int size = substitutedLiterals.size(); + for (int i = 0; i < size; i++) { + var literal = substitutedLiterals.get(i); + var sortableLiteral = new SortableLiteral(i, literal); + sortableLiteral.enqueue(); + } + while (!literalsWithAllInputsBound.isEmpty()) { + var variable = literalsWithAllInputsBound.remove(); + variable.addToSortedLiterals(); + } + if (!variableToLiteralInputMap.isEmpty()) { + throw new InvalidQueryException("Unbound input variables %s" + .formatted(variableToLiteralInputMap.keySet())); + } + } + + private boolean hasContradictoryCall(Collection filteredLiterals) { + var positiveCalls = new HashMap>(); + for (var literal : filteredLiterals) { + if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.POSITIVE) { + var callsOfTarget = positiveCalls.computeIfAbsent(callLiteral.getTarget(), key -> new HashSet<>()); + callsOfTarget.add(callLiteral); + } + } + for (var literal : filteredLiterals) { + if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.NEGATIVE) { + var callsOfTarget = positiveCalls.get(callLiteral.getTarget()); + if (contradicts(callLiteral, callsOfTarget)) { + return true; + } + } + } + return false; + } + + private boolean contradicts(CallLiteral negativeCall, Collection positiveCalls) { + if (positiveCalls == null) { + return false; + } + for (var positiveCall : positiveCalls) { + if (contradicts(negativeCall, positiveCall)) { + return true; + } + } + return false; + } + + private boolean contradicts(CallLiteral negativeCall, CallLiteral positiveCall) { + var privateVariables = negativeCall.getPrivateVariables(positiveVariables); + var negativeArguments = negativeCall.getArguments(); + var positiveArguments = positiveCall.getArguments(); + int arity = negativeArguments.size(); + for (int i = 0; i < arity; i++) { + var negativeArgument = negativeArguments.get(i); + if (privateVariables.contains(negativeArgument)) { + continue; + } + var positiveArgument = positiveArguments.get(i); + if (!negativeArgument.equals(positiveArgument)) { + return false; + } + } + return true; + } + + private class SortableLiteral implements Comparable { + private final int index; + private final Literal literal; + private final Set remainingInputs; + + private SortableLiteral(int index, Literal literal) { + this.index = index; + this.literal = literal; + remainingInputs = new HashSet<>(literal.getInputVariables(positiveVariables)); + for (var pair : parameters.entrySet()) { + if (pair.getValue().direction() == ParameterDirection.IN) { + remainingInputs.remove(pair.getKey()); + } + } + } + + public void enqueue() { + if (allInputsBound()) { + addToAllInputsBoundQueue(); + } else { + addToVariableToLiteralInputMap(); + } + } + + private void bindVariable(Variable input) { + if (!remainingInputs.remove(input)) { + throw new AssertionError("Already processed input %s of literal %s".formatted(input, literal)); + } + if (allInputsBound()) { + addToAllInputsBoundQueue(); + } + } + + private boolean allInputsBound() { + return remainingInputs.isEmpty(); + } + + private void addToVariableToLiteralInputMap() { + for (var inputVariable : remainingInputs) { + var literalSetForInput = variableToLiteralInputMap.computeIfAbsent( + inputVariable, key -> new HashSet<>()); + literalSetForInput.add(this); + } + } + + private void addToAllInputsBoundQueue() { + literalsWithAllInputsBound.add(this); + } + + public void addToSortedLiterals() { + if (!allInputsBound()) { + throw new AssertionError("Inputs %s of %s are not yet bound".formatted(remainingInputs, literal)); + } + // Add literal if we haven't yet added a duplicate of this literal. + topologicallySortedLiterals.add(literal); + for (var variable : literal.getOutputVariables()) { + var literalSetForInput = variableToLiteralInputMap.remove(variable); + if (literalSetForInput == null) { + continue; + } + for (var targetSortableLiteral : literalSetForInput) { + targetSortableLiteral.bindVariable(variable); + } + } + } + + @Override + public int compareTo(@NotNull ClausePostProcessor.SortableLiteral other) { + return Integer.compare(index, other.index); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SortableLiteral that = (SortableLiteral) o; + return index == that.index && Objects.equals(literal, that.literal); + } + + @Override + public int hashCode() { + return Objects.hash(index, literal); + } + } + + public sealed interface Result permits ClauseResult, ConstantResult { + } + + public record ClauseResult(DnfClause clause) implements Result { + } + + public enum ConstantResult implements Result { + ALWAYS_TRUE, + ALWAYS_FALSE + } + + public record ParameterInfo(ParameterDirection direction, int index) { + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.DnfEqualityChecker; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.SubstitutingLiteralEqualityHelper; +import tools.refinery.logic.equality.SubstitutingLiteralHashCodeHelper; +import tools.refinery.logic.literal.Reduction; +import tools.refinery.logic.term.Parameter; +import tools.refinery.logic.term.Variable; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public final class Dnf implements Constraint { + private static final String INDENTATION = " "; + + private final String name; + private final String uniqueName; + private final List symbolicParameters; + private final List> functionalDependencies; + private final List clauses; + + Dnf(String name, List symbolicParameters, + List> functionalDependencies, List clauses) { + validateFunctionalDependencies(symbolicParameters, functionalDependencies); + this.name = name; + this.uniqueName = DnfUtils.generateUniqueName(name); + this.symbolicParameters = symbolicParameters; + this.functionalDependencies = functionalDependencies; + this.clauses = clauses; + } + + private static void validateFunctionalDependencies( + Collection symbolicParameters, + Collection> functionalDependencies) { + var parameterSet = symbolicParameters.stream().map(SymbolicParameter::getVariable).collect(Collectors.toSet()); + for (var functionalDependency : functionalDependencies) { + validateParameters(symbolicParameters, parameterSet, functionalDependency.forEach(), functionalDependency); + validateParameters(symbolicParameters, parameterSet, functionalDependency.unique(), functionalDependency); + } + } + + private static void validateParameters(Collection symbolicParameters, + Set parameterSet, Collection toValidate, + FunctionalDependency functionalDependency) { + for (var variable : toValidate) { + if (!parameterSet.contains(variable)) { + throw new InvalidQueryException( + "Variable %s of functional dependency %s does not appear in the parameter list %s" + .formatted(variable, functionalDependency, symbolicParameters)); + } + } + } + + @Override + public String name() { + return name == null ? uniqueName : name; + } + + public boolean isExplicitlyNamed() { + return name != null; + } + + public String getUniqueName() { + return uniqueName; + } + + public List getSymbolicParameters() { + return symbolicParameters; + } + + public List getParameters() { + return Collections.unmodifiableList(symbolicParameters); + } + + public List> getFunctionalDependencies() { + return functionalDependencies; + } + + @Override + public int arity() { + return symbolicParameters.size(); + } + + public List getClauses() { + return clauses; + } + + public RelationalQuery asRelation() { + return new RelationalQuery(this); + } + + public FunctionalQuery asFunction(Class type) { + return new FunctionalQuery<>(this, type); + } + + @Override + public Reduction getReduction() { + if (clauses.isEmpty()) { + return Reduction.ALWAYS_FALSE; + } + for (var clause : clauses) { + if (clause.literals().isEmpty()) { + return Reduction.ALWAYS_TRUE; + } + } + return Reduction.NOT_REDUCIBLE; + } + + public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) { + if (arity() != other.arity()) { + return false; + } + for (int i = 0; i < arity(); i++) { + if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) { + return false; + } + } + int numClauses = clauses.size(); + if (numClauses != other.clauses.size()) { + return false; + } + for (int i = 0; i < numClauses; i++) { + var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(callEqualityChecker, symbolicParameters, + other.symbolicParameters); + if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { + return false; + } + } + return true; + } + + @Override + public boolean equals(LiteralEqualityHelper helper, Constraint other) { + if (other instanceof Dnf otherDnf) { + return helper.dnfEqual(this, otherDnf); + } + return false; + } + + public int hashCodeWithSubstitution() { + var helper = new SubstitutingLiteralHashCodeHelper(); + int result = 0; + for (var symbolicParameter : symbolicParameters) { + result = result * 31 + symbolicParameter.hashCodeWithSubstitution(helper); + } + for (var clause : clauses) { + result = result * 31 + clause.hashCodeWithSubstitution(helper); + } + return result; + } + + @Override + public String toString() { + return "%s/%d".formatted(name(), arity()); + } + + @Override + public String toReferenceString() { + return "@Dnf " + name(); + } + + public String toDefinitionString() { + var builder = new StringBuilder(); + builder.append("pred ").append(name()).append("("); + var parameterIterator = symbolicParameters.iterator(); + if (parameterIterator.hasNext()) { + builder.append(parameterIterator.next()); + while (parameterIterator.hasNext()) { + builder.append(", ").append(parameterIterator.next()); + } + } + builder.append(") <->"); + var clauseIterator = clauses.iterator(); + if (clauseIterator.hasNext()) { + appendClause(clauseIterator.next(), builder); + while (clauseIterator.hasNext()) { + builder.append("\n;"); + appendClause(clauseIterator.next(), builder); + } + } else { + builder.append("\n").append(INDENTATION).append(""); + } + builder.append(".\n"); + return builder.toString(); + } + + private static void appendClause(DnfClause clause, StringBuilder builder) { + var iterator = clause.literals().iterator(); + if (!iterator.hasNext()) { + builder.append("\n").append(INDENTATION).append(""); + return; + } + builder.append("\n").append(INDENTATION).append(iterator.next()); + while (iterator.hasNext()) { + builder.append(",\n").append(INDENTATION).append(iterator.next()); + } + } + + public static DnfBuilder builder() { + return builder(null); + } + + public static DnfBuilder builder(String name) { + return new DnfBuilder(name); + } + + public static DnfBuilder builderFrom(Dnf original) { + var builder = builder(original.name()); + builder.symbolicParameters(original.getSymbolicParameters()); + builder.functionalDependencies(original.getFunctionalDependencies()); + return builder; + } + + public static Dnf of(Consumer callback) { + return of(null, callback); + } + + public static Dnf of(String name, Consumer callback) { + var builder = builder(name); + callback.accept(builder); + return builder.build(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.dnf.callback.*; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.*; + +import java.util.*; + +@SuppressWarnings("UnusedReturnValue") +public final class DnfBuilder { + private final String name; + private final Set parameterVariables = new LinkedHashSet<>(); + private final List parameters = new ArrayList<>(); + private final List> functionalDependencies = new ArrayList<>(); + private final List> clauses = new ArrayList<>(); + + DnfBuilder(String name) { + this.name = name; + } + + public NodeVariable parameter() { + return parameter((String) null); + } + + public NodeVariable parameter(String name) { + return parameter(name, ParameterDirection.OUT); + } + + public NodeVariable parameter(ParameterDirection direction) { + return parameter((String) null, direction); + } + + public NodeVariable parameter(String name, ParameterDirection direction) { + var variable = Variable.of(name); + parameter(variable, direction); + return variable; + } + + public DataVariable parameter(Class type) { + return parameter(null, type); + } + + public DataVariable parameter(String name, Class type) { + return parameter(name, type, ParameterDirection.OUT); + } + + public DataVariable parameter(Class type, ParameterDirection direction) { + return parameter(null, type, direction); + } + + public DataVariable parameter(String name, Class type, ParameterDirection direction) { + var variable = Variable.of(name, type); + parameter(variable, direction); + return variable; + } + + public Variable parameter(Parameter parameter) { + return parameter(null, parameter); + } + + public Variable parameter(String name, Parameter parameter) { + var type = parameter.tryGetType(); + if (type.isPresent()) { + return parameter(name, type.get(), parameter.getDirection()); + } + return parameter(name, parameter.getDirection()); + } + + public DnfBuilder parameter(Variable variable) { + return parameter(variable, ParameterDirection.OUT); + } + + public DnfBuilder parameter(Variable variable, ParameterDirection direction) { + return symbolicParameter(new SymbolicParameter(variable, direction)); + } + + public DnfBuilder parameters(Variable... variables) { + return parameters(List.of(variables)); + } + + public DnfBuilder parameters(Collection variables) { + return parameters(variables, ParameterDirection.OUT); + } + + public DnfBuilder parameters(Collection variables, ParameterDirection direction) { + for (var variable : variables) { + parameter(variable, direction); + } + return this; + } + + public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) { + var variable = symbolicParameter.getVariable(); + if (!parameterVariables.add(variable)) { + throw new InvalidQueryException("Variable %s is already on the parameter list %s" + .formatted(variable, parameters)); + } + parameters.add(symbolicParameter); + return this; + } + + public DnfBuilder symbolicParameters(SymbolicParameter... symbolicParameters) { + return symbolicParameters(List.of(symbolicParameters)); + } + + public DnfBuilder symbolicParameters(Collection symbolicParameters) { + for (var symbolicParameter : symbolicParameters) { + symbolicParameter(symbolicParameter); + } + return this; + } + + public DnfBuilder functionalDependencies(Collection> functionalDependencies) { + this.functionalDependencies.addAll(functionalDependencies); + return this; + } + + public DnfBuilder functionalDependency(FunctionalDependency functionalDependency) { + functionalDependencies.add(functionalDependency); + return this; + } + + public DnfBuilder functionalDependency(Set forEach, Set unique) { + return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique))); + } + + public DnfBuilder clause(ClauseCallback0 callback) { + return clause(callback.toLiterals()); + } + + public DnfBuilder clause(ClauseCallback1Data0 callback) { + return clause(callback.toLiterals(Variable.of("v1"))); + } + + public DnfBuilder clause(Class type1, ClauseCallback1Data1 callback) { + return clause(callback.toLiterals(Variable.of("d1", type1))); + } + + public DnfBuilder clause(ClauseCallback2Data0 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"))); + } + + public DnfBuilder clause(Class type1, ClauseCallback2Data1 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1))); + } + + public DnfBuilder clause(Class type1, Class type2, ClauseCallback2Data2 callback) { + return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2))); + } + + public DnfBuilder clause(ClauseCallback3Data0 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"))); + } + + public DnfBuilder clause(Class type1, ClauseCallback3Data1 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1))); + } + + public DnfBuilder clause(Class type1, Class type2, ClauseCallback3Data2 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2))); + } + + public DnfBuilder clause(Class type1, Class type2, Class type3, + ClauseCallback3Data3 callback) { + return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2), + Variable.of("d3", type3))); + } + + public DnfBuilder clause(ClauseCallback4Data0 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("v4"))); + } + + public DnfBuilder clause(Class type1, ClauseCallback4Data1 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("d1", + type1))); + } + + public DnfBuilder clause(Class type1, Class type2, ClauseCallback4Data2 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1), + Variable.of("d2", type2))); + } + + public DnfBuilder clause(Class type1, Class type2, Class type3, + ClauseCallback4Data3 callback) { + return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2), + Variable.of("d3", type3))); + } + + public DnfBuilder clause(Class type1, Class type2, Class type3, Class type4, + ClauseCallback4Data4 callback) { + return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2), + Variable.of("d3", type3), Variable.of("d4", type4))); + } + + public DnfBuilder clause(Literal... literals) { + clause(List.of(literals)); + return this; + } + + public DnfBuilder clause(Collection literals) { + clauses.add(List.copyOf(literals)); + return this; + } + + void output(DataVariable outputVariable) { + // Copy parameter variables to exclude the newly added {@code outputVariable}. + var fromParameters = Set.copyOf(parameterVariables); + parameter(outputVariable, ParameterDirection.OUT); + functionalDependency(fromParameters, Set.of(outputVariable)); + } + + public Dnf build() { + var postProcessor = new DnfPostProcessor(parameters, clauses); + var postProcessedClauses = postProcessor.postProcessClauses(); + return new Dnf(name, Collections.unmodifiableList(parameters), + Collections.unmodifiableList(functionalDependencies), + Collections.unmodifiableList(postProcessedClauses)); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.Variable; + +import java.util.List; +import java.util.Set; + +public record DnfClause(Set positiveVariables, List literals) { + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) { + int size = literals.size(); + if (size != other.literals.size()) { + return false; + } + for (int i = 0; i < size; i++) { + if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) { + return false; + } + } + return true; + } + + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + int result = 0; + for (var literal : literals) { + result = result * 31 + literal.hashCodeWithSubstitution(helper); + } + return result; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.DnfEqualityChecker; +import tools.refinery.logic.equality.SubstitutingLiteralEqualityHelper; +import tools.refinery.logic.equality.SubstitutingLiteralHashCodeHelper; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.*; + +class DnfPostProcessor { + private final List parameters; + private final List> clauses; + + public DnfPostProcessor(List parameters, List> clauses) { + this.parameters = parameters; + this.clauses = clauses; + } + + public List postProcessClauses() { + var parameterInfoMap = getParameterInfoMap(); + var postProcessedClauses = LinkedHashSet.newLinkedHashSet(clauses.size()); + int index = 0; + for (var literals : clauses) { + var postProcessor = new ClausePostProcessor(parameterInfoMap, literals); + ClausePostProcessor.Result result; + try { + result = postProcessor.postProcessClause(); + } catch (InvalidQueryException e) { + throw new InvalidClauseException(index, e); + } + if (result instanceof ClausePostProcessor.ClauseResult clauseResult) { + postProcessedClauses.add(new CanonicalClause(clauseResult.clause())); + } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) { + switch (constantResult) { + case ALWAYS_TRUE -> { + var inputVariables = getInputVariables(); + return List.of(new DnfClause(inputVariables, List.of())); + } + case ALWAYS_FALSE -> { + // Skip this clause because it can never match. + } + default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " + + constantResult); + } + } else { + throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result); + } + index++; + } + return postProcessedClauses.stream().map(CanonicalClause::getDnfClause).toList(); + } + + private Map getParameterInfoMap() { + var mutableParameterInfoMap = new LinkedHashMap(); + int arity = parameters.size(); + for (int i = 0; i < arity; i++) { + var parameter = parameters.get(i); + mutableParameterInfoMap.put(parameter.getVariable(), + new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i)); + } + return Collections.unmodifiableMap(mutableParameterInfoMap); + } + + private Set getInputVariables() { + var inputParameters = new LinkedHashSet(); + for (var parameter : parameters) { + if (parameter.getDirection() == ParameterDirection.IN) { + inputParameters.add(parameter.getVariable()); + } + } + return Collections.unmodifiableSet(inputParameters); + } + + private class CanonicalClause { + private final DnfClause dnfClause; + + public CanonicalClause(DnfClause dnfClause) { + this.dnfClause = dnfClause; + } + + public DnfClause getDnfClause() { + return dnfClause; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + var otherCanonicalClause = (CanonicalClause) obj; + var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, parameters, parameters); + return dnfClause.equalsWithSubstitution(helper, otherCanonicalClause.dnfClause); + } + + @Override + public int hashCode() { + var helper = new SubstitutingLiteralHashCodeHelper(parameters); + return dnfClause.hashCodeWithSubstitution(helper); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import java.util.UUID; + +public final class DnfUtils { + private DnfUtils() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static String generateUniqueName(String originalName) { + UUID uuid = UUID.randomUUID(); + String uniqueString = "_" + uuid.toString().replace('-', '_'); + if (originalName == null) { + return uniqueString; + } else { + return originalName + uniqueString; + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.InvalidQueryException; + +import java.util.HashSet; +import java.util.Set; + +public record FunctionalDependency(Set forEach, Set unique) { + public FunctionalDependency { + var uniqueForEach = new HashSet<>(unique); + uniqueForEach.retainAll(forEach); + if (!uniqueForEach.isEmpty()) { + throw new InvalidQueryException("Variables %s appear on both sides of the functional dependency" + .formatted(uniqueForEach)); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.literal.CallPolarity; +import tools.refinery.logic.term.Aggregator; +import tools.refinery.logic.term.AssignedValue; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.Variable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public final class FunctionalQuery extends Query { + private final Class type; + + FunctionalQuery(Dnf dnf, Class type) { + super(dnf); + var parameters = dnf.getSymbolicParameters(); + int outputIndex = dnf.arity() - 1; + for (int i = 0; i < outputIndex; i++) { + var parameter = parameters.get(i); + var parameterType = parameter.tryGetType(); + if (parameterType.isPresent()) { + throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead" + .formatted(parameter, dnf, parameterType.get().getName())); + } + } + var outputParameter = parameters.get(outputIndex); + var outputParameterType = outputParameter.tryGetType(); + if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) { + throw new InvalidQueryException("Expected parameter %s of %s to be %s, but got %s instead".formatted( + outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); + } + this.type = type; + } + + @Override + public int arity() { + return getDnf().arity() - 1; + } + + @Override + public Class valueType() { + return type; + } + + @Override + public T defaultValue() { + return null; + } + + @Override + protected FunctionalQuery withDnfInternal(Dnf newDnf) { + return newDnf.asFunction(type); + } + + @Override + public FunctionalQuery withDnf(Dnf newDnf) { + return (FunctionalQuery) super.withDnf(newDnf); + } + + public AssignedValue call(List arguments) { + return targetVariable -> { + var argumentsWithTarget = new ArrayList(arguments.size() + 1); + argumentsWithTarget.addAll(arguments); + argumentsWithTarget.add(targetVariable); + return getDnf().call(CallPolarity.POSITIVE, argumentsWithTarget); + }; + } + + public AssignedValue call(NodeVariable... arguments) { + return call(List.of(arguments)); + } + + public AssignedValue aggregate(Aggregator aggregator, List arguments) { + return targetVariable -> { + var placeholderVariable = Variable.of(type); + var argumentsWithPlaceholder = new ArrayList(arguments.size() + 1); + argumentsWithPlaceholder.addAll(arguments); + argumentsWithPlaceholder.add(placeholderVariable); + return getDnf() + .aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder) + .toLiteral(targetVariable); + }; + } + + public AssignedValue aggregate(Aggregator aggregator, NodeVariable... arguments) { + return aggregate(aggregator, List.of(arguments)); + } + + public AssignedValue leftJoin(T defaultValue, List arguments) { + return targetVariable -> { + var placeholderVariable = Variable.of(type); + var argumentsWithPlaceholder = new ArrayList(arguments.size() + 1); + argumentsWithPlaceholder.addAll(arguments); + argumentsWithPlaceholder.add(placeholderVariable); + return getDnf() + .leftJoinBy(placeholderVariable, defaultValue, argumentsWithPlaceholder) + .toLiteral(targetVariable); + }; + } + + public AssignedValue leftJoin(T defaultValue, NodeVariable... arguments) { + return leftJoin(defaultValue, List.of(arguments)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + FunctionalQuery that = (FunctionalQuery) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), type); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.term.DataVariable; + +public final class FunctionalQueryBuilder extends AbstractQueryBuilder> { + private final DataVariable outputVariable; + private final Class type; + + FunctionalQueryBuilder(DataVariable outputVariable, DnfBuilder dnfBuilder, Class type) { + super(dnfBuilder); + this.outputVariable = outputVariable; + this.type = type; + } + + @Override + protected FunctionalQueryBuilder self() { + return this; + } + + public FunctionalQuery build() { + dnfBuilder.output(outputVariable); + return dnfBuilder.build().asFunction(type); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.InvalidQueryException; + +public class InvalidClauseException extends InvalidQueryException { + private final int clauseIndex; + + public InvalidClauseException(int clauseIndex) { + this.clauseIndex = clauseIndex; + } + + public InvalidClauseException(int clauseIndex, String message) { + super(message); + this.clauseIndex = clauseIndex; + } + + public InvalidClauseException(int clauseIndex, String message, Throwable cause) { + super(message, cause); + this.clauseIndex = clauseIndex; + } + + public InvalidClauseException(int clauseIndex, Throwable cause) { + super(cause); + this.clauseIndex = clauseIndex; + } + + public int getClauseIndex() { + return clauseIndex; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.dnf.callback.*; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.Objects; + +public abstract sealed class Query implements AnyQuery permits FunctionalQuery, RelationalQuery { + private static final String OUTPUT_VARIABLE_NAME = "output"; + + private final Dnf dnf; + + protected Query(Dnf dnf) { + for (var parameter : dnf.getSymbolicParameters()) { + if (parameter.getDirection() != ParameterDirection.OUT) { + throw new IllegalArgumentException("Query parameter %s with direction %s is not allowed" + .formatted(parameter.getVariable(), parameter.getDirection())); + } + } + this.dnf = dnf; + } + + @Override + public String name() { + return dnf.name(); + } + + @Override + public Dnf getDnf() { + return dnf; + } + + // Allow redeclaration of the method with refined return type. + @SuppressWarnings("squid:S3038") + @Override + public abstract Class valueType(); + + public abstract T defaultValue(); + + public Query withDnf(Dnf newDnf) { + if (dnf.equals(newDnf)) { + return this; + } + int arity = dnf.arity(); + if (newDnf.arity() != arity) { + throw new IllegalArgumentException("Arity of %s and %s do not match".formatted(dnf, newDnf)); + } + var parameters = dnf.getParameters(); + var newParameters = newDnf.getParameters(); + for (int i = 0; i < arity; i++) { + var parameter = parameters.get(i); + var newParameter = newParameters.get(i); + if (!parameter.matches(newParameter)) { + throw new IllegalArgumentException("Parameter #%d mismatch: %s does not match %s" + .formatted(i, parameter, newParameter)); + } + } + return withDnfInternal(newDnf); + } + + protected abstract Query withDnfInternal(Dnf newDnf); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Query that = (Query) o; + return Objects.equals(dnf, that.dnf); + } + + @Override + public int hashCode() { + return Objects.hash(dnf); + } + + @Override + public String toString() { + return dnf.toString(); + } + + public static QueryBuilder builder() { + return builder(null); + } + + public static QueryBuilder builder(String name) { + return new QueryBuilder(name); + } + + public static RelationalQuery of(QueryCallback0 callback) { + return of(null, callback); + } + + public static RelationalQuery of(String name, QueryCallback0 callback) { + var builder = builder(name); + callback.accept(builder); + return builder.build(); + } + + public static RelationalQuery of(QueryCallback1 callback) { + return of(null, callback); + } + + public static RelationalQuery of(String name, QueryCallback1 callback) { + var builder = builder(name); + callback.accept(builder, builder.parameter("p1")); + return builder.build(); + } + + public static RelationalQuery of(QueryCallback2 callback) { + return of(null, callback); + } + + public static RelationalQuery of(String name, QueryCallback2 callback) { + var builder = builder(name); + callback.accept(builder, builder.parameter("p1"), builder.parameter("p2")); + return builder.build(); + } + + public static RelationalQuery of(QueryCallback3 callback) { + return of(null, callback); + } + + public static RelationalQuery of(String name, QueryCallback3 callback) { + var builder = builder(name); + callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3")); + return builder.build(); + } + + public static RelationalQuery of(QueryCallback4 callback) { + return of(null, callback); + } + + public static RelationalQuery of(String name, QueryCallback4 callback) { + var builder = builder(name); + callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), + builder.parameter("p4")); + return builder.build(); + } + + public static FunctionalQuery of(Class type, FunctionalQueryCallback0 callback) { + return of(null, type, callback); + } + + public static FunctionalQuery of(String name, Class type, FunctionalQueryCallback0 callback) { + var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); + var builder = builder(name).output(outputVariable); + callback.accept(builder, outputVariable); + return builder.build(); + } + + public static FunctionalQuery of(Class type, FunctionalQueryCallback1 callback) { + return of(null, type, callback); + } + + public static FunctionalQuery of(String name, Class type, FunctionalQueryCallback1 callback) { + var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); + var builder = builder(name).output(outputVariable); + callback.accept(builder, builder.parameter("p1"), outputVariable); + return builder.build(); + } + + public static FunctionalQuery of(Class type, FunctionalQueryCallback2 callback) { + return of(null, type, callback); + } + + public static FunctionalQuery of(String name, Class type, FunctionalQueryCallback2 callback) { + var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); + var builder = builder(name).output(outputVariable); + callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), outputVariable); + return builder.build(); + } + + public static FunctionalQuery of(Class type, FunctionalQueryCallback3 callback) { + return of(null, type, callback); + } + + public static FunctionalQuery of(String name, Class type, FunctionalQueryCallback3 callback) { + var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); + var builder = builder(name).output(outputVariable); + callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), + outputVariable); + return builder.build(); + } + + public static FunctionalQuery of(Class type, FunctionalQueryCallback4 callback) { + return of(null, type, callback); + } + + public static FunctionalQuery of(String name, Class type, FunctionalQueryCallback4 callback) { + var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); + var builder = builder(name).output(outputVariable); + callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), + builder.parameter("p4"), outputVariable); + return builder.build(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.term.DataVariable; + +public final class QueryBuilder extends AbstractQueryBuilder { + QueryBuilder(String name) { + super(Dnf.builder(name)); + } + + @Override + protected QueryBuilder self() { + return this; + } + + public FunctionalQueryBuilder output(DataVariable outputVariable) { + return new FunctionalQueryBuilder<>(outputVariable, dnfBuilder, outputVariable.getType()); + } + + public RelationalQuery build() { + return dnfBuilder.build().asRelation(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.literal.CallLiteral; +import tools.refinery.logic.literal.CallPolarity; +import tools.refinery.logic.term.AssignedValue; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collections; +import java.util.List; + +public final class RelationalQuery extends Query { + RelationalQuery(Dnf dnf) { + super(dnf); + for (var parameter : dnf.getSymbolicParameters()) { + var parameterType = parameter.tryGetType(); + if (parameterType.isPresent()) { + throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead" + .formatted(parameter, dnf, parameterType.get().getName())); + } + } + } + + @Override + public int arity() { + return getDnf().arity(); + } + + @Override + public Class valueType() { + return Boolean.class; + } + + @Override + public Boolean defaultValue() { + return false; + } + + @Override + protected RelationalQuery withDnfInternal(Dnf newDnf) { + return newDnf.asRelation(); + } + + @Override + public RelationalQuery withDnf(Dnf newDnf) { + return (RelationalQuery) super.withDnf(newDnf); + } + + public CallLiteral call(CallPolarity polarity, List arguments) { + return getDnf().call(polarity, Collections.unmodifiableList(arguments)); + } + + public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) { + return getDnf().call(polarity, arguments); + } + + public CallLiteral call(NodeVariable... arguments) { + return getDnf().call(arguments); + } + + public CallLiteral callTransitive(NodeVariable left, NodeVariable right) { + return getDnf().callTransitive(left, right); + } + + public AssignedValue count(List arguments) { + return getDnf().count(Collections.unmodifiableList(arguments)); + } + + public AssignedValue count(NodeVariable... arguments) { + return getDnf().count(arguments); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf; + +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.term.Parameter; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.Objects; + +public final class SymbolicParameter extends Parameter { + private final Variable variable; + + public SymbolicParameter(Variable variable, ParameterDirection direction) { + super(variable.tryGetType().orElse(null), direction); + this.variable = variable; + } + + public Variable getVariable() { + return variable; + } + + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCode(), helper.getVariableHashCode(variable)); + } + + @Override + public String toString() { + var direction = getDirection(); + if (direction == ParameterDirection.OUT) { + return variable.toString(); + } + return "%s %s".formatted(getDirection(), variable); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SymbolicParameter that = (SymbolicParameter) o; + return Objects.equals(variable, that.variable); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), variable); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback0 { + Collection toLiterals(); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback1Data0 { + Collection toLiterals(NodeVariable v1); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback1Data1 { + Collection toLiterals(DataVariable d1); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback2Data0 { + Collection toLiterals(NodeVariable v1, NodeVariable v2); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback2Data1 { + Collection toLiterals(NodeVariable v1, DataVariable x1); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback2Data2 { + Collection toLiterals(DataVariable x1, DataVariable x2); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback3Data0 { + Collection toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback3Data1 { + Collection toLiterals(NodeVariable v1, NodeVariable v2, DataVariable d1); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback3Data2 { + Collection toLiterals(NodeVariable v1, DataVariable d1, DataVariable d2); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback3Data3 { + Collection toLiterals(DataVariable d1, DataVariable d2, DataVariable d3); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback4Data0 { + Collection toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, NodeVariable v4); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback4Data1 { + Collection toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, DataVariable d1); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback4Data2 { + Collection toLiterals(NodeVariable v1, NodeVariable v2, DataVariable d1, DataVariable d2); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback4Data3 { + Collection toLiterals(NodeVariable v1, DataVariable d1, DataVariable d2, DataVariable d3); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.DataVariable; + +import java.util.Collection; + +@FunctionalInterface +public interface ClauseCallback4Data4 { + Collection toLiterals(DataVariable d1, DataVariable d2, DataVariable d3, DataVariable d4); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.FunctionalQueryBuilder; +import tools.refinery.logic.term.DataVariable; + +@FunctionalInterface +public interface FunctionalQueryCallback0 { + void accept(FunctionalQueryBuilder builder, DataVariable output); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.FunctionalQueryBuilder; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface FunctionalQueryCallback1 { + void accept(FunctionalQueryBuilder builder, NodeVariable p1, DataVariable output); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.FunctionalQueryBuilder; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface FunctionalQueryCallback2 { + void accept(FunctionalQueryBuilder builder, NodeVariable p1, NodeVariable p2, DataVariable output); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.FunctionalQueryBuilder; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface FunctionalQueryCallback3 { + void accept(FunctionalQueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, + DataVariable output); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.FunctionalQueryBuilder; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface FunctionalQueryCallback4 { + void accept(FunctionalQueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4, + DataVariable output); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.QueryBuilder; + +@FunctionalInterface +public interface QueryCallback0 { + void accept(QueryBuilder builder); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.QueryBuilder; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface QueryCallback1 { + void accept(QueryBuilder builder, NodeVariable p1); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.QueryBuilder; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface QueryCallback2 { + void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.QueryBuilder; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface QueryCallback3 { + void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.dnf.callback; + +import tools.refinery.logic.dnf.QueryBuilder; +import tools.refinery.logic.term.NodeVariable; + +@FunctionalInterface +public interface QueryCallback4 { + void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.equality; + +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.DnfClause; +import tools.refinery.logic.dnf.SymbolicParameter; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.util.CycleDetectingMapper; + +import java.util.List; + +public class DeepDnfEqualityChecker implements DnfEqualityChecker { + private final CycleDetectingMapper mapper = new CycleDetectingMapper<>(this::doCheckEqual); + + @Override + public boolean dnfEqual(Dnf left, Dnf right) { + return mapper.map(new Pair(left, right)); + } + + public boolean dnfEqualRaw(List symbolicParameters, + List> clauses, Dnf other) { + int arity = symbolicParameters.size(); + if (arity != other.arity()) { + return false; + } + for (int i = 0; i < arity; i++) { + if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) { + return false; + } + } + int numClauses = clauses.size(); + if (numClauses != other.getClauses().size()) { + return false; + } + for (int i = 0; i < numClauses; i++) { + var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(this, symbolicParameters, + other.getSymbolicParameters()); + if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) { + return false; + } + } + return true; + } + + private boolean equalsWithSubstitutionRaw(LiteralEqualityHelper helper, List literals, + DnfClause other) { + int size = literals.size(); + if (size != other.literals().size()) { + return false; + } + for (int i = 0; i < size; i++) { + if (!literals.get(i).equalsWithSubstitution(helper, other.literals().get(i))) { + return false; + } + } + return true; + } + + protected boolean doCheckEqual(Pair pair) { + return pair.left.equalsWithSubstitution(this, pair.right); + } + + protected List getInProgress() { + return mapper.getInProgress(); + } + + protected record Pair(Dnf left, Dnf right) { + @Override + public String toString() { + return "(%s, %s)".formatted(left.name(), right.name()); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.equality; + +import tools.refinery.logic.dnf.Dnf; + +import java.util.Objects; + +@FunctionalInterface +public interface DnfEqualityChecker { + DnfEqualityChecker DEFAULT = Objects::equals; + + boolean dnfEqual(Dnf left, Dnf right); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.equality; + +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.term.Variable; + +import java.util.Objects; + +public interface LiteralEqualityHelper extends DnfEqualityChecker { + LiteralEqualityHelper DEFAULT = new LiteralEqualityHelper() { + @Override + public boolean variableEqual(Variable left, Variable right) { + return Objects.equals(left, right); + } + + @Override + public boolean dnfEqual(Dnf left, Dnf right) { + return DnfEqualityChecker.DEFAULT.dnfEqual(left, right); + } + }; + + boolean variableEqual(Variable left, Variable right); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.equality; + +import tools.refinery.logic.term.Variable; + +import java.util.Objects; + +@FunctionalInterface +public interface LiteralHashCodeHelper { + LiteralHashCodeHelper DEFAULT = Objects::hashCode; + + int getVariableHashCode(Variable variable); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.equality; + +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.SymbolicParameter; +import tools.refinery.logic.term.Variable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SubstitutingLiteralEqualityHelper implements LiteralEqualityHelper { + private final DnfEqualityChecker dnfEqualityChecker; + private final Map leftToRight; + private final Map rightToLeft; + + public SubstitutingLiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, + List leftParameters, + List rightParameters) { + this.dnfEqualityChecker = dnfEqualityChecker; + var arity = leftParameters.size(); + if (arity != rightParameters.size()) { + throw new IllegalArgumentException("Parameter lists have unequal length"); + } + leftToRight = new HashMap<>(arity); + rightToLeft = new HashMap<>(arity); + for (int i = 0; i < arity; i++) { + if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) { + throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i); + } + } + } + + @Override + public boolean dnfEqual(Dnf left, Dnf right) { + return dnfEqualityChecker.dnfEqual(left, right); + } + + @Override + public boolean variableEqual(Variable left, Variable right) { + if (left.tryGetType().equals(right.tryGetType()) && + checkMapping(leftToRight, left, right) && + checkMapping(rightToLeft, right, left)) { + leftToRight.put(left, right); + rightToLeft.put(right, left); + return true; + } + return false; + } + + private static boolean checkMapping(Map map, Variable key, Variable expectedValue) { + var currentValue = map.get(key); + return currentValue == null || currentValue.equals(expectedValue); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.equality; + +import tools.refinery.logic.dnf.SymbolicParameter; +import tools.refinery.logic.term.Variable; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class SubstitutingLiteralHashCodeHelper implements LiteralHashCodeHelper { + private final Map assignedHashCodes = new LinkedHashMap<>(); + + // 0 is for {@code null}, so we start with 1. + private int next = 1; + + public SubstitutingLiteralHashCodeHelper() { + this(List.of()); + } + + public SubstitutingLiteralHashCodeHelper(List parameters) { + for (var parameter : parameters) { + getVariableHashCode(parameter.getVariable()); + } + } + + @Override + public int getVariableHashCode(Variable variable) { + if (variable == null) { + return 0; + } + return assignedHashCodes.computeIfAbsent(variable, key -> { + int sequenceNumber = next; + next++; + return variable.hashCodeWithSubstitution(sequenceNumber); + }); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.*; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public abstract class AbstractCallLiteral extends tools.refinery.logic.literal.AbstractLiteral { + private final Constraint target; + private final List arguments; + private final Set inArguments; + private final Set outArguments; + + // Use exhaustive switch over enums. + @SuppressWarnings("squid:S1301") + protected AbstractCallLiteral(Constraint target, List arguments) { + int arity = target.arity(); + if (arguments.size() != arity) { + throw new InvalidQueryException("%s needs %d arguments, but got %s".formatted(target.name(), + target.arity(), arguments.size())); + } + this.target = target; + this.arguments = arguments; + var mutableInArguments = new LinkedHashSet(); + var mutableOutArguments = new LinkedHashSet(); + var parameters = target.getParameters(); + for (int i = 0; i < arity; i++) { + var argument = arguments.get(i); + var parameter = parameters.get(i); + if (!parameter.isAssignable(argument)) { + throw new InvalidQueryException("Argument %d of %s is not assignable to parameter %s" + .formatted(i, target, parameter)); + } + switch (parameter.getDirection()) { + case IN -> { + mutableOutArguments.remove(argument); + mutableInArguments.add(argument); + } + case OUT -> { + if (!mutableInArguments.contains(argument)) { + mutableOutArguments.add(argument); + } + } + } + } + inArguments = Collections.unmodifiableSet(mutableInArguments); + outArguments = Collections.unmodifiableSet(mutableOutArguments); + } + + public Constraint getTarget() { + return target; + } + + public List getArguments() { + return arguments; + } + + protected Set getArgumentsOfDirection(ParameterDirection direction) { + return switch (direction) { + case IN -> inArguments; + case OUT -> outArguments; + }; + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + var inputVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT)); + inputVariables.retainAll(positiveVariablesInClause); + inputVariables.addAll(getArgumentsOfDirection(ParameterDirection.IN)); + return Collections.unmodifiableSet(inputVariables); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + var privateVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT)); + privateVariables.removeAll(positiveVariablesInClause); + return Collections.unmodifiableSet(privateVariables); + } + + @Override + public tools.refinery.logic.literal.Literal substitute(Substitution substitution) { + var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList(); + return doSubstitute(substitution, substitutedArguments); + } + + protected abstract tools.refinery.logic.literal.Literal doSubstitute(Substitution substitution, List substitutedArguments); + + public AbstractCallLiteral withTarget(Constraint newTarget) { + if (Objects.equals(target, newTarget)) { + return this; + } + return withArguments(newTarget, arguments); + } + + public abstract AbstractCallLiteral withArguments(Constraint newTarget, List newArguments); + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, tools.refinery.logic.literal.Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherCallLiteral = (AbstractCallLiteral) other; + var arity = arguments.size(); + if (arity != otherCallLiteral.arguments.size()) { + return false; + } + for (int i = 0; i < arity; i++) { + if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) { + return false; + } + } + return target.equals(helper, otherCallLiteral.target); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + int result = super.hashCodeWithSubstitution(helper) * 31 + target.hashCode(); + for (var argument : arguments) { + result = result * 31 + helper.getVariableHashCode(argument); + } + return result; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.term.ConstantTerm; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.Variable; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public abstract class AbstractCountLiteral extends AbstractCallLiteral { + private final Class resultType; + private final DataVariable resultVariable; + + protected AbstractCountLiteral(Class resultType, DataVariable resultVariable, Constraint target, + List arguments) { + super(target, arguments); + if (!resultVariable.getType().equals(resultType)) { + throw new InvalidQueryException("Count result variable %s must be of type %s, got %s instead".formatted( + resultVariable, resultType, resultVariable.getType().getName())); + } + if (arguments.contains(resultVariable)) { + throw new InvalidQueryException("Count result variable %s must not appear in the argument list" + .formatted(resultVariable)); + } + this.resultType = resultType; + this.resultVariable = resultVariable; + } + + public Class getResultType() { + return resultType; + } + + public DataVariable getResultVariable() { + return resultVariable; + } + + @Override + public Set getOutputVariables() { + return Set.of(resultVariable); + } + + protected abstract T zero(); + + protected abstract T one(); + + @Override + public Literal reduce() { + var reduction = getTarget().getReduction(); + return switch (reduction) { + case ALWAYS_FALSE -> getResultVariable().assign(new ConstantTerm<>(resultType, zero())); + // The only way a constant {@code true} predicate can be called in a negative position is to have all of + // its arguments bound as input variables. Thus, there will only be a single match. + case ALWAYS_TRUE -> getResultVariable().assign(new ConstantTerm<>(resultType, one())); + case NOT_REDUCIBLE -> this; + }; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherCountLiteral = (AbstractCountLiteral) other; + return Objects.equals(resultType, otherCountLiteral.resultType) && + helper.variableEqual(resultVariable, otherCountLiteral.resultVariable); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), resultType, + helper.getVariableHashCode(resultVariable)); + } + + protected abstract String operatorName(); + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append(resultVariable); + builder.append(" is "); + builder.append(operatorName()); + builder.append(' '); + builder.append(getTarget().toReferenceString()); + builder.append('('); + var argumentIterator = getArguments().iterator(); + if (argumentIterator.hasNext()) { + builder.append(argumentIterator.next()); + while (argumentIterator.hasNext()) { + builder.append(", ").append(argumentIterator.next()); + } + } + builder.append(')'); + return builder.toString(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; + +public abstract class AbstractLiteral implements Literal { + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + return other != null && getClass() == other.getClass(); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractLiteral that = (AbstractLiteral) o; + return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that); + } + + @Override + public int hashCode() { + return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.*; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public class AggregationLiteral extends AbstractCallLiteral { + private final DataVariable resultVariable; + private final DataVariable inputVariable; + private final Aggregator aggregator; + + public AggregationLiteral(DataVariable resultVariable, Aggregator aggregator, + DataVariable inputVariable, Constraint target, List arguments) { + super(target, arguments); + if (!inputVariable.getType().equals(aggregator.getInputType())) { + throw new InvalidQueryException("Input variable %s must of type %s, got %s instead".formatted( + inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); + } + if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { + throw new InvalidQueryException("Input variable %s must be bound with direction %s in the argument list" + .formatted(inputVariable, ParameterDirection.OUT)); + } + if (!resultVariable.getType().equals(aggregator.getResultType())) { + throw new InvalidQueryException("Result variable %s must of type %s, got %s instead".formatted( + resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); + } + if (arguments.contains(resultVariable)) { + throw new InvalidQueryException("Result variable %s must not appear in the argument list".formatted( + resultVariable)); + } + this.resultVariable = resultVariable; + this.inputVariable = inputVariable; + this.aggregator = aggregator; + } + + public DataVariable getResultVariable() { + return resultVariable; + } + + public DataVariable getInputVariable() { + return inputVariable; + } + + public Aggregator getAggregator() { + return aggregator; + } + + @Override + public Set getOutputVariables() { + return Set.of(resultVariable); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + if (positiveVariablesInClause.contains(inputVariable)) { + throw new InvalidQueryException("Aggregation variable %s must not be bound".formatted(inputVariable)); + } + return super.getInputVariables(positiveVariablesInClause); + } + + @Override + public Literal reduce() { + var reduction = getTarget().getReduction(); + return switch (reduction) { + case ALWAYS_FALSE -> { + var emptyValue = aggregator.getEmptyResult(); + yield emptyValue == null ? BooleanLiteral.FALSE : + resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); + } + case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to aggregate over an infinite set"); + case NOT_REDUCIBLE -> this; + }; + } + + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator, + substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments); + } + + @Override + public AbstractCallLiteral withArguments(Constraint newTarget, List newArguments) { + return new AggregationLiteral<>(resultVariable, aggregator, inputVariable, newTarget, newArguments); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherAggregationLiteral = (AggregationLiteral) other; + return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) && + aggregator.equals(otherAggregationLiteral.aggregator) && + helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable), + helper.getVariableHashCode(inputVariable), aggregator); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append(resultVariable); + builder.append(" is "); + builder.append(getTarget().toReferenceString()); + builder.append("("); + var argumentIterator = getArguments().iterator(); + if (argumentIterator.hasNext()) { + var argument = argumentIterator.next(); + if (inputVariable.equals(argument)) { + builder.append("@Aggregate(\"").append(aggregator).append("\") "); + } + builder.append(argument); + while (argumentIterator.hasNext()) { + builder.append(", "); + argument = argumentIterator.next(); + if (inputVariable.equals(argument)) { + builder.append("@Aggregate(\"").append(aggregator).append("\") "); + } + builder.append(argument); + } + } + builder.append(")"); + return builder.toString(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.Term; +import tools.refinery.logic.term.Variable; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public class AssignLiteral extends AbstractLiteral { + private final DataVariable variable; + private final Term term; + + public AssignLiteral(DataVariable variable, Term term) { + if (!term.getType().equals(variable.getType())) { + throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted( + term, variable.getType().getName(), term.getType().getName())); + } + var inputVariables = term.getInputVariables(); + if (inputVariables.contains(variable)) { + throw new InvalidQueryException("Result variable %s must not appear in the term %s".formatted( + variable, term)); + } + this.variable = variable; + this.term = term; + } + + public DataVariable getVariable() { + return variable; + } + + public Term getTerm() { + return term; + } + + @Override + public Set getOutputVariables() { + return Set.of(variable); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + return Collections.unmodifiableSet(term.getInputVariables()); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public Literal substitute(Substitution substitution) { + return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution)); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + var otherAssignLiteral = (AssignLiteral) other; + return helper.variableEqual(variable, otherAssignLiteral.variable) && + term.equalsWithSubstitution(helper, otherAssignLiteral.term); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), + term.hashCodeWithSubstitution(helper)); + } + + @Override + public String toString() { + return "%s is (%s)".formatted(variable, term); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Variable; + +import java.util.Set; + +public enum BooleanLiteral implements CanNegate { + TRUE(true), + FALSE(false); + + private final boolean value; + + BooleanLiteral(boolean value) { + this.value = value; + } + + @Override + public Set getOutputVariables() { + return Set.of(); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public Literal substitute(Substitution substitution) { + // No variables to substitute. + return this; + } + + @Override + public BooleanLiteral negate() { + return fromBoolean(!value); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + return equals(other); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return hashCode(); + } + + @Override + public String toString() { + return Boolean.toString(value); + } + + public static BooleanLiteral fromBoolean(boolean value) { + return value ? TRUE : FALSE; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.*; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public final class CallLiteral extends AbstractCallLiteral implements CanNegate { + private final CallPolarity polarity; + + public CallLiteral(CallPolarity polarity, Constraint target, List arguments) { + super(target, arguments); + var parameters = target.getParameters(); + int arity = target.arity(); + if (polarity.isTransitive()) { + if (arity != 2) { + throw new InvalidQueryException("Transitive closures can only take binary relations"); + } + if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { + throw new InvalidQueryException("Transitive closures can only be computed over nodes"); + } + if (parameters.get(0).getDirection() != ParameterDirection.OUT || + parameters.get(1).getDirection() != ParameterDirection.OUT) { + throw new InvalidQueryException("Transitive closures cannot take input parameters"); + } + } + this.polarity = polarity; + } + + public CallPolarity getPolarity() { + return polarity; + } + + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new CallLiteral(polarity, getTarget(), substitutedArguments); + } + + @Override + public Set getOutputVariables() { + if (polarity.isPositive()) { + return getArgumentsOfDirection(ParameterDirection.OUT); + } + return Set.of(); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + if (polarity.isPositive()) { + return getArgumentsOfDirection(ParameterDirection.IN); + } + return super.getInputVariables(positiveVariablesInClause); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + if (polarity.isPositive()) { + return Set.of(); + } + return super.getPrivateVariables(positiveVariablesInClause); + } + + @Override + public Literal reduce() { + var reduction = getTarget().getReduction(); + var negatedReduction = polarity.isPositive() ? reduction : reduction.negate(); + return switch (negatedReduction) { + case ALWAYS_TRUE -> BooleanLiteral.TRUE; + case ALWAYS_FALSE -> BooleanLiteral.FALSE; + default -> this; + }; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherCallLiteral = (CallLiteral) other; + return polarity.equals(otherCallLiteral.polarity); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), polarity); + } + + @Override + public CallLiteral negate() { + return new CallLiteral(polarity.negate(), getTarget(), getArguments()); + } + + @Override + public AbstractCallLiteral withArguments(Constraint newTarget, List newArguments) { + return new CallLiteral(polarity, newTarget, newArguments); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + if (!polarity.isPositive()) { + builder.append("!("); + } + builder.append(getTarget().toReferenceString()); + if (polarity.isTransitive()) { + builder.append("+"); + } + builder.append("("); + var argumentIterator = getArguments().iterator(); + if (argumentIterator.hasNext()) { + builder.append(argumentIterator.next()); + while (argumentIterator.hasNext()) { + builder.append(", ").append(argumentIterator.next()); + } + } + builder.append(")"); + if (!polarity.isPositive()) { + builder.append(")"); + } + return builder.toString(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.InvalidQueryException; + +public enum CallPolarity { + POSITIVE(true, false), + NEGATIVE(false, false), + TRANSITIVE(true, true); + + private final boolean positive; + + private final boolean transitive; + + CallPolarity(boolean positive, boolean transitive) { + this.positive = positive; + this.transitive = transitive; + } + + public boolean isPositive() { + return positive; + } + + public boolean isTransitive() { + return transitive; + } + + public CallPolarity negate() { + return switch (this) { + case POSITIVE -> NEGATIVE; + case NEGATIVE -> POSITIVE; + case TRANSITIVE -> throw new InvalidQueryException("Transitive polarity cannot be negated"); + }; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +public interface CanNegate> extends Literal { + T negate(); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.ConstantTerm; +import tools.refinery.logic.term.Term; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.term.bool.BoolNotTerm; +import tools.refinery.logic.term.bool.BoolTerms; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public class CheckLiteral extends AbstractLiteral implements CanNegate { + private final Term term; + + public CheckLiteral(Term term) { + if (!term.getType().equals(Boolean.class)) { + throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted( + term, Boolean.class.getName(), term.getType().getName())); + } + this.term = term; + } + + public Term getTerm() { + return term; + } + + @Override + public Set getOutputVariables() { + return Set.of(); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + return Collections.unmodifiableSet(term.getInputVariables()); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public Literal substitute(Substitution substitution) { + return new CheckLiteral(term.substitute(substitution)); + } + + @Override + public CheckLiteral negate() { + if (term instanceof BoolNotTerm notTerm) { + return new CheckLiteral(notTerm.getBody()); + } + return new CheckLiteral(BoolTerms.not(term)); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + var otherAssumeLiteral = (CheckLiteral) other; + return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), term.hashCodeWithSubstitution(helper)); + } + + @Override + public Literal reduce() { + if (term instanceof ConstantTerm constantTerm) { + // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. + return Boolean.TRUE.equals(constantTerm.getValue()) ? BooleanLiteral.TRUE : + BooleanLiteral.FALSE; + } + return this; + } + + @Override + public String toString() { + return "(%s)".formatted(term); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import java.util.Locale; + +public enum Connectivity { + WEAK, + STRONG; + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.Variable; + +import java.util.Objects; +import java.util.Set; + +public class ConstantLiteral extends AbstractLiteral { + private final NodeVariable variable; + private final int nodeId; + + public ConstantLiteral(NodeVariable variable, int nodeId) { + this.variable = variable; + this.nodeId = nodeId; + } + + public NodeVariable getVariable() { + return variable; + } + + public int getNodeId() { + return nodeId; + } + + + @Override + public Set getOutputVariables() { + return Set.of(variable); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public ConstantLiteral substitute(Substitution substitution) { + return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (other.getClass() != getClass()) { + return false; + } + var otherConstantLiteral = (ConstantLiteral) other; + return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId; + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), nodeId); + } + + @Override + public String toString() { + return "%s === @Constant %d".formatted(variable, nodeId); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.Variable; + +import java.util.List; + +public class CountLiteral extends AbstractCountLiteral { + public CountLiteral(DataVariable resultVariable, Constraint target, List arguments) { + super(Integer.class, resultVariable, target, arguments); + } + + @Override + protected Integer zero() { + return 0; + } + + @Override + protected Integer one() { + return 1; + } + + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new CountLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(), + substitutedArguments); + } + + @Override + public AbstractCallLiteral withArguments(Constraint newTarget, List newArguments) { + return new CountLiteral(getResultVariable(), newTarget, newArguments); + } + + @Override + protected String operatorName() { + return "count"; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Variable; + +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public final class EquivalenceLiteral extends AbstractLiteral implements CanNegate { + private final boolean positive; + private final Variable left; + private final Variable right; + + public EquivalenceLiteral(boolean positive, Variable left, Variable right) { + if (!left.tryGetType().equals(right.tryGetType())) { + throw new InvalidQueryException("Variables %s and %s of different type cannot be equivalent" + .formatted(left, right)); + } + this.positive = positive; + this.left = left; + this.right = right; + } + + public boolean isPositive() { + return positive; + } + + public Variable getLeft() { + return left; + } + + public Variable getRight() { + return right; + } + + @Override + public Set getOutputVariables() { + return Set.of(left); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + return Set.of(right); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public EquivalenceLiteral negate() { + return new EquivalenceLiteral(!positive, left, right); + } + + @Override + public EquivalenceLiteral substitute(Substitution substitution) { + return new EquivalenceLiteral(positive, substitution.getSubstitute(left), + substitution.getSubstitute(right)); + } + + @Override + public Literal reduce() { + if (left.equals(right)) { + return positive ? BooleanLiteral.TRUE : BooleanLiteral.FALSE; + } + return this; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (other.getClass() != getClass()) { + return false; + } + var otherEquivalenceLiteral = (EquivalenceLiteral) other; + return helper.variableEqual(left, otherEquivalenceLiteral.left) && + helper.variableEqual(right, otherEquivalenceLiteral.right); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(left), + helper.getVariableHashCode(right)); + } + + @Override + public String toString() { + return "%s %s %s".formatted(left, positive ? "===" : "!==", right); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.ConstantTerm; +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.*; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public class LeftJoinLiteral extends AbstractCallLiteral { + private final DataVariable resultVariable; + private final DataVariable placeholderVariable; + private final T defaultValue; + + public LeftJoinLiteral(DataVariable resultVariable, DataVariable placeholderVariable, + T defaultValue, Constraint target, List arguments) { + super(target, arguments); + this.resultVariable = resultVariable; + this.placeholderVariable = placeholderVariable; + this.defaultValue = defaultValue; + if (defaultValue == null) { + throw new InvalidQueryException("Default value must not be null"); + } + if (!resultVariable.getType().isInstance(defaultValue)) { + throw new InvalidQueryException("Default value %s must be assignable to result variable %s type %s" + .formatted(defaultValue, resultVariable, resultVariable.getType().getName())); + } + if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(placeholderVariable)) { + throw new InvalidQueryException( + "Placeholder variable %s must be bound with direction %s in the argument list" + .formatted(resultVariable, ParameterDirection.OUT)); + } + if (arguments.contains(resultVariable)) { + throw new InvalidQueryException("Result variable must not appear in the argument list"); + } + } + + public DataVariable getResultVariable() { + return resultVariable; + } + + public DataVariable getPlaceholderVariable() { + return placeholderVariable; + } + + public T getDefaultValue() { + return defaultValue; + } + + @Override + public Set getOutputVariables() { + return Set.of(resultVariable); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + var inputVariables = new LinkedHashSet<>(getArguments()); + inputVariables.remove(placeholderVariable); + return Collections.unmodifiableSet(inputVariables); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + return Set.of(placeholderVariable); + } + + @Override + public Literal reduce() { + var reduction = getTarget().getReduction(); + return switch (reduction) { + case ALWAYS_FALSE -> resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), defaultValue)); + case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to left join an infinite set"); + case NOT_REDUCIBLE -> this; + }; + } + + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new LeftJoinLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), + substitution.getTypeSafeSubstitute(placeholderVariable), defaultValue, getTarget(), + substitutedArguments); + } + + @Override + public AbstractCallLiteral withArguments(Constraint newTarget, List newArguments) { + return new LeftJoinLiteral<>(resultVariable, placeholderVariable, defaultValue, newTarget, newArguments); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherLeftJoinLiteral = (LeftJoinLiteral) other; + return helper.variableEqual(resultVariable, otherLeftJoinLiteral.resultVariable) && + helper.variableEqual(placeholderVariable, otherLeftJoinLiteral.placeholderVariable) && + Objects.equals(defaultValue, otherLeftJoinLiteral.defaultValue); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable), + helper.getVariableHashCode(placeholderVariable), defaultValue); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + var argumentIterator = getArguments().iterator(); + if (argumentIterator.hasNext()) { + appendArgument(builder, argumentIterator.next()); + while (argumentIterator.hasNext()) { + builder.append(", "); + appendArgument(builder, argumentIterator.next()); + } + } + builder.append(")"); + return builder.toString(); + } + + private void appendArgument(StringBuilder builder, Variable argument) { + if (placeholderVariable.equals(argument)) { + builder.append("@Default(").append(defaultValue).append(") "); + argument = resultVariable; + } + builder.append(argument); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Variable; + +import java.util.Set; + +public interface Literal { + Set getOutputVariables(); + + Set getInputVariables(Set positiveVariablesInClause); + + Set getPrivateVariables(Set positiveVariablesInClause); + + Literal substitute(Substitution substitution); + + default Literal reduce() { + return this; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other); + + int hashCodeWithSubstitution(LiteralHashCodeHelper helper); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.term.Term; + +public final class Literals { + private Literals() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static > T not(CanNegate literal) { + return literal.negate(); + } + + public static CheckLiteral check(Term term) { + return new CheckLiteral(term); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +public enum Reduction { + /** + * Signifies that a literal should be preserved in the clause. + */ + NOT_REDUCIBLE, + + /** + * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty). + */ + ALWAYS_TRUE, + + /** + * Signifies that the clause with the literal may be omitted entirely. + */ + ALWAYS_FALSE; + + public Reduction negate() { + return switch (this) { + case NOT_REDUCIBLE -> NOT_REDUCIBLE; + case ALWAYS_TRUE -> ALWAYS_FALSE; + case ALWAYS_FALSE -> ALWAYS_TRUE; + }; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.literal; + +import tools.refinery.logic.Constraint; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.ParameterDirection; +import tools.refinery.logic.term.Variable; + +import java.util.List; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. +@SuppressWarnings("squid:S2160") +public class RepresentativeElectionLiteral extends AbstractCallLiteral { + private final Connectivity connectivity; + + public RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, NodeVariable specific, + NodeVariable representative) { + this(connectivity, target, List.of(specific, representative)); + } + + private RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, List arguments) { + super(target, arguments); + this.connectivity = connectivity; + var parameters = target.getParameters(); + int arity = target.arity(); + if (arity != 2) { + throw new InvalidQueryException("SCCs can only take binary relations"); + } + if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { + throw new InvalidQueryException("SCCs can only be computed over nodes"); + } + if (parameters.get(0).getDirection() != ParameterDirection.OUT || + parameters.get(1).getDirection() != ParameterDirection.OUT) { + throw new InvalidQueryException("SCCs cannot take input parameters"); + } + } + + public Connectivity getConnectivity() { + return connectivity; + } + + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new RepresentativeElectionLiteral(connectivity, getTarget(), substitutedArguments); + } + + @Override + public Set getOutputVariables() { + return getArgumentsOfDirection(ParameterDirection.OUT); + } + + @Override + public Set getInputVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public Set getPrivateVariables(Set positiveVariablesInClause) { + return Set.of(); + } + + @Override + public Literal reduce() { + var reduction = getTarget().getReduction(); + return switch (reduction) { + case ALWAYS_FALSE -> BooleanLiteral.FALSE; + case ALWAYS_TRUE -> throw new InvalidQueryException( + "Trying to elect representatives over an infinite set"); + case NOT_REDUCIBLE -> this; + }; + } + + @Override + public AbstractCallLiteral withArguments(Constraint newTarget, List newArguments) { + return new RepresentativeElectionLiteral(connectivity, newTarget, newArguments); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherRepresentativeElectionLiteral = (RepresentativeElectionLiteral) other; + return connectivity.equals(otherRepresentativeElectionLiteral.connectivity); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return super.hashCodeWithSubstitution(helper) * 31 + connectivity.hashCode(); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append("@Representative(\""); + builder.append(connectivity); + builder.append("\") "); + builder.append(getTarget().toReferenceString()); + builder.append("("); + var argumentIterator = getArguments().iterator(); + if (argumentIterator.hasNext()) { + builder.append(argumentIterator.next()); + while (argumentIterator.hasNext()) { + builder.append(", ").append(argumentIterator.next()); + } + } + builder.append(")"); + return builder.toString(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.equality.DnfEqualityChecker; +import tools.refinery.logic.util.CycleDetectingMapper; + +public abstract class AbstractRecursiveRewriter implements DnfRewriter { + private final CycleDetectingMapper mapper = new CycleDetectingMapper<>(Dnf::name, this::map); + + @Override + public Dnf rewrite(Dnf dnf) { + return mapper.map(dnf); + } + + protected Dnf map(Dnf dnf) { + var result = doRewrite(dnf); + return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, result) ? dnf : result; + } + + protected abstract Dnf doRewrite(Dnf dnf); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.DnfClause; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.literal.*; +import tools.refinery.logic.term.ParameterDirection; + +import java.util.*; + +class ClauseInputParameterResolver { + private final InputParameterResolver rewriter; + private final String dnfName; + private final int clauseIndex; + private final Set positiveVariables = new LinkedHashSet<>(); + private final List inlinedLiterals = new ArrayList<>(); + private final Deque workList; + private int helperIndex = 0; + + public ClauseInputParameterResolver(InputParameterResolver rewriter, List context, DnfClause clause, + String dnfName, int clauseIndex) { + this.rewriter = rewriter; + this.dnfName = dnfName; + this.clauseIndex = clauseIndex; + workList = new ArrayDeque<>(clause.literals().size() + context.size()); + for (var literal : context) { + workList.addLast(literal); + } + for (var literal : clause.literals()) { + workList.addLast(literal); + } + } + + public List rewriteClause() { + while (!workList.isEmpty()) { + var literal = workList.removeFirst(); + processLiteral(literal); + } + return inlinedLiterals; + } + + private void processLiteral(Literal literal) { + if (!(literal instanceof AbstractCallLiteral abstractCallLiteral) || + !(abstractCallLiteral.getTarget() instanceof Dnf targetDnf)) { + markAsDone(literal); + return; + } + boolean hasInputParameter = hasInputParameter(targetDnf); + if (!hasInputParameter) { + targetDnf = rewriter.rewrite(targetDnf); + } + if (inlinePositiveClause(abstractCallLiteral, targetDnf)) { + return; + } + if (eliminateDoubleNegation(abstractCallLiteral, targetDnf)) { + return; + } + if (hasInputParameter) { + rewriteWithCurrentContext(abstractCallLiteral, targetDnf); + return; + } + markAsDone(abstractCallLiteral.withTarget(targetDnf)); + } + + private void markAsDone(Literal literal) { + positiveVariables.addAll(literal.getOutputVariables()); + inlinedLiterals.add(literal); + } + + private boolean inlinePositiveClause(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { + var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.POSITIVE); + if (targetLiteral == null) { + return false; + } + var substitution = asSubstitution(abstractCallLiteral, targetDnf); + var substitutedLiteral = targetLiteral.substitute(substitution); + workList.addFirst(substitutedLiteral); + return true; + } + + private boolean eliminateDoubleNegation(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { + var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.NEGATIVE); + if (!(targetLiteral instanceof CallLiteral targetCallLiteral) || + targetCallLiteral.getPolarity() != CallPolarity.NEGATIVE) { + return false; + } + var substitution = asSubstitution(abstractCallLiteral, targetDnf); + var substitutedLiteral = (CallLiteral) targetCallLiteral.substitute(substitution); + workList.addFirst(substitutedLiteral.negate()); + return true; + } + + private void rewriteWithCurrentContext(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { + var contextBuilder = Dnf.builder("%s#clause%d#helper%d".formatted(dnfName, clauseIndex, helperIndex)); + helperIndex++; + contextBuilder.parameters(positiveVariables, ParameterDirection.OUT); + contextBuilder.clause(inlinedLiterals); + var contextDnf = contextBuilder.build(); + var contextCall = new CallLiteral(CallPolarity.POSITIVE, contextDnf, List.copyOf(positiveVariables)); + inlinedLiterals.clear(); + var substitution = Substitution.builder().renewing().build(); + var context = new ArrayList(); + context.add(contextCall.substitute(substitution)); + int arity = targetDnf.arity(); + for (int i = 0; i < arity; i++) { + var parameter = targetDnf.getSymbolicParameters().get(i).getVariable(); + var argument = abstractCallLiteral.getArguments().get(i); + context.add(new EquivalenceLiteral(true, parameter, substitution.getSubstitute(argument))); + } + var rewrittenDnf = rewriter.rewriteWithContext(context, targetDnf); + workList.addFirst(abstractCallLiteral.withTarget(rewrittenDnf)); + workList.addFirst(contextCall); + } + + private static boolean hasInputParameter(Dnf targetDnf) { + for (var parameter : targetDnf.getParameters()) { + if (parameter.getDirection() != ParameterDirection.OUT) { + return true; + } + } + return false; + } + + private static Literal getSingleLiteral(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf, + CallPolarity polarity) { + if (!(abstractCallLiteral instanceof CallLiteral callLiteral) || + callLiteral.getPolarity() != polarity) { + return null; + } + var clauses = targetDnf.getClauses(); + if (clauses.size() != 1) { + return null; + } + var targetLiterals = clauses.get(0).literals(); + if (targetLiterals.size() != 1) { + return null; + } + return targetLiterals.get(0); + } + + private static Substitution asSubstitution(AbstractCallLiteral callLiteral, Dnf targetDnf) { + var builder = Substitution.builder().renewing(); + var arguments = callLiteral.getArguments(); + var parameters = targetDnf.getSymbolicParameters(); + int arity = arguments.size(); + if (parameters.size() != arity) { + throw new IllegalArgumentException("Call %s of %s arity mismatch".formatted(callLiteral, targetDnf)); + } + for (int i = 0; i < arity; i++) { + builder.putChecked(parameters.get(i).getVariable(), arguments.get(i)); + } + return builder.build(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import tools.refinery.logic.dnf.Dnf; + +import java.util.ArrayList; +import java.util.List; + +public class CompositeRewriter implements DnfRewriter { + private final List rewriterList = new ArrayList<>(); + + public void addFirst(DnfRewriter rewriter) { + rewriterList.add(rewriter); + } + + @Override + public Dnf rewrite(Dnf dnf) { + Dnf rewrittenDnf = dnf; + for (int i = rewriterList.size() - 1; i >= 0; i--) { + var rewriter = rewriterList.get(i); + rewrittenDnf = rewriter.rewrite(rewrittenDnf); + } + return rewrittenDnf; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import tools.refinery.logic.dnf.AnyQuery; +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.Query; + +@FunctionalInterface +public interface DnfRewriter { + Dnf rewrite(Dnf dnf); + + default AnyQuery rewrite(AnyQuery query) { + return rewrite((Query) query); + } + + default Query rewrite(Query query) { + var rewrittenDnf = rewrite(query.getDnf()); + return query.withDnf(rewrittenDnf); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.DnfClause; +import tools.refinery.logic.dnf.Query; +import tools.refinery.logic.equality.DnfEqualityChecker; +import tools.refinery.logic.literal.AbstractCallLiteral; +import tools.refinery.logic.literal.Literal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DuplicateDnfRemover extends AbstractRecursiveRewriter { + private final Map dnfCache = new HashMap<>(); + private final Map> queryCache = new HashMap<>(); + + @Override + protected Dnf map(Dnf dnf) { + var result = super.map(dnf); + return dnfCache.computeIfAbsent(new CanonicalDnf(result), CanonicalDnf::getDnf); + } + + @Override + protected Dnf doRewrite(Dnf dnf) { + var builder = Dnf.builderFrom(dnf); + for (var clause : dnf.getClauses()) { + builder.clause(rewriteClause(clause)); + } + return builder.build(); + } + + private List rewriteClause(DnfClause clause) { + var originalLiterals = clause.literals(); + var literals = new ArrayList(originalLiterals.size()); + for (var literal : originalLiterals) { + var rewrittenLiteral = literal; + if (literal instanceof AbstractCallLiteral abstractCallLiteral && + abstractCallLiteral.getTarget() instanceof Dnf targetDnf) { + var rewrittenTarget = rewrite(targetDnf); + rewrittenLiteral = abstractCallLiteral.withTarget(rewrittenTarget); + } + literals.add(rewrittenLiteral); + } + return literals; + } + + @Override + public Query rewrite(Query query) { + var rewrittenDnf = rewrite(query.getDnf()); + // {@code withDnf} will always return the appropriate type. + @SuppressWarnings("unchecked") + var rewrittenQuery = (Query) queryCache.computeIfAbsent(rewrittenDnf, query::withDnf); + return rewrittenQuery; + } + + private static class CanonicalDnf { + private final Dnf dnf; + private final int hash; + + public CanonicalDnf(Dnf dnf) { + this.dnf = dnf; + hash = dnf.hashCodeWithSubstitution(); + } + + public Dnf getDnf() { + return dnf; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + var otherCanonicalDnf = (CanonicalDnf) obj; + return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, otherCanonicalDnf.dnf); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public String toString() { + return dnf.name(); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.rewriter; + +import tools.refinery.logic.dnf.Dnf; +import tools.refinery.logic.dnf.DnfBuilder; +import tools.refinery.logic.term.Variable; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.term.ParameterDirection; + +import java.util.HashSet; +import java.util.List; + +public class InputParameterResolver extends AbstractRecursiveRewriter { + @Override + protected Dnf doRewrite(Dnf dnf) { + return rewriteWithContext(List.of(), dnf); + } + + Dnf rewriteWithContext(List context, Dnf dnf) { + var dnfName = dnf.name(); + var builder = Dnf.builder(dnfName); + createSymbolicParameters(context, dnf, builder); + builder.functionalDependencies(dnf.getFunctionalDependencies()); + var clauses = dnf.getClauses(); + int clauseCount = clauses.size(); + for (int i = 0; i < clauseCount; i++) { + var clause = clauses.get(i); + var clauseRewriter = new ClauseInputParameterResolver(this, context, clause, dnfName, i); + builder.clause(clauseRewriter.rewriteClause()); + } + return builder.build(); + } + + private static void createSymbolicParameters(List context, Dnf dnf, DnfBuilder builder) { + var positiveInContext = new HashSet(); + for (var literal : context) { + positiveInContext.addAll(literal.getOutputVariables()); + } + for (var symbolicParameter : dnf.getSymbolicParameters()) { + var variable = symbolicParameter.getVariable(); + var isOutput = symbolicParameter.getDirection() == ParameterDirection.OUT || + positiveInContext.contains(variable); + var direction = isOutput ? ParameterDirection.OUT : ParameterDirection.IN; + builder.parameter(variable, direction); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.substitution; + +import tools.refinery.logic.term.Variable; + +import java.util.Map; + +public record MapBasedSubstitution(Map map, Substitution fallback) implements Substitution { + @Override + public Variable getSubstitute(Variable variable) { + var value = map.get(variable); + return value == null ? fallback.getSubstitute(variable) : value; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.substitution; + +import tools.refinery.logic.term.Variable; + +import java.util.HashMap; +import java.util.Map; + +public class RenewingSubstitution implements Substitution { + private final Map alreadyRenewed = new HashMap<>(); + + @Override + public Variable getSubstitute(Variable variable) { + return alreadyRenewed.computeIfAbsent(variable, Variable::renew); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.substitution; + +import tools.refinery.logic.term.Variable; + +public enum StatelessSubstitution implements Substitution { + FAILING { + @Override + public Variable getSubstitute(Variable variable) { + throw new IllegalArgumentException("No substitute for " + variable); + } + }, + IDENTITY { + @Override + public Variable getSubstitute(Variable variable) { + return variable; + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.substitution; + +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.Variable; + +@FunctionalInterface +public interface Substitution { + Variable getSubstitute(Variable variable); + + default NodeVariable getTypeSafeSubstitute(NodeVariable variable) { + var substitute = getSubstitute(variable); + return substitute.asNodeVariable(); + } + + default DataVariable getTypeSafeSubstitute(DataVariable variable) { + var substitute = getSubstitute(variable); + return substitute.asDataVariable(variable.getType()); + } + + static SubstitutionBuilder builder() { + return new SubstitutionBuilder(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.substitution; + +import tools.refinery.logic.term.DataVariable; +import tools.refinery.logic.term.NodeVariable; +import tools.refinery.logic.term.Variable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("UnusedReturnValue") +public class SubstitutionBuilder { + private final Map map = new HashMap<>(); + private Substitution fallback; + + SubstitutionBuilder() { + total(); + } + + public SubstitutionBuilder put(NodeVariable original, NodeVariable substitute) { + return putChecked(original, substitute); + } + + public SubstitutionBuilder put(DataVariable original, DataVariable substitute) { + return putChecked(original, substitute); + } + + public SubstitutionBuilder putChecked(Variable original, Variable substitute) { + if (!original.tryGetType().equals(substitute.tryGetType())) { + throw new IllegalArgumentException("Cannot substitute variable %s of sort %s with variable %s of sort %s" + .formatted(original, original.tryGetType().map(Class::getName).orElse("node"), substitute, + substitute.tryGetType().map(Class::getName).orElse("node"))); + } + if (map.containsKey(original)) { + throw new IllegalArgumentException("Already has substitution for variable %s".formatted(original)); + } + map.put(original, substitute); + return this; + } + + public SubstitutionBuilder putManyChecked(List originals, List substitutes) { + int size = originals.size(); + if (size != substitutes.size()) { + throw new IllegalArgumentException("Cannot substitute %d variables %s with %d variables %s" + .formatted(size, originals, substitutes.size(), substitutes)); + } + for (int i = 0; i < size; i++) { + putChecked(originals.get(i), substitutes.get(i)); + } + return this; + } + + public SubstitutionBuilder fallback(Substitution newFallback) { + fallback = newFallback; + return this; + } + + public SubstitutionBuilder total() { + return fallback(StatelessSubstitution.FAILING); + } + + public SubstitutionBuilder partial() { + return fallback(StatelessSubstitution.IDENTITY); + } + + public SubstitutionBuilder renewing() { + return fallback(new RenewingSubstitution()); + } + + public Substitution build() { + return map.isEmpty() ? fallback : new MapBasedSubstitution(Collections.unmodifiableMap(map), fallback); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; + +import java.util.Objects; + +public abstract class AbstractTerm implements Term { + private final Class type; + + protected AbstractTerm(Class type) { + this.type = type; + } + + @Override + public Class getType() { + return type; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + return other != null && getClass() == other.getClass() && type.equals(other.getType()); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(getClass(), type); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractTerm that = (AbstractTerm) o; + return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that); + } + + @Override + public int hashCode() { + return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import java.util.stream.Stream; + +public interface Aggregator { + Class getResultType(); + + Class getInputType(); + + R aggregateStream(Stream stream); + + R getEmptyResult(); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; + +import java.util.Optional; +import java.util.Set; + +public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable { + protected AnyDataVariable(String name) { + super(name); + } + + @Override + public Optional> tryGetType() { + return Optional.of(getType()); + } + + @Override + public boolean isNodeVariable() { + return false; + } + + @Override + public boolean isDataVariable() { + return true; + } + + @Override + public NodeVariable asNodeVariable() { + throw new InvalidQueryException("%s is a data variable".formatted(this)); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable); + } + + @Override + public Set getInputVariables() { + return Set.of(this); + } + + @Override + public abstract AnyDataVariable renew(@Nullable String name); + + @Override + public abstract AnyDataVariable renew(); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; + +import java.util.Set; + +public sealed interface AnyTerm permits AnyDataVariable, Term { + Class getType(); + + AnyTerm substitute(Substitution substitution); + + boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other); + + int hashCodeWithSubstitution(LiteralHashCodeHelper helper); + + Set getInputVariables(); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import tools.refinery.logic.literal.Literal; + +@FunctionalInterface +public interface AssignedValue { + Literal toLiteral(DataVariable targetVariable); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.valuation.Valuation; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}. +@SuppressWarnings("squid:S2160") +public abstract class BinaryTerm extends AbstractTerm { + private final Class leftType; + private final Class rightType; + private final Term left; + private final Term right; + + protected BinaryTerm(Class type, Class leftType, Class rightType, Term left, Term right) { + super(type); + if (!left.getType().equals(leftType)) { + throw new InvalidQueryException("Expected left %s to be of type %s, got %s instead".formatted( + left, leftType.getName(), left.getType().getName())); + } + if (!right.getType().equals(rightType)) { + throw new InvalidQueryException("Expected right %s to be of type %s, got %s instead".formatted( + right, rightType.getName(), right.getType().getName())); + } + this.leftType = leftType; + this.rightType = rightType; + this.left = left; + this.right = right; + } + + public Class getLeftType() { + return leftType; + } + + public Class getRightType() { + return rightType; + } + + public Term getLeft() { + return left; + } + + public Term getRight() { + return right; + } + + @Override + public R evaluate(Valuation valuation) { + var leftValue = left.evaluate(valuation); + if (leftValue == null) { + return null; + } + var rightValue = right.evaluate(valuation); + if (rightValue == null) { + return null; + } + return doEvaluate(leftValue, rightValue); + } + + protected abstract R doEvaluate(T1 leftValue, T2 rightValue); + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherBinaryTerm = (BinaryTerm) other; + return leftType.equals(otherBinaryTerm.leftType) && + rightType.equals(otherBinaryTerm.rightType) && + left.equalsWithSubstitution(helper, otherBinaryTerm.left) && + right.equalsWithSubstitution(helper, otherBinaryTerm.right); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), leftType.hashCode(), rightType.hashCode(), + left.hashCodeWithSubstitution(helper), right.hashCodeWithSubstitution(helper)); + } + + @Override + public Term substitute(Substitution substitution) { + return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution)); + } + + public abstract Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight); + + @Override + public Set getInputVariables() { + var inputVariables = new HashSet<>(left.getInputVariables()); + inputVariables.addAll(right.getInputVariables()); + return Collections.unmodifiableSet(inputVariables); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.valuation.Valuation; + +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}. +@SuppressWarnings("squid:S2160") +public final class ConstantTerm extends AbstractTerm { + private final T value; + + public ConstantTerm(Class type, T value) { + super(type); + if (value != null && !type.isInstance(value)) { + throw new InvalidQueryException("Value %s is not an instance of %s".formatted(value, type.getName())); + } + this.value = value; + } + + public T getValue() { + return value; + } + + @Override + public T evaluate(Valuation valuation) { + return getValue(); + } + + @Override + public Term substitute(Substitution substitution) { + return this; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherConstantTerm = (ConstantTerm) other; + return Objects.equals(value, otherConstantTerm.value); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), Objects.hash(value)); + } + + @Override + public Set getInputVariables() { + return Set.of(); + } + + @Override + public String toString() { + return value.toString(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.literal.EquivalenceLiteral; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.valuation.Valuation; + +import java.util.Objects; + +public final class DataVariable extends AnyDataVariable implements Term { + private final Class type; + + DataVariable(String name, Class type) { + super(name); + this.type = type; + } + + @Override + public Class getType() { + return type; + } + + @Override + public DataVariable renew(@Nullable String name) { + return new DataVariable<>(name, type); + } + + @Override + public DataVariable renew() { + return renew(getExplicitName()); + } + + @Override + public DataVariable asDataVariable(Class newType) { + if (!getType().equals(newType)) { + throw new InvalidQueryException("%s is not of type %s but of type %s" + .formatted(this, newType.getName(), getType().getName())); + } + @SuppressWarnings("unchecked") + var result = (DataVariable) this; + return result; + } + + @Override + public T evaluate(Valuation valuation) { + return valuation.getValue(this); + } + + @Override + public Term substitute(Substitution substitution) { + return substitution.getTypeSafeSubstitute(this); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + return other instanceof DataVariable dataVariable && helper.variableEqual(this, dataVariable); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return helper.getVariableHashCode(this); + } + + @Override + public int hashCodeWithSubstitution(int sequenceNumber) { + return Objects.hash(type, sequenceNumber); + } + + public Literal assign(AssignedValue value) { + return value.toLiteral(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + DataVariable that = (DataVariable) o; + return type.equals(that.type); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), type); + } + + public EquivalenceLiteral isEquivalent(DataVariable other) { + return new EquivalenceLiteral(true, this, other); + } + + public EquivalenceLiteral notEquivalent(DataVariable other) { + return new EquivalenceLiteral(false, this, other); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import java.util.Comparator; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +public class ExtremeValueAggregator implements StatefulAggregator { + private final Class type; + private final T emptyResult; + private final Comparator comparator; + + public ExtremeValueAggregator(Class type, T emptyResult) { + this(type, emptyResult, null); + } + + public ExtremeValueAggregator(Class type, T emptyResult, Comparator comparator) { + this.type = type; + this.emptyResult = emptyResult; + this.comparator = comparator; + } + + @Override + public Class getResultType() { + return getInputType(); + } + + @Override + public Class getInputType() { + return type; + } + + @Override + public StatefulAggregate createEmptyAggregate() { + return new Aggregate(); + } + + @Override + public T getEmptyResult() { + return emptyResult; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtremeValueAggregator that = (ExtremeValueAggregator) o; + return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator, + that.comparator); + } + + @Override + public int hashCode() { + return Objects.hash(type, emptyResult, comparator); + } + + private class Aggregate implements StatefulAggregate { + private final SortedMap values; + + private Aggregate() { + values = new TreeMap<>(comparator); + } + + private Aggregate(Aggregate other) { + values = new TreeMap<>(other.values); + } + + @Override + public void add(T value) { + values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1); + } + + @Override + public void remove(T value) { + values.compute(value, (theValue, currentCount) -> { + if (currentCount == null || currentCount <= 0) { + throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue)); + } + return currentCount.equals(1) ? null : currentCount - 1; + }); + } + + @Override + public T getResult() { + return isEmpty() ? emptyResult : values.firstKey(); + } + + @Override + public boolean isEmpty() { + return values.isEmpty(); + } + + @Override + public StatefulAggregate deepCopy() { + return new Aggregate(this); + } + + @Override + public boolean contains(T value) { + return StatefulAggregate.super.contains(value); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.literal.ConstantLiteral; +import tools.refinery.logic.literal.EquivalenceLiteral; + +import java.util.Optional; + +public final class NodeVariable extends Variable { + NodeVariable(@Nullable String name) { + super(name); + } + + @Override + public Optional> tryGetType() { + return Optional.empty(); + } + + @Override + public NodeVariable renew(@Nullable String name) { + return of(name); + } + + @Override + public NodeVariable renew() { + return renew(getExplicitName()); + } + + @Override + public boolean isNodeVariable() { + return true; + } + + @Override + public boolean isDataVariable() { + return false; + } + + @Override + public NodeVariable asNodeVariable() { + return this; + } + + @Override + public DataVariable asDataVariable(Class type) { + throw new InvalidQueryException("%s is a node variable".formatted(this)); + } + + @Override + public int hashCodeWithSubstitution(int sequenceNumber) { + return sequenceNumber; + } + + public ConstantLiteral isConstant(int value) { + return new ConstantLiteral(this, value); + } + + public EquivalenceLiteral isEquivalent(NodeVariable other) { + return new EquivalenceLiteral(true, this, other); + } + + public EquivalenceLiteral notEquivalent(NodeVariable other) { + return new EquivalenceLiteral(false, this, other); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import java.util.Objects; +import java.util.Optional; + +public class Parameter { + public static final Parameter NODE_OUT = new Parameter(null); + + private final Class dataType; + private final ParameterDirection direction; + + public Parameter(Class dataType) { + this(dataType, ParameterDirection.OUT); + } + + public Parameter(Class dataType, ParameterDirection direction) { + this.dataType = dataType; + this.direction = direction; + } + + public boolean isNodeVariable() { + return dataType == null; + } + + public boolean isDataVariable() { + return !isNodeVariable(); + } + + public Optional> tryGetType() { + return Optional.ofNullable(dataType); + } + + public ParameterDirection getDirection() { + return direction; + } + + public boolean matches(Parameter other) { + return Objects.equals(dataType, other.dataType) && direction == other.direction; + } + + public boolean isAssignable(Variable variable) { + if (variable instanceof AnyDataVariable dataVariable) { + return dataVariable.getType().equals(dataType); + } else if (variable instanceof NodeVariable) { + return !isDataVariable(); + } else { + throw new IllegalArgumentException("Unknown variable " + variable); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Parameter parameter = (Parameter) o; + return matches(parameter); + } + + @Override + public int hashCode() { + return Objects.hash(dataType, direction); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +public enum ParameterDirection { + OUT("out"), + IN("in"); + + private final String name; + + ParameterDirection(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +public interface StatefulAggregate { + void add(T value); + + void remove(T value); + + R getResult(); + + boolean isEmpty(); + + StatefulAggregate deepCopy(); + + default boolean contains(T value) { + throw new UnsupportedOperationException(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import java.util.stream.Stream; + +public interface StatefulAggregator extends Aggregator { + StatefulAggregate createEmptyAggregate(); + + @Override + default R aggregateStream(Stream stream) { + var accumulator = createEmptyAggregate(); + var iterator = stream.iterator(); + while (iterator.hasNext()) { + var value = iterator.next(); + accumulator.add(value); + } + return accumulator.getResult(); + } + + @Override + default R getEmptyResult() { + return createEmptyAggregate().getResult(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import java.util.stream.Stream; + +public interface StatelessAggregator extends Aggregator { + R add(R current, T value); + + R remove(R current, T value); + + @Override + default R aggregateStream(Stream stream) { + var accumulator = getEmptyResult(); + var iterator = stream.iterator(); + while (iterator.hasNext()) { + var value = iterator.next(); + accumulator = add(accumulator, value); + } + return accumulator; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import tools.refinery.logic.literal.AssignLiteral; +import tools.refinery.logic.literal.Literal; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.valuation.Valuation; + +public non-sealed interface Term extends AnyTerm, AssignedValue { + @Override + Class getType(); + + T evaluate(Valuation valuation); + + @Override + Term substitute(Substitution substitution); + + @Override + default Literal toLiteral(DataVariable targetVariable) { + return new AssignLiteral<>(targetVariable, this); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import tools.refinery.logic.InvalidQueryException; +import tools.refinery.logic.equality.LiteralEqualityHelper; +import tools.refinery.logic.equality.LiteralHashCodeHelper; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.valuation.Valuation; + +import java.util.Objects; +import java.util.Set; + +// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}. +@SuppressWarnings("squid:S2160") +public abstract class UnaryTerm extends AbstractTerm { + private final Class bodyType; + private final Term body; + + protected UnaryTerm(Class type, Class bodyType, Term body) { + super(type); + if (!body.getType().equals(bodyType)) { + throw new InvalidQueryException("Expected body %s to be of type %s, got %s instead".formatted(body, + bodyType.getName(), body.getType().getName())); + } + this.bodyType = bodyType; + this.body = body; + } + + public Class getBodyType() { + return bodyType; + } + + public Term getBody() { + return body; + } + + @Override + public R evaluate(Valuation valuation) { + var bodyValue = body.evaluate(valuation); + return bodyValue == null ? null : doEvaluate(bodyValue); + } + + protected abstract R doEvaluate(T bodyValue); + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherUnaryTerm = (UnaryTerm) other; + return bodyType.equals(otherUnaryTerm.bodyType) && body.equalsWithSubstitution(helper, otherUnaryTerm.body); + } + + @Override + public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { + return Objects.hash(super.hashCodeWithSubstitution(helper), bodyType, body.hashCodeWithSubstitution(helper)); + } + + @Override + public Term substitute(Substitution substitution) { + return doSubstitute(substitution, body.substitute(substitution)); + } + + protected abstract Term doSubstitute(Substitution substitution, Term substitutedBody); + + @Override + public Set getInputVariables() { + return body.getInputVariables(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.logic.dnf.DnfUtils; + +import java.util.Objects; +import java.util.Optional; + +public abstract sealed class Variable permits AnyDataVariable, NodeVariable { + private final String explicitName; + private final String uniqueName; + + protected Variable(String name) { + this.explicitName = name; + uniqueName = DnfUtils.generateUniqueName(name); + } + + public abstract Optional> tryGetType(); + + public String getName() { + return explicitName == null ? uniqueName : explicitName; + } + + protected String getExplicitName() { + return explicitName; + } + + public boolean isExplicitlyNamed() { + return explicitName != null; + } + + public String getUniqueName() { + return uniqueName; + } + + public abstract Variable renew(@Nullable String name); + + public abstract Variable renew(); + + public abstract boolean isNodeVariable(); + + public abstract boolean isDataVariable(); + + public abstract NodeVariable asNodeVariable(); + + public abstract DataVariable asDataVariable(Class type); + + public abstract int hashCodeWithSubstitution(int sequenceNumber); + + @Override + public String toString() { + return getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Variable variable = (Variable) o; + return Objects.equals(uniqueName, variable.uniqueName); + } + + @Override + public int hashCode() { + return Objects.hash(uniqueName); + } + + public static NodeVariable of(@Nullable String name) { + return new NodeVariable(name); + } + + public static NodeVariable of() { + return of((String) null); + } + + public static DataVariable of(@Nullable String name, Class type) { + return new DataVariable<>(name, type); + } + + public static DataVariable of(Class type) { + return of(null, type); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.bool; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class BoolAndTerm extends BoolBinaryTerm { + public BoolAndTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new BoolAndTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) { + return leftValue && rightValue; + } + + @Override + public String toString() { + return "(%s && %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.bool; + +import tools.refinery.logic.term.BinaryTerm; +import tools.refinery.logic.term.Term; + +public abstract class BoolBinaryTerm extends BinaryTerm { + protected BoolBinaryTerm(Term left, Term right) { + super(Boolean.class, Boolean.class, Boolean.class, left, right); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.bool; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; +import tools.refinery.logic.term.UnaryTerm; + +public class BoolNotTerm extends UnaryTerm { + protected BoolNotTerm(Term body) { + super(Boolean.class, Boolean.class, body); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new BoolNotTerm(substitutedBody); + } + + @Override + protected Boolean doEvaluate(Boolean bodyValue) { + return !bodyValue; + } + + @Override + public String toString() { + return "(!%s)".formatted(getBody()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.bool; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class BoolOrTerm extends BoolBinaryTerm { + public BoolOrTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new BoolOrTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) { + return leftValue || rightValue; + } + + @Override + public String toString() { + return "(%s || %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.bool; + +import tools.refinery.logic.term.ConstantTerm; +import tools.refinery.logic.term.Term; + +public final class BoolTerms { + private BoolTerms() { + throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); + } + + public static Term constant(Boolean value) { + return new ConstantTerm<>(Boolean.class, value); + } + + public static Term not(Term body) { + return new BoolNotTerm(body); + } + + public static Term and(Term left, Term right) { + return new BoolAndTerm(left, right); + } + + public static Term or(Term left, Term right) { + return new BoolOrTerm(left, right); + } + + public static Term xor(Term left, Term right) { + return new BoolXorTerm(left, right); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.bool; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class BoolXorTerm extends BoolBinaryTerm { + public BoolXorTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new BoolXorTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) { + return leftValue ^ rightValue; + } + + @Override + public String toString() { + return "(%s ^^ %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import tools.refinery.logic.term.uppercardinality.FiniteUpperCardinality; +import tools.refinery.logic.AbstractDomain; + +import java.util.Optional; + +// Singleton pattern, because there is only one domain for truth values. +@SuppressWarnings("squid:S6548") +public class CardinalityDomain implements AbstractDomain { + public static final CardinalityDomain INSTANCE = new CardinalityDomain(); + + private CardinalityDomain() { + } + + @Override + public Class abstractType() { + return CardinalityInterval.class; + } + + @Override + public Class concreteType() { + return Integer.class; + } + + @Override + public CardinalityInterval toAbstract(Integer concreteValue) { + return CardinalityIntervals.exactly(concreteValue); + } + + @Override + public Optional toConcrete(CardinalityInterval abstractValue) { + return isConcrete(abstractValue) ? Optional.of(abstractValue.lowerBound()) : Optional.empty(); + } + + @Override + public boolean isConcrete(CardinalityInterval abstractValue) { + if (!(abstractValue instanceof NonEmptyCardinalityInterval nonEmptyValue) || + !((nonEmptyValue.upperBound()) instanceof FiniteUpperCardinality finiteUpperCardinality)) { + return false; + } + return nonEmptyValue.lowerBound() == finiteUpperCardinality.finiteUpperBound(); + } + + @Override + public CardinalityInterval commonRefinement(CardinalityInterval leftValue, CardinalityInterval rightValue) { + return leftValue.meet(rightValue); + } + + @Override + public CardinalityInterval commonAncestor(CardinalityInterval leftValue, CardinalityInterval rightValue) { + return leftValue.join(rightValue); + } + + @Override + public CardinalityInterval unknown() { + return CardinalityIntervals.SET; + } + + @Override + public boolean isError(CardinalityInterval abstractValue) { + return abstractValue.isEmpty(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import tools.refinery.logic.term.uppercardinality.UpperCardinality; + +public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval { + int lowerBound(); + + UpperCardinality upperBound(); + + boolean isEmpty(); + + CardinalityInterval min(CardinalityInterval other); + + CardinalityInterval max(CardinalityInterval other); + + CardinalityInterval add(CardinalityInterval other); + + CardinalityInterval take(int count); + + CardinalityInterval multiply(CardinalityInterval other); + + CardinalityInterval meet(CardinalityInterval other); + + CardinalityInterval join(CardinalityInterval other); +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import tools.refinery.logic.term.uppercardinality.UpperCardinalities; +import tools.refinery.logic.term.uppercardinality.UpperCardinality; + +public final class CardinalityIntervals { + public static final CardinalityInterval NONE = exactly(0); + + public static final CardinalityInterval ONE = exactly(1); + + public static final CardinalityInterval LONE = atMost(1); + + public static final CardinalityInterval SET = atLeast(0); + + public static final CardinalityInterval SOME = atLeast(1); + + public static final CardinalityInterval ERROR = EmptyCardinalityInterval.INSTANCE; + + private CardinalityIntervals() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static CardinalityInterval between(int lowerBound, UpperCardinality upperBound) { + if (upperBound.compareToInt(lowerBound) < 0) { + return ERROR; + } + return new NonEmptyCardinalityInterval(lowerBound, upperBound); + } + + public static CardinalityInterval between(int lowerBound, int upperBound) { + return between(lowerBound, UpperCardinalities.atMost(upperBound)); + } + + public static CardinalityInterval atMost(UpperCardinality upperBound) { + return new NonEmptyCardinalityInterval(0, upperBound); + } + + public static CardinalityInterval atMost(int upperBound) { + return atMost(UpperCardinalities.atMost(upperBound)); + } + + public static CardinalityInterval atLeast(int lowerBound) { + return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.UNBOUNDED); + } + + public static CardinalityInterval exactly(int lowerBound) { + return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.atMost(lowerBound)); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import tools.refinery.logic.term.uppercardinality.UpperCardinalities; +import tools.refinery.logic.term.uppercardinality.UpperCardinality; + +// Singleton implementation, because there is only a single empty interval. +@SuppressWarnings("squid:S6548") +public final class EmptyCardinalityInterval implements CardinalityInterval { + static final EmptyCardinalityInterval INSTANCE = new EmptyCardinalityInterval(); + + private EmptyCardinalityInterval() { + // Singleton constructor. + } + + @Override + public int lowerBound() { + return 1; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public UpperCardinality upperBound() { + return UpperCardinalities.ZERO; + } + + @Override + public CardinalityInterval min(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval max(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval add(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval take(int count) { + return this; + } + + @Override + public CardinalityInterval multiply(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval meet(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval join(CardinalityInterval other) { + return other; + } + + @Override + public String toString() { + return "error"; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.cardinalityinterval; + +import tools.refinery.logic.term.uppercardinality.FiniteUpperCardinality; +import tools.refinery.logic.term.uppercardinality.UpperCardinality; + +import java.util.Objects; +import java.util.function.BinaryOperator; +import java.util.function.IntBinaryOperator; + +public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upperBound) implements CardinalityInterval { + public NonEmptyCardinalityInterval { + if (lowerBound < 0) { + throw new IllegalArgumentException("lowerBound must not be negative"); + } + if (upperBound.compareToInt(lowerBound) < 0) { + throw new IllegalArgumentException("lowerBound must not be larger than upperBound"); + } + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public CardinalityInterval min(CardinalityInterval other) { + return lift(other, Math::min, UpperCardinality::min); + } + + @Override + public CardinalityInterval max(CardinalityInterval other) { + return lift(other, Math::max, UpperCardinality::max); + } + + @Override + public CardinalityInterval add(CardinalityInterval other) { + return lift(other, Integer::sum, UpperCardinality::add); + } + + @Override + public CardinalityInterval multiply(CardinalityInterval other) { + return lift(other, (a, b) -> a * b, UpperCardinality::multiply); + } + + @Override + public CardinalityInterval meet(CardinalityInterval other) { + return lift(other, Math::max, UpperCardinality::min); + } + + @Override + public CardinalityInterval join(CardinalityInterval other) { + return lift(other, Math::min, UpperCardinality::max, this); + } + + @Override + public CardinalityInterval take(int count) { + int newLowerBound = Math.max(lowerBound - count, 0); + var newUpperBound = upperBound.take(count); + if (newUpperBound == null) { + return CardinalityIntervals.ERROR; + } + return CardinalityIntervals.between(newLowerBound, newUpperBound); + } + + private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator, + BinaryOperator upperOperator, + CardinalityInterval whenEmpty) { + if (other instanceof NonEmptyCardinalityInterval nonEmptyOther) { + return CardinalityIntervals.between(lowerOperator.applyAsInt(lowerBound, nonEmptyOther.lowerBound), + upperOperator.apply(upperBound, nonEmptyOther.upperBound)); + } + if (other instanceof EmptyCardinalityInterval) { + return whenEmpty; + } + throw new IllegalArgumentException("Unknown CardinalityInterval: " + other); + } + + private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator, + BinaryOperator upperOperator) { + return lift(other, lowerOperator, upperOperator, CardinalityIntervals.ERROR); + } + + @Override + public String toString() { + if (upperBound instanceof FiniteUpperCardinality finiteUpperCardinality && + finiteUpperCardinality.finiteUpperBound() == lowerBound) { + return "[%d]".formatted(lowerBound); + } + return "[%d..%s]".formatted(lowerBound, upperBound); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NonEmptyCardinalityInterval that = (NonEmptyCardinalityInterval) o; + return lowerBound == that.lowerBound && Objects.equals(upperBound, that.upperBound); + } + + @Override + public int hashCode() { + return lowerBound * 31 + upperBound.hashCode(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.comparable; + +import tools.refinery.logic.term.BinaryTerm; +import tools.refinery.logic.term.Term; + +public abstract class ComparisonTerm extends BinaryTerm { + protected ComparisonTerm(Class argumentType, Term left, Term right) { + super(Boolean.class, argumentType, argumentType, left, right); + } + + public Class getArgumentType() { + return getLeftType(); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.comparable; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class EqTerm extends ComparisonTerm { + public EqTerm(Class argumentType, Term left, Term right) { + super(argumentType, left, right); + } + + @Override + protected Boolean doEvaluate(T leftValue, T rightValue) { + return leftValue.equals(rightValue); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new EqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s == %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.comparable; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class GreaterEqTerm> extends ComparisonTerm { + public GreaterEqTerm(Class argumentType, Term left, Term right) { + super(argumentType, left, right); + } + + @Override + protected Boolean doEvaluate(T leftValue, T rightValue) { + return leftValue.compareTo(rightValue) >= 0; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new GreaterEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s >= %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.comparable; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class GreaterTerm> extends ComparisonTerm { + public GreaterTerm(Class argumentType, Term left, Term right) { + super(argumentType, left, right); + } + + @Override + protected Boolean doEvaluate(T leftValue, T rightValue) { + return leftValue.compareTo(rightValue) > 0; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new GreaterTerm<>(getArgumentType(), substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s > %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.comparable; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class LessEqTerm> extends ComparisonTerm { + public LessEqTerm(Class argumentType, Term left, Term right) { + super(argumentType, left, right); + } + + @Override + protected Boolean doEvaluate(T leftValue, T rightValue) { + return leftValue.compareTo(rightValue) <= 0; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new LessEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s <= %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.comparable; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class LessTerm> extends ComparisonTerm { + public LessTerm(Class argumentType, Term left, Term right) { + super(argumentType, left, right); + } + + @Override + protected Boolean doEvaluate(T leftValue, T rightValue) { + return leftValue.compareTo(rightValue) < 0; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new LessTerm<>(getArgumentType(), substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s < %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.comparable; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class NotEqTerm extends ComparisonTerm { + public NotEqTerm(Class argumentType, Term left, Term right) { + super(argumentType, left, right); + } + + @Override + protected Boolean doEvaluate(T leftValue, T rightValue) { + return !leftValue.equals(rightValue); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new NotEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s != %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntAddTerm extends IntBinaryTerm { + public IntAddTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntAddTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return leftValue + rightValue; + } + + @Override + public String toString() { + return "(%s + %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.term.BinaryTerm; +import tools.refinery.logic.term.Term; + +public abstract class IntBinaryTerm extends BinaryTerm { + protected IntBinaryTerm(Term left, Term right) { + super(Integer.class, Integer.class, Integer.class, left, right); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntDivTerm extends IntBinaryTerm { + public IntDivTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntDivTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return rightValue == 0 ? null : leftValue / rightValue; + } + + @Override + public String toString() { + return "(%s / %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntMaxTerm extends IntBinaryTerm { + public IntMaxTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntMaxTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return Math.max(rightValue, leftValue); + } + + @Override + public String toString() { + return "max(%s, %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntMinTerm extends IntBinaryTerm { + public IntMinTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntMinTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return Math.min(rightValue, leftValue); + } + + @Override + public String toString() { + return "min(%s, %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntMinusTerm extends IntUnaryTerm { + public IntMinusTerm(Term body) { + super(body); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new IntMinusTerm(substitutedBody); + } + + @Override + protected Integer doEvaluate(Integer bodyValue) { + return -bodyValue; + } + + @Override + public String toString() { + return "(-%s)".formatted(getBody()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntMulTerm extends IntBinaryTerm { + public IntMulTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntMulTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return leftValue * rightValue; + } + + @Override + public String toString() { + return "(%s * %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntPlusTerm extends IntUnaryTerm { + public IntPlusTerm(Term body) { + super(body); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new IntPlusTerm(substitutedBody); + } + + @Override + protected Integer doEvaluate(Integer bodyValue) { + return bodyValue; + } + + @Override + public String toString() { + return "(+%s)".formatted(getBody()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntPowTerm extends IntBinaryTerm { + public IntPowTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntPowTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return rightValue < 0 ? null : power(leftValue, rightValue); + } + + private static int power(int base, int exponent) { + int accum = 1; + while (exponent > 0) { + if (exponent % 2 == 1) { + accum = accum * base; + } + base = base * base; + exponent = exponent / 2; + } + return accum; + } + + @Override + public String toString() { + return "(%s ** %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntSubTerm extends IntBinaryTerm { + public IntSubTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntSubTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return leftValue - rightValue; + } + + @Override + public String toString() { + return "(%s - %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.term.StatelessAggregator; + +public final class IntSumAggregator implements StatelessAggregator { + public static final IntSumAggregator INSTANCE = new IntSumAggregator(); + + private IntSumAggregator() { + } + + @Override + public Class getResultType() { + return Integer.class; + } + + @Override + public Class getInputType() { + return Integer.class; + } + + @Override + public Integer getEmptyResult() { + return 0; + } + + @Override + public Integer add(Integer current, Integer value) { + return current + value; + } + + @Override + public Integer remove(Integer current, Integer value) { + return current - value; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.term.Aggregator; +import tools.refinery.logic.term.ConstantTerm; +import tools.refinery.logic.term.ExtremeValueAggregator; +import tools.refinery.logic.term.Term; +import tools.refinery.logic.term.comparable.*; +import tools.refinery.logic.term.comparable.*; + +import java.util.Comparator; + +public final class IntTerms { + public static final Aggregator INT_SUM = IntSumAggregator.INSTANCE; + public static final Aggregator INT_MIN = new ExtremeValueAggregator<>(Integer.class, + Integer.MAX_VALUE); + public static final Aggregator INT_MAX = new ExtremeValueAggregator<>(Integer.class, + Integer.MIN_VALUE, Comparator.reverseOrder()); + + private IntTerms() { + throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); + } + + public static Term constant(Integer value) { + return new ConstantTerm<>(Integer.class, value); + } + + public static Term plus(Term body) { + return new IntPlusTerm(body); + } + + public static Term minus(Term body) { + return new IntMinusTerm(body); + } + + public static Term add(Term left, Term right) { + return new IntAddTerm(left, right); + } + + public static Term sub(Term left, Term right) { + return new IntSubTerm(left, right); + } + + public static Term mul(Term left, Term right) { + return new IntMulTerm(left, right); + } + + public static Term div(Term left, Term right) { + return new IntDivTerm(left, right); + } + + public static Term pow(Term left, Term right) { + return new IntPowTerm(left, right); + } + + public static Term min(Term left, Term right) { + return new IntMinTerm(left, right); + } + + public static Term max(Term left, Term right) { + return new IntMaxTerm(left, right); + } + + public static Term eq(Term left, Term right) { + return new EqTerm<>(Integer.class, left, right); + } + + public static Term notEq(Term left, Term right) { + return new NotEqTerm<>(Integer.class, left, right); + } + + public static Term less(Term left, Term right) { + return new LessTerm<>(Integer.class, left, right); + } + + public static Term lessEq(Term left, Term right) { + return new LessEqTerm<>(Integer.class, left, right); + } + + public static Term greater(Term left, Term right) { + return new GreaterTerm<>(Integer.class, left, right); + } + + public static Term greaterEq(Term left, Term right) { + return new GreaterEqTerm<>(Integer.class, left, right); + } + + public static Term asInt(Term body) { + return new RealToIntTerm(body); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.term.UnaryTerm; +import tools.refinery.logic.term.Term; + +public abstract class IntUnaryTerm extends UnaryTerm { + protected IntUnaryTerm(Term body) { + super(Integer.class, Integer.class, body); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.int_; + +import tools.refinery.logic.term.Term; +import tools.refinery.logic.term.UnaryTerm; +import tools.refinery.logic.substitution.Substitution; + +public class RealToIntTerm extends UnaryTerm { + protected RealToIntTerm(Term body) { + super(Integer.class, Double.class, body); + } + + @Override + protected Integer doEvaluate(Double bodyValue) { + return bodyValue.isNaN() ? null : bodyValue.intValue(); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new RealToIntTerm(substitutedBody); + } + + @Override + public String toString() { + return "(%s as int)".formatted(getBody()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.term.UnaryTerm; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class IntToRealTerm extends UnaryTerm { + protected IntToRealTerm(Term body) { + super(Double.class, Integer.class, body); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new IntToRealTerm(substitutedBody); + } + + @Override + protected Double doEvaluate(Integer bodyValue) { + return bodyValue.doubleValue(); + } + + @Override + public String toString() { + return "(%s as real)".formatted(getBody()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealAddTerm extends RealBinaryTerm { + public RealAddTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealAddTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return leftValue + rightValue; + } + + @Override + public String toString() { + return "(%s + %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.term.BinaryTerm; +import tools.refinery.logic.term.Term; + +public abstract class RealBinaryTerm extends BinaryTerm { + protected RealBinaryTerm(Term left, Term right) { + super(Double.class, Double.class, Double.class, left, right); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealDivTerm extends RealBinaryTerm { + public RealDivTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealDivTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return leftValue / rightValue; + } + + @Override + public String toString() { + return "(%s / %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealMaxTerm extends RealBinaryTerm { + public RealMaxTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealMaxTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return Math.max(leftValue, rightValue); + } + + @Override + public String toString() { + return "max(%s, %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealMinTerm extends RealBinaryTerm { + public RealMinTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealMinTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return Math.min(leftValue, rightValue); + } + + @Override + public String toString() { + return "min(%s, %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealMinusTerm extends RealUnaryTerm { + public RealMinusTerm(Term body) { + super(body); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new RealMinusTerm(substitutedBody); + } + + @Override + protected Double doEvaluate(Double bodyValue) { + return -bodyValue; + } + + @Override + public String toString() { + return "(-%s)".formatted(getBody()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealMulTerm extends RealBinaryTerm { + public RealMulTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealMulTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return leftValue * rightValue; + } + + @Override + public String toString() { + return "(%s * %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealPlusTerm extends RealUnaryTerm { + public RealPlusTerm(Term body) { + super(body); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new RealPlusTerm(substitutedBody); + } + + @Override + protected Double doEvaluate(Double bodyValue) { + return bodyValue; + } + + @Override + public String toString() { + return "(+%s)".formatted(getBody()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealPowTerm extends RealBinaryTerm { + public RealPowTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealPowTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return Math.pow(leftValue, rightValue); + } + + @Override + public String toString() { + return "(%s ** %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class RealSubTerm extends RealBinaryTerm { + public RealSubTerm(Term left, Term right) { + super(left, right); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealSubTerm(substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return leftValue - rightValue; + } + + @Override + public String toString() { + return "(%s - %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.term.StatefulAggregate; +import tools.refinery.logic.term.StatefulAggregator; + +import java.util.Map; +import java.util.TreeMap; + +public final class RealSumAggregator implements StatefulAggregator { + public static final RealSumAggregator INSTANCE = new RealSumAggregator(); + + private RealSumAggregator() { + } + + @Override + public Class getResultType() { + return Double.class; + } + + @Override + public Class getInputType() { + return Double.class; + } + + @Override + public StatefulAggregate createEmptyAggregate() { + return new Aggregate(); + } + + @Override + public Double getEmptyResult() { + return 0d; + } + + private static class Aggregate implements StatefulAggregate { + private final Map values; + + public Aggregate() { + values = new TreeMap<>(); + } + + private Aggregate(Aggregate other) { + values = new TreeMap<>(other.values); + } + + @Override + public void add(Double value) { + values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1); + } + + @Override + public void remove(Double value) { + values.compute(value, (theValue, currentCount) -> { + if (currentCount == null || currentCount <= 0) { + throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue)); + } + return currentCount.equals(1) ? null : currentCount - 1; + }); + } + + @Override + public Double getResult() { + return values.entrySet() + .stream() + .mapToDouble(entry -> entry.getKey() * entry.getValue()) + .reduce(Double::sum) + .orElse(0d); + } + + @Override + public boolean isEmpty() { + return values.isEmpty(); + } + + @Override + public StatefulAggregate deepCopy() { + return new Aggregate(this); + } + + @Override + public boolean contains(Double value) { + return values.containsKey(value); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.term.Aggregator; +import tools.refinery.logic.term.ConstantTerm; +import tools.refinery.logic.term.ExtremeValueAggregator; +import tools.refinery.logic.term.Term; +import tools.refinery.logic.term.comparable.*; +import tools.refinery.logic.term.comparable.*; + +import java.util.Comparator; + +public final class RealTerms { + public static final Aggregator REAL_SUM = RealSumAggregator.INSTANCE; + public static final Aggregator REAL_MIN = new ExtremeValueAggregator<>(Double.class, + Double.POSITIVE_INFINITY); + public static final Aggregator REAL_MAX = new ExtremeValueAggregator<>(Double.class, + Double.NEGATIVE_INFINITY, Comparator.reverseOrder()); + + private RealTerms() { + throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); + } + + public static Term constant(Double value) { + return new ConstantTerm<>(Double.class, value); + } + + public static Term plus(Term body) { + return new RealPlusTerm(body); + } + + public static Term minus(Term body) { + return new RealMinusTerm(body); + } + + public static Term add(Term left, Term right) { + return new RealAddTerm(left, right); + } + + public static Term sub(Term left, Term right) { + return new RealSubTerm(left, right); + } + + public static Term mul(Term left, Term right) { + return new RealMulTerm(left, right); + } + + public static Term div(Term left, Term right) { + return new RealDivTerm(left, right); + } + + public static Term pow(Term left, Term right) { + return new RealPowTerm(left, right); + } + + public static Term min(Term left, Term right) { + return new RealMinTerm(left, right); + } + + public static Term max(Term left, Term right) { + return new RealMaxTerm(left, right); + } + + public static Term eq(Term left, Term right) { + return new EqTerm<>(Double.class, left, right); + } + + public static Term notEq(Term left, Term right) { + return new NotEqTerm<>(Double.class, left, right); + } + + public static Term less(Term left, Term right) { + return new LessTerm<>(Double.class, left, right); + } + + public static Term lessEq(Term left, Term right) { + return new LessEqTerm<>(Double.class, left, right); + } + + public static Term greater(Term left, Term right) { + return new GreaterTerm<>(Double.class, left, right); + } + + public static Term greaterEq(Term left, Term right) { + return new GreaterEqTerm<>(Double.class, left, right); + } + + public static Term asReal(Term body) { + return new IntToRealTerm(body); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.real; + +import tools.refinery.logic.term.UnaryTerm; +import tools.refinery.logic.term.Term; + +public abstract class RealUnaryTerm extends UnaryTerm { + protected RealUnaryTerm(Term body) { + super(Double.class, Double.class, body); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.truthvalue; + +public enum TruthValue { + TRUE("true"), + + FALSE("false"), + + UNKNOWN("unknown"), + + ERROR("error"); + + private final String name; + + TruthValue(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static TruthValue toTruthValue(boolean value) { + return value ? TRUE : FALSE; + } + + public boolean isConsistent() { + return this != ERROR; + } + + public boolean isComplete() { + return this != UNKNOWN; + } + + public boolean isConcrete() { + return this == TRUE || this == FALSE; + } + + public boolean must() { + return this == TRUE || this == ERROR; + } + + public boolean may() { + return this == TRUE || this == UNKNOWN; + } + + public TruthValue not() { + return switch (this) { + case TRUE -> FALSE; + case FALSE -> TRUE; + default -> this; + }; + } + + public TruthValue merge(TruthValue other) { + return switch (this) { + case TRUE -> other == UNKNOWN || other == TRUE ? TRUE : ERROR; + case FALSE -> other == UNKNOWN || other == FALSE ? FALSE : ERROR; + case UNKNOWN -> other; + case ERROR -> ERROR; + }; + } + + public TruthValue join(TruthValue other) { + return switch (this) { + case TRUE -> other == ERROR || other == TRUE ? TRUE : UNKNOWN; + case FALSE -> other == ERROR || other == FALSE ? FALSE : UNKNOWN; + case UNKNOWN -> UNKNOWN; + case ERROR -> other; + }; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.truthvalue; + +import tools.refinery.logic.AbstractDomain; + +import java.util.Optional; + +// Singleton pattern, because there is only one domain for truth values. +@SuppressWarnings("squid:S6548") +public final class TruthValueDomain implements AbstractDomain { + public static final TruthValueDomain INSTANCE = new TruthValueDomain(); + + private TruthValueDomain() { + } + + @Override + public Class abstractType() { + return TruthValue.class; + } + + @Override + public Class concreteType() { + return Boolean.class; + } + + @Override + public TruthValue toAbstract(Boolean concreteValue) { + return TruthValue.toTruthValue(concreteValue); + } + + @Override + public Optional toConcrete(TruthValue abstractValue) { + return switch (abstractValue) { + case TRUE -> Optional.of(true); + case FALSE -> Optional.of(false); + default -> Optional.empty(); + }; + } + + @Override + public boolean isConcrete(TruthValue abstractValue) { + return abstractValue.isConcrete(); + } + + @Override + public TruthValue commonRefinement(TruthValue leftValue, TruthValue rightValue) { + return leftValue.merge(rightValue); + } + + @Override + public TruthValue commonAncestor(TruthValue leftValue, TruthValue rightValue) { + return leftValue.join(rightValue); + } + + @Override + public TruthValue unknown() { + return TruthValue.UNKNOWN; + } + + @Override + public boolean isError(TruthValue abstractValue) { + return !abstractValue.isConsistent(); + } +} + 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.IntBinaryOperator; + +public record FiniteUpperCardinality(int finiteUpperBound) implements UpperCardinality { + public FiniteUpperCardinality { + if (finiteUpperBound < 0) { + throw new IllegalArgumentException("finiteUpperBound must not be negative"); + } + } + + @Override + public UpperCardinality add(UpperCardinality other) { + return lift(other, Integer::sum); + } + + @Override + @Nullable + public UpperCardinality take(int count) { + if (finiteUpperBound < count) { + return null; + } + return new FiniteUpperCardinality(finiteUpperBound - count); + } + + @Override + public UpperCardinality multiply(UpperCardinality other) { + return lift(other, (a, b) -> a * b); + } + + @Override + public int compareTo(@NotNull UpperCardinality upperCardinality) { + if (upperCardinality instanceof FiniteUpperCardinality finiteUpperCardinality) { + return compareToInt(finiteUpperCardinality.finiteUpperBound); + } + if (upperCardinality instanceof UnboundedUpperCardinality) { + return -1; + } + throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality); + } + + @Override + public int compareToInt(int value) { + return Integer.compare(finiteUpperBound, value); + } + + @Override + public String toString() { + return Integer.toString(finiteUpperBound); + } + + private UpperCardinality lift(@NotNull UpperCardinality other, IntBinaryOperator operator) { + if (other instanceof FiniteUpperCardinality finiteUpperCardinality) { + return UpperCardinalities.atMost(operator.applyAsInt(finiteUpperBound, + finiteUpperCardinality.finiteUpperBound)); + } + if (other instanceof UnboundedUpperCardinality) { + return UpperCardinalities.UNBOUNDED; + } + throw new IllegalArgumentException("Unknown UpperCardinality: " + other); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FiniteUpperCardinality that = (FiniteUpperCardinality) o; + return finiteUpperBound == that.finiteUpperBound; + } + + @Override + public int hashCode() { + return finiteUpperBound; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.jetbrains.annotations.NotNull; + +// Singleton implementation, because there is only a single countable infinity. +@SuppressWarnings("squid:S6548") +public final class UnboundedUpperCardinality implements UpperCardinality { + static final UnboundedUpperCardinality INSTANCE = new UnboundedUpperCardinality(); + + private UnboundedUpperCardinality() { + // Singleton constructor. + } + + @Override + public UpperCardinality add(UpperCardinality other) { + return this; + } + + @Override + public UpperCardinality take(int count) { + return this; + } + + @Override + public UpperCardinality multiply(UpperCardinality other) { + return this; + } + + // This should always be greater than any finite cardinality. + @SuppressWarnings("ComparatorMethodParameterNotUsed") + @Override + public int compareTo(@NotNull UpperCardinality upperCardinality) { + if (upperCardinality instanceof FiniteUpperCardinality) { + return 1; + } + if (upperCardinality instanceof UnboundedUpperCardinality) { + return 0; + } + throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality); + } + + @Override + public int compareToInt(int value) { + return 1; + } + + @Override + public String toString() { + return "*"; + } + + @Override + public boolean equals(Object obj) { + return this == obj || (obj != null && getClass() == obj.getClass()); + } + + @Override + public int hashCode() { + return -1; + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +public final class UpperCardinalities { + public static final UpperCardinality UNBOUNDED = UnboundedUpperCardinality.INSTANCE; + + public static final UpperCardinality ZERO; + + public static final UpperCardinality ONE; + + private static final FiniteUpperCardinality[] cache = new FiniteUpperCardinality[256]; + + static { + for (int i = 0; i < cache.length; i++) { + cache[i] = new FiniteUpperCardinality(i); + } + ZERO = cache[0]; + ONE = cache[1]; + } + + private UpperCardinalities() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static UpperCardinality atMost(int upperBound) { + if (upperBound < 0) { + return UNBOUNDED; + } + if (upperBound < cache.length) { + return cache[upperBound]; + } + return new FiniteUpperCardinality(upperBound); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import org.jetbrains.annotations.Nullable; + +public sealed interface UpperCardinality extends Comparable permits FiniteUpperCardinality, + UnboundedUpperCardinality { + default UpperCardinality min(UpperCardinality other) { + return this.compareTo(other) <= 0 ? this : other; + } + + default UpperCardinality max(UpperCardinality other) { + return this.compareTo(other) >= 0 ? this : other; + } + + UpperCardinality add(UpperCardinality other); + + @Nullable + UpperCardinality take(int count); + + UpperCardinality multiply(UpperCardinality other); + + int compareToInt(int value); + + static UpperCardinality of(int upperBound) { + return UpperCardinalities.atMost(upperBound); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class UpperCardinalityAddTerm extends UpperCardinalityBinaryTerm { + protected UpperCardinalityAddTerm(Term left, Term right) { + super(left, right); + } + + @Override + protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { + return leftValue.add(rightValue); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new UpperCardinalityAddTerm(substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s + %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import tools.refinery.logic.term.BinaryTerm; +import tools.refinery.logic.term.Term; + +public abstract class UpperCardinalityBinaryTerm extends BinaryTerm { + protected UpperCardinalityBinaryTerm(Term left, Term right) { + super(UpperCardinality.class, UpperCardinality.class, UpperCardinality.class, left, right); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class UpperCardinalityMaxTerm extends UpperCardinalityBinaryTerm { + protected UpperCardinalityMaxTerm(Term left, Term right) { + super(left, right); + } + + @Override + protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { + return leftValue.max(rightValue); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new UpperCardinalityMaxTerm(substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "max(%s, %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class UpperCardinalityMinTerm extends UpperCardinalityBinaryTerm { + protected UpperCardinalityMinTerm(Term left, Term right) { + super(left, right); + } + + @Override + protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { + return leftValue.min(rightValue); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new UpperCardinalityMinTerm(substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "min(%s, %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.Term; + +public class UpperCardinalityMulTerm extends UpperCardinalityBinaryTerm { + protected UpperCardinalityMulTerm(Term left, Term right) { + super(left, right); + } + + @Override + protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { + return leftValue.multiply(rightValue); + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, Term substitutedRight) { + return new UpperCardinalityMulTerm(substitutedLeft, substitutedRight); + } + + @Override + public String toString() { + return "(%s * %s)".formatted(getLeft(), getRight()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import tools.refinery.logic.term.StatefulAggregate; +import tools.refinery.logic.term.StatefulAggregator; + +// Singleton implementation, since there is only one way to aggregate upper cardinalities. +@SuppressWarnings("squid:S6548") +public class UpperCardinalitySumAggregator implements StatefulAggregator { + public static final UpperCardinalitySumAggregator INSTANCE = new UpperCardinalitySumAggregator(); + + private UpperCardinalitySumAggregator() { + } + + @Override + public Class getResultType() { + return UpperCardinality.class; + } + + @Override + public Class getInputType() { + return UpperCardinality.class; + } + + @Override + public StatefulAggregate createEmptyAggregate() { + return new Aggregate(); + } + + private static class Aggregate implements StatefulAggregate { + private int sumFiniteUpperBounds; + private int countUnbounded; + + public Aggregate() { + this(0, 0); + } + + private Aggregate(int sumFiniteUpperBounds, int countUnbounded) { + this.sumFiniteUpperBounds = sumFiniteUpperBounds; + this.countUnbounded = countUnbounded; + } + + @Override + public void add(UpperCardinality value) { + if (value instanceof FiniteUpperCardinality finiteUpperCardinality) { + sumFiniteUpperBounds += finiteUpperCardinality.finiteUpperBound(); + } else if (value instanceof UnboundedUpperCardinality) { + countUnbounded += 1; + } else { + throw new IllegalArgumentException("Unknown UpperCardinality: " + value); + } + } + + @Override + public void remove(UpperCardinality value) { + if (value instanceof FiniteUpperCardinality finiteUpperCardinality) { + sumFiniteUpperBounds -= finiteUpperCardinality.finiteUpperBound(); + } else if (value instanceof UnboundedUpperCardinality) { + countUnbounded -= 1; + } else { + throw new IllegalArgumentException("Unknown UpperCardinality: " + value); + } + } + + @Override + public UpperCardinality getResult() { + return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(sumFiniteUpperBounds); + } + + @Override + public boolean isEmpty() { + return sumFiniteUpperBounds == 0 && countUnbounded == 0; + } + + @Override + public StatefulAggregate deepCopy() { + return new Aggregate(sumFiniteUpperBounds, countUnbounded); + } + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.term.uppercardinality; + +import tools.refinery.logic.term.Aggregator; +import tools.refinery.logic.term.ConstantTerm; +import tools.refinery.logic.term.ExtremeValueAggregator; +import tools.refinery.logic.term.Term; +import tools.refinery.logic.term.comparable.*; + +import java.util.Comparator; + +public final class UpperCardinalityTerms { + public static final Aggregator UPPER_CARDINALITY_SUM = + UpperCardinalitySumAggregator.INSTANCE; + public static final Aggregator UPPER_CARDINALITY_MIN = + new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.UNBOUNDED); + public static final Aggregator UPPER_CARDINALITY_MAX = + new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.ZERO, Comparator.reverseOrder()); + + private UpperCardinalityTerms() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static Term constant(UpperCardinality value) { + return new ConstantTerm<>(UpperCardinality.class, value); + } + + public static Term add(Term left, Term right) { + return new UpperCardinalityAddTerm(left, right); + } + + public static Term mul(Term left, Term right) { + return new UpperCardinalityMulTerm(left, right); + } + + public static Term min(Term left, Term right) { + return new UpperCardinalityMinTerm(left, right); + } + + public static Term max(Term left, Term right) { + return new UpperCardinalityMaxTerm(left, right); + } + + public static Term eq(Term left, Term right) { + return new EqTerm<>(UpperCardinality.class, left, right); + } + + public static Term notEq(Term left, Term right) { + return new NotEqTerm<>(UpperCardinality.class, left, right); + } + + public static Term less(Term left, Term right) { + return new LessTerm<>(UpperCardinality.class, left, right); + } + + public static Term lessEq(Term left, Term right) { + return new LessEqTerm<>(UpperCardinality.class, left, right); + } + + public static Term greater(Term left, Term right) { + return new GreaterTerm<>(UpperCardinality.class, left, right); + } + + public static Term greaterEq(Term left, Term right) { + return new GreaterEqTerm<>(UpperCardinality.class, left, right); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.util; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class CycleDetectingMapper { + private static final String SEPARATOR = " -> "; + + private final Function getName; + + private final Function doMap; + + private final Set inProgress = new LinkedHashSet<>(); + + private final Map results = new HashMap<>(); + + public CycleDetectingMapper(Function doMap) { + this(Objects::toString, doMap); + } + + public CycleDetectingMapper(Function getName, Function doMap) { + this.getName = getName; + this.doMap = doMap; + } + + public R map(T input) { + if (inProgress.contains(input)) { + var path = inProgress.stream().map(getName).collect(Collectors.joining(SEPARATOR)); + throw new IllegalArgumentException("Circular reference %s%s%s detected".formatted(path, SEPARATOR, + getName.apply(input))); + } + // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant + // way, which would cause a ConcurrentModificationException with computeIfAbsent. + @SuppressWarnings("squid:S3824") + var result = results.get(input); + if (result == null) { + inProgress.add(input); + try { + result = doMap.apply(input); + results.put(input, result); + } finally { + inProgress.remove(input); + } + } + return result; + } + + public List getInProgress() { + return List.copyOf(inProgress); + } + + public R getAlreadyMapped(T input) { + return results.get(input); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.valuation; + +import tools.refinery.logic.term.AnyDataVariable; +import tools.refinery.logic.term.DataVariable; + +import java.util.Map; + +record MapBasedValuation(Map values) implements Valuation { + @Override + public T getValue(DataVariable variable) { + if (!values.containsKey(variable)) { + throw new IllegalArgumentException("No value for variable %s".formatted(variable)); + } + var value = values.get(variable); + return variable.getType().cast(value); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.valuation; + +import tools.refinery.logic.term.AnyDataVariable; +import tools.refinery.logic.term.DataVariable; + +import java.util.Set; + +public record RestrictedValuation(Valuation valuation, Set allowedVariables) implements Valuation { + @Override + public T getValue(DataVariable variable) { + if (!allowedVariables.contains(variable)) { + throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable)); + } + return valuation.getValue(variable); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.valuation; + +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.DataVariable; + +public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation { + @Override + public T getValue(DataVariable variable) { + return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable)); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.valuation; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.logic.substitution.Substitution; +import tools.refinery.logic.term.AnyDataVariable; +import tools.refinery.logic.term.DataVariable; + +import java.util.Map; +import java.util.Set; + +public interface Valuation { + T getValue(DataVariable variable); + + default Valuation substitute(@Nullable Substitution substitution) { + if (substitution == null) { + return this; + } + return new SubstitutedValuation(this, substitution); + } + + default Valuation restrict(Set allowedVariables) { + return new RestrictedValuation(this, Set.copyOf(allowedVariables)); + } + + static ValuationBuilder builder() { + return new ValuationBuilder(); + } + + static Valuation empty() { + return new MapBasedValuation(Map.of()); + } +} 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.logic.valuation; + +import tools.refinery.logic.term.AnyDataVariable; +import tools.refinery.logic.term.DataVariable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ValuationBuilder { + private final Map values = new HashMap<>(); + + ValuationBuilder() { + } + + public ValuationBuilder put(DataVariable variable, T value) { + return putChecked(variable, value); + } + + public ValuationBuilder putChecked(AnyDataVariable variable, Object value) { + if (value != null && !variable.getType().isInstance(value)) { + throw new IllegalArgumentException("Value %s is not an instance of %s" + .formatted(value, variable.getType().getName())); + } + if (values.containsKey(variable)) { + throw new IllegalArgumentException("Already has value for variable %s".formatted(variable)); + } + values.put(variable, value); + return this; + } + + public Valuation build() { + return new MapBasedValuation(Collections.unmodifiableMap(values)); + } +} -- cgit v1.2.3-54-g00ecf