Step 4: Analyze results¶
Warning
The pycompwa is no longer maintained. Use the ComPWA packages QRules, AmpForm, and TensorWaves instead!
Note
We load the output from the previous steps first. Note that you need to define an estimator before loading the fit result to the intensity.
import pycompwa.ui as pwa
2021-10-27 10:28:00,481 INFO [default] Logging to file disabled!
2021-10-27 10:28:00,481 [INFO] Log level: INFO
2021-10-27 10:28:00,481 [INFO] Current date and time: Wed Oct 27 10:28:00 2021
pwa.Logging("error") # at this stage, we are not interested in the back-end
particle_list = pwa.read_particles("model.xml")
kinematics = pwa.create_helicity_kinematics("model.xml", particle_list)
kinematics.create_all_subsystems()
data_sample = pwa.read_root_data(input_file="generated_data.root")
phsp_sample = pwa.read_root_data(input_file="generated_phsp.root")
intensity_builder = pwa.IntensityBuilderXML(
"model.xml", particle_list, kinematics, phsp_sample
)
fit_result = pwa.load("fit_result.xml")
intensity = intensity_builder.create_intensity()
data_set = kinematics.convert(data_sample)
phsp_set = kinematics.convert(phsp_sample)
(
estimator,
initial_parameters,
) = pwa.create_unbinned_log_likelihood_function_tree_estimator(
intensity, data_set
)
intensity.updateParametersFrom(fit_result.final_parameters)
2021-10-27 10:28:00,888 [INFO] Logging to file disabled!
4.1 Convert to pandas¶
It is easiest to analyze your data with pandas.DataFrame
. We are mostly interested in the distributions of the kinematic variables, so we first have to convert the DataSet
instances to a DataFrame
. We can do this with the data.convert
module:
from pycompwa.data import convert
frame_data = convert.data_set_to_pandas(data_set)
frame_phsp = convert.data_set_to_pandas(phsp_set)
frame_data
phi_2_34 | phi_24_3 | mSq_(2,3) | phi_2_3_vs_4 | theta_2_34 | mSq_(2,4) | theta_23_4 | mSq_(3,4) | theta_34_2 | mSq_(2,3,4) | theta_3_4_vs_2 | phi_34_2 | phi_23_4 | theta_2_3_vs_4 | theta_24_3 | phi_2_4_vs_3 | phi_3_4_vs_2 | theta_2_4_vs_3 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2.102657 | 2.247974 | 7.041085 | 1.782934 | 1.002477 | 0.543847 | 2.745762 | 2.042295 | 2.139116 | 9.59079 | 0.502226 | -1.038936 | 0.498287 | 2.224991 | 0.797810 | -0.554556 | 0.463670 | 0.915063 |
1 | 2.071876 | 2.106510 | 3.162373 | 0.072829 | 2.604591 | 4.057776 | 2.852961 | 2.407078 | 0.537001 | 9.59079 | 1.697711 | -1.069717 | -0.965088 | 1.299054 | 1.578960 | -0.020714 | 0.040498 | 1.424155 |
2 | -0.889536 | -1.756459 | 4.671107 | -0.724954 | 2.297602 | 3.687157 | 1.794325 | 1.268963 | 0.843991 | 9.59079 | 1.448707 | 2.252057 | -0.229754 | 1.039627 | 2.432907 | 1.456694 | -2.095938 | 0.938116 |
3 | 2.007197 | -3.122791 | 2.253370 | 1.288726 | 1.385250 | 3.558244 | 0.925994 | 3.815613 | 1.756343 | 9.59079 | 1.800957 | -1.134396 | 1.112438 | 1.595627 | 2.159360 | -1.965977 | 2.245262 | 1.827414 |
4 | -2.553175 | -3.092292 | 1.277894 | -0.255728 | 1.157882 | 5.765827 | 0.701375 | 2.583506 | 1.983710 | 9.59079 | 2.277980 | 0.588417 | -2.428574 | 1.152534 | 2.789166 | 2.649058 | -2.962439 | 1.918852 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
9995 | -0.222349 | 0.534366 | 3.694790 | 0.860415 | 2.257719 | 1.919805 | 1.999034 | 4.012632 | 0.883873 | 9.59079 | 1.243871 | 2.919244 | -2.017723 | 1.929566 | 1.716018 | -0.771193 | 1.101634 | 1.600783 |
9996 | -1.343847 | -1.411040 | 6.871474 | -0.685393 | 0.886257 | 0.165163 | 2.657135 | 2.590590 | 2.255336 | 9.59079 | 0.238120 | 1.797745 | 1.425939 | 2.746994 | 0.773063 | 0.435808 | -0.390363 | 0.879317 |
9997 | -1.598526 | -0.592358 | 2.555019 | 1.917279 | 0.627246 | 6.196502 | 0.665568 | 0.875706 | 2.514347 | 9.59079 | 2.022378 | 1.543067 | -2.206541 | 0.684207 | 1.084305 | -2.424707 | 1.715182 | 1.026751 |
9998 | -2.735183 | 0.200863 | 0.232193 | 0.320457 | 1.459895 | 5.701769 | 1.247911 | 3.693265 | 1.681698 | 9.59079 | 2.783947 | 0.406410 | -2.805244 | 1.244396 | 2.221093 | -0.384746 | 2.836304 | 2.694432 |
9999 | -2.831066 | 2.733274 | 4.462864 | -1.123583 | 2.271190 | 4.105117 | 2.130626 | 1.059246 | 0.870403 | 9.59079 | 1.527329 | 0.310527 | -2.036393 | 0.914699 | 2.111935 | 1.100360 | -1.532029 | 0.881110 |
10000 rows × 18 columns
We converted the phase space sample as well, because it serves as a uniform space over which to evaluate the intensity model. We therefore attribute an intensity as a weight to each entry in the phase space DataFrame
:
intensity_set = intensity.evaluate(phsp_set.data)
frame_phsp["weights"] = intensity_set
Hint
The pycompwa.data
contains several tools for converting from and to pandas.DataFrame
. There is also a data.io
submodule, which allows you to import form and export to file formats of other PWA frameworks. A demonstration of this module can be found under Data module.
4.2 Visualize¶
Now that we converted our data to pandas.DataFrame
, we can use its full potential! In this section, we will use some of that DataFrame
functionality to create histograms of the distributions of the kinematic variables and will have a look at the Dalitz plots both data and fit result.
Kinematic variables¶
Currently, the columns of the DataFrame
created above contain final state IDs. Of course, you would rather see particle names there, but we need to use unique IDs in case there are identical particle names. The data.naming
module provides a tool that can convert the final state IDs to recognizable names:
from pycompwa.data import naming
naming.replace_ids("2,3", "model.xml")
'gamma,pi0'
Now you can apply the usual matplotlib.pyplot
tools to plot the distributions.
import matplotlib.pyplot as plt
def plot_1d_comparison(name, bins=100, **kwargs):
"""Helper function for comparing the 1D distributions of fit and data."""
frame_data[name].hist(
bins=bins, density=True, alpha=0.5, label="data", **kwargs
)
frame_phsp[name].hist(
bins=bins,
weights=frame_phsp["weights"],
density=True,
histtype="step",
color="red",
label="fit",
**kwargs,
)
plt.ylabel("normalized intensity")
title = naming.replace_ids(name, kinematics)
plt.xlabel(title)
plot_1d_comparison("theta_2_4_vs_3")
plt.legend();
![../../_images/step4_20_0.png](../../_images/step4_20_0.png)
Applying a transformation to one of the columns is also easily done for a pandas.DataFrame
. For instance, if you are interested in the invariant mass (and not the square value of the invariant mass), simply use numpy.sqrt()
:
from numpy import sqrt
frame_data["M(3,4)"] = sqrt(frame_data["mSq_(3,4)"])
frame_phsp["M(3,4)"] = sqrt(frame_phsp["mSq_(3,4)"])
plot_1d_comparison("M(3,4)")
plt.legend();
![../../_images/step4_22_0.png](../../_images/step4_22_0.png)
Dalitz plots¶
Dalitz plots are 2-dimensional histograms of the square values of the invariant masses. We are therefore interested in the following variables:
variable_names = data_set.data.keys()
[var for var in variable_names if var.startswith("mSq_")]
['mSq_(2,3)', 'mSq_(2,4)', 'mSq_(3,4)', 'mSq_(2,3,4)']
We now use hist2d()
to generate the Dalitz plots:
def dalitz_plot(frame, mass_x, mass_y, bins=50, **kwargs):
"""Helper function to create a Dalitz plot with useful axis titles."""
plt.hist2d(frame[mass_x], frame[mass_y], bins=bins, **kwargs)
plt.xlabel(naming.replace_ids(mass_x, kinematics))
plt.ylabel(naming.replace_ids(mass_y, kinematics))
from matplotlib.colors import LogNorm # logarithmic z-axis
fig, axs = plt.subplots(1, 2, figsize=(9, 4))
plt.sca(axs[0])
axs[0].set_title("data")
dalitz_plot(frame_data, "mSq_(3,4)", "mSq_(2,4)", norm=LogNorm())
plt.sca(axs[1])
axs[1].set_title("fit")
dalitz_plot(
frame_phsp,
"mSq_(3,4)",
"mSq_(2,4)",
norm=LogNorm(),
weights=frame_phsp["weights"],
)
![../../_images/step4_28_0.png](../../_images/step4_28_0.png)
4.3 Calculate fit fractions¶
The original intensity model that we optimized consists of several components. All registered component names can be retrieved via get_all_component_names()
:
intensity_builder.get_all_component_names()
{'J/psi_-1_to_f0(1500)_0+gamma_-1;f0(1500)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f0(1500)_0+gamma_1;f0(1500)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f0(980)_0+gamma_-1;f0(980)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f0(980)_0+gamma_1;f0(980)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1270)_-1+gamma_-1;f2(1270)_-1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1270)_-2+gamma_-1;f2(1270)_-2_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1270)_0+gamma_-1;f2(1270)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1270)_0+gamma_1;f2(1270)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1270)_1+gamma_1;f2(1270)_1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1270)_2+gamma_1;f2(1270)_2_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1950)_-1+gamma_-1;f2(1950)_-1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1950)_-2+gamma_-1;f2(1950)_-2_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1950)_0+gamma_-1;f2(1950)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1950)_0+gamma_1;f2(1950)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1950)_1+gamma_1;f2(1950)_1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_-1_to_f2(1950)_2+gamma_1;f2(1950)_2_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f0(1500)_0+gamma_-1;f0(1500)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f0(1500)_0+gamma_1;f0(1500)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f0(980)_0+gamma_-1;f0(980)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f0(980)_0+gamma_1;f0(980)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1270)_-1+gamma_-1;f2(1270)_-1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1270)_-2+gamma_-1;f2(1270)_-2_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1270)_0+gamma_-1;f2(1270)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1270)_0+gamma_1;f2(1270)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1270)_1+gamma_1;f2(1270)_1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1270)_2+gamma_1;f2(1270)_2_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1950)_-1+gamma_-1;f2(1950)_-1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1950)_-2+gamma_-1;f2(1950)_-2_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1950)_0+gamma_-1;f2(1950)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1950)_0+gamma_1;f2(1950)_0_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1950)_1+gamma_1;f2(1950)_1_to_pi0_0+pi0_0;': 'Amplitude',
'J/psi_1_to_f2(1950)_2+gamma_1;f2(1950)_2_to_pi0_0+pi0_0;': 'Amplitude',
'coherent_J/psi_-1_to_gamma_-1+pi0_0+pi0_0': 'Intensity',
'coherent_J/psi_-1_to_gamma_1+pi0_0+pi0_0': 'Intensity',
'coherent_J/psi_1_to_gamma_-1+pi0_0+pi0_0': 'Intensity',
'coherent_J/psi_1_to_gamma_1+pi0_0+pi0_0': 'Intensity',
'incoherent_with_strength': 'Intensity'}
As you can see, these components are amplitudes and intensities that are added coherently or incoherently.
In PWA, you are often interested in the fractions that those amplitudes contribute to the total intensity. These fit fractions can be calculated using the fit_fractions_with_propagated_errors()
function. It requires amplitude or Intensity
components that can be extracted from the IntensityBuilderXML
instance. A nested list of the component names has to be specified as well. These are the names specified in the component XML attributes. If the inner lists contain more than one component, these components will be added coherently. In this way, you can calculate your own customized fit fractions.
components = intensity_builder.create_intensity_components(
[
["coherent_J/psi_-1_to_gamma_-1+pi0_0+pi0_0"],
["J/psi_-1_to_f2(1270)_-1+gamma_-1;f2(1270)_-1_to_pi0_0+pi0_0;"],
["J/psi_-1_to_f2(1270)_-2+gamma_-1;f2(1270)_-2_to_pi0_0+pi0_0;"],
["J/psi_-1_to_f2(1270)_0+gamma_-1;f2(1270)_0_to_pi0_0+pi0_0;"],
[
"J/psi_-1_to_f2(1270)_-1+gamma_-1;f2(1270)_-1_to_pi0_0+pi0_0;",
"J/psi_-1_to_f2(1270)_-2+gamma_-1;f2(1270)_-2_to_pi0_0+pi0_0;",
"J/psi_-1_to_f2(1270)_0+gamma_-1;f2(1270)_0_to_pi0_0+pi0_0;",
],
]
)
The last step is to call the actual fit fraction calculation function fit_fractions_with_propagated_errors()
. Here, the first required argument is a list of component pairs. Each pair resembles the nominator and denominator of a fit fraction.
fit_fractions = pwa.fit_fractions_with_propagated_errors(
[
(components[1], components[0]),
(components[2], components[0]),
(components[3], components[0]),
(components[4], components[0]),
],
phsp_set,
fit_result,
)
for fraction in fit_fractions:
print(fraction.name.replace(";", "\n "))
print(fraction.value, "+/-", fraction.error)
print()
J/psi_-1_to_f2(1270)_-1+gamma_-1
f2(1270)_-1_to_pi0_0+pi0_0
/coherent_J/psi_-1_to_gamma_-1+pi0_0+pi0_0
0.005501850830271934 +/- 0.0022183924278350166
J/psi_-1_to_f2(1270)_-2+gamma_-1
f2(1270)_-2_to_pi0_0+pi0_0
/coherent_J/psi_-1_to_gamma_-1+pi0_0+pi0_0
0.012420672414339593 +/- 0.0026534378040259095
J/psi_-1_to_f2(1270)_0+gamma_-1
f2(1270)_0_to_pi0_0+pi0_0
/coherent_J/psi_-1_to_gamma_-1+pi0_0+pi0_0
0.010391539894976263 +/- 0.002190682079425624
J/psi_-1_to_f2(1270)_-1+gamma_-1
f2(1270)_-1_to_pi0_0+pi0_0
_J/psi_-1_to_f2(1270)_-2+gamma_-1
f2(1270)_-2_to_pi0_0+pi0_0
_J/psi_-1_to_f2(1270)_0+gamma_-1
f2(1270)_0_to_pi0_0+pi0_0
/coherent_J/psi_-1_to_gamma_-1+pi0_0+pi0_0
0.028241292333928133 +/- 0.0029959295033270307
That’s it! You can check some of the other examples to learn about more detailed features of ComPWA.
And, please, feel free to provide feedback or contribute ;)