How to do a number of things users have asked about

Quick solutions to common tasks (taken from #pint and elsewhere)

How to upgrade PINT

With pip:

pip install -U pint-pulsar

With conda:

conda update pint-pulsar

How to check out some user’s particular branch for testing:

If you wish to checkout branch testbranch from user pintuser:

git checkout -b pintuser-testbranch master
git pull https://github.com/pintuser/PINT.git testbranch

The first command makes a new local branch with name pintuser-testbranch from the master branch. The second pulls the remote branch from the desired user’s fork into that local branch. You may still need to install/reinstall that branch, depending on how you have things set up (so pip install . or pip install -e ., where the later keeps the files in-place for faster developing).

How to go to a specific version of PINT

With pip:

pip install -U pint-pulsar==0.8.4

or similar.

With conda:

conda install pint-pulsar=0.8.4

or similar.

Find data files for testing/tutorials

The data files (par and tim) associated with the tutorials and other examples can be located via pint.config.examplefile() (available via the pint.config module):

import pint.config
fullfilename = pint.config.examplefile(filename)

For example, the file NGC6440E.par from the Time a Pulsar notebook can be found via:

import pint
fullfilename = pint.config.examplefile("NGC6440E.par")

Load a par file

To load a par file:

from pint.models import get_model
m = get_model(parfile)

Load a tim file

To load a tim file:

from pint.toa import get_TOAs
t = get_TOAs(timfile)

Note that a par file may contain information, like which solar system ephemeris to use, that affects how a tim file should be loaded:

t = get_TOAs(timfile, model=model)

Load a tim and par file together

To load both:

from pint.models import get_model_and_toas
m, t = get_model_and_toas(parfile, timfile)

Create TOAs from an array of times

A pint.toa.TOA object represents a single TOA as an object that contains both a time and a location, along with optional information like frequency, measurement error, etc. So each TOA object should only contain a single time, since otherwise the location information would be ambiguous. If you wish to create TOAs from a astropy.time.Time object containing multiple times, you can do:

import numpy as np
from astropy import units as u, constants as c
from pint import pulsar_mjd
from astropy.time import Time
from pint import toa

t = Time(np.array([55000, 56000]), scale="utc", format="pulsar_mjd")
obs = "gbt"

toas = toa.get_TOAs_array(t, obs)

Note that we import pint.pulsar_mjd to allow the pulsar_mjd format, designed to deal properly with leap seconds. We use pint.toa.get_TOAs_array() to make sure clock corrections are applied when constructing the TOAs. Other information like errors, frequencies, and flags can be added. You can also merge multiple data-sets with pint.toa.merge_TOAs()

Get the red noise basis functions and the corresponding coefficients out of a PINT fitter object

…?

Select TOAs

You can index by column name into the TOAs object, so you can do toas["observatory"] or whatever the column is called; and that’s an array, so you can do toas["observatory"]=="arecibo" to get a Boolean array; and you can index with boolean arrays, so you can do toas[toas["observatory"]=="arecibo"] to get a new TOAs object referencing a subset.

Modify TOAs

The TOAs have a table with mjd, mjd_float, tdb, and tdbld columns. To modify them all safely and consistently the best way is to use:

t.adjust_TOAs(dt)

where dt is an astropy.time.TimeDelta object. This function does not change the pulse numbers column, if present, but does recompute mjd_float, the TDB times, and the observatory positions and velocities.

Avoid “KeyError: ‘obs_jupiter_pos’ error when trying to grab residuals?”

You need to have the TOAs object compute the positions of the planets and add them to the table:

ts.compute_posvels(ephem,planets=True)

This should be done automatically if you load your TOAs with the pint.toa.get_TOAs() or pint.models.model_builder.get_model_and_toas()

Convert from ELAT/ELONG <-> RA/DEC if I have a timing model

If model is in ecliptic coordinates:

model.as_ICRS(epoch=epoch)

which will give it to you as a model with pint.models.astrometry.AstrometryEquatorial components at the requested epoch. Similarly:

model.as_ECL(epoch=epoch)

does the same for pint.models.astrometry.AstrometryEcliptic (with an optional specification of the obliquity).

Convert between binary models

If m is your initial model, say an ELL1 binary:

from pint import binaryconvert
m2 = binaryconvert.convert_binary(m, "DD")

will convert it to a DD binary.

Some binary types need additional parameters. For ELL1H, you can set the number of harmonics and whether to use H4 or STIGMA:

m2 = binaryconvert.convert_binary(m, "ELL1H", NHARMS=3, useSTIGMA=True)

For DDK, you can set OM (known as KOM):

m2 = binaryconvert.convert_binary(mDD, "DDK", KOM=12 * u.deg)

Parameter values and uncertainties will be converted. It will also make a best-guess as to which parameters should be frozen, but it can still be useful to refit with the new model and check which parameters are fit.

Note

The T2 model from tempo2 is not implemented, as this is a complex model that actually encapsulates several models. The best practice is to change the model to the actual underlying model (ELL1, DD, BT, etc).

These conversions can also be done on the command line using convert_parfile:

convert_parfile --binary=DD ell1.par -o dd.par

Add a jump programmatically

PINT can handle jumps in the model outside a par file. An example is:

import numpy as np
from astropy import units as u, constants as c
from pint.models import get_model, get_model_and_toas, parameter
from pint import fitter
from pint.models import PhaseJump
import pint.config

m, t = get_model_and_toas(pint.config.examplefile("NGC6440E.par"),
                          pint.config.examplefile("NGC6440E.tim"))

# fit the nominal model
f = fitter.WLSFitter(toas=t, model=m)
f.fit_toas()

# group TOAs: find clusters with gaps of <2h
clusters = t.get_clusters(add_column=True)

# put in the pulse numbers based on the previous fit
t.compute_pulse_numbers(f.model)
# just for a test, add an offset to a set of TOAs
t['delta_pulse_number'][clusters==3]+=3

# now fit without a jump
fnojump = fitter.WLSFitter(toas=t, model=m, track_mode="use_pulse_numbers")
fnojump.fit_toas()


# add the Jump Component to the model
m.add_component(PhaseJump(), validate=False)

# now add the actual jump
# it can be keyed on any parameter that maskParameter will accept
# here we will use a range of MJDs
par = parameter.maskParameter(
    "JUMP",
    key="mjd",
    value=0.0,
    key_value=[t[clusters==3].get_mjds().min().value,
               t[clusters==3].get_mjds().max().value],
    units=u.s,
    frozen=False,
    )
m.components['PhaseJump'].add_param(par, setup=True)

# you can also do it indirectly through the flags as:
# m.components["PhaseJump"].add_jump_and_flags(t.table["flags"][clusters == 3])

# and fit with a jump
fjump = fitter.WLSFitter(toas=t, model=m, track_mode="use_pulse_numbers")
fjump.fit_toas()

print(f"Original chi^2 = {f.resids.calc_chi2():.2f} for {f.resids.dof} DOF")
print(f"After adding 3 rotations to some TOAs, chi^2 = {fnojump.resids.calc_chi2():.2f} for {fnojump.resids.dof} DOF")
print(f"Then after adding a jump to those TOAs, chi^2 = {fjump.resids.calc_chi2():.2f} for {fjump.resids.dof} DOF")
print(f"Best-fit value of the jump is {fjump.model.JUMP1.quantity} +/- {fjump.model.JUMP1.uncertainty} ({(fjump.model.JUMP1.quantity*fjump.model.F0.quantity).decompose():.3f} +/- {(fjump.model.JUMP1.uncertainty*fjump.model.F0.quantity).decompose():.3f} rotations)")

which returns:

Original chi^2 = 59.57 for 56 DOF
After adding 3 rotations to some TOAs, chi^2 = 19136746.30 for 56 DOF
Then after adding a jump to those TOAs, chi^2 = 56.60 for 55 DOF
Best-fit value of the jump is -0.048772786677935796 s +/- 1.114921182802775e-05 s (-2.999 +/- 0.001 rotations)

showing that the offset we applied has been absorbed by the jump (plus a little extra, so chi^2 has actually improved).

See pint.models.parameter.maskParameter documentation on the ways to select the TOAs.

Choose a fitter

Use pint.fitter.Fitter.auto():

f = pint.fitter.Fitter.auto(toas, model)

Include logging in a script

PINT now uses loguru for its logging. To get this working within a script, try:

import pint.logging
from loguru import logger as log

pint.logging.setup(sink=sys.stderr, level="WARNING", usecolors=True)

That sets up the logging and ensures it will play nicely with the rest of PINT. You can customize the level, the destination (e.g., file, stderr, …) and format. The pint.logging.LogFilter suppresses some INFO/DEBUG messages that can clog up your screen: you can make a custom filter as well to add/remove messages.

If you want to include a standard way to control the level using command line arguments, you can do:

parser.add_argument(
    "--log-level",
    type=str,
    choices=("TRACE", "DEBUG", "INFO", "WARNING", "ERROR"),
    default=pint.logging.script_level,
    help="Logging level",
    dest="loglevel",
)
...
pint.logging.setup(level=args.loglevel, ...)

assuming you are using argparse. Note that loguru doesn’t let you change existing loggers: you should just remove and add (which the pint.logging.setup() function does).

Make PINT stop reporting a particular warning

If PINT keeps emitting a warning you know is irrelevant from somewhere inside your code, you can disable that specific warning coming from that place. For example if you are reading a par file with T2CMETHOD set but you know that’s fine, you can shut off the message about T2CMETHOD while you’re loading the file:

with warnings.catch_warnings():
    warnings.filterwarnings("ignore", message=r".*T2CMETHOD.*")
    model = get_model(os.path.join(datadir, "J1614-2230_NANOGrav_12yv3.wb.gls.par"))