```{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
{-# LANGUAGE Safe #-}

{- |
Module      :  Physics.Learn.Position
Copyright   :  (c) Scott N. Walck 2012-2018
License     :  BSD3 (see LICENSE)
Maintainer  :  Scott N. Walck <walck@lvc.edu>
Stability   :  experimental

A module for working with the idea of position and coordinate systems.
-}

module Physics.Learn.Position
( Position
, Displacement
, ScalarField
, VectorField
, Field
, CoordinateSystem
, cartesian
, cylindrical
, spherical
, cart
, cyl
, sph
, cartesianCoordinates
, cylindricalCoordinates
, sphericalCoordinates
, displacement
, shiftPosition
, shiftObject
, shiftField
, addFields
, rHat
, thetaHat
, phiHat
, sHat
, xHat
, yHat
, zHat
)
where

import Data.VectorSpace
( AdditiveGroup
)
import Physics.Learn.CarrotVec
( Vec
, vec
, xComp
, yComp
, zComp
, iHat
, jHat
, kHat
, sumV
, magnitude
, (^/)
)

-- | A type for position.
--   Position is not a vector because it makes no sense to add positions.
data Position = Cart Double Double Double
deriving (Show)

-- | A displacement is a vector.
type Displacement = Vec

-- | A scalar field associates a number with each position in space.
type ScalarField = Position -> Double

{-
-- | Scalar fields can be added, subtracted, multiplied, and negated,
--   just like scalars themselves.
instance Num ScalarField where
(f + g) x = f x + g x
(f * g) x = f x * g x
(f - g) x = f x - g x
negate f x = negate (f x)
abs f x = abs (f x)
signum f x = signum (f x)
fromInteger n = const (fromInteger n)

-- | Scalar fields can be divided, just like scalars themselves.
instance Fractional ScalarField where
(f / g) x = f x / g x
recip f x = recip (f x)
fromRational rat = const (fromRational rat)

-- | Cosine of a scalar field, etc.
instance Floating ScalarField where
pi = const pi
exp f x = exp (f x)
sqrt f x = sqrt (f x)
log f x = log (f x)
(f ** g) x = f x ** g x
logBase f g x = logBase (f x) (g x)
sin f x = sin (f x)
cos f x = cos (f x)
tan f x = tan (f x)
asin f x = asin (f x)
acos f x = acos (f x)
atan f x = atan (f x)
sinh f x = sinh (f x)
cosh f x = cosh (f x)
tanh f x = tanh (f x)
asinh f x = asinh (f x)
acosh f x = acosh (f x)
atanh f x = atanh (f x)
-}

-- | A vector field associates a vector with each position in space.
type VectorField = Position -> Vec

-- | Sometimes we want to be able to talk about a field without saying
--   whether it is a scalar field or a vector field.
type Field v     = Position -> v

-- | A coordinate system is a function from three parameters to space.
type CoordinateSystem = (Double,Double,Double) -> Position

-- | Add two scalar fields or two vector fields.
addFields :: AdditiveGroup v => [Field v] -> Field v
addFields flds r = sumV [fld r | fld <- flds]

-- | The Cartesian coordinate system.  Coordinates are (x,y,z).
cartesian :: CoordinateSystem
cartesian (x,y,z) = Cart x y z

-- | The cylindrical coordinate system.  Coordinates are (s,phi,z),
--   where s is the distance from the z axis and phi is the angle
--   with the x axis.
cylindrical :: CoordinateSystem
cylindrical (s,phi,z) = Cart (s * cos phi) (s * sin phi) z

-- | The spherical coordinate system.  Coordinates are (r,theta,phi),
--   where r is the distance from the origin, theta is the angle with
--   the z axis, and phi is the azimuthal angle.
spherical :: CoordinateSystem
spherical (r,th,phi) = Cart (r * sin th * cos phi) (r * sin th * sin phi) (r * cos th)

-- | A helping function to take three numbers x, y, and z and form the
--   appropriate position using Cartesian coordinates.
cart :: Double  -- ^ x coordinate
-> Double  -- ^ y coordinate
-> Double  -- ^ z coordinate
-> Position
cart = Cart

-- | A helping function to take three numbers s, phi, and z and form the
--   appropriate position using cylindrical coordinates.
cyl :: Double  -- ^ s coordinate
-> Double  -- ^ phi coordinate
-> Double  -- ^ z coordinate
-> Position
cyl s phi z = Cart (s * cos phi) (s * sin phi) z

-- | A helping function to take three numbers r, theta, and phi and form the
--   appropriate position using spherical coordinates.
sph :: Double  -- ^ r coordinate
-> Double  -- ^ theta coordinate
-> Double  -- ^ phi coordinate
-> Position
sph r theta phi = Cart (r * sin theta * cos phi) (r * sin theta * sin phi) (r * cos theta)

-- | Returns the three Cartesian coordinates as a triple from a position.
cartesianCoordinates :: Position -> (Double,Double,Double)
cartesianCoordinates (Cart x y z) = (x,y,z)

-- | Returns the three cylindrical coordinates as a triple from a position.
cylindricalCoordinates :: Position -> (Double,Double,Double)
cylindricalCoordinates (Cart x y z) = (s,phi,z)
where
s = sqrt(x**2 + y**2)
phi = atan2 y x

-- | Returns the three spherical coordinates as a triple from a position.
sphericalCoordinates :: Position -> (Double,Double,Double)
sphericalCoordinates (Cart x y z) = (r,theta,phi)
where
r = sqrt(x**2 + y**2 + z**2)
theta = atan2 s z
s = sqrt(x**2 + y**2)
phi = atan2 y x

-- | Displacement from source position to target position.
displacement :: Position  -- ^ source position
-> Position  -- ^ target position
-> Displacement
displacement (Cart x' y' z') (Cart x y z) = vec (x-x') (y-y') (z-z')

-- | Shift a position by a displacement.
shiftPosition :: Displacement -> Position -> Position
shiftPosition v (Cart x y z) = Cart (x + xComp v) (y + yComp v) (z + zComp v)

-- | An object is a map into 'Position'.
shiftObject :: Displacement -> (a -> Position) -> (a -> Position)
shiftObject d f = shiftPosition d . f

-- | A field is a map from 'Position'.
shiftField :: Displacement -> (Position -> v) -> (Position -> v)
shiftField d f = f . shiftPosition d

-- | The vector field in which each point in space is associated
--   with a unit vector in the direction of increasing spherical coordinate
--   r, while spherical coordinates theta and phi
--   are held constant.
--   Defined everywhere except at the origin.
--   The unit vector 'rHat' points in different directions at different points
--   in space.  It is therefore better interpreted as a vector field, rather
--   than a vector.
rHat :: VectorField
rHat rv = d ^/ magnitude d
where
d = displacement (cart 0 0 0) rv

-- | The vector field in which each point in space is associated
--   with a unit vector in the direction of increasing spherical coordinate
--   theta, while spherical coordinates r and phi are held constant.
--   Defined everywhere except on the z axis.
thetaHat :: VectorField
thetaHat r = vec (cos theta * cos phi) (cos theta * sin phi) (-sin theta)
where
(_,theta,phi) = sphericalCoordinates r

-- | The vector field in which each point in space is associated
--   with a unit vector in the direction of increasing (cylindrical or spherical) coordinate
--   phi, while cylindrical coordinates s and z
--   (or spherical coordinates r and theta) are held constant.
--   Defined everywhere except on the z axis.
phiHat :: VectorField
phiHat r = vec (-sin phi) (cos phi) 0
where
(_,phi,_) = cylindricalCoordinates r

-- | The vector field in which each point in space is associated
--   with a unit vector in the direction of increasing cylindrical coordinate
--   s, while cylindrical coordinates phi and z
--   are held constant.
--   Defined everywhere except on the z axis.
sHat :: VectorField
sHat r = vec (cos phi) (sin phi) 0
where
(_,phi,_) = cylindricalCoordinates r

-- | The vector field in which each point in space is associated
--   with a unit vector in the direction of increasing Cartesian coordinate
--   x, while Cartesian coordinates y and z
--   are held constant.
--   Defined everywhere.
xHat :: VectorField
xHat = const iHat

-- | The vector field in which each point in space is associated
--   with a unit vector in the direction of increasing Cartesian coordinate
--   y, while Cartesian coordinates x and z
--   are held constant.
--   Defined everywhere.
yHat :: VectorField
yHat = const jHat

-- | The vector field in which each point in space is associated
--   with a unit vector in the direction of increasing Cartesian coordinate
--   z, while Cartesian coordinates x and y
--   are held constant.
--   Defined everywhere.
zHat :: VectorField
zHat = const kHat

```