This Jupyter notebook can be downloaded from understanding_parameters.ipynb, or viewed as a python script at understanding_parameters.py.

Understanding Parameters

[1]:
import pint.models
import pint.models.parameter as pp
import astropy.units as u
from astropy.time import Time
import pint.config
import pint.logging

pint.logging.setup(level="INFO")
[1]:
1
[2]:
# Load a model to play with
model = pint.models.get_model(
    pint.config.examplefile("B1855+09_NANOGrav_dfg+12_TAI.par")
)
[3]:
# This model has a large number of parameters of various types
model.params
[3]:
['PSR',
 'TRACK',
 'EPHEM',
 'CLOCK',
 'UNITS',
 'START',
 'FINISH',
 'RM',
 'INFO',
 'TIMEEPH',
 'T2CMETHOD',
 'BINARY',
 'DILATEFREQ',
 'DMDATA',
 'NTOA',
 'CHI2',
 'CHI2R',
 'TRES',
 'DMRES',
 'POSEPOCH',
 'PX',
 'RAJ',
 'DECJ',
 'PMRA',
 'PMDEC',
 'F0',
 'PEPOCH',
 'F1',
 'CORRECT_TROPOSPHERE',
 'PLANET_SHAPIRO',
 'NE_SW',
 'SWP',
 'SWM',
 'DM',
 'DM1',
 'DMEPOCH',
 'DMX',
 'DMX_0001',
 'DMXR1_0001',
 'DMXR2_0001',
 'DMX_0002',
 'DMX_0003',
 'DMX_0004',
 'DMX_0005',
 'DMX_0006',
 'DMX_0007',
 'DMX_0008',
 'DMX_0009',
 'DMX_0010',
 'DMX_0011',
 'DMX_0012',
 'DMX_0013',
 'DMX_0014',
 'DMX_0015',
 'DMX_0016',
 'DMX_0017',
 'DMX_0018',
 'DMX_0019',
 'DMX_0020',
 'DMX_0021',
 'DMX_0022',
 'DMX_0023',
 'DMX_0024',
 'DMX_0025',
 'DMX_0026',
 'DMX_0027',
 'DMX_0028',
 'DMX_0029',
 'DMX_0030',
 'DMXR1_0002',
 'DMXR1_0003',
 'DMXR1_0004',
 'DMXR1_0005',
 'DMXR1_0006',
 'DMXR1_0007',
 'DMXR1_0008',
 'DMXR1_0009',
 'DMXR1_0010',
 'DMXR1_0011',
 'DMXR1_0012',
 'DMXR1_0013',
 'DMXR1_0014',
 'DMXR1_0015',
 'DMXR1_0016',
 'DMXR1_0017',
 'DMXR1_0018',
 'DMXR1_0019',
 'DMXR1_0020',
 'DMXR1_0021',
 'DMXR1_0022',
 'DMXR1_0023',
 'DMXR1_0024',
 'DMXR1_0025',
 'DMXR1_0026',
 'DMXR1_0027',
 'DMXR1_0028',
 'DMXR1_0029',
 'DMXR1_0030',
 'DMXR2_0002',
 'DMXR2_0003',
 'DMXR2_0004',
 'DMXR2_0005',
 'DMXR2_0006',
 'DMXR2_0007',
 'DMXR2_0008',
 'DMXR2_0009',
 'DMXR2_0010',
 'DMXR2_0011',
 'DMXR2_0012',
 'DMXR2_0013',
 'DMXR2_0014',
 'DMXR2_0015',
 'DMXR2_0016',
 'DMXR2_0017',
 'DMXR2_0018',
 'DMXR2_0019',
 'DMXR2_0020',
 'DMXR2_0021',
 'DMXR2_0022',
 'DMXR2_0023',
 'DMXR2_0024',
 'DMXR2_0025',
 'DMXR2_0026',
 'DMXR2_0027',
 'DMXR2_0028',
 'DMXR2_0029',
 'DMXR2_0030',
 'PB',
 'PBDOT',
 'A1',
 'A1DOT',
 'ECC',
 'EDOT',
 'T0',
 'OM',
 'OMDOT',
 'M2',
 'SINI',
 'FB0',
 'A0',
 'B0',
 'GAMMA',
 'DR',
 'DTH',
 'TZRMJD',
 'TZRSITE',
 'TZRFRQ',
 'JUMP1',
 'JUMP2',
 'JUMP3',
 'JUMP4',
 'JUMP5',
 'JUMP6',
 'JUMP7',
 'JUMP8',
 'JUMP9',
 'JUMP10',
 'JUMP11',
 'JUMP12',
 'JUMP13',
 'JUMP14',
 'JUMP15',
 'JUMP16',
 'JUMP17',
 'JUMP18',
 'JUMP19',
 'JUMP20',
 'JUMP21']

