Skip to content

Commit

Permalink
Merge 9d565ea into b23a969
Browse files Browse the repository at this point in the history
  • Loading branch information
LebedevRI authored Nov 10, 2023
2 parents b23a969 + 9d565ea commit c4029d1
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 96 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
fail-fast: false
matrix:
julia-version:
- "1.0"
- "1.6"
- "1"
- "nightly"
os:
Expand Down
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# History of Measurements.jl

## v2.12.0 (????)

### New features

* Support for [symbolic variables](https://symbolics.juliasymbolics.org/stable/manual/variables/),
as provided by `Symbolics.jl` package.

## v2.11.0 (2023-11-03)

### New features
Expand Down
7 changes: 5 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ BaseType = "7fbed51b-1ef5-4d67-9085-a4a9b26f478c"
Juno = "e5e0dc1b-0480-54bc-9374-aad01c23163d"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[extensions]
MeasurementsBaseTypeExt = "BaseType"
MeasurementsJunoExt = "Juno"
MeasurementsRecipesBaseExt = "RecipesBase"
MeasurementsSpecialFunctionsExt = "SpecialFunctions"
MeasurementsSymbolicsExt = "Symbolics"
MeasurementsUnitfulExt = "Unitful"

[compat]
Expand All @@ -30,7 +32,7 @@ Calculus = "0.4.1, 0.5"
LinearAlgebra = "<0.0.1, 1"
RecipesBase = "0.6.0, 0.7, 0.8, 1.0"
Requires = "0.5.0, 1"
julia = "1"
julia = "1.6"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Expand All @@ -40,8 +42,9 @@ QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[targets]
test = ["Aqua", "BaseType", "QuadGK", "RecipesBase", "SpecialFunctions", "Statistics", "Test", "Unitful"]
test = ["Aqua", "BaseType", "QuadGK", "RecipesBase", "SpecialFunctions", "Statistics", "Symbolics", "Test", "Unitful"]
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ easy-to-use calculator.
[arbitrary precision](https://docs.julialang.org/en/v1/manual/integers-and-floating-point-numbers/#Arbitrary-Precision-Arithmetic-1)
(also called multiple precision) numbers with uncertainties. This is useful
for measurements with very low relative error
* Support for numbers with uncertainties of
[symbolic variable](https://symbolics.juliasymbolics.org/stable/manual/variables/)
type, as provided by `Symbolics.jl` package.
* Define arrays of measurements and perform calculations with them. Some linear
algebra functions work out-of-the-box
* Propagate uncertainty for any function of real arguments (including functions
Expand Down
4 changes: 2 additions & 2 deletions docs/src/appendix.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The `Measurement` Type
`Measurement` is a
[composite](https://docs.julialang.org/en/v1/manual/types/#Composite-Types-1)
[parametric](https://docs.julialang.org/en/v1/manual/types/#Parametric-Types-1)
type, whose parameter is the `AbstractFloat` subtype of the nominal value and
type, whose parameter is the `Real` subtype of the nominal value and
the uncertainty of the measurement. `Measurement` type itself is subtype of
`AbstractFloat`, thus `Measurement` objects can be used in any function taking
`AbstractFloat` arguments without redefining it, and calculation of uncertainty
Expand All @@ -20,7 +20,7 @@ will be exact.
In detail, this is the definition of the type:

```julia
struct Measurement{T<:AbstractFloat} <: AbstractFloat
struct Measurement{T<:Real} <: AbstractFloat
val::T
err::T
tag::UInt64
Expand Down
27 changes: 27 additions & 0 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,33 @@ hypot(a, b)
log(2a) ^ b
```

Symbolic Variables
------------------

You can perform any mathematical operation supported in `Measurements.jl` using
[symbolic variables](https://symbolics.juliasymbolics.org/stable/manual/variables/),
as provided by `Symbolics.jl` package:

``` julia
julia> using Measurements, Symbolics

julia> @variables x_val, x_err, y_val, y_err
4-element Vector{Num}:
x_val x_err y_val y_err

julia> x = x_val ± x_err
x_val ± x_err

julia> y = y_val ± y_err
y_val ± y_err

julia> x + y
x_val + y_val ± sqrt(abs2(x_err) + abs2(y_err))

julia> x - x
0 ± 0.0
```

Operations with Arrays and Linear Algebra
-----------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ The main features of the package are:
precision](https://docs.julialang.org/en/v1/manual/integers-and-floating-point-numbers/#Arbitrary-Precision-Arithmetic-1)
(also called multiple precision) numbers with uncertainties. This is useful
for measurements with very low relative error
- Support for numbers with uncertainties of
[symbolic variable](https://symbolics.juliasymbolics.org/stable/manual/variables/)
type, as provided by `Symbolics.jl` package.
- Define arrays of measurements and perform calculations with them. Some linear
algebra functions work out-of-the-box
- Propagate uncertainty for any function of real arguments (including functions
Expand Down
7 changes: 4 additions & 3 deletions docs/src/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ where
- `err` is its uncertainty, assumed to be a [standard
deviation](https://en.wikipedia.org/wiki/Standard_deviation).

They are both subtype of `AbstractFloat`. Some keyboard layouts provide an easy
They are both subtype of `Real`. Some keyboard layouts provide an easy
way to type the `±` sign, if your does not, remember you can insert it in Julia
REPL with `\pm` followed by `TAB` key. You can provide `val` and `err` of any
subtype of `Real` that can be converted to `AbstractFloat`. Thus,
`measurement(42, 33//12)` and `pi ± 0.1` are valid.
subtype of `Real` that can be converted to `AbstractFloat`, or are
[symbolic variables](https://symbolics.juliasymbolics.org/stable/manual/variables/).
Thus, `measurement(42, 33//12)` and `pi ± 0.1` are valid.

`measurement(value)` creates a `Measurement` object with zero uncertainty, like
mathematical constants. See below for further examples.
Expand Down
14 changes: 7 additions & 7 deletions ext/MeasurementsSpecialFunctionsExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,40 +30,40 @@ else
end
# Error function: erf, erfinv, erfc, erfcinv, erfcx, erfi, dawson

function SpecialFunctions.erf(a::Measurement{T}) where {T<:AbstractFloat}
function SpecialFunctions.erf(a::Measurement{T}) where {T<:Real}
aval = a.val
return result(erf(aval), 2*exp(-abs2(aval))/sqrt(T(pi)), a)
end

function SpecialFunctions.erfinv(a::Measurement{T}) where {T<:AbstractFloat}
function SpecialFunctions.erfinv(a::Measurement{T}) where {T<:Real}
res = erfinv(a.val)
# For the derivative, see http://mathworld.wolfram.com/InverseErf.html
return result(res, sqrt(T(pi)) * exp(abs2(res)) / 2, a)
end

function SpecialFunctions.erfc(a::Measurement{T}) where {T<:AbstractFloat}
function SpecialFunctions.erfc(a::Measurement{T}) where {T<:Real}
aval = a.val
return result(erfc(aval), -2*exp(-abs2(aval))/sqrt(T(pi)), a)
end

function SpecialFunctions.erfcinv(a::Measurement{T}) where {T<:AbstractFloat}
function SpecialFunctions.erfcinv(a::Measurement{T}) where {T<:Real}
res = erfcinv(a.val)
# For the derivative, see http://mathworld.wolfram.com/InverseErfc.html
return result(res, -sqrt(T(pi)) * exp(abs2(res)) / 2, a)
end

function SpecialFunctions.erfcx(a::Measurement{T}) where {T<:AbstractFloat}
function SpecialFunctions.erfcx(a::Measurement{T}) where {T<:Real}
aval = a.val
res = erfcx(aval)
return result(res, 2 * (aval * res - inv(sqrt(T(pi)))), a)
end

function SpecialFunctions.erfi(a::Measurement{T}) where {T<:AbstractFloat}
function SpecialFunctions.erfi(a::Measurement{T}) where {T<:Real}
aval = a.val
return result(erfi(aval), 2*exp(abs2(aval))/sqrt(T(pi)), a)
end

function SpecialFunctions.dawson(a::Measurement{T}) where {T<:AbstractFloat}
function SpecialFunctions.dawson(a::Measurement{T}) where {T<:Real}
aval = a.val
res = dawson(aval)
return result(res, one(T) - 2 * aval * res, a)
Expand Down
30 changes: 30 additions & 0 deletions ext/MeasurementsSymbolicsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
### MeasurementsSymbolicsExt.jl
#
# Copyright (C) 2023 Roman Lebedev.
#
# Maintainer: Mosè Giordano <mose AT gnu DOT org>
# Keywords: uncertainty, error propagation, physics
#
# This file is a part of Measurements.jl.
#
# License is MIT "Expat".
#
### Commentary:
#
# This file contains integration with Symbolics.jl.
#
### Code:

### Symbolics
module MeasurementsSymbolicsExt

import Measurements
import Symbolics

Measurements._is_symbolic(::Symbolics.Num) = true

Measurements.measurement(val::Symbolics.Num) = Measurements._measurement(val)

Measurements.measurement(val::Symbolics.Num, err::Symbolics.Num) = Measurements._measurement(val, err)

end
22 changes: 16 additions & 6 deletions src/Measurements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ using Calculus
# Functions provided by this package and exposed to users
export Measurement, measurement, ±

# Query whether the value is of symbolic type.
_is_symbolic(::Real) = false

# Define the "Derivatives" type, used inside "Measurement" type. This should be
# a lightweight and immutable dictionary.
include("derivatives-type.jl")
Expand All @@ -46,7 +49,7 @@ 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).
struct Measurement{T<:AbstractFloat} <: AbstractFloat
struct Measurement{T<:Real} <: AbstractFloat
val::T
err::T
tag::UInt64
Expand All @@ -72,16 +75,19 @@ function Measurement{T}(::S) where {T, S}
end

# Functions to quickly create an empty Derivatives object.
@generated empty_der1(x::Measurement{T}) where {T<:AbstractFloat} = Derivatives{T}()
@generated empty_der2(x::T) where {T<:AbstractFloat} = Derivatives{x}()
@generated empty_der1(x::Measurement{T}) where {T<:Real} = Derivatives{T}()
@generated empty_der2(x::T) where {T<:Real} = Derivatives{x}()

# Start from 1, 0 is reserved to derived quantities
const tag_counter = Threads.Atomic{UInt64}(1)

measurement(x::Measurement) = x
measurement(val::T) where {T<:AbstractFloat} = Measurement(val, zero(T), UInt64(0), empty_der2(val))

_measurement(val::T) where {T<:Real} = Measurement(val, zero(T), UInt64(0), empty_der2(val))
measurement(val::AbstractFloat) = _measurement(val)
measurement(val::Real) = measurement(float(val))
function measurement(val::T, err::T) where {T<:AbstractFloat}

function _measurement(val::T, err::T) where {T<:Real}
newder = empty_der2(val)
if iszero(err)
Measurement{T}(val, err, UInt64(0), newder)
Expand All @@ -90,8 +96,12 @@ function measurement(val::T, err::T) where {T<:AbstractFloat}
return Measurement{T}(val, err, tag, Derivatives(newder, (val, err, tag)=>one(T)))
end
end
measurement(val::Real, err::Real) = measurement(promote(float(val), float(err))...)
measurement(val::T, err::T) where {T<:AbstractFloat} = _measurement(val, err)
measurement(val::T, err::T) where {T<:Real} = measurement(float(val), float(err))
measurement(val::V, err::E) where {V<:Real, E<:Real} = measurement(promote(val, err)...)

measurement(::Missing, ::Union{Real,Missing} = missing) = missing

const ± = measurement

"""
Expand Down
18 changes: 9 additions & 9 deletions src/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
#
### Code:

Base.convert(::Type{Measurement{T}}, a::Irrational) where {T<:AbstractFloat} =
Base.convert(::Type{Measurement{T}}, a::Irrational) where {T<:Real} =
measurement(T(a))::Measurement{T}
Base.convert(::Type{Measurement{T}}, a::Rational{<:Integer}) where {T<:AbstractFloat} =
Base.convert(::Type{Measurement{T}}, a::Rational{<:Integer}) where {T<:Real} =
measurement(T(a))::Measurement{T}
Base.convert(::Type{Measurement{T}}, a::Real) where {T<:AbstractFloat} =
Base.convert(::Type{Measurement{T}}, a::Real) where {T<:Real} =
measurement(T(a))::Measurement{T}
Base.convert(::Type{Measurement{T}}, a::Base.TwicePrecision) where {T<:AbstractFloat} =
Base.convert(::Type{Measurement{T}}, a::Base.TwicePrecision) where {T<:Real} =
measurement(T(a))::Measurement{T}
Base.convert(::Type{Measurement{T}}, a::AbstractChar) where {T<:AbstractFloat} =
Base.convert(::Type{Measurement{T}}, a::AbstractChar) where {T<:Real} =
measurement(T(a))::Measurement{T}

function Base.convert(::Type{Measurement{T}}, a::Complex) where {T}
Expand All @@ -35,9 +35,9 @@ function Base.convert(::Type{Measurement{T}}, a::Complex) where {T}
end
end

Base.convert(::Type{Measurement{T}}, a::Measurement{T}) where {T<:AbstractFloat} = a
Base.convert(::Type{Measurement{T}}, a::Measurement{T}) where {T<:Real} = a
function Base.convert(::Type{Measurement{T}},
a::Measurement{<:AbstractFloat}) where {T<:AbstractFloat}
a::Measurement{<:Real}) where {T<:Real}
newder = empty_der2(zero(T))
for tag in keys(a.der)
newder = Derivatives(newder, (T(tag[1]), T(tag[2]), tag[3])=>T(a.der[tag]))
Expand All @@ -55,10 +55,10 @@ function Base.convert(::Type{Int}, a::Measurement)
return convert(Int, a.val)::Int
end

Base.promote_rule(::Type{Measurement{T}}, ::Type{S}) where {T<:AbstractFloat, S<:Real} =
Base.promote_rule(::Type{Measurement{T}}, ::Type{S}) where {T<:Real, S<:Real} =
Measurement{promote_type(T, S)}
Base.promote_rule(::Type{Measurement{T}},
::Type{Measurement{S}}) where {T<:AbstractFloat, S<:AbstractFloat} =
::Type{Measurement{S}}) where {T<:Real, S<:Real} =

Check warning on line 61 in src/conversions.jl

View check run for this annotation

Codecov / codecov/patch

src/conversions.jl#L61

Added line #L61 was not covered by tests
Measurement{promote_type(T, S)}

# adaptation of JuliaLang/julia#30952
Expand Down
Loading

0 comments on commit c4029d1

Please sign in to comment.