packages feed

moonlight-algebra-0.1.0.2: test/abstract/QuantaleSpec.hs

module QuantaleSpec
  ( tests,
  )
where

import Data.List (maximumBy)
import Data.Ratio ((%))
import Hedgehog qualified as HH
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Moonlight.Algebra
  ( BoundedJoinSemilattice (..),
    ChainOrder (..),
    ChainQuantale,
    IntegralQuantale,
    JoinSemilattice (..),
    Quantale (..),
    ResiduatedQuantale (..),
    joinLeq,
  )
import Moonlight.Algebra
  ( Lukasiewicz (..),
    Tropical (..),
    Viterbi (..),
  )
import Test.Tasty
  ( TestTree,
    testGroup,
  )
import Test.Tasty.Hedgehog
  ( testProperty,
  )

tests :: TestTree
tests =
  testGroup
    "quantale"
    [ quantaleLawTests "Bool" Gen.bool,
      quantaleLawTests "Viterbi Rational" genViterbiRational,
      quantaleLawTests "Lukasiewicz Rational" genLukasiewiczRational,
      quantaleLawTests "Tropical Rational" genTropicalRational,
      quantaleLawTests "(Bool, Viterbi Rational)" genBoolViterbiPair,
      testGroup
        "integral markers"
        [ integralQuantaleLawTests "Bool" Gen.bool,
          integralQuantaleLawTests "Viterbi Rational" genViterbiRational,
          integralQuantaleLawTests "Lukasiewicz Rational" genLukasiewiczRational,
          integralQuantaleLawTests "(Bool, Viterbi Rational)" genBoolViterbiPair
        ],
      testGroup
        "chain markers"
        [ chainQuantaleLawTests "Bool" Gen.bool,
          chainQuantaleLawTests "Viterbi Rational" genViterbiRational,
          chainQuantaleLawTests "Lukasiewicz Rational" genLukasiewiczRational,
          chainQuantaleLawTests "Tropical Rational (signed)" genTropicalSignedRational
        ],
      testGroup
        "the chain order induced by a selective join"
        [ chainOrderLawTests "Viterbi Rational" genViterbiRational,
          chainOrderLawTests "Lukasiewicz Rational" genLukasiewiczRational,
          chainOrderLawTests "Tropical Rational (signed)" genTropicalSignedRational
        ]
    ]

quantaleLawTests ::
  (ResiduatedQuantale carrier, Eq carrier, Show carrier) =>
  String ->
  HH.Gen carrier ->
  TestTree
quantaleLawTests label gen =
  testGroup
    label
    [ testProperty "tensor is associative" $ HH.property $ do
        left <- HH.forAll gen
        middle <- HH.forAll gen
        right <- HH.forAll gen
        tensor (tensor left middle) right HH.=== tensor left (tensor middle right),
      testProperty "tensorUnit is a left identity" $ HH.property $ do
        value <- HH.forAll gen
        tensor tensorUnit value HH.=== value,
      testProperty "tensorUnit is a right identity" $ HH.property $ do
        value <- HH.forAll gen
        tensor value tensorUnit HH.=== value,
      testProperty "tensor is commutative" $ HH.property $ do
        left <- HH.forAll gen
        right <- HH.forAll gen
        tensor left right HH.=== tensor right left,
      testProperty "tensor distributes over join on the left" $ HH.property $ do
        left <- HH.forAll gen
        middle <- HH.forAll gen
        right <- HH.forAll gen
        tensor left (join middle right)
          HH.=== join (tensor left middle) (tensor left right),
      testProperty "tensor distributes over join on the right" $ HH.property $ do
        left <- HH.forAll gen
        middle <- HH.forAll gen
        right <- HH.forAll gen
        tensor (join left middle) right
          HH.=== join (tensor left right) (tensor middle right),
      testProperty "bottom annihilates tensor on the left" $ HH.property $ do
        value <- HH.forAll gen
        tensor bottom value HH.=== bottom,
      testProperty "bottom annihilates tensor on the right" $ HH.property $ do
        value <- HH.forAll gen
        tensor value bottom HH.=== bottom,
      testProperty "residual is right adjoint to tensor" $ HH.property $ do
        left <- HH.forAll gen
        middle <- HH.forAll gen
        right <- HH.forAll gen
        joinLeq (tensor left middle) right HH.=== joinLeq left (residual middle right),
      testProperty "tensorUnit is the greatest element" $ HH.property $ do
        value <- HH.forAll gen
        HH.assert (joinLeq value tensorUnit)
    ]

