Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminate the Variable class #262

Open
wants to merge 55 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3a53d8c
A first pass on linear_combo -> ucombo
jagerber48 Aug 9, 2024
01ef70d
revert test changes
jagerber48 Aug 10, 2024
f338549
rework UCombo, delete Variable
jagerber48 Aug 11, 2024
5f64b9e
some test updates
jagerber48 Aug 11, 2024
80e3d31
Pass more tests
jagerber48 Aug 12, 2024
f939766
Merge branch 'master' into feature/linear_combo_refactor
jagerber48 Nov 5, 2024
d7b9a2e
remove large float test
jagerber48 Nov 16, 2024
48c3973
Clean up UFloat class
jagerber48 Nov 17, 2024
1731b5a
some formatting changes
jagerber48 Nov 17, 2024
f0d4225
uncertainty instead of _linear_combo
jagerber48 Nov 17, 2024
1b385f2
variety of updates and test cleanup
jagerber48 Nov 17, 2024
fe849ff
copy tests
jagerber48 Nov 17, 2024
c8fc497
pickling tests
jagerber48 Nov 17, 2024
681bef9
no negative std_dev in tests
jagerber48 Nov 17, 2024
2c1b11d
modify test
jagerber48 Nov 17, 2024
ac6b497
more tests
jagerber48 Nov 17, 2024
b3f7fb2
handle some edge cases with 0 uncertainty
jagerber48 Nov 18, 2024
fc6e592
fix test_hypot
jagerber48 Dec 5, 2024
f2bfc60
compare analytic and numerical derivatives on math functions
jagerber48 Dec 7, 2024
2361849
unumpy
jagerber48 Dec 7, 2024
3c428a2
mainly code to handle inv and pinv array function
jagerber48 Dec 12, 2024
24f1249
Merge remote-tracking branch 'lmfit/master' into feature/linear_combo…
jagerber48 Dec 12, 2024
4084e7f
fix import
jagerber48 Dec 12, 2024
ec8d7f8
slim down UCombo, especially remove __hash__
jagerber48 Dec 13, 2024
eba7ccd
Add hashes
jagerber48 Dec 13, 2024
3f78e5c
bring back is_expanded
jagerber48 Dec 13, 2024
c80dd9b
do not check std_dev during operations. This kills performance.
jagerber48 Dec 13, 2024
5fc3cbd
avoid empty ucombos, or ucombo/uatom weights exactly equal to zero.
jagerber48 Dec 13, 2024
6d53afb
special case empty uncertainty in the tests. Is this something we want??
jagerber48 Dec 13, 2024
7ad82ba
fix broken test that wasn't running
jagerber48 Dec 17, 2024
fb69b1f
ucombo clean up and refactor
jagerber48 Dec 17, 2024
7c35233
don't calculate std_dev with kwargs inputs, cleanup f_with_affine_out…
jagerber48 Dec 17, 2024
0eb135b
some correlated_values_norm cleanup
jagerber48 Dec 17, 2024
c6f116f
todo on untested, probably broken functions
jagerber48 Dec 17, 2024
a8fd413
codecov CI
jagerber48 Dec 18, 2024
0abc0f6
revert previous commit (it was made to the wrong branch and had a wro…
jagerber48 Dec 18, 2024
0086a0a
fixed ucombo hash
jagerber48 Dec 26, 2024
e6211ca
Merge branch 'master' into feature/linear_combo_refactor
jagerber48 Dec 28, 2024
7aeeed0
start updating docs
jagerber48 Dec 28, 2024
ddf2e1d
instance check for ABC real seems to be slow. See if removing that im…
jagerber48 Dec 28, 2024
40b088c
need to include int
jagerber48 Dec 28, 2024
968feef
documentation and doctest
jagerber48 Dec 30, 2024
d5edb5f
fix index
jagerber48 Dec 30, 2024
76a4b80
cast default dict to dict for ucombo expanded dict. Also don't do eag…
jagerber48 Dec 30, 2024
1da88c3
Add back make.bat
jagerber48 Dec 30, 2024
54179b7
add doctest sphinx extension in conf.py so doctests can run
jagerber48 Dec 30, 2024
e8aba3f
Merge branch 'feature/add_make.bat' into feature/linear_combo_refactor
jagerber48 Dec 30, 2024
1da127e
UAtom repr
jagerber48 Dec 30, 2024
17bc5a7
error_components doctest
jagerber48 Dec 30, 2024
34701c2
doctests
jagerber48 Dec 30, 2024
5c309d4
more documentation
jagerber48 Dec 30, 2024
79e00d0
don't document Variable
jagerber48 Dec 31, 2024
9607a0d
add tuples instead of nesting for addition. Will this be faster?
jagerber48 Dec 31, 2024
e7e327d
Merge branch 'master' into feature/linear_combo_refactor
jagerber48 Mar 15, 2025
357a37f
refactor test_power_derivatives
jagerber48 Mar 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ quick taste of how to use :mod:`uncertainties`:
The :mod:`uncertainties` library calculates uncertainties using linear `error
propagation theory`_ by automatically :ref:`calculating derivatives
<derivatives>` and analytically propagating these to the results. Correlations
between variables are automatically handled. This library can also yield the
derivatives of any expression with respect to the variables that have uncertain
values. For other approaches, see soerp_ (using higher-order terms) and mcerp_
(using a Monte-Carlo approach).
between variables are automatically handled. For other approaches, see soerp_ (using
higher-order terms) and mcerp_ (using a Monte-Carlo approach).

The `source code`_ for the uncertainties package is licensed under the `Revised
BSD License`_. This documentation is licensed under the `CC-SA-3 License`_.
Expand Down
2 changes: 1 addition & 1 deletion doc/tech_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ user-supplied function.

.. autofunction:: ufloat_fromstr

.. autoclass:: Variable
.. autoclass:: UFloat

.. autofunction:: wrap

Expand Down
312 changes: 194 additions & 118 deletions doc/user_guide.rst

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions tests/cases/double_inputs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"real": [
[
-2.9249265792461614,
-55.85012579473991
],
[
-80.47715772186896,
-95.52360204965058
],
[
-46.66317330650271,
-54.246799586133164
],
[
76.83020774845227,
-65.30072771420942
],
[
-99.30113055934278,
50.251117687128925
],
[
4.490698628165916,
-53.589861183978435
],
[
78.91935395791347,
-15.236501815329234
],
[
-41.52820441856526,
-36.1797093396341
],
[
-39.0871976088637,
-16.61234220198655
],
[
-19.463566425184382,
-7.86900097049228
]
],
"positive": [
[
85.53554359280284,
5.2077314883582915
],
[
71.05349046418226,
59.57350881505704
],
[
94.503286127752,
5.991036642740532
],
[
18.27907078802746,
6.567889719237552
],
[
8.280279561927417,
67.82173626757626
],
[
45.78565106549721,
78.79955748827399
],
[
55.8027580912824,
22.9918264565118
],
[
9.057003520483697,
25.932550147696997
],
[
16.75232871348641,
62.41335089010337
],
[
38.6284075569242,
65.17474144156256
]
]
}
34 changes: 34 additions & 0 deletions tests/cases/generate_math_cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import json
import random


