Skip to content

Commit

Permalink
loop through args and kwargs instead of using inspect.signature
Browse files Browse the repository at this point in the history
Sadly, some built-in functions including `math.log` do not work with inspect.signature because they have multiple signatures.
  • Loading branch information
jagerber48 committed Jul 17, 2024
1 parent a602f84 commit d7ee99b
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 32 deletions.
94 changes: 64 additions & 30 deletions uncertainties/core_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from collections import defaultdict
from dataclasses import dataclass, field
from functools import lru_cache, wraps
import inspect
from math import sqrt, isnan
from numbers import Real
import sys
Expand Down Expand Up @@ -218,22 +217,38 @@ def numerical_partial_derivative(
target_param (string name or position number of the float parameter to f to be
varied) holding all other arguments, *args and **kwargs, constant.
"""
sig = inspect.signature(f)
lower_bound_sig = sig.bind(*args, **kwargs)
upper_bound_sig = sig.bind(*args, **kwargs)

target_param_name = get_param_name(sig, target_param)

x = lower_bound_sig.arguments[target_param_name]
if isinstance(target_param, int):
x = args[target_param]

Check warning on line 221 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L220-L221

Added lines #L220 - L221 were not covered by tests
else:
x = kwargs[target_param]
dx = abs(x) * SQRT_EPS # Numerical Recipes 3rd Edition, eq. 5.7.5

Check warning on line 224 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L223-L224

Added lines #L223 - L224 were not covered by tests

# Inject x - dx into target_param and evaluate f
lower_bound_sig.arguments[target_param_name] = x - dx
lower_y = f(*lower_bound_sig.args, **lower_bound_sig.kwargs)
# TODO: The construction below could be simplied using inspect.signature. However,
# the math.log, and other math functions do not yet (as of python 3.12) work with
# inspect.signature. Therefore, we need to manually loop of args and kwargs.
# Monitor https://github.com/python/cpython/pull/117671
lower_args = []
upper_args = []
for idx, arg in enumerate(args):
if idx == target_param:
lower_args.append(x - dx)
upper_args.append(x + dx)

Check warning on line 235 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L230-L235

Added lines #L230 - L235 were not covered by tests
else:
lower_args.append(arg)
upper_args.append(arg)

Check warning on line 238 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L237-L238

Added lines #L237 - L238 were not covered by tests

lower_kwargs = {}
upper_kwargs = {}
for key, arg in kwargs.items():
if key == target_param:
lower_kwargs[key] = x - dx
upper_kwargs[key] = x + dx

Check warning on line 245 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L240-L245

Added lines #L240 - L245 were not covered by tests
else:
lower_kwargs[key] = arg
upper_kwargs[key] = arg

Check warning on line 248 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L247-L248

Added lines #L247 - L248 were not covered by tests

# Inject x + dx into target_param and evaluate f
upper_bound_sig.arguments[target_param_name] = x + dx
upper_y = f(*upper_bound_sig.args, **upper_bound_sig.kwargs)
lower_y = f(*lower_args, **lower_kwargs)
upper_y = f(*upper_args, **upper_kwargs)

Check warning on line 251 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L250-L251

Added lines #L250 - L251 were not covered by tests

derivative = (upper_y - lower_y) / (2 * dx)
return derivative

Check warning on line 254 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L253-L254

Added lines #L253 - L254 were not covered by tests
Expand Down Expand Up @@ -277,40 +292,59 @@ def __init__(
self.deriv_func_dict: DerivFuncDict = deriv_func_dict

def __call__(self, f: Callable[..., float]):
sig = inspect.signature(f)
# sig = inspect.signature(f)

@wraps(f)
def wrapped(*args, **kwargs):
float_bound = sig.bind(*args, **kwargs)

# TODO: The construction below could be simplied using inspect.signature.
# However, the math.log, and other math functions do not yet
# (as of python 3.12) work with inspect.signature. Therefore, we need to
# manually loop of args and kwargs.
# Monitor https://github.com/python/cpython/pull/117671
return_u_val = False
for param, param_val in float_bound.arguments.items():
if isinstance(param_val, UFloat):
float_bound.arguments[param] = param_val.val
float_args = []
for arg in args:
if isinstance(arg, UFloat):
float_args.append(arg.val)
return_u_val = True
elif isinstance(param_val, Real):
float_bound.arguments[param] = float(param_val)
else:
float_args.append(arg)
float_kwargs = {}
for key, arg in kwargs.items():
if isinstance(arg, UFloat):
float_kwargs[key] = arg.val
return_u_val = True

Check warning on line 316 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L314-L316

Added lines #L314 - L316 were not covered by tests
else:
float_kwargs[key] = arg

Check warning on line 318 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L318

Added line #L318 was not covered by tests

new_val = f(*float_args, **float_kwargs)

new_val = f(*float_bound.args, **float_bound.kwargs)
if not return_u_val:
return new_val

Check warning on line 323 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L323

Added line #L323 was not covered by tests

ufloat_bound = sig.bind(*args, **kwargs)
new_uncertainty_lin_combo = []
for u_float_param in self.ufloat_params:
u_float_param_name = get_param_name(sig, u_float_param)
arg = ufloat_bound.arguments[u_float_param_name]
if isinstance(u_float_param, int):
try:
arg = args[u_float_param]
except IndexError:
continue

Check warning on line 331 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L330-L331

Added lines #L330 - L331 were not covered by tests
else:
try:
arg = kwargs[u_float_param]
except KeyError:
continue

Check warning on line 336 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L333-L336

Added lines #L333 - L336 were not covered by tests
if isinstance(arg, UFloat):
deriv_func = self.deriv_func_dict[u_float_param]
if deriv_func is None:
derivative = numerical_partial_derivative(

Check warning on line 340 in uncertainties/core_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/core_new.py#L340

Added line #L340 was not covered by tests
f,
u_float_param_name,
*float_bound.args,
**float_bound.kwargs,
u_float_param,
*float_args,
**float_kwargs,
)
else:
derivative = deriv_func(*float_bound.args, **float_bound.kwargs)
derivative = deriv_func(*float_args, **float_kwargs)

new_uncertainty_lin_combo.append(
(arg.uncertainty_lin_combo, derivative)
Expand Down
4 changes: 2 additions & 2 deletions uncertainties/umath_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def erfc(value: UReal) -> UReal: ...
def exp(value: UReal) -> UReal: ...

Check warning on line 45 in uncertainties/umath_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/umath_new.py#L45

Added line #L45 was not covered by tests


# def log(value: UReal) -> UReal: ...
def log(value: UReal) -> UReal: ...

Check warning on line 48 in uncertainties/umath_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/umath_new.py#L48

Added line #L48 was not covered by tests


def log10(value: UReal) -> UReal: ...

Check warning on line 51 in uncertainties/umath_new.py

View check run for this annotation

Codecov / codecov/patch

uncertainties/umath_new.py#L51

Added line #L51 was not covered by tests
Expand Down Expand Up @@ -95,7 +95,7 @@ def log_der0(*args):
"erf": ("(2/math.sqrt(math.pi))*math.exp(-(x**2))",),
"erfc": ("-(2/math.sqrt(math.pi))*math.exp(-(x**2))",),
"exp": ("math.exp(x)",),
# "log": (log_der0, "-math.log(x, y) / y / math.log(y)"),
"log": (log_der0, "-math.log(x, y) / y / math.log(y)"),
"log10": ("1/x/math.log(10)",),
"radians": ("math.radians(1)",),
"sin": ("math.cos(x)",),
Expand Down

0 comments on commit d7ee99b

Please sign in to comment.