Skip to content

Commit

Permalink
LTS-17.9. Add Cyclic (from astro), doctests
Browse files Browse the repository at this point in the history
  • Loading branch information
bjornbm committed May 1, 2021
1 parent c366183 commit 6dbba52
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 14 deletions.
26 changes: 23 additions & 3 deletions dimensional-experimental.cabal
Expand Up @@ -19,25 +19,45 @@ Description:


Category: Math, Physics Category: Math, Physics
Build-Type: Simple Build-Type: Simple
Build-Depends: Extra-source-files: README, LICENSE
cabal-version: >=1.10

library
default-language: Haskell2010
hs-source-dirs: src
Build-Depends:


base, base,
time, time,
ieee754,
dimensional, dimensional,
dimensional-dk-experimental, dimensional-dk-experimental,
dimensional-homo, dimensional-homo,
numtype-dk, numtype-dk,
numeric-quest, numeric-quest,
ad ad


Exposed-Modules: Exposed-Modules:


Numeric.Units.Dimensional.AD Numeric.Units.Dimensional.AD
Numeric.Units.Dimensional.Constants Numeric.Units.Dimensional.Constants
Numeric.Units.Dimensional.Cyclic
Numeric.Units.Dimensional.Formulae Numeric.Units.Dimensional.Formulae
Numeric.Units.Dimensional.LinearAlgebra.VectorAD Numeric.Units.Dimensional.LinearAlgebra.VectorAD
Numeric.Units.Dimensional.LinearAlgebra.Rotation Numeric.Units.Dimensional.LinearAlgebra.Rotation
Numeric.Units.Dimensional.LinearAlgebra.PosVel Numeric.Units.Dimensional.LinearAlgebra.PosVel


Extra-source-files: README, LICENSE


test-suite doctests
type: exitcode-stdio-1.0
ghc-options: -threaded
default-language: Haskell2010
hs-source-dirs: test
main-is: Doctests.hs
build-depends: base
, doctest >= 0.8
, Glob
, ieee754
, QuickCheck
, dimensional
, dimensional-homo
File renamed without changes.
File renamed without changes.
152 changes: 152 additions & 0 deletions src/Numeric/Units/Dimensional/Cyclic.hs
@@ -0,0 +1,152 @@
-- | This module contains various function for working with functions
-- that are is cyclic in meaning (but not in value). An obvious example
-- are angles, where in many cases there is no value in distinguishing
-- between x and x + n2π.

module Numeric.Units.Dimensional.Cyclic where

import qualified Prelude
import Numeric.Units.Dimensional.Prelude

import Data.AEq


-- Adjusting values with respect to a reference
-- --------------------------------------------

-- | Returns the integral part of a value, leaving only a fractional
-- part in the interval (-1,1).
-- (@/= snd . properFraction@).
fractionalPart :: RealFrac a => Dimensionless a -> Dimensionless a
fractionalPart = fmap (snd . properFraction)

-- | Removes the integral part of a value so that a fractional part
-- in the interval [0,1) is returned. This differs in behavior from
-- @(snd . properFraction)@ for negative values:
-- @(snd . properFraction) 1.2@ = 0.2
-- @adjustZeroOne 1.2@ = 0.2
-- @(snd . properFraction) (-1.2)@ = -0.2
-- @adjustZeroOne (-1.2)@ = 0.8
adjustZeroOne :: RealFrac a => Dimensionless a -> Dimensionless a
--adjustZeroOne = fmap (\x -> x Prelude.- fromIntegral (floor x))
adjustZeroOne x = x - fmap (fromIntegral . floor) x

-- | @adjustAbove1 min x@ adjusts x by an integer so that it lies in the
-- interval [min,min+1).
adjust1Above :: RealFrac a
=> Dimensionless a -> Dimensionless a -> Dimensionless a
adjust1Above min x = adjustZeroOne (x - min) + min

-- | @adjustAbove cycle min x@ adjusts x by an integer number or cycles
-- so that it lies in the interval [min,min+cycle).
adjustAbove :: RealFrac a
=> Quantity d a -> Quantity d a -> Quantity d a -> Quantity d a
adjustAbove cycle min x = adjust1Above (min/cycle) (x/cycle) * cycle

-- | @adjustAbout1 center x@ adjusts x by an integer so that it lies in the
-- interval [center-1/2,center+1/2).
adjust1About :: RealFrac a
=> Dimensionless a -> Dimensionless a -> Dimensionless a
adjust1About center = adjust1Above (center - _1/_2)

-- | @adjustAbout center cycle x@ adjusts x by an integer number or cycles
-- so that it lies in the interval [min-cycle/2,min+cycle/2).
adjustAbout :: RealFrac a
=> Quantity d a -> Quantity d a -> Quantity d a -> Quantity d a
adjustAbout center cycle x = adjust1About (center/cycle) (x/cycle) * cycle


