API

`err` at the REPL / top level

Julia's REPL defines its own global variable err (the last thrown error, the companion of ans) in Base.MainInclude. At the REPL prompt, in julia -e scripts, or at the top level of an included file, this variable shadows the err function exported by ADerrors, producing an UndefVarError. Inside functions and user modules bare err(...) always works. At the top level of a script or at the REPL, use the qualified form ADerrors.err(...).

Observables: creating uwreal values

ADerrors.uwreal — Type
uwreal(x::Float64)
uwreal([value::Float64, error::Float64], mcid)
uwreal(data::Vector{Float64}, mcid[, replica::Vector{Int64}])
uwreal(data::Vector{Float64}, mcid[, replica::Vector{Int64}], idm::Vector{Int64}, nms::Int64)

Returns an uwreal data type. Depending on the first argument, the uwreal stores the following information:

  • Input is a single Float64. In this case the variable acts in exactly the same way as a real number. This is understood as a quantity with zero error.
  • Input is a 2 element Vector of Float64 [value, error]. In this case the data is understood as value +/- error.
  • Input is a Vector of Float64 of length larger than 4. In this case the data is understood as consecutive measurements of an observable in a Monte Carlo (MC) simulation.

In the last two cases, an ensemble ID is required as input. Data with the same ID are considered as correlated (i.e. fully correlated for the case of a value +/- error observables measured on the same sample for the case of data from a MC simulation). The preferred way to input the ensemble tag is via a String that uniquely identifies the ensemble, but an integer is also supported for legacy reasons. Note that the legacy integer interface is deprecated and a warning is issued on each use; also note that a String name that parses as an integer (e.g. "100") is mapped to the same internal ID as the legacy integer 100, so the two interfaces share the ID space and such names should be avoided. For example:

using ADerrors # hide
a = uwreal([1.2, 0.2], "Simple var with error")   # a = 1.2 +/- 0.2
b = uwreal([1.2, 0.2], "Simple var with error")   # b = 1.2 +/- 0.2
c = uwreal([1.2, 0.2], "Another var with error")  # c = 1.2 +/- 0.2

d = a-b
uwerr(d)
println("d has zero error because a and b are correlated", d)

e = a-c
uwerr(e)
println("e has non zero error because a and c are independent", e)

Replica

data can contain measurements in several replica (i.e. independent simulations with the same physical and algorithmic parameters). How many measurements correspond to each replica are specified by an optional replica vector argument replica. Note that length(replica) is the number of replica (i.e. independent simulations), and that sum(replica) must match length(data)

using ADerrors # hide
# 1000 measurements in three replica of lengths
# 500, 100 and 400
a = uwreal(rand(1000), "Ensemble with three replica", [500, 100, 400]) 

Gaps in the measurements

In some situations an observable is not measured in every configuration. In this case two additional arguments are needed to define the observable

  • idm. Type Vector{Int64}. idm[n] labels the configuration where data[n]is measured.
  • nms. Type Int64. The total number of measurements in the ensemble
using ADerrors # hide
# Observable measured on the odd configurations 
# 1, 3, 5, ..., 999 on an emsemble of length 1000
a = uwreal(rand(500), "Observable with gaps", collect(1:2:999), 1000)

# Observable measured on the first 900 configurations 
# on the same emsemble
b = uwreal(rand(900), "Observable with gaps", collect(1:900), 1000)

Note that in this case, if the ensemble has different replica, sum(replica) must match nms

using ADerrors # hide
# Observable measured on the even configurations 
# 2, 4, 6, ..., 200 on an emsemble of length 200
# with two replica of lengths 75, 125
a = uwreal(data_a[1:500], "Observable with gaps in an ensemble with replica", [75, 125], collect(2:2:200), 200)
source
ADerrors.cobs — Function
cobs(avgs::Vector{Float64}, Mcov::Array{Float64, 2}, str::String)
cobs(avgs::Vector{Float64}, Mcov::Array{Float64, 2}, ids::Vector{Int64})

Returns a vector of uwreal such that their mean values are avgs[:] and their covariance is Mcov[:,:]. In order to construct these observables n=length(avgs) ensemble ID are used. These are generated either from the string str or have to be specified in the vector ids[:].

using ADerrors # hide
# Put some average values and covariance 
avg = [16.26, 0.12, -0.0038]
Mcov = [0.478071 -0.176116 0.0135305
        -0.176116 0.0696489 -0.00554431
        0.0135305 -0.00554431 0.000454180]

# Produce observables with ensemble ID 
# [1, 2001, 32]. Do error analysis
p = cobs(avg, Mcov, "GF beta function parameters")
uwerr.(p)

# Check central values are ok
avg2 = value.(p)
println("Better be zero: ", sum((avg.-avg2).^2))

# Check that the covariance is ok
Mcov2 = cov(p)
println("Better be zero: ", sum((Mcov.-Mcov2).^2))
source

uwreal values can be created from MC series measured on a named ensemble (or replica ensembles, for several runs with the same simulation parameters), or from central values with externally known covariances (cobs).

Error analysis (uwerr)

ADerrors.uwerr — Function
 uwerr(a::uwreal[, wpm::Dict{Int64,Vector{Float64}}])
 uwerr(a::uwreal[, wpm::Dict{String,Vector{Float64}}])

