Skip to content

Commit

Permalink
New types for independent and dependent quantities
Browse files Browse the repository at this point in the history
Addresses issue #7.  This is mostly a proof of concept, does not work
very well.

Complex numbers may not currently work (at a first glance, it looks
impossible to have Complex{IndependentMeasurement}, see for example
log(::Complex{T}) which internally has log(convert(T,2))::T which cannot
be true).  As a work around, complex numbers may be forced to
Complex{DependentMeasurement}, which however defeats the purpose of this
change.

Still TODO: implement specific methods for `result` function with
`IndependentMeasurement`-only arguments, which will greatly improve
performance.
  • Loading branch information
giordano committed Oct 30, 2016
1 parent 22d8e17 commit ffa7351
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 69 deletions.
19 changes: 12 additions & 7 deletions src/Measurements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,17 @@ include("derivatives-type.jl")
# measurement and propagate the uncertainty in the case of functions with
# more than one argument (in order to deal with correlation between
# arguments).
immutable Measurement{T<:AbstractFloat} <: AbstractFloat
abstract Measurement{T<:AbstractFloat} <: AbstractFloat

immutable IndependentMeasurement{T<:AbstractFloat} <: Measurement{T}
val::T
err::T
tag::Float64
end

immutable DependentMeasurement{T<:AbstractFloat} <: Measurement{T}
val::T
err::T
der::Derivatives{Tuple{T, T, Float64}, T}
end