-- Adjusting angles
-- ----------------

-- | @adjustAngle center a@ adjusts the angle @a@ to
-- be within ±π of @center@.
adjustAngle :: RealFloat a => Angle a -> Angle a -> Angle a
adjustAngle center = adjustAbout center tau

-- | Adjusts an angle to the range [-pi,pi).
plusMinusPi :: RealFloat a => Angle a -> Angle a
plusMinusPi = adjustAngle _0

-- | Adjusts an angle to the range [0,2pi).
zeroTwoPi :: RealFloat a => Angle a -> Angle a
zeroTwoPi = adjustAngle pi

-- | Adjusts an angle to the range [0,tau).
zeroTau :: RealFloat a => Angle a -> Angle a
zeroTau = zeroTwoPi


-- Consistency metric for cyclic values
-- ------------------------------------

-- | Assume that y(x) is cyclic in meaning (but not in value, as
-- for e.g. angles) with a periodicity @period@ and cycle length @cycle@.
-- Then @cyclesOff (x,y) period cycle@ compute the approximate (closest)
-- integral number of cycles that @y@ differs from what one would expect
-- given @x@ and an assumption that y = cycle * x / period.
cyclesOff :: RealFrac a
=> (Quantity d a, Quantity dy a)
-> Quantity d a -> Quantity dy a -> Dimensionless a
cyclesOff (x,y) period cycle = cyclesOff1 (x/period, y/cycle)

-- | Same as 'cyclesOff' but assumes that both the period and the cycle
-- of y(x) is one.
cyclesOff1 :: RealFrac a => (Dimensionless a, Dimensionless a) -> Dimensionless a
cyclesOff1 (x, y) = fmap (fromIntegral . round) (x - y)


-- Adjusting for relative total rotation
-- -------------------------------------

-- | Assume that y(x) is cyclic in meaning (but not in value, as
-- for e.g. angles). Then @adjustCyclic (x0,y0) (x1,y1) period cycle@
-- returns a new @y1@ adjusted so that the difference @y1 - y0@
-- corresponds roughly to the difference @x1 - x0@.
-- (See also adjustCyclic1.)
adjustCyclic :: RealFrac a
=> (Quantity d a, Quantity dy a) -> (Quantity d a, Quantity dy a)
-> Quantity d a -> Quantity dy a -> Quantity dy a
adjustCyclic (x0,y0) (x1,y1) period cycle =
y1 + cyclesOff (x1-x0, y1-y0) period cycle * cycle
-- Could be defined as:
-- adjustCyclic1 (x0/period,y0/cycle) (x1/period,y1/cycle) * cycle
-- but that has worse numerical properties!

-- | Assume that y(x) is cyclic in meaning (but not in value, as
-- for e.g. angles) where the meaning has a cycle (in y) of 1 with
-- a period (in x) of 1. Then @adjustCyclic1 (x0,y0) (x1,y1)@
-- returns a new @y1@ adjusted so that @y1 - y0@ is roughly the same
-- as @x1 - x0@.
--
-- Property of returned y1:
-- | (y1 - y0) - (x1 - x0) | < 0.5
--
-- (This is a "normalized" version of 'adjustCyclic' for cycle and
-- period of 1.)
adjustCyclic1 :: RealFrac a
=> (Dimensionless a, Dimensionless a)
-> (Dimensionless a, Dimensionless a)
-> Dimensionless a
adjustCyclic1 (x0,y0) (x1,y1) = y1 + cyclesOff1 (x1-x0, y1-y0)

-- | Adjust assuming x0 = 0 and y0 = 0
adjustCyclic0 :: RealFrac a
=> (Quantity d a, Quantity dy a)
-> Quantity d a -> Quantity dy a -> Quantity dy a
adjustCyclic0 (x1,y1) period cycle = y1 + cyclesOff (x1,y1) period cycle * cycle


-- Angle comparisons
-- -----------------

-- | Compares two angles for cyclic equality.
(==~) :: (RealFloat a, Eq a) => Angle a -> Angle a -> Bool
x ==~ y = plusMinusPi x == plusMinusPi y

-- | Compares two angles for approximate cyclic equality.
(~==~) :: (RealFloat a, AEq a) => Angle a -> Angle a -> Bool
x ~==~ y = plusMinusPi x ~== plusMinusPi y
|| zeroTwoPi x ~== zeroTwoPi y -- move the boundaries.

infixl 4 ==~, ~==~
File renamed without changes.
Expand Up @@ -8,15 +8,21 @@
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveAnyClass #-}


module Numeric.Units.Dimensional.LinearAlgebra.PosVel where module Numeric.Units.Dimensional.LinearAlgebra.PosVel where


