Source code for jesterTOV.inference.likelihoods.rex
# TODO: still needs to be implemented. For now, raises NotImplementedError
r"""
PREX and CREX neutron skin measurement constraints.
This module implements likelihood functions based on the PREX (Lead Radius
Experiment) and CREX (Calcium Radius Experiment) measurements of neutron skin
thickness in heavy nuclei. These experiments use parity-violating electron
scattering to measure the weak charge distribution, which is sensitive to the
neutron skin and thus constrains the symmetry energy parameters of the nuclear
equation of state.
The measurements are particularly sensitive to the symmetry energy E_sym and
its density slope L_sym, providing independent constraints that complement
astrophysical observations of neutron stars. The likelihood is implemented
using a kernel density estimate (KDE) of the experimental posterior in
(E_sym, L_sym) space.
References
----------
.. [1] Adhikari et al., "Precision Determination of the Neutral Weak Form Factor
of Pb-208," Phys. Rev. Lett. 126, 172502 (2021).
.. [2] Adhikari et al., "Accurate Determination of the Neutron Skin Thickness of
Ca-48 through Parity-Violation in Electron Scattering," Phys. Rev. Lett. 129,
042501 (2022).
"""
from typing import Any
import jax.numpy as jnp
from jaxtyping import Array, Float
from jesterTOV.inference.base import LikelihoodBase
[docs]
class REXLikelihood(LikelihoodBase):
"""Likelihood function for PREX or CREX neutron skin measurements.
This likelihood constrains the symmetry energy parameters (E_sym, L_sym)
using experimental measurements of neutron skin thickness from parity-violating
electron scattering. The experimental data is represented as a kernel density
estimate (KDE) of the posterior distribution in (E_sym, L_sym) parameter space,
which is then evaluated for each candidate EOS.
The neutron skin thickness is primarily sensitive to the pressure of neutron
matter at subsaturation densities, which is controlled by E_sym (symmetry
energy at saturation) and L_sym (its density slope). These experiments provide
complementary information to astrophysical mass-radius measurements, as they
probe different density regimes and physical processes.
Parameters
----------
experiment_name : str
Name of the experiment providing the constraint. Must be either "PREX"
(Lead Radius Experiment, Pb-208) or "CREX" (Calcium Radius Experiment,
Ca-48). The two experiments probe different mass regions and have
different systematic uncertainties.
posterior : Any
Kernel density estimate of the experimental posterior distribution in
(E_sym, L_sym) parameter space. This should be a callable object with
a `logpdf` method that accepts a 2D array of shape (2, n_samples) and
returns log-probability values. Typically constructed using
scipy.stats.gaussian_kde or similar.
Attributes
----------
experiment_name : str
The experiment name ("PREX" or "CREX")
counter : int
Internal counter tracking the number of likelihood evaluations.
Used for debugging and performance monitoring.
posterior : Any
The KDE object representing the experimental posterior
Raises
------
AssertionError
If experiment_name is not "PREX" or "CREX"
Notes
-----
The likelihood evaluation extracts only the E_sym and L_sym parameters from
the full parameter dictionary and evaluates the KDE at that point. Other
nuclear parameters (K_sat, Q_sat, etc.) do not directly enter this likelihood,
though they affect the overall EOS and may have indirect correlations.
The KDE is evaluated in log-space to avoid numerical underflow for low-probability
regions and to match the log-likelihood framework used throughout the inference.
See Also
--------
ChiEFTLikelihood : Low-density constraints from chiral effective field theory
Examples
--------
Create a PREX likelihood with a pre-computed KDE:
>>> from scipy.stats import gaussian_kde
>>> import numpy as np
>>> # Load PREX posterior samples (example)
>>> samples = np.load("prex_samples.npy") # shape: (2, n_samples) for (E_sym, L_sym)
>>> kde = gaussian_kde(samples)
>>> from jesterTOV.inference.likelihoods import REXLikelihood
>>> likelihood = REXLikelihood("PREX", kde)
>>> params = {"E_sym": 32.0, "L_sym": 60.0}
>>> log_like = likelihood.evaluate(params, data={})
"""
experiment_name: str
counter: int
posterior: Any # gaussian_kde type
[docs]
def __init__(
self,
experiment_name: str,
posterior: Any,
) -> None:
super().__init__()
assert experiment_name in [
"PREX",
"CREX",
], "Only PREX and CREX are supported as experiment name arguments."
self.experiment_name = experiment_name
self.counter = 0
self.posterior = posterior
[docs]
def evaluate(self, params: dict[str, Float | Array]) -> Float:
"""Evaluate the log-likelihood for PREX/CREX constraints.
Parameters
----------
params : dict[str, Float | Array]
Dictionary containing EOS parameters. Required keys:
- "E_sym" : Symmetry energy at saturation density (MeV)
- "L_sym" : Slope of symmetry energy (MeV)
Other parameters in the dict are ignored.
Returns
-------
Float
Natural logarithm of the likelihood. This is the log-probability
density of the (E_sym, L_sym) point under the experimental posterior
KDE.
Notes
-----
The method extracts E_sym and L_sym from the parameter dictionary,
constructs a 2D array [E_sym, L_sym], and evaluates the KDE's logpdf
method. The result is a scalar log-probability suitable for combination
with other log-likelihoods in the inference pipeline.
"""
log_likelihood_array = self.posterior.logpdf(
jnp.array([params["E_sym"], params["L_sym"]])
)
log_likelihood = log_likelihood_array.at[0].get()
return log_likelihood