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"))