brush-strokes-0.1.0.0: src/lib/Math/Root/Isolation/Degree.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Math.Root.Isolation.Degree
( -- * Computing the topological degree
topologicalDegree
)
where
-- base
import Data.Kind
( Type )
import GHC.TypeNats
( Nat )
-- primitive
import Data.Primitive.SmallArray
-- brush-strokes
import Math.Interval
import Math.Linear
--------------------------------------------------------------------------------
-- Topological degree.
-- | Compute the topological degree of the given interval function (2D only).
topologicalDegree
:: Double
-- ^ minimum width when bisecting facets
-> ( πβ 2 -> πβ 2 )
-- ^ function
-> πβ 2
-- ^ box
-> Maybe Int
topologicalDegree Ξ΅_bis f ( πβ2 ( π x_lo x_hi ) ( π y_lo y_hi ) ) =
topologicalDegreeFromContour $
smallArrayFromList $
concatMap ( tagFacet Ξ΅_bis f )
[ ( Tag ( Fin 2 ) P, πβ2 ( π x_hi x_hi ) ( π y_lo y_hi ) )
, ( Tag ( Fin 1 ) N, πβ2 ( π x_lo x_hi ) ( π y_hi y_hi ) )
, ( Tag ( Fin 2 ) N, πβ2 ( π x_lo x_lo ) ( π y_lo y_hi ) )
, ( Tag ( Fin 1 ) P, πβ2 ( π x_lo x_hi ) ( π y_lo y_lo ) ) ]
-- | Tag a facet with the sign of one of the equations that is non-zero
-- on that facet.
--
-- If all equations take the value 0 on that facet, bisect and recur.
tagFacet
:: Double
-> ( πβ 2 -> πβ 2 )
-> ( Tag 2, πβ 2 )
-> [ Tag 2 ]
tagFacet Ξ΅_bis f ( tag@( Tag i _ ), facet0 ) = go facet0
where
go facet
| f1_b_lo > 0
= [ Tag ( Fin 1 ) P ]
| f1_b_hi < 0
= [ Tag ( Fin 1 ) N ]
| f2_b_lo > 0
= [ Tag ( Fin 2 ) P ]
| f2_b_hi < 0
= [ Tag ( Fin 2 ) N ]
| width ( facet `index` i ) < Ξ΅_bis
= [ NoTag ] -- TODO: shortcut the whole computation;
-- if we fail to tag one segment, the final answer could
-- be off by { -2, -1, 0, 1, 2 } (I believe)
| otherwise
= concatMap go $ bisectFacet tag facet
where
π f1_b_lo f1_b_hi = f facet `index` Fin 1
π f2_b_lo f2_b_hi = f facet `index` Fin 2
tagFacet _ _ ( NoTag, _ ) = error "impossible: input always tagged"
bisectFacet :: Tag 2 -> πβ 2 -> [ πβ 2 ]
bisectFacet ( Tag i s ) x =
( if s == P then id else reverse ) $ bisectInCoord i x
bisectFacet NoTag _ = error "impossible: input always tagged"
bisectInCoord :: Fin n -> πβ 2 -> [ πβ 2 ]
bisectInCoord ( Fin 1 ) ( πβ2 ( π x_lo x_hi ) ( π y_lo y_hi ) ) =
[ πβ2 ( π x_lo x_mid ) ( π y_lo y_hi )
, πβ2 ( π x_mid x_hi ) ( π y_lo y_hi ) ]
where x_mid = 0.5 * ( x_lo + x_hi )
bisectInCoord _ ( πβ2 ( π x_lo x_hi ) ( π y_lo y_hi ) ) =
[ πβ2 ( π x_lo x_hi ) ( π y_lo y_mid )
, πβ2 ( π x_lo x_hi ) ( π y_mid y_hi ) ]
where y_mid = 0.5 * ( y_lo + y_hi )
-- | Compute the topological degree of \( f \colon \mathbb{IR}^n \to \mathbb{IR}^n \)
-- on a box \( D \subset \mathbb{IR}^n \) by using the signs of the components
-- of \( f \) on a counter-clockwise polygonal contour describing the boundary of \( D \).
topologicalDegreeFromContour :: SmallArray ( Tag 2 ) -> Maybe Int
topologicalDegreeFromContour facetTags = go 0 0
where
n = sizeofSmallArray facetTags
go :: Int -> Int -> Maybe Int
go !d !i
| i >= n
= Just d
| otherwise
= case indexSmallArray facetTags i of
NoTag -> Nothing
Tag ( Fin 1 ) P -> go ( d + Ξ΄ ) ( i + 1 )
where
Ξ΄ = if indexSmallArray facetTags ( if i == n - 1 then 0 else i + 1 ) == Tag ( Fin 2 ) P
then 1 else 0
- if indexSmallArray facetTags ( if i == 0 then n - 1 else i - 1 ) == Tag ( Fin 2 ) P
then 1 else 0
_ -> go d ( i + 1 )
--------------------------------------------------------------------------------
-- Tagging edges with signs of certain components of the function.
data Sign = P | N
deriving stock ( Eq, Ord )
instance Show Sign where
showsPrec _ = \case { P -> showString "+"; N -> showString "-" }
type Tag :: Nat -> Type
newtype Tag d = MkTag Int
deriving newtype ( Eq, Ord )
pattern NoTag :: Tag d
pattern NoTag = MkTag 0
pattern Tag :: Fin d -> Sign -> Tag d
pattern Tag c s <- ( getTag -> (# c, s #) )
where Tag ( Fin c ) s = MkTag $ case s of { P -> fromIntegral c; N -> -( fromIntegral c ) }
{-# COMPLETE Tag, NoTag #-}
getTag :: Tag d -> (# Fin d, Sign #)
getTag ( MkTag t ) = (# Fin ( fromIntegral $ abs t ), if t >= 0 then P else N #)