Attributes of Parameters

Each parameter has attributes that specify the name and type of the parameter, its units, and the uncertainty. The par.quantity and par.uncertainty are both astropy quantities with units. If you need the bare values, access par.value and par.uncertainty_value, which will be numerical values in the units of par.units

Let’s look at those for each of the types of parameters in this model.

[4]:
printed = []
for p in model.params:
    par = getattr(model, p)
    if type(par) in printed:
        continue
    print("Name           ", par.name)
    print("Type           ", type(par))
    print("Quantity       ", par.quantity, type(par.quantity))
    print("Value          ", par.value)
    print("units          ", par.units)
    print("Uncertainty    ", par.uncertainty)
    print("Uncertainty_value", par.uncertainty_value)
    print("Summary        ", par)
    print("Parfile Style  ", par.as_parfile_line())
    print()
    printed.append(type(par))
Name            PSR
Type            <class 'pint.models.parameter.strParameter'>
Quantity        1855+09 <class 'str'>
Value           1855+09
units           None
Uncertainty     None
Uncertainty_value None
Summary         strParameter(   PSR                 1855+09           frozen=True)
Parfile Style   PSRJ                              1855+09


Name            START
Type            <class 'pint.models.parameter.MJDParameter'>
Quantity        53358.7264648894852140 <class 'astropy.time.core.Time'>
Value           53358.726464889485214
units           d
Uncertainty     None
Uncertainty_value None
Summary         MJDParameter(   START               53358.7264648894852140 (d) frozen=True)
Parfile Style   START              53358.7264648894852140


