Unbinned fit#
Imagine we have the following data distribution over \(x,y\):
import numpy as np
sample_size = 50_000
rng = np.random.default_rng(seed=0)
data = {
"x": rng.rayleigh(size=sample_size),
"y": rng.normal(size=sample_size),
}
The data distribution has been generated by numpy.random.normal() and numpy.random.rayleigh() and can therefore be described by the following expression:
import sympy as sp
def rayleigh(x, sigma):
return x / sigma**2 * sp.exp(-(x**2) / (2 * sigma**2))
def gaussian(x, mu, sigma):
return sp.exp(-((x - mu) ** 2) / (2 * sigma**2)) / sp.sqrt(2 * sp.pi * sigma**2)
x, y, mu, sigma_x, sigma_y = sp.symbols("x y mu sigma_x sigma_y")
expression = rayleigh(x, sigma_x) * gaussian(y, mu, sigma_y)
expression
We would like to find values for \(\mu, \sigma_x, \sigma_y\), so that the expression describe this distribution as best as possible. For this, we first formulate this expression as a ParametrizedFunction in a specific computational backend, so that we can use it to quickly compute values over a number of data points. We also provide some initial guesses for the parameter values:
from tensorwaves.function.sympy import create_parametrized_function
function = create_parametrized_function(
expression,
parameters={mu: -0.3, sigma_x: 0.3, sigma_y: 2.7},
backend="jax",
)
initial_parameters = function.parameters
The function can be used to visualize the expression with this choice of parameter values over a certain \(xy\)-domain and compare it to the original data distribution.
Next, we use a UnbinnedNLL optimize the parameters with regard to the data distribution. Note that a UnbinnedNLL requires a domain over which to integrate the ParametrizedFunction, in order to normalize the log likelihood.
from tensorwaves.estimator import UnbinnedNLL
from tensorwaves.optimizer import Minuit2
integration_domain = {
"x": rng.uniform(0, 4, size=200_000),
"y": rng.uniform(-3, +3, size=200_000),
}
estimator = UnbinnedNLL(function, data, integration_domain, backend="jax")
optimizer = Minuit2()
fit_result = optimizer.optimize(estimator, initial_parameters)
fit_result
FitResult(
minimum_valid=True,
execution_time=0.8374977111816406,
function_calls=254,
estimator_value=-41104.124002373996,
parameter_values={
'mu': -0.0032882857079386935,
'sigma_x': 1.0015447225927316,
'sigma_y': -1.0094427512925899,
},
parameter_errors={
'mu': 0.004564612643744601,
'sigma_x': 0.0022676565540249057,
'sigma_y': 0.0034326069443002807,
},
)
The values are indeed close to the default values for numpy.random.rayleigh() (\(\sigma_x=1\)) numpy.random.normal() (\(\mu=0, \sigma_y=1\)), with which the data distribution was generated.