Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
9be4509
fix: correct M_10 definition and change defaults to use new templates
jrs65 Sep 29, 2021
f5036a1
feat(models): add ability for sampler to run in a transformed basis
jrs65 Sep 29, 2021
61799cf
refactor: tidy up class constructors to avoid repetition
jrs65 Sep 23, 2021
5af9cfa
fix: switch default params for new templates
jrs65 Sep 23, 2021
640d9c9
feat: option to explicitly symmetrize templates
jrs65 Sep 23, 2021
aa03d86
feat(signal): option to reverse templates
jrs65 Sep 25, 2021
c2148eb
feat(models): add option to effectively reverse the data
jrs65 Sep 25, 2021
08662c7
feat(signal,models): add some useful debug logging
jrs65 Sep 25, 2021
45fcfcf
fix(mcmc): broken slicing with single polarisation fits
jrs65 Sep 29, 2021
ab54a0e
feat(mcmc): add `flag_ind` option to manually exclude data points
jrs65 Sep 29, 2021
37ae7eb
feat(models): transform to FoG+/FoG- basis when sampling
jrs65 Sep 29, 2021
649017b
style: black
sjforeman Dec 11, 2024
36aaf0a
refactor: fix ruff warnings
sjforeman Dec 11, 2024
eab14af
feat: add code for power spectrum containers and fits
sjforeman Dec 18, 2024
c4e72f4
fix(SignalTemplate): fix to work with updated utils routines
sjforeman Dec 19, 2024
1698d7e
feat: add ability to have models starting from 2d power spectra
sjforeman Dec 20, 2024
a59fe07
feat(models, signal): add FoG-kernel power spectrum model
sjforeman Dec 20, 2024
5b57235
fix(SignalTemplateFoG): fix typo in weights for FoG scale calulation
sjforeman Dec 20, 2024
8be758e
fix(AutoSignalTemplate2DFoG): fix error in variances for scale calcul…
sjforeman Dec 20, 2024
764addb
docs(SignalTemplateFoG): add comments about effective FoG scale
sjforeman Dec 20, 2024
7c20776
feat(RunMCMC): add config property for data_2d
May 16, 2025
e99181d
fix(AutoSignalTemplate2D): set default nbins = 7
May 16, 2025
240ebc8
feat(load_from_ps2Dfiles): modify to load templates based on new file…
May 16, 2025
15b737d
fix(multiply_pre_noncomp): calculate the conversion factor connecting…
May 16, 2025
e60578f
feat(average_data): modify the variance calculation when we have sing…
May 16, 2025
c4a706d
refactor(AutoSignalTemplate2D): change print to logger.debug
sjforeman May 19, 2025
1a3609b
refactor(AutoSignalTemplate2DFoG): use cora definitions of nu21, c
sjforeman May 19, 2025
d848c4a
refactor(average_data): remove commented line
sjforeman May 19, 2025
7bd5f25
fix: update power spectrum container/dataset/axis names
sjforeman May 20, 2025
14efcd1
fix(containers): redefine datasets in Mock containers
sjforeman May 20, 2025
03625d3
fix(mcmc, utils): update for PowerSpectrum pol labels
sjforeman May 20, 2025
10190e5
fix(average_data): set axes and k datasets correctly
sjforeman May 21, 2025
97bfeeb
feat: add force_real option
sjforeman May 21, 2025
81f7c3d
fix(average_data): propagate mask and neff for 2d power spectrum
sjforeman May 23, 2025
157748a
fix(models, signal): fix docstring escape sequence errors
sjforeman May 23, 2025
c9c75e6
feat(Model): add negative_log_likelihood method
sjforeman May 23, 2025
f2f0caa
feat(models): add null model
sjforeman May 23, 2025
c2008ca
fix(AutoSimulationTemplate2Dto1D): remove unused k1D check
sjforeman May 23, 2025
663595a
fix(containers, mcmc, utils): correctly handle datasets without mock …
sjforeman May 24, 2025
21eb14e
feat(mcmc, utils): handle inputs with Stokes parameters
sjforeman May 25, 2025
4aaf13b
feat(run_mcmc): add warning if thinned chain has length zero
sjforeman May 25, 2025
1f08f21
fix(AutoSignalTemplate2D): switch to more general regex for templates
sjforeman May 25, 2025
9e511dc
feat(models, signal): add ability to specify filename glob for templates
sjforeman May 25, 2025
5963516
fix(AutoSignalTemplate2D): fix template directory regex
sjforeman May 25, 2025
a1b1350
fix(run_mcmc): save PowerSpectrum1D k1D to output container
sjforeman May 26, 2025
c17dc7c
feat(run_mcmc): save model/param info in output container
sjforeman May 26, 2025
bb8520a
feat(models, signal): add 2d models that sample in omega^2
sjforeman May 27, 2025
92af6f8
feat(chisq, containers): add code for detection significance computat…
sjforeman Jun 18, 2025
b9982ff
fix(mcmc), style: fix bug in saving pol_fit, and blacken
sjforeman Jun 18, 2025
6dafbea
(feat): add MockPowerSpectrum1D container support from fitstack
Jun 15, 2025
5188a90
(feat): add cached binning scheme for 1D power spectrum template
Jun 15, 2025
7e61bf4
feat(models): allow user to toggle slow 2d-to-1d binning
sjforeman Jun 18, 2025
0a5d758
style: blacken
sjforeman Jun 18, 2025
e517a26
refactor(initialize_pol): remove unused variables
sjforeman Jun 18, 2025
f892961
feat(mcmc, models, signal): add classes for 1d auto templates
sjforeman Jun 27, 2025
3bc8425
fix(AutoSignalTemplate2D): fix typo in FoG kernel
sjforeman Jun 27, 2025
4baca75
feat(priors): add power-law prior
sjforeman Aug 10, 2025
d149f24
feat(models): add Omega*b sampling for power spectrum
sjforeman Aug 10, 2025
1b02afd
fix(get_bounds): correctly handle PowerLaw prior
sjforeman Aug 20, 2025
48c2db8
fix(powerspectrum1d_min_chisq_fit): fix bug when loading chain from file
sjforeman Aug 20, 2025
b410d52
fix(powerspectrum1d_min_chisq_fit): fix issue accessing input spectrum)
sjforeman Aug 20, 2025
aa37c85
fix(powerspectrum1d_min_chisq_fit): fix bug in previous commit
sjforeman Aug 20, 2025
2f04511
fix(powerspectrum1d_min_chisq_fit): fix reference to input container
sjforeman Aug 20, 2025
f12e89d
fix(powerspectrum1d_min_chisq_fit): switch to more stable computation…
sjforeman Aug 22, 2025
205a406
feat(powerspectrum1d_min_chisq_fit): save param0 to output container
sjforeman Aug 22, 2025
4bbc5c5
feat(models): add 1d power spectrum classes that sample in Omega^2
sjforeman Aug 23, 2025
32a7db0
fix(AutoSimulationTemplate2Dto1D_Omega2): fix bug in complexifying om…
sjforeman Aug 23, 2025
213ea97
feat(powerspectrum1d_min_chisq_fit): add option to use mocks from sec…
sjforeman Aug 24, 2025
cb5daa4
refactor(mcmc): factor out code that prepares inputs to MCMC
sjforeman Aug 24, 2025
0d364db
feat(mcmc): add option to apply Hartlap factor to inverse covariance
sjforeman Aug 25, 2025
86d56f6
feat(chisq, containers): add option to start optimizer from several p…
sjforeman Aug 25, 2025
4ab6b10
feat(chisq): add ability to use leave-one-out covariance for each mock
sjforeman Aug 28, 2025
f87d224
feat(chisq): save each optimizer starting point to output container
sjforeman Aug 28, 2025
17a41b8
feat(chisq): add ability to split mocks for cov estimation and fitting
sjforeman Aug 28, 2025
dfcf12d
feat(chisq): add ability to specify seed for extra starts
sjforeman Aug 28, 2025
b621862
fix(AutoSignalTemplate1DFoG): evaluate model at abs value of alphaFoG
sjforeman Sep 7, 2025
a87b70b
refactor, fix(chisq): refactor code for setting up minimizer
sjforeman Sep 7, 2025
39c33cf
fix(AutoSimulationTemplate1D_Omega2): select polarizations correctly …
sjforeman Sep 11, 2025
1c0b7a5
feat(initialize_mcmc_ingredients): save useful quantities as containe…
sjforeman Sep 11, 2025
4907829
feat(chisq): allow starting points for optimizer to be chosen in log(…
sjforeman Sep 11, 2025
3ae3f7d
fix(get_powerspectrum1d_LOO_invcovariance): handle single-pol option …
sjforeman Sep 11, 2025
8817a0d
fix(AutoSignalTemplate1D): fix typo in shot noise template parsing
sjforeman Sep 24, 2025
20de49e
fix(signal): allow for FoGh=1 template to have 1.0 in filename
sjforeman Sep 24, 2025
421637b
feat(chisq): add routine to combine Delta chi^2 results from multiple…
sjforeman Sep 24, 2025
d1548d7
fix(get_powerspectrum1d_LOO_invcovariance): remove pol selection
sjforeman Sep 27, 2025
a4f679b
feat(Model): add amplitude parameter to likelihood evaluation
sjforeman Sep 28, 2025
1cc48e4
feat(stats): add routines for fitting to distributions
sjforeman Sep 28, 2025
0100517
feat(chisq): add more detection significance calculations
sjforeman Sep 28, 2025
3021017
fix(signal): fix shot noise template bugs
sjforeman Sep 28, 2025
46d88f1
fix(chisq): align task properties with routine arguments
sjforeman Sep 28, 2025
d25d6e7
feat(chisq): add option to center mocks on data
sjforeman Oct 3, 2025
130ab9e
docs(compute_MC_calibrated_distribution_test): add details to docstring
sjforeman Oct 3, 2025
3c2b1ab
fix bool
Aug 28, 2025
e2fd2e0
add chaintools
Aug 28, 2025
1cf715f
example notebooks
Aug 29, 2025
f76d74d
Create README
Arnab-half-blood-prince Aug 29, 2025
3f7972b
fix(mcmc): set seed for the sampler
Sep 4, 2025
14ca63f
feat(chisq_test): code that was used for stacking analysis
May 9, 2025
a4e86a5
feat(mcmc): add ability to toggle emcee progress bar
sjforeman Oct 4, 2025
a159e1e
fix(powerspectrum_min_chisq_fit): center mocks on best-fit signal model
sjforeman Oct 6, 2025
1ef4911
feat(chisq): add property for tolerance of minimizer
sjforeman Oct 6, 2025
e42528a
feat(compute_MC_calibrated_distribution_test): add bootstrap and ndof…
sjforeman Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
714 changes: 714 additions & 0 deletions fitstack/chaintools.py

Large diffs are not rendered by default.

1,002 changes: 1,002 additions & 0 deletions fitstack/chisq.py

Large diffs are not rendered by default.

339 changes: 339 additions & 0 deletions fitstack/chisq_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
"""Routines for chi^2 tests of stacking measurements.

Used for CHIME-eBOSS stacking analysis in arXiv:2202.01242.
"""

import logging
import inspect

import numpy as np
import scipy.optimize

from caput import config, pipeline

from draco.core import task

from . import containers
from . import utils
from . import models
from . import priors

# Set up logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())


def _all_subclasses(cls):
return set(cls.__subclasses__()).union(
[s for c in cls.__subclasses__() for s in _all_subclasses(c)]
)


SIMULATION_MODELS = [
c.__name__
for c in [models.SimulationTemplate]
+ list(_all_subclasses(models.SimulationTemplate))
]

BOUNDED_MINIMIZATION = ["L-BFGS-B", "Nelder-Mead", "Powell", "TNC"]


def get_param0(fit, param_to_fit):

chisq = fit["chisq"][:]
chain = fit["chain"][:]
param = list(fit.index_map["param"][:])

imin = np.argmin(np.abs(chisq.flatten()))
theta_min = chain.reshape(-1, chain.shape[-1])[imin]

ifit = np.array([param.index(pfit) for pfit in param_to_fit])

return theta_min[ifit]


def get_bounds(mdl, scale_bound=0.0):

param = mdl.param_name_fit
nparam = len(param)

lb = np.zeros(nparam, dtype=np.float64)
ub = np.zeros(nparam, dtype=np.float64)

for nn, name in enumerate(param):

prior = mdl.priors[name]

if isinstance(prior, priors.Uniform):
lb[nn] = prior.low
ub[nn] = prior.high
else:
lb[nn] = prior.loc - 5.0 * prior.scale
ub[nn] = prior.loc + 5.0 * prior.scale

if scale_bound > 0.0:
db = ub[nn] - lb[nn]
lb[nn], ub[nn] = lb[nn] - scale_bound * db, ub[nn] + scale_bound * db

return scipy.optimize.Bounds(lb, ub)


def chisq_test(
restricted,
unrestricted,
model_kwargs=None,
param_spec=None,
method="L-BFGS-B",
options=None,
scale_bound=0.0,
required_pol=None,
):

if required_pol is None:
required_pol = ["XX", "YY"]

if model_kwargs is None:
model_kwargs = {"restricted": {}, "unrestricted": {}}

if param_spec is None:
param_spec = {"restricted": {}, "unrestricted": {}}

if options is None:
options = {}

fr = containers.MCMCFit1D.from_file(utils.find_file(restricted))
fu = containers.MCMCFit1D.from_file(utils.find_file(unrestricted))

pol_sel = np.array([list(fr.pol).index(pstr) for pstr in required_pol])

for key in ["restricted", "unrestricted"]:
model_kwargs[key]["pol"] = ["XX", "YY"]
model_kwargs[key]["combine"] = False
model_kwargs[key]["sort"] = True

fit_kwargs = {}
fit_kwargs["freq"] = fr.freq
fit_kwargs["data"] = fr.stack[pol_sel]
fit_kwargs["inv_cov"] = fr["precision"][:]
fit_kwargs["transfer"] = None

eval_kwargs_r = {"freq": fr.freq, "transfer": None}

# Prepare the model
namer = fr.attrs["model"]
Modelr = getattr(models, namer)
modelr = Modelr(**{**model_kwargs["restricted"], **param_spec["restricted"]})

nameu = fu.attrs["model"]
Modelu = getattr(models, nameu)
modelu = Modelu(**{**model_kwargs["unrestricted"], **param_spec["unrestricted"]})

# Specialized fit keywords
fit_kwargs_r = {key: val for key, val in fit_kwargs.items()}
if (
(namer in SIMULATION_MODELS)
and ("DualPol" not in namer)
and ("Split" not in namer)
):
fit_kwargs_r["pol_sel"] = pol_sel
eval_kwargs_r["pol_sel"] = pol_sel

fit_kwargs_u = {key: val for key, val in fit_kwargs.items()}
if (
(nameu in SIMULATION_MODELS)
and ("DualPol" not in nameu)
and ("Split" not in nameu)
):
fit_kwargs_u["pol_sel"] = pol_sel

# Construct data realizations
mr = fr["mock"][:, pol_sel]

nmock, npol, nfreq = mr.shape

# Create output container
out = containers.ChisqTest(
mock=np.arange(nmock, dtype=np.int),
restricted_param=np.array(modelr.param_name_fit),
unrestricted_param=np.array(modelu.param_name_fit),
)

# Fit the restricted model
# --------------------------
modelr.set_data(**fit_kwargs_r)

if modelr.nfit > 0:

# Determine the starting point and parameter bounds
param0r = get_param0(fr, modelr.param_name_fit)
nparamr = param0r.size
if method in BOUNDED_MINIMIZATION:
boundr = get_bounds(modelr, scale_bound=scale_bound)
else:
boundr = None

resdr = scipy.optimize.minimize(
modelr.negative_log_likelihood,
param0r,
method=method,
bounds=boundr,
options=options,
)

out.attrs["restricted_success"] = resdr.success
out.attrs["restricted_chisq"] = 2.0 * modelr.negative_log_likelihood(resdr.x)
out.attrs["restricted_param"] = resdr.x

thetar = modelr.get_all_params(resdr.x)

out.add_dataset("restricted_param")
paramr = out["restricted_param"][:].view(np.ndarray)

else:

out.attrs["restricted_success"] = True
out.attrs["restricted_chisq"] = 2.0 * modelr.negative_log_likelihood([])

thetar = modelr.get_all_params([])

yr = modelr.model(thetar, **eval_kwargs_r)[np.newaxis, ...] + mr

# Fit the unrestricted model
# --------------------------
modelu.set_data(**fit_kwargs_u)

# Determine the starting point and parameter bounds
param0u = get_param0(fu, modelu.param_name_fit)
nparamu = param0u.size
if method in BOUNDED_MINIMIZATION:
boundu = get_bounds(modelu, scale_bound=scale_bound)
else:
boundu = None

resdu = scipy.optimize.minimize(
modelu.negative_log_likelihood,
param0u,
method=method,
bounds=boundu,
options=options,
)

out.attrs["unrestricted_success"] = resdu.success
out.attrs["unrestricted_chisq"] = 2.0 * modelu.negative_log_likelihood(resdu.x)
out.attrs["unrestricted_param"] = resdu.x

# Dereference datasets
successr = out["restricted_success"][:].view(np.ndarray)
chisqr = out["restricted_chisq"][:].view(np.ndarray)

successu = out["unrestricted_success"][:].view(np.ndarray)
paramu = out["unrestricted_param"][:].view(np.ndarray)
chisqu = out["unrestricted_chisq"][:].view(np.ndarray)

# Loop over mocks
for mm in range(nmock):

# Print progress update to log
if (mm % 100) == 0:
logger.info(f"Fitting data realization {mm} of {nmock}.")

# Fit restricted model
fit_kwargs_r["data"] = yr[mm]
modelr.set_data(**fit_kwargs_r)

if modelr.nfit > 0:
resr = scipy.optimize.minimize(
modelr.negative_log_likelihood,
param0r,
method=method,
bounds=boundr,
options=options,
)

successr[mm] = resr.success
paramr[mm] = resr.x
chisqr[mm] = 2.0 * modelr.negative_log_likelihood(resr.x)

else:
successr[mm] = True
chisqr[mm] = 2.0 * modelr.negative_log_likelihood(modelr.default_values)

# Fit unrestricted model
fit_kwargs_u["data"] = yr[mm]
modelu.set_data(**fit_kwargs_u)

resu = scipy.optimize.minimize(
modelu.negative_log_likelihood,
param0u,
method=method,
bounds=boundu,
options=options,
)

successu[mm] = resu.success
paramu[mm] = resu.x
chisqu[mm] = 2.0 * modelu.negative_log_likelihood(resu.x)

# Return the output container
return out


class ChisqTest(task.SingleTask):
"""Pipeline task that calls the chisq_test function.

Enables the user to call the chisq_test method with caput-pipeline,
which provides many useful features including profiling, job script
generation, job templating, and saving the results to disk.

Attributes
----------
max_iter : int
Number of times to call the chisq_test method.
Defaults to 1.

See the arguments of the chisq_test method for a list of
additional attributes and their default values.
"""

max_iter = config.Property(proptype=int, default=1)

restricted = config.Property(proptype=str)
unrestricted = config.Property(proptype=str)
param_spec = config.Property(proptype=dict)
model_kwargs = config.Property(proptype=dict)
options = config.Property(proptype=dict)
method = config.Property(proptype=str)
scale_bound = config.Property(proptype=float)
required_pol = config.Property(proptype=list)

def setup(self):
"""Prepare all arguments to the chisq_test function."""

# Use the default values from the chisq_test method,
# so we do not have to repeat them in two places.
signature = inspect.signature(chisq_test)
defaults = {
k: v.default if v.default is not inspect.Parameter.empty else None
for k, v in signature.parameters.items()
}

self.kwargs = {}
for key, default_val in defaults.items():
if hasattr(self, key):
prop_val = getattr(self, key)
self.kwargs[key] = prop_val if prop_val is not None else default_val
else:
self.log.warning(
"ChisqTest does not have a property corresponding "
f"to the {key} keyword argument to chisq_test."
)

def process(self):
"""Fit a model to the source stack using an MCMC."""

if self._count == self.max_iter:
raise pipeline.PipelineStopIteration

result = chisq_test(**self.kwargs)

return result
Loading