import qualified Prelude import qualified Prelude
import Data.AEq
import Numeric.NumType.DK.Integers (TypeInt (Pos2)) import Numeric.NumType.DK.Integers (TypeInt (Pos2))
import Numeric.Units.Dimensional.Cyclic
import Numeric.Units.Dimensional.Prelude import Numeric.Units.Dimensional.Prelude
import Numeric.Units.Dimensional.LinearAlgebra import Numeric.Units.Dimensional.LinearAlgebra
import Numeric.Units.Dimensional.LinearAlgebra.VectorAD (applyLinear) import Numeric.Units.Dimensional.LinearAlgebra.VectorAD (applyLinear)


-- $setup
-- >>> :set -XDataKinds
-- >>> import Data.AEq


-- Type synonyms for clearer documentation. -- Type synonyms for clearer documentation.


Expand All @@ -40,9 +46,17 @@ data Sph d a = Sph { magnitude :: Quantity d a
, rightAscension :: PlaneAngle a , rightAscension :: PlaneAngle a
} deriving (Eq) } deriving (Eq)
deriving instance (KnownDimension d, Show a, Real a) => Show (Sph d a) -- Needed since d unknown. deriving instance (KnownDimension d, Show a, Real a) => Show (Sph d a) -- Needed since d unknown.

instance (RealFloat a, AEq a) => AEq (Sph d a) where
s1 ~== s2 =
magnitude s1 ~== magnitude s2 &&
zenith s1 ~==~ zenith s2 &&
rightAscension s1 ~==~ rightAscension s2

type SPos = Sph DRadius type SPos = Sph DRadius

data SVel a = SVel (Velocity a) (AngularVelocity a) (AngularVelocity a) data SVel a = SVel (Velocity a) (AngularVelocity a) (AngularVelocity a)
deriving (Eq, Show) deriving (Eq, Show, AEq)






Expand Down Expand Up @@ -75,8 +89,13 @@ hourAngle = rightAscension


-- Converting -- Converting
-- ---------- -- ----------
-- Converts a cartesian position vector into a spherical position vector.


-- | Converts a cartesian vector into a spherical vector.
--
-- >>> let v = _1 <: _2 <:. _3 in s2c (c2s v) ~== v
-- True
-- >>> let v = fromListErr ([1, 2, 3] *~~ meter) in s2c (c2s v) ~== v
-- True
c2s :: RealFloat a => Car d a -> Sph d a c2s :: RealFloat a => Car d a -> Sph d a
c2s c = Sph r zen az c2s c = Sph r zen az
where where
Expand All @@ -86,8 +105,14 @@ c2s c = Sph r zen az
az = atan2 y x az = atan2 y x




-- Converts a spherical position vector into a cartesian position vector. -- | Converts a spherical vector into a cartesian vector.

--
-- >>> let v = Sph _4 _2 _3 in c2s (s2c v) ~== v
-- True
-- >>> let v = Sph (4 *~ meter) _2 _3 in c2s (s2c v) ~== v
-- True
--
-- prop> let v = Sph _0 _3 _2 in s2c v == (nullVector :: CVel Double)
s2c :: Floating a => Sph d a -> Car d a s2c :: Floating a => Sph d a -> Car d a
s2c s = x <: y <:. z s2c s = x <: y <:. z
where where
Expand All @@ -96,8 +121,7 @@ s2c s = x <: y <:. z
y = r * sin zen * sin az y = r * sin zen * sin az
z = r * cos zen z = r * cos zen


-- Convert declination/latitude to zenith. -- | Convert declination/latitude to zenith.

toZenith :: Floating a => Angle a -> Zenith a toZenith :: Floating a => Angle a -> Zenith a
toZenith decl = tau / _4 - decl toZenith decl = tau / _4 - decl


Expand All @@ -123,6 +147,9 @@ type SPosVel a = (SPos a, SVel a)
-- Conversions -- Conversions
-- ----------- -- -----------


-- |
-- >>> let pv = (fromListErr ([1, 2, 3] *~~ meter), fromListErr ([2, 2, 3] *~~ (meter / second))) in s2cEphem (c2sEphem pv) ~== pv
-- True
c2sEphem :: RealFloat a => CPosVel a -> SPosVel a c2sEphem :: RealFloat a => CPosVel a -> SPosVel a
c2sEphem = pvf . applyLinear (fp . c2s) -- unlinearize (c2s . linearize c :: RealFloat b => Time b -> SPos b) c2sEphem = pvf . applyLinear (fp . c2s) -- unlinearize (c2s . linearize c :: RealFloat b => Time b -> SPos b)
where where
Expand Down
Expand Up @@ -7,6 +7,15 @@ import Numeric.Units.Dimensional.Prelude
import Numeric.Units.Dimensional.LinearAlgebra import Numeric.Units.Dimensional.LinearAlgebra
import qualified Prelude import qualified Prelude