valid_inputs_dict = {}


def main():
num_reps = 10

single_inputs_dict = {
"real": [random.uniform(-100, 100) for _ in range(num_reps)],
"positive": [random.uniform(0, 100) for _ in range(num_reps)],
"minus_one_to_plus_one": [random.uniform(-1, +1) for _ in range(num_reps)],
"greater_than_one": [random.uniform(+1, 100) for _ in range(num_reps)],
}
with open("single_inputs.json", "w+") as f:
json.dump(single_inputs_dict, f, indent=True)

double_inputs_dict = {
"real": [
[random.uniform(-100, 100), random.uniform(-100, +100)]
for _ in range(num_reps)
],
"positive": [
[random.uniform(0, 100), random.uniform(0, +100)] for _ in range(num_reps)
],
}
with open("double_inputs.json", "w+") as f:
json.dump(double_inputs_dict, f, indent=True)


if __name__ == "__main__":
main()
50 changes: 50 additions & 0 deletions tests/cases/single_inputs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"real": [
69.82891420951046,
8.744849762337111,
9.501568500140195,
-0.2697150283348435,
-85.39870720878719,
61.19125397363706,
-83.8244247280742,
18.175708127431946,
89.59079964302475,
-7.696969156332287
],
"positive": [
89.27720557828748,
31.22932658045743,
39.91471622647749,
76.33848530340148,
9.676420060716396,
26.249479197071125,
78.52001380075208,
42.43263280403463,
11.746554290944955,
49.031136773444885
],
"minus_one_to_plus_one": [
0.580566969475276,
0.14866137712403948,
0.16898539840736726,
-0.5004965202207128,
0.7509607357841148,
0.8848172839099921,
0.5100407276275787,
0.06547584726766464,
-0.1806707468903621,
-0.09163094454513665
],
"greater_than_one": [
27.47869311012394,
86.15750156710183,
34.31440803475614,
71.90645538572487,
4.247290853432516,
21.366978683255986,
52.47488483558816,
55.67005996752599,
71.12816870731481,
85.93669459425669
]
}
149 changes: 8 additions & 141 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import random
from math import isclose, isnan, isinf

import uncertainties.core as uncert_core
from uncertainties.core import AffineScalarFunc
from uncertainties.core import UFloat


