Source code for GWForge.population_fisher.compare
"""Fit a population block to the injections and report what came back."""
import logging
import numpy
from .fit import compare_to, fit_model
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
)
# How each block's fit should be read.
[docs]
BLOCK_ROLE = {
"redshift": (
"fiducial (the generator convolves psi_MD with a time delay, so the "
"generation values are not this model's fiducials)"
),
"mass": "consistency check (should return the generation values)",
"spin": "consistency check (should return the generation values)",
}
[docs]
def compare_block(model, block, injections, free_parameters=None):
"""Fit one block to the injections and format the comparison.
Parameters
----------
model : GWForge.population_fisher.base.PopulationModel
Fitted **in place**: on return ``model.fiducial`` holds the fitted
values, so the Fisher that follows is evaluated at them.
block : str
``"mass"``, ``"redshift"`` or ``"spin"``.
injections : dict
Every injection, detected or not.
free_parameters : sequence of str or None
Vary only these, holding the rest at their config values. Defaults to
every parameter the model has.
The driver passes the *Fisher's* free list, so the fit varies exactly
what the forecast will vary. That matters when a parameter is pinned
because the data cannot constrain it: ``delta_m`` for a taper narrower
than the sampling grid has no maximum to find, so an optimiser given it
rails against whichever bound it was handed and drags the correlated
parameters with it. Since the catalogue was *generated* here, such
parameters are known exactly and there is nothing to fit.
Returns
-------
list of str
Lines for the summary file.
"""
generated = dict(model.fiducial)
count = len(numpy.atleast_1d(injections[model.event_keys[0]]))
if free_parameters is not None:
free_parameters = [
name for name in free_parameters if name in model.parameter_names
]
if not free_parameters:
free_parameters = None
logging.info(
"Fitting the %s block to %d injections over %s",
block,
count,
free_parameters if free_parameters is not None else "all parameters",
)
fitted = fit_model(model, injections, free_parameters=free_parameters)
for name in model.parameter_names:
model.fiducial[name] = float(fitted[name])
varied = free_parameters or model.parameter_names
held = [name for name in model.parameter_names if name not in varied]
lines = [
"{} block fit to {} injections -- {}".format(
block, count, BLOCK_ROLE.get(block, "")
),
compare_to(fitted, generated, varied),
" mean log p = {:.6f} over {} samples{}".format(
fitted["_mean_log_prob"],
fitted["_n_samples"],
"" if fitted["_success"] else " (optimiser: {})".format(fitted["_message"]),
),
]
if held:
lines.append(
" held at their config values: "
+ ", ".join("{}={:g}".format(name, generated[name]) for name in held)
)
if fitted["_n_dropped"]:
lines.append(
" WARNING: {} sample(s) were outside the model's support and were "
"dropped from the fit.".format(fitted["_n_dropped"])
)
return lines