Name            RM
Type            <class 'pint.models.parameter.floatParameter'>
Quantity        None <class 'NoneType'>
Value           None
units           rad / m2
Uncertainty     None
Uncertainty_value None
Summary         floatParameter( RM                  UNSET
Parfile Style

Name            DILATEFREQ
Type            <class 'pint.models.parameter.boolParameter'>
Quantity        False <class 'bool'>
Value           False
units           None
Uncertainty     None
Uncertainty_value None
Summary         boolParameter(  DILATEFREQ          N                 frozen=True)
Parfile Style   DILATEFREQ                              N


Name            NTOA
Type            <class 'pint.models.parameter.intParameter'>
Quantity        702 <class 'int'>
Value           702
units           None
Uncertainty     None
Uncertainty_value None
Summary         intParameter(   NTOA                702               frozen=True)
Parfile Style   NTOA                                  702


Name            RAJ
Type            <class 'pint.models.parameter.AngleParameter'>
Quantity        18h57m36.3932884s <class 'astropy.coordinates.angles.core.Angle'>
Value           18.960109246777776
units           hourangle
Uncertainty     0h00m00.00002603s
Uncertainty_value 7.2298063352084138888e-09
Summary         AngleParameter( RAJ                 18:57:36.39328840 (hourangle) +/- 0h00m00.00002603s frozen=False)
Parfile Style   RAJ                     18:57:36.39328840 1 0.00002602730280675029


Name            F0
Type            <class 'pint.models.parameter.prefixParameter'>
Quantity        186.49408156698235 Hz <class 'astropy.units.quantity.Quantity'>
Value           186.49408156698235146
units           Hz
Uncertainty     6.98911818e-12 Hz
Uncertainty_value 6.98911818e-12
Summary         floatParameter( F0                  186.49408156698235146 (Hz) +/- 6.98911818e-12 Hz frozen=False)
Parfile Style   F0                  186.49408156698235146 1 6.98911818e-12


Name            JUMP1
Type            <class 'pint.models.parameter.maskParameter'>
Quantity        7.6456527699426e-07 s <class 'astropy.units.quantity.Quantity'>
Value           7.6456527699426e-07
units           s
Uncertainty     0.0 s
Uncertainty_value 0.0
Summary         maskParameter(JUMP1 -chanid asp_424 7.6456527699426e-07 +/- 0.0 s (s))
Parfile Style   JUMP            -chanid asp_424       7.6456527699426e-07 1 0.0


Note that DMX_nnnn is an example of a prefixParameter. These are parameters that are indexed by a numerical value and a componenent can have an arbitrary number of them. In some cases, like Fn they are coefficients of a Taylor expansion and so all indices up to the maximum must be present. For others, like DMX_nnnn some indices can be missing without a problem.

prefixParameters can be used to hold indexed parameters of various types ( float, bool, str, MJD, angle ). Each one will instantiate a parameter of that type as par.param_comp. When you print the parameter it looks like the param_comp type.

[5]:
# Note that for each instance of a prefix parameter is of type `prefixParameter`
print("Type = ", type(model.DMX_0016))
print("param_comp type = ", type(model.DMX_0016.param_comp))
print("Printing gives : ", model.DMX_0016)
Type =  <class 'pint.models.parameter.prefixParameter'>
param_comp type =  <class 'pint.models.parameter.floatParameter'>
Printing gives :  floatParameter( DMX_0016            0.0009456692079715063 (pc / cm3) +/- 4.382957805166126e-05 pc / cm3 frozen=False)

Constructing a parameter

You can make a Parameter instance by calling its constructor

[6]:
# You can specify the vaue as a number
t = pp.floatParameter(name="TEST", value=100, units="Hz", uncertainty=0.03)
print(t)
floatParameter( TEST                100.0             (Hz) +/- 0.03 Hz frozen=True)
[7]:
# Or as a string that will be parsed
t2 = pp.floatParameter(name="TEST", value="200", units="Hz", uncertainty=".04")
print(t2)
floatParameter( TEST                200.0             (Hz) +/- 0.04 Hz frozen=True)
[8]:
# Or as an astropy Quantity with units (this is the preferred method!)
t3 = pp.floatParameter(
    name="TEST", value=0.3 * u.kHz, units="Hz", uncertainty=4e-5 * u.kHz
)
print(t3)
print(t3.quantity)
print(t3.value)
print(t3.uncertainty)
print(t3.uncertainty_value)
floatParameter( TEST                300.0             (Hz) +/- 0.04 Hz frozen=True)
0.3 kHz
300.0
0.04 Hz
0.04

Setting Parameters

The value of a parameter can be set in multiple ways. As usual, the preferred method is to set it using an astropy Quantity, so units will be checked and respected

[9]:
par = model.F0
# Here we set it using a Quantity in kHz. Because astropy Quantities are used, it does the right thing!
par.quantity = 0.3 * u.kHz
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
Quantity        0.3 kHz <class 'astropy.units.quantity.Quantity'>
Value           299.9999999999999889
floatParameter( F0                  299.9999999999999889 (Hz) +/- 6.98911818e-12 Hz frozen=False)
[10]:
# Here we set it with a bare number, which is interpreted as being in the units `par.units`
print(par)
par.quantity = 200
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
floatParameter( F0                  299.9999999999999889 (Hz) +/- 6.98911818e-12 Hz frozen=False)
Quantity        200.0 Hz <class 'astropy.units.quantity.Quantity'>
Value           200.0
floatParameter( F0                  200.0             (Hz) +/- 6.98911818e-12 Hz frozen=False)
[11]:
# If you try to set the parameter to a quantity that isn't compatible with the units, it raises an exception
try:
    print(par)
    par.value = 100 * u.second  # SET F0 to seconds as time.
    print("Quantity       ", par.quantity, type(par.quantity))
    print("Value          ", par.value)
    print(par)
except u.UnitConversionError as e:
    print("Exception raised:", e)
else:
    raise ValueError("That was supposed to raise an exception!")
floatParameter( F0                  200.0             (Hz) +/- 6.98911818e-12 Hz frozen=False)
Exception raised: 's' (time) and 'Hz' (frequency) are not convertible

MJD parameters

These parameters hold a date as an astropy Time object. Numbers will be interpreted as MJDs in the default time scale of the parameter (which is UTC for the TZRMJD parameter)

[12]:
par = model.TZRMJD
print(par)
par.quantity = 54000
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
par.quantity
MJDParameter(   TZRMJD              54177.5083593432625578 (d) frozen=True)
Quantity        54000.0 <class 'astropy.time.core.Time'>
Value           54000.0
MJDParameter(   TZRMJD              54000.0000000000000000 (d) frozen=True)
[12]:
<Time object: scale='utc' format='pulsar_mjd' value=54000.0>
[13]:
# And of course, you can set them with a `Time` object
par.quantity = Time.now()
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
par.quantity
Quantity        2024-04-22 16:25:02.030734 <class 'astropy.time.core.Time'>
Value           60422.68405128164352
MJDParameter(   TZRMJD              60422.6840512816435185 (d) frozen=True)
[13]:
<Time object: scale='utc' format='datetime' value=2024-04-22 16:25:02.030734>
[14]:
# I wonder if this should get converted to UTC?
par.quantity = Time(58000.0, format="mjd", scale="tdb")
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
par.quantity
Quantity        58000.0 <class 'astropy.time.core.Time'>
Value           58000.0
MJDParameter(   TZRMJD              58000.0000000000000000 (d) frozen=True)
[14]:
<Time object: scale='tdb' format='mjd' value=58000.0>

AngleParameters

These store quanities as angles using astropy coordinates

[15]:
# The unit for RAJ is hourangle
par = model.RAJ
print(par)
par.quantity = 12
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
AngleParameter( RAJ                 18:57:36.39328840 (hourangle) +/- 0h00m00.00002603s frozen=False)
Quantity        12h00m00s <class 'astropy.coordinates.angles.core.Angle'>
Value           12.0
AngleParameter( RAJ                 12:00:00.00000000 (hourangle) +/- 0h00m00.00002603s frozen=False)
[16]:
# Best practice is to set using a quantity with units
print(par)
par.quantity = 30.5 * u.hourangle
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
par.quantity
AngleParameter( RAJ                 12:00:00.00000000 (hourangle) +/- 0h00m00.00002603s frozen=False)
Quantity        30h30m00s <class 'astropy.coordinates.angles.core.Angle'>
Value           30.5
AngleParameter( RAJ                 30:30:00.00000000 (hourangle) +/- 0h00m00.00002603s frozen=False)
[16]:
$30^{\mathrm{h}}30^{\mathrm{m}}00^{\mathrm{s}}$
[17]:
# But a string will work
par.quantity = "20:30:00"
print("Quantity       ", par.quantity, type(par.quantity))
print("Value          ", par.value)
print(par)
par.quantity
Quantity        20h30m00s <class 'astropy.coordinates.angles.core.Angle'>
Value           20.5
AngleParameter( RAJ                 20:30:00.00000000 (hourangle) +/- 0h00m00.00002603s frozen=False)
[17]:
$20^{\mathrm{h}}30^{\mathrm{m}}00^{\mathrm{s}}$
[18]:
# And the units can be anything that is convertable to hourangle
print(par)
par.quantity = 30 * u.deg
print("Quantity       ", par.quantity, type(par.quantity))
print("Quantity in deg", par.quantity.to(u.deg))
print("Value          ", par.value)
print(par)
par.quantity
AngleParameter( RAJ                 20:30:00.00000000 (hourangle) +/- 0h00m00.00002603s frozen=False)
Quantity        2h00m00s <class 'astropy.coordinates.angles.core.Angle'>
Quantity in deg 30d00m00s
Value           2.0000000000000004
AngleParameter( RAJ                 2:00:00.00000000  (hourangle) +/- 0h00m00.00002603s frozen=False)
[18]:
$2^{\mathrm{h}}00^{\mathrm{m}}00^{\mathrm{s}}$
[19]:
# Here, setting RAJ to an incompatible unit will raise an exception
try:
    # Example for RAJ
    print(par)
    par.quantity = 30 * u.hour  # Here hour is in the unit of time, not hourangle
    print("Quantity       ", par.quantity, type(par.quantity))
    print(par)
    par.quantity
except u.UnitConversionError as e:
    print("Exception raised:", e)
else:
    raise ValueError("That was supposed to raise an exception!")
AngleParameter( RAJ                 2:00:00.00000000  (hourangle) +/- 0h00m00.00002603s frozen=False)
Exception raised: 'h' (time) and 'hourangle' (angle) are not convertible