Expand All @@ -70,11 +77,9 @@ uncertainty. `err` defaults to 0 if omitted.
The binary operator `±` is equivalent to `measurement`, so you can construct a
`Measurement` object by explicitely writing `123 ± 4`.
"""
function measurement(val::Real, err::Real=zero(float(val)))
val, err, der = promote(float(val), float(err), one(float(val)))
tag = rand()
return Measurement(val, err, tag, Derivatives((val, err, tag)=>der))
end
measurement(val::Real, err::Real=zero(float(val))) =
IndependentMeasurement(promote(float(val), float(err))..., rand())

@vectorize_2arg Real measurement
const ± = measurement

Expand Down Expand Up @@ -102,8 +107,8 @@ function show{T<:Measurement}(io::IO, measure::Complex{T})
end

include("conversions.jl")
include("comparisons-tests.jl")
include("utils.jl")
include("comparisons-tests.jl")
include("math.jl")
include("parsing.jl")

Expand Down
111 changes: 98 additions & 13 deletions src/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,116 @@
#
### Code:

### Conversions

import Base: convert, float, promote_rule

convert{T<:AbstractFloat}(::Type{Measurement{T}}, a::Irrational) =
## Independent Measurement

convert(::Type{IndependentMeasurement}, a::IndependentMeasurement) = a

convert{T<:AbstractFloat}(::Type{IndependentMeasurement{T}},
a::IndependentMeasurement{T}) = a

convert{T<:AbstractFloat}(::Type{IndependentMeasurement{T}},
a::IndependentMeasurement) =
IndependentMeasurement(T(a.val), T(a.err), a.tag)

convert(::Type{Measurement}, a::IndependentMeasurement) = a

convert{T<:AbstractFloat}(::Type{Measurement{T}}, a::IndependentMeasurement) =
IndependentMeasurement{T}(a)

# Numbers.
convert{S}(::Type{IndependentMeasurement}, a::Rational{S}) = measurement(a)
convert{R<:Real}(::Type{IndependentMeasurement}, a::R) = measurement(a)
convert(::Type{Signed}, a::Measurement) = convert(Signed, a.val)

convert{T<:AbstractFloat}(::Type{IndependentMeasurement{T}}, a::Irrational) =
measurement(T(a), zero(T))
convert{T<:AbstractFloat, S}(::Type{Measurement{T}}, a::Rational{S}) =
convert{T<:AbstractFloat, S}(::Type{IndependentMeasurement{T}}, a::Rational{S}) =
measurement(T(a), zero(T))
convert{T<:AbstractFloat}(::Type{Measurement{T}}, a::Real) =
convert{T<:AbstractFloat,R<:Real}(::Type{IndependentMeasurement{T}}, a::R) =
measurement(T(a), zero(T))

function convert{T<:AbstractFloat}(::Type{Measurement{T}}, a::Measurement)
## Dependent Measurement

convert(::Type{DependentMeasurement}, a::DependentMeasurement) = a

convert{T<:AbstractFloat}(::Type{DependentMeasurement{T}},
a::DependentMeasurement{T}) = a

function convert{T<:AbstractFloat}(::Type{DependentMeasurement{T}},
a::DependentMeasurement)
newder = Derivatives{Tuple{T, T, Float64}, T}()
for tag in keys(a.der)
newder = Derivatives(newder, (T(tag[1]), T(tag[2]), tag[3])=>T(a.der[tag]))
end
Measurement(T(a.val), T(a.err), a.tag, newder)
DependentMeasurement(T(a.val), T(a.err), newder)
end

convert(::Type{Measurement}, a::Measurement) = a
convert{S}(::Type{Measurement}, a::Rational{S}) = measurement(a)
convert(::Type{Measurement}, a::Real) = measurement(a)
convert(::Type{Signed}, a::Measurement) = convert(Signed, a.val)
convert(::Type{DependentMeasurement}, a::IndependentMeasurement) =
DependentMeasurement(a.val, a.err,
Derivatives((a.val,a.err,a.tag)=>one(a.val)))

function convert{T<:AbstractFloat}(::Type{DependentMeasurement{T}},
a::IndependentMeasurement{T})
return DependentMeasurement(a.val, a.err,
Derivatives((a.val,a.err,a.tag)=>one(T)))
end

function convert{T<:AbstractFloat}(::Type{DependentMeasurement{T}},
a::IndependentMeasurement)
val, err = T(a.val), T(a.err)
return DependentMeasurement(val, err,
Derivatives((val,err,a.tag)=>one(T)))
end

convert(::Type{Measurement}, a::DependentMeasurement) = a

convert{T<:AbstractFloat}(::Type{Measurement{T}}, a::DependentMeasurement) =
DependentMeasurement{T}(a)

# Numbers.
convert{S}(::Type{DependentMeasurement}, a::Rational{S}) =
convert(DependentMeasurement, measurement(a))
convert{R<:Real}(::Type{DependentMeasurement}, a::R) =
convert(DependentMeasurement, measurement(a))

convert{T<:AbstractFloat}(::Type{DependentMeasurement{T}}, a::Irrational) =
convert(DependentMeasurement{T}, measurement(T(a), zero(T)))
convert{T<:AbstractFloat, S}(::Type{DependentMeasurement{T}}, a::Rational{S}) =
convert(DependentMeasurement{T}, measurement(T(a), zero(T)))
convert{T<:AbstractFloat,R<:Real}(::Type{DependentMeasurement{T}}, a::R) =
convert(DependentMeasurement{T}, measurement(T(a), zero(T)))



### To floating point

float{T<:AbstractFloat}(a::Measurement{T}) = a

promote_rule{T<:AbstractFloat, S<:Real}(::Type{Measurement{T}}, ::Type{S}) =
Measurement{promote_type(T, S)}
promote_rule{T<:AbstractFloat, S<:AbstractFloat}(::Type{Measurement{T}}, ::Type{Measurement{S}}) =
Measurement{promote_type(T, S)}
### Promotion

# IndependentMeasurement + IndependentMeasurement = IndependentMeasurement
promote_rule{T<:AbstractFloat, S<:AbstractFloat}(::Type{IndependentMeasurement{T}},
::Type{IndependentMeasurement{S}}) =
IndependentMeasurement{promote_type(T, S)}
# DependentMeasurement + DependentMeasurement = DependentMeasurement
promote_rule{T<:AbstractFloat, S<:AbstractFloat}(::Type{DependentMeasurement{T}},
::Type{DependentMeasurement{S}}) =
DependentMeasurement{promote_type(T, S)}
# IndependentMeasurement + DependentMeasurement = DependentMeasurement
promote_rule{T<:AbstractFloat, S<:AbstractFloat}(::Type{IndependentMeasurement{T}},
::Type{DependentMeasurement{S}}) =
DependentMeasurement{promote_type(T, S)}
# DependentMeasurement + IndependentMeasurement = DependentMeasurement
promote_rule{T<:AbstractFloat, S<:AbstractFloat}(::Type{DependentMeasurement{T}},
::Type{IndependentMeasurement{S}}) =
DependentMeasurement{promote_type(T, S)}
# IndependentMeasurement + Real = IndependentMeasurement
promote_rule{T<:AbstractFloat, S<:Real}(::Type{IndependentMeasurement{T}}, ::Type{S}) =
IndependentMeasurement{promote_type(T, S)}
# DependentMeasurement + Real = DependentMeasurement
promote_rule{T<:AbstractFloat, S<:Real}(::Type{DependentMeasurement{T}}, ::Type{S}) =
DependentMeasurement{promote_type(T, S)}
24 changes: 15 additions & 9 deletions src/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ export @uncertain
# ∂G/∂a · previous_derivatives
function result{T<:AbstractFloat}(val::Real, der::Real, a::Measurement{T})
val, der = promote(val, der)
newder = similar(a.der)
@inbounds for tag in keys(a.der)
newder = Derivatives{Tuple{T,T,Float64},T}()
@inbounds for tag in keys(getder(a))
if tag[2] != 0.0 # Skip values with 0 uncertainty
newder = Derivatives(newder, tag=>der*a.der[tag])
newder = Derivatives(newder, tag=>der*derivative(a, tag))
end
end
# If uncertainty of "a" is null, the uncertainty of result is null as well,
Expand All @@ -52,7 +52,7 @@ function result{T<:AbstractFloat}(val::Real, der::Real, a::Measurement{T})
σ = (a.err == 0.0) ? 0.0 : abs(der*a.err)
# The tag is NaN because we don't care about tags of derived quantities, we
# are only interested in independent ones.
Measurement(val, σ, NaN, newder)
DependentMeasurement(val, σ, newder)
end

# This function is similar to the previous one, but applies to mathematical
Expand All @@ -76,14 +76,14 @@ function result(val::Real, der::Tuple{Vararg{Real}},
@assert length(der) == length(a)
a = promote(a...)
T = typeof(a[1].val)
newder = similar(a[1].der)
newder = Derivatives{Tuple{T,T,Float64},T}()
err::T = zero(T)
# Iterate over all independent variables. We first iterate over all
# variables listed in `a' in order to get all independent variables upon
# which those variables depend, then we get the `tag' of each independent
# variable, skipping variables that have been already taken into account.
@inbounds for y in a
for tag in keys(y.der)
for tag in keys(getder(y))
if tag keys(newder) # Skip independent variables already considered
σ_x = tag[2]
if σ_x != 0.0 # Skip values with 0 uncertainty
Expand All @@ -107,7 +107,7 @@ function result(val::Real, der::Tuple{Vararg{Real}},
end
end
end
return Measurement(T(val), sqrt(err), NaN, newder)
return DependentMeasurement(T(val), sqrt(err), newder)
end

# "result" function for complex-valued functions (like "besselh"). This takes
Expand Down Expand Up @@ -893,6 +893,8 @@ mod2pi(a::Measurement) = result(mod2pi(a.val), 1, a)

import Base: eps, nextfloat, maxintfloat, typemax

eps{T<:AbstractFloat}(::Type{IndependentMeasurement{T}}) = eps(T)
eps{T<:AbstractFloat}(::Type{DependentMeasurement{T}}) = eps(T)
eps{T<:AbstractFloat}(::Type{Measurement{T}}) = eps(T)
eps{T<:AbstractFloat}(a::Measurement{T}) = eps(a.val)

Expand All @@ -917,6 +919,10 @@ trunc{T<:Integer}(::Type{T}, a::Measurement) = trunc(T, a.val)
# Widening
import Base: widen

widen{T<:AbstractFloat}(::Type{IndependentMeasurement{T}}) =
IndependentMeasurement{widen(T)}
widen{T<:AbstractFloat}(::Type{DependentMeasurement{T}}) =
DependentMeasurement{widen(T)}
widen{T<:AbstractFloat}(::Type{Measurement{T}}) = Measurement{widen(T)}

# To big float
Expand All @@ -933,14 +939,14 @@ import Base: sum, prod
# arguments, so in the end the function goes from cubic to quadratic. Still not
# ideal, but this is an improvement.
sum{T<:Measurement}(a::AbstractArray{T}) =
result(sum(value(a)), (ones(length(a))...), (a...))
result(sum(value.(a)), (ones(length(a))...), (a...))

# Same as above. I'm not particularly proud of how the derivatives are
# computed, but something like this is needed in order to avoid errors with null
# nominal values: you may think to x ./ prod(x), but that would fail if one or
# more elements are zero.
function prod{T<:Measurement}(a::AbstractArray{T})
x = value(a)
x = value.(a)
return result(prod(x),
ntuple(i -> prod(deleteat!(copy(x), i)), length(x)),
(a...))
Expand Down
30 changes: 12 additions & 18 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

export stdscore, weightedmean, value, uncertainty

getder(a::DependentMeasurement) = getfield(a, :der)
getder{T<:AbstractFloat}(a::IndependentMeasurement{T}) =
Derivatives((a.val, a.err, a.tag) => one(T))

# Standard Score
"""
stdscore(measure::Measurement, expected_value::Real) -> standard_score
Expand Down Expand Up @@ -56,7 +60,7 @@ end
# Derivative and Gradient
derivative{F<:AbstractFloat, T<:AbstractFloat}(a::Measurement{F},
tag::Tuple{T, T, Float64}) =
get(a.der, tag, zero(F))
get(getder(a), tag, zero(F))

"""
derivative(x::Measurement, y::Measurement)
Expand All @@ -68,9 +72,13 @@ independent measurement `y`, calculated on the nominal value of `y`. Return
Use `Measurements.gradient` to calculate the gradient of `x` with respect to an
arrays of independent measurements.
"""
derivative(a::Measurement, b::Measurement) =
derivative(a::DependentMeasurement, b::IndependentMeasurement) =
derivative(a, (b.val, b.err, b.tag))

derivative{S<:AbstractFloat,T<:AbstractFloat}(a::IndependentMeasurement{S},
b::IndependentMeasurement{T}) =
zero(promote_type(S, T))

"""
gradient(x::Measurement, [y::AbstractArray{Measurement}])
Expand All @@ -94,25 +102,11 @@ end
# value and uncertainty
for (f, field) in ((:value, :val), (:uncertainty, :err))
@eval begin
($f)(a::IndependentMeasurement) = a.$field
($f)(a::DependentMeasurement) = a.$field
($f)(a::Measurement) = a.$field
($f){T<:AbstractFloat}(a::Complex{Measurement{T}}) =
complex(($f)(a.re), ($f)(a.im))

function ($f){T<:AbstractFloat}(A::AbstractArray{Measurement{T}})
out = similar(A, T)
for i in eachindex(A)
out[i] = ($f)(A[i])
end
return out
end

function ($f){T<:AbstractFloat}(A::AbstractArray{Complex{Measurement{T}}})
out = similar(A, Complex{T})
for i in eachindex(A)
out[i] = ($f)(A[i])
end
return out
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions test/complex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ imu = 6 ± 0.4
u = complex(5 ± 0.3, imu)
v = complex(imu, 8 ± 0.9)

k = complex(3*imu, imu/4)

# Addition and subtraction
test_approx_eq(2u - u - 3v + 2v + pi, u - v + pi)

Expand Down
Loading

0 comments on commit ffa7351

Please sign in to comment.