-- $setup
-- >>> :set -XTypeSynonymInstances
-- >>> :set -XFlexibleInstances
-- >>> :set -XFlexibleContexts
-- >>> :set -XDataKinds
-- >>> import Data.AEq
-- >>> import Test.QuickCheck
-- >>> instance (HasDimension (Data.Proxy.Proxy d), Arbitrary a, Num a) => Arbitrary (Quantity d a) where arbitrary = (*~ siUnit) <$> arbitrary

-- | The type of homogenous vectors with three elements. -- | The type of homogenous vectors with three elements.
type Homo3 d = Vec d 3 type Homo3 d = Vec d 3


Expand All @@ -26,16 +35,22 @@ type Homo33 d = Mat d 3 3
-- Rotation matrices. Rotates a vector by the given angle (analogous -- Rotation matrices. Rotates a vector by the given angle (analogous
-- to rotating the coordinate system in opposite direction). -- to rotating the coordinate system in opposite direction).


-- |
-- prop> rotX a ~== rotV unit_x (a :: Angle Double)
rotX :: Floating a => PlaneAngle a -> Homo33 DOne a rotX :: Floating a => PlaneAngle a -> Homo33 DOne a
rotX a = (_1 <: _0 <:. _0 ) rotX a = (_1 <: _0 <:. _0 )
|: (_0 <: cos a <:. negate (sin a)) |: (_0 <: cos a <:. negate (sin a))
|:. (_0 <: sin a <:. cos a ) |:. (_0 <: sin a <:. cos a )


-- |
-- prop> rotY a ~== rotV unit_y (a :: Angle Double)
rotY :: Floating a => PlaneAngle a -> Homo33 DOne a rotY :: Floating a => PlaneAngle a -> Homo33 DOne a
rotY a = ( cos a <: _0 <:. sin a) rotY a = ( cos a <: _0 <:. sin a)
|: ( _0 <: _1 <:. _0) |: ( _0 <: _1 <:. _0)
|:. (negate (sin a) <: _0 <:. cos a) |:. (negate (sin a) <: _0 <:. cos a)


-- |
-- prop> rotZ a ~== rotV unit_z (a :: Angle Double)
rotZ :: Floating a => PlaneAngle a -> Homo33 DOne a rotZ :: Floating a => PlaneAngle a -> Homo33 DOne a
rotZ a = (cos a <: negate (sin a) <:. _0) rotZ a = (cos a <: negate (sin a) <:. _0)
|: (sin a <: cos a <:. _0) |: (sin a <: cos a <:. _0)
Expand Down
8 changes: 3 additions & 5 deletions stack.yaml
Expand Up @@ -15,7 +15,7 @@
# resolver: # resolver:
# name: custom-snapshot # name: custom-snapshot
# location: "./custom-snapshot.yaml" # location: "./custom-snapshot.yaml"
resolver: lts-13.19 resolver: lts-17.9
allow-newer: true allow-newer: true


# User packages to be built. # User packages to be built.
Expand All @@ -38,15 +38,13 @@ allow-newer: true
# will not be run. This is useful for tweaking upstream packages. # will not be run. This is useful for tweaking upstream packages.
packages: packages:
- '.' - '.'
- '../dimensional'
- '../dimensional-dk-experimental'
- '../dimensional-homo' - '../dimensional-homo'
# Dependency packages to be pulled from upstream that are not in the resolver # Dependency packages to be pulled from upstream that are not in the resolver
# (e.g., acme-missiles-0.3) # (e.g., acme-missiles-0.3)
extra-deps: extra-deps:
- numeric-quest-0.2.0.2 - numeric-quest-0.2.0.2
- HaTeX-3.20.0.0 - git: https://github.com/bjornbm/dimensional-dk-experimental.git
- wl-pprint-extras-3.5.0.5 commit: 0516f3f10f2589b414a8ede6b949e44dbc1ceebc # origin/dimensional-head


# Override default flag values for local packages and extra-deps # Override default flag values for local packages and extra-deps
flags: {} flags: {}
Expand Down
8 changes: 8 additions & 0 deletions test/Doctests.hs
@@ -0,0 +1,8 @@
import System.FilePath.Glob (glob)
import Test.DocTest (doctest)

main = glob "src/**/*.*hs" >>= doctest
-- main = do
-- src_hs <- glob "src/**/*.hs"
-- src_lhs <- glob "src/**/*.lhs"
-- doctest (src_hs ++ src_lhs ++ ["test/TestInstances.hs", "test/TestUtil.hs"])
File renamed without changes.
File renamed without changes.

0 comments on commit 6dbba52

Please sign in to comment.