Performs error analysis on the observable a.

using ADerrors # hide
a = uwreal([1.3, 0.01], "Var with error") # 1.3 +/- 0.01
b = sin(2.0*a)
uwerr(b)
println("Look how I propagate errors:              ", b)
c = 1.0 + b - 2.0*sin(a)*cos(a)
uwerr(c)
println("Look how good I am at this (zero error!): ", c)

Optimal window

Error in data coming from a Monte Carlo ensemble is determined by summing the autocorrelation function $\Gamma_{\rm ID}(t)$ of the data for each ensemble ID. In practice this sum is truncated up to a window $W_{\rm ID}$.

By default, the summation window is determined as proposed by U. Wolff with a parameter $S_{\tau} = 4$, but other methods are avilable via the optional argument wpm.

For each ensemble, one a pass the following parameters:

  • window: The autocorrelation function is summed up to t = round(vp[1]).
  • stau: The sumation window is determined using U. Wolff poposal with $S_{\tau} = {\rm vp[2]}$.
  • signal: The autocorrelation function $\Gamma(t)$ is summed up a point where its error $\delta\Gamma(t)$ is a factor vp[3] times larger than the signal.

These three parameters fix the summation window. The following options also affect the error analysis

  • texp: Add a tail to the autocorrelation function with this value of $\tau_{\rm exp}$. See the reference for an explanation of the procedure.
  • bin: Bin the data before computing the autocorrelation function, usg this value as bin size.
using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

# Load the data in a uwreal
a = uwreal(x.^2, "Random walk in [-1,1]")
wpm = Dict{String,Dict{String, Real}}()

