Skip to content

Commit 910bb0c

Browse files
authored
Merge pull request #1615 from abhisrkckl/fdjump-ugly
FDJUMP implementation (Hacky version)
2 parents ef2b0b3 + 6a73c83 commit 910bb0c

8 files changed

+858
-2
lines changed

CHANGELOG-unreleased.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ the released changes.
1818
- Options to add a TZR TOA (`AbsPhase`) during the creation of a `TimingModel` using `ModelBuilder.__call__`, `get_model`, and `get_model_and_toas`
1919
- `pint.print_info()` function for bug reporting
2020
- Added an autocorrelation function to check for chain convergence in `event_optimize`
21+
- A hacky implementation of system-dependent FD parameters (FDJUMP)
2122
- Minor doc updates to explain default NHARMS and missing derivative functions
2223
### Fixed
2324
- Deleting JUMP1 from flag tables will not prevent fitting

src/pint/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from pint.models.solar_system_shapiro import SolarSystemShapiro
3737
from pint.models.solar_wind_dispersion import SolarWindDispersion, SolarWindDispersionX
3838
from pint.models.spindown import Spindown
39+
from pint.models.fdjump import FDJump
3940

4041
# Import the main timing model classes
4142
from pint.models.timing_model import DEFAULT_ORDER, TimingModel

