Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: lmfit/uncertainties
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: ces42/uncertainties
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 9 commits
  • 1 file changed
  • 1 contributor

Commits on Jul 9, 2019

  1. Copy the full SHA
    b6165f7 View commit details

Commits on Jul 30, 2019

  1. Copy the full SHA
    e539c59 View commit details
  2. make this actually work

    ces42 committed Jul 30, 2019
    Copy the full SHA
    d4b9cb7 View commit details
  3. Copy the full SHA
    7d6aec2 View commit details
  4. rename EPS to TOL

    ces42 committed Jul 30, 2019
    Copy the full SHA
    77c1037 View commit details

Commits on Aug 1, 2019

  1. docstrings and comments

    ces42 committed Aug 1, 2019
    Copy the full SHA
    e87eb43 View commit details
  2. Copy the full SHA
    ae02984 View commit details
  3. Copy the full SHA
    cf28d69 View commit details
  4. cosmetic

    ces42 committed Aug 1, 2019
    Copy the full SHA
    1c09804 View commit details
Showing with 100 additions and 63 deletions.
  1. +100 −63 uncertainties/core.py
163 changes: 100 additions & 63 deletions uncertainties/core.py
Original file line number Diff line number Diff line change
@@ -155,31 +155,69 @@ def correlated_values(nom_values, covariance_mat, tags=None):
independent variable.
"""

# !!! It would in principle be possible to handle 0 variance
# variables by first selecting the sub-matrix that does not contain
# such variables (with the help of numpy.ix_()), and creating
# them separately.

std_devs = numpy.sqrt(numpy.diag(covariance_mat))

# For numerical stability reasons, we go through the correlation
# matrix, because it is insensitive to any change of scale in the
# quantities returned. However, care must be taken with 0 variance
# variables: calculating the correlation matrix cannot be simply done
# by dividing by standard deviations. We thus use specific
# normalization values, with no null value:
norm_vector = std_devs.copy()
norm_vector[norm_vector==0] = 1

return correlated_values_norm(
# !! The following zip() is a bit suboptimal: correlated_values()
# separates back the nominal values and the standard deviations:
zip(nom_values, std_devs),
covariance_mat/norm_vector/norm_vector[:,numpy.newaxis],
tags)
# We perform a cholesky decomposition of the covariance matrix.
# If the matrix is only positive semidefinite numpy will refuse to
# perform a cholesky decomposition, so we 'manually' do a LDL
# decomposition
try:
L = numpy.linalg.cholesky(covariance_mat)
except numpy.linalg.LinAlgError:
L0, D = ldl(covariance_mat)
L = L0 * numpy.sqrt(D)

# Creation of new, independent variables:
if tags is None:
tags = (None, ) * len(nom_values)
variables = tuple(Variable(0, 1, tag) for tag in tags)

return nom_values + numpy.dot(L, variables)

__all__.append('correlated_values')

def ldl(A):
"""
Return the LDL factorisation of a symmetric, positive semidefinite
matrix. This is a lower triangular matrix L and an array representing
the diagonal matrix D. If the matrix is not square or positive
semi-definite, an error is raised.
A -- a square (symmetric) positive semi-definite matrix. Only the
lower half of A is read.
"""
# square root of float64-accuracy. In places where there should be
# a positive number we will only accept numbers larger than -TOL
TOL = 1.49e-8

n, n_ = numpy.shape(A)
if n != n_:
raise numpy.linalg.LinAlgError('matrix must be square')

A = numpy.array(A, copy=True, dtype=numpy.float64)
L = numpy.zeros_like(A) # we will only write in the lower half of L
D = numpy.zeros(n)

for i in range(n):
L[i, i] = 1

a = A[i, i]
l = A[i+1:, i]

if a <= 0:
if a < -TOL or (i < n - 1 and any(abs(l) >= TOL)):
raise numpy.linalg.LinAlgError('matrix must be positive '
'semidefinite (failed on %s-th diagonal entry)' % i)
# If we get here, then the whole first column of L[i:, i:] is
# (nearly) zero
D[i] = 0
else:
D[i] = a
L[i+1:, i] = l / a
A[i+1:, i+1:] -= numpy.outer(l, l) / a

return L, D



def correlated_values_norm(values_with_std_dev, correlation_mat,
tags=None):
'''
@@ -193,10 +231,8 @@ def correlated_values_norm(values_with_std_dev, correlation_mat,
deviation) pairs. The returned, correlated values have these
nominal values and standard deviations.
correlation_mat -- correlation matrix between the given values, except
that any value with a 0 standard deviation must have its correlations
set to 0, with a diagonal element set to an arbitrary value (something
close to 0-1 is recommended, for a better numerical precision). When
correlation_mat -- correlation matrix between the given values. The
entries corresponding to values with 0 variance are ignored. When
no value has a 0 variance, this is the covariance matrix normalized by
standard deviations, and thus a symmetric matrix with ones on its
diagonal. This matrix must be an NumPy array-like (list of lists,
@@ -205,49 +241,50 @@ def correlated_values_norm(values_with_std_dev, correlation_mat,
tags -- like for correlated_values().
'''

nominal_values, std_devs = numpy.transpose(values_with_std_dev)

# If no tags were given, we prepare tags for the newly created
# variables:
if tags is None:
tags = (None,) * len(values_with_std_dev)

(nominal_values, std_devs) = numpy.transpose(values_with_std_dev)

# We diagonalize the correlation matrix instead of the
# covariance matrix, because this is generally more stable
# numerically. In fact, the covariance matrix can have
# coefficients with arbitrary values, through changes of units
# of its input variables. This creates numerical instabilities.
#
# The covariance matrix is diagonalized in order to define
# the independent variables that model the given values:
(variances, transform) = numpy.linalg.eigh(correlation_mat)

# Numerical errors might make some variances negative: we set
# them to zero:
variances[variances < 0] = 0.
tags = (None,) * len(nominal_values)

# For values with zero uncertainty we ignore the corresponding entries
# in the correlation matrix
zero_stdev = numpy.where(std_devs == 0)[0]
eff_corr_mat = numpy.delete(
numpy.delete(correlation_mat, zero_stdev, axis=0),
zero_stdev,
axis=1
)

# We perform a cholesky decomposition of the correlation matrix.
# If the matrix is only positive semidefinite numpy will refuse to
# perform a cholesky decomposition, so we 'manually' do a LDL
# decomposition
try:
L = numpy.linalg.cholesky(eff_corr_mat)
except numpy.linalg.LinAlgError:
L0, D = ldl(eff_corr_mat)
L = L0 * numpy.sqrt(D)

# Creation of new, independent variables:

# We use the fact that the eigenvectors in 'transform' are
# special: 'transform' is unitary: its inverse is its transpose:

variables = tuple(
eff_variables = tuple(
# The variables represent "pure" uncertainties:
Variable(0, sqrt(variance), tag)
for (variance, tag) in zip(variances, tags))

# The coordinates of each new uncertainty as a function of the
# new variables must include the variable scale (standard deviation):
transform *= std_devs[:, numpy.newaxis]

# Representation of the initial correlated values:
values_funcs = tuple(
AffineScalarFunc(
value,
LinearCombination(dict(zip(variables, coords))))
for (coords, value) in zip(transform, nominal_values))

return values_funcs
Variable(0, 1, tag) for i, tag in enumerate(tags)
if std_devs[i] != 0
)
zero_stdev_variables = tuple(
Variable(0, 0, tag) for i, tag in enumerate(tags)
if std_devs[i] == 0
)

uncert = std_devs[std_devs != 0] * numpy.dot(L, eff_variables)
# we need to subtract arange(len(zero_stdev)) because the indices in
# zero_stdev refer to the original array
numpy.insert(uncert, zero_stdev - numpy.arange(len(zero_stdev)),
zero_stdev_variables)

return nominal_values + uncert

__all__.append('correlated_values_norm')