/
understanding_fitters.py
219 lines (176 loc) · 7.36 KB
/
understanding_fitters.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# -*- coding: utf-8 -*-
# ---
# jupyter:
# jupytext:
# formats: ipynb,py:percent
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.14.4
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
# name: python3
# ---
# %% [markdown]
# # Understanding Fitters
#
#
# %%
from IPython.display import display_markdown
import pint.toa
import pint.models
import pint.fitter
import pint.config
import pint.logging
pint.logging.setup(level="INFO")
# %%
# %matplotlib inline
import matplotlib.pyplot as plt
# Turn on quantity support for plotting. This is very helpful!
from astropy.visualization import quantity_support
quantity_support()
# %%
# Load some TOAs and a model to fit
m, t = pint.models.get_model_and_toas(
pint.config.examplefile("NGC6440E.par"), pint.config.examplefile("NGC6440E.tim")
)
# %%
# You can check if a model includes a noise model with correlated errors (e.g. ECORR or TNRED) by checking the has_correlated_errors property
m.has_correlated_errors
# %% [markdown]
# There are several fitters in PINT, each of which is a subclass of `Fitter`
#
# * `DownhillWLSFitter` - PINT's workhorse fitter, which does a basic weighted least-squares minimization of the residuals.
# * `DownhillGLSFitter` - A generalized least squares fitter, like "tempo -G", that can handle noise processes like ECORR and red noise that are specified by their correlation function properties.
# * `WidebandDownhillFitter` - A fitter that uses DM estimates associated with each TOA. Also supports generalized least squares.
# * `PowellFitter` - A very simple example fitter that uses the Powell method implemented in scipy. One notable feature is that it does not require evaluating derivatives w.r.t the model parameters.
# * `MCMCFitter` - A fitter that does an MCMC fit using the [emcee](https://emcee.readthedocs.io/en/stable/) package. This can be very slow, but accomodates Priors on the parameter values and can produce corner plots and other analyses of the posterior distributions of the parameters.
# * `WLSFitter`, `GLSFitter`, `WidebandFitter` - Simpler fitters that make no attempt to ensure convergence.
#
# You can normally use the function `pint.fitter.Fitter.auto(toas, model)` to construct an appropriate fitter for your model and data.
#
# %% [markdown]
# ## Weighted Least Squares Fitter
# %%
# Instantiate a fitter
wlsfit = pint.fitter.DownhillWLSFitter(toas=t, model=m)
# %% [markdown]
# A fit is performed by calling `fit_toas()`
#
# For most fitters, multiple iterations can be limited by setting the `maxiter` keyword argument.
#
# Downhill fitters will raise the `pint.fitter.MaxiterReached` exception if they stop before detecting convergence; you can capture this exception and continue if you don't mind not having the best-fit answer.
# %%
try:
wlsfit.fit_toas(maxiter=1)
except pint.fitter.MaxiterReached:
print("Fitter has not fully converged.")
# %%
# A summary of the fit and resulting model parameters can easily be printed
# Only free parameters will have values and uncertainties in the Postfit column
wlsfit.print_summary()
# %%
# The WLS fitter doesn't handle correlated errors
wlsfit.resids.model.has_correlated_errors
# %%
# You can request a pretty-printed covariance matrix
cov = wlsfit.get_parameter_covariance_matrix(pretty_print=True)
# %%
# plot() will make a plot of the post-fit residuals
wlsfit.plot()
# %% [markdown]
#
# ## Comparing models
#
# There also a convenience function for pretty printing a comparison of two models with the differences measured in sigma.
# %%
display_markdown(wlsfit.model.compare(wlsfit.model_init, format="markdown"), raw=True)
# %% [markdown]
# You can see just how much F1 changed. Let's compare the $\chi^2$ values:
# %%
print(f"Pre-fit chi-squared value: {wlsfit.resids_init.chi2}")
print(f"Post-fit chi-squared value: {wlsfit.resids.chi2}")
# %% [markdown]
# ## Generalized Least Squares fitter
#
# The GLS fitter is capable of handling correlated noise models.
#
# It has some more complex options using the `maxiter`, `threshold`, and `full_cov` keyword arguments to `fit_toas()`.
#
# If `maxiter` is less than one, **no fitting is done**, just the
# chi-squared computation. In this case, you must provide the `residuals`
# argument.
#
# If `maxiter` is one or more, so fitting is actually done, the
# chi-squared value returned is only approximately the chi-squared
# of the improved(?) model. In fact it is the chi-squared of the
# solution to the linear fitting problem, and the full non-linear
# model should be evaluated and new residuals produced if an accurate
# chi-squared is desired.
#
# A first attempt is made to solve the fitting problem by Cholesky
# decomposition, but if this fails singular value decomposition is
# used instead. In this case singular values below threshold are removed.
#
# `full_cov` determines which calculation is used. If True, the full
# covariance matrix is constructed and the calculation is relatively
# straightforward but the full covariance matrix may be enormous.
# If False, an algorithm is used that takes advantage of the structure
# of the covariance matrix, based on information provided by the noise
# model. The two algorithms should give the same result up to numerical
# accuracy where they both can be applied.
# %% [markdown]
# To test this fitter properly, we need a model that includes correlated noise components, so we will load one from NANOGrav 9yr data release.
# %%
m1855 = pint.models.get_model(pint.config.examplefile("B1855+09_NANOGrav_9yv1.gls.par"))
# %%
# You can check if a model includes a noise model with correlated errors (e.g. ECORR or TNRED) by checking the has_correlated_errors property
m1855.has_correlated_errors
# %%
print(m1855)
# %%
ts1855 = pint.toa.get_TOAs(
pint.config.examplefile("B1855+09_NANOGrav_9yv1.tim"), model=m1855
)
ts1855.print_summary()
# %% [markdown]
# There is currently a problem with `DownhillGLSFitter`: it doesn't record appropriate noise parameters.
# %%
glsfit = pint.fitter.GLSFitter(toas=ts1855, model=m1855)
# %%
m1855.DMX_0001.prefix
# %%
glsfit.fit_toas(maxiter=1)
# %%
glsfit.print_summary()
# %% [markdown]
# The GLS fitter produces two types of residuals, the normal residuals to the deterministic model and those from the noise model.
# %%
glsfit.resids.time_resids
# %%
glsfit.resids.noise_resids
# %%
# Here we can plot both the residuals to the deterministic model as well as the realization of the noise model residuals
# The difference will be the "whitened" residuals
fig, ax = plt.subplots(figsize=(16, 9))
mjds = glsfit.toas.get_mjds()
ax.plot(mjds, glsfit.resids.time_resids, ".")
ax.plot(mjds, glsfit.resids.noise_resids["pl_red_noise"], ".")
# %% [markdown]
# ## Choosing fitters
#
# You can use the automatic fitter selection to help you choose between `WLSFitter`, `GLSFitter`, and their wideband variants.
# The default `Downhill` fitters generally have better performance than the plain variants.
# %%
autofit = pint.fitter.Fitter.auto(toas=ts1855, model=m1855)
# %%
autofit.fit_toas()
# %%
display_markdown(autofit.model.compare(glsfit.model, format="markdown"), raw=True)
# %% [markdown]
# The results are (thankfully) identical.
# %% [markdown]
# The MCMC fitter is considerably more complicated, so it has its own dedicated walkthroughs in `MCMC_walkthrough.ipynb`
# (for photon data) and `examples/fit_NGC6440E_MCMC.py` (for fitting TOAs).