src/pint/models/fdjump.py

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
"""System and frequency dependent delays to model profile evolution."""
2+
3+
import re
4+
from warnings import warn
5+
6+
import astropy.units as u
7+
import numpy as np
8+
9+
from pint.models.parameter import boolParameter, maskParameter
10+
from pint.models.timing_model import DelayComponent
11+
12+
fdjump_max_index = 20
13+
14+
15+
class FDJump(DelayComponent):
16+
"""A timing model for system-dependent frequency evolution of pulsar
17+
profiles.
18+
19+
This model expresses the delay as a polynomial function of the
20+
observing frequency/logarithm of observing frequency in the SSB frame.
21+
This is intended to compensate for the delays introduced by frequency-dependent
22+
profile structure when a different profiles are used for different systems.
23+
24+
The default behavior is to have FDJUMPs as polynomials of the observing
25+
frequency (rather than log-frequency). This is different from the convention
26+
used for global FD parameters. This choice is made to be compatible with tempo2.
27+
This is controlled using the FDJUMPLOG parameter. "FDJUMPLOG Y" may not be
28+
tempo2-compatible.
29+
30+
Note
31+
----
32+
FDJUMPs have two indices: the polynomial/FD/prefix index and the system/mask
33+
index. i.e., they have properties of both maskParameters such as JUMPs and
34+
prefixParameters such as FDs. There is currently no elegant way in PINT to implement
35+
such parameters due to the way parameter indexing is implemented; there is no way to
36+
distinguish between mask and prefix indices.
37+
38+
Hence, they are implemented here as maskParameters as a stopgap measure.
39+
This means that there must be an upper limit for the FD indices. This is controlled
40+
using the `pint.models.fdjump.fdjump_max_index` variable, and is 20 by default.
41+
Note that this is strictly a limitation of the implementation and not a property
42+
of FDJUMPs themselves.
43+
44+
FDJUMPs appear in tempo2-format par files as "FDJUMPp", where p is the FD index.
45+
The mask index is not explicitly mentioned in par files similar to JUMPs.
46+
PINT understands both "FDJUMPp" and "FDpJUMP" as the same parameter in par files,
47+
but the internal representation is always "FDpJUMPq", where q is the mask index.
48+
49+
PINT understands 'q' as the mask parameter just fine, but the identification of 'p'
50+
as the prefix parameter is done in a hacky way.
51+
52+
This implementation may be overhauled in the future.
53+
54+
Parameters supported:
55+
56+
.. paramtable::
57+
:class: pint.models.fdjump.FDJump
58+
"""
59+
60+
register = True
61+
category = "fdjump"
62+
63+
def __init__(self):
64+
super().__init__()
65+
66+
# Matches "FDpJUMPq" where p and q are integers.
67+
self.param_regex = re.compile("^FD(\\d+)JUMP(\\d+)")
68+
69+
self.add_param(
70+
boolParameter(
71+
name="FDJUMPLOG",
72+
value=False,
73+
description="Whether to use log-frequency (Y) or linear-frequency (N) for computing FDJUMPs.",
74+
)
75+
)
76+
for j in range(1, fdjump_max_index + 1):
77+
self.add_param(
78+
maskParameter(
79+
name=f"FD{j}JUMP",
80+
units="second",
81+
description=f"System-dependent FD parameter of polynomial index {j}",
82+
)
83+
)
84+
85+
self.delay_funcs_component += [self.fdjump_delay]
86+
87+
def setup(self):
88+
super().setup()
89+
90+
self.fdjumps = [
91+
mask_par
92+
for mask_par in self.get_params_of_type("maskParameter")
93+
if self.param_regex.match(mask_par)
94+
]
95+
96+
for fdj in self.fdjumps:
97+
# prevents duplicates from being added to phase_deriv_funcs
98+
if fdj in self.deriv_funcs.keys():
99+
del self.deriv_funcs[fdj]
100+
self.register_deriv_funcs(self.d_delay_d_FDJUMP, fdj)
101+
102+
def get_fd_index(self, par):
103+
"""Extract the FD index from an FDJUMP parameter name. In a parameter name
104+
"FDpJUMPq", p is the FD/prefix index and q is the mask index.
105+
106+
Parameters
107+
----------
108+
par: Parameter name (str)
109+
110+
Returns
111+
-------
112+
FD index (int)
113+
"""
114+
if m := self.param_regex.match(par):
115+
return int(m.groups()[0])
116+
else:
117+
raise ValueError(
118+
f"The given parameter {par} does not correspond to an FDJUMP."
119+
)
120+
121+
def get_freq_y(self, toas):
122+
"""Get frequency or log-frequency in GHz based on the FDJUMPLOG value.
123+
Returns (freq/1_GHz) if FDJUMPLOG==N and log(freq/1_GHz) if FDJUMPLOG==Y.
124+
Any non-finite values are replaced by zero.
125+
126+
Parameters
127+
----------
128+
toas: pint.toa.TOAs
129+
130+
Returns
131+
-------
132+
(freq/1_GHz) or log(freq/1_GHz) depending on the value of FDJUMPLOG (float).
133+
"""
134+
tbl = toas.table
135+
try:
136+
freq = self._parent.barycentric_radio_freq(toas)
137+
except AttributeError:
138+
warn("Using topocentric frequency for frequency dependent delay!")
139+
freq = tbl["freq"]
140+
141+
y = (
142+
np.log(freq.to(u.GHz).value)
143+
if self.FDJUMPLOG.value
144+
else freq.to(u.GHz).value
145+
)
146+
non_finite = np.invert(np.isfinite(y))
147+
y[non_finite] = 0.0
148+
149+
return y
150+
151+
def fdjump_delay(self, toas, acc_delay=None):
152+
"""Calculate frequency dependent delay.
153+
154+
If FDJUMPLOG is Y, use the following expression (similar to global FD parameters):
155+
156+
FDJUMP_delay = sum_i(c_i * (log(obs_freq/1GHz))^i)
157+
158+
If FDJUMPLOG is N, use the following expression (same as in tempo2, default):
159+
160+
FDJUMP_delay = sum_i(c_i * (obs_freq/1GHz)^i)
161+
"""
162+
y = self.get_freq_y(toas)
163+
164+
delay = np.zeros_like(y)
165+
for fdjump in self.fdjumps:
166+
fdj = getattr(self, fdjump)
167+
if fdj.quantity is not None:
168+
mask = fdj.select_toa_mask(toas)
169+
ymask = y[mask]
170+
fdidx = self.get_fd_index(fdjump)
171+
fdcoeff = fdj.value
172+
delay[mask] += fdcoeff * ymask**fdidx
173+
174+
return delay * u.s
175+
176+
def d_delay_d_FDJUMP(self, toas, param, acc_delay=None):
177+
"""Derivative of delay w.r.t. FDJUMP parameters."""
178+
assert (
179+
bool(self.param_regex.match(param))
180+
and hasattr(self, param)
181+
and getattr(self, param).quantity is not None
182+
), f"{param} is not present in the FDJUMP model."
183+
184+
y = self.get_freq_y(toas)
185+
mask = getattr(self, param).select_toa_mask(toas)
186+
ymask = y[mask]
187+
fdidx = self.get_fd_index(param)
188+
189+
delay_derivative = np.zeros_like(y)
190+
delay_derivative[mask] = ymask**fdidx
191+
192+
return delay_derivative * u.dimensionless_unscaled
193+
194+
def print_par(self, format="pint"):
195+
par = super().print_par(format)
196+
197+
if format != "tempo2":
198+
return par
199+
200+
for fdjump in self.fdjumps:
201+
if getattr(self, fdjump).quantity is not None:
202+
j = self.get_fd_index(fdjump)
203+
par = par.replace(f"FD{j}JUMP", f"FDJUMP{j}")
204+
205+
return par

src/pint/models/frequency_dependent.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class FD(DelayComponent):
2424

2525
@classmethod
2626
def _description_template(cls, x):
27-
return "%d term of frequency dependent coefficients" % x
27+
return f"{x} term of frequency dependent coefficients"
2828

