-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Complex phsp factor produces nonsensical intensity #235
Comments
Hi @sebastianJaeger, thanks for your report. I adapted your code snippet a bit so that it has exactly one resonance and extended it so that it generates momenta. (Note: it's not necessary to generate a hit-and-miss data sample to view the intensity distribution.) Extended code snippetRunning on cfca442 and ComPWA/tensorwaves@e422bce: import ampform
import matplotlib.pyplot as plt
import numpy as np
import qrules
from ampform.dynamics import PhaseSpaceFactorComplex
from ampform.dynamics.builder import RelativisticBreitWignerBuilder
from matplotlib import cm
from tensorwaves.data import (
SympyDataTransformer,
TFPhaseSpaceGenerator,
TFUniformRealNumberGenerator,
)
from tensorwaves.function.sympy import create_parametrized_function
# Generate model
reaction = qrules.generate_transitions(
initial_state=("J/psi(1S)", [-1, +1]),
final_state=["p", "p~", "eta"],
allowed_intermediate_particles=[
"N(1440)+",
],
allowed_interaction_types=["strong", "EM"],
formalism="canonical-helicity",
mass_conservation_factor=1,
)
model_builder = ampform.get_builder(reaction)
breit_wigner_builder = RelativisticBreitWignerBuilder(
form_factor=True, phsp_factor=PhaseSpaceFactorComplex
)
for name in reaction.get_intermediate_particles().names:
model_builder.set_dynamics(name, breit_wigner_builder)
model = model_builder.formulate()
# Generate phase space sample
rng = TFUniformRealNumberGenerator(seed=0)
phsp_generator = TFPhaseSpaceGenerator(
initial_state_mass=reaction.initial_state[-1].mass,
final_state_masses={
i: p.mass for i, p in reaction.final_state.items()
},
)
phsp_momenta = phsp_generator.generate(1_000_000, rng)
helicity_transformer = SympyDataTransformer.from_sympy(
model.kinematic_variables, backend="jax"
)
phsp = helicity_transformer(phsp_momenta)
# Visualize intensity distribution
unfolded_expression = model.expression.doit()
intensity_func = create_parametrized_function(
expression=unfolded_expression,
parameters=model.parameter_defaults,
backend="jax",
)
fig, ax = plt.subplots(figsize=(9, 4))
intensities = intensity_func(phsp)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(intensities),
bins=200,
alpha=0.5,
density=True,
)
ax.set_yscale("log")
ax.set_xlabel(R"$m_{p\eta}$ [GeV]")
resonances = sorted(
reaction.get_intermediate_particles(),
key=lambda p: p.mass,
)
evenly_spaced_interval = np.linspace(0, 1, len(resonances))
colors = [cm.rainbow(x) for x in evenly_spaced_interval]
for p, color in zip(resonances, colors):
ax.axvline(x=p.mass, linestyle="dotted", label=p.name, color=color)
ax.legend()
plt.show() Initially, I thought that something was wrong with the new Breit-Wigner builder (#206), but if the same script is run on a resonance above threshold (where Snippet to generate resonance above thresholdreaction = qrules.generate_transitions(
initial_state=("J/psi(1S)", [-1, +1]),
final_state=["gamma", "pi0", "pi0"],
allowed_intermediate_particles=["f(0)(980)"],
allowed_interaction_types=["strong", "EM"],
) My guess is that this phase space factor simply does not make sense for decay products with unequal masses (here: N(1440)⁺ → proton eta), see #151. |
Another observation: the peak seems to originate from the form factor. Same code as in #235 (comment) with Versions: cfca442 and ComPWA/tensorwaves@e422bce %config InlineBackend.figure_formats = ['svg']
import ampform
import matplotlib.pyplot as plt
import numpy as np
import qrules
from ampform.dynamics import PhaseSpaceFactorComplex
from ampform.dynamics.builder import RelativisticBreitWignerBuilder
from matplotlib import cm
from tensorwaves.data import (
SympyDataTransformer,
TFPhaseSpaceGenerator,
TFUniformRealNumberGenerator,
)
from tensorwaves.function.sympy import create_parametrized_function
# Generate model
reaction = qrules.generate_transitions(
initial_state=("J/psi(1S)", [-1, +1]),
final_state=["p", "p~", "eta"],
allowed_intermediate_particles=["N(1440)+"],
allowed_interaction_types=["strong", "EM"],
mass_conservation_factor=1,
)
model_builder = ampform.get_builder(reaction)
breit_wigner_builder = RelativisticBreitWignerBuilder(
form_factor=False,
phsp_factor=PhaseSpaceFactorComplex,
)
for name in reaction.get_intermediate_particles().names:
model_builder.set_dynamics(name, breit_wigner_builder)
model = model_builder.formulate()
# Generate phase space sample
rng = TFUniformRealNumberGenerator(seed=0)
phsp_generator = TFPhaseSpaceGenerator(
initial_state_mass=reaction.initial_state[-1].mass,
final_state_masses={i: p.mass for i, p in reaction.final_state.items()},
)
phsp_momenta = phsp_generator.generate(1_000_000, rng)
helicity_transformer = SympyDataTransformer.from_sympy(
model.kinematic_variables, backend="jax"
)
phsp = helicity_transformer(phsp_momenta)
# Visualize intensity distribution
unfolded_expression = model.expression.doit()
intensity_func = create_parametrized_function(
expression=unfolded_expression,
parameters=model.parameter_defaults,
backend="jax",
)
fig, ax = plt.subplots(figsize=(9, 4))
intensities = intensity_func(phsp)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(intensities),
bins=200,
alpha=0.5,
density=True,
)
ax.set_yscale("log")
ax.set_xlabel(R"$m_{p\eta}$ [GeV]")
resonances = sorted(
reaction.get_intermediate_particles(),
key=lambda p: p.mass,
)
evenly_spaced_interval = np.linspace(0, 1, len(resonances))
colors = [cm.rainbow(x) for x in evenly_spaced_interval]
for p, color in zip(resonances, colors):
ax.axvline(x=p.mass, linestyle="dotted", label=p.name, color=color)
ax.legend()
plt.show() This seems to be a bug, though, because setting model.expression.args[0].args[0].args[0].args[0].doit() |
Okay, I take that back.
Source codeNote: this code could be simplified with scalar masses, which turns the final state masses into parameters. pip install \
git+https://github.com/ComPWA/ampform@3ed3ed5 \
"tensorwaves[jax,pwa] @ git+https://github.com/ComPWA/tensorwaves@bea8cd9" # %config InlineBackend.figure_formats = ['svg']
import ampform
import attrs
import matplotlib.pyplot as plt
import numpy as np
import qrules
from ampform.dynamics import PhaseSpaceFactorComplex
from ampform.dynamics.builder import RelativisticBreitWignerBuilder
from matplotlib import cm
from tensorwaves.data import (
SympyDataTransformer,
TFPhaseSpaceGenerator,
TFUniformRealNumberGenerator,
)
from tensorwaves.function.sympy import create_parametrized_function
# Modify particle masses
PDG = qrules.load_pdg()
eta_mass = PDG["eta"].mass
proton_mass = PDG["eta"].mass
resonance_mass = 0.97 * (proton_mass + eta_mass)
particle_set = set(PDG)
particle = PDG["p"]
modified_particle = attrs.evolve(particle, mass=proton_mass)
particle_set.remove(particle)
particle_set.add(modified_particle)
particle = PDG["N(1440)+"]
modified_particle = attrs.evolve(particle, mass=resonance_mass)
particle_set.remove(particle)
particle_set.add(modified_particle)
PDG = qrules.ParticleCollection(particle_set)
# Create amplitude model
reaction = qrules.generate_transitions(
initial_state=("J/psi(1S)", [+1]),
final_state=["p", "p~", "eta"],
allowed_intermediate_particles=["N(1440)+"],
allowed_interaction_types=["strong", "EM"],
particle_db=PDG,
mass_conservation_factor=1,
# max_angular_momentum=1, # filter out L=2
)
assert len(reaction.get_intermediate_particles()) == 1
model_builder = ampform.get_builder(reaction)
breit_wigner_builder = RelativisticBreitWignerBuilder(
phsp_factor=PhaseSpaceFactorComplex
)
for name in reaction.get_intermediate_particles().names:
model_builder.set_dynamics(name, breit_wigner_builder)
breit_wigner_builder.energy_dependent_width = False
breit_wigner_builder.form_factor = False
model_standard_bw = model_builder.formulate()
breit_wigner_builder.energy_dependent_width = False
breit_wigner_builder.form_factor = True
model_standard_bw_with_ff = model_builder.formulate()
breit_wigner_builder.energy_dependent_width = True
breit_wigner_builder.form_factor = False
model_energy_dependent = model_builder.formulate()
breit_wigner_builder.energy_dependent_width = True
breit_wigner_builder.form_factor = True
model_energy_dependent_with_ff = model_builder.formulate()
# Generate phase space sample
rng = TFUniformRealNumberGenerator(seed=0)
phsp_generator = TFPhaseSpaceGenerator(
initial_state_mass=reaction.initial_state[-1].mass,
final_state_masses={i: p.mass for i, p in reaction.final_state.items()},
)
phsp_momenta = phsp_generator.generate(1_000_000, rng)
helicity_transformer = SympyDataTransformer.from_sympy(
model_standard_bw.kinematic_variables, backend="jax"
)
phsp = helicity_transformer(phsp_momenta)
phsp = {k: v.real for k, v in phsp.items()} # important for sqrt of Blatt-Weisskopf!
# Generate intensity distributions
func_standard_bw = create_parametrized_function(
expression=model_standard_bw.expression.doit(),
parameters=model_standard_bw.parameter_defaults,
backend="jax",
)
func_standard_bw_with_ff = create_parametrized_function(
expression=model_standard_bw_with_ff.expression.doit(),
parameters=model_standard_bw_with_ff.parameter_defaults,
backend="jax",
)
func_energy_dependent = create_parametrized_function(
expression=model_energy_dependent.expression.doit(),
parameters=model_energy_dependent.parameter_defaults,
backend="jax",
)
func_energy_dependent_with_ff = create_parametrized_function(
expression=model_energy_dependent_with_ff.expression.doit(),
parameters=model_energy_dependent_with_ff.parameter_defaults,
backend="jax",
)
# Plot 'em!
fig, ax = plt.subplots(figsize=(9, 4))
hist_kwargs = dict(
bins=200,
histtype="step",
density=True,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_standard_bw(phsp)),
label="Standard Breit-Wigner",
**hist_kwargs,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_standard_bw_with_ff(phsp)),
label="Standard Breit-Wigner with form factor",
linestyle="dotted",
**hist_kwargs,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_energy_dependent(phsp)),
label="Energy-dependent Breit-Wigner",
**hist_kwargs,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_energy_dependent_with_ff(phsp)),
label="Energy-dependent Breit-Wigner with form factor",
linestyle="dotted",
**hist_kwargs,
)
ax.set_yscale("log")
ax.set_xlabel(R"$m_{p\eta}$ [GeV]")
resonances = sorted(
reaction.get_intermediate_particles(),
key=lambda p: p.mass,
)
evenly_spaced_interval = np.linspace(0, 1, len(resonances))
colors = [cm.rainbow(x) for x in evenly_spaced_interval]
for p, color in zip(resonances, colors):
ax.axvline(x=p.mass, linestyle="dotted", label=p.name, color=color)
ax.legend()
plt.show() N(1440)⁺ mass at 97% of proton+eta massN(1440)⁺ mass at 99% of proton+eta massN(1440)⁺ mass at 95% of 2x eta mass (proton mass set to eta mass) |
@sebastianJaeger, it may be worth looking into the new Resonances section of the PDG. Existing implementation is based on PDG 2020. For the energy dependent width, compare in particular: See also existing implementation/formula for |
This problem is probably fixed through #265: the narrow peak is probably an artifact of the phase space factor behaviour below m₁-m₂. Figure below shows the phase space factors that were implemented as of posting this bug report. Chew-Mandelstam (lower right) displays the proper behaviour below m₁-m₂ and was implemented in #265. |
Sub-threshold Breit-Wigner comparison form this page, without phase space.Note that, technically speaking, one should compute the phase space factor from the dispersion integral (TR-003) in the case of higher angular momenta (here: L=1), but using this phase space factor for S-waves at least ensures better behaviour of the phase space factor below threshold. Modified snippet of #235 (comment) Source codepip install ampform==0.14.0 tensorwaves[jax,pwa]==0.4.4 # %config InlineBackend.figure_formats = ['svg']
import ampform
import attrs
import matplotlib.pyplot as plt
import numpy as np
import qrules
from ampform.dynamics import PhaseSpaceFactorSWave
from ampform.dynamics.builder import RelativisticBreitWignerBuilder
from matplotlib import cm
from tensorwaves.data import (
SympyDataTransformer,
TFPhaseSpaceGenerator,
TFUniformRealNumberGenerator,
)
from tensorwaves.function.sympy import create_parametrized_function
# Modify particle masses
PDG = qrules.load_pdg()
eta_mass = PDG["eta"].mass
proton_mass = PDG["eta"].mass
resonance_mass = 0.97 * (proton_mass + eta_mass)
particle_set = set(PDG)
particle = PDG["p"]
modified_particle = attrs.evolve(particle, mass=proton_mass)
particle_set.remove(particle)
particle_set.add(modified_particle)
particle = PDG["N(1440)+"]
modified_particle = attrs.evolve(particle, mass=resonance_mass)
particle_set.remove(particle)
particle_set.add(modified_particle)
PDG = qrules.ParticleCollection(particle_set)
# Create amplitude model
reaction = qrules.generate_transitions(
initial_state=("J/psi(1S)", [+1]),
final_state=["p", "p~", "eta"],
allowed_intermediate_particles=["N(1440)+"],
allowed_interaction_types=["strong", "EM"],
particle_db=PDG,
mass_conservation_factor=1,
)
assert len(reaction.get_intermediate_particles()) == 1
model_builder = ampform.get_builder(reaction)
model_builder.scalar_initial_state_mass = True
model_builder.stable_final_state_ids = [0, 1, 2]
breit_wigner_builder = RelativisticBreitWignerBuilder(
phsp_factor=PhaseSpaceFactorSWave # <-- fixes the problem
)
for name in reaction.get_intermediate_particles().names:
model_builder.set_dynamics(name, breit_wigner_builder)
breit_wigner_builder.energy_dependent_width = False
breit_wigner_builder.form_factor = False
model_standard_bw = model_builder.formulate()
breit_wigner_builder.energy_dependent_width = False
breit_wigner_builder.form_factor = True
model_standard_bw_with_ff = model_builder.formulate()
breit_wigner_builder.energy_dependent_width = True
breit_wigner_builder.form_factor = False
model_energy_dependent = model_builder.formulate()
breit_wigner_builder.energy_dependent_width = True
breit_wigner_builder.form_factor = True
model_energy_dependent_with_ff = model_builder.formulate()
# Generate phase space sample
rng = TFUniformRealNumberGenerator(seed=0)
phsp_generator = TFPhaseSpaceGenerator(
initial_state_mass=reaction.initial_state[-1].mass,
final_state_masses={i: p.mass for i, p in reaction.final_state.items()},
)
phsp_momenta = phsp_generator.generate(1_000_000, rng)
helicity_transformer = SympyDataTransformer.from_sympy(
model_standard_bw.kinematic_variables, backend="jax"
)
phsp = helicity_transformer(phsp_momenta)
phsp = {k: v.real for k, v in phsp.items()} # important for sqrt of Blatt-Weisskopf!
# Generate intensity distributions
func_standard_bw = create_parametrized_function(
expression=model_standard_bw.expression.doit(),
parameters=model_standard_bw.parameter_defaults,
backend="jax",
)
func_standard_bw_with_ff = create_parametrized_function(
expression=model_standard_bw_with_ff.expression.doit(),
parameters=model_standard_bw_with_ff.parameter_defaults,
backend="jax",
)
func_energy_dependent = create_parametrized_function(
expression=model_energy_dependent.expression.doit(),
parameters=model_energy_dependent.parameter_defaults,
backend="jax",
)
func_energy_dependent_with_ff = create_parametrized_function(
expression=model_energy_dependent_with_ff.expression.doit(),
parameters=model_energy_dependent_with_ff.parameter_defaults,
backend="jax",
)
# Plot 'em!
fig, ax = plt.subplots(figsize=(9, 4))
hist_kwargs = dict(
bins=200,
histtype="step",
density=True,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_standard_bw(phsp)),
label="Standard Breit-Wigner",
**hist_kwargs,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_standard_bw_with_ff(phsp)),
label="Standard Breit-Wigner with form factor",
linestyle="dotted",
**hist_kwargs,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_energy_dependent(phsp)),
label="Energy-dependent Breit-Wigner",
**hist_kwargs,
)
ax.hist(
np.array(phsp["m_02"].real),
weights=np.array(func_energy_dependent_with_ff(phsp)),
label="Energy-dependent Breit-Wigner with form factor",
linestyle="dotted",
**hist_kwargs,
)
ax.set_yscale("log")
ax.set_xlabel(R"$m_{p\eta}$ [GeV]")
resonances = sorted(
reaction.get_intermediate_particles(),
key=lambda p: p.mass,
)
evenly_spaced_interval = np.linspace(0, 1, len(resonances))
colors = [cm.rainbow(x) for x in evenly_spaced_interval]
for p, color in zip(resonances, colors):
ax.axvline(x=p.mass, linestyle="dotted", label=p.name, color=color)
ax.legend()
plt.show() N(1440)⁺ mass set to 97% of p+η massN(1440)⁺ mass at 95% of 2x eta mass (proton mass set to eta mass) |
|
Let me add to the discussion.
|
Thanks for your feedback!
The analysis will for now remain an efficiency study, so it was decided that a normal BW suffices for now just to describe the distribution (without the resonance positions necessarily making sense).
Yes, you mentioned. From an internal note: Your recommendations can be addressed once the analysis can move to an actual PWA stage. |
Just one more practical thing:
|
If the complex phasespace factor is used to model a resonance below the kinematic threshold, the resulting intensity has an extremely high and sharp peak at a (seemingly) random position within the kinematic borders. A minimal working example to generate a model with this problem is:
Attached is a simple plot of the distribution of events generated with this model.
Complex_PHSP_Factor_example.pdf
The text was updated successfully, but these errors were encountered: