manifolds-core-0.4.0.0: Math/Manifold/Core/PseudoAffine.hs
-- |
-- Module : Math.Manifold.Core.PseudoAffine
-- Copyright : (c) Justus Sagemüller 2016
-- License : GPL v3
--
-- Maintainer : (@) sagemueller $ geo.uni-koeln.de
-- Stability : experimental
-- Portability : portable
--
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
module Math.Manifold.Core.PseudoAffine where
import Data.VectorSpace
import Data.AffineSpace
import Data.Tagged
import Data.Fixed (mod')
import Math.Manifold.Core.Types
import Math.Manifold.VectorSpace.ZeroDimensional
import Control.Applicative
data BoundarylessWitness m where
BoundarylessWitness :: (Semimanifold m, Interior m ~ m)
=> BoundarylessWitness m
-- | This is the reified form of the property that the interior of a semimanifold
-- is a manifold. These constraints would ideally be expressed directly as
-- superclass constraints, but that would require the @UndecidableSuperclasses@
-- extension, which is not reliable yet.
--
-- Also, if all those equality constraints are in scope, GHC tends to infer needlessly
-- complicated types like @'Interior' ('Interior' ('Needle' ('Interior' x)))@, which is
-- the same as just @'Needle' x@.
data SemimanifoldWitness x where
SemimanifoldWitness ::
( Semimanifold (Needle x), Needle (Interior x) ~ Needle x
, Needle (Needle x) ~ Needle x
, Interior (Needle x) ~ Needle x )
=> BoundarylessWitness (Interior x) -> SemimanifoldWitness x
data PseudoAffineWitness x where
PseudoAffineWitness ::
( PseudoAffine (Interior x), PseudoAffine (Needle x) )
=> SemimanifoldWitness x -> PseudoAffineWitness x
infix 6 .-~., .-~!
infixl 6 .+~^, .-~^
class AdditiveGroup (Needle x) => Semimanifold x where
{-# MINIMAL ((.+~^) | fromInterior), toInterior, translateP #-}
-- | The space of “natural” ways starting from some reference point
-- and going to some particular target point. Hence,
-- the name: like a compass needle, but also with an actual length.
-- For affine spaces, 'Needle' is simply the space of
-- line segments (aka vectors) between two points, i.e. the same as 'Diff'.
-- The 'AffineManifold' constraint makes that requirement explicit.
--
-- This space should be isomorphic to the tangent space (and is in fact
-- used somewhat synonymously).
type Needle x :: *
-- | Manifolds with boundary are a bit tricky. We support such manifolds,
-- but carry out most calculations only in “the fleshy part” – the
-- interior, which is an “infinite space”, so you can arbitrarily scale paths.
--
-- The default implementation is @'Interior' x = x@, which corresponds
-- to a manifold that has no boundary to begin with.
type Interior x :: *
type Interior x = x
-- | Generalised translation operation. Note that the result will always also
-- be in the interior; scaling up the needle can only get you ever /closer/
-- to a boundary.
(.+~^) :: Interior x -> Needle x -> x
(.+~^) = addvp
where addvp :: ∀ x . Semimanifold x => Interior x -> Needle x -> x
addvp p = fromInterior . tp p
where (Tagged tp) = translateP :: Tagged x (Interior x -> Needle x -> Interior x)
-- | 'id' sans boundary.
fromInterior :: Interior x -> x
fromInterior p = p .+~^ zeroV
toInterior :: x -> Maybe (Interior x)
-- | The signature of '.+~^' should really be @'Interior' x -> 'Needle' x -> 'Interior' x@,
-- only, this is not possible because it only consists of non-injective type families.
-- The solution is this tagged signature, which is of course rather unwieldy. That's
-- why '.+~^' has the stronger, but easier usable signature. Without boundary, these
-- functions should be equivalent, i.e. @translateP = Tagged (.+~^)@.
translateP :: Tagged x (Interior x -> Needle x -> Interior x)
-- | Shorthand for @\\p v -> p .+~^ 'negateV' v@, which should obey the /asymptotic/ law
--
-- @
-- p .-~^ v .+~^ v ≅ p
-- @
--
-- Meaning: if @v@ is scaled down with sufficiently small factors /η/, then
-- the difference @(p.-~^v.+~^v) .-~. p@ should scale down even faster:
-- as /O/ (/η/²). For large vectors, it will however behave differently,
-- except in flat spaces (where all this should be equivalent to the 'AffineSpace'
-- instance).
(.-~^) :: Interior x -> Needle x -> x
p .-~^ v = p .+~^ negateV v
semimanifoldWitness :: SemimanifoldWitness x
default semimanifoldWitness ::
( Semimanifold (Interior x), Semimanifold (Needle x)
, Interior (Interior x) ~ Interior x, Needle (Interior x) ~ Needle x
, Needle (Needle x) ~ Needle x
, Interior (Needle x) ~ Needle x )
=> SemimanifoldWitness x
semimanifoldWitness = SemimanifoldWitness BoundarylessWitness
-- | This is the class underlying manifolds. ('Manifold' only precludes boundaries
-- and adds an extra constraint that would be circular if it was in a single
-- class. You can always just use 'Manifold' as a constraint in your signatures,
-- but you must /define/ only 'PseudoAffine' for manifold types –
-- the 'Manifold' instance follows universally from this, if @'Interior x ~ x@.)
--
-- The interface is (boundaries aside) almost identical to the better-known
-- 'AffineSpace' class, but we don't require associativity of '.+~^' with '^+^'
-- – except in an /asymptotic sense/ for small vectors.
--
-- That innocent-looking change makes the class applicable to vastly more general types:
-- while an affine space is basically nothing but a vector space without particularly
-- designated origin, a pseudo-affine space can have nontrivial topology on the global
-- scale, and yet be used in practically the same way as an affine space. At least the
-- usual spheres and tori make good instances, perhaps the class is in fact equivalent to
-- manifolds in their usual maths definition (with an atlas of charts: a family of
-- overlapping regions of the topological space, each homeomorphic to the 'Needle'
-- vector space or some simply-connected subset thereof).
class Semimanifold x => PseudoAffine x where
{-# MINIMAL (.-~.) | (.-~!) #-}
-- | The path reaching from one point to another.
-- Should only yield 'Nothing' if
--
-- * The points are on disjoint segments of a non–path-connected space.
--
-- * Either of the points is on the boundary. Use '|-~.' to deal with this.
--
-- On manifolds, the identity
--
-- @
-- p .+~^ (q.-~.p) ≡ q
-- @
--
-- should hold, at least save for floating-point precision limits etc..
--
-- '.-~.' and '.+~^' only really work in manifolds without boundary. If you consider
-- the path between two points, one of which lies on the boundary, it can't really
-- be possible to scale this path any longer – it would have to reach “out of the
-- manifold”. To adress this problem, these functions basically consider only the
-- /interior/ of the space.
(.-~.) :: x -> x -> Maybe (Needle x)
p.-~.q = return $ p.-~!q
-- | Unsafe version of '.-~.'. If the two points lie in disjoint regions,
-- the behaviour is undefined.
(.-~!) :: x -> x -> Needle x
p.-~!q = case p.-~.q of
Just v -> v
pseudoAffineWitness :: PseudoAffineWitness x
default pseudoAffineWitness ::
( PseudoAffine (Interior x), PseudoAffine (Needle x) )
=> PseudoAffineWitness x
pseudoAffineWitness = PseudoAffineWitness semimanifoldWitness
-- | Interpolate between points, approximately linearly. For
-- points that aren't close neighbours (i.e. lie in an almost
-- flat region), the pathway is basically undefined – save for
-- its end points.
--
-- A proper, really well-defined (on global scales) interpolation
-- only makes sense on a Riemannian manifold, as 'Data.Manifold.Riemannian.Geodesic'.
palerp :: ∀ x. (PseudoAffine x, VectorSpace (Needle x))
=> x -> x -> Maybe (Scalar (Needle x) -> x)
palerp p₀ p₁ = case (toInterior p₀, p₁.-~.p₀) of
(Just b, Just v) -> return $ \t -> b .+~^ t *^ v
_ -> Nothing
-- | Like 'palerp', but actually restricted to the interval between the points,
-- with a signature like 'Data.Manifold.Riemannian.geodesicBetween'
-- rather than 'Data.AffineSpace.alerp'.
palerpB :: ∀ x. (PseudoAffine x, VectorSpace (Needle x), Scalar (Needle x) ~ ℝ)
=> x -> x -> Maybe (D¹ -> x)
palerpB p₀ p₁ = case (toInterior p₀, p₁.-~.p₀) of
(Just b, Just v) -> return $ \(D¹ t) -> b .+~^ ((t+1)/2) *^ v
_ -> Nothing
-- | Like 'alerp', but actually restricted to the interval between the points.
alerpB :: ∀ x. (AffineSpace x, VectorSpace (Diff x), Scalar (Diff x) ~ ℝ)
=> x -> x -> D¹ -> x
alerpB p1 p2 = case p2 .-. p1 of
v -> \(D¹ t) -> p1 .+^ ((t+1)/2) *^ v
hugeℝVal :: ℝ
hugeℝVal = 1e+100
#define deriveAffine(c,t) \
instance (c) => Semimanifold (t) where { \
type Needle (t) = Diff (t); \
fromInterior = id; \
toInterior = pure; \
translateP = Tagged (.+^); \
(.+~^) = (.+^) }; \
instance (c) => PseudoAffine (t) where { \
a.-~.b = pure (a.-.b); }
deriveAffine((),Double)
deriveAffine((),Rational)
instance Semimanifold (ZeroDim k) where
type Needle (ZeroDim k) = ZeroDim k
fromInterior = id
toInterior = pure
Origin .+~^ Origin = Origin
Origin .-~^ Origin = Origin
translateP = Tagged (.+~^)
instance PseudoAffine (ZeroDim k) where
Origin .-~. Origin = pure Origin
instance ∀ a b . (Semimanifold a, Semimanifold b) => Semimanifold (a,b) where
type Needle (a,b) = (Needle a, Needle b)
type Interior (a,b) = (Interior a, Interior b)
(a,b).+~^(v,w) = (a.+~^v, b.+~^w)
(a,b).-~^(v,w) = (a.-~^v, b.-~^w)
fromInterior (i,j) = (fromInterior i, fromInterior j)
toInterior (a,b) = (,) <$> toInterior a <*> toInterior b
translateP = Tagged $ \(a,b) (v,w) -> (ta a v, tb b w)
where Tagged ta = translateP :: Tagged a (Interior a -> Needle a -> Interior a)
Tagged tb = translateP :: Tagged b (Interior b -> Needle b -> Interior b)
semimanifoldWitness = case ( semimanifoldWitness :: SemimanifoldWitness a
, semimanifoldWitness :: SemimanifoldWitness b ) of
(SemimanifoldWitness BoundarylessWitness, SemimanifoldWitness BoundarylessWitness)
-> SemimanifoldWitness BoundarylessWitness
instance (PseudoAffine a, PseudoAffine b) => PseudoAffine (a,b) where
(a,b).-~.(c,d) = liftA2 (,) (a.-~.c) (b.-~.d)
pseudoAffineWitness = case ( pseudoAffineWitness :: PseudoAffineWitness a
, pseudoAffineWitness :: PseudoAffineWitness b ) of
( PseudoAffineWitness (SemimanifoldWitness BoundarylessWitness)
, PseudoAffineWitness (SemimanifoldWitness BoundarylessWitness) )
->PseudoAffineWitness (SemimanifoldWitness BoundarylessWitness)
instance ∀ a b c . (Semimanifold a, Semimanifold b, Semimanifold c)
=> Semimanifold (a,b,c) where
type Needle (a,b,c) = (Needle a, Needle b, Needle c)
type Interior (a,b,c) = (Interior a, Interior b, Interior c)
(a,b,c).+~^(v,w,x) = (a.+~^v, b.+~^w, c.+~^x)
(a,b,c).-~^(v,w,x) = (a.-~^v, b.-~^w, c.-~^x)
fromInterior (i,j,k) = (fromInterior i, fromInterior j, fromInterior k)
toInterior (a,b,c) = liftA3 (,,) (toInterior a) (toInterior b) (toInterior c)
translateP = Tagged $ \(a,b,c) (v,w,x) -> (ta a v, tb b w, tc c x)
where Tagged ta = translateP :: Tagged a (Interior a -> Needle a -> Interior a)
Tagged tb = translateP :: Tagged b (Interior b -> Needle b -> Interior b)
Tagged tc = translateP :: Tagged c (Interior c -> Needle c -> Interior c)
semimanifoldWitness = case ( semimanifoldWitness :: SemimanifoldWitness a
, semimanifoldWitness :: SemimanifoldWitness b
, semimanifoldWitness :: SemimanifoldWitness c ) of
( SemimanifoldWitness BoundarylessWitness
,SemimanifoldWitness BoundarylessWitness
,SemimanifoldWitness BoundarylessWitness )
-> SemimanifoldWitness BoundarylessWitness
instance (PseudoAffine a, PseudoAffine b, PseudoAffine c) => PseudoAffine (a,b,c) where
(a,b,c).-~.(d,e,f) = liftA3 (,,) (a.-~.d) (b.-~.e) (c.-~.f)
pseudoAffineWitness = case ( pseudoAffineWitness :: PseudoAffineWitness a
, pseudoAffineWitness :: PseudoAffineWitness b
, pseudoAffineWitness :: PseudoAffineWitness c ) of
( PseudoAffineWitness (SemimanifoldWitness BoundarylessWitness)
, PseudoAffineWitness (SemimanifoldWitness BoundarylessWitness)
, PseudoAffineWitness (SemimanifoldWitness BoundarylessWitness) )
->PseudoAffineWitness (SemimanifoldWitness BoundarylessWitness)
instance Semimanifold S⁰ where
type Needle S⁰ = ZeroDim ℝ
fromInterior = id
toInterior = pure
translateP = Tagged (.+~^)
p .+~^ Origin = p
p .-~^ Origin = p
instance PseudoAffine S⁰ where
PositiveHalfSphere .-~. PositiveHalfSphere = pure Origin
NegativeHalfSphere .-~. NegativeHalfSphere = pure Origin
_ .-~. _ = Nothing
instance Semimanifold S¹ where
type Needle S¹ = ℝ
fromInterior = id
toInterior = pure
translateP = Tagged (.+~^)
S¹ φ₀ .+~^ δφ
| φ' < 0 = S¹ $ φ' + tau
| otherwise = S¹ $ φ'
where φ' = toS¹range $ φ₀ + δφ
instance PseudoAffine S¹ where
S¹ φ₁ .-~. S¹ φ₀
| δφ > pi = pure (δφ - 2*pi)
| δφ < (-pi) = pure (δφ + 2*pi)
| otherwise = pure δφ
where δφ = φ₁ - φ₀
instance Semimanifold D¹ where
type Needle D¹ = ℝ
type Interior D¹ = ℝ
fromInterior = D¹ . tanh
toInterior (D¹ x) | abs x < 1 = return $ atanh x
| otherwise = empty
translateP = Tagged (+)
instance PseudoAffine D¹ where
D¹ 1 .-~. _ = empty
D¹ (-1) .-~. _ = empty
D¹ x .-~. D¹ y
| abs x < 1, abs y < 1 = return $ atanh x - atanh y
| otherwise = empty
tau :: ℝ
tau = 2 * pi
toS¹range :: ℝ -> ℝ
toS¹range φ = (φ+pi)`mod'`tau - pi