brush-strokes-0.1.0.0: src/cusps/bench/Main.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
-- base
import Control.Arrow
( second )
import Control.Monad
( when )
import Data.Foldable
( for_ )
import qualified Data.List.NonEmpty as NE
( NonEmpty(..)
, fromList, head, length, sort
)
import Data.Semigroup
( Arg(..), Sum(..), Product(..) )
import Data.Traversable
( for )
import GHC.Clock
( getMonotonicTime )
import Numeric
( showFFloat )
-- code-page
import System.IO.CodePage
( withCP65001 )
-- containers
import qualified Data.IntMap.Strict as IntMap
( fromList, toList )
import Data.Tree
( foldTree )
-- deepseq
import Control.DeepSeq
( rnf )
-- brush-strokes
import Math.Bezier.Stroke
import Math.Interval
import Math.Linear
import Math.Root.Isolation
import Math.Root.Isolation.Core
import Math.Root.Isolation.Newton
import Math.Root.Isolation.Newton.GaussSeidel
-- brush-strokes bench:cusps
import Bench.Cases
import Bench.Types
--------------------------------------------------------------------------------
pattern ROUND_UP :: Word
pattern ROUND_UP = 0x4000
foreign import ccall "set_sse_rounding_mode"
setSSERoundingMode :: Word -> IO ()
main :: IO ()
main = withCP65001 $ do
setSSERoundingMode ROUND_UP
putStrLn "Running cusp-finding benchmarks."
for_ benchGroups $ \ ( groupName, benchGroupCases ) -> do
putStrLn $ unlines
[ replicate 40 '='
, "Benchmark group '" ++ groupName ++ "':" ]
testsWithTime <- for benchGroupCases $ \ tst -> do
dt <- benchTestCase groupName tst
return $ Arg dt tst
let Arg bestTime bestTest = NE.head $ NE.sort testsWithTime
when ( NE.length benchGroupCases >= 1 ) $
putStrLn $ unlines $
[ "Best time in '" ++ groupName ++ "' group: " ++ show bestTime ++ "s" ]
++ [ " (" ++ descr ++ ")"
| let descr = testDescription bestTest
, not ( null descr )
]
-- TODO: run the benchmark multiple times to reduce noise.
benchTestCase :: String -> TestCase -> IO Double
benchTestCase testName ( TestCase { testDescription, testBrushStroke, testCuspOptions, testStartBoxes } ) = do
putStr $ unlines
[ " " ++ replicate 38 '-'
, " -- Test case: " ++ testName ++ ( if null testDescription then "" else " (" ++ testDescription ++ ")" )
, " --" ]
before <- getMonotonicTime
let ( _, cornerAndCuspFnI ) = brushStrokeFunctions testBrushStroke
( trees, dunno, sols ) =
foldMap
( \ ( i, ( trees, DoneBoxes { doneSolBoxes = defCusps, doneGiveUpBoxes = mbCusps } ) ) ->
( map ( i, ) trees, map ( ( i , ) . fst ) mbCusps, map ( i, ) defCusps ) ) $
IntMap.toList $
findCuspsIn testCuspOptions ( snd . cornerAndCuspFnI ) $
IntMap.fromList
[ ( i, boxes )
| ( i, boxes ) <- testStartBoxes
]
rnf dunno `seq` rnf sols `seq` return ()
after <- getMonotonicTime
let dt = after - before
putStrLn $ unlines $
showResultsList "sols" sols
++
showResultsList "dunno" dunno
++
[ " -- Time elapsed: " ++ show dt ++ "s"
, " -- Tree size: " ++ show (getSum $ foldMap (foldMap sizeRootIsolationTree) trees)
, " -- Newton stats: "
] ++ map ( " -- " ++ ) ( showNewtonStats (foldMap (foldMap newtonStats) trees) )
return dt
-- | Format a list of results with empty set symbol for empty lists
showResultsList :: Show a => String -> [a] -> [String]
showResultsList label items =
[ " -- " ++ label ++ ":" ++ (if null items then " β
" else "") ]
++
[ " -- β’ " ++ show item | item <- items ]
sizeRootIsolationTree :: ( Box 2, RootIsolationTree ( Box 2 ) ) -> Sum Int
sizeRootIsolationTree ( box, tree ) = foldMap ( const $ Sum 1 ) ( showRootIsolationTree box tree )
data NewtonStats
= NewtonStats
{ newtonImprovement :: !( Sum Double )
, newtonElimination :: !( Sum Int, BigBoxes )
, newtonTime :: !( Sum Double )
, newtonTotal :: !( Sum Int )
}
instance Semigroup NewtonStats where
NewtonStats imp1 el1 t1 tot1 <> NewtonStats imp2 el2 t2 tot2 =
NewtonStats ( imp1 <> imp2 ) ( el1 <> el2 ) ( t1 <> t2 ) ( tot1 <> tot2 )
instance Monoid NewtonStats where
mempty = NewtonStats mempty mempty mempty mempty
showNewtonStats :: NewtonStats -> [ String ]
showNewtonStats ( NewtonStats ( Sum improv ) ( Sum elims, big ) ( Sum time ) ( Sum tot ) )
| tot == 0
= [ ]
| otherwise
= [ "average improvement: " ++ showPercent ( improv / fromIntegral tot )
, "average eliminations: " ++ showPercent ( fromIntegral elims / fromIntegral tot )
, "time elapsed: " ++ show ( time / fromIntegral tot ) ++ "s"
]
showPercent :: Double -> String
showPercent x = fixed 3 2 ( 100 * x ) <> "%"
fixed :: Int -> Int -> Double -> String
fixed digitsBefore digitsAfter x =
case second ( drop 1 ) . break ( == '.' ) $ showFFloat ( Just digitsAfter ) x "" of
( as, bs ) ->
let
l, r :: Int
l = length as
r = length bs
in
replicate ( digitsBefore - l ) ' ' <> as <> "." <> bs <> replicate ( digitsAfter - r ) '0'
data BigBoxes =
BigBoxes
{ biggestArea :: Maybe ( Box 2 )
, biggestX :: Maybe ( Box 2 )
, biggestY :: Maybe ( Box 2 )
}
deriving stock ( Show, Eq )
instance Semigroup BigBoxes where
BigBoxes area1 x1 y1 <> BigBoxes area2 x2 y2 =
BigBoxes
( pickBig boxArea area1 area2 )
( pickBig widthX x1 x2 )
( pickBig widthY y1 y2 )
where
pickBig _ Nothing Nothing = Nothing
pickBig _ (Just x1) Nothing = Just x1
pickBig _ Nothing (Just x2) = Just x2
pickBig f j1@(Just x1) j2@(Just x2)
| f x1 >= f x2
= j1
| otherwise
= j2
widthX ( πβ2 x _y ) = width x
widthY ( πβ2 _x y ) = width y
instance Monoid BigBoxes where
mempty = BigBoxes Nothing Nothing Nothing
newtonStats :: ( Box 2, RootIsolationTree ( Box 2 ) ) -> NewtonStats
newtonStats ( _box, RootIsolationLeaf {} ) = mempty
newtonStats ( box, RootIsolationStep step boxes )
| IsolationStep @Newton ( _, TimeInterval dt ) <- step
= thisImprovement dt <> foldMap newtonStats boxes
| otherwise
= foldMap newtonStats boxes
where
thisImprovement dt =
NewtonStats
{ newtonImprovement = Sum $ ( old - new ) / old
, newtonElimination =
if new == 0
then ( Sum 1, BigBoxes ( Just box ) ( Just box ) ( Just box ) )
else mempty
, newtonTime = Sum dt
, newtonTotal = Sum 1
}
where
old = boxArea box
new = sum ( map ( boxArea . fst ) boxes )
benchGroups :: [ ( String, NE.NonEmpty TestCase ) ]
benchGroups =
[ ( "ellipse"
, NE.fromList
[ ( ellipseTestCase ( mkOpts gs )
( 0, 1 ) pi
( defaultStartBoxes [ 0 .. 3 ] )
) { testDescription = show gs }
| gs <- [ GS_Partial, GS_Complete ]
]
)
, ( "trickyCusp2", NE.fromList [ trickyCusp2TestCase ] )
, ( "rotation", NE.fromList [ ( rotationTestCase ( mkOpts gs ) ) { testDescription = show gs }
| gs <- [ GS_Partial, GS_Complete ] ] )
, ( "missingCusp2", NE.fromList [ missingCusp2TestCase ] )
]
where
minWidth = 1e-5
Ξ΅_bis = 5e-3
mkOpts gs_opts =
RootIsolationOptions
{ rootIsolationAlgorithms = \ hist ->
defaultRootIsolationAlgorithms minWidth Ξ΅_bis
( newtOpts gs_opts hist ) hist
}
newtOpts gs_opts hist =
NewtonLP
--NewtonGaussSeidel $
-- ( defaultGaussSeidelOptions @2 @3 hist )
-- { gsUpdate = gs_opts }
--------------------------------------------------------------------------------
{- -- Old testing code.
getR1 (β1 u) = u
eval
:: ( I i 1 -> Seq ( I i 1 -> StrokeDatum k i ) )
-> ( I i 1, Int, I i 1 )
-> StrokeDatum k i
eval f ( t, i, s ) = ( f t `Seq.index` i ) s
mkVal :: Double -> Int -> Double -> ( β 1, Int, β 1 )
mkVal t i s = ( β1 t, i, β1 s )
mkI :: ( Double, Double ) -> π
mkI ( lo, hi ) = π lo hi
mkBox :: ( Double, Double ) -> ( Double, Double ) -> Box 2
mkBox ( t_min, t_max ) ( s_min, s_max ) =
( π ( β2 t_min s_min ) ( β2 t_max s_max ) )
potentialCusp :: StrokeDatum 3 π -> Bool
potentialCusp
( StrokeDatum
{ ee = D22 { _D22_v = π ( β1 ee_min ) ( β1 ee_max ) }
, πΏEπΏsdcdt = D12 { _D12_v = T ( π ( β2 vx_min vy_min ) ( β2 vx_max vy_max ) )}
}
) = ee_min <= 0 && ee_max >= 0
&& vx_min <= 0 && vx_max >= 0
&& vy_min <= 0 && vy_max >= 0
dEdsdcdt :: StrokeDatum k i -> D ( k - 2 ) ( I i 2 ) ( T ( I i 2 ) )
dEdsdcdt ( StrokeDatum { πΏEπΏsdcdt = v } ) = v
width :: πβ 1 -> Double
width (π (β1 lo) (β1 hi)) = hi - lo
negV2 :: πβ 2 -> πβ 2
negV2 ( π ( β2 x_lo y_lo ) ( β2 x_hi y_hi ) ) =
let !( π x'_lo x'_hi ) = negate $ π x_lo x_hi
!( π y'_lo y'_hi ) = negate $ π y_lo y_hi
in π ( β2 x'_lo y'_lo ) ( β2 x'_hi y'_hi )
midV2 :: πβ 2 -> β 2
midV2 ( π ( β2 x_lo y_lo ) ( β2 x_hi y_hi ) ) =
β2 ( 0.5 * ( x_lo + x_hi ) ) ( 0.5 * ( y_lo + y_hi ) )
logLines :: [ String ]
logLines =
[ "E, dE/ds * dc/dt"
, "{" ++
(intercalate ","
[ "{" ++ showD t ++ "," ++ showD s ++ ",{" ++ intercalate "," vals ++ "}}"
| t <- [ 0.5484, 0.5484 + 0.00001 .. 0.5488 ]
, s <- [ 0.5479, 0.5479 + 0.00001 .. 0.5483 ]
, let StrokeDatum
{ ee = D22 ee _ _ _ _ _
, πΏEπΏsdcdt = D12 (T f) (T (T f_t)) (T (T f_s))
} = (curvesI (singleton (β1 t)) `Seq.index` i) (singleton (β1 s))
β2 vx vy = midPoint2 f
--β2 vx_t vy_t = midPoint2 f_t
--β2 vx_s vy_s = midPoint2 f_s
vals = [ showD ( midPoint ee )
, "{" ++ showD vx ++ "," ++ showD vy ++ "}"
-- , "{" ++ showD vx_t ++ "," ++ showD vy_t ++ "}"
-- , "{" ++ showD vx_s ++ "," ++ showD vy_s ++ "}"
]
]
) ++ "}"
]
where
i = 2
( curves, curvesI ) = brushStrokeFunctions $ ellipseBrushStroke ( 0, 1 ) pi
midPoint (π (β1 lo) (β1 hi)) = 0.5 * ( lo + hi )
midPoint2 (π (β2 lo_x lo_y) (β2 hi_x hi_y))
= β2 ( 0.5 * ( lo_x + hi_x ) ) ( 0.5 * ( lo_y + hi_y ) )
_x ( β2 x _ ) = x
_y ( β2 _ y ) = y
showD :: Double -> String
showD float = showFFloat (Just 6) float ""
-}