packages feed

moonlight-planar-1.1.0.0: test/lifted-sign/Moonlight/Planar/LiftedSignCases.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Shared inputs for the exact sign law and its forced timing instrument.
-- Construction, rational normalization, and distribution naming stay outside
-- the predicate timings.
module Moonlight.Planar.LiftedSignCases
  ( LiftedSignFamily (..)
  , LiftedSignInput
  , liftedSignCases
  , candidateSign
  , magnitudeSign
  , permutedInputs
  , conventionInputs
  ) where

import Control.DeepSeq (NFData)
import Data.Bits (shiftL)
import Data.List (permutations)
import GHC.Generics (Generic)
import Moonlight.Planar.Internal.ExactRational
  ( ExactArithmeticError
  , exactRational
  , exactRationalFromDyadic
  , exactSignum
  )
import Moonlight.Planar.Internal.PowerDiagram.Generator
  ( ExactPowerGenerator (..)
  , exactGeneratorLiftedSign
  , exactGeneratorLiftedVolume
  )

data LiftedSignFamily
  = IntegralCoordinates
  | DyadicCoordinates
  | OddDenominators
  | DisparateExponents
  | ExactlyCoplanar
  | NearlyCoplanar
  | LargeRationalWeights
  | PowerSiteGenerators
  deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)
  deriving anyclass (NFData)

type LiftedSignInput =
  ( ExactPowerGenerator Int
  , ExactPowerGenerator Int
  , ExactPowerGenerator Int
  , ExactPowerGenerator Int
  )

liftedSignCases
  :: Either ExactArithmeticError [(LiftedSignFamily, [LiftedSignInput])]
liftedSignCases =
  traverse
    (\family -> (,) family <$> traverse (familyInput family) [1 .. 128])
    [minBound .. maxBound]

familyInput
  :: LiftedSignFamily
  -> Int
  -> Either ExactArithmeticError LiftedSignInput
familyInput family seed =
  (,,,)
    <$> familyGenerator family seed 0
    <*> familyGenerator family seed 1
    <*> familyGenerator family seed 2
    <*> familyGenerator family seed 3

familyGenerator
  :: LiftedSignFamily
  -> Int
  -> Int
  -> Either ExactArithmeticError (ExactPowerGenerator Int)
familyGenerator family seed label =
  let value axis = toInteger ((seed * (17 + axis * 11) + label * (31 + axis * 7)) `mod` 127 - 63)
      dyadic axis = exactRationalFromDyadic (value axis) (negate (3 + (seed + axis * 17 + label * 11) `mod` 54))
      oddCoordinate axis = exactRational (value axis) (toInteger (3 + 2 * ((seed + label * 7 + axis * 11) `mod` 61)))
      construct = ExactPowerGenerator label
   in case family of
        IntegralCoordinates ->
          pure (construct (fromInteger (value 0)) (fromInteger (value 1)) (fromInteger (value 2)))
        DyadicCoordinates -> pure (construct (dyadic 0) (dyadic 1) (dyadic 2))
        OddDenominators -> construct <$> oddCoordinate 0 <*> oddCoordinate 1 <*> oddCoordinate 2
        DisparateExponents ->
          let coordinateExponent :: Int -> Int
              coordinateExponent axis = case (label + axis + seed) `mod` 4 of
                0 -> -1074
                1 -> -513
                2 -> 17
                _ -> 1023
           in pure
                ( construct
                    (exactRationalFromDyadic (value 0) (coordinateExponent 0))
                    (exactRationalFromDyadic (value 1) (coordinateExponent 1))
                    (exactRationalFromDyadic (value 2) (coordinateExponent 2))
                )
        ExactlyCoplanar -> do
          coordinateX <- oddCoordinate 0
          coordinateY <- oddCoordinate 1
          pure (construct coordinateX coordinateY (3 * coordinateX - 2 * coordinateY + 7))
        NearlyCoplanar ->
          let coordinateX = fromIntegral (label `mod` 2)
              coordinateY = fromIntegral (label `quot` 2)
              perturbation =
                if label == 3
                  then exactRationalFromDyadic (if even seed then 1 else -1) (negate (seed * 9))
                  else 0
           in pure (construct coordinateX coordinateY (3 * coordinateX - 2 * coordinateY + 7 + perturbation))
        LargeRationalWeights -> do
          weight <-
            exactRational
              (((1 :: Integer) `shiftL` (1024 + seed)) + value 2)
              (((1 :: Integer) `shiftL` (127 + label * 13)) - 1)
          pure (construct (dyadic 0) (dyadic 1) weight)
        PowerSiteGenerators ->
          let positionX = dyadic 0
              positionY = dyadic 1
              weight = dyadic 2
           in pure (construct (2 * positionX) (2 * positionY) (weight - positionX * positionX - positionY * positionY))

candidateSign :: LiftedSignInput -> Ordering
candidateSign (firstGenerator, secondGenerator, thirdGenerator, fourthGenerator) =
  exactGeneratorLiftedSign firstGenerator secondGenerator thirdGenerator fourthGenerator

magnitudeSign :: LiftedSignInput -> Ordering
magnitudeSign (firstGenerator, secondGenerator, thirdGenerator, fourthGenerator) =
  exactSignum (exactGeneratorLiftedVolume firstGenerator secondGenerator thirdGenerator fourthGenerator)

-- | All row permutations, without a partial list-to-tuple conversion.
permutedInputs :: LiftedSignInput -> [LiftedSignInput]
permutedInputs (firstGenerator, secondGenerator, thirdGenerator, fourthGenerator) =
  [ (firstPermuted, secondPermuted, thirdPermuted, fourthPermuted)
  | [firstPermuted, secondPermuted, thirdPermuted, fourthPermuted] <-
      permutations [firstGenerator, secondGenerator, thirdGenerator, fourthGenerator]
  ]

-- | Leading homogeneous 1 gives a positive determinant for the unit
-- tetrahedron, hence an above-plane query conflicts with a CCW slope face.
conventionInputs :: [(Ordering, LiftedSignInput)]
conventionInputs =
  let origin = ExactPowerGenerator 0 0 0 0
      onX = ExactPowerGenerator 1 1 0 0
      onY = ExactPowerGenerator 2 0 1 0
      above = ExactPowerGenerator 3 0 0 1
      below = ExactPowerGenerator 3 0 0 (-1)
   in [ (GT, (origin, onX, onY, above))
      , (LT, (origin, onY, onX, above))
      , (LT, (origin, onX, onY, below))
      , (EQ, (origin, onX, onY, origin))
      ]