moonlight-planar-1.1.0.0: bench/hex/PlanarSelectionBench.hs
module PlanarSelectionBench
( holedRegion
, runPlanarRepeated
) where
import BenchMeasure (requireRight, timedValue)
import Control.DeepSeq (force)
import Control.Exception (evaluate)
import Data.Foldable (traverse_)
import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty qualified as NonEmpty
import Data.Ratio ((%), denominator, numerator)
import Moonlight.Hex.Coordinate (HexCoord (..))
import Moonlight.Hex.Element (hexCellVertices, hexVertexCoordinates)
import Moonlight.Hex.Planar
( hexRegionByCenterInPlanarRegion
, hexRegionFullyCoveredByPlanarRegion
, hexRegionIntersectingPlanarRegion
, hexRegionPlanarRegion
)
import Moonlight.Hex.Region
( HexLayout
, HexRegion
, fullHexRegion
, hexLayout
, hexLayoutCellCount
, hexLayoutWordCount
, hexRegionCardinality
)
import Moonlight.Planar.Exact (ExactPoint, exactPoint, exactRational)
import Moonlight.Planar.Region
( ExactLoop
, PlanarRegion
, exactLoop
, planarRegion
, polygonComponent
)
import PlanarSelectionBaseline qualified as Baseline
import System.Mem (performGC)
import Test.Tasty.Bench (Benchmark, bench, bgroup, defaultMain, nf)
-- Every function application below receives the same fully evaluated pair.
-- The region builders, output-agreement checks, and one-shot allocation
-- receipts are outside the repeated Tasty timing distribution.
runPlanarRepeated :: IO ()
runPlanarRepeated = do
fixtures <- planarFixtures >>= evaluate . force
traverse_ reportFixture fixtures
defaultMain (fmap benchmarkFixture fixtures)
type PlanarFixture = (String, (HexLayout, PlanarRegion))
benchmarkFixture :: PlanarFixture -> Benchmark
benchmarkFixture (label, input) =
bgroup label
[ bgroup "centre"
[ bench "baseline" (nf (uncurry Baseline.hexRegionByCenterInPlanarRegion) input)
, bench "candidate" (nf (uncurry hexRegionByCenterInPlanarRegion) input)
]
, bgroup "coverage"
[ bench "baseline" (nf (uncurry Baseline.hexRegionFullyCoveredByPlanarRegion) input)
, bench "candidate" (nf (uncurry hexRegionFullyCoveredByPlanarRegion) input)
]
, bgroup "intersection"
[ bench "baseline" (nf (uncurry Baseline.hexRegionIntersectingPlanarRegion) input)
, bench "candidate" (nf (uncurry hexRegionIntersectingPlanarRegion) input)
]
]
reportFixture :: PlanarFixture -> IO ()
reportFixture (label, input@(layout, _)) = do
baselineCentre <- measured "centre/baseline" Baseline.hexRegionByCenterInPlanarRegion
candidateCentre <- measured "centre/candidate" hexRegionByCenterInPlanarRegion
baselineCoverage <- measured "coverage/baseline" Baseline.hexRegionFullyCoveredByPlanarRegion
candidateCoverage <- measured "coverage/candidate" hexRegionFullyCoveredByPlanarRegion
baselineIntersection <- measured "intersection/baseline" Baseline.hexRegionIntersectingPlanarRegion
candidateIntersection <- measured "intersection/candidate" hexRegionIntersectingPlanarRegion
if baselineCentre == candidateCentre && baselineCoverage == candidateCoverage && baselineIntersection == candidateIntersection
then putStrLn
( label <> "-matched-receipt: cells=" <> show (hexLayoutCellCount layout)
<> " words=" <> show (hexLayoutWordCount layout)
<> " selected=" <> show (hexRegionCardinality candidateCentre, hexRegionCardinality candidateCoverage, hexRegionCardinality candidateIntersection)
<> " centre-coverage-intersection=equal"
)
else fail (label <> ": baseline/candidate packed outputs disagree")
where
measured :: String -> (HexLayout -> PlanarRegion -> HexRegion) -> IO HexRegion
measured lane classify = do
performGC
timedValue (label <> "/" <> lane) (evaluate (force (uncurry classify input)))
planarFixtures :: IO [PlanarFixture]
planarFixtures = do
holed <- holedRegion
holedFixtures <- traverse (holedFixture holed) [("holed/1", 1, 1), ("holed/85", 17, 5), ("holed/4096", 64, 64)]
alignedFixtures <- traverse alignedFixture [("aligned/1", HexCoord 0 0, 1, 1), ("aligned/85", HexCoord (-8) (-2), 17, 5)]
contactFixtures <-
concat <$> traverse boundaryFixtures
[ ("vertex-contact", [rectangle 2 0 3 1], [])
, ("side-contact", [rectangle (-1) 1 1 2], [])
, ("tiny-island", [rectangle (2 % 5) (1 % 10) (3 % 5) (3 % 10)], [])
, ("tiny-hole", [rectangle (-12) (-12) 12 12], [rectangle (2 % 5) (1 % 10) (3 % 5) (3 % 10)])
, ("sliver", [sliver], [])
, ("empty", [], [])
]
translated <- rationalComponents [fmap (translatePoint largeOrigin) sliver] []
translatedLayout <- requireRight (hexLayout (offsetOrigin largeOrigin (-8) (-2)) 17 5)
negative <- rationalComponents [fmap (translatePoint (HexCoord (-31) (-17))) sliver] []
negativeLayout <- requireRight (hexLayout (HexCoord (-39) (-19)) 17 5)
dense <- rationalComponents [fmap (\(q, r) -> (3 * q, 2 * r + q)) [(-2, -2), (514, -2), (514, 514), (-2, 514)]] []
denseLayout <- requireRight (hexLayout (HexCoord 0 0) 512 512)
pure
( holedFixtures <> alignedFixtures <> contactFixtures
<> [ ("large-translation/85", (translatedLayout, translated))
, ("negative-translation/85", (negativeLayout, negative))
, ("dense/262144", (denseLayout, dense))
]
)
where
holedFixture :: PlanarRegion -> (String, Int, Int) -> IO PlanarFixture
holedFixture region (label, width, height) = do
layout <- requireRight (hexLayout (HexCoord 0 0) width height)
pure (label, (layout, region))
alignedFixture :: (String, HexCoord, Int, Int) -> IO PlanarFixture
alignedFixture (label, origin, width, height) = do
layout <- requireRight (hexLayout origin width height)
region <- requireRight (hexRegionPlanarRegion (fullHexRegion layout))
pure (label, (layout, region))
boundaryFixtures :: (String, [[RationalPoint]], [[RationalPoint]]) -> IO [PlanarFixture]
boundaryFixtures (label, outers, holes) = do
region <- rationalComponents outers holes
traverse
(\(suffix, origin, width, height) -> do
layout <- requireRight (hexLayout origin width height)
pure (label <> suffix, (layout, region)))
[("/1", HexCoord 0 0, 1, 1), ("/85", HexCoord (-8) (-2), 17, 5)]
rectangle :: Rational -> Rational -> Rational -> Rational -> [RationalPoint]
rectangle left bottom right top =
[(left, bottom), (right, bottom), (right, top), (left, top)]
sliver :: [RationalPoint]
sliver = [(-22, -7), (22, 7), (22, 7 + 1 % 1024), (-22, -7 + 1 % 1024)]
largeOrigin :: HexCoord
largeOrigin = HexCoord (2 ^ (55 :: Int)) (negate (2 ^ (54 :: Int)))
offsetOrigin :: HexCoord -> Int -> Int -> HexCoord
offsetOrigin (HexCoord q r) dq dr = HexCoord (q + dq) (r + dr)
type RationalPoint = (Rational, Rational)
translatePoint :: HexCoord -> RationalPoint -> RationalPoint
translatePoint (HexCoord q r) (x, y) =
( x + fromInteger (3 * toInteger q)
, y + fromInteger (2 * toInteger r + toInteger q)
)
rationalComponents :: [[RationalPoint]] -> [[RationalPoint]] -> IO PlanarRegion
rationalComponents outers holes = do
admittedHoles <- traverse (admitLoop . reverse) holes
components <- traverse
(\outer -> admitLoop outer >>= \loop -> requireRight (polygonComponent loop admittedHoles))
outers
requireRight (planarRegion components)
where
admitLoop :: [RationalPoint] -> IO ExactLoop
admitLoop points = do
exactPoints <- traverse pointOf points
maybe (fail "planar benchmark loop is empty") (requireRight . exactLoop) (NonEmpty.nonEmpty exactPoints)
pointOf :: RationalPoint -> IO ExactPoint
pointOf (x, y) =
exactPoint
<$> requireRight (exactRational (numerator x) (denominator x))
<*> requireRight (exactRational (numerator y) (denominator y))
holedRegion :: IO PlanarRegion
holedRegion = do
outer <-
loopOf
[ (5, 2, 7, 3)
, (541, 3, 29, 7)
, (191, 1, 201, 2)
, (852, 5, 556, 3)
, (181, 3, 1711, 9)
, (31, 3, 452, 3)
]
square <- loopOf [(121, 3, 121, 3), (121, 3, 211, 3), (211, 3, 211, 3), (211, 3, 121, 3)]
triangle <- loopOf [(241, 2, 241, 2), (241, 2, 321, 2), (451, 3, 281, 2)]
cellHole <-
requireRight
( exactLoop
( NonEmpty.reverse
( fmap
(\vertex -> let (x, y) = hexVertexCoordinates vertex in exactPoint (fromInteger x) (fromInteger y))
(hexCellVertices (HexCoord 30 20))
)
)
)
component <- requireRight (polygonComponent outer [square, cellHole, triangle])
requireRight (planarRegion [component])
where
loopOf :: [(Integer, Integer, Integer, Integer)] -> IO ExactLoop
loopOf fractions = do
points <- traverse pointOf fractions
case points of
firstPoint : rest -> requireRight (exactLoop (firstPoint :| rest))
[] -> fail "holedRegion: empty loop"
pointOf :: (Integer, Integer, Integer, Integer) -> IO ExactPoint
pointOf (xNumerator, xDenominator, yNumerator, yDenominator) =
exactPoint
<$> requireRight (exactRational xNumerator xDenominator)
<*> requireRight (exactRational yNumerator yDenominator)