2929
register = True
3030
category = "frequency_dependent"
@@ -72,7 +72,7 @@ def FD_delay(self, toas, acc_delay=None):
7272
Time Measurements, and Analysis of 37 Millisecond Pulsars, The
7373
Astrophysical Journal, Volume 813, Issue 1, article id. 65, 31 pp.(2015).
7474
Eq.(2):
75-
FDdelay = sum(c_i * (log(obs_freq/1GHz))^i)
75+
FD_delay = sum_i(c_i * (log(obs_freq/1GHz))^i)
7676
"""
7777
tbl = toas.table
7878
try:

src/pint/models/model_builder.py

+28
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pathlib import Path
88
from astropy import units as u
99
from loguru import logger as log
10+
import re
1011

1112
from pint.models.astrometry import Astrometry
1213
from pint.models.parameter import maskParameter
@@ -64,6 +65,28 @@ def parse_parfile(parfile):
6465
return parfile_dict
6566

6667

68+
def _replace_fdjump_in_parfile_dict(pardict):
69+
"""Replace parameter names s of the form "FDJUMPp" by "FDpJUMP"
70+
while reading the par file, where p is the prefix index.
71+
72+
Ideally, this should have been done using the parameter alias
73+
mechanism, but there is no easy way to do this currently due to the
74+
mask and prefix indices being treated in an identical manner.
75+
76+
See :class:`~pint.models.fdjump.FDJump` for more details."""
77+
fdjumpn_regex = re.compile("^FDJUMP(\\d+)")
78+
pardict_new = {}
79+
for key, value in pardict.items():
80+
if m := fdjumpn_regex.match(key):
81+
j = int(m.groups()[0])
82+
new_key = f"FD{j}JUMP"
83+
pardict_new[new_key] = value
84+
else:
85+
pardict_new[key] = value
86+
87+
return pardict_new
88+
89+
6790
class ModelBuilder:
6891
"""Class for building a `TimingModel` object from a parameter file.
6992
@@ -330,6 +353,11 @@ def _pintify_parfile(self, parfile, allow_name_mixing=False):
330353
parfile_dict = parse_parfile(parfile)
331354
else:
332355
parfile_dict = parfile
356+
357+
# This is a special-case-hack to deal with FDJUMP parameters.
358+
# @TODO: Implement a general mechanism to deal with cases like this.
359+
parfile_dict = _replace_fdjump_in_parfile_dict(parfile_dict)
360+
333361
for k, v in parfile_dict.items():
334362
try:
335363
pint_name, init0 = self.all_components.alias_to_pint_param(k)

tests/datafile/J1909-3744.NB.par

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Created: 2023-08-04T12:11:27.791090
2+
# PINT_version: 0.9.6+53.g447cc061.dirty
3+
# User: Abhimanyu S (abhimanyu)
4+
# Host: abhimanyu-HP-Notebook
5+
# OS: Linux-5.15.0-71-generic-x86_64-with-glibc2.35
6+
# Python: 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:58:50)
7+
# [GCC 10.3.0]
8+
# Format: pint
9+
PSRJ J1909-3744
10+
EPHEM DE440
11+
CLK TT(BIPM2019)
12+
UNITS TDB
13+
START 58260.0274418715905556
14+
FINISH 59496.4896411149411921
15+
TIMEEPH FB90
16+
T2CMETHOD IAU2000B
17+
DILATEFREQ N
18+
DMDATA N
19+
NTOA 446
20+
CHI2 13166.811752135152
21+
RAJ 19:09:47.42480160 0 0.00000036670717903709
22+
DECJ -37:44:14.90754000 0 0.00001603708497544348
23+
PMRA -9.51261566578421 0 0.001606518013582412
24+
PMDEC -35.776735309087286 0 0.0057765724888464666
25+
PX 0.881638358140426 0 0.013674560477413309
26+
POSEPOCH 58999.9997541495914071
27+
F0 339.31569192154273734 1 1.2497063444336193814e-12
28+
F1 -1.6123419575084687074e-15 1 4.3048206597047727197e-20
29+
F2 0.0
30+
PEPOCH 58999.9997541495914071
31+
CORRECT_TROPOSPHERE Y
32+
PLANET_SHAPIRO N
33+
NE_SW 0.0
34+
SWM 0.0
35+
DM 10.3912220010111820715 1 5.91828415377580018e-06
36+
DM1 0.000118226481794639765015 1 5.4915807592283010527e-06
37+
DM2 0.0
38+
DMEPOCH 59629.9997443813168694
39+
BINARY ELL1
40+
PB 1.5334494515017690281 0 1.3220826895008462359e-12
41+
PBDOT 5.053209847126312e-13 0 2.521391425976759e-15
42+
A1 1.8979910746557984 0 2.117833848195567e-08
43+
XDOT -3.9210500492434694e-16 0 7.037623119797259e-17
44+
M2 0.2086645878980989 0 0.0012758962708215615
45+
SINI 0.9980748600209708 0 5.844811546142236e-05
46+
TASC 55015.4279066849879101 0 9.73762114761625686e-10
47+
EPS1 2.6338029192577372046e-08 0 1.033583508265e-08
48+
EPS2 -1.0054034660955184656e-07 0 5.72187338209e-09
49+
# ECC 1.0393292586143181096e-07
50+
# OM 165.32038917886206575
51+
TZRMJD 59000.9659140826392014
52+
TZRSITE gmrt
53+
TZRFRQ 1310.097656

0 commit comments

Comments
 (0)