# Use default analysis (stau = 4.0)
uwerr(a)
println("default:                   ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
println("  - window: ", window(a, "Random walk in [-1,1]"))

# This will still do default analysis because 
# a does not depend on emsemble foo
wpm["Ensemble foo"] = Dict("window" => 34)
uwerr(a, wpm)
println("default:                   ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
println("  - window: ", window(a, "Random walk in [-1,1]"))

# Use default analysis (stau = 4.0), bin data with bin size 5
wpm["Random walk in [-1,1]"] = Dict("bin" => 5)
uwerr(a, wpm)
println("default (bin size 5):     ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
println("  - window: ", window(a, "Random walk in [-1,1]"))

# Fix the summation window to 1 (i.e. uncorrelated analysis)
wpm["Random walk in [-1,1]"] = Dict("window" => 1)
uwerr(a, wpm)
println("uncorrelated:              ",  a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
println("  - window: ", window(a, "Random walk in [-1,1]"))

# Use stau = 1.5
wpm["Random walk in [-1,1]"] = Dict("stau" => 1.5)
uwerr(a, wpm)
println("stau = 1.5:                ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
println("  - window: ", window(a, "Random walk in [-1,1]"))

# Use fixed window 2 and add tail with texp = 100.0
wpm["Random walk in [-1,1]"] = Dict("window" => 2, "texp" => 100.0)
uwerr(a, wpm)
println("Fixed window 2, texp=100: ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
println("  - window: ", window(a, "Random walk in [-1,1]"))

# Sum up to the point that the signal in Gamma is 
# 1.5 times the error and add a tail with texp = 10.0
wpm["Random walk in [-1,1]"] = Dict("signal" => 1.5, "texp" => 10.0)
uwerr(a, wpm)
println("signal/noise=1.5, texp=10: ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
println("  - window: ", window(a, "Random walk in [-1,1]"))

Old (legacy) way to set the window parameters

For each ensemble ID one can pass a vector of Float64 of length 4. The first three components of the vector specify the criteria to determine the summation window:

  • vp[1]: The autocorrelation function is summed up to t = round(vp[1]).
  • vp[2]: The sumation window is determined using U. Wolff poposal with $S_{\tau} = {\rm vp[2]}$.
  • vp[3]: The autocorrelation function $\Gamma(t)$ is summed up a point where its error $\delta\Gamma(t)$ is a factor vp[3] times larger than the signal.

An additional fourth parameter vp[4], tells ADerrors to add a tail to the error with $\tau_{\rm exp} = {\rm vp[4]}$. See the reference for an explanation of the procedure.

Note that:

  • Negative values of vp[1:4] are ignored.
  • One, and only one, of the components vp[1:3] has to be positive. This chooses your criteria to determine the summation window.
using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

# Load the data in a uwreal
a = uwreal(x.^2, "Random walk in [-1,1]")
wpm = Dict{String,Vector{Float64}}()

# Use default analysis (stau = 4.0)
uwerr(a)
println("default:                   ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")

# This will still do default analysis because 
# a does not depend on emsemble foo
wpm["Ensemble foo"] = [-1.0, 8.0, -1.0, 145.0]
uwerr(a, wpm)
println("default:                   ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")

# Fix the summation window to 1 (i.e. uncorrelated data)
wpm["Random walk in [-1,1]"] = [1.0, -1.0, -1.0, -1.0]
uwerr(a, wpm)
println("uncorrelated:              ",  a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")

# Use stau = 1.5
wpm["Random walk in [-1,1]"] = [-1.0, 1.5, -1.0, -1.0]
uwerr(a, wpm)
println("stau = 1.5:                ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")

# Use fixed window 15 and add tail with texp = 100.0
wpm["Random walk in [-1,1]"] = [15.0, -1.0, -1.0, 100.0]
uwerr(a, wpm)
println("Fixed window 15, texp=100: ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")

# Sum up to the point that the signal in Gamma is 
# 1.5 times the error and add a tail with texp = 10.0
wpm["Random walk in [-1,1]"] = [-1.0, -1.0, 1.5, 30.0]
uwerr(a, wpm)
println("signal/noise=1.5, texp=10: ", a, " (tauint = ", taui(a, "Random walk in [-1,1]"), ")")
source

The error analysis uses the $\Gamma$-method of Wolff (with the automatic-differentiation implementation of Ramos 2019). The summation window is determined automatically, or can be set per ensemble with the wpm criteria accepted by uwerr and most other routines; see the uwerr documentation for details.

Managing the ensemble ID database

ADerrors.change_id — Function
change_id(; from::String, to::String)

Changes the ensemble id from the string/integer from to the string to. Note that a call to this routine will affect the output of all currently defined uwreal types that depend on ensemble ID from. On the other hand, it will not affect data that is read after the call to change_id. This means that:

  • All input should be performed before any calls to change_id.
  • All output (i.e. calls to write_uwreal or details) is affected by previous calls to change_id
using ADerrors # hide
a = uwreal([1.2, 0.2], "Simple var with error")   # a = 1.2 +/- 0.2
b = uwreal([1.2, 0.2], 1233)  # c = 1.2 +/- 0.2

d = a-b
uwerr(d)
details(d)

change_id(from=1233, to="Error source from experiment")
change_id(from="Simple var with error", to="Error source from simulations")
details(d)
source
ADerrors.ensembles — Function
ensembles(a::uwreal)

Returns the list of ensembles in lexicographic order contributing to the error of a as a Vector{String}.

using ADerrors # hide
using BDIO
a = uwreal(rand(2000), "Ensemble A")
b = uwreal(rand(2034), "Ensemble B")
c = sin(a+b)
for en in ensembles(c)
    println("Ensemble "*en*" contributing")
end
source

The internal workspace database is not thread-safe. Concurrent calls to ADerrors analysis functions from multiple tasks may corrupt the global workspace (wsg), since new ensemble IDs are registered there lazily (unsynchronized dictionary updates and ID counters). Run error analysis from a single task, or ensure that all ensemble IDs are registered (e.g. by running the relevant error analyses) before parallelizing any further work.

I/O

ADerrors.write_uwreal — Function
write_uwreal(p::uwreal, fb, iu::Int)

Given a BDIO file handler fb, this writes the observable p in a BDIO record with user info iu.

using ADerrors # hide
using BDIO
a = uwreal(rand(2000), 12)

# Create a BDIO file and write observable with user info 8.
fb = BDIO_open("/tmp/foo.bdio", "w", "Test file")
write(a, fb, 8)
BDIO_close(fb)

# Open the file and move to first record
fb = BDIO_open("/tmp/foo.bdio", "r")
BDIO_seek!(fb)

# Read observable
b = read_uwreal(fb)

# Check
c = a - b
uwerr(c)
println("Better be zero: ", c)
source
ADerrors.read_uwreal — Function
read_uwreal(fb[, map_ids::Dict{Int64, Int64}])

Given a BDIO file handler fb, this routine returns the observable stored in the current record. Optionally, ensemble ID stored in the file can be changed at read time by passing a dictionary map_ids

using ADerrors # hide
using BDIO
a = uwreal(rand(2000), 12)

fb = BDIO_open("/tmp/foo.bdio", "w", "Test file")
write(a, fb, 8)
BDIO_close(fb)

# Open the file and move to first record
fb = BDIO_open("/tmp/foo.bdio", "r")
BDIO_seek!(fb)

# Read observable
changeID_12_by_120 = Dict(12 => 120)
b = read_uwreal(fb, changeID_12_by_120)

# Check
c = a - b
uwerr(c)
print("Better be zero: ")
details(c)
source

Correlations between observables

ADerrors.cov — Function
cov(a::Vector{uwreal}[, wpm]; method::Symbol = :sym)

Determine the covariance matrix between the vector of observables a[:].

The cross-covariances of observables measured on the same Monte Carlo ensemble can be estimated with two conventions, selected with the method keyword:

  • method = :sym (default): symmetrized estimator, averaging both lag directions of the cross-correlation function. Statistically preferable; this is the convention used internally by chiexp/fit_error/trcov.
  • method = :legacy: historical one-direction convention (kept to reproduce published numbers computed with older versions of ADerrors).

For uncorrelated data (or diagonal elements) both conventions agree exactly.

using ADerrors, LinearAlgebra # hide
a = uwreal([1.3, 0.01], "Var 1") # 1.3 +/- 0.01
b = uwreal([5.3, 0.23], "Var 2") # 5.3 +/- 0.23
uwerr(a)
uwerr(b)

x = [a+b, a-b]
mat = ADerrors.cov(x)
println("Covariance: ", mat[1,1], " ", mat[1,2])
println("            ", mat[2,1], " ", mat[2,2])
println("Check (should be zero): ",  mat[1,1] - mat[2,2])
println("Check (should be zero): ",  mat[1,2] - (err(a)^2-err(b)^2))

Case of Monte Carlo data

An optional parameter wpm can be used to choose the summation window for the relevant autocorrelation functions. The situation is completely analogous to the case of error analysis of single variables. All three forms of the window dictionary are accepted: Dict{Int64,Vector{Float64}}, Dict{String,Vector{Float64}} and the preferred Dict{String,Dict{String,Real}} (named parameters; see uwerr).

source
ADerrors.trcov — Function
 trcov(M::Array{Float64, 2}, a::Vector{uwreal}[, wmp])

Given a vector of uwreal, a[:] and a two dimensional symmtric positive definite array M, this routine computes ${\rm tr}(MC)$, where $C_{ij} = {\rm cov}(a[i], a[j])$.

using ADerrors, LinearAlgebra # hide
a = uwreal([1.3, 0.01], "Var with error 1") # 1.3 +/- 0.01
b = uwreal([5.3, 0.23], "Var with error 2") # 5.3 +/- 0.23
c = uwreal(rand(2000), "White noise ensemble")

x = [a+b+sin(c), a-b+cos(c), c-b/a]
M = [1.0 0.2 0.1
     0.2 2.0 0.3
     0.1 0.3 1.0]

mcov = cov(x)
d = tr(mcov * M)
println("Better be zero: ", d -trcov(M, x))

# Case of Monte Carlo data

An optional parameter `wpm` can be used to choose the summation window for the relevant autocorrelation functions. The situation is completely analogous to the case of error analysis of single variables. All three forms of the window dictionary are accepted: `Dict{Int64,Vector{Float64}}`, `Dict{String,Vector{Float64}}` and the preferred `Dict{String,Dict{String,Real}}` (named parameters; see `uwerr`).
source

cov gives the covariance matrix of a set of observables; trcov and trcorr give direct access to the (auto- and cross-correlation) $\Gamma$ functions between ensembles.

Inspecting results

There are several methods to get information about the error analysis of a uwreal variable. With the exception of value, these methods require that a proper error analysis has been performed via a call to the uwerr method.

ADerrors.value — Function
value(a::uwreal)

Returns the (mean) value of the uwreal variable a

using ADerrors # hide
a = uwreal([1.2, 0.2], 12)   # a = 1.2 +/- 0.2
uwerr(a)
println("a has central value: ", value(a))
source
ADerrors.err — Function
err(a::uwreal)

Returns the error of the uwreal variable a. It is assumed that uwerr has been run on the variable so that an error is available. Otherwise an error message is printed.

Name shadowing at top level

Julia's REPL defines a global variable err (last thrown errors) in Base.MainInclude, which shadows the exported err in the Main module (REPL prompt, julia -e scripts, top-level include). Inside functions and user modules, bare err(...) always resolves to this function. At the top level of a script or at the REPL, use the qualified form ADerrors.err(...).

using ADerrors # hide
a = uwreal([1.2, 0.2], 12)   # a = 1.2 +/- 0.2
uwerr(a)
println("a has error: ", err(a))
source
ADerrors.derror — Function
derror(a::uwreal)

Returns an estimate of teh error of the error of the uwreal variable a. It is assumed that uwerr has been run on the variable so that an error is available. Otherwise an error message is printed.

using ADerrors # hide
a = uwreal([1.2, 0.2], 12)   # a = 1.2 +/- 0.2
uwerr(a)
println("a has error of the error: ", derror(a))
source
ADerrors.taui — Function
taui(a::uwreal, mcid)

Returns the value of tauint for the ensemble mcid. It is assumed that uwerr has been run on the variable and that mcid contributes to the observable a. Otherwise an error message is printed. mcid can be either an Int64 (the proper ensemble ID), or a String (the ensemble tag).

using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

a = uwreal(x.^2, "Some simple ensemble")
uwerr(a)
println("Error analysis result: ", a, " (tauint = ", taui(a, "Some simple ensemble"), ")")
source
ADerrors.dtaui — Function
dtaui(a::uwreal, mcid)

Returns an estimate on the error of tauint for the ensemble mcid. It is assumed that uwerr has been run on the variable and that mcid contributes to the observable a. Otherwise an error message is printed.

using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

a = uwreal(x.^2, "Some simple ensemble")
uwerr(a)
println("Error analysis result: ", a, 
        " (tauint = ", taui(a, "Some simple ensemble"), " +/- ", dtaui(a, "Some simple ensemble"), ")")
source
ADerrors.rho — Function
rho(a::uwreal, mcid)

Returns the normalized autocorrelation function of a for the ensemble mcid. It is assumed that uwerr has been run on the variable and that mcid contributes to the observable a. Otherwise an error message is printed.

using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

a = uwreal(x.^2, "Some simple ensemble")
uwerr(a)
v = rho(a, "Some simple ensemble")
for i in 1:length(v)
    println(i, " ", v[i])
end
source
ADerrors.drho — Function
drho(a::uwreal, mcid)

Returns an estimate of the error on the normalized autocorrelation function of a for the ensemble mcid. It is assumed that uwerr has been run on the variable and that mcid contributes to the observable a. Otherwise an error message is printed.

using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

a = uwreal(x.^2, "Some simple ensemble")
uwerr(a)
v  =  rho(a, "Some simple ensemble")
dv = drho(a, "Some simple ensemble")
for i in 1:length(v)
    println(i, " ", v[i], " +/- ", dv[i])
end
source
ADerrors.mchist — Function
mchist(a::uwreal, mcid)

Returns the fluctuations of the uwreal variable a on ensemble mcid. It is assumed that uwerr has been run on the variable and that mcid contributes to the observable a. Otherwise an error message is printed.

using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

a = uwreal(x.^2, "Some simple ensemble")
uwerr(a)
v = mchist(a, "Some simple ensemble")
for i in 1:length(v)
    println(i, " ", v[i])
end
source
ADerrors.replicas — Function
    replicas(a::uwreal, mcid::String)

Returns the replica vector for ensemble mcid contributing to observable a

source
ADerrors.window — Function
window(a::uwreal, mcid)

Returns the summation window for the ensemble mcid. It is assumed that uwerr has been run on the variable and that mcid contributes to the observable a. Otherwise an error message is printed.

using ADerrors # hide
# Generate some correlated data
eta  = randn(1000)
x    = Vector{Float64}(undef, 1000)
x[1] = 0.0
for i in 2:1000
    x[i] = x[i-1] + eta[i]
    if abs(x[i]) > 1.0
        x[i] = x[i-1]
    end
end

a = uwreal(x.^2, "Some simple ensemble")
uwerr(a)
println("Error analysis result: ", a, 
        " (window = ", window(a, "Some simple ensemble"), ")")
source
ADerrors.details — Function
details(a::uwreal; io::IO=stdout, names::Dict{Int64, String} = Dict{Int64, String}())
details(a::uwreal, ensemble::String, ws::wspace, io::IO=stdout)

Write out a detailed information on the error of a.

Arguments

If a the ergument ensemble is present, this routine writes information of the fluctuations of the observable in this ensemble. Optionally one can pass as a keyword argument (io) the IO stream to write to.

Example

using ADerrors # hide
a = uwreal(rand(2000),   "Ensemble A12", ["A12 REP 0", "A12 REP 1", "A12 REP 2"], [1000, 500, 500])
b = uwreal([1.2, 0.023], "Ensemble XYZ")
c = uwreal([5.2, 0.03],  "Ensemble RRR")
d = a + b - c
uwerr(d)

details(d)
for en in ensembles(d)
    details(d, en)
end
source
ADerrors.neid — Function
neid(a::uwreal)

Returns the number of different ensemble ID's contributing to a

using ADerrors # hide
a = uwreal([1.2, 0.2], 12)   # a = 1.2 +/- 0.2
b = uwreal([7.2, 0.5], 13)   # a = 7.2 +/- 0.5

c = a*b
println("Number od ID contributing to c: ", neid(a))
source
ADerrors.derivative — Function
derivative(a::uwreal, b::uwreal)

Determines the derivate of the observable a with respect to observable b (i.e. `rac{{ m d} a}{{ m d} b}). Observablebmust depend on only a single ensemble (typicallyb` is just a primary observable).

using ADerrors # hide

# Put some data 
a = uwreal(1.2 .+ randn(2000), "Ensemble test A", ["R0", "Rep 1", "Last rep"], [1000, 550, 450])
b = uwreal(0.8 .+ randn(2034), "Ensemble test B", [1000, 584, 450])

d = sin(a+b)
println("Derivative of d w.r.t a:      ", derivative(d, a))
println("Derivative of d w.r.t log(a): ", derivative(d, log(a)))
source

Comparisons and ordering

Comparison operators (<, >, ≤, ≥, ≠) and isless compare the mean values only (fluctuations are ignored). isless enables sort, minimum, etc. isapprox (and ≈) also compares mean values. == between uwreal values throws an error, as it is meaningless for observables with statistical errors.

Base.:< — Function
comparison operators on `uwreal`

Comparison operators (<, >, ≤, ≥, ≠) on uwreal values act on the mean value only; errors (noise) are completely ignored. The == operator is not meaningful for uwreal and throws an error; use ≈ (isapprox) to compare mean values approximately. The isless method is defined analogously, enabling sort, minimum, maximum and searchsorted on vectors of uwreal (by mean value).

source
Base.:> — Function
comparison operators on `uwreal`

Comparison operators (<, >, ≤, ≥, ≠) on uwreal values act on the mean value only; errors (noise) are completely ignored. The == operator is not meaningful for uwreal and throws an error; use ≈ (isapprox) to compare mean values approximately. The isless method is defined analogously, enabling sort, minimum, maximum and searchsorted on vectors of uwreal (by mean value).

source
Base.:≤ — Function
comparison operators on `uwreal`

Comparison operators (<, >, ≤, ≥, ≠) on uwreal values act on the mean value only; errors (noise) are completely ignored. The == operator is not meaningful for uwreal and throws an error; use ≈ (isapprox) to compare mean values approximately. The isless method is defined analogously, enabling sort, minimum, maximum and searchsorted on vectors of uwreal (by mean value).

source
Base.:≥ — Function
comparison operators on `uwreal`

Comparison operators (<, >, ≤, ≥, ≠) on uwreal values act on the mean value only; errors (noise) are completely ignored. The == operator is not meaningful for uwreal and throws an error; use ≈ (isapprox) to compare mean values approximately. The isless method is defined analogously, enabling sort, minimum, maximum and searchsorted on vectors of uwreal (by mean value).

source
Base.:≠ — Function
comparison operators on `uwreal`

Comparison operators (<, >, ≤, ≥, ≠) on uwreal values act on the mean value only; errors (noise) are completely ignored. The == operator is not meaningful for uwreal and throws an error; use ≈ (isapprox) to compare mean values approximately. The isless method is defined analogously, enabling sort, minimum, maximum and searchsorted on vectors of uwreal (by mean value).

source

Error propagation in iterative algorithms

Finding root of functions

ADerrors.root_error — Function
root_error(fnew::Function, x0::Float64, data::Vector{uwreal})

Returns x such that fnew(x, data) = 0 (i.e. a root of the function). The error in data is propagated to the root position x.

using ADerrors # hide
# First define some arbitrary data
data = Vector{uwreal}(undef, 3)
data[1] = uwreal([1.0, 0.2],   "Var A")
data[2] = uwreal([1.2, 0.023], "Var B")
data[3] = uwreal(rand(1000),   "White noise ensemble")

# Now define a function
f(x, p) = x + p[1]*x + cos(p[2]*x+p[3])

# Find its root using x0=1.0 as initial
# guess of the position of the root
x = root_error(f, 1.0, data)
uwerr(x)
println("Root: ", x)

# Check
z = f(x, data)
uwerr(z)
print("Better be zero (with zero error): ")
details(z)
source

Integrals

ADerrors.int_error — Function
int_error(fint::Function, a, b, p::AbstractVector{uwreal})

Computes the integral

$\int_a^b {\rm fint}(x; p)\, {\rm d}x$

The errors in the parameters p[:] and (optionally) in the limits of the intgeral (a, b) are propagated to an error of the integral. The result is returned as an uwreal.

using ADerrors

# Average values and covariance from
# https://inspirehep.net/literature/1477411
# here we try to reproduce the result
# Scale ratio = 21.86(42) Eq.5.6
avg = [16.26, 0.12, -0.0038]
Mcov = [ 0.478071  -0.176116   0.0135305
        -0.176116   0.0696489 -0.00554431
         0.0135305 -0.00554431 0.000454180]

p = cobs(avg, Mcov, "Beta function fit parameters")
g1s = uwreal([2.6723, 0.0064], 4)
g2s = 11.31

fint(x, p) = - (p[1] + p[2]*x^2 + p[3]*x^4)/x^3
g1 = sqrt(g1s)
g2 = sqrt(g2s)
sint = 2.0*exp(-int_error(fint, g1, g2, p))
uwerr(sint)
print("  From integral evaluation: ")
details(sint)
source

Fits and p-values

ADerrors.jl is agnostic about how you approach a fit (i.e. minimize a $\chi^2$ function), but once the central values of the fit parameters have been found (i.e. using LsqFit.jl, LeastSquaresOptim.jl, etc...) ADerrors.jl is able to determine the error of the fit parameters, returning them as uwreal type.

For goodness of fit, ADerrors assumes the $\chi^2$ is quadratic in the data. Under this assumption the expected $\chi^2$ is the mean of a generalized $\chi^2$ distribution, $\mathrm{tr}(C \cdot P_x)$, and its p-value is computed from the (eigen)spectrum of the projected data covariance: either with the deterministic Imhof inversion of the characteristic function (default, accurate to ~1e-10) or with a Monte Carlo estimate (method = :mc). A pivoted factorization (Ctol > 0) regularizes close-to-singular (MC-estimated) covariances. pvalue returns the tuple (Q, chie) where Q is the p-value and chie = tr(C \cdot P_x) the mean of the generalized $\chi^2$ actually used for Q — consistent with it by construction; for uncorrelated fits chie = m - n exactly. fit_error(pval = true) returns the same chie together with the fit parameters and p-value.

ADerrors.fit_error — Function
fit_error(chisq::Function, xp::Vector{Float64}, data::Vector{uwreal}[, wpm][, W];
               chi_exp = true, C = nothing, pval = false, Ctol = 0.0)

Given a $\chi^2(p, d)$, function of the fit parameters p[:] and the data d[:], this routine returns the fit parameters as uwreal type and optionally, the expected value of $\chi^2(p, d)$ and the p-value of the fit.

Arguments

  • chisq: Must be a function of two vectors (i.e. chisq(p::Vector, d::Vector)). To determine the fit parameters, the function can be arbitrary, but for the determination of the expected $\chi^2(p, d)$ is assumed to have the form

$\chi^2(p, d) = \sum_{ij} [d_i - f_i(p)]W_{ij}[d_j - f_j(p)]$

where the function $f_i(p)$ is an arbitrary function of the fit parameters. In simple words, the expected $\chi^2(p, d)$ is determined assuming that the function $\chi^2(p, d)$ is quadratic in the data.

  • xp: A vector of Float64. The value of the fit parameters at the minima.
  • data: A vector of uwreal. The data whose fluctuations enter in the evaluation of the chisq.
  • wpm: Dict{Int64,Vector{Float64}} or Dict{String,Vector{Float64}}. The criteria to determine the summation window. See the documentation on uwerr function for more details.
  • W: A matrix. The weights that enter in the evaluation of the chisq function. If a vector is passed, the matrix is assumed to be diagonal (i.e. uncorrelated fit). If no weights are passed, the routines assumes that W is diagonal with entries given by the inverse errors squared of the data (i.e. the chisq is weighted with the errors of the data). All entries of W must be positive.
  • C: A matrix. The data covariance matrix. If not passed, it is computed internally with cov_sym. Only used for the expected $\chi^2$ and the p-value.
  • chi_exp: Bool type (keyword). If false, do not compute the expected $\chi^2(p, d)$. Implied true if pval = true.
  • pval: Bool type (keyword, default false). If true, also compute the p-value of the fit, i.e. $P(\chi^2 \geq \chi^2_{\rm obs})$ for the generalized $\chi^2$ distribution with mean χexp (using the Imhof method; see pvalue), reusing the Hessian computed for the expected $\chi^2$. Requires m > n (more data points than fit parameters); otherwise an ArgumentError is thrown. The routine then returns a 3-tuple (param, cse, p). When pval = true the returned cse is the mean tr(C \cdot P_x) of the generalized $\chi^2$ distribution actually used for the p-value (consistent with it by construction); for uncorrelated fits cse = m - n exactly.
  • Ctol: Float64 (keyword, default 0.0). Only relevant if pval = true. Relative tolerance for the factorization of the data covariance C; see the documentation of pvalue for details.

Return value

  • pval = false: (param, cse) — fit parameters as a vector of uwreal and the expected $\chi^2$ (cse = 0.0 if m <= n). If also chi_exp = false, only param is returned.
  • pval = true: (param, cse, p) — as above plus the p-value p of the fit.
Expensive determination of the pvalue

The computation of the covariance between data is by far the most expensive operation in this routine. This is only needed to determine the p-value. When the same data (or a subset) is fitted to different functional forms, it is encouraged to pre-compute the covariance and pass the keyword parameter C=....

Example

using ADerrors, Distributions # hide

# Generate correlated samples with average 0.1
npt = 12
sig = zeros(npt, npt)
dx  = zeros(npt)
for i in 1:npt
    dx[i]    = 0.01*i
    sig[i,i] = dx[i]^2
    for j in i+1:npt
        sig[i,j] = 0.0001 - 0.000005*abs(i-j)
        sig[j,i] = 0.0001 - 0.000005*abs(i-j)
    end
end
dmv = MvNormal([0.1 for n in 1:npt], sig)
vs  = rand(dmv, 1)

# Create the uwreal data that we want to 
# fit to a constant
dt = cobs(vs[:,1], sig, "Data points")

# Define the chi^2
chisq(p, d) = sum( (d .- p[1]) .^ 2 ./ dx .^2 )

# The result of an uncorrelated fit to a 
# constant is the weighted average
xp = [sum(value.(dt) ./ dx)/sum(1.0 ./ dx)]


# Propagate errors to the fit parameters and
# determine the expected chi^2
(fitp, csqexp) = fit_error(chisq, xp, dt)

uwerr.(fitp)
println(" *** FIT RESULTS ***")
print("Fit parameter:     ")
details.(fitp)
println("chi^2 / chi_exp^2: ", chisq(xp, value.(dt)), " / ", csqexp, "  (dof: ", npt-1, ")")
source
ADerrors.chiexp — Function
chiexp(chisq::Function,
            xp::Vector{Float64},
            data::Vector{uwreal};
            W = Vector{Float64}(),
            C = nothing)

Given a $\chi^2(p, d)$, function of the fit parameters p[:] and the data d[:], compute the expected value of the $\chi^2(p, d)$.

Arguments

  • chisq: Must be a function of two vectors (i.e. chisq(p::Vector, d::Vector)). The function is assumed to have the form

$\chi^2(p, d) = \sum_i [d_i - f_i(p)]W_{ij}[d_j - f_j(p)]$

where the function $f_i(p)$ is an arbitrary function of the fit parameters. In simple words, the function is assumed to be quadratic in the data.

  • xp: A vector of Float64. The value of the fit parameters at the minima.
  • data: A vector of uwreal. The data whose fluctuations enter in the evaluation of the chisq.
  • W: A matrix. The weights that enter in the evaluation of the chisq function. If a vector is passed, the matrix is assumed to be diagonal (i.e. uncorrelated fit). If no weights are passed, the routines assumes that W is diagonal with entries given by the inverse errors squared of the data (i.w. the chisq is weighted with the errors of the data). All entries of W must be positive.
  • C: A matrix. The covariance matrix of the data. if not given (i.e. is nothing), ADerrors will infer the covariance matrix form the data.

Example

using ADerrors, Distributions # hide

# Generate correlated samples with average 0.1
npt = 12
sig = zeros(npt, npt)
dx  = zeros(npt)
for i in 1:npt
    dx[i]    = 0.01*i
    sig[i,i] = dx[i]^2
    for j in i+1:npt
        sig[i,j] = 0.0001 - 0.000005*abs(i-j)
        sig[j,i] = 0.0001 - 0.000005*abs(i-j)
    end
end
dmv = MvNormal([0.1 for n in 1:npt], sig)
vs  = rand(dmv, 1)

# Create the uwreal data that we want to
# fit to a constant
dt = cobs(vs[:,1], sig, [100+n for n in 1:npt])

# Define the chi^2
chisq(p, d) = sum( (d .- p[1]) .^ 2 ./ dx .^2 )

# The result of an uncorrelated fit to a
# constant is the weighted average
xp = [sum(value.(dt) ./ dx)/sum(1.0 ./ dx)]

# Compare chi^2 and expected chi^2
println("chi^2 / chi_exp^2: ", chisq(xp, value.(dt)), " / ", chiexp(chisq, xp, dt))
source
ADerrors.pvalue — Function
pvalue(chisq::Function, chi2::Float64, xp::AbstractVector{Float64},
       data::AbstractVector{uwreal},
       W::AbstractVector{Float64}=Vector{Float64}();
       wpm = Dict{Int64,Vector{Float64}}(), nmc::Int64 = 5000,
       C::AbstractMatrix{Float64} = zeros(0,0),
       method::Symbol = :imhof,
       Ctol::Float64 = 0.0,
       rng::AbstractRNG = Random.default_rng())

Computes the p-value of a previously done fit, using as input the observed $\chi^2$ of the fit at the minimum, the fit parameters and the fitted data. The p-value for a given observed $\chi^2$ is the probability of, given the data you have, finding such a $\chi^2$ or worse from a fit, and still have the data well described by the fit function.

  • method = :imhof (default): deterministic numerical inversion of the characteristic function of the generalized $\chi^2$ distribution (Imhof 1961; Davies 1973). Accurate to ~1e-10. Reduces exactly to the textbook $\chi^2$ result for (weighted) uncorrelated fits.
  • method = :mc: Monte Carlo estimate with nmc samples (statistical resolution ~$\sqrt{p(1-p)/nmc}$); useful as a cross-check.
  • Ctol: relative tolerance for the factorization of the data covariance C. If zero (default), a plain Cholesky factorization is used (fails loudly if C is not positive definite). If positive, a pivoted (RowMaximum) Cholesky with relative tolerance Ctol is used instead: modes of C below the tolerance are treated as noise-free (exact zero eigenvalues of the projected covariance), a rank-revealing regularization for close-to-singular MC covariances. A warning is issued if the effective rank drops to the number of fit parameters or below.

Arguments

  • chisq: Must be a function of two vectors (i.e. chisq(p::Vector, d::Vector)), the same $\chi^2$ function used in the fit. For the determination of the expected $\chi^2(p, d)$ is assumed to have the form

$\chi^2(p, d) = \sum_{ij} [d_i - f_i(p)]W_{ij}[d_j - f_j(p)]$

  • chi2: Float64. The observed value of $\chi^2(p, d)$ of the fit at the minimum (e.g. fit.ssr in LeastSquaresOptim, or chisq(xp, value.(data))).
  • xp: A vector of Float64. The value of the fit parameters at the minima.
  • data: A vector of uwreal. The data whose fluctuations enter in the evaluation of the chisq.
  • W: The weights that enter in the evaluation of the chisq function. If a vector is passed, the matrix is assumed to be diagonal (i.e. uncorrelated fit). If no weights are passed, the routine assumes that W is diagonal with entries given by the inverse errors squared of the data. All entries of W must be positive.
  • wpm: Dict{Int64,Vector{Float64}} or Dict{String,Vector{Float64}}. The criteria to determine the summation window. See the documentation on uwerr function for more details. Only used if C is not passed.
  • nmc: Number of Monte Carlo samples (only used if method = :mc).
  • C: A matrix. The data covariance matrix. If not passed, it is computed internally with cov_sym.
  • method: :imhof (default) or :mc. See above.
  • Ctol: Relative tolerance for the factorization of C. See above.
  • rng: AbstractRNG used by the Monte Carlo method (only used if method = :mc).

Return value

A tuple (Q, chie):

  • Q: the p-value, $P(\chi^2 \geq \chi^2_{\rm obs})$ for the generalized $\chi^2$ distribution with the spectrum of the projected covariance.
  • chie: the mean of that generalized $\chi^2$ distribution, $\mathrm{tr}(C \cdot P_x)$ — consistent by construction with the spectrum used for Q. For (weighted) uncorrelated fits, chie = m - n exactly. If an explicit C is passed, chie is computed from that C.

Requires m > n (more data points than fit parameters). Use ADerrors.pvalue at the REPL / top level (bare pvalue works inside functions and modules).

function fit_defs(f::Function, x, W)
    chisq(p, d) = sum((d - f(x, p)).^2 .* W)
    return chisq
end

# given the result of a fit: chi2 (observed chi^2), xp (parameters), y (data)
wpm = Dict{Int64,Vector{Float64}}()
wpm[1] = [-1.0, -1.0, 4.0, -1.0]
(Q, chie) = pvalue(chisq, chi2, value.(xp), y, 1.0 ./ ADerrors.err.(y) .^ 2; wpm = wpm)
# (Q, chie) = pvalue(chisq, chi2, value.(xp), y, 1.0 ./ ADerrors.err.(y) .^ 2;
#                    method = :mc, nmc = 10000)

Reference

Mattia Bruno, Rainer Sommer, Comput.Phys.Commun. 285 (2023), 108643, arxiv: 2209.14188 J. P. Imhof, Biometrika 48 (1961) 419.

Acknowledgments

The pvalue routine was developed and debugged with contributions from Antonino D'Anna and Alejandro Saez.

source