packages feed

moonlight-triangulation-0.1.0.0: test/native/Moonlight/Triangulation/FilteredPredicateOptimizationSpec.hs

{-# LANGUAGE NumericUnderscores #-}
{-# OPTIONS_GHC -O1 #-}

-- | The allocation law for the optimized filtered-predicate artifact. This
-- module remains at O1 when the surrounding behavioral body is compiled at O0;
-- without the simplifier, the loop does not unbox and the witness is vacuous.
module Moonlight.Triangulation.FilteredPredicateOptimizationSpec
  ( assertFilteredPredicatesSkipExactOracle
  ) where

import Control.DeepSeq (force)
import Control.Exception (evaluate)
import Control.Monad (unless)
import GHC.Stats (allocated_bytes, getRTSStats, getRTSStatsEnabled)
import Moonlight.Triangulation.Math
  ( inCircle
  , inCircleDetApprox
  , orient2d
  , orientDetApprox
  )
import Moonlight.Triangulation.Types (Point (..))
import System.Mem (performGC)
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Data.Word (Word64)

-- | A filtered predicate must not evaluate its exact oracle when the floating
-- approximation already certifies the sign. The exact path allocates
-- 'Integer's, so equal-shaped certified and degenerate folds expose whether
-- that fallback remains genuinely conditional.
assertFilteredPredicatesSkipExactOracle :: IO ()
assertFilteredPredicatesSkipExactOracle = do
  enabled <- getRTSStatsEnabled
  unless enabled $ fail "allocation counters unavailable: the suite must run with -T"
  let count = 20_000
      bigScale = 2 ^^ (200 :: Int) :: Double
      smallScale = 2 ^^ (-200 :: Int) :: Double
      orientCertified =
        V.generate
          count
          (\index -> Point (fromIntegral index * bigScale) (if even index then 0 else smallScale))
      orientDegenerate =
        V.generate
          count
          (\index -> Point (fromIntegral index * bigScale) (fromIntegral index * smallScale))
      zigzag = V.generate count (\index -> Point (fromIntegral index) (if even index then 0 else 1))
      collinear = V.generate count (\index -> Point (fromIntegral index) (fromIntegral index))
      orientationIndices = U.enumFromN 0 (max 0 (count - 2))
      circleIndices = U.enumFromN 0 (max 0 (count - 3))
  _ <- evaluate (force orientCertified)
  _ <- evaluate (force orientDegenerate)
  _ <- evaluate (force zigzag)
  _ <- evaluate (force collinear)
  _ <- evaluate (force orientationIndices)
  _ <- evaluate (force circleIndices)
  certifiedSupport <- allocationOf (sumTripleRelations approximateOrientation orientationIndices orientCertified)
  fallbackSupport <- allocationOf (sumTripleRelations approximateOrientation orientationIndices orientDegenerate)
  certified <- allocationOf (sumTripleRelations orient2d orientationIndices orientCertified)
  fallback <- allocationOf (sumTripleRelations orient2d orientationIndices orientDegenerate)
  let certifiedOracle = allocationBeyondApproximation certified certifiedSupport
      fallbackOracle = allocationBeyondApproximation fallback fallbackSupport
  unless (fallbackOracle > 0) $
    fail "degenerate orientations allocated nothing: the measurement is not observing the oracle"
  unless (certifiedOracle * 8 < fallbackOracle) $
    fail
      ( "orient2d evaluates its exact oracle on certified input: "
          <> show certifiedOracle
          <> " excess bytes certified versus "
          <> show fallbackOracle
          <> " excess bytes degenerate (raw "
          <> show certified
          <> " versus "
          <> show fallback
          <> ")"
      )
  certifiedCircleSupport <- allocationOf (sumQuadRelations approximateInCircle circleIndices zigzag)
  fallbackCircleSupport <- allocationOf (sumQuadRelations approximateInCircle circleIndices collinear)
  certifiedCircle <- allocationOf (sumQuadRelations inCircle circleIndices zigzag)
  fallbackCircle <- allocationOf (sumQuadRelations inCircle circleIndices collinear)
  let certifiedCircleOracle = allocationBeyondApproximation certifiedCircle certifiedCircleSupport
      fallbackCircleOracle = allocationBeyondApproximation fallbackCircle fallbackCircleSupport
  unless (fallbackCircleOracle > 0) $
    fail "degenerate incircles allocated nothing: the measurement is not observing the oracle"
  unless (certifiedCircleOracle * 8 < fallbackCircleOracle) $
    fail
      ( "inCircle evaluates its exact oracle on certified input: "
          <> show certifiedCircleOracle
          <> " excess bytes certified versus "
          <> show fallbackCircleOracle
          <> " excess bytes degenerate (raw "
          <> show certifiedCircle
          <> " versus "
          <> show fallbackCircle
          <> ")"
      )

allocationOf :: Int -> IO Word64
allocationOf work = do
  performGC
  before <- allocated_bytes <$> getRTSStats
  _ <- evaluate work
  performGC
  after <- allocated_bytes <$> getRTSStats
  pure (after - before)

allocationBeyondApproximation :: Word64 -> Word64 -> Word64
allocationBeyondApproximation measured support = measured - min measured support

sumTripleRelations
  :: (Point -> Point -> Point -> Ordering)
  -> U.Vector Int
  -> V.Vector (Point)
  -> Int
sumTripleRelations relation indices points =
  U.foldl'
    (\accumulated index ->
       accumulated
         + fromEnum
           ( relation
               (V.unsafeIndex points index)
               (V.unsafeIndex points (index + 1))
               (V.unsafeIndex points (index + 2))
           )
    )
    0
    indices
{-# INLINE sumTripleRelations #-}

sumQuadRelations
  :: (Point -> Point -> Point -> Point -> Ordering)
  -> U.Vector Int
  -> V.Vector (Point)
  -> Int
sumQuadRelations relation indices points =
  U.foldl'
    (\accumulated index ->
       accumulated
         + fromEnum
           ( relation
               (V.unsafeIndex points index)
               (V.unsafeIndex points (index + 1))
               (V.unsafeIndex points (index + 2))
               (V.unsafeIndex points (index + 3))
           )
    )
    0
    indices
{-# INLINE sumQuadRelations #-}

approximateOrientation :: Point -> Point -> Point -> Ordering
approximateOrientation a b c = compare (orientDetApprox a b c) 0
{-# INLINE approximateOrientation #-}

approximateInCircle :: Point -> Point -> Point -> Point -> Ordering
approximateInCircle a b c d = compare (inCircleDetApprox a b c d) 0
{-# INLINE approximateInCircle #-}