moonlight-planar-1.1.0.0: bench/layer/Moonlight/Planar/LayerBench.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
-- | Timing and allocation evidence for exact affine envelopes and layer
-- composition. Manual lanes use only the pre-existing public operations a
-- caller otherwise has to compose and discard.
module Moonlight.Planar.LayerBench
( LayerBenchFixtures
, prepareFixtures
, runBenchmarks
) where
import Control.DeepSeq (NFData)
import Control.Monad (foldM)
import Data.Bifunctor (first)
import Data.Ratio (numerator, denominator)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as Map
import GHC.Generics (Generic)
import Moonlight.Planar.Exact (ExactPoint, exactPoint, exactRational)
import Moonlight.Planar.Internal.ExactRational (ExactRational)
import Moonlight.Planar.Overlay (OverlayResult, layerCovers, overlayAll, overlayConfusion, overlayLayers, overlayMass, overlayPlanarLayer, overlaySelectedRegion, foldBoundedOverlayCells)
import Moonlight.Planar.PowerDiagram (AffineForm (..), upperEnvelope)
import Moonlight.Planar.Region (PlanarLayer, PlanarRegion, PolygonComponent, exactLoop, planarLayer, planarLayerOutsideLabel, planarLayerRegions, planarRegion, planarRegionComponents, polygonComponent)
import Moonlight.Planar.Valuation (ExactArea, exactAreaValue, regionValuations, valuationArea, ExactPlanarMoments, orientedBoundaryMoments, polygonComponentMoments)
import Test.Tasty.Bench (bench, bgroup, defaultMain, nf)
data LayerBenchFixtures = LayerBenchFixtures
{ envelopeWindow :: !PolygonComponent
, envelopeForms16 :: !(Map.Map Int AffineForm)
, envelopeForms64 :: !(Map.Map Int AffineForm)
, envelopeForms169 :: !(Map.Map Int AffineForm)
, overlayLayers8 :: !(NonEmpty (PlanarLayer Bool))
, overlayLayers16 :: !(NonEmpty (PlanarLayer Bool))
, measuredOverlay :: !(OverlayResult (Bool, Bool))
, coverageLayer :: !(PlanarLayer Bool)
, momentBoundaries :: ![(String, [(ExactPoint, ExactPoint)])]
}
deriving stock (Generic)
deriving anyclass (NFData)
prepareFixtures :: IO LayerBenchFixtures
prepareFixtures = do
window <- rectangleComponent (-20) (-20) 20 20
layers8 <- prepareLayerFamily 8
layers16 <- prepareLayerFamily 16
left <- rectangleLayer 0 0 60 100
right <- rectangleLayer 40 0 100 100
massOverlay <- requireRight "mass benchmark overlay" (overlayLayers left right)
windowRegion <- requireRight "coverage benchmark region" (planarRegion [window])
completeCoverage <-
requireRight
"coverage benchmark layer"
(planarLayer False (Map.singleton True windowRegion))
boundaries <- traverse (traverse (traverse admitMomentEdge)) rationalMomentBoundaries
pure
LayerBenchFixtures
{ envelopeWindow = window
, envelopeForms16 = affineForms 16
, envelopeForms64 = affineForms 64
, envelopeForms169 = affineForms 169
, overlayLayers8 = layers8
, overlayLayers16 = layers16
, measuredOverlay = massOverlay
, coverageLayer = completeCoverage
, momentBoundaries = boundaries
}
runBenchmarks :: LayerBenchFixtures -> IO ()
runBenchmarks fixtures =
defaultMain
[ bgroup
"exact affine upper envelope"
[ bench "n=16" (nf (upperEnvelope (envelopeWindow fixtures)) (envelopeForms16 fixtures))
, bench "n=64" (nf (upperEnvelope (envelopeWindow fixtures)) (envelopeForms64 fixtures))
, bench "n=169" (nf (upperEnvelope (envelopeWindow fixtures)) (envelopeForms169 fixtures))
]
, bgroup
"n-ary overlay"
[ bench "fused/publish/n=8" (nf fusedPublishedOverlay (overlayLayers8 fixtures))
, bench "left-associated/publish/n=8" (nf leftAssociatedOverlay (overlayLayers8 fixtures))
, bench "fused/publish/n=16" (nf fusedPublishedOverlay (overlayLayers16 fixtures))
, bench "left-associated/publish/n=16" (nf leftAssociatedOverlay (overlayLayers16 fixtures))
]
, bgroup "oriented edge moments"
[ bench label (nf orientedBoundaryMoments boundary)
| (label, boundary) <- momentBoundaries fixtures
]
, bgroup
"overlay analytics"
[ bench "mass/direct" (nf directMass (measuredOverlay fixtures))
, bench "mass/publish-and-value" (nf publishedMass (measuredOverlay fixtures))
, bench "confusion/direct" (nf directConfusion (measuredOverlay fixtures))
, bench "confusion/repeated-publication" (nf publishedConfusion (measuredOverlay fixtures))
, bench "moments/direct" (nf directMoments (measuredOverlay fixtures))
, bench "moments/publish-and-value" (nf publishedMoments (measuredOverlay fixtures))
, bench
"coverage/complete"
(nf (coverageResult (coverageLayer fixtures)) (envelopeWindow fixtures))
]
]
fusedPublishedOverlay
:: NonEmpty (PlanarLayer Bool)
-> Either String (PlanarLayer (NonEmpty Bool))
fusedPublishedOverlay sources = do
result <- first show (overlayAll sources)
first show (overlayPlanarLayer result)
leftAssociatedOverlay
:: NonEmpty (PlanarLayer Bool)
-> Either String (PlanarLayer (NonEmpty Bool))
leftAssociatedOverlay (firstLayer :| remainingLayers) = do
initial <- singletonLabelLayer firstLayer
liftedRemaining <- traverse singletonLabelLayer remainingLayers
foldM overlayNext initial liftedRemaining
where
overlayNext left right = do
result <- first show (overlayLayers left right)
published <- first show (overlayPlanarLayer result)
flattenPublicLayer published
singletonLabelLayer
:: PlanarLayer Bool
-> Either String (PlanarLayer (NonEmpty Bool))
singletonLabelLayer layer =
first show
( planarLayer
(planarLayerOutsideLabel layer :| [])
( Map.fromList
[ (label :| [], region)
| (label, region) <- Map.toAscList (planarLayerRegions layer)
]
)
)
flattenPublicLayer
:: PlanarLayer (NonEmpty Bool, NonEmpty Bool)
-> Either String (PlanarLayer (NonEmpty Bool))
flattenPublicLayer layer =
first show
( planarLayer
(uncurry (<>) (planarLayerOutsideLabel layer))
( Map.fromList
[ (leftLabels <> rightLabels, region)
| ((leftLabels, rightLabels), region) <- Map.toAscList (planarLayerRegions layer)
]
)
)
directMass :: OverlayResult (Bool, Bool) -> Either String ExactArea
directMass = first show . overlayMass (== (True, True))
publishedMass :: OverlayResult (Bool, Bool) -> Either String ExactArea
publishedMass result = do
region <- first show (overlaySelectedRegion (== (True, True)) result)
valuationArea <$> first show (regionValuations region)
directConfusion :: OverlayResult (Bool, Bool) -> Either String (Map.Map (Bool, Bool) ExactRational)
directConfusion = fmap (fmap exactAreaValue) . first show . overlayConfusion
publishedConfusion
:: OverlayResult (Bool, Bool)
-> Either String (Map.Map (Bool, Bool) ExactRational)
publishedConfusion result =
Map.fromList
<$> traverse
(\labels -> (labels,) . exactAreaValue <$> publishedPairMass labels)
[(False, True), (True, False), (True, True)]
where
publishedPairMass labels = do
region <- first show (overlaySelectedRegion (== labels) result)
valuationArea <$> first show (regionValuations region)
directMoments :: OverlayResult (Bool, Bool) -> Either String ExactPlanarMoments
directMoments = first show . foldBoundedOverlayCells
(\moments labels boundary -> if labels == (True, True) then moments <> orientedBoundaryMoments boundary else moments)
mempty
publishedMoments :: OverlayResult (Bool, Bool) -> Either String ExactPlanarMoments
publishedMoments result = do
region <- first show (overlaySelectedRegion (== (True, True)) result)
pure (foldMap polygonComponentMoments (planarRegionComponents region))
coverageResult
:: PlanarLayer Bool
-> PolygonComponent
-> Either String ()
coverageResult layer = first show . layerCovers layer
prepareLayerFamily :: Int -> IO (NonEmpty (PlanarLayer Bool))
prepareLayerFamily count = do
layers <- traverse prepareLayer [0 .. count - 1]
case layers of
firstLayer : remaining -> pure (firstLayer :| remaining)
[] -> fail "layer benchmark requires a positive family size"
where
prepareLayer :: Int -> IO (PlanarLayer Bool)
prepareLayer index =
rectangleLayer
(-64 + fromIntegral ((7 * index) `mod` 31))
(-64 + fromIntegral ((11 * index) `mod` 29))
(32 + fromIntegral ((13 * index) `mod` 31))
(32 + fromIntegral ((17 * index) `mod` 29))
rectangleLayer :: Integer -> Integer -> Integer -> Integer -> IO (PlanarLayer Bool)
rectangleLayer minimumX minimumY maximumX maximumY = do
region <- rectangleRegion minimumX minimumY maximumX maximumY
requireRight "benchmark rectangle layer" (planarLayer False (Map.singleton True region))
rectangleRegion :: Integer -> Integer -> Integer -> Integer -> IO PlanarRegion
rectangleRegion minimumX minimumY maximumX maximumY = do
component <- rectangleComponent minimumX minimumY maximumX maximumY
requireRight "benchmark rectangle region" (planarRegion [component])
rectangleComponent :: Integer -> Integer -> Integer -> Integer -> IO PolygonComponent
rectangleComponent minimumX minimumY maximumX maximumY = do
loop <-
requireRight
"benchmark rectangle loop"
( exactLoop
( exactPoint (fromInteger minimumX) (fromInteger minimumY)
:| [ exactPoint (fromInteger maximumX) (fromInteger minimumY)
, exactPoint (fromInteger maximumX) (fromInteger maximumY)
, exactPoint (fromInteger minimumX) (fromInteger maximumY)
]
)
)
requireRight "benchmark rectangle component" (polygonComponent loop [])
affineForms :: Int -> Map.Map Int AffineForm
affineForms count =
Map.fromAscList
[ (index, tangentForm index)
| index <- [0 .. count - 1]
]
where
tangentForm :: Int -> AffineForm
tangentForm index =
let coordinateX = fromIntegral (index `mod` 17 - 8)
coordinateY = fromIntegral (index `div` 17 - 5)
in AffineForm
(negate (coordinateX * coordinateX + coordinateY * coordinateY))
(2 * coordinateX)
(2 * coordinateY)
requireRight :: Show failure => String -> Either failure value -> IO value
requireRight label = either (fail . ((label <> ": ") <>) . show) pure
-- Identical fixture covers are prepared before both copied benchmark binaries.
-- These lanes time the existing public six-moment kernel, not input admission.
-- The independent Rational formula oracle lives in the existing ValuationSpec.
rationalMomentBoundaries :: [(String, [((Rational, Rational), (Rational, Rational))])]
rationalMomentBoundaries =
[ ("integer/n=64", [((n, 2*n+1), (n+3, 1-n)) | n <- [-32 .. 31]])
, ("common-denominator/n=64", [((n/64, (2*n+1)/64), ((n+3)/64, (1-n)/64)) | n <- [-32 .. 31]])
, ("mixed-coprime/n=64", [((n/101, (2*n+1)/103), ((n+3)/107, (1-n)/109)) | n <- [-32 .. 31]])
, ("high-bitwidth/n=64", [(((huge+n)/p, (huge-n)/q), ((huge+2*n+1)/r, (n-huge)/s)) | n <- [-32 .. 31]])
, ("zero-cross/n=64", [((n/101, 2*n/101), ((n+3)/107, 2*(n+3)/107)) | n <- [-32 .. 31]])
]
where
huge = fromInteger (2 ^ (191 :: Int))
p = fromInteger (2 ^ (127 :: Int) - 1)
q = fromInteger (2 ^ (107 :: Int) - 1)
r = fromInteger (2 ^ (89 :: Int) - 1)
s = fromInteger (2 ^ (61 :: Int) - 1)
admitMomentEdge :: ((Rational, Rational), (Rational, Rational)) -> IO (ExactPoint, ExactPoint)
admitMomentEdge (from, to) = (,) <$> admitMomentPoint from <*> admitMomentPoint to
admitMomentPoint :: (Rational, Rational) -> IO ExactPoint
admitMomentPoint (x, y) =
exactPoint <$> admit x <*> admit y
where
admit :: Rational -> IO ExactRational
admit value = requireRight "moment benchmark coordinate"
(exactRational (numerator value) (denominator value))