From 742b8dfc6c196f5af1087d51053fe2b827f1b793 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 17:20:50 -0600 Subject: [PATCH 01/51] allow_tcb option --- src/pint/models/model_builder.py | 20 ++++++++++++++------ src/pint/models/timing_model.py | 24 +++++++++++++----------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index a90383f85..bac097fe5 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -73,7 +73,7 @@ def __init__(self): self._validate_components() self.default_components = ["SolarSystemShapiro"] - def __call__(self, parfile, allow_name_mixing=False): + def __call__(self, parfile, allow_name_mixing=False, allow_tcb=False): """Callable object for making a timing model from .par file. Parameters @@ -103,7 +103,14 @@ def __call__(self, parfile, allow_name_mixing=False): # Make timing model cps = [self.all_components.components[c] for c in selected] tm = TimingModel(components=cps) - self._setup_model(tm, pint_param_dict, original_name, setup=True, validate=True) + self._setup_model( + tm, + pint_param_dict, + original_name, + setup=True, + validate=True, + allow_tcb=allow_tcb, + ) # Report unknown line for k, v in unknown_param.items(): p_line = " ".join([k] + v) @@ -418,6 +425,7 @@ def _setup_model( original_name=None, setup=True, validate=True, + allow_tcb=False, ): """Fill up a timing model with parameter values and then setup the model. @@ -529,7 +537,7 @@ def _setup_model( if setup: timing_model.setup() if validate: - timing_model.validate() + timing_model.validate(allow_tcb=allow_tcb) return timing_model def _report_conflict(self, conflict_graph): @@ -543,7 +551,7 @@ def _report_conflict(self, conflict_graph): ) -def get_model(parfile, allow_name_mixing=False): +def get_model(parfile, allow_name_mixing=False, allow_tcb=False): """A one step function to build model from a parfile. Parameters @@ -570,11 +578,11 @@ def get_model(parfile, allow_name_mixing=False): # # parfile is a filename and can be handled by ModelBuilder # if _model_builder is None: # _model_builder = ModelBuilder() - model = model_builder(parfile, allow_name_mixing) + model = model_builder(parfile, allow_name_mixing, allow_tcb=allow_tcb) model.name = parfile return model else: - tm = model_builder(StringIO(contents), allow_name_mixing) + tm = model_builder(StringIO(contents), allow_name_mixing, allow_tcb=allow_tcb) return tm diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 55d0d2473..57fa231d2 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -359,7 +359,7 @@ def __repr__(self): def __str__(self): return self.as_parfile() - def validate(self): + def validate(self, allow_tcb=False): """Validate component setup. The checks include required parameters and parameter values. @@ -373,17 +373,19 @@ def validate(self): if self.T2CMETHOD.value not in [None, "IAU2000B"]: # FIXME: really? warn("PINT only supports 'T2CMETHOD IAU2000B'") self.T2CMETHOD.value = "IAU2000B" - if self.UNITS.value not in [None, "TDB"]: - if self.UNITS.value == "TCB": - error_message = """The TCB timescale is not supported by PINT. (PINT only supports 'UNITS TDB'.) - See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales for an explanation - on different timescales. The par file can be converted from TCB to TDB using the `transform` - plugin of TEMPO2 like so: - $ tempo2 -gr transform J1234+6789_tcb.par J1234+6789_tdb.par tdb - """ - else: - error_message = f"PINT only supports 'UNITS TDB'. The given timescale '{self.UNITS.value}' is invalid." + + if self.UNITS.value not in [None, "TDB", "TCB"]: + error_message = f"PINT only supports 'UNITS TDB'. The given timescale '{self.UNITS.value}' is invalid." + raise ValueError(error_message) + elif self.UNITS.value == "TCB" and not allow_tcb: + error_message = """The TCB timescale is not supported by PINT. (PINT only supports 'UNITS TDB'.) + See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales for an explanation + on different timescales. The par file can be converted from TCB to TDB using the `transform` + plugin of TEMPO2 like so: + $ tempo2 -gr transform J1234+6789_tcb.par J1234+6789_tdb.par tdb + """ raise ValueError(error_message) + if not self.START.frozen: warn("START cannot be unfrozen...setting START.frozen to True") self.START.frozen = True From 2e961ebed4e8f1d756d486b7cb0eca61017ea911 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 17:34:03 -0600 Subject: [PATCH 02/51] improved error message --- src/pint/models/timing_model.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 57fa231d2..453768f42 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -378,11 +378,13 @@ def validate(self, allow_tcb=False): error_message = f"PINT only supports 'UNITS TDB'. The given timescale '{self.UNITS.value}' is invalid." raise ValueError(error_message) elif self.UNITS.value == "TCB" and not allow_tcb: - error_message = """The TCB timescale is not supported by PINT. (PINT only supports 'UNITS TDB'.) - See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales for an explanation - on different timescales. The par file can be converted from TCB to TDB using the `transform` - plugin of TEMPO2 like so: - $ tempo2 -gr transform J1234+6789_tcb.par J1234+6789_tdb.par tdb + error_message = """The TCB timescale is not fully supported by PINT. PINT only supports 'UNITS TDB' + internally. See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales for an + explanation on different timescales. A TCB par file can be converted to TDB upon read using the + `allow_tcb` option. However, this conversion is not exact and a fit must be performed to obtain + reliable results. Note that PINT only supports writing TDB par files. A TCB par file can also be + converted to TDB using the `tcb2tdb` command like so: + $ tcb2tdb J1234+6789_tcb.par J1234+6789_tdb.par """ raise ValueError(error_message) From aabd2b877e09caaf79b5714e49b042e2040f8d0a Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 17:34:16 -0600 Subject: [PATCH 03/51] IFTE constants --- src/pint/models/tcb_conversion.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/pint/models/tcb_conversion.py diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py new file mode 100644 index 000000000..f2e878697 --- /dev/null +++ b/src/pint/models/tcb_conversion.py @@ -0,0 +1,8 @@ +import numpy as np + +# These constants are taken from Irwin & Fukushima 1999. +# These are the same as the constants used in tempo2 as of 10 Feb 2023. +IFTE_MJD0 = np.longdouble("43144.0003725") +IFTE_KM1 = np.longdouble("1.55051979176e-8") +IFTE_K = 1 + IFTE_KM1 +IFTE_F = 1/(1 + IFTE_KM1) \ No newline at end of file From 199cce74b96132a2d0abfe02b48d4900d760cdf3 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 18:45:04 -0600 Subject: [PATCH 04/51] tcb conversion code --- src/pint/models/tcb_conversion.py | 79 ++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index f2e878697..bf3bd306a 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -5,4 +5,81 @@ IFTE_MJD0 = np.longdouble("43144.0003725") IFTE_KM1 = np.longdouble("1.55051979176e-8") IFTE_K = 1 + IFTE_KM1 -IFTE_F = 1/(1 + IFTE_KM1) \ No newline at end of file + + +def scale_parameter(model, param, n): + factor = IFTE_K**n + + if hasattr(model, param) and getattr(model, param).quantity is not None: + par = getattr(model, param) + par.value *= factor + if par.uncertainty_value is not None: + par.uncertainty_value *= factor + + +def transform_mjd_parameter(model, param): + factor = 1/IFTE_K + tref = IFTE_MJD0 + + if hasattr(model, param) and getattr(model, param).quantity is not None: + par = getattr(model, param) + par.value = (par.value - tref)*factor + tref + if par.uncertainty_value is not None: + par.uncertainty_value *= factor + +def convert_tcb_to_tdb(model): + """This function performs a partial conversion of a model + specified in TCB to TDB. While this should be sufficient as + a starting point, the resulting parameters are only approximate + and the accompanying TOAs should be re-fit. + + The following parameters are converted to TCB: + 1. Spin frequency, its derivatives and spin epoch + 2. Sky coordinates, proper motion and the position epoch + 3. Keplerian binary parameters + + The following parameters are NOT converted which are in fact + affected by the TCB to TDB conversion: + 1. Parallax + 2. TZRMJD and TZRFRQ + 2. DM, DM derivatives, DM epoch, DMX parameters + 3. Solar wind parameters + 4. Binary post-Keplerian parameters including Shapiro delay + parameters + 5. Jumps + 6. FD parameters + 7. EQUADs + 8. Red noise parameters including WAVE, powerlaw red noise and + powerlaw DM noise parameters + """ + + if model.UNITS in ["TDB", None]: + # ~ issue warning here ~ + return + + if "Spindown" in model.components: + for Fn_par in model.components["Spindown"].F_terms: + n = int(Fn_par[1:]) + scale_parameter(model, Fn_par, n+1) + + transform_mjd_parameter(model, "PEPOCH") + + if "AstrometryEquatorial" in model.components: + scale_parameter(model, "PMRA", IFTE_K) + scale_parameter(model, "PMDEC", IFTE_K) + transform_mjd_parameter(model, "POSEPOCH") + elif "AstrometryEcliptic" in model.components: + scale_parameter(model, "PMELAT", IFTE_K) + scale_parameter(model, "PMELONG", IFTE_K) + transform_mjd_parameter(model, "POSEPOCH") + + if hasattr(model, "BINARY") and hasattr(model, "BINARY").value is not None: + transform_mjd_parameter(model, "T0") + transform_mjd_parameter(model, "TASC") + scale_parameter(model, "PB", -1) + scale_parameter(model, "FB0", 1) + scale_parameter(model, "A1", -1) + + model.UNITS = "TDB" + + model.validate() \ No newline at end of file From 717ac2461309d01be86fbea1a4c078dac9d4c13c Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 19:04:24 -0600 Subject: [PATCH 05/51] conversion script --- src/pint/models/tcb_conversion.py | 10 ++++++---- src/pint/scripts/tcb2tdb.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/pint/scripts/tcb2tdb.py diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index bf3bd306a..2740e972a 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -33,23 +33,25 @@ def convert_tcb_to_tdb(model): a starting point, the resulting parameters are only approximate and the accompanying TOAs should be re-fit. + This is based on the `transform` plugin of tempo2. + The following parameters are converted to TCB: 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch 3. Keplerian binary parameters - The following parameters are NOT converted which are in fact - affected by the TCB to TDB conversion: + The following parameters are NOT converted although they are + in fact affected by the TCB to TDB conversion: 1. Parallax 2. TZRMJD and TZRFRQ 2. DM, DM derivatives, DM epoch, DMX parameters 3. Solar wind parameters 4. Binary post-Keplerian parameters including Shapiro delay parameters - 5. Jumps + 5. Jumps and DM Jumps 6. FD parameters 7. EQUADs - 8. Red noise parameters including WAVE, powerlaw red noise and + 8. Red noise parameters including FITWAVES, powerlaw red noise and powerlaw DM noise parameters """ diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py new file mode 100644 index 000000000..121fcbbcb --- /dev/null +++ b/src/pint/scripts/tcb2tdb.py @@ -0,0 +1,25 @@ +from loguru import logger as log + +import pint.logging +from pint.models.tcb_conversion import convert_tcb_to_tdb +from pint.models import get_model + +import argparse + +pint.logging.setup(level=pint.logging.script_level) + +def main(argv): + parser = argparse.ArgumentParser( + description="PINT tool for converting TCB par files to TBD.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument("input_par", help="Input par file name (TCB)") + parser.add_argument("output_par", help="Output par file name (TDB)") + + args = parser.parse_args(argv) + + model = get_model(args.input_par, allow_tcb=True) + convert_tcb_to_tdb(model) + model.write_parfile(args.output_par) + + log.info(f"Output written to {args.output_par}") \ No newline at end of file From 8480903972550d6e3272d8521e23a81de35a0399 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 19:14:19 -0600 Subject: [PATCH 06/51] warning --- src/pint/models/model_builder.py | 4 +++- src/pint/models/timing_model.py | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index bac097fe5..825913d6c 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -116,6 +116,7 @@ def __call__(self, parfile, allow_name_mixing=False, allow_tcb=False): p_line = " ".join([k] + v) warnings.warn(f"Unrecognized parfile line '{p_line}'", UserWarning) # log.warning(f"Unrecognized parfile line '{p_line}'") + return tm def _validate_components(self): @@ -600,6 +601,7 @@ def get_model_and_toas( picklefilename=None, allow_name_mixing=False, limits="warn", + allow_tcb=False, ): """Load a timing model and a related TOAs, using model commands as needed @@ -643,7 +645,7 @@ def get_model_and_toas( ------- A tuple with (model instance, TOAs instance) """ - mm = get_model(parfile, allow_name_mixing) + mm = get_model(parfile, allow_name_mixing, allow_tcb=allow_tcb) tt = get_TOAs( timfile, include_pn=include_pn, diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 453768f42..985fb57c0 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -387,7 +387,12 @@ def validate(self, allow_tcb=False): $ tcb2tdb J1234+6789_tcb.par J1234+6789_tdb.par """ raise ValueError(error_message) - + elif allow_tcb and self.UNITS.value == "TCB": + log.warning( + "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless" + "because the `allow_tcb` option was given. This `TimingModel` object should not be" + "used for anything except converting to TDB." + ) if not self.START.frozen: warn("START cannot be unfrozen...setting START.frozen to True") self.START.frozen = True From 8fe36818a55d656580db404dfd746ef9e71f7c58 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 19:16:35 -0600 Subject: [PATCH 07/51] setup --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index c62af7a06..80a0c6408 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,7 @@ console_scripts = pintk = pint.scripts.pintk:main convert_parfile = pint.scripts.convert_parfile:main compare_parfiles = pint.scripts.compare_parfiles:main + tcb2tdb = pint.scripts.tcb2tdb:main # See the docstring in versioneer.py for instructions. Note that you must From 0f0c03eafd2fe1a741f01ffef87b1f6c7ac13762 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 19:21:51 -0600 Subject: [PATCH 08/51] welcome message --- src/pint/scripts/tcb2tdb.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index 121fcbbcb..a377d4d46 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -18,8 +18,33 @@ def main(argv): args = parser.parse_args(argv) + welcome_message = """This script converts TCB par files to TDB. + Please note that this conversion is exact and the timing model + should be re-fit to the TOAs. + + The following parameters are converted to TCB: + 1. Spin frequency, its derivatives and spin epoch + 2. Sky coordinates, proper motion and the position epoch + 3. Keplerian binary parameters + + The following parameters are NOT converted although they are + in fact affected by the TCB to TDB conversion: + 1. Parallax + 2. TZRMJD and TZRFRQ + 2. DM, DM derivatives, DM epoch, DMX parameters + 3. Solar wind parameters + 4. Binary post-Keplerian parameters including Shapiro delay + parameters + 5. Jumps and DM Jumps + 6. FD parameters + 7. EQUADs + 8. Red noise parameters including FITWAVES, powerlaw red noise and + powerlaw DM noise parameters + """ + log.info(welcome_message) + model = get_model(args.input_par, allow_tcb=True) convert_tcb_to_tdb(model) model.write_parfile(args.output_par) - log.info(f"Output written to {args.output_par}") \ No newline at end of file + log.info(f"Output written to {args.output_par}.") \ No newline at end of file From 58677bcaaf9da85d497a159a6c1f4da7f5ff5bd8 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 19:22:54 -0600 Subject: [PATCH 09/51] typo --- src/pint/models/tcb_conversion.py | 2 +- src/pint/scripts/tcb2tdb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 2740e972a..27b240979 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -35,7 +35,7 @@ def convert_tcb_to_tdb(model): This is based on the `transform` plugin of tempo2. - The following parameters are converted to TCB: + The following parameters are converted to TDB: 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch 3. Keplerian binary parameters diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index a377d4d46..bfec229a2 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -22,7 +22,7 @@ def main(argv): Please note that this conversion is exact and the timing model should be re-fit to the TOAs. - The following parameters are converted to TCB: + The following parameters are converted to TDB: 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch 3. Keplerian binary parameters From 2a638b2fcd7ce13254edc189d634363db6708c62 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 10 Feb 2023 19:23:48 -0600 Subject: [PATCH 10/51] black --- src/pint/models/model_builder.py | 2 +- src/pint/models/tcb_conversion.py | 21 +++++++++++---------- src/pint/models/timing_model.py | 4 ++-- src/pint/scripts/tcb2tdb.py | 3 ++- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index 825913d6c..96a0e802b 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -116,7 +116,7 @@ def __call__(self, parfile, allow_name_mixing=False, allow_tcb=False): p_line = " ".join([k] + v) warnings.warn(f"Unrecognized parfile line '{p_line}'", UserWarning) # log.warning(f"Unrecognized parfile line '{p_line}'") - + return tm def _validate_components(self): diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 27b240979..7dd0602bf 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -9,7 +9,7 @@ def scale_parameter(model, param, n): factor = IFTE_K**n - + if hasattr(model, param) and getattr(model, param).quantity is not None: par = getattr(model, param) par.value *= factor @@ -18,15 +18,16 @@ def scale_parameter(model, param, n): def transform_mjd_parameter(model, param): - factor = 1/IFTE_K + factor = 1 / IFTE_K tref = IFTE_MJD0 - + if hasattr(model, param) and getattr(model, param).quantity is not None: par = getattr(model, param) - par.value = (par.value - tref)*factor + tref + par.value = (par.value - tref) * factor + tref if par.uncertainty_value is not None: par.uncertainty_value *= factor + def convert_tcb_to_tdb(model): """This function performs a partial conversion of a model specified in TCB to TDB. While this should be sufficient as @@ -39,19 +40,19 @@ def convert_tcb_to_tdb(model): 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch 3. Keplerian binary parameters - - The following parameters are NOT converted although they are + + The following parameters are NOT converted although they are in fact affected by the TCB to TDB conversion: 1. Parallax 2. TZRMJD and TZRFRQ 2. DM, DM derivatives, DM epoch, DMX parameters 3. Solar wind parameters - 4. Binary post-Keplerian parameters including Shapiro delay + 4. Binary post-Keplerian parameters including Shapiro delay parameters 5. Jumps and DM Jumps 6. FD parameters 7. EQUADs - 8. Red noise parameters including FITWAVES, powerlaw red noise and + 8. Red noise parameters including FITWAVES, powerlaw red noise and powerlaw DM noise parameters """ @@ -62,7 +63,7 @@ def convert_tcb_to_tdb(model): if "Spindown" in model.components: for Fn_par in model.components["Spindown"].F_terms: n = int(Fn_par[1:]) - scale_parameter(model, Fn_par, n+1) + scale_parameter(model, Fn_par, n + 1) transform_mjd_parameter(model, "PEPOCH") @@ -84,4 +85,4 @@ def convert_tcb_to_tdb(model): model.UNITS = "TDB" - model.validate() \ No newline at end of file + model.validate() diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 985fb57c0..71748a8fb 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -373,7 +373,7 @@ def validate(self, allow_tcb=False): if self.T2CMETHOD.value not in [None, "IAU2000B"]: # FIXME: really? warn("PINT only supports 'T2CMETHOD IAU2000B'") self.T2CMETHOD.value = "IAU2000B" - + if self.UNITS.value not in [None, "TDB", "TCB"]: error_message = f"PINT only supports 'UNITS TDB'. The given timescale '{self.UNITS.value}' is invalid." raise ValueError(error_message) @@ -389,7 +389,7 @@ def validate(self, allow_tcb=False): raise ValueError(error_message) elif allow_tcb and self.UNITS.value == "TCB": log.warning( - "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless" + "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless" "because the `allow_tcb` option was given. This `TimingModel` object should not be" "used for anything except converting to TDB." ) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index bfec229a2..4535fcb0f 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -8,6 +8,7 @@ pint.logging.setup(level=pint.logging.script_level) + def main(argv): parser = argparse.ArgumentParser( description="PINT tool for converting TCB par files to TBD.", @@ -47,4 +48,4 @@ def main(argv): convert_tcb_to_tdb(model) model.write_parfile(args.output_par) - log.info(f"Output written to {args.output_par}.") \ No newline at end of file + log.info(f"Output written to {args.output_par}.") From 3ddcc31a47382ada28af2768c01aeaca45e4597b Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 10:00:39 -0600 Subject: [PATCH 11/51] sourcery --- src/pint/models/solar_system_shapiro.py | 6 +-- src/pint/models/timing_model.py | 54 +++++++++---------------- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/pint/models/solar_system_shapiro.py b/src/pint/models/solar_system_shapiro.py index c06d5a366..1cd850065 100644 --- a/src/pint/models/solar_system_shapiro.py +++ b/src/pint/models/solar_system_shapiro.py @@ -111,13 +111,13 @@ def solar_system_shapiro_delay(self, toas, acc_delay=None): if self.PLANET_SHAPIRO.value: for pl in ("jupiter", "saturn", "venus", "uranus", "neptune"): delay[grp] += self.ss_obj_shapiro_delay( - tbl[grp]["obs_" + pl + "_pos"], + tbl[grp][f"obs_{pl}_pos"], psr_dir, self._ss_mass_sec[pl], ) - except KeyError: + except KeyError as e: raise KeyError( "Planet positions not found when trying to compute Solar System Shapiro delay. " "Make sure that you include `planets=True` in your `get_TOAs()` call, or use `get_model_and_toas()`." - ) + ) from e return delay * u.second diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 71748a8fb..a8dc88a3d 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -447,34 +447,30 @@ def params_ordered(self): used_cats = set() pstart = copy.copy(self.top_level_params) for cat in start_order: - if cat in compdict: - cp = compdict[cat] - for cpp in cp: - pstart += cpp.params - used_cats.add(cat) - else: + if cat not in compdict: continue - + cp = compdict[cat] + for cpp in cp: + pstart += cpp.params + used_cats.add(cat) pend = [] for cat in last_order: - if cat in compdict: - cp = compdict[cat] - for cpp in cp: - pend += cpp.parms - used_cats.add(cat) - else: + if cat not in compdict: continue + cp = compdict[cat] + for cpp in cp: + pend += cpp.parms + used_cats.add(cat) # Now collect any components that haven't already been included in the list pmid = [] for cat in compdict: if cat in used_cats: continue - else: - cp = compdict[cat] - for cpp in cp: - pmid += cpp.params - used_cats.add(cat) + cp = compdict[cat] + for cpp in cp: + pmid += cpp.params + used_cats.add(cat) return pstart + pmid + pend @@ -1467,13 +1463,9 @@ def jump_flags_to_params(self, toas): if tjv in tim_jump_values: log.info(f"JUMP -tim_jump {tjv} already exists") tim_jump_values.remove(tjv) - if used_indices: - num = max(used_indices) + 1 - else: - num = 1 - + num = max(used_indices) + 1 if used_indices else 1 if not tim_jump_values: - log.info(f"All tim_jump values have corresponding JUMPs") + log.info("All tim_jump values have corresponding JUMPs") return # FIXME: arrange for these to be in a sensible order (might not be integers @@ -1519,7 +1511,7 @@ def delete_jump_and_flags(self, toa_table, jump_num): Specifies the index of the jump to be deleted. """ # remove jump of specified index - self.remove_param("JUMP" + str(jump_num)) + self.remove_param(f"JUMP{jump_num}") # remove jump flags from selected TOA tables if toa_table is not None: @@ -1691,10 +1683,7 @@ def d_phase_d_param_num(self, toas, param, step=1e-2): par = getattr(self, param) ori_value = par.value unit = par.units - if ori_value == 0: - h = 1.0 * step - else: - h = ori_value * step + h = 1.0 * step if ori_value == 0 else ori_value * step parv = [par.value - h, par.value + h] phase_i = ( @@ -1725,13 +1714,10 @@ def d_delay_d_param_num(self, toas, param, step=1e-2): ori_value = par.value if ori_value is None: # A parameter did not get to use in the model - log.warning("Parameter '%s' is not used by timing model." % param) + log.warning(f"Parameter '{param}' is not used by timing model.") return np.zeros(toas.ntoas) * (u.second / par.units) unit = par.units - if ori_value == 0: - h = 1.0 * step - else: - h = ori_value * step + h = 1.0 * step if ori_value == 0 else ori_value * step parv = [par.value - h, par.value + h] delay = np.zeros((toas.ntoas, 2)) for ii, val in enumerate(parv): From 9764280aaffb996d05275107fd83ac5cada0a4a4 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 10:28:13 -0600 Subject: [PATCH 12/51] message --- src/pint/models/timing_model.py | 45 +++++++++++++++++---------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index a8dc88a3d..a12846820 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -26,6 +26,8 @@ See :ref:`Timing Models` for more details on how PINT's timing models work. """ + + import abc import copy import inspect @@ -86,22 +88,19 @@ # errors in the par file. # # Comparisons with keywords in par file lines is done in a case insensitive way. -ignore_params = set( - [ - "TRES", - "TZRMJD", - "TZRFRQ", - "TZRSITE", - "NITS", - "IBOOT", - "CHI2R", - "MODE", - "PLANET_SHAPIRO2", - # 'NE_SW', 'NE_SW2', - ] -) - -ignore_prefix = set(["DMXF1_", "DMXF2_", "DMXEP_"]) # DMXEP_ for now. +ignore_params = { + "TRES", + "TZRMJD", + "TZRFRQ", + "TZRSITE", + "NITS", + "IBOOT", + "CHI2R", + "MODE", + "PLANET_SHAPIRO2", +} + +ignore_prefix = {"DMXF1_", "DMXF2_", "DMXEP_"} DEFAULT_ORDER = [ "astrometry", @@ -378,13 +377,15 @@ def validate(self, allow_tcb=False): error_message = f"PINT only supports 'UNITS TDB'. The given timescale '{self.UNITS.value}' is invalid." raise ValueError(error_message) elif self.UNITS.value == "TCB" and not allow_tcb: - error_message = """The TCB timescale is not fully supported by PINT. PINT only supports 'UNITS TDB' - internally. See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales for an - explanation on different timescales. A TCB par file can be converted to TDB upon read using the - `allow_tcb` option. However, this conversion is not exact and a fit must be performed to obtain - reliable results. Note that PINT only supports writing TDB par files. A TCB par file can also be + error_message = """The TCB timescale is not fully supported by PINT. + PINT only supports 'UNITS TDB' internally. See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales + for an explanation on different timescales. A TCB par file can be converted to TDB using the `tcb2tdb` command like so: + $ tcb2tdb J1234+6789_tcb.par J1234+6789_tdb.par + + However, this conversion is not exact and a fit must be performed to obtain + reliable results. Note that PINT only supports writing TDB par files. """ raise ValueError(error_message) elif allow_tcb and self.UNITS.value == "TCB": @@ -703,7 +704,7 @@ def orbital_phase(self, barytimes, anom="mean", radians=True): elif anom.lower() == "true": anoms = bbi.nu() # can be negative else: - raise ValueError("anom='%s' is not a recognized type of anomaly" % anom) + raise ValueError(f"anom='{anom}' is not a recognized type of anomaly") # Make sure all angles are between 0-2*pi anoms = np.remainder(anoms.value, 2 * np.pi) if radians: # return with radian units From 88d60ba17246a271f548d89a90605e9e2b80e093 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 10:33:42 -0600 Subject: [PATCH 13/51] script works. --- src/pint/models/tcb_conversion.py | 6 +++--- src/pint/scripts/tcb2tdb.py | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 7dd0602bf..061c926f8 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -32,7 +32,7 @@ def convert_tcb_to_tdb(model): """This function performs a partial conversion of a model specified in TCB to TDB. While this should be sufficient as a starting point, the resulting parameters are only approximate - and the accompanying TOAs should be re-fit. + and the model should be re-fit. This is based on the `transform` plugin of tempo2. @@ -76,13 +76,13 @@ def convert_tcb_to_tdb(model): scale_parameter(model, "PMELONG", IFTE_K) transform_mjd_parameter(model, "POSEPOCH") - if hasattr(model, "BINARY") and hasattr(model, "BINARY").value is not None: + if hasattr(model, "BINARY") and getattr(model, "BINARY").value is not None: transform_mjd_parameter(model, "T0") transform_mjd_parameter(model, "TASC") scale_parameter(model, "PB", -1) scale_parameter(model, "FB0", 1) scale_parameter(model, "A1", -1) - model.UNITS = "TDB" + model.UNITS.value = "TDB" model.validate() diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index 4535fcb0f..2372c9740 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -6,10 +6,12 @@ import argparse -pint.logging.setup(level=pint.logging.script_level) +pint.logging.setup(level="INFO") +__all__ = ["main"] -def main(argv): + +def main(argv=None): parser = argparse.ArgumentParser( description="PINT tool for converting TCB par files to TBD.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, @@ -20,7 +22,7 @@ def main(argv): args = parser.parse_args(argv) welcome_message = """This script converts TCB par files to TDB. - Please note that this conversion is exact and the timing model + Please note that this conversion is not exact and the timing model should be re-fit to the TOAs. The following parameters are converted to TDB: From eb0f52cd8e5819048f4fb04d44db4b9fb726c5a6 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 10:36:40 -0600 Subject: [PATCH 14/51] test_tcb2tdb --- tests/test_tcb2tdb.py | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/test_tcb2tdb.py diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py new file mode 100644 index 000000000..3fcabb703 --- /dev/null +++ b/tests/test_tcb2tdb.py @@ -0,0 +1,53 @@ +from pint.models.tcb_conversion import convert_tcb_to_tdb, IFTE_K +from pint.models import get_model +from pint.scripts import tcb2tdb +from io import StringIO +import pytest +import numpy as np +import os + +simplepar = """ +PSR PSRTEST +RAJ 17:48:52.75 1 +DECJ -20:21:29.0 1 +F0 61.485476554 1 +F1 -1.181D-15 1 +PEPOCH 53750.000000 +POSEPOCH 53750.000000 +DM 223.9 1 +SOLARN0 0.00 +EPHEM DE436 +CLK TT(BIPM2017) +UNITS TCB +TIMEEPH FB90 +T2CMETHOD TEMPO +CORRECT_TROPOSPHERE N +PLANET_SHAPIRO Y +DILATEFREQ N +""" + + +def test_convert_to_tdb(): + with pytest.raises(ValueError): + m = get_model(StringIO(simplepar)) + + m = get_model(StringIO(simplepar), allow_tcb=True) + f0_tcb = m.F0.value + convert_tcb_to_tdb(m) + assert m.UNITS.value == "TDB" + assert np.isclose(m.F0.value, f0_tcb * IFTE_K) + + +def test_tcb2tdb(tmp_path): + tmppar1 = tmp_path / "tmp1.par" + tmppar2 = tmp_path / "tmp2.par" + with open(tmppar1, "w") as f: + f.write(simplepar) + + cmd = f"{tmppar1} {tmppar2}" + tcb2tdb.main(cmd.split()) + + assert os.path.isfile(tmppar2) + + m2 = get_model(tmppar2) + assert m2.UNITS.value == "TDB" From 824bf8a81434787ca736698cc112e4700aa34800 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 11:46:12 -0600 Subject: [PATCH 15/51] refact error msg --- src/pint/models/timing_model.py | 41 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index a12846820..b33136132 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -376,29 +376,30 @@ def validate(self, allow_tcb=False): if self.UNITS.value not in [None, "TDB", "TCB"]: error_message = f"PINT only supports 'UNITS TDB'. The given timescale '{self.UNITS.value}' is invalid." raise ValueError(error_message) - elif self.UNITS.value == "TCB" and not allow_tcb: - error_message = """The TCB timescale is not fully supported by PINT. - PINT only supports 'UNITS TDB' internally. See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales - for an explanation on different timescales. A TCB par file can be - converted to TDB using the `tcb2tdb` command like so: - - $ tcb2tdb J1234+6789_tcb.par J1234+6789_tdb.par - - However, this conversion is not exact and a fit must be performed to obtain - reliable results. Note that PINT only supports writing TDB par files. - """ - raise ValueError(error_message) - elif allow_tcb and self.UNITS.value == "TCB": - log.warning( - "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless" - "because the `allow_tcb` option was given. This `TimingModel` object should not be" - "used for anything except converting to TDB." - ) + elif self.UNITS.value == "TCB": + if not allow_tcb: + error_message = """The TCB timescale is not fully supported by PINT. + PINT only supports 'UNITS TDB' internally. See https://nanograv-pint.readthedocs.io/en/latest/explanation.html#time-scales + for an explanation on different timescales. A TCB par file can be + converted to TDB using the `tcb2tdb` command like so: + + $ tcb2tdb J1234+6789_tcb.par J1234+6789_tdb.par + + However, this conversion is not exact and a fit must be performed to obtain + reliable results. Note that PINT only supports writing TDB par files. + """ + raise ValueError(error_message) + else: + log.warning( + "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless" + "because the `allow_tcb` option was given. This `TimingModel` object should not be" + "used for anything except converting to TDB." + ) if not self.START.frozen: - warn("START cannot be unfrozen...setting START.frozen to True") + warn("START cannot be unfrozen... Setting START.frozen to True") self.START.frozen = True if not self.FINISH.frozen: - warn("FINISH cannot be unfrozen...setting FINISH.frozen to True") + warn("FINISH cannot be unfrozen... Setting FINISH.frozen to True") self.FINISH.frozen = True for cp in self.components.values(): From 02a3f6abbaf26f35d6af7498ff92cadd6deec53b Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 11:48:28 -0600 Subject: [PATCH 16/51] doc entry --- docs/command-line.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/command-line.rst b/docs/command-line.rst index 04ba8ef76..fa04b56c8 100644 --- a/docs/command-line.rst +++ b/docs/command-line.rst @@ -111,3 +111,11 @@ the examples subdirectory of the PINT distro. event_optimize J0030+0451_P8_15.0deg_239557517_458611204_ft1weights_GEO_wt.gt.0.4.fits PSRJ0030+0451_psrcat.par templateJ0030.3gauss --weightcol=PSRJ0030+0451 --minWeight=0.9 --nwalkers=100 --nsteps=500 +tcb2tdb +------- + +A command line tool that converts par files from TCB timescale to TDB timescale. + +:: + + tcb2tdb J0030+0451_tcb.par J0030+0451_tdb.par \ No newline at end of file From 44d64f9fc2ea48d6ab8f1367a30a0d50cc948442 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 11:58:01 -0600 Subject: [PATCH 17/51] docs --- src/pint/models/model_builder.py | 2 ++ src/pint/models/tcb_conversion.py | 9 +++++++++ src/pint/models/timing_model.py | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index 96a0e802b..b51886634 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -1,3 +1,5 @@ +"""Building a timing model from a par file.""" + import copy import warnings from io import StringIO diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 061c926f8..150f08a65 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -1,5 +1,14 @@ +"""TCB to TDB conversion of a timing model.""" + import numpy as np +__all__ = [ + "IFTE_K", + "scale_parameter", + "transform_mjd_parameter", + "convert_tcb_to_tdb", +] + # These constants are taken from Irwin & Fukushima 1999. # These are the same as the constants used in tempo2 as of 10 Feb 2023. IFTE_MJD0 = np.longdouble("43144.0003725") diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index b33136132..c07244b38 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -80,7 +80,7 @@ "MissingBinaryError", "UnknownBinaryModel", ] -# Parameters or lines in parfiles we don't understand but shouldn't +# Parameters or lines in par files we don't understand but shouldn't # complain about. These are still passed to components so that they # can use them if they want to. # From 382b559aca5d800cd2604620247bb7a4f3039c7d Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 12:01:22 -0600 Subject: [PATCH 18/51] sourcery --- src/pint/models/model_builder.py | 53 ++++++++++++++------------------ 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index b51886634..d77202920 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -176,9 +176,9 @@ def _get_component_param_overlap(self, component): # Add aliases compare overlap = in_param & cpm_param # translate to PINT parameter - overlap_pint_par = set( - [self.all_components.alias_to_pint_param(ovlp)[0] for ovlp in overlap] - ) + overlap_pint_par = { + self.all_components.alias_to_pint_param(ovlp)[0] for ovlp in overlap + } # The degree of overlapping for input component and compared component overlap_deg_in = len(component.params) - len(overlap_pint_par) overlap_deg_cpm = len(cp.params) - len(overlap_pint_par) @@ -256,7 +256,7 @@ def _pintify_parfile(self, parfile, allow_name_mixing=False): try: pint_name, init0 = self.all_components.alias_to_pint_param(k) except UnknownParameter: - if k in ignore_params: # Parameter is known but in the ingore list + if k in ignore_params: # Parameter is known but in the ignore list continue else: # Check ignored prefix try: @@ -272,22 +272,23 @@ def _pintify_parfile(self, parfile, allow_name_mixing=False): original_name_map[pint_name].append(k) repeating[pint_name] += len(v) # Check if this parameter is allowed to be repeated by PINT - if len(pint_param_dict[pint_name]) > 1: - if pint_name not in self.all_components.repeatable_param: - raise TimingModelError( - f"Parameter {pint_name} is not a repeatable parameter. " - f"However, multiple line use it." - ) + if ( + len(pint_param_dict[pint_name]) > 1 + and pint_name not in self.all_components.repeatable_param + ): + raise TimingModelError( + f"Parameter {pint_name} is not a repeatable parameter. " + f"However, multiple line use it." + ) # Check if the name is mixed for p_n, o_n in original_name_map.items(): - if len(o_n) > 1: - if not allow_name_mixing: - raise TimingModelError( - f"Parameter {p_n} have mixed input names/alias " - f"{o_n}. If you want to have mixing names, please use" - f" 'allow_name_mixing=True', and the output .par file " - f"will use '{original_name_map[pint_name][0]}'." - ) + if len(o_n) > 1 and not allow_name_mixing: + raise TimingModelError( + f"Parameter {p_n} have mixed input names/alias " + f"{o_n}. If you want to have mixing names, please use" + f" 'allow_name_mixing=True', and the output .par file " + f"will use '{original_name_map[pint_name][0]}'." + ) original_name_map[p_n] = o_n[0] return pint_param_dict, original_name_map, unknown_param @@ -362,7 +363,7 @@ def choose_model(self, param_inpar): param_components_inpar[p_name] = p_cp # Back map the possible_components and the parameters in the parfile # This will remove the duplicate components. - conflict_components = defaultdict(set) # graph for confilict + conflict_components = defaultdict(set) # graph for conflict for k, cps in param_components_inpar.items(): # If `timing_model` in param --> component mapping skip # Timing model is the base. @@ -455,10 +456,7 @@ def _setup_model( validate : bool, optional Whether to run the validate function in the timing model. """ - if original_name is not None: - use_alias = True - else: - use_alias = False + use_alias = original_name is not None for pp, v in pint_param_dict.items(): try: par = getattr(timing_model, pp) @@ -488,10 +486,7 @@ def _setup_model( # Fill up the values param_line = len(v) if param_line < 2: - if use_alias: - name = original_name[pp] - else: - name = pp + name = original_name[pp] if use_alias else pp par.from_parfile_line(" ".join([name] + v)) else: # For the repeatable parameters lines = copy.deepcopy(v) # Line queue. @@ -549,9 +544,7 @@ def _report_conflict(self, conflict_graph): # Put all the conflict components together from the graph cf_cps = list(v) cf_cps.append(k) - raise ComponentConflict( - "Can not decide the one component from:" " {}".format(cf_cps) - ) + raise ComponentConflict(f"Can not decide the one component from: {cf_cps}") def get_model(parfile, allow_name_mixing=False, allow_tcb=False): From 0cea703891ac9e1881d9709bdd46ce67ffb408f8 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 12:26:01 -0600 Subject: [PATCH 19/51] sourcery --- src/pint/models/timing_model.py | 113 +++++++++++++------------------- 1 file changed, 46 insertions(+), 67 deletions(-) diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index c07244b38..7aae3529e 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -576,7 +576,7 @@ def get_params_of_component_type(self, component_type): ------- list """ - component_type_list_str = "{}_list".format(component_type) + component_type_list_str = f"{component_type}_list" if hasattr(self, component_type_list_str): component_type_list = getattr(self, component_type_list_str) return [ @@ -607,17 +607,14 @@ def set_param_uncertainties(self, fitp): """Set the model parameters to the value contained in the input dict.""" for k, v in fitp.items(): p = getattr(self, k) - if isinstance(v, u.Quantity): - p.uncertainty = v - else: - p.uncertainty = v * p.units + p.uncertainty = v if isinstance(v, u.Quantity) else v * p.units @property_exists def components(self): """All the components in a dictionary indexed by name.""" comps = {} for ct in self.component_types: - for cp in getattr(self, ct + "_list"): + for cp in getattr(self, f"{ct}_list"): comps[cp.__class__.__name__] = cp return comps @@ -708,10 +705,8 @@ def orbital_phase(self, barytimes, anom="mean", radians=True): raise ValueError(f"anom='{anom}' is not a recognized type of anomaly") # Make sure all angles are between 0-2*pi anoms = np.remainder(anoms.value, 2 * np.pi) - if radians: # return with radian units - return anoms * u.rad - else: # return as unitless cycles from 0-1 - return anoms / (2 * np.pi) + # return with radian units or return as unitless cycles from 0-1 + return anoms * u.rad if radians else anoms / (2 * np.pi) def conjunction(self, baryMJD): """Return the time(s) of the first superior conjunction(s) after baryMJD. @@ -770,10 +765,7 @@ def funct(t): break # Now use scipy to find the root scs.append(brentq(funct, ts[lb], ts[lb + 1])) - if len(scs) == 1: - return scs[0] # Return a float - else: - return np.asarray(scs) # otherwise return an array + return scs[0] if len(scs) == 1 else np.asarray(scs) @property_exists def dm_funcs(self): @@ -869,13 +861,13 @@ def d_phase_d_delay_funcs(self): def get_deriv_funcs(self, component_type, derivative_type=""): """Return dictionary of derivative functions.""" - # TODO, this function can be a more generical function collector. + # TODO, this function can be a more generic function collector. deriv_funcs = defaultdict(list) - if not derivative_type == "": + if derivative_type != "": derivative_type += "_" - for cp in getattr(self, component_type + "_list"): + for cp in getattr(self, f"{component_type}_list"): try: - df = getattr(cp, derivative_type + "deriv_funcs") + df = getattr(cp, f"{derivative_type}deriv_funcs") except AttributeError: continue for k, v in df.items(): @@ -918,13 +910,11 @@ def get_component_type(self, component): comp_base = inspect.getmro(component.__class__) if comp_base[-2].__name__ != "Component": raise TypeError( - "Class '%s' is not a Component type class." - % component.__class__.__name__ + f"Class '{component.__class__.__name__}' is not a Component type class." ) elif len(comp_base) < 3: raise TypeError( - "'%s' class is not a subclass of 'Component' class." - % component.__class__.__name__ + f"'{component.__class__.__name__}' class is not a subclass of 'Component' class." ) else: comp_type = comp_base[-3].__name__ @@ -952,17 +942,16 @@ def map_component(self, component): comps = self.components if isinstance(component, str): if component not in list(comps.keys()): - raise AttributeError("No '%s' in the timing model." % component) + raise AttributeError(f"No '{component}' in the timing model.") comp = comps[component] - else: # When component is an component instance. - if component not in list(comps.values()): - raise AttributeError( - "No '%s' in the timing model." % component.__class__.__name__ - ) - else: - comp = component + elif component in list(comps.values()): + comp = component + else: + raise AttributeError( + f"No '{component.__class__.__name__}' in the timing model." + ) comp_type = self.get_component_type(comp) - host_list = getattr(self, comp_type + "_list") + host_list = getattr(self, f"{comp_type}_list") order = host_list.index(comp) return comp, order, host_list, comp_type @@ -981,9 +970,9 @@ def add_component( If true, add a duplicate component. Default is False. """ comp_type = self.get_component_type(component) + cur_cps = [] if comp_type in self.component_types: - comp_list = getattr(self, comp_type + "_list") - cur_cps = [] + comp_list = getattr(self, f"{comp_type}_list") for cp in comp_list: # If component order is not defined. cp_order = ( @@ -993,32 +982,28 @@ def add_component( # Check if the component has been added already. if component.__class__ in (x.__class__ for x in comp_list): log.warning( - "Component '%s' is already present but was added again." - % component.__class__.__name__ + f"Component '{component.__class__.__name__}' is already present but was added again." ) if not force: raise ValueError( - "Component '%s' is already present and will not be " - "added again. To force add it, use force=True option." - % component.__class__.__name__ + f"Component '{component.__class__.__name__}' is already present and will not be " + f"added again. To force add it, use force=True option." ) else: self.component_types.append(comp_type) - cur_cps = [] - # link new component to TimingModel component._parent = self - # If the categore is not in the order list, it will be added to the end. + # If the category is not in the order list, it will be added to the end. if component.category not in order: - new_cp = tuple((len(order) + 1, component)) + new_cp = len(order) + 1, component else: - new_cp = tuple((order.index(component.category), component)) + new_cp = order.index(component.category), component # add new component cur_cps.append(new_cp) cur_cps.sort(key=lambda x: x[0]) new_comp_list = [c[1] for c in cur_cps] - setattr(self, comp_type + "_list", new_comp_list) + setattr(self, f"{comp_type}_list", new_comp_list) # Set up components if setup: self.setup() @@ -1094,12 +1079,12 @@ def add_param_from_top(self, param, target_component, setup=False): if target_component == "": setattr(self, param.name, param) self.top_level_params += [param.name] - else: - if target_component not in list(self.components.keys()): - raise AttributeError( - "Can not find component '%s' in " "timing model." % target_component - ) + elif target_component in list(self.components.keys()): self.components[target_component].add_param(param, setup=setup) + else: + raise AttributeError( + f"Can not find component '{target_component}' in " "timing model." + ) def remove_param(self, param): """Remove a parameter from timing model. @@ -1111,7 +1096,7 @@ def remove_param(self, param): """ param_map = self.get_params_mapping() if param not in param_map: - raise AttributeError("Can not find '%s' in timing model." % param) + raise AttributeError(f"Can not find '{param}' in timing model.") if param_map[param] == "timing_model": delattr(self, param) self.top_level_params.remove(param) @@ -1121,10 +1106,8 @@ def remove_param(self, param): self.setup() def get_params_mapping(self): - """Report whick component each parameter name comes from.""" - param_mapping = {} - for p in self.top_level_params: - param_mapping[p] = "timing_model" + """Report which component each parameter name comes from.""" + param_mapping = {p: "timing_model" for p in self.top_level_params} for cp in list(self.components.values()): for pp in cp.params: param_mapping[pp] = cp.__class__.__name__ @@ -1147,7 +1130,7 @@ def get_prefix_mapping(self, prefix): Returns ------- dict - A dictionary with prefix pararameter real index as key and parameter + A dictionary with prefix parameter real index as key and parameter name as value. """ for cp in self.components.values(): @@ -1233,13 +1216,12 @@ def delay(self, toas, cutoff_component="", include_last=True): idx = len(self.DelayComponent_list) else: delay_names = [x.__class__.__name__ for x in self.DelayComponent_list] - if cutoff_component in delay_names: - idx = delay_names.index(cutoff_component) - if include_last: - idx += 1 - else: - raise KeyError("No delay component named '%s'." % cutoff_component) + if cutoff_component not in delay_names: + raise KeyError(f"No delay component named '{cutoff_component}'.") + idx = delay_names.index(cutoff_component) + if include_last: + idx += 1 # Do NOT cycle through delay_funcs - cycle through components until cutoff for dc in self.DelayComponent_list[:idx]: for df in dc.delay_funcs_component: @@ -1262,10 +1244,7 @@ def phase(self, toas, abs_phase=None): # abs_phase defaults to True if AbsPhase is in the model, otherwise to # False. Of course, if you manually set it, it will use that setting. if abs_phase is None: - if "AbsPhase" in list(self.components.keys()): - abs_phase = True - else: - abs_phase = False + abs_phase = "AbsPhase" in list(self.components.keys()) # If the absolute phase flag is on, use the TZR parameters to compute # the absolute phase. if abs_phase: @@ -1400,8 +1379,8 @@ def noise_model_dimensions(self, toas): """Number of basis functions for each noise model component. Returns a dictionary of correlated-noise components in the noise - model. Each entry contains a tuple (offset, size) where size is the - number of basis funtions for the component, and offset is their + model. Each entry contains a tuple (offset, size) where size is the + number of basis functions for the component, and offset is their starting location in the design matrix and weights vector. """ result = {} From 9638fc44e22873b47f9657fd04ad1c2a256bef62 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 12:32:20 -0600 Subject: [PATCH 20/51] docs --- src/pint/models/tcb_conversion.py | 7 +++++++ src/pint/scripts/tcb2tdb.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 150f08a65..4eb015bfc 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -17,6 +17,7 @@ def scale_parameter(model, param, n): + """Scale parameter by a power of IFTE_K""" factor = IFTE_K**n if hasattr(model, param) and getattr(model, param).quantity is not None: @@ -27,6 +28,7 @@ def scale_parameter(model, param, n): def transform_mjd_parameter(model, param): + """Convert an MJD from TCB to TDB.""" factor = 1 / IFTE_K tref = IFTE_MJD0 @@ -63,6 +65,11 @@ def convert_tcb_to_tdb(model): 7. EQUADs 8. Red noise parameters including FITWAVES, powerlaw red noise and powerlaw DM noise parameters + + Parameters + ---------- + model : pint.models.timing_model.TimingModel + Timing model to be converted. """ if model.UNITS in ["TDB", None]: diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index 2372c9740..67eb52cce 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -1,3 +1,5 @@ +"""PINT-based tool for converting TCB par files to TDB.""" + from loguru import logger as log import pint.logging From 97ce07eb158a4a8a0798ad8b33d05df8b3a825b6 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 12:39:19 -0600 Subject: [PATCH 21/51] docs and sourcery --- src/pint/models/model_builder.py | 76 ++++++++++++++++++-------------- src/pint/models/timing_model.py | 35 +++++++-------- 2 files changed, 58 insertions(+), 53 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index d77202920..8eb061bd0 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -89,6 +89,9 @@ def __call__(self, parfile, allow_name_mixing=False, allow_tcb=False): T2EFAC and EFAC, both of them maps to PINT parameter EFAC, present in the parfile at the same time. + allow_tcb : bool, optional + Whether to allow reading TCB par files + Returns ------- pint.models.timing_model.TimingModel @@ -256,17 +259,18 @@ def _pintify_parfile(self, parfile, allow_name_mixing=False): try: pint_name, init0 = self.all_components.alias_to_pint_param(k) except UnknownParameter: - if k in ignore_params: # Parameter is known but in the ignore list + if k in ignore_params: + # Parameter is known but in the ignore list continue - else: # Check ignored prefix - try: - pfx, idxs, idx = split_prefixed_name(k) - if pfx in ignore_prefix: # It is an ignored prefix. - continue - else: - unknown_param[k] += v - except PrefixError: + # Check ignored prefix + try: + pfx, idxs, idx = split_prefixed_name(k) + if pfx in ignore_prefix: # It is an ignored prefix. + continue + else: unknown_param[k] += v + except PrefixError: + unknown_param[k] += v continue pint_param_dict[pint_name] += v original_name_map[pint_name].append(k) @@ -358,8 +362,7 @@ def choose_model(self, param_inpar): if p_name != first_init: param_not_in_pint.append(pp) - p_cp = self.all_components.param_component_map.get(first_init, None) - if p_cp: + if p_cp := self.all_components.param_component_map.get(first_init, None): param_components_inpar[p_name] = p_cp # Back map the possible_components and the parameters in the parfile # This will remove the duplicate components. @@ -400,7 +403,7 @@ def choose_model(self, param_inpar): # Check if the selected component in the confilict graph. If it is # remove the selected componens with its conflict components. for ps_cp in selected_components: - cf_cps = conflict_components.get(ps_cp, None) + cf_cps = conflict_components.get(ps_cp) if cf_cps is not None: # Had conflict, but resolved. for cf_cp in cf_cps: del conflict_components[cf_cp] @@ -409,9 +412,7 @@ def choose_model(self, param_inpar): selected_cates = {} for cp in selected_components: cate = self.all_components.component_category_map[cp] - if cate not in selected_cates.keys(): - selected_cates[cate] = cp - else: + if cate in selected_cates: exisit_cp = selected_cates[cate] raise TimingModelError( f"Component '{cp}' and '{exisit_cp}' belong to the" @@ -420,6 +421,8 @@ def choose_model(self, param_inpar): f" Please check your input (e.g., .par file)." ) + else: + selected_cates[cate] = cp return selected_components, conflict_components, param_not_in_pint def _setup_model( @@ -455,18 +458,20 @@ def _setup_model( Whether to run the setup function in the timing model. validate : bool, optional Whether to run the validate function in the timing model. + allow_tcb : bool, optional + Whether to allow reading TCB par files """ use_alias = original_name is not None for pp, v in pint_param_dict.items(): try: par = getattr(timing_model, pp) except AttributeError: - # since the input is pintfied, it should be an uninitized indexed parameter + # since the input is pintfied, it should be an uninitialized indexed parameter # double check if the missing parameter an indexed parameter. pint_par, first_init = self.all_components.alias_to_pint_param(pp) try: prefix, _, index = split_prefixed_name(pint_par) - except PrefixError: + except PrefixError as e: par_hosts = self.all_components.param_component_map[pint_par] currnt_cp = timing_model.components.keys() raise TimingModelError( @@ -474,7 +479,7 @@ def _setup_model( f" by PINT, but not used in the current" f" timing model. It is used in {par_hosts}," f" but the current timing model uses {currnt_cp}." - ) + ) from e # TODO need to create a better API for _locate_param_host host_component = timing_model._locate_param_host(first_init) timing_model.add_param_from_top( @@ -522,16 +527,16 @@ def _setup_model( # There is no current repeatable parameter matching the new line # First try to fill up an empty space. - if empty_repeat_param != []: - emt_par = empty_repeat_param.pop(0) - emt_par.from_parfile_line(" ".join([emt_par.name, li])) - if use_alias: # Use the input alias as input - emt_par.use_alias = original_name[pp] - else: + if not empty_repeat_param: # No empty space, add a new parameter to the timing model. host_component = timing_model._locate_param_host(pp) timing_model.add_param_from_top(temp_par, host_component[0][0]) + else: + emt_par = empty_repeat_param.pop(0) + emt_par.from_parfile_line(" ".join([emt_par.name, li])) + if use_alias: # Use the input alias as input + emt_par.use_alias = original_name[pp] if setup: timing_model.setup() if validate: @@ -561,6 +566,9 @@ def get_model(parfile, allow_name_mixing=False, allow_tcb=False): T2EFAC and EFAC, both of them maps to PINT parameter EFAC, present in the parfile at the same time. + allow_tcb : bool, optional + Whether to allow reading TCB par files + Returns ------- Model instance get from parfile. @@ -570,16 +578,14 @@ def get_model(parfile, allow_name_mixing=False, allow_tcb=False): contents = parfile.read() except AttributeError: contents = None - if contents is None: - # # parfile is a filename and can be handled by ModelBuilder - # if _model_builder is None: - # _model_builder = ModelBuilder() - model = model_builder(parfile, allow_name_mixing, allow_tcb=allow_tcb) - model.name = parfile - return model - else: - tm = model_builder(StringIO(contents), allow_name_mixing, allow_tcb=allow_tcb) - return tm + if contents is not None: + return model_builder(StringIO(contents), allow_name_mixing, allow_tcb=allow_tcb) + # # parfile is a filename and can be handled by ModelBuilder + # if _model_builder is None: + # _model_builder = ModelBuilder() + model = model_builder(parfile, allow_name_mixing, allow_tcb=allow_tcb) + model.name = parfile + return model def get_model_and_toas( @@ -635,6 +641,8 @@ def get_model_and_toas( in the parfile at the same time. limits : "warn" or "error" What to do when encountering TOAs for which clock corrections are not available. + allow_tcb : bool, optional + Whether to allow reading TCB par files Returns ------- diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 7aae3529e..13fe4c5ed 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -1245,26 +1245,23 @@ def phase(self, toas, abs_phase=None): # False. Of course, if you manually set it, it will use that setting. if abs_phase is None: abs_phase = "AbsPhase" in list(self.components.keys()) - # If the absolute phase flag is on, use the TZR parameters to compute - # the absolute phase. - if abs_phase: - if "AbsPhase" not in list(self.components.keys()): - # if no absolute phase (TZRMJD), add the component to the model and calculate it - from pint.models import absolute_phase - - self.add_component(absolute_phase.AbsPhase(), validate=False) - self.make_TZR_toa( - toas - ) # TODO:needs timfile to get all toas, but model doesn't have access to timfile. different place for this? - self.validate() - tz_toa = self.get_TZR_toa(toas) - tz_delay = self.delay(tz_toa) - tz_phase = Phase(np.zeros(len(toas.table)), np.zeros(len(toas.table))) - for pf in self.phase_funcs: - tz_phase += Phase(pf(tz_toa, tz_delay)) - return phase - tz_phase - else: + if not abs_phase: return phase + if "AbsPhase" not in list(self.components.keys()): + # if no absolute phase (TZRMJD), add the component to the model and calculate it + from pint.models import absolute_phase + + self.add_component(absolute_phase.AbsPhase(), validate=False) + self.make_TZR_toa( + toas + ) # TODO:needs timfile to get all toas, but model doesn't have access to timfile. different place for this? + self.validate() + tz_toa = self.get_TZR_toa(toas) + tz_delay = self.delay(tz_toa) + tz_phase = Phase(np.zeros(len(toas.table)), np.zeros(len(toas.table))) + for pf in self.phase_funcs: + tz_phase += Phase(pf(tz_toa, tz_delay)) + return phase - tz_phase def total_dm(self, toas): """Calculate dispersion measure from all the dispersion type of components.""" From 1ad4153cc64dd857c2a5141e9966d44085eaa9be Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 12:47:17 -0600 Subject: [PATCH 22/51] improve test --- tests/test_tcb2tdb.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index 3fcabb703..77b8e3988 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -16,6 +16,12 @@ POSEPOCH 53750.000000 DM 223.9 1 SOLARN0 0.00 +BINARY BT +T0 53750 +A1 100.0 1 0.1 +ECC 1.0 +OM 0.0 +PB 10.0 EPHEM DE436 CLK TT(BIPM2017) UNITS TCB From a41c8fdcd04a514e6ad6bba92d2f3261405e343e Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 12:48:39 -0600 Subject: [PATCH 23/51] improve test --- tests/test_tcb2tdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index 77b8e3988..2b13702fe 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -39,9 +39,10 @@ def test_convert_to_tdb(): m = get_model(StringIO(simplepar), allow_tcb=True) f0_tcb = m.F0.value + pb_tcb = m.PB.value convert_tcb_to_tdb(m) assert m.UNITS.value == "TDB" - assert np.isclose(m.F0.value, f0_tcb * IFTE_K) + assert np.isclose(m.F0.value / f0_tcb, pb_tcb / m.PB.value) def test_tcb2tdb(tmp_path): From 145a3954e234e8459556a7fd12d3f85fe65dd41e Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 13:56:03 -0600 Subject: [PATCH 24/51] assert --- src/pint/models/tcb_conversion.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 4eb015bfc..f02429b2c 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -2,6 +2,8 @@ import numpy as np +from pint.models.parameter import MJDParameter + __all__ = [ "IFTE_K", "scale_parameter", @@ -34,6 +36,8 @@ def transform_mjd_parameter(model, param): if hasattr(model, param) and getattr(model, param).quantity is not None: par = getattr(model, param) + assert isinstance(par, MJDParameter) + par.value = (par.value - tref) * factor + tref if par.uncertainty_value is not None: par.uncertainty_value *= factor From 412ed7b750bbab0ec7a3862c634ddcc44b5d508b Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 13:58:12 -0600 Subject: [PATCH 25/51] docs --- src/pint/scripts/tcb2tdb.py | 14 +++++++------- tests/test_tcb2tdb.py | 12 +++++++----- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index 67eb52cce..f9f8f2e54 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -36,14 +36,14 @@ def main(argv=None): in fact affected by the TCB to TDB conversion: 1. Parallax 2. TZRMJD and TZRFRQ - 2. DM, DM derivatives, DM epoch, DMX parameters - 3. Solar wind parameters - 4. Binary post-Keplerian parameters including Shapiro delay + 3. DM, DM derivatives, DM epoch, DMX parameters + 4. Solar wind parameters + 5. Binary post-Keplerian parameters including Shapiro delay parameters - 5. Jumps and DM Jumps - 6. FD parameters - 7. EQUADs - 8. Red noise parameters including FITWAVES, powerlaw red noise and + 6. Jumps and DM Jumps + 7. FD parameters + 8. EQUADs + 9. Red noise parameters including FITWAVES, powerlaw red noise and powerlaw DM noise parameters """ log.info(welcome_message) diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index 2b13702fe..0d7c10dc2 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -1,10 +1,12 @@ -from pint.models.tcb_conversion import convert_tcb_to_tdb, IFTE_K -from pint.models import get_model -from pint.scripts import tcb2tdb +import os from io import StringIO -import pytest + import numpy as np -import os +import pytest + +from pint.models import get_model +from pint.models.tcb_conversion import IFTE_K, convert_tcb_to_tdb +from pint.scripts import tcb2tdb simplepar = """ PSR PSRTEST From 6c2b249e28b19b66b61d62c53ecd14fa7087a442 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 14:07:59 -0600 Subject: [PATCH 26/51] typos --- src/pint/models/model_builder.py | 12 ++++++------ src/pint/models/timing_model.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index 8eb061bd0..a4a3b308b 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -400,8 +400,8 @@ def choose_model(self, param_inpar): temp_cf_cp.remove(cp) conflict_components[cp].update(set(temp_cf_cp)) continue - # Check if the selected component in the confilict graph. If it is - # remove the selected componens with its conflict components. + # Check if the selected component in the conflict graph. If it is + # remove the selected components with its conflict components. for ps_cp in selected_components: cf_cps = conflict_components.get(ps_cp) if cf_cps is not None: # Had conflict, but resolved. @@ -413,9 +413,9 @@ def choose_model(self, param_inpar): for cp in selected_components: cate = self.all_components.component_category_map[cp] if cate in selected_cates: - exisit_cp = selected_cates[cate] + exist_cp = selected_cates[cate] raise TimingModelError( - f"Component '{cp}' and '{exisit_cp}' belong to the" + f"Component '{cp}' and '{exist_cp}' belong to the" f" same category '{cate}'. Only one component from" f" the same category can be used for a timing model." f" Please check your input (e.g., .par file)." @@ -473,12 +473,12 @@ def _setup_model( prefix, _, index = split_prefixed_name(pint_par) except PrefixError as e: par_hosts = self.all_components.param_component_map[pint_par] - currnt_cp = timing_model.components.keys() + current_cp = timing_model.components.keys() raise TimingModelError( f"Parameter {pint_par} is recognized" f" by PINT, but not used in the current" f" timing model. It is used in {par_hosts}," - f" but the current timing model uses {currnt_cp}." + f" but the current timing model uses {current_cp}." ) from e # TODO need to create a better API for _locate_param_host host_component = timing_model._locate_param_host(first_init) diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 13fe4c5ed..ec7cdb022 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -1035,8 +1035,8 @@ def _locate_param_host(self, param): list of tuples All possible components that host the target parameter. The first element is the component object that have the target parameter, the - second one is the parameter object. If it is a prefix-style parameter - , it will return one example of such parameter. + second one is the parameter object. If it is a prefix-style parameter, + it will return one example of such parameter. """ result_comp = [] for cp_name, cp in self.components.items(): @@ -1554,7 +1554,7 @@ def d_phase_d_toa(self, toas, sample_step=None): if sample_step is None: pulse_period = 1.0 / (self.F0.quantity) sample_step = pulse_period * 2 - # Note that sample_dt is applied cumulatively, so this evaulates phase at TOA-dt and TOA+dt + # Note that sample_dt is applied cumulatively, so this evaluates phase at TOA-dt and TOA+dt sample_dt = [-sample_step, 2 * sample_step] sample_phase = [] @@ -1643,8 +1643,8 @@ def d_delay_d_param(self, toas, param, acc_delay=None): delay_derivs = self.delay_deriv_funcs if param not in list(delay_derivs.keys()): raise AttributeError( - "Derivative function for '%s' is not provided" - " or not registered. " % param + "Derivative function for '{param}' is not provided" + " or not registered. " ) for df in delay_derivs[param]: result += df(toas, param, acc_delay).to( @@ -1715,7 +1715,7 @@ def d_dm_d_param(self, data, param): result = np.zeros(len(data)) << (u.pc / u.cm**3 / par.units) dm_df = self.dm_derivs.get(param, None) if dm_df is None: - if param not in self.params: # Maybe add differentitable params + if param not in self.params: # Maybe add differentiable params raise AttributeError(f"Parameter {param} does not exist") else: return result From d4ce4026a8fd273e86018e51e176778c99b29389 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Mon, 13 Feb 2023 14:08:49 -0600 Subject: [PATCH 27/51] isort --- src/pint/scripts/tcb2tdb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index f9f8f2e54..cbe29c17b 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -1,12 +1,12 @@ """PINT-based tool for converting TCB par files to TDB.""" +import argparse + from loguru import logger as log import pint.logging -from pint.models.tcb_conversion import convert_tcb_to_tdb from pint.models import get_model - -import argparse +from pint.models.tcb_conversion import convert_tcb_to_tdb pint.logging.setup(level="INFO") From f4b0e72fc1affefe2c9eae1c3fa86e61d2cf0fa6 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Tue, 14 Feb 2023 08:46:25 -0600 Subject: [PATCH 28/51] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f1c2c7c..4de9b4276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project, at least loosely, adheres to [Semantic Versioning](https://sem - `funcParameters` defined as functions operating on other parameters - Option to save `emcee` backend chains in `event_optimize` - Documentation on how to extract a covariance matrix +- TCB to TDB conversion script (`tcb2tdb`) ### Fixed - Broken notebooks CI test - BIPM correction for simulated TOAs From 805c5e9abea814b099081f17c546e2131cbc36ac Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Tue, 14 Feb 2023 13:34:55 -0600 Subject: [PATCH 29/51] welcome msg as warning --- src/pint/scripts/tcb2tdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index cbe29c17b..6b67acad3 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -46,7 +46,7 @@ def main(argv=None): 9. Red noise parameters including FITWAVES, powerlaw red noise and powerlaw DM noise parameters """ - log.info(welcome_message) + log.warning(welcome_message) model = get_model(args.input_par, allow_tcb=True) convert_tcb_to_tdb(model) From 0a1cd02c4477bcbc77a21cc27b9547f5c95f5104 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Tue, 14 Feb 2023 13:57:51 -0600 Subject: [PATCH 30/51] convert DM and DM derivatives --- src/pint/models/tcb_conversion.py | 15 +++++++++++++-- src/pint/scripts/tcb2tdb.py | 5 +++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index f02429b2c..439d79fb0 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -54,13 +54,14 @@ def convert_tcb_to_tdb(model): The following parameters are converted to TDB: 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch - 3. Keplerian binary parameters + 3. DM, DM derivatives and DM epoch + 4. Keplerian binary parameters The following parameters are NOT converted although they are in fact affected by the TCB to TDB conversion: 1. Parallax 2. TZRMJD and TZRFRQ - 2. DM, DM derivatives, DM epoch, DMX parameters + 2. DMX parameters 3. Solar wind parameters 4. Binary post-Keplerian parameters including Shapiro delay parameters @@ -96,6 +97,16 @@ def convert_tcb_to_tdb(model): scale_parameter(model, "PMELONG", IFTE_K) transform_mjd_parameter(model, "POSEPOCH") + # Although DM has the unit pc/cm^3, the quantity that enters + # the timing model is DMconst*DM, which has dimensions + # of frequency. Hence, DM and its derivatives will be + # scaled by IFTE_K**(i+1). + if "DispersionDM" in model.components: + scale_parameter(model, "DM", IFTE_K) + for i in range(1, len(model.get_DM_terms())): + scale_parameter(model, f"DM{i}", IFTE_K ** (i + 1)) + transform_mjd_parameter(model, "DMEPOCH") + if hasattr(model, "BINARY") and getattr(model, "BINARY").value is not None: transform_mjd_parameter(model, "T0") transform_mjd_parameter(model, "TASC") diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index 6b67acad3..f68b49c98 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -30,13 +30,14 @@ def main(argv=None): The following parameters are converted to TDB: 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch - 3. Keplerian binary parameters + 3. DM, DM derivatives and DM epoch + 4. Keplerian binary parameters The following parameters are NOT converted although they are in fact affected by the TCB to TDB conversion: 1. Parallax 2. TZRMJD and TZRFRQ - 3. DM, DM derivatives, DM epoch, DMX parameters + 3. DMX parameters 4. Solar wind parameters 5. Binary post-Keplerian parameters including Shapiro delay parameters From eb66a35ba272308af4ce3b47167e462227b12fe7 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Wed, 15 Feb 2023 15:17:19 -0600 Subject: [PATCH 31/51] docstrings --- src/pint/models/tcb_conversion.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 439d79fb0..33cef31a8 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -19,7 +19,18 @@ def scale_parameter(model, param, n): - """Scale parameter by a power of IFTE_K""" + """Scale a parameter x by a power of IFTE_K + x_tdb = x_tdb * IFTE_K**n + + Parameter + --------- + model : pint.models.timing_model.TimingModel + The timing model + param : str + The parameter name to be converted + n : int + The power of IFTE_K in the scaling factor + """ factor = IFTE_K**n if hasattr(model, param) and getattr(model, param).quantity is not None: @@ -30,7 +41,16 @@ def scale_parameter(model, param, n): def transform_mjd_parameter(model, param): - """Convert an MJD from TCB to TDB.""" + """Convert an MJD from TCB to TDB. + t_tdb = (t_tcb - IFTE_MJD0) / IFTE_K + + Parameters + ---------- + model : pint.models.timing_model.TimingModel + The timing model + param : str + The parameter name to be converted + """ factor = 1 / IFTE_K tref = IFTE_MJD0 From 96f78620bcefbb1b4885bff3fa825a11e1f88f64 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Wed, 15 Feb 2023 15:34:51 -0600 Subject: [PATCH 32/51] warning --- src/pint/models/tcb_conversion.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 33cef31a8..403baf6ac 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -3,6 +3,7 @@ import numpy as np from pint.models.parameter import MJDParameter +from loguru import logger as log __all__ = [ "IFTE_K", @@ -98,7 +99,7 @@ def convert_tcb_to_tdb(model): """ if model.UNITS in ["TDB", None]: - # ~ issue warning here ~ + log.warning("The input par file is already in TDB units. Doing nothing.") return if "Spindown" in model.components: From 8845e16aba1253b214ae26835952413b8af5a53a Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Wed, 15 Feb 2023 15:46:23 -0600 Subject: [PATCH 33/51] warning and help message --- src/pint/models/tcb_conversion.py | 5 +++ src/pint/scripts/tcb2tdb.py | 51 +++++++++++++++---------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 403baf6ac..f5b2873e2 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -102,6 +102,11 @@ def convert_tcb_to_tdb(model): log.warning("The input par file is already in TDB units. Doing nothing.") return + log.warning( + "The TCB to TDB conversion is only approximate. " + "The resulting timing model should be re-fit to get reliable results." + ) + if "Spindown" in model.components: for Fn_par in model.components["Spindown"].F_terms: n = int(Fn_par[1:]) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index f68b49c98..c9c3b0a7d 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -15,7 +15,30 @@ def main(argv=None): parser = argparse.ArgumentParser( - description="PINT tool for converting TCB par files to TBD.", + description="""`tcb2tdb` converts TCB par files to TDB. + Please note that this conversion is not exact and the timing model + should be re-fit to the TOAs. + + The following parameters are converted to TDB: + 1. Spin frequency, its derivatives and spin epoch + 2. Sky coordinates, proper motion and the position epoch + 3. DM, DM derivatives and DM epoch + 4. Keplerian binary parameters + + The following parameters are NOT converted although they are + in fact affected by the TCB to TDB conversion: + 1. Parallax + 2. TZRMJD and TZRFRQ + 3. DMX parameters + 4. Solar wind parameters + 5. Binary post-Keplerian parameters including Shapiro delay + parameters + 6. Jumps and DM Jumps + 7. FD parameters + 8. EQUADs + 9. Red noise parameters including FITWAVES, powerlaw red noise and + powerlaw DM noise parameters + """, formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("input_par", help="Input par file name (TCB)") @@ -23,32 +46,6 @@ def main(argv=None): args = parser.parse_args(argv) - welcome_message = """This script converts TCB par files to TDB. - Please note that this conversion is not exact and the timing model - should be re-fit to the TOAs. - - The following parameters are converted to TDB: - 1. Spin frequency, its derivatives and spin epoch - 2. Sky coordinates, proper motion and the position epoch - 3. DM, DM derivatives and DM epoch - 4. Keplerian binary parameters - - The following parameters are NOT converted although they are - in fact affected by the TCB to TDB conversion: - 1. Parallax - 2. TZRMJD and TZRFRQ - 3. DMX parameters - 4. Solar wind parameters - 5. Binary post-Keplerian parameters including Shapiro delay - parameters - 6. Jumps and DM Jumps - 7. FD parameters - 8. EQUADs - 9. Red noise parameters including FITWAVES, powerlaw red noise and - powerlaw DM noise parameters - """ - log.warning(welcome_message) - model = get_model(args.input_par, allow_tcb=True) convert_tcb_to_tdb(model) model.write_parfile(args.output_par) From 41b11984dc41cb5f602fc1518eba62a66bf777cc Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Wed, 15 Feb 2023 15:55:29 -0600 Subject: [PATCH 34/51] get_prefix_mapping --- src/pint/models/tcb_conversion.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index f5b2873e2..d32a9f351 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -108,8 +108,7 @@ def convert_tcb_to_tdb(model): ) if "Spindown" in model.components: - for Fn_par in model.components["Spindown"].F_terms: - n = int(Fn_par[1:]) + for n, Fn_par in model.get_prefix_mapping("F"): scale_parameter(model, Fn_par, n + 1) transform_mjd_parameter(model, "PEPOCH") @@ -129,8 +128,8 @@ def convert_tcb_to_tdb(model): # scaled by IFTE_K**(i+1). if "DispersionDM" in model.components: scale_parameter(model, "DM", IFTE_K) - for i in range(1, len(model.get_DM_terms())): - scale_parameter(model, f"DM{i}", IFTE_K ** (i + 1)) + for n, DMn_par in model.get_prefix_mapping("DM"): + scale_parameter(model, DMn_par, IFTE_K ** (n + 1)) transform_mjd_parameter(model, "DMEPOCH") if hasattr(model, "BINARY") and getattr(model, "BINARY").value is not None: From 920b74951f395287f34113910717e4bbf9abbf09 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Wed, 15 Feb 2023 16:14:49 -0600 Subject: [PATCH 35/51] fix power --- src/pint/models/tcb_conversion.py | 31 +++++++++++++++++++++++-------- src/pint/scripts/tcb2tdb.py | 4 ++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index d32a9f351..670ef76e0 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -23,6 +23,18 @@ def scale_parameter(model, param, n): """Scale a parameter x by a power of IFTE_K x_tdb = x_tdb * IFTE_K**n + The power n depends on the "effective dimensionality" of + the parameter as it appears in the timing model. Some examples + are given bellow: + + 1. F0 has effective dimensionality of frequency and n = 1 + 2. F1 has effective dimensionality of frequency^2 and n = 2 + 3. A1 has effective dimensionality of time because it appears as + A1/c in the timing model. Therefore, its n = -1 + 4. DM has effective dimensionality of frequency because it appears + as DM*DMconst in the timing model. Therefore, its n = 1 + 5. PBDOT is dimensionless and has n = 0. i.e., it is not scaled. + Parameter --------- model : pint.models.timing_model.TimingModel @@ -32,6 +44,8 @@ def scale_parameter(model, param, n): n : int The power of IFTE_K in the scaling factor """ + assert isinstance(n, int), "The power must be an integer." + factor = IFTE_K**n if hasattr(model, param) and getattr(model, param).quantity is not None: @@ -76,7 +90,7 @@ def convert_tcb_to_tdb(model): 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch 3. DM, DM derivatives and DM epoch - 4. Keplerian binary parameters + 4. Keplerian binary parameters and FB1 The following parameters are NOT converted although they are in fact affected by the TCB to TDB conversion: @@ -85,7 +99,7 @@ def convert_tcb_to_tdb(model): 2. DMX parameters 3. Solar wind parameters 4. Binary post-Keplerian parameters including Shapiro delay - parameters + parameters (except FB1) 5. Jumps and DM Jumps 6. FD parameters 7. EQUADs @@ -114,12 +128,12 @@ def convert_tcb_to_tdb(model): transform_mjd_parameter(model, "PEPOCH") if "AstrometryEquatorial" in model.components: - scale_parameter(model, "PMRA", IFTE_K) - scale_parameter(model, "PMDEC", IFTE_K) + scale_parameter(model, "PMRA", 1) + scale_parameter(model, "PMDEC", 1) transform_mjd_parameter(model, "POSEPOCH") elif "AstrometryEcliptic" in model.components: - scale_parameter(model, "PMELAT", IFTE_K) - scale_parameter(model, "PMELONG", IFTE_K) + scale_parameter(model, "PMELAT", 1) + scale_parameter(model, "PMELONG", 1) transform_mjd_parameter(model, "POSEPOCH") # Although DM has the unit pc/cm^3, the quantity that enters @@ -127,9 +141,9 @@ def convert_tcb_to_tdb(model): # of frequency. Hence, DM and its derivatives will be # scaled by IFTE_K**(i+1). if "DispersionDM" in model.components: - scale_parameter(model, "DM", IFTE_K) + scale_parameter(model, "DM", 1) for n, DMn_par in model.get_prefix_mapping("DM"): - scale_parameter(model, DMn_par, IFTE_K ** (n + 1)) + scale_parameter(model, DMn_par, n + 1) transform_mjd_parameter(model, "DMEPOCH") if hasattr(model, "BINARY") and getattr(model, "BINARY").value is not None: @@ -137,6 +151,7 @@ def convert_tcb_to_tdb(model): transform_mjd_parameter(model, "TASC") scale_parameter(model, "PB", -1) scale_parameter(model, "FB0", 1) + scale_parameter(model, "FB1", 2) scale_parameter(model, "A1", -1) model.UNITS.value = "TDB" diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index c9c3b0a7d..bc58d7963 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -23,7 +23,7 @@ def main(argv=None): 1. Spin frequency, its derivatives and spin epoch 2. Sky coordinates, proper motion and the position epoch 3. DM, DM derivatives and DM epoch - 4. Keplerian binary parameters + 4. Keplerian binary parameters and FB1 The following parameters are NOT converted although they are in fact affected by the TCB to TDB conversion: @@ -32,7 +32,7 @@ def main(argv=None): 3. DMX parameters 4. Solar wind parameters 5. Binary post-Keplerian parameters including Shapiro delay - parameters + parameters (except FB1) 6. Jumps and DM Jumps 7. FD parameters 8. EQUADs From ebd1d5fd5b84609b0190c4f349810b595b8dfc2f Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 16 Feb 2023 09:53:10 -0600 Subject: [PATCH 36/51] fix for loop --- src/pint/models/tcb_conversion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 670ef76e0..3aea104f1 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -122,7 +122,7 @@ def convert_tcb_to_tdb(model): ) if "Spindown" in model.components: - for n, Fn_par in model.get_prefix_mapping("F"): + for n, Fn_par in model.get_prefix_mapping("F").items(): scale_parameter(model, Fn_par, n + 1) transform_mjd_parameter(model, "PEPOCH") @@ -142,7 +142,7 @@ def convert_tcb_to_tdb(model): # scaled by IFTE_K**(i+1). if "DispersionDM" in model.components: scale_parameter(model, "DM", 1) - for n, DMn_par in model.get_prefix_mapping("DM"): + for n, DMn_par in model.get_prefix_mapping("DM").items(): scale_parameter(model, DMn_par, n + 1) transform_mjd_parameter(model, "DMEPOCH") From 5e0229f36d1ba6fff237c077b3ac36599c17e481 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 16 Feb 2023 10:02:08 -0600 Subject: [PATCH 37/51] get_model and get_model_and_toas do not need allow_tcb option. --- src/pint/models/model_builder.py | 14 ++++---------- src/pint/scripts/tcb2tdb.py | 5 +++-- tests/test_tcb2tdb.py | 8 ++++---- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index a4a3b308b..750b833f2 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -552,7 +552,7 @@ def _report_conflict(self, conflict_graph): raise ComponentConflict(f"Can not decide the one component from: {cf_cps}") -def get_model(parfile, allow_name_mixing=False, allow_tcb=False): +def get_model(parfile, allow_name_mixing=False): """A one step function to build model from a parfile. Parameters @@ -566,9 +566,6 @@ def get_model(parfile, allow_name_mixing=False, allow_tcb=False): T2EFAC and EFAC, both of them maps to PINT parameter EFAC, present in the parfile at the same time. - allow_tcb : bool, optional - Whether to allow reading TCB par files - Returns ------- Model instance get from parfile. @@ -579,11 +576,11 @@ def get_model(parfile, allow_name_mixing=False, allow_tcb=False): except AttributeError: contents = None if contents is not None: - return model_builder(StringIO(contents), allow_name_mixing, allow_tcb=allow_tcb) + return model_builder(StringIO(contents), allow_name_mixing) # # parfile is a filename and can be handled by ModelBuilder # if _model_builder is None: # _model_builder = ModelBuilder() - model = model_builder(parfile, allow_name_mixing, allow_tcb=allow_tcb) + model = model_builder(parfile, allow_name_mixing) model.name = parfile return model @@ -602,7 +599,6 @@ def get_model_and_toas( picklefilename=None, allow_name_mixing=False, limits="warn", - allow_tcb=False, ): """Load a timing model and a related TOAs, using model commands as needed @@ -641,14 +637,12 @@ def get_model_and_toas( in the parfile at the same time. limits : "warn" or "error" What to do when encountering TOAs for which clock corrections are not available. - allow_tcb : bool, optional - Whether to allow reading TCB par files Returns ------- A tuple with (model instance, TOAs instance) """ - mm = get_model(parfile, allow_name_mixing, allow_tcb=allow_tcb) + mm = get_model(parfile, allow_name_mixing) tt = get_TOAs( timfile, include_pn=include_pn, diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index bc58d7963..6deb2ad6e 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -5,7 +5,7 @@ from loguru import logger as log import pint.logging -from pint.models import get_model +from pint.models.model_builder import ModelBuilder from pint.models.tcb_conversion import convert_tcb_to_tdb pint.logging.setup(level="INFO") @@ -46,7 +46,8 @@ def main(argv=None): args = parser.parse_args(argv) - model = get_model(args.input_par, allow_tcb=True) + mb = ModelBuilder() + model = mb(args.input_par, allow_tcb=True) convert_tcb_to_tdb(model) model.write_parfile(args.output_par) diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index 0d7c10dc2..91d685761 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -4,7 +4,7 @@ import numpy as np import pytest -from pint.models import get_model +from pint.models.model_builder import ModelBuilder from pint.models.tcb_conversion import IFTE_K, convert_tcb_to_tdb from pint.scripts import tcb2tdb @@ -37,9 +37,9 @@ def test_convert_to_tdb(): with pytest.raises(ValueError): - m = get_model(StringIO(simplepar)) + m = ModelBuilder()(StringIO(simplepar)) - m = get_model(StringIO(simplepar), allow_tcb=True) + m = ModelBuilder()(StringIO(simplepar), allow_tcb=True) f0_tcb = m.F0.value pb_tcb = m.PB.value convert_tcb_to_tdb(m) @@ -58,5 +58,5 @@ def test_tcb2tdb(tmp_path): assert os.path.isfile(tmppar2) - m2 = get_model(tmppar2) + m2 = ModelBuilder()(tmppar2) assert m2.UNITS.value == "TDB" From fd18592beafa529af23434110543afd1d3b62996 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 16 Feb 2023 10:40:22 -0600 Subject: [PATCH 38/51] back conversion and roundtrip test --- src/pint/models/tcb_conversion.py | 61 ++++++++++++++++++------------- src/pint/scripts/tcb2tdb.py | 4 +- tests/test_tcb2tdb.py | 27 ++++++++++++-- 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 3aea104f1..5c840e221 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -19,7 +19,7 @@ IFTE_K = 1 + IFTE_KM1 -def scale_parameter(model, param, n): +def scale_parameter(model, param, n, backwards): """Scale a parameter x by a power of IFTE_K x_tdb = x_tdb * IFTE_K**n @@ -43,10 +43,14 @@ def scale_parameter(model, param, n): The parameter name to be converted n : int The power of IFTE_K in the scaling factor + backwards : bool + Whether to do TDB to TCB conversion. """ assert isinstance(n, int), "The power must be an integer." - factor = IFTE_K**n + p = -1 if backwards else 1 + + factor = IFTE_K ** (p * n) if hasattr(model, param) and getattr(model, param).quantity is not None: par = getattr(model, param) @@ -55,9 +59,10 @@ def scale_parameter(model, param, n): par.uncertainty_value *= factor -def transform_mjd_parameter(model, param): - """Convert an MJD from TCB to TDB. - t_tdb = (t_tcb - IFTE_MJD0) / IFTE_K +def transform_mjd_parameter(model, param, backwards): + """Convert an MJD from TCB to TDB or vice versa. + t_tdb = (t_tcb - IFTE_MJD0) / IFTE_K + IFTE_MJD0 + t_tcb = (t_tdb - IFTE_MJD0) * IFTE_K + IFTE_MJD0 Parameters ---------- @@ -65,8 +70,10 @@ def transform_mjd_parameter(model, param): The timing model param : str The parameter name to be converted + backwards : bool + Whether to do TDB to TCB conversion. """ - factor = 1 / IFTE_K + factor = IFTE_K if backwards else 1 / IFTE_K tref = IFTE_MJD0 if hasattr(model, param) and getattr(model, param).quantity is not None: @@ -78,7 +85,7 @@ def transform_mjd_parameter(model, param): par.uncertainty_value *= factor -def convert_tcb_to_tdb(model): +def convert_tcb_tdb(model, backwards=False): """This function performs a partial conversion of a model specified in TCB to TDB. While this should be sufficient as a starting point, the resulting parameters are only approximate @@ -110,6 +117,8 @@ def convert_tcb_to_tdb(model): ---------- model : pint.models.timing_model.TimingModel Timing model to be converted. + backwards : bool + Whether to do TDB to TCB conversion. The default is TCB to TDB. """ if model.UNITS in ["TDB", None]: @@ -123,37 +132,37 @@ def convert_tcb_to_tdb(model): if "Spindown" in model.components: for n, Fn_par in model.get_prefix_mapping("F").items(): - scale_parameter(model, Fn_par, n + 1) + scale_parameter(model, Fn_par, n + 1, backwards) - transform_mjd_parameter(model, "PEPOCH") + transform_mjd_parameter(model, "PEPOCH", backwards) if "AstrometryEquatorial" in model.components: - scale_parameter(model, "PMRA", 1) - scale_parameter(model, "PMDEC", 1) - transform_mjd_parameter(model, "POSEPOCH") + scale_parameter(model, "PMRA", 1, backwards) + scale_parameter(model, "PMDEC", 1, backwards) + transform_mjd_parameter(model, "POSEPOCH", backwards) elif "AstrometryEcliptic" in model.components: - scale_parameter(model, "PMELAT", 1) - scale_parameter(model, "PMELONG", 1) - transform_mjd_parameter(model, "POSEPOCH") + scale_parameter(model, "PMELAT", 1, backwards) + scale_parameter(model, "PMELONG", 1, backwards) + transform_mjd_parameter(model, "POSEPOCH", backwards) # Although DM has the unit pc/cm^3, the quantity that enters # the timing model is DMconst*DM, which has dimensions # of frequency. Hence, DM and its derivatives will be # scaled by IFTE_K**(i+1). if "DispersionDM" in model.components: - scale_parameter(model, "DM", 1) + scale_parameter(model, "DM", 1, backwards) for n, DMn_par in model.get_prefix_mapping("DM").items(): - scale_parameter(model, DMn_par, n + 1) - transform_mjd_parameter(model, "DMEPOCH") + scale_parameter(model, DMn_par, n + 1, backwards) + transform_mjd_parameter(model, "DMEPOCH", backwards) if hasattr(model, "BINARY") and getattr(model, "BINARY").value is not None: - transform_mjd_parameter(model, "T0") - transform_mjd_parameter(model, "TASC") - scale_parameter(model, "PB", -1) - scale_parameter(model, "FB0", 1) - scale_parameter(model, "FB1", 2) - scale_parameter(model, "A1", -1) + transform_mjd_parameter(model, "T0", backwards) + transform_mjd_parameter(model, "TASC", backwards) + scale_parameter(model, "PB", -1, backwards) + scale_parameter(model, "FB0", 1, backwards) + scale_parameter(model, "FB1", 2, backwards) + scale_parameter(model, "A1", -1, backwards) - model.UNITS.value = "TDB" + model.UNITS.value = "TCB" if backwards else "TDB" - model.validate() + model.validate(allow_tcb=backwards) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index 6deb2ad6e..ff8a0bf11 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -6,7 +6,7 @@ import pint.logging from pint.models.model_builder import ModelBuilder -from pint.models.tcb_conversion import convert_tcb_to_tdb +from pint.models.tcb_conversion import convert_tcb_tdb pint.logging.setup(level="INFO") @@ -48,7 +48,7 @@ def main(argv=None): mb = ModelBuilder() model = mb(args.input_par, allow_tcb=True) - convert_tcb_to_tdb(model) + convert_tcb_tdb(model) model.write_parfile(args.output_par) log.info(f"Output written to {args.output_par}.") diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index 91d685761..59701e9fd 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -1,11 +1,12 @@ import os +from copy import deepcopy from io import StringIO import numpy as np import pytest from pint.models.model_builder import ModelBuilder -from pint.models.tcb_conversion import IFTE_K, convert_tcb_to_tdb +from pint.models.tcb_conversion import convert_tcb_tdb from pint.scripts import tcb2tdb simplepar = """ @@ -35,18 +36,36 @@ """ -def test_convert_to_tdb(): +@pytest.mark.parametrize("backwards", [True, False]) +def test_convert_units(backwards): with pytest.raises(ValueError): m = ModelBuilder()(StringIO(simplepar)) m = ModelBuilder()(StringIO(simplepar), allow_tcb=True) f0_tcb = m.F0.value pb_tcb = m.PB.value - convert_tcb_to_tdb(m) - assert m.UNITS.value == "TDB" + convert_tcb_tdb(m, backwards=backwards) + assert m.UNITS.value == ("TCB" if backwards else "TDB") assert np.isclose(m.F0.value / f0_tcb, pb_tcb / m.PB.value) +def test_convert_units_roundtrip(): + m = ModelBuilder()(StringIO(simplepar), allow_tcb=True) + m_ = deepcopy(m) + convert_tcb_tdb(m, backwards=False) + convert_tcb_tdb(m, backwards=True) + + for par in m.params: + p = getattr(m, par) + p_ = getattr(m_, par) + if p.value is None: + assert p_.value is None + elif isinstance(p.value, str): + assert getattr(m, par).value == getattr(m_, par).value + else: + assert np.isclose(getattr(m, par).value, getattr(m_, par).value) + + def test_tcb2tdb(tmp_path): tmppar1 = tmp_path / "tmp1.par" tmppar2 = tmp_path / "tmp2.par" From dd6622ed64fb5efc4159da9e3a575f61ee2d540b Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 16 Feb 2023 11:03:38 -0600 Subject: [PATCH 39/51] docstring --- tests/test_tcb2tdb.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index 59701e9fd..be3b9a0b7 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -1,3 +1,5 @@ +"""Tests for `pint.models.tcb_conversion` and the `tcb2tdb` script.""" + import os from copy import deepcopy from io import StringIO From e7d5d4ff5a8044d5f3f4550e933e59b7077b0b92 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 17 Feb 2023 09:05:09 -0600 Subject: [PATCH 40/51] minor corrections --- src/pint/models/tcb_conversion.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 5c840e221..5b84f343a 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -21,7 +21,7 @@ def scale_parameter(model, param, n, backwards): """Scale a parameter x by a power of IFTE_K - x_tdb = x_tdb * IFTE_K**n + x_tdb = x_tcb * IFTE_K**n The power n depends on the "effective dimensionality" of the parameter as it appears in the timing model. Some examples @@ -139,11 +139,10 @@ def convert_tcb_tdb(model, backwards=False): if "AstrometryEquatorial" in model.components: scale_parameter(model, "PMRA", 1, backwards) scale_parameter(model, "PMDEC", 1, backwards) - transform_mjd_parameter(model, "POSEPOCH", backwards) elif "AstrometryEcliptic" in model.components: scale_parameter(model, "PMELAT", 1, backwards) scale_parameter(model, "PMELONG", 1, backwards) - transform_mjd_parameter(model, "POSEPOCH", backwards) + transform_mjd_parameter(model, "POSEPOCH", backwards) # Although DM has the unit pc/cm^3, the quantity that enters # the timing model is DMconst*DM, which has dimensions From 6cb11dde4b49b8db3aa4c694169371fd250934c0 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 17 Feb 2023 10:37:11 -0600 Subject: [PATCH 41/51] convert upon read option --- src/pint/models/model_builder.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index 750b833f2..017f2211e 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -23,7 +23,7 @@ ) from pint.toa import get_TOAs from pint.utils import PrefixError, interesting_lines, lines_of, split_prefixed_name - +from pint.models.tcb_conversion import convert_tcb_tdb __all__ = ["ModelBuilder", "get_model", "get_model_and_toas"] @@ -552,7 +552,7 @@ def _report_conflict(self, conflict_graph): raise ComponentConflict(f"Can not decide the one component from: {cf_cps}") -def get_model(parfile, allow_name_mixing=False): +def get_model(parfile, allow_name_mixing=False, allow_tcb=False): """A one step function to build model from a parfile. Parameters @@ -570,18 +570,31 @@ def get_model(parfile, allow_name_mixing=False): ------- Model instance get from parfile. """ + assert allow_tcb in [True, False, "raw"] + + convert_tcb = allow_tcb == True + allow_tcb_ = allow_tcb in [True, "raw"] + model_builder = ModelBuilder() try: contents = parfile.read() except AttributeError: contents = None if contents is not None: - return model_builder(StringIO(contents), allow_name_mixing) + model = model_builder( + StringIO(contents), allow_name_mixing, allow_tcb=allow_tcb_ + ) + if convert_tcb: + convert_tcb_tdb(model) + return model + # # parfile is a filename and can be handled by ModelBuilder # if _model_builder is None: # _model_builder = ModelBuilder() - model = model_builder(parfile, allow_name_mixing) + model = model_builder(parfile, allow_name_mixing, allow_tcb=allow_tcb_) model.name = parfile + if convert_tcb: + convert_tcb_tdb(model) return model @@ -599,6 +612,7 @@ def get_model_and_toas( picklefilename=None, allow_name_mixing=False, limits="warn", + allow_tcb=False, ): """Load a timing model and a related TOAs, using model commands as needed @@ -642,7 +656,7 @@ def get_model_and_toas( ------- A tuple with (model instance, TOAs instance) """ - mm = get_model(parfile, allow_name_mixing) + mm = get_model(parfile, allow_name_mixing, allow_tcb=allow_tcb) tt = get_TOAs( timfile, include_pn=include_pn, From c9e70d55e25df9529c3a95c88f8067251dd81d96 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 17 Feb 2023 11:04:16 -0600 Subject: [PATCH 42/51] target_units --- CHANGELOG.md | 2 +- src/pint/models/tcb_conversion.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62309d95c..fac80632b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ and this project, at least loosely, adheres to [Semantic Versioning](https://sem - `funcParameters` defined as functions operating on other parameters - Option to save `emcee` backend chains in `event_optimize` - Documentation on how to extract a covariance matrix -- TCB to TDB conversion script (`tcb2tdb`) +- TCB to TDB conversion on read and conversion script (`tcb2tdb`) ### Fixed - Broken notebooks CI test - BIPM correction for simulated TOAs diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index 5b84f343a..f20287d5a 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -121,7 +121,9 @@ def convert_tcb_tdb(model, backwards=False): Whether to do TDB to TCB conversion. The default is TCB to TDB. """ - if model.UNITS in ["TDB", None]: + target_units = "TCB" if backwards else "TDB" + + if model.UNITS.value in [target_units, None]: log.warning("The input par file is already in TDB units. Doing nothing.") return @@ -162,6 +164,6 @@ def convert_tcb_tdb(model, backwards=False): scale_parameter(model, "FB1", 2, backwards) scale_parameter(model, "A1", -1, backwards) - model.UNITS.value = "TCB" if backwards else "TDB" + model.UNITS.value = target_units model.validate(allow_tcb=backwards) From 570eb8e3d0816c574ee92e7ae43da7a9dd717af8 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 17 Feb 2023 11:07:19 -0600 Subject: [PATCH 43/51] target_units skip --- src/pint/models/tcb_conversion.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index f20287d5a..eb192f619 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -123,8 +123,10 @@ def convert_tcb_tdb(model, backwards=False): target_units = "TCB" if backwards else "TDB" - if model.UNITS.value in [target_units, None]: - log.warning("The input par file is already in TDB units. Doing nothing.") + if model.UNITS.value == target_units or ( + model.UNITS.value is None and not backwards + ): + log.warning("The input par file is already in the target units. Doing nothing.") return log.warning( From 7b1399c84d3c07ef9975e7c062965e8b221064cd Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Fri, 17 Feb 2023 11:18:16 -0600 Subject: [PATCH 44/51] convert tcb upon read in ModelBuilder --- src/pint/models/model_builder.py | 43 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/pint/models/model_builder.py b/src/pint/models/model_builder.py index 017f2211e..2a14bd73c 100644 --- a/src/pint/models/model_builder.py +++ b/src/pint/models/model_builder.py @@ -89,14 +89,22 @@ def __call__(self, parfile, allow_name_mixing=False, allow_tcb=False): T2EFAC and EFAC, both of them maps to PINT parameter EFAC, present in the parfile at the same time. - allow_tcb : bool, optional - Whether to allow reading TCB par files + allow_tcb : True, False, or "raw", optional + Whether to read TCB par files. Default is False, and will throw an + error upon encountering TCB par files. If True, the par file will be + converted to TDB upon read. If "raw", an unconverted malformed TCB + TimingModel object will be returned. Returns ------- pint.models.timing_model.TimingModel The result timing model based on the input .parfile or file object. """ + + assert allow_tcb in [True, False, "raw"] + convert_tcb = allow_tcb == True + allow_tcb_ = allow_tcb in [True, "raw"] + pint_param_dict, original_name, unknown_param = self._pintify_parfile( parfile, allow_name_mixing ) @@ -114,7 +122,7 @@ def __call__(self, parfile, allow_name_mixing=False, allow_tcb=False): original_name, setup=True, validate=True, - allow_tcb=allow_tcb, + allow_tcb=allow_tcb_, ) # Report unknown line for k, v in unknown_param.items(): @@ -122,6 +130,9 @@ def __call__(self, parfile, allow_name_mixing=False, allow_tcb=False): warnings.warn(f"Unrecognized parfile line '{p_line}'", UserWarning) # log.warning(f"Unrecognized parfile line '{p_line}'") + if tm.UNITS.value == "TCB" and convert_tcb: + convert_tcb_tdb(tm) + return tm def _validate_components(self): @@ -566,14 +577,16 @@ def get_model(parfile, allow_name_mixing=False, allow_tcb=False): T2EFAC and EFAC, both of them maps to PINT parameter EFAC, present in the parfile at the same time. + allow_tcb : True, False, or "raw", optional + Whether to read TCB par files. Default is False, and will throw an + error upon encountering TCB par files. If True, the par file will be + converted to TDB upon read. If "raw", an unconverted malformed TCB + TimingModel object will be returned. + Returns ------- Model instance get from parfile. """ - assert allow_tcb in [True, False, "raw"] - - convert_tcb = allow_tcb == True - allow_tcb_ = allow_tcb in [True, "raw"] model_builder = ModelBuilder() try: @@ -581,20 +594,13 @@ def get_model(parfile, allow_name_mixing=False, allow_tcb=False): except AttributeError: contents = None if contents is not None: - model = model_builder( - StringIO(contents), allow_name_mixing, allow_tcb=allow_tcb_ - ) - if convert_tcb: - convert_tcb_tdb(model) - return model + return model_builder(StringIO(contents), allow_name_mixing, allow_tcb=allow_tcb) # # parfile is a filename and can be handled by ModelBuilder # if _model_builder is None: # _model_builder = ModelBuilder() - model = model_builder(parfile, allow_name_mixing, allow_tcb=allow_tcb_) + model = model_builder(parfile, allow_name_mixing, allow_tcb=allow_tcb) model.name = parfile - if convert_tcb: - convert_tcb_tdb(model) return model @@ -651,6 +657,11 @@ def get_model_and_toas( in the parfile at the same time. limits : "warn" or "error" What to do when encountering TOAs for which clock corrections are not available. + allow_tcb : True, False, or "raw", optional + Whether to read TCB par files. Default is False, and will throw an + error upon encountering TCB par files. If True, the par file will be + converted to TDB upon read. If "raw", an unconverted malformed TCB + TimingModel object will be returned. Returns ------- From 307f69ae439bf1cfc4338ac6d532f98e8c6e1e97 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 23 Mar 2023 10:04:58 -0500 Subject: [PATCH 45/51] fix roundtrip test --- tests/test_tcb2tdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index be3b9a0b7..11ab50835 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -52,7 +52,7 @@ def test_convert_units(backwards): def test_convert_units_roundtrip(): - m = ModelBuilder()(StringIO(simplepar), allow_tcb=True) + m = ModelBuilder()(StringIO(simplepar), allow_tcb="raw") m_ = deepcopy(m) convert_tcb_tdb(m, backwards=False) convert_tcb_tdb(m, backwards=True) From 628f78e40d14f9d06fb642557d544e3cb68b4246 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 6 Apr 2023 10:36:49 -0500 Subject: [PATCH 46/51] don't (try to) convert twice --- src/pint/scripts/tcb2tdb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index ff8a0bf11..a920c28bc 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -48,7 +48,6 @@ def main(argv=None): mb = ModelBuilder() model = mb(args.input_par, allow_tcb=True) - convert_tcb_tdb(model) model.write_parfile(args.output_par) log.info(f"Output written to {args.output_par}.") From 296ce2c12fa40e3a62e552965090a94110457052 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 6 Apr 2023 10:42:48 -0500 Subject: [PATCH 47/51] space --- src/pint/models/timing_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 9373ca654..7b578eb00 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -391,7 +391,7 @@ def validate(self, allow_tcb=False): raise ValueError(error_message) else: log.warning( - "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless" + "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless " "because the `allow_tcb` option was given. This `TimingModel` object should not be" "used for anything except converting to TDB." ) From 91daff7b2b42e1f1c64516b1de3f5e14424421eb Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 6 Apr 2023 10:44:27 -0500 Subject: [PATCH 48/51] space --- src/pint/models/timing_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 7b578eb00..d119e4119 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -392,7 +392,7 @@ def validate(self, allow_tcb=False): else: log.warning( "PINT does not support 'UNITS TCB' internally. Reading this par file nevertheless " - "because the `allow_tcb` option was given. This `TimingModel` object should not be" + "because the `allow_tcb` option was given. This `TimingModel` object should not be " "used for anything except converting to TDB." ) if not self.START.frozen: From 458a04470c02d9389ad849f5dc43de6adee9cb37 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 6 Apr 2023 10:47:40 -0500 Subject: [PATCH 49/51] log msg --- src/pint/models/tcb_conversion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pint/models/tcb_conversion.py b/src/pint/models/tcb_conversion.py index eb192f619..c4adccfed 100644 --- a/src/pint/models/tcb_conversion.py +++ b/src/pint/models/tcb_conversion.py @@ -130,8 +130,9 @@ def convert_tcb_tdb(model, backwards=False): return log.warning( - "The TCB to TDB conversion is only approximate. " - "The resulting timing model should be re-fit to get reliable results." + "Converting this timing model from TCB to TDB. " + "Please note that the TCB to TDB conversion is only approximate and " + "the resulting timing model should be re-fit to get reliable results." ) if "Spindown" in model.components: From 664cdd8d2665420ab75b765f2c33f8c5011185bf Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 6 Apr 2023 11:18:37 -0500 Subject: [PATCH 50/51] update tcb explanation --- docs/explanation.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/explanation.rst b/docs/explanation.rst index 35e37ac54..2a6b11864 100644 --- a/docs/explanation.rst +++ b/docs/explanation.rst @@ -147,12 +147,14 @@ in, and what kind of time you're asking for:: The conventional time scale for working with pulsars, and the one PINT uses, is Barycentric Dynamical Time (TDB). You should be aware that there -is another time scale, not yet supported in PINT, called Barycentric -Coordinate Time (TCB), and that because of different handling of -relativistic corrections, it does not advance at the same rate as TDB +is another time scale, not yet fully supported in PINT, called Barycentric +Coordinate Time (TCB). Because of different handling of relativistic +corrections, the TCB timescale does not advance at the same rate as TDB (there is also a many-second offset). TEMPO2 uses TCB by default, so you may encounter pulsar timing models or even measurements that use -TCB. PINT will attempt to detect this and let you know. +TCB. PINT provides a command line tool `tcb2tdb` to approximately convert +TCB timing models to TDB. PINT can also optionally convert TCB timing models +to TDB (approximately) upon read. Note that the need for leap seconds is because the Earth's rotation is somewhat erratic - no, we're not about to be thrown off, but its From 0976fa4d7489c281546fcd06173f1d45174bf231 Mon Sep 17 00:00:00 2001 From: Abhimanyu Susobhanan Date: Thu, 6 Apr 2023 11:31:28 -0500 Subject: [PATCH 51/51] fix test --- docs/explanation.rst | 2 +- tests/test_tcb2tdb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/explanation.rst b/docs/explanation.rst index 2a6b11864..a005c9bc5 100644 --- a/docs/explanation.rst +++ b/docs/explanation.rst @@ -154,7 +154,7 @@ corrections, the TCB timescale does not advance at the same rate as TDB you may encounter pulsar timing models or even measurements that use TCB. PINT provides a command line tool `tcb2tdb` to approximately convert TCB timing models to TDB. PINT can also optionally convert TCB timing models -to TDB (approximately) upon read. +to TDB (approximately) upon read. Note that the need for leap seconds is because the Earth's rotation is somewhat erratic - no, we're not about to be thrown off, but its diff --git a/tests/test_tcb2tdb.py b/tests/test_tcb2tdb.py index 11ab50835..34e7373a1 100644 --- a/tests/test_tcb2tdb.py +++ b/tests/test_tcb2tdb.py @@ -43,7 +43,7 @@ def test_convert_units(backwards): with pytest.raises(ValueError): m = ModelBuilder()(StringIO(simplepar)) - m = ModelBuilder()(StringIO(simplepar), allow_tcb=True) + m = ModelBuilder()(StringIO(simplepar), allow_tcb="raw") f0_tcb = m.F0.value pb_tcb = m.PB.value convert_tcb_tdb(m, backwards=backwards)