def get_single_uatom(num_with_uncertainty: UFloat):
error_components = num_with_uncertainty.error_components
if len(error_components) > 1:
raise ValueError("UFloat has more than one error component.")

Check warning on line 10 in tests/helpers.py

View check run for this annotation

Codecov / codecov/patch

tests/helpers.py#L10

Added line #L10 was not covered by tests
return next(iter(error_components.keys()))


def nan_close(first, second):
Expand Down Expand Up @@ -67,145 +73,6 @@
pass


def compare_derivatives(func, numerical_derivatives, num_args_list=None):
"""
Checks the derivatives of a function 'func' (as returned by the
wrap() wrapper), by comparing them to the
'numerical_derivatives' functions.

Raises a DerivativesDiffer exception in case of problem.

These functions all take the number of arguments listed in
num_args_list. If num_args is None, it is automatically obtained.

Tests are done on random arguments.
"""

try:
funcname = func.name
except AttributeError:
funcname = func.__name__

# print "Testing", func.__name__

if not num_args_list:
# Detecting automatically the correct number of arguments is not
# always easy (because not all values are allowed, etc.):

num_args_table = {
"atanh": [1],
"log": [1, 2], # Both numbers of arguments are tested
}
if funcname in num_args_table:
num_args_list = num_args_table[funcname]
else:
num_args_list = []

# We loop until we find reasonable function arguments:
# We get the number of arguments by trial and error:
for num_args in range(10):
try:
#! Giving integer arguments is good for preventing
# certain functions from failing even though num_args
# is their correct number of arguments
# (e.g. math.ldexp(x, i), where i must be an integer)
func(*(1,) * num_args)
except TypeError:
pass # Not the right number of arguments
else: # No error
# num_args is a good number of arguments for func:
num_args_list.append(num_args)

if not num_args_list:
raise Exception(
"Can't find a reasonable number of arguments"
" for function '%s'." % funcname
)

for num_args in num_args_list:
# Argument numbers that will have a random integer value:
integer_arg_nums = set()

if funcname == "ldexp":
# The second argument must be an integer:
integer_arg_nums.add(1)

while True:
try:
# We include negative numbers, for more thorough tests:
args = []
for arg_num in range(num_args):
if arg_num in integer_arg_nums:
args.append(random.choice(range(-10, 10)))
else:
args.append(uncert_core.Variable(random.random() * 4 - 2, 0))

# 'args', but as scalar values:
args_scalar = [uncert_core.nominal_value(v) for v in args]

func_approx = func(*args)

# Some functions yield simple Python constants, after
# wrapping in wrap(): no test has to be performed.
# Some functions also yield tuples...
if isinstance(func_approx, AffineScalarFunc):
# We compare all derivatives:
for arg_num, (arg, numerical_deriv) in enumerate(
zip(args, numerical_derivatives)
):
# Some arguments might not be differentiable:
if isinstance(arg, int):
continue

fixed_deriv_value = func_approx.derivatives[arg]

num_deriv_value = numerical_deriv(*args_scalar)

# This message is useful: the user can see that
# tests are really performed (instead of not being
# performed, silently):
print(
"Testing derivative #%d of %s at %s"
% (arg_num, funcname, args_scalar)
)

if not numbers_close(fixed_deriv_value, num_deriv_value, 1e-4):
# It is possible that the result is NaN:
if not isnan(func_approx):
raise DerivativesDiffer(
"Derivative #%d of function '%s' may be"
" wrong: at args = %s,"
" value obtained = %.16f,"
" while numerical approximation = %.16f."
% (
arg_num,
funcname,
args,
fixed_deriv_value,
num_deriv_value,
)
)

except ValueError as err: # Arguments out of range, or of wrong type
# Factorial(real) lands here:
if str(err).startswith("factorial"):
integer_arg_nums = set([0])
continue # We try with different arguments
# Some arguments might have to be integers, for instance:
except TypeError as err:
if len(integer_arg_nums) == num_args:
raise Exception(
"Incorrect testing procedure: unable to "
"find correct argument values for %s: %s" % (funcname, err)
)

# Another argument might be forced to be an integer:
integer_arg_nums.add(random.choice(range(num_args)))
else:
# We have found reasonable arguments, and the test passed:
break


###############################################################################


Expand Down
4 changes: 0 additions & 4 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ def test_repr():
x = ufloat(3.14159265358979, 0)
assert repr(x) == "3.14159265358979+/-0"

# Tagging:
x = ufloat(3, 1, "length")
assert repr(x) == "< length = 3.0+/-1.0 >"


# The way NaN is formatted with F, E and G depends on the version of Python (NAN for
# Python 2.5+ at least):
Expand Down
Loading
Loading