igrf-0.4.0.0: src/Math/SphericalHarmonics.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeFamilies #-}
-- | Provides spherical harmonic models of scalar-valued functions.
module Math.SphericalHarmonics
(
SphericalHarmonicModel
, sphericalHarmonicModel
, scaledSphericalHarmonicModel
, evaluateModel
, evaluateModelCartesian
, evaluateModelGradient
, evaluateModelGradientCartesian
, evaluateModelGradientInLocalTangentPlane
)
where
import Data.Complex
import Data.VectorSpace hiding (magnitude)
import Math.SphericalHarmonics.AssociatedLegendre
import Numeric.AD
-- | Represents a spherical harmonic model of a scalar-valued function.
data SphericalHarmonicModel a = SphericalHarmonicModel [[(a, a)]]
deriving (Functor)
-- | Creates a spherical harmonic model.
-- Result in an error if the length of the list is not a triangular number.
sphericalHarmonicModel :: (Fractional a) => [[(a, a)]] -- ^ A list of g and h coefficients for the model
-> SphericalHarmonicModel a -- ^ The spherical harmonic model
sphericalHarmonicModel cs | valid = SphericalHarmonicModel cs
| otherwise = error "The number of coefficients is not a triangular number."
where
valid = and $ zipWith (==) (fmap length cs) [1..length cs]
-- | Creates a spherical harmonic model, scaling coefficients for the supplied reference radius.
-- Result in an error if the length of the list is not a triangular number.
scaledSphericalHarmonicModel :: (Fractional a) => a -- ^ The reference radius
-> [[(a, a)]] -- ^ A list of g and h coefficients for the model
-> SphericalHarmonicModel a -- ^ The spherical harmonic model
scaledSphericalHarmonicModel r cs = sphericalHarmonicModel cs'
where
cs' = normalizeReferenceRadius r cs
instance(Fractional a, Eq a) => AdditiveGroup (SphericalHarmonicModel a) where
zeroV = SphericalHarmonicModel [[(0,0)]]
negateV = fmap negate
(SphericalHarmonicModel m1) ^+^ (SphericalHarmonicModel m2) = SphericalHarmonicModel (combineCoefficients m1 m2)
where
combineCoefficients [] cs = cs
combineCoefficients cs [] = cs
combineCoefficients (c1:cs1) (c2:cs2) = zipWith addPairs c1 c2 : combineCoefficients cs1 cs2
addPairs (g1, h1) (g2, h2) = (g1 + g2, h1 + h2)
instance (Fractional a, Eq a) => VectorSpace (SphericalHarmonicModel a) where
type Scalar (SphericalHarmonicModel a) = a
x *^ m = fmap (* x) m
normalizeReferenceRadius :: (Fractional a) => a -> [[(a, a)]] -> [[(a, a)]]
normalizeReferenceRadius r = zipWith (fmap . mapWholePair . transform) [0 :: Int ..]
where
transform n = (* (r ^ (2 + n)))
-- | Computes the scalar value of the spherical harmonic model at a specified spherical position.
evaluateModel :: (RealFloat a, Ord a) => SphericalHarmonicModel a -- ^ Spherical harmonic model
-> a -- ^ Spherical radius
-> a -- ^ Spherical colatitude (radian)
-> a -- ^ Spherical longitude (radian)
-> a -- ^ Model value
evaluateModel m r colat lon = evaluateModel' m r (cos colat) (cis lon)
-- | Computes the scalar value of the spherical harmonic model at a specified Cartesian position.
evaluateModelCartesian :: (RealFloat a, Ord a) => SphericalHarmonicModel a -- ^ Spherical harmonic model
-> a -- ^ X position
-> a -- ^ Y position
-> a -- ^ Z position
-> a -- ^ Model value
evaluateModelCartesian m x y z = evaluateModel' m r cosColat cisLon
where
r = sqrt $ (x*x) + (y*y) + (z*z)
cosColat = z / r
cisLon = normalize $ mkPolar x y
evaluateModel' :: (RealFloat a, Ord a) => SphericalHarmonicModel a
-> a -- r
-> a -- cosColat
-> Complex a -- cisLon
-> a
evaluateModel' (SphericalHarmonicModel cs) r cosColat cisLon = sum $ zipWith (*) (iterate (/ r) (recip r)) (zipWith evaluateDegree [0..] cs)
where
sines = 1 : iterate (* cisLon) cisLon
evaluateDegree n cs' = sum $ zipWith3 evaluateOrder (fmap (schmidtSemiNormalizedAssociatedLegendreFunction n) [0..n]) cs' sines
evaluateOrder p (g, h) cisMLon = ((g * realPart cisMLon) + (h * imagPart cisMLon)) * (p (cosColat))
-- | Computes the gradient of the scalar value of the spherical harmonic model, in spherical coordinates, at a specified location.
evaluateModelGradient :: (RealFloat a, Ord a) => SphericalHarmonicModel a -- ^ Spherical harmonic model
-> a -- ^ Spherical radius
-> a -- ^ Spherical colatitude (radian)
-> a -- ^ Spherical longitude (radian)
-> (a, a, a) -- ^ Radial, colatitudinal, and longitudinal components of gradient
evaluateModelGradient model r colat lon = makeTuple . fmap negate $ modelGrad [r, colat, lon]
where
modelGrad = grad (\[r', c', l'] -> evaluateModel (fmap auto model) r' c' l')
-- | Computes the gradient of the scalar value of the spherical harmonic model at a specified location, in Cartesian coordinates.
-- The result is expressed in right-handed coordinates centered at the origin of the sphere, with the positive Z-axis piercing the
-- north pole and the positive x-axis piercing the reference meridian.
evaluateModelGradientCartesian :: (RealFloat a, Ord a) => SphericalHarmonicModel a -- ^ Spherical harmonic model
-> a -- ^ X position
-> a -- ^ Y position
-> a -- ^ Z position
-> (a, a, a) -- X, Y, and Z components of gradient
evaluateModelGradientCartesian model x y z = makeTuple . fmap negate $ modelGrad [x, y, z]
where
modelGrad = grad (\[x', y', z'] -> evaluateModelCartesian (fmap auto model) x' y' z')
-- | Computes the gradient of the scalar value of the spherical harmonic model at a specified location, in Cartesian coordinates.
-- The result is expressed in a reference frame locally tangent to the sphere at the specified location.
evaluateModelGradientInLocalTangentPlane :: (RealFloat a, Ord a) => SphericalHarmonicModel a -- ^ Spherical harmonic model
-> a -- ^ Spherical radius
-> a -- ^ Spherical colatitude (radian)
-> a -- ^ Spherical longitude (radian)
-> (a, a, a) -- ^ East, North, and up components of gradient
evaluateModelGradientInLocalTangentPlane model r colat lon = (e, n, u)
where
(r', colat', lon') = evaluateModelGradient model r colat lon
e = lon' / (r * sin colat)
n = -colat' / r -- negated because the colatitude increase southward
u = r'
normalize :: (RealFloat a) => Complex a -> Complex a
normalize r@(x :+ y) | isInfinite m' = 0
| otherwise = (x * m') :+ (y * m')
where
m' = recip . magnitude $ r
mapWholePair :: (a -> b) -> (a, a) -> (b, b)
mapWholePair f (a, b) = (f a, f b)
makeTuple :: [a] -> (a, a, a)
makeTuple [x, y, z] = (x, y, z)