Talk by Takayuki Muranushi (me) in Haskell Symposium 2014, ICFP. "Type-checking Polymorphic Units for Astrophysics Research in Haskell." c.f. http://www.cis.upenn.edu/~eir/papers/2014/units/units.pdf for the full paper.
Type-checking Polymorphic Units for Astrophysics Research in Haskell
1. Experience Report: Type‐checking
Polymorphic Units for Astrophysics
Research in Haskell
Takayuki Muranushi
Advanced Institute for
Computational Science,
RIKEN
takayuki.muranushi@riken.jp
Richard A. Eisenberg
University of Pennsylvania
eir@cis.upenn.edu
2. July 23,
1983 .
• Air Canada Flight 143, a Boeing 767‐233 was
departing Montreal to Edmonton.
• The fuel gauge is not working.
• The crews use a backup system and manually
calculated the amount of fuel to be refueled.
3. Manual calculation
mass of
fuel required density of fuel volume to be refueled
÷ =
[kg] [pound/L] [*L]
The correct calculation
mass of
fuel required density of fuel volume to be refueled
÷ =
[kg] [kg/L] [L]
Flight 143 took off with 22,300 pounds of fuel to
Edmonton, where 22,300 kg was actually needed.
4. • Flight 143 made an emergency landing on
runway 32L of Gimli abandoned airport.
• It was “Family Day” festival; go‐carts, campers,
families and barbecues were on 32L.
• No one was seriously hurt nor killed.
Wade H. Nelson (1997)
5. A same law of physics can be
represented in many different units
Mass of
Density of fuel Volume to be
fuel required Dimensions Level:
Mass ÷ Density =
Volume
[kg] [kg/L] [L]
÷ =
[lb.] ÷ [lb./L] = [L]
Units Level:
÷ = refueled
Quantity Level:
quantity value = numerical value [ unit ]
[kg] ÷ [lb./L] = [L]
6. “units‐of‐measure are to science what
types are to programming” ‐‐‐ A. J. Kennedy
• To avoid mistakes like Gimli Glider, we would
like to use type system to enforce the
correctness of the dimensions and units in our
calculations.
• Such correctness of “laws of physics” is more
than just about specific set of units; we can
represent one quantity in many different units,
but they mean the same quantity.
7. “units‐of‐measure are to science what
types are to programming” ‐‐‐ A. J. Kennedy
“Laws of physics are dimension‐monomorphic
and unit‐polymorphic”
‐‐‐ T. Muranushi
Dimensions Level:
Mass ÷ Density = Volume
[kg] ÷ [kg/L] = [L]
[lb.] ÷ [lb./L] = [L]
Units Level:
8. “units‐of‐measure are to science what
types are to programming” ‐‐‐ A. J. Kennedy
“Laws of physics are dimension‐monomorphic
and unit‐polymorphic”
‐‐‐ T. Muranushi
• We already have type system of units for many
languages including C, F#, simulink and of
course in Haskell; we already have
polymorphism. Will they blend?
9. Using `unittyped` by Thijs Alkemade,
I started an attempt to encode knowledge
of physics in Haskell.
10. A unit‐monomorphic quantity function
refuel :: Fractional f =>
Value Mass KiloGram f
‐> Value Density KiloGramPerLiter f
‐> Value Volume Liter f
refuel gasMass gasDen = gasMass |/| gasDen
A unit‐polymorphic version?
refuel :: Fractional f =>
Value Mass uniMass f ‐‐ Here we replace the unit types
‐> Value Density uniDen f ‐‐ with type variables
‐> Value Volume uniVol f ‐‐
refuel gasMass gasDen = gasMass |/| gasDen
This code doesn’t compile.
11. This code
refuel :: Fractional f =>
Value Mass uniMass f
‐> Value Density uniDen f
‐> Value Volume uniVol f
refuel gasMass gasDen = gasMass |/| gasDen
needs these annotations to compile:
refuel :: (Fractional f,
Convertible' Mass uniMass,
Convertible' Density uniDen,
Convertible' Volume uniVol,
MapNeg negUniDen uniDen, ‐‐ negUniDen = 1 / uniDen
MapMerge uniMass negUniDen uniVol ‐‐ uniMass * negUniDen = uniVol
) =>
Value Mass uniMass f
‐> Value Density uniDen f
‐> Value Volume uniVol f
refuel gasMass gasDen = gasMass |/| gasDen
12. Problem with unit polymorphism in `unittyped`
too much type constraint!!
Colors indicate: Type constraints, Types, Values
refuel :: (Fractional f,
Convertible' Mass umass,
Convertible' Density uden,
Convertible' Volume uvol,
MapNeg negUden uden,
MapMerge umass negUden uvol) =>
Value Mass umass f
gravityPoisson ::
(Fractional x
, dimLen ~ LengthDimension
, dimPot ~ '[ '(Time, NTwo), '(Length, PTwo)]
, dimDen ~ Density
, dimZhz ~ '[ '(Time, NTwo)]
, Convertible' dimLen uniLen
, Convertible' dimPot uniPot
, Convertible' dimDen uniDen
, Convertible' dimZhz uniZhz
, Convertible' dimZhz uniZhz'
, MapMerge dimLen dimLen dimLen2
, MapNeg dimLen2 dimLenNeg2
, MapMerge dimPot dimLenNeg2 dimZhz
, MapMerge dimDen '[ '(Time, NTwo), '(Length, PThree),
'(Mass, NOne) ] dimZhz
, MapMerge uniLen uniLen uniLen2
, MapNeg uniLen2 uniLenNeg2
, MapMerge uniPot uniLenNeg2 uniZhz
, MapMerge uniDen '[ '(Second, NTwo), '(Meter,
PThree), '((Kilo Gram), NOne) ] uniZhz'
) =>
(forall s. AD.Mode s =>
Vec3 (Value dimLen uniLen (AD s x)) ‐> Value dimPot
uniPot (AD s x))
‐> (Vec3 (Value dimLen uniLen x) ‐> (Value dimDen
uniDen x))
‐> (Vec3 (Value dimLen uniLen x) ‐> (Value dimZhz
uniZhz x))
gravityPoisson gravitationalPotential density r
= laplacian gravitationalPotential r |‐| (4 *| pi |*|
density r |*| g)
‐> Value Density uden f
‐> Value Volume uvol f
refuel gasMass gasDen = gasMass |/| gasDen
gravityPoisson ::
(Fractional x
, dimLen ~ LengthDimension
, dimPot ~ '[ '(Time, NTwo), '(Length, PTwo)]
, dimDen ~ Density
, dimZhz ~ '[ '(Time, NTwo)]
, Convertible' dimLen uniLen
, Convertible' dimPot uniPot
, Convertible' dimDen uniDen
, Convertible' dimZhz uniZhz
, Convertible' dimZhz uniZhz'
, MapMerge dimLen dimLen dimLen2
, MapNeg dimLen2 dimLenNeg2
, MapMerge dimPot dimLenNeg2 dimZhz
, MapMerge dimDen '[ '(Time, NTwo), '(Length, PThree), '(Mass, NOne) ] dimZhz
, MapMerge uniLen uniLen uniLen2
, MapNeg uniLen2 uniLenNeg2
, MapMerge uniPot uniLenNeg2 uniZhz
, MapMerge uniDen '[ '(Second, NTwo), '(Meter, PThree), '((Kilo Gram), NOne) ] uniZhz'
) =>
(forall s. AD.Mode s =>
Vec3 (Value dimLen uniLen (AD s x)) ‐> Value dimPot uniPot (AD s x))
‐> (Vec3 (Value dimLen uniLen x) ‐> (Value dimDen uniDen x))
‐> (Vec3 (Value dimLen uniLen x) ‐> (Value dimZhz uniZhz x))
gravityPoisson gravitationalPotential density r
= laplacian gravitationalPotential r |‐| (4 *| pi |*| density r |*| g)
gravitationalPotentialToDensity ::
forall x
dimLen dimDen dimDen' dimLen2 dimNegLen2 dimZhz dimNegGC dimPot
uniLen uniDen uniDen' uniLen2 uniNegLen2 uniZhz uniNegGC uniPot .
(Fractional x
, dimLen ~ LengthDimension
, dimPot ~ '[ '(Time, NTwo), '(Length, PTwo)]
, dimDen ~ Density
, MapEq dimDen' dimDen
, Convertible' dimLen uniLen
, Convertible' dimPot uniPot
, Convertible' dimZhz uniZhz
, Convertible' dimDen' uniDen'
, Convertible' dimDen uniDen
, MapMerge dimLen dimLen dimLen2
, MapNeg dimLen2 dimNegLen2
, MapMerge dimPot dimNegLen2 dimZhz
, MapNeg '[ '(Time, NTwo), '(Length, PThree), '(Mass, NOne) ] dimNegGC
, dimNegGC ~ '[ '(Time, PTwo), '(Length, NThree), '(Mass, POne) ]
, MapMerge dimZhz dimNegGC dimDen'
, MapMerge uniLen uniLen uniLen2
, MapNeg uniLen2 uniNegLen2
, MapMerge uniPot uniNegLen2 uniZhz
, MapNeg '[ '(Second, NTwo), '(Meter, PThree), '((Kilo Gram), NOne) ] uniNegGC
, uniNegGC ~ '[ '(Second, PTwo), '(Meter, NThree), '((Kilo Gram), POne) ]
, MapMerge uniZhz uniNegGC uniDen'
) =>
(forall s. AD.Mode s =>
Vec3 (Value dimLen uniLen (AD s x)) ‐> Value dimPot uniPot (AD s x))
‐> (Vec3 (Value dimLen uniLen x) ‐> (Value dimDen uniDen x))
gravitationalPotentialToDensity gravitationalPotential r
= to (undefined :: Value dimDen uniDen x) $
(laplacian gravitationalPotential r |/| (4 *| pi |*| g) :: Value dimDen' uniDen' x)
hydrostatic ::
forall x
dimLen dimPre dimDen dimAcc dimGpr dimNegLen dimNegDen dimAcc'
uniLen uniPre uniDen uniAcc uniGpr uniNegLen uniNegDen uniAcc' .
( Fractional x
, dimLen ~ LengthDimension
, dimPre ~ Pressure
, dimDen ~ Density
, dimAcc ~ Acceleration
, dimNegDen ~ '[ '(Length, PThree), '(Mass, NOne) ]
, dimGpr ~ '[ '(Length, NTwo), '(Mass, POne) , '(Time, NTwo) ]
, Convertible' dimLen uniLen
, Convertible' dimPre uniPre
, Convertible' dimGpr uniGpr
, Convertible' dimDen uniDen
, Convertible' dimAcc uniAcc
, Convertible' dimAcc' uniAcc'
, MapNeg dimLen dimNegLen
, MapMerge dimPre dimNegLen dimGpr
, MapNeg dimDen dimNegDen
, MapMerge dimGpr dimNegDen dimAcc'
, MapEq dimAcc' dimAcc
, MapNeg uniLen uniNegLen
, MapMerge uniPre uniNegLen uniGpr
, MapNeg uniDen uniNegDen
, MapMerge uniGpr uniNegDen uniAcc'
) =>
(forall s. AD.Mode s =>
Vec3 (Value dimLen uniLen (AD s x)) ‐> Value dimPre uniPre (AD s x))
‐> (Vec3 (Value dimLen uniLen x) ‐> Value dimDen uniDen x)
‐> (Vec3 (Value dimLen uniLen x) ‐> Vec3 (Value dimAcc uniAcc x))
‐> (Vec3 (Value dimLen uniLen x) ‐> Vec3 (Value dimAcc uniAcc x))
hydrostatic pressure density externalAcc r
= compose $ ¥i ‐> to (undefined :: Value dimAcc uniAcc x) $
(externalAcc r ! i) |+| (gradP r ! i) |/| (density r)
where
gradP :: Vec3 (Value dimLen uniLen x) ‐> Vec3 (Value dimGpr uniGpr x)
gradP = grad pressure
pressureToAcc ::
forall x
dimLen dimPre dimDen dimAcc dimGpr dimNegLen dimNegDen dimAcc'
uniLen uniPre uniDen uniGpr uniNegLen uniNegDen uniAcc' .
( Fractional x
, dimLen ~ LengthDimension
, dimPre ~ Pressure
, dimDen ~ Density
, dimAcc ~ Acceleration
, dimNegDen ~ '[ '(Length, PThree), '(Mass, NOne) ]
, dimGpr ~ '[ '(Length, NTwo), '(Mass, POne) , '(Time, NTwo) ]
, Convertible' dimLen uniLen
, Convertible' dimPre uniPre
, Convertible' dimGpr uniGpr
, Convertible' dimDen uniDen
, Convertible' dimAcc' uniAcc'
, Convertible' dimAcc uniAcc'
, MapNeg dimLen dimNegLen
, MapMerge dimPre dimNegLen dimGpr
, MapNeg dimDen dimNegDen
, MapMerge dimGpr dimNegDen dimAcc'
, MapEq dimAcc' dimAcc
, MapNeg uniLen uniNegLen
, MapMerge uniPre uniNegLen uniGpr
, MapNeg uniDen uniNegDen
, MapMerge uniGpr uniNegDen uniAcc'
) =>
(forall s. AD.Mode s =>
Vec3 (Value dimLen uniLen (AD s x)) ‐> Value dimPre uniPre (AD s x))
‐> (Vec3 (Value dimLen uniLen x) ‐> Value dimDen uniDen x)
‐> (Vec3 (Value dimLen uniLen x) ‐> Vec3 (Value dimAcc uniAcc' x))
pressureToAcc pressure density r
= compose $ ¥i ‐> to (undefined :: Value dimAcc uniAcc' x) $
(gradP r ! i) |/| (density r)
where
gradP :: Vec3 (Value dimLen uniLen x) ‐> Vec3 (Value dimGpr uniGpr x)
gradP = grad pressure
gravitationalPotentialToAcc ::
forall x
dimLen dimPot dimAcc' dimNegLen dimAcc
uniLen uniPot uniAcc' uniNegLen
( Fractional x
, dimLen ~ LengthDimension
, dimPot ~ '[ '(Time, NTwo), '(Length, PTwo)]
, dimAcc' ~ '[ '(Length, POne), '(Time, NTwo)]
, dimAcc ~ Acceleration
, Convertible' dimLen uniLen
, Convertible' dimPot uniPot
, Convertible' dimAcc' uniAcc'
, Convertible' dimAcc uniAcc'
, MapNeg dimLen dimNegLen
, MapMerge dimPot dimNegLen dimAcc'
, MapEq dimAcc' dimAcc
, MapNeg uniLen uniNegLen
, MapMerge uniPot uniNegLen uniAcc'
) =>
(forall s. AD.Mode s =>
Vec3 (Value dimLen uniLen (AD s x)) ‐> Value dimPot uniPot (AD s x))
‐> (Vec3 (Value dimLen uniLen x) ‐> Vec3 (Value dimAcc uniAcc' x))
gravitationalPotentialToAcc pot r =
(fmap $ to (undefined :: Value dimAcc uniAcc x)) $
grad pot r
We need at least two
type constraints per one
arithmetic operator, in
order to encode type‐level
unit calculations.
15. Quantity representation in `unittyped`
Type constructor takes (dimensions) (units) (numerical value)
λ> :t 88 *| mile |/| hour
… :: Fractional f =>
Value '[ '(Length, POne), '(Time, NOne)]
'[ '(Mile, POne), '(Hour, NOne)] f
Quantity representation in `units`
Type constructor takes
(dimensions) (map from dimensions to units) (numerical value )
λ> :t 88 % mile :/ hour :: Fractional f => Qu Velocity SI f
… :: Fractional f =>
Qu '[ '(Length, One), '(Time, MOne)]
'[ '(Length, Meter), '(Time, Second), …] f
16. System of Units as type argument
• The map from dimensions to units represents
a system of units; e.g. SI system, CGS
(centimeter–gram–second) system, etc.
λ> 88 % Miles :/ Hour :: Qu Velocity SI Float
39.33952 m/s
λ> :info SI
type SI = MkLCSU
'[(Length, Meter), (Mass, Kilo :@ Gram), (Time, Second),
(Current, Ampere), (Temperature, Kelvin),
(AmountOfSubstance, Mole),
(LuminousIntensity, Lumen)]
λ> :info CGS
type CGS = MkLCSU
'[(Length, Centi :@ Meter), (Mass, Gram), (Time, Second)]
17. [Def] A coherent system of unit ℓ
ℓ = {u1, u2, … , un}∪ {u1
p u2
q …un
r | p,q, … , r ∈ }
base units units derived by products of base units
• A Joule (1 [kg/m2s2]) is the coherent derived unit of
energy in SI
• An erg (1 [g/cm2s2] = 10-7J) is the coherent derived
unit of energy in centimeter‐gram‐second system
1:1 mapping between dimensions and units
[kg] ÷ [kg/m3] = [m3]
[SI mass]÷ [SI density] = [SI volume]
18. Unit‐polymorphic calculations in `units`
refuel :: Fractional f =>
Qu Mass ℓ f ‐> Qu Density ℓ f ‐> Qu Volume ℓ f
refuel gasMass gasDen = gasMass |/| gasDen
• local coherent system of unit ℓ is the only one,
unconstrained, type variable
• The computation is nondimensionalized; can be
carried out without details units.
[kg] ÷ [kg/m3] = [m3]
[SI mass]÷ [SI density] = [SI volume]
19. • Unit polymorphism with fundeps gave rise to
overwhelming complexes of constrained type
variables
refuel :: (Fractional f,
Convertible' Mass umass,
Convertible' Density uden,
Convertible' Volume uvol,
MapNeg negUden uden,
MapMerge umass negUden uvol) =>
Value Mass umass f
‐> Value Density uden f
‐> Value Volume uvol f
refuel gasMass gasDen = gasMass |/| gasDen
• Take local coherent system of unit ℓ as only one
free variable
refuel :: Fractional f =>
Qu Mass ℓ f ‐> Qu Density ℓ f ‐> Qu Volume ℓ f
refuel gasMass gasDen = gasMass |/| gasDen
21. Exercise: How many Newtons is the Lennard-Jones
force F between two argon atoms at distance 4Å,
where
Solution #1:
ljForce :: Energy ℓ Float ‐> Length ℓ Float
‐> Length ℓ Float ‐> Force ℓ Float
ljForce eps sigma r
= (24 *| eps |*| sigma |ˆ pSix) |/| (r |ˆ pSeven)
|‐|(48 *| eps |*| sigma |ˆ pTwelve) |/| (r |ˆ pThirteen)
λ> let sigmaAr = 3.4e‐8 % Meter
epsAr = 1.68e‐21 % Joule
r = 4.0e‐8 % Meter
λ> (ljForce epsAr sigmaAr r :: Force SI Float) # Newton
NaN
22. Exercise: How many Newtons is the Lennard-Jones
force F between two argon atoms at distance 4Å,
where
Solution #2:
ljForce :: Energy ℓ Float ‐> Length ℓ Float
‐> Length ℓ Float ‐> Force ℓ Float
ljForce eps sigma r
= (24 *| eps |*| sigma |ˆ pSix) |/| (r |ˆ pSeven)
|‐|(48 *| eps |*| sigma |ˆ pTwelve) |/| (r |ˆ pThirteen)
type CU = MkLCSU '[ '(Length, Angstrom),
'(Mass, ProtonMass), '(Time, Pico :@ Second)]
λ> (ljForce epsAr sigmaAr r :: Force CU Float) # Newton
9.3407324e‐14
23. Astrophysics research in Haskell
• A 27‐page astrophysics paper has been
written in Haskell; its quantitative reasoning is
powered by the units library.
25. When you design type system of units
consider unit polymorphism
because, with unit polymorphism
• We can faithfully express unit‐independent
nature of laws of physics.
• We can write quantity expressions, which
users can later interpret in unit systems of
their choice.
• We can avoid overflows/ underflows by
appropriately choosing system of units.
26. • An easy way to implement unit
polymorphism is to take local coherent
system of unit ℓ as only one free variable
refuel :: Fractional f =>
Qu Mass ℓ f ‐> Qu Density ℓ f ‐> Qu Volume ℓ f
refuel gasMass gasDen = gasMass |/| gasDen
27. In the paper
• Extensibility
• Quantity combinators
• Value‐level units
• Protect numerical values from manipulation
Things came after paper
• defaultLCSU
• Template Haskell
28. Comments
• Haskell is such a cool language that allows
something like `units` at all. Its type system is
so programmable that these features can be built
on top of, instead of being integrated in (like F#)
or externally analyzed (like C)
• As far as we know, `units` is the only practical
system that supports unit polymorphism.
• Heartfelt thanks to all people’s work that enabled
GHC 7.8, and to Richard who implemented
`units`.
29. ↑ ✈ Thanks!
↓ ✈ Questions?
cabal install units
and enjoy unit polymorphism!
refuel :: Fractional f =>
Qu Mass ℓ f ‐> Qu Density ℓ f ‐> Qu Volume ℓ f
refuel gasMass gasDen = gasMass |/| gasDen