metabrush/brush-strokes/bench/Main.hs
2024-02-24 19:37:34 +01:00

371 lines
12 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
module Main
( main
-- Testing
, TestCase(..)
, testCases
, testCaseStrokeFunctions
, eval
, mkVal, mkBox
, potentialCusp
, dEdsdcdt
)
where
-- base
import Control.Concurrent.MVar
( newMVar )
import Data.Coerce
( coerce )
import Data.Foldable
( for_ )
import Data.List
( intercalate )
import GHC.Exts
( Proxy#, proxy# )
import GHC.Generics
( Generic )
import GHC.TypeNats
( type (-) )
import Numeric
( showFFloat )
-- containers
import Data.Sequence
( Seq )
import qualified Data.Sequence as Seq
( index )
import Data.Tree
( foldTree )
-- brush-strokes
import Calligraphy.Brushes
import Debug.Utils
( logToFile )
import Math.Algebra.Dual
import Math.Bezier.Spline
import Math.Bezier.Stroke
import Math.Bezier.Stroke.EnvelopeEquation
import Math.Differentiable
import Math.Interval
import Math.Linear
import Math.Module
import Math.Ring
( Transcendental )
--------------------------------------------------------------------------------
main :: IO ()
main = for_ testCases $ \ testCase@( TestCase { testName, testAlgorithmParams } ) -> do
let ( _, testStrokeFnI ) = testCaseStrokeFunctions testCase
( newtTrees, ( dunno, sols ) ) = computeCusps testAlgorithmParams testStrokeFnI
showedTrees = map ( uncurry showIntervalNewtonTree ) newtTrees
putStrLn $ unlines $
[ ""
, "Test case '" ++ testName ++ "':" ] ++
map ( " " ++ )
[ " #sols: " ++ show (length sols)
, "#dunno: " ++ show (length dunno)
, "#trees: " ++ show @Int (sum @_ @Int $ map (foldTree ( \ _ bs -> 1 + sum bs )) showedTrees)
, " dunno: " ++ show dunno
, " sols: " ++ show sols
]
--logFileMVar <- newMVar "logs/trickyCusp.log"
--logToFile logFileMVar (unlines logLines)
-- `seq` return ()
testCases :: [ TestCase ]
testCases = [ ellipse , trickyCusp2 ]
--------------------------------------------------------------------------------
data TestCase =
forall nbParams. ParamsCt nbParams =>
TestCase
{ testName :: !String
, testBrush :: !( Brush nbParams )
, testStroke :: !( Point nbParams, Curve Open () ( Point nbParams ))
, testAlgorithmParams :: !CuspAlgorithmParams
}
testCaseStrokeFunctions
:: TestCase
-> ( 1 -> Seq ( 1 -> StrokeDatum 2 () )
, 𝕀 1 -> Seq ( 𝕀 1 -> StrokeDatum 3 𝕀 ) )
testCaseStrokeFunctions ( TestCase { testStroke = ( sp0, crv ), testBrush } ) =
getStrokeFunctions testBrush sp0 crv
-- Utilities to use in GHCi to help debugging.
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 )
mkBox :: ( Double, Double ) -> Int -> ( Double, Double ) -> Box
mkBox ( t_min, t_max ) i ( s_min, s_max ) =
( 𝕀 ( 1 t_min ) ( 1 t_max ) , i, 𝕀 ( 1 s_min ) ( 1 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
{-
let (f, fI) = testCaseStrokeFunctions trickyCusp2
take 10 $ Data.List.sortOn ( \ ( _, 1 e, v) -> abs e + norm v ) [ let { v = mkVal x 3 y; d = eval f v } in ( v, _D12_v $ ee d, _D0_v $ dEdsdcdt d ) | x <- [0.57,0.5701 .. 0.58], y <- [0.29,0.291..0.3] ]
> ((1 0.5798800000000057,3,1 0.267980000000008),1 -2.8596965543670194e-4,V2 7.79559474412963e-2 2.0389671921293484e-2)
potentialCusp $ eval fI $ mkBox (0.5798, 0.5799) 3 (0.26798, 0.26799)
> True
let nbPotentialSols b = let ( _newtTrees, ( dunno, sols ) ) = intervalNewtonGSFrom NoPreconditioning 1e-7 fI b in length dunno + length sols
nbPotentialSols $ mkBox (0.5798, 0.5799) 3 (0.26798, 0.26799)
1
nbPotentialSols $ mkBox (0.5798, 0.675) 3 (0.26798, 0.26799)
0
let showTrees b = map ( uncurry showIntervalNewtonTree ) $ fst $ intervalNewtonGSFrom NoPreconditioning 1e-7 fI b
putStrLn $ unlines $ map Data.Tree.View.showTree $ showTrees $ mkBox (0.5798, 0.675) 3 (0.26798, 0.26799)
([1 0.5798, 1 0.675],3,[1 0.26798, 1 0.26799]) (area 0.000001) N []
└─ ([1 0.5973000285624527, 1 0.6750000000000002],3,[1 0.26798, 1 0.26799000000000006]) (area 0.000001) NoSolution "ee" ([1 0.5973000285624527, 1 0.6750000000000002],3,[1 0.26798, 1 0.26799000000000006])
eval fI $ mkBox (0.5798, 0.675) 3 (0.26798, 0.26799)
> D12 {_D12_v = T[2 -10088.6674944889 -3281.3820867312834, 2 4124.668381545453 4524.807156085763], _D12_dx = TT[2 -173746.97965005718 -33281.18494907289, 2 298.2609121556852 23639.772884799597], _D12_dy = TT[2 -18454.27716258352 -28337.509817580823, 2 1163.6949532017436 -13936.383137525536]}}
i.e.
> f = [2 -10088.6674944889 -3281.3820867312834, 2 4124.668381545453 4524.807156085763]
> f_t = [2 -173746.97965005718 -33281.18494907289, 2 298.2609121556852 23639.772884799597]
> f_s = [2 -18454.27716258352 -28337.509817580823, 2 1163.6949532017436 -13936.383137525536]
(f, fI) = testCaseStrokeFunctions trickyCusp2
t = 𝕀 (1 0.5798) (1 0.675)
s = 𝕀 (1 0.26798) (1 0.26799)
t_mid = 0.5 * ( 0.5798 + 0.675 )
s_mid = 0.5 * ( 0.26798 + 0.26799 )
D12 ( T f ) ( T ( T f_t ) ) ( T ( T f_s ) ) = dEdsdcdt $ eval fI (t, 3, s)
t' = coerce ( (-) @( 𝕀 Double ) ) t ( singleton ( 1 t_mid ) ) :: 𝕀 1
s' = coerce ( (-) @( 𝕀 Double ) ) s ( singleton ( 1 s_mid ) ) :: 𝕀 1
a = ( f_t, f_s )
b = negV2 $ singleton $ midV2 f
[((t2', s2'), isContr)] = gaussSeidel a b (t', s')
t2 = coerce ( (+) @( 𝕀 Double ) ) t2' ( singleton ( 1 t_mid ) ) :: 𝕀 1
s2 = coerce ( (+) @( 𝕀 Double ) ) s2' ( singleton ( 1 s_mid ) ) :: 𝕀 1
t2
> [1 0.6102365832093095, 1 0.6750000000000002]
s2
> [1 0.26798, 1 0.26799000000000006]
let ( 𝕀 ( 2 a11_lo a21_lo ) ( 2 a11_hi a21_hi ), 𝕀 ( 2 a12_lo a22_lo ) ( 2 a12_hi a22_hi ) ) = a
let ( 𝕀 ( 2 b1_lo b2_lo ) ( 2 b1_hi b2_hi ) ) = b
let ( 𝕀 ( 1 x1_lo ) ( 1 x1_hi ), 𝕀 ( 1 x2_lo ) ( 1 x2_hi ) ) = ( t', s' )
a11 = 𝕀 a11_lo a11_hi
a12 = 𝕀 a12_lo a12_hi
a21 = 𝕀 a21_lo a21_hi
a22 = 𝕀 a22_lo a22_hi
b1 = 𝕀 b1_lo b1_hi
b2 = 𝕀 b2_lo b2_hi
x1 = 𝕀 x1_lo x1_hi
x2 = 𝕀 x2_lo x2_hi
( b1 - a12 * x2 )
> [2981.90728508591, 2982.0918278575364]
extendedRecip a11
-}
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 =
[ "f = dE/ds * dc/dt: f, df/dt, df/ds"
, "{" ++
(intercalate ","
[ "{" ++ showD (midPoint t) ++ "," ++ showD (midPoint s) ++ ",{" ++ intercalate "," vals ++ "}}"
| t <- map ( around 0.5798 ) [-0.05, -0.049.. 0.05]
, let i = 3
, s <- map ( around 0.26798 ) [-0.05, -0.049.. 0.05]
, let StrokeDatum
{ 𝛿E𝛿sdcdt = D12 (T f) (T (T f_t)) (T (T f_s))
} = (curvesI t `Seq.index` i) s
2 vx vy = midPoint2 f
2 vx_t vy_t = midPoint2 f_t
2 vx_s vy_s = midPoint2 f_s
vals = [ "{" ++ showD vx ++ "," ++ showD vy ++ "}"
, "{" ++ showD vx_t ++ "," ++ showD vy_t ++ "}"
, "{" ++ showD vx_s ++ "," ++ showD vy_s ++ "}"
]
]
) ++ "}"
]
where
around :: Double -> Double -> 𝕀 1
around z0 z = 𝕀 ( 1 ( z + z0 - 1e-6 ) ) ( 1 ( z + z0 + 1e-6 ) )
( _, curvesI ) = testCaseStrokeFunctions trickyCusp2
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 ) )
showD :: Double -> String
showD float = showFFloat (Just 6) float ""
--------------------------------------------------------------------------------
ellipse :: TestCase
ellipse =
TestCase
{ testName = "ellipse"
, testBrush = ellipseBrush
, testStroke = ( p0, LineTo ( NextPoint p1 ) () )
, testAlgorithmParams =
CuspAlgorithmParams
{ preconditioning = NoPreconditioning
, maxWidth = 1e-7
}
}
where
mkPt x y w h phi =
Point
{ pointCoords = 2 x y
, pointParams = Params $ 3 w h phi
}
p0 = mkPt 0 0 10 25 0
p1 = mkPt 100 0 15 40 pi
trickyCusp2 :: TestCase
trickyCusp2 =
TestCase
{ testName = "trickyCusp2"
, testBrush = circleBrush
, testStroke = ( p0, Bezier3To p1 p2 ( NextPoint p3 ) () )
, testAlgorithmParams =
CuspAlgorithmParams
{ preconditioning = NoPreconditioning
, maxWidth = 1e-7
}
}
where
mkPt x y =
Point
{ pointCoords = 2 x y
, pointParams = Params $ 1 5.0
}
p0 = mkPt 5e+1 -5e+1
p1 = mkPt -7.72994362904069e+1 -3.124468786098509e+1
p2 = mkPt -5.1505430313958364e+1 -3.9826386521527986e+1
p3 = mkPt -5e+1 -5e+1
--------------------------------------------------------------------------------
type ParamsCt nbParams
= ( Show ( nbParams )
, HasChainRule Double 2 ( nbParams )
, HasChainRule ( 𝕀 Double ) 3 ( 𝕀 ( nbParams ) )
, Applicative ( D 2 ( nbParams ) )
, Applicative ( D 3 ( nbParams ) )
, Traversable ( D 2 ( nbParams ) )
, Traversable ( D 3 ( nbParams ) )
, Representable Double ( nbParams )
, Module Double ( T ( nbParams ) )
, Module ( 𝕀 Double ) ( T ( 𝕀 ( nbParams ) ) )
, Module ( D 2 ( nbParams ) Double ) ( D 2 ( nbParams ) ( 2 ) )
, Module ( D 3 ( nbParams ) ( 𝕀 Double ) ) ( D 3 ( nbParams ) ( 𝕀 ( 2 ) ) )
, Transcendental ( D 2 ( nbParams ) Double )
, Transcendental ( D 3 ( nbParams ) ( 𝕀 Double ) )
)
newtype Params nbParams = Params { getParams :: ( nbParams ) }
deriving newtype instance Show ( nbParams ) => Show ( Params nbParams )
data Point nbParams =
Point
{ pointCoords :: !( 2 )
, pointParams :: !( Params nbParams ) }
deriving stock Generic
deriving stock instance Show ( nbParams ) => Show ( Point nbParams )
data CuspAlgorithmParams =
CuspAlgorithmParams
{ preconditioning :: !Preconditioner
, maxWidth :: !Double
}
deriving stock Show
type Brush nbParams
= forall {t} k (i :: t)
. DiffInterp k i ( nbParams )
=> Proxy# i
-> ( forall a. a -> I i a )
-> C k ( I i ( nbParams ) )
( Spline Closed () ( I i ( 2 ) ) )
getStrokeFunctions
:: forall nbParams
. ParamsCt nbParams
=> Brush nbParams
-- ^ brush shape
-> Point nbParams
-- ^ start point
-> Curve Open () ( Point nbParams )
-- ^ curve points
-> ( 1 -> Seq ( 1 -> StrokeDatum 2 () )
, 𝕀 1 -> Seq ( 𝕀 1 -> StrokeDatum 3 𝕀 ) )
getStrokeFunctions brush sp0 crv =
let
usedParams :: C 2 ( 1 ) ( nbParams )
path :: C 2 ( 1 ) ( 2 )
( path, usedParams ) =
pathAndUsedParams @2 @() coerce id ( getParams . pointParams )
sp0 crv
usedParamsI :: C 3 ( 𝕀 1 ) ( 𝕀 nbParams )
pathI :: C 3 ( 𝕀 1 ) ( 𝕀 2 )
( pathI, usedParamsI ) =
pathAndUsedParams @3 @𝕀 coerce singleton ( getParams . pointParams )
sp0 crv
in ( brushStrokeData @2 @( nbParams ) coerce coerce
path usedParams $
brush @2 @() proxy# id
, brushStrokeData @3 @( nbParams ) coerce coerce
pathI usedParamsI $
brush @3 @𝕀 proxy# singleton )
{-# INLINEABLE getStrokeFunctions #-}
computeCusps
:: CuspAlgorithmParams
-> ( 𝕀 1 -> Seq ( 𝕀 1 -> StrokeDatum 3 𝕀 ) )
-> ( [ ( Box, IntervalNewtonTree Box ) ], ( [ Box ], [ Box ] ) )
computeCusps params =
intervalNewtonGS ( preconditioning params ) ( maxWidth params )