DPD acceptance matrix#
Introduction#
To speed up the fit, we can precompute the part of the negative log likelihood (NLL) function that normalizes the intensity function. Normally, you normalize the intensity function numerically with Monte-Carlo integration over a phase space sample (with or without detector efficiency). When we only vary scaling factors (couplings) in the amplitude model (i.e., masses and widths fixed), it is possible to precompute the normalization, as the normalization factor changes when we vary these non-linear parameters.
Statistically, this approach can be derived as follows. Given total sample \(\vec{x}\) consisting of \(N\) independent observations of a set, the likelihood function can be written as the product of the Probability Density Functions (PDFs) \(f\) of each single observation:
Here, \(\vec{\theta}\) represents the set of \(m\) unknown parameters that the PDF depends on. In an amplitude analysis, the PDFs are the normalized intensity functions for each partial wave:
The variables \(\vec{x}_i\) now represent the Mandelstam variables and the decay and production angles of each event \(i\). With this expression for \(f\), we can rewrite the NLL function as
Since the second term integrates over all phase-space points, it is independent of \(x_i\) and can be approximated using Monte Carlo integration:
The term \(\frac{1}{N_{\mathrm{MC}}} \sum_{j=1}^{N_{\mathrm{MC}}} I(\vec{x}_j^{\mathrm{MC}};\vec{\theta})\) represents the so-called acceptance matrix, a hermitian matrix, which we label \(X\) from now on. The acceptance matrix can be used obtain integrated intensity via the bilinear relation,
Construction of the acceptance matrix#
The acceptance matrix can be constructed by probing with four different value combinations for the \(c_ij\) leading to the four equations:
The \(A_{ij}\) and \(B_{ij}\) are the elements of the sub-intensity matrix, the results of the integration over the phase space when setting \(c_i\) and \(c_j\) to the respective values and the rest of to couplings to zero.\ The elements of \(X\) can finally be expressed as:
As you can see in the expression above, two equations are needed for the real part of \(X_{ij}\) and two for the imaginary part.
Speed up the construction of \(X\)#
Using the four equations described above is not computationally efficient, we have to compute the sub-intensity matrix for all \(4m^2\) combinations of \(c_i\) and \(c_j\), with \(m\) the number of couplings. However, we can use the hermitian property of the acceptance matrix to reduce the required number of equations to construct the matrix. A hermitian matrix is mirror symmetric with respect to its main diagonal, up to the complex conjugation of all entries. Therefore one only has to calculate the upper triangle of \(X\) and then fill the lower triangle with its complex-conjugate transpose. As probes the vectors with \(c_i=1,c_j=1\) are used to obtain the real part, while \(c_i=i,c_j=1\) is used to obtain the imaginary part of the elements of \(X_{ij}\). The diagonal of \(X\) is real-valued and is equal to the branching fractions (that is, the diagonal of the sub-intensity matrix with \(c_i=c_j=1\)).
With this, we can compute the acceptance matrix as
Prepare model and data#
Define reaction#
Define amplitude model#
decay = to_three_body_decay(reaction.transitions, min_ls=False)
builder = DalitzPlotDecompositionBuilder(decay, min_ls=False)
for chain in builder.decay.chains:
builder.dynamics_choices.register_builder(chain, formulate_breit_wigner_with_ff)
model = builder.formulate(reference_subsystem=2, cleanup_summations=True)
model = prepare_for_phsp(model)
model.intensity.cleanup()
Generate phase space sample#
dpd_transformer = SympyDataTransformer.from_sympy(model.variables, backend="jax")
phsp
{'\\zeta^0_{2(2)}': Array(0, dtype=int64, weak_type=True),
'\\zeta^0_{3(2)}': Array([-2.53357417, -2.43246297, -2.75588118, ..., -2.88331429, -2.45355527, -2.89980823], dtype=float64),
'\\zeta^2_{2(2)}': Array(0, dtype=int64, weak_type=True),
'\\zeta^2_{3(2)}': Array([0.48645084, 0.58458208, 0.26202874, ..., 0.34503341, 0.77149637, 0.18183345], dtype=float64),
'\\zeta^3_{2(2)}': Array(0, dtype=int64, weak_type=True),
'\\zeta^3_{3(2)}': Array([0.78587981, 0.67615773, 1.17998783, ..., 0.56729844, 0.50850767, 1.20546428], dtype=float64),
'm0': Array([3.0969, 3.0969, 3.0969, ..., 3.0969, 3.0969, 3.0969], dtype=float64),
'm1': Array([0.497611, 0.497611, 0.497611, ..., 0.497611, 0.497611, 0.497611], dtype=float64),
'm2': Array([1.18937, 1.18937, 1.18937, ..., 1.18937, 1.18937, 1.18937], dtype=float64),
'm3': Array([0.93827209, 0.93827209, 0.93827209, ..., 0.93827209, 0.93827209, 0.93827209], dtype=float64),
'sigma1': Array([5.9276109 , 5.8019359 , 6.08202612, ..., 6.50903172, 5.84419671, 6.24721317], dtype=float64),
'sigma2': Array([2.51882444, 2.67122 , 2.23124228, ..., 2.27933047, 2.81951794, 2.13741812], dtype=float64),
'sigma3': Array([3.68692649, 3.66020593, 3.82009343, ..., 3.34499964, 3.46964718, 3.74873053], dtype=float64),
'theta_12': Array([2.00195379, 1.79913544, 2.46359496, ..., 2.51073213, 1.63263745, 2.69699582], dtype=float64),
'theta_31': Array([1.46225035, 1.45618234, 1.61596876, ..., 0.86704215, 1.26372834, 1.48250258], dtype=float64)}
Prepare numerical function#
4
full_intensity_expr = cached.unfold(model)
full_intensity_expr = cached.xreplace(full_intensity_expr, fixed_parameters)
intensity_func = cached.lambdify(
full_intensity_expr,
parameters=coupling_parameters,
backend="jax",
)
Compute acceptance matrix#
Brute-force computation#
%%time
A_plus = compute_intensity_matrix(intensity_func, phsp, ci=1, cj=+1)
B_plus = compute_intensity_matrix(intensity_func, phsp, ci=1, cj=+1j)
A_minus = compute_intensity_matrix(intensity_func, phsp, ci=1, cj=-1)
B_minus = compute_intensity_matrix(intensity_func, phsp, ci=1, cj=-1j)
CPU times: user 24.2 s, sys: 1.75 s, total: 26 s
Wall time: 5.57 s
array([[ 9.18200e-04+0.0000e+00j, 1.72000e-05+1.7800e-04j, 0.00000e+00+0.0000e+00j, 2.80000e-06+3.1000e-06j],
[ 1.72000e-05-1.7800e-04j, 1.95700e-03+0.0000e+00j, 0.00000e+00+0.0000e+00j, -8.19900e-04-3.1568e-03j],
[ 0.00000e+00+0.0000e+00j, 0.00000e+00+0.0000e+00j, 0.00000e+00+0.0000e+00j, 0.00000e+00+0.0000e+00j],
[ 2.80000e-06-3.1000e-06j, -8.19900e-04+3.1568e-03j, 0.00000e+00+0.0000e+00j, 9.51062e-02+0.0000e+00j]])
np.testing.assert_almost_equal(X_brute_force, X_brute_force.T.conj())
Smarter computation#
%%time
X = compute_acceptance_matrix(intensity_func, phsp)
CPU times: user 4.27 s, sys: 475 ms, total: 4.74 s
Wall time: 636 ms
X.round(7)
array([[ 9.18200e-04+0.0000e+00j, 1.72000e-05+1.7800e-04j, 0.00000e+00+0.0000e+00j, 2.80000e-06+3.1000e-06j],
[ 1.72000e-05-1.7800e-04j, 1.95700e-03+0.0000e+00j, 0.00000e+00+0.0000e+00j, -8.19900e-04-3.1568e-03j],
[ 0.00000e+00+0.0000e+00j, 0.00000e+00+0.0000e+00j, 0.00000e+00+0.0000e+00j, 0.00000e+00+0.0000e+00j],
[ 2.80000e-06-3.1000e-06j, -8.19900e-04+3.1568e-03j, 0.00000e+00+0.0000e+00j, 9.51062e-02+0.0000e+00j]])
np.testing.assert_almost_equal(X, X.T.conj())
np.testing.assert_almost_equal(X, X_brute_force)
Test bilinear form#
The the bilinear form with the acceptance matrix is to be used within for the optimization of the coupling parameters \(c_{ij}\). The bilinear form should lead to the same result when calculating the sub-intensity directly by Monte-Carlo integration. In the following this expected behavior is tested for different \(c\).
Result taking every element of \(c\) as \(2+1i\) or 0:
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1775)^{-}, 1, 2}\) |
\(\mathcal{H}^\mathrm{LS,production}_{N(2060)^+, 1, 2}\) |
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1750)^{-}, 1, 0}\) |
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1750)^{-}, 1, 1}\) |
bilinear |
MC |
relative difference |
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Now new values for the elements of \(c\) are generated by varying the magnitude of the \(c_i\) around one according the a log-normal distribution and the complex phase according to a uniform distribution. Note that this time each \(c_i\) has a different phase and magnitude.
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1775)^{-}, 1, 2}\) |
\(\mathcal{H}^\mathrm{LS,production}_{N(2060)^+, 1, 2}\) |
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1750)^{-}, 1, 0}\) |
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1750)^{-}, 1, 1}\) |
bilinear |
MC |
relative difference |
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1775)^{-}, 1, 2}\) |
\(\mathcal{H}^\mathrm{LS,production}_{N(2060)^+, 1, 2}\) |
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1750)^{-}, 1, 0}\) |
\(\mathcal{H}^\mathrm{LS,production}_{\overline{\Sigma}(1750)^{-}, 1, 1}\) |
bilinear |
MC |
relative difference |
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note: the last two comparisons where each element of \(c\) vector has a different complex part or phase