A collection of tools and utilities that I often use in my work.
- Scientific
- Error propagation for numerical calculations
- Pretty printing of numerical results with uncertainties
- Functional
- Function composition utilities
- Partial function application
- Overload function definitions
pip install luxtools
import torch
from luxtools import get_error
# Create tensors with uncertainties
x = torch.tensor([1.0, 3.0], dtype=torch.float32, requires_grad=True)
x.sigma = torch.tensor([0.1, 0.2], dtype=torch.float32)
y = torch.tensor([2.0, 4.0], dtype=torch.float32, requires_grad=True)
y = Variable(y, torch.tensor([0.2, 0.3]))
# Perform calculations
f = x * y
# Get propagated error
error = get_error(f)
# tensor([0.2828, 1.2042])
from luxtools import NumericResult
# Create a measurement with uncertainty
result = NumericResult(1.234, 0.193)
print(result)
# (1.2 ± 0.2)
# Scientific notation
result = NumericResult(234.23424, 10)
print(result)
# (2.3 ± 0.1)*10^(2)
# LaTeX output
print(result.latex())
# (2.3 \pm 0.1)\cdot 10^{2}
from luxtools import chain
# Compose functions
f = lambda x: x + 1
g = lambda x: x * 2
h = lambda x: x ** 2
# Create a new function that applies f, then g, then h
composed = chain(f, g, h)
result = composed(3) # ((3 + 1) * 2) ** 2 = 64
Allows you to partially apply arguments to a function, creating a new function with fewer arguments. See article for discussion.
from luxtools import partial
@partial
def greet(greeting, name):
return f"{greeting}, {name}!"
# Create a new function with 'Hello' fixed as the greeting
say_hello = greet("Hello")
result = say_hello("World") # "Hello, World!"
Allows you to have multiple function definitions for the same function name. It uses typehints to determine which function to call. See article for discussion.
from luxtools import overload
class Email:
def __init__(self, email: str):
self.email = email
def __str__(self) -> str:
return self.email
class PhoneNumber:
def __init__(self, phone_number: str):
self.phone_number = phone_number
def __str__(self) -> str:
return self.phone_number
@overload
def get_user(email: Email):
print("Email:", email)
return email
@overload
def get_user(phone_number: PhoneNumber):
print("Phone:", phone_number)
return phone_number
get_user(Email("[email protected]")) # prints: Email: [email protected]
get_user(PhoneNumber("123-456-789")) # prints: Phone: 123-456-789
Caveat, if the function is defined in a non-global scope such as a class, or inside a function, then you need to pass the local scope to the decorator.
def local_scope():
@overload(scope=locals())
def get_user(email: Email):
print("Email:", email)
@overload(scope=locals())
def get_user(phone_number: PhoneNumber):
print("Phone:", phone_number)
This is necessary because the parent stack frame doesn't exist inside the overload
function, so it has to be passed explicitly. See tests.