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',
'NE_SW1',
'SWEPOCH',
'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',
'ORBWAVEC0',
'ORBWAVES0',
'ORBWAVE_OM',
'ORBWAVE_EPOCH',
'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) frozen=False)
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.
prefixParameter
s 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,
tcb2tdb_scale_factor=u.Quantity(1),
)
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",
tcb2tdb_scale_factor=u.Quantity(1),
)
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,
tcb2tdb_scale_factor=u.Quantity(1),
)
print(t3)
print(t3.quantity)
print(t3.value)
print(t3.uncertainty)
print(t3.uncertainty_value)
# `tcb2tdb_scale_factor` is a quantity that controls how the parameter transforms
# under the TCB <-> TDB conversion. Please see the Explanation about Timescales and
# the How-To about TCB <-> TDB conversion for more details.
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-12-18 17:15:58.225204 <class 'astropy.time.core.Time'>
Value 60662.719423902824076
MJDParameter( TZRMJD 60662.7194239028240741 (d) frozen=True)
[13]:
<Time object: scale='utc' format='datetime' value=2024-12-18 17:15:58.225204>
[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]:
[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]:
[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]:
[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