Skip to content
Anne Archibald edited this page Jun 30, 2022 · 24 revisions

Quick solutions to common tasks (taken from #pint and elsewhere). It's also worth checking the PINT HOWTOs, particularly as some things that used to be here were moved there.

How to upgrade PINT:

With pip:

pip install -U pint-pulsar

With conda:

conda update pint-pulsar

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

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

Load a tim file

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

Load a tim and par file together

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

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 get_TOAs() or get_model_and_TOAs()

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

model.as_ICRS(epoch=epoch)

which will give it to you as a model with AstrometryEquatorial components at the requested epoch. Similarly,

model.as_ECL(epoch=epoch)

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

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 maskParamter documentation on the ways to select the TOAs.

Choose a fitter

See Fitter documentation, but to summarize:

Wideband Narrowband
Correlated errors (ECORR or RN) No correlated errors
Downhill WidebandDownhillFitter DownhillGLSFitter DownhillWLSFitter
Not downhill WidebandTOAFitter GLSFitter WLSFitter

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 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 setup() function does).

New pulsar timing tools

Basic Tools

How To: Simple Python PINT Tools

Timing Workflow

How To: Common Timing Workflow