integralQuantaleLawTests ::
  (IntegralQuantale carrier, Eq carrier, Show carrier) =>
  String ->
  HH.Gen carrier ->
  TestTree
integralQuantaleLawTests label gen =
  testProperty (label <> ": tensor is two-sided deflationary") $ HH.property $ do
    left <- HH.forAll gen
    right <- HH.forAll gen
    HH.assert (joinLeq (tensor left right) left)
    HH.assert (joinLeq (tensor left right) right)

chainQuantaleLawTests ::
  (ChainQuantale carrier, Eq carrier, Show carrier) =>
  String ->
  HH.Gen carrier ->
  TestTree
chainQuantaleLawTests label gen =
  testProperty (label <> ": join is selective") $ HH.property $ do
    left <- HH.forAll gen
    right <- HH.forAll gen
    HH.assert (join left right == left || join left right == right)

chainOrderLawTests ::
  (ChainQuantale carrier, Eq carrier, Show carrier) =>
  String ->
  HH.Gen carrier ->
  TestTree
chainOrderLawTests label gen =
  testGroup
    label
    [ testProperty "strict ascent is the lattice order minus equality" $ HH.property $ do
        left <- HH.forAll gen
        right <- HH.forAll gen
        (compare (ChainOrder left) (ChainOrder right) == LT)
          HH.=== (joinLeq left right && left /= right),
      testProperty "the order is antisymmetric" $ HH.property $ do
        left <- HH.forAll gen
        right <- HH.forAll gen
        compare (ChainOrder left) (ChainOrder left) HH.=== EQ
        (chainOrderLeq left right && chainOrderLeq right left)
          HH.=== (left == right),
      testProperty "the order is transitive" $ HH.property $ do
        left <- HH.forAll gen
        middle <- HH.forAll gen
        right <- HH.forAll gen
        let triple = [left, middle, right]
        HH.assert
          ( and
              [ not (chainOrderLeq lower between && chainOrderLeq between upper)
                  || chainOrderLeq lower upper
              | lower <- triple,
                between <- triple,
                upper <- triple
              ]
          ),
      testProperty "the greatest element under the order attains the join" $ HH.property $ do
        values <- HH.forAll (Gen.list (Range.linear 1 12) gen)
        getChainOrder (maximumBy compare (map ChainOrder values))
          HH.=== foldr1 join values
    ]
  where
    chainOrderLeq :: (ChainQuantale value, Eq value) => value -> value -> Bool
    chainOrderLeq lower upper =
      compare (ChainOrder lower) (ChainOrder upper) /= GT

genUnitIntervalRational :: HH.Gen Rational
genUnitIntervalRational = do
  denominator <- Gen.integral (Range.linear 1 12)
  numerator <- Gen.integral (Range.linear 0 denominator)
  pure (numerator % denominator)

genViterbiRational :: HH.Gen (Viterbi Rational)
genViterbiRational =
  Viterbi <$> genUnitIntervalRational

genLukasiewiczRational :: HH.Gen (Lukasiewicz Rational)
genLukasiewiczRational =
  Lukasiewicz <$> genUnitIntervalRational

genTropicalRational :: HH.Gen (Tropical Rational)
genTropicalRational =
  Gen.frequency
    [ (1, pure TropicalInfinity),
      (6, TropicalFinite <$> genNonnegativeRational)
    ]

genTropicalSignedRational :: HH.Gen (Tropical Rational)
genTropicalSignedRational =
  Gen.frequency
    [ (1, pure TropicalInfinity),
      (6, TropicalFinite <$> genSignedRational)
    ]

genSignedRational :: HH.Gen Rational
genSignedRational = do
  denominator <- Gen.integral (Range.linear 1 12)
  numerator <- Gen.integral (Range.linearFrom 0 (-12 * denominator) (12 * denominator))
  pure (numerator % denominator)

genNonnegativeRational :: HH.Gen Rational
genNonnegativeRational = do
  denominator <- Gen.integral (Range.linear 1 12)
  numerator <- Gen.integral (Range.linear 0 (12 * denominator))
  pure (numerator % denominator)

genBoolViterbiPair :: HH.Gen (Bool, Viterbi Rational)
genBoolViterbiPair =
  (,) <$> Gen.bool <*> genViterbiRational