module Number.Physical.UnitDatabase where
import qualified Number.Physical.Unit as Unit
import qualified Algebra.Field as Field
import Algebra.NormedSpace.Sum(norm)
import Data.Maybe.HT (toMaybe)
import Data.List (findIndices, partition, unfoldr, find, minimumBy)
import NumericPrelude.Base
import NumericPrelude.Numeric
type T i a = [UnitSet i a]
data InitUnitSet i a =
InitUnitSet {
initUnit :: Unit.T i,
initIndependent :: Bool,
initScales :: [InitScale a]
}
data InitScale a =
InitScale {
initSymbol :: String,
initMag :: a,
initIsUnit :: Bool,
initDefault :: Bool
}
data UnitSet i a =
UnitSet {
unit :: Unit.T i,
independent :: Bool,
defScaleIx :: Int,
reciprocal :: Bool,
scales :: [Scale a]
}
deriving Show
data Scale a =
Scale {
symbol :: String,
magnitude :: a
}
deriving Show
extractOne :: [a] -> a
extractOne (x:[]) = x
extractOne _ = error "There must be exactly one default unit in the data base."
initScale :: String -> a -> Bool -> Bool -> InitScale a
initScale = InitScale
initUnitSet :: Unit.T i -> Bool -> [InitScale a] -> InitUnitSet i a
initUnitSet = InitUnitSet
createScale :: InitScale a -> Scale a
createScale (InitScale sym mg _ _) = (Scale sym mg)
createUnitSet :: InitUnitSet i a -> UnitSet i a
createUnitSet (InitUnitSet u ind scs) = (UnitSet u ind
(extractOne (findIndices initDefault scs))
False
(map createScale scs)
)
showableUnit :: InitUnitSet i a -> Maybe (InitUnitSet i a)
showableUnit (InitUnitSet u ind scs) =
let sscs = filter initIsUnit scs
in toMaybe (not (null sscs)) (InitUnitSet u ind sscs)
powerOfUnitSet :: (Ord i, Field.C a) => Int -> UnitSet i a -> UnitSet i a
powerOfUnitSet n us@UnitSet { unit = u, reciprocal = rec, scales = scs } =
us { unit = n *> u,
reciprocal = rec == (n>0),
scales = map (powerOfScale n) scs }
powerOfScale :: Field.C a => Int -> Scale a -> Scale a
powerOfScale n Scale { symbol = sym, magnitude = mag } =
if n>0
then Scale { symbol = sym ++ showExp n, magnitude = ringPower n mag }
else Scale { symbol = sym ++ showExp (n), magnitude = fieldPower n mag }
showExp :: Int -> String
showExp 1 = ""
showExp expo = "^" ++ show expo
positiveToFront :: [UnitSet i a] -> [UnitSet i a]
positiveToFront = uncurry (++) . partition (not . reciprocal)
decompose :: (Ord i, Field.C a) => Unit.T i -> T i a -> [UnitSet i a]
decompose u db =
case (findIndep u db) of
Just us -> [us]
Nothing ->
unfoldr (\urem ->
toMaybe (not (Unit.isScalar urem))
(let us = findClosest urem db
in (us, subtract (unit us) urem))
) u
findIndep :: (Eq i) => Unit.T i -> T i a -> Maybe (UnitSet i a)
findIndep u = find (\UnitSet {unit=un} -> u==un) . filter independent
findClosest :: (Ord i, Field.C a) => Unit.T i -> T i a -> UnitSet i a
findClosest u =
fst . minimumBy (\(_,dist0) (_,dist1) -> compare dist0 dist1) .
evalDist u . filter (not.independent)
evalDist :: (Ord i, Field.C a)
=> Unit.T i
-> T i a
-> [(UnitSet i a, Int)]
evalDist target = map (\us->
let (expo,dist)=findBestExp target (unit us)
in (powerOfUnitSet expo us, dist)
)
findBestExp :: (Ord i) => Unit.T i -> Unit.T i -> (Int, Int)
findBestExp target u =
let bestl = findMinExp (distances target (listMultiples (subtract u) (1)))
bestr = findMinExp (distances target (listMultiples ((+) u) 1 ))
in if distLE bestl bestr
then bestl
else bestr
findMinExp :: [(Int, Int)] -> (Int, Int)
findMinExp (x0:x1:rest) =
if distLE x0 x1
then x0
else findMinExp (x1:rest)
findMinExp _ = error "List of unit approximations with respect to the unit exponent must be infinite."
distLE :: (Int, Int) -> (Int, Int) -> Bool
distLE (_,dist0) (_,dist1) = dist0<=dist1
distances :: (Ord i) => Unit.T i -> [(Int, Unit.T i)] -> [(Int, Int)]
distances targetu = map (\(expo,u)->(expo, norm (subtract u targetu)))
listMultiples :: (Unit.T i -> Unit.T i) -> Int -> [(Int, Unit.T i)]
listMultiples f dir = iterate (\(expo,u)->(expo+dir,f u)) (0,Unit.scalar)