Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of WaveX model #1609

Merged
merged 70 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
fefe0e5
Adding wavex model
LuluAgazie Jul 5, 2023
28ff9a0
add wavex_delay
LuluAgazie Jul 5, 2023
06e2b6a
commit changes
LuluAgazie Jul 6, 2023
71c0b0c
uncomment register
LuluAgazie Jul 7, 2023
3a2424d
added comments
LuluAgazie Jul 7, 2023
fb4854c
comments added
LuluAgazie Jul 7, 2023
4ca631d
started add_wavex_components
LuluAgazie Jul 7, 2023
91edc28
add wavex component
LuluAgazie Jul 7, 2023
e4731f2
fixed add_wavex_component(s)
LuluAgazie Jul 7, 2023
d023865
added remove_wavex_component
LuluAgazie Jul 7, 2023
8c7d58d
add get_indices
LuluAgazie Jul 7, 2023
df01593
Add validate and setup
LuluAgazie Jul 7, 2023
bb82654
fixed delay function
LuluAgazie Jul 7, 2023
ba1ec72
add inital component
LuluAgazie Jul 10, 2023
c07e8df
registering new class
LuluAgazie Jul 10, 2023
5311f2c
added category
LuluAgazie Jul 10, 2023
6fd886e
add comma
LuluAgazie Jul 10, 2023
d923234
Fixed wavex_delay
LuluAgazie Jul 11, 2023
1778ca4
fix validate
LuluAgazie Jul 11, 2023
8cb296f
Add validate test and start tests
LuluAgazie Jul 11, 2023
c42b050
Adding test
LuluAgazie Jul 17, 2023
518d60d
Validate that num wxsin = num wxcos
LuluAgazie Jul 17, 2023
26a8e85
Add warnings to validate
LuluAgazie Jul 18, 2023
2044907
Adding tests
LuluAgazie Jul 18, 2023
c005106
Additional tests
LuluAgazie Jul 18, 2023
a633b2c
Small fixes to tests
LuluAgazie Jul 18, 2023
cdc4583
Added amplitude fitting
LuluAgazie Jul 19, 2023
9cd23d1
fixed base_phase
LuluAgazie Jul 27, 2023
7188ccc
Add fitter and deriv test
LuluAgazie Aug 15, 2023
9397bcf
work on utils functions
LuluAgazie Aug 15, 2023
1399c99
Add setup helper function
LuluAgazie Aug 15, 2023
66b72ee
Fixes to setup utils function
LuluAgazie Aug 15, 2023
2c3faaf
Fixed astropy table issue
LuluAgazie Aug 16, 2023
43c960d
Added ValueError to utils
LuluAgazie Aug 16, 2023
e3dd81a
Added translate wave to wavex utils
LuluAgazie Aug 16, 2023
027e6dd
Fix wave epoch
LuluAgazie Aug 16, 2023
1aa7062
add deepcopy
LuluAgazie Aug 17, 2023
0aef628
Added myself to authors
LuluAgazie Aug 17, 2023
cc34402
changelog entry
LuluAgazie Aug 17, 2023
4e4709a
Merge branch 'master' into wavex_model
dlakaplan Aug 17, 2023
bbbd423
Added docstring fixed freqs
LuluAgazie Aug 17, 2023
f2011c5
Removed old comment
LuluAgazie Aug 17, 2023
503ff32
Edited docstrings
LuluAgazie Aug 17, 2023
7623d79
Change wavex_setup to act on model
LuluAgazie Aug 21, 2023
d580c27
Changed wavex_setup exception to warning
LuluAgazie Aug 21, 2023
4ab24af
wavex_setup returns new model
LuluAgazie Aug 21, 2023
e695cbd
fixed error
LuluAgazie Aug 21, 2023
f1286be
More utils functions
LuluAgazie Aug 21, 2023
e891982
inverse translate
LuluAgazie Aug 22, 2023
e115091
fixed import issue
LuluAgazie Aug 22, 2023
d32b965
inverse translate function with additional fixes
LuluAgazie Aug 22, 2023
debc69e
changed atol for inverse translate
LuluAgazie Aug 22, 2023
0448eec
Fixed negative amplitudes
LuluAgazie Aug 22, 2023
cb55db7
Fixed wave number error
LuluAgazie Aug 22, 2023
b0ad68c
Fixing errors
LuluAgazie Aug 24, 2023
88cbecb
Changes to facilitate inverse translate
LuluAgazie Aug 25, 2023
2eccfe7
Roundtrip test and minor edits
LuluAgazie Aug 25, 2023
5521f49
Editted comments
LuluAgazie Aug 25, 2023
b00ff15
Merge branch 'master' into wavex_model
LuluAgazie Aug 25, 2023
4c1e233
Merge branch 'nanograv:master' into wavex_model
LuluAgazie Aug 27, 2023
f349c87
Minor edits from PR comments
LuluAgazie Aug 28, 2023
3bd18fb
Merge branch 'wavex_model' of https://github.com/LuluAgazie/PINT into…
LuluAgazie Aug 28, 2023
6cd0cd9
Fixed changelog
LuluAgazie Aug 28, 2023
9e3cd5b
Changed to fstrings
LuluAgazie Aug 28, 2023
7665a78
Merge branch 'master' into wavex_model
abhisrkckl Aug 29, 2023
62de390
Added wave.py changes to changelog
LuluAgazie Aug 29, 2023
1586dac
Added more tests and edited documentation
LuluAgazie Aug 29, 2023
e1b0ee7
Added unit conversion test
LuluAgazie Aug 29, 2023
4e12d0c
More tests
LuluAgazie Aug 31, 2023
325940e
Merge branch 'master' into wavex_model
LuluAgazie Aug 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/pint/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from pint.models.timing_model import DEFAULT_ORDER, TimingModel
from pint.models.troposphere_delay import TroposphereDelay
from pint.models.wave import Wave
from pint.models.wavex import WaveX

# Define a standard basic model
StandardTimingModel = TimingModel(
Expand Down
1 change: 1 addition & 0 deletions src/pint/models/timing_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"spindown",
"phase_jump",
"wave",
"wavex",
]


Expand Down
372 changes: 372 additions & 0 deletions src/pint/models/wavex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@
"""Delays expressed as a sum of sinusoids."""
import astropy.units as u
import numpy as np
from loguru import logger as log
from warnings import warn

from pint.models.parameter import MJDParameter, floatParameter, prefixParameter
from pint.models.timing_model import DelayComponent, MissingParameter


class WaveX(DelayComponent):
"""Implementation of the wave model as a delay correction

Delays are expressed as a sum of sinusoids.

Used for decomposition of timing noise into a series of sine/cosine components with the amplitudes as fitted parameters.

Parameters supported:

.. paramtable::
:class: pint.models.wavex.WaveX

This is an extension of the L13 method described in Lentati et al., 2013
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
"""

register = True
category = "wavex"

def __init__(self):
super().__init__()
self.add_param(
MJDParameter(
name="WXEPOCH",
description="Reference epoch for wave delay solution",
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
time_scale="tdb",
)
)
self.add_wavex_component(0.1, index=1, wxsin=0, wxcos=0, frozen=False)
self.set_special_params(["WXFREQ_0001", "WXSIN_0001", "WXCOS_0001"])
self.delay_funcs_component += [self.wavex_delay]

# Register derivative functions PLACEHOLDER

def add_wavex_component(self, wxfreq, index=None, wxsin=0, wxcos=0, frozen=True):
"""Add WaveX component

Parameters
----------

wxfreq : float or astropy.quantity.Quantity
Base frequency for WaveX component
index : int, None
Interger label for WaveX component. If None, will increment largest used index by 1.
wxsin : float or astropy.quantity.Quantity
Sine amplitude for WaveX component
wxcos : float or astropy.quantity.Quantity
Cosine amplitude for WaveX component
frozen : iterable of bool or bool
Indicates whether wavex will be fit

Returns
-------

index : int
Index that has been assigned to new WaveX component
"""

#### If index is None, increment the current max WaveX index by 1. Increment using WXFREQ
if index is None:
dct = self.get_prefix_mapping_component("WXFREQ_")
index = np.max(list(dct.keys())) + 1

Check warning on line 71 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L70-L71

Added lines #L70 - L71 were not covered by tests
i = f"{int(index):04d}"

if int(index) in self.get_prefix_mapping_component("WXFREQ_"):
raise ValueError(

Check warning on line 75 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L75

Added line #L75 was not covered by tests
f"Index '{index}' is already in use in this model. Please choose another"
)

if isinstance(wxsin, u.quantity.Quantity):
wxsin = wxsin.to_value(u.s)

Check warning on line 80 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L80

Added line #L80 was not covered by tests
if isinstance(wxcos, u.quantity.Quantity):
wxcos = wxcos.to_value(u.s)

Check warning on line 82 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L82

Added line #L82 was not covered by tests
if isinstance(wxfreq, u.quantity.Quantity):
# wxfreq = wxfreq.value
wxfreq = wxfreq.to_value(1 / u.d)

Check warning on line 85 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L85

Added line #L85 was not covered by tests
self.add_param(
prefixParameter(
name=f"WXFREQ_{i}",
description="Base frequency of wave delay solution",
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
units="1/d",
value=wxfreq,
parameter_type="float",
)
)
self.add_param(
prefixParameter(
name=f"WXSIN_{i}",
description="Sine amplitudes for wave delay function",
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
units="s",
value=wxsin,
frozen=frozen,
parameter_type="float",
)
)
self.add_param(
prefixParameter(
name=f"WXCOS_{i}",
description="Cosine amplitudes for wave delay function",
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
units="s",
value=wxcos,
frozen=frozen,
parameter_type="float",
)
)
self.setup()
self.validate()
return index

def add_wavex_components(
self, wxfreqs, indices=None, wxsins=0, wxcoses=0, frozens=True
):
"""Add WaveX components with specified base frequencies

Parameters
----------

wxfreqs : iterable of float or astropy.quantity.Quantity
Base frequencies for WaveX components
indices : iterable of int, None
Interger labels for WaveX components. If None, will increment largest used index by 1.
wxsins : iterable of float or astropy.quantity.Quantity
Sine amplitudes for WaveX components
wxcoses : iterable of float or astropy.quantity.Quantity
Cosine amplitudes for WaveX components
frozens : iterable of bool or bool
Indicates whether sine adn cosine amplitudes of wavex components will be fit
Returns
-------

indices : list
Indices that have been assigned to new WaveX components
"""

if indices is None:
indices = [None] * len(wxfreqs)

Check warning on line 145 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L145

Added line #L145 was not covered by tests
wxsins = np.atleast_1d(wxsins)
wxcoses = np.atleast_1d(wxcoses)
if len(wxsins) == 1:
wxsins = np.repeat(wxsins, len(wxfreqs))

Check warning on line 149 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L149

Added line #L149 was not covered by tests
if len(wxcoses) == 1:
wxcoses = np.repeat(wxcoses, len(wxfreqs))

Check warning on line 151 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L151

Added line #L151 was not covered by tests
if len(wxsins) != len(wxfreqs):
raise ValueError(
f"Number of base frequencies {len(wxfreqs)} doesn't match number of sine ampltudes {len(wxsins)}"
)
if len(wxcoses) != len(wxfreqs):
raise ValueError(
f"Number of base frequencies {len(wxfreqs)} doesn't match number of cosine ampltudes {len(wxcoses)}"
)
frozens = np.atleast_1d(frozens)
if len(frozens) == 1:
frozens = np.repeat(frozens, len(wxfreqs))
if len(frozens) != len(wxfreqs):
raise ValueError(
f"Number of base frequencies must match number of frozen values"
)
#### If indices is None, increment the current max WaveX index by 1. Increment using WXFREQ
dct = self.get_prefix_mapping_component("WXFREQ_")
last_index = np.max(list(dct.keys()))
added_indices = []
for wxfreq, index, wxsin, wxcos, frozen in zip(
wxfreqs, indices, wxsins, wxcoses, frozens
):
if index is None:
index = last_index + 1
last_index += 1

Check warning on line 176 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L175-L176

Added lines #L175 - L176 were not covered by tests
elif index in list(dct.keys()):
raise ValueError(
f"Attempting to insert WXFREQ_{index:04d} but it already exists"
)
added_indices.append(index)
i = f"{int(index):04d}"

if int(index) in dct:
raise ValueError(

Check warning on line 185 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L185

Added line #L185 was not covered by tests
f"Index '{index}' is already in use in this model. Please choose another"
)
if isinstance(wxfreq, u.quantity.Quantity):
wxfreq = wxfreq.to_value(u.d**-1)

Check warning on line 189 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L189

Added line #L189 was not covered by tests
if isinstance(wxsin, u.quantity.Quantity):
wxsin = wxsin.to_value(u.s)

Check warning on line 191 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L191

Added line #L191 was not covered by tests
if isinstance(wxcos, u.quantity.Quantity):
wxcos = wxcos.to_value(u.s)

Check warning on line 193 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L193

Added line #L193 was not covered by tests
log.trace(f"Adding WXSIN_{i} and WXCOS_{i} at frequency WXFREQ_{i}")
self.add_param(
prefixParameter(
name=f"WXFREQ_{i}",
description="Base frequency of wave delay solution",
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
units="1/d",
value=wxfreq,
parameter_type="float",
)
)
self.add_param(
prefixParameter(
name=f"WXSIN_{i}",
description="Sine amplitudes for wave delay function",
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
units="s",
value=wxsin,
parameter_type="float",
frozen=frozen,
)
)
self.add_param(
prefixParameter(
name=f"WXCOS_{i}",
description="Cosine amplitudes for wave delay function",
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
units="s",
value=wxcos,
parameter_type="float",
frozen=frozen,
)
)
self.setup()
self.validate()
return added_indices

def remove_wavex_component(self, index):
"""Remove all WaveX components associated with a given index or list of indices

Parameters
----------
index : float, int, list, np.ndarray
Number or list/array of numbers corresponding to WaveX indices to be removed from model.
"""

if isinstance(index, (int, float, np.int64)):
indices = [index]
elif isinstance(index, (list, set, np.ndarray)):
indices = index

Check warning on line 240 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L240

Added line #L240 was not covered by tests
else:
raise TypeError(

Check warning on line 242 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L242

Added line #L242 was not covered by tests
f"index most be a float, int, set, list, or array - not {type(index)}"
)
for index in indices:
index_rf = f"{int(index):04d}"
for prefix in ["WXFREQ_", "WXSIN_", "WXCOS_"]:
self.remove_param(prefix + index_rf)
self.validate()

def get_indices(self):
"""Returns an array of intergers corresponding to WaveX component parameters using WXFREQs

Returns
-------
inds : np.ndarray
Array of WaveX indices in model.
"""
inds = [int(p.split("_")[-1]) for p in self.params if "WXFREQ_" in p]
return np.array(inds)

# Initialize setup
def setup(self):
super().setup()

# Get WaveX mapping
# Register WXSIN and WXCOS derivatives PLACEHOLDER
for prefix_par in self.get_params_of_type("prefixParameter"):
if prefix_par.startswith("WXSIN_"):
self.register_deriv_funcs(self.d_wavex_delay_d_WXSIN, prefix_par)
if prefix_par.startswith("WXCOS_"):
self.register_deriv_funcs(self.d_wavex_delay_d_WXCOS, prefix_par)
self.wave_freqs = list(self.get_prefix_mapping_component("WXFREQ_").keys())
self.num_wave_freqs = len(self.wave_freqs)

def validate(self):
"""Validate all the WaveX parameters"""
super().validate()
self.setup()
WXFREQ_mapping = self.get_prefix_mapping_component("WXFREQ_")
WXSIN_mapping = self.get_prefix_mapping_component("WXSIN_")
WXCOS_mapping = self.get_prefix_mapping_component("WXCOS_")
if WXFREQ_mapping.keys() != WXSIN_mapping.keys():
# PLACEHOLDER : Report the mismatched parameters
raise ValueError(

Check warning on line 285 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L285

Added line #L285 was not covered by tests
"WXFREQ_ parameters do not match WXSIN_ parameters."
"Please check your prefixed parameters"
)
if WXFREQ_mapping.keys() != WXCOS_mapping.keys():
raise ValueError(

Check warning on line 290 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L290

Added line #L290 was not covered by tests
"WXFREQ_ parameters do not match WXCOS_ parameters."
"Please check your prefixed parameters"
)
# if len(WXFREQ_mapping.keys()) != len(WXSIN_mapping.keys()):
# raise ValueError(
# "The number of WXFREQ_ parameters do not match the number of WXSIN_ parameters."
# "Please check your prefixed parameters"
# )
# if len(WXFREQ_mapping.keys()) != len(WXCOS_mapping.keys()):
# raise ValueError(
# "The number of WXFREQ_ parameters do not match the number of WXCOS_ parameters."
# "Please check your prefixed parameters"
# )
if WXSIN_mapping.keys() != WXCOS_mapping.keys():
raise ValueError(

Check warning on line 305 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L305

Added line #L305 was not covered by tests
"WXSIN_ parameters do not match WXCOS_ parameters."
"Please check your prefixed parameters"
)
if len(WXSIN_mapping.keys()) != len(WXCOS_mapping.keys()):
raise ValueError(

Check warning on line 310 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L310

Added line #L310 was not covered by tests
"The number of WXSIN_ and WXCOS_ parameters do not match"
"Please check your prefixed parameters"
)
wfreqs = np.zeros(len(WXFREQ_mapping))
for j, index in enumerate(WXFREQ_mapping):
if (getattr(self, f"WXFREQ_{index:04d}").value == 0) or (
getattr(self, f"WXFREQ_{index:04d}").quantity is None
):
raise ValueError(

Check warning on line 319 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L319

Added line #L319 was not covered by tests
f"WXFREQ_{index:04d} is zero or None. Please check your prefixed parameters"
)
if getattr(self, f"WXFREQ_{index:04d}").value < 0.0:
warn(f"Frequency WXFREQ_{index:04d} is negative")

Check warning on line 323 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L323

Added line #L323 was not covered by tests
wfreqs[j] = getattr(self, f"WXFREQ_{index:04d}").value
wfreqs.sort()
if np.any(np.diff(wfreqs) <= (1.0 / (2.0 * 364.25))):
warn("Frequency resolution is greater than 1/yr")

Check warning on line 327 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L327

Added line #L327 was not covered by tests
if self.WXEPOCH.value is None:
if self._parent is not None:
if self._parent.PEPOCH.value is None:
raise MissingParameter(

Check warning on line 331 in src/pint/models/wavex.py

View check run for this annotation

Codecov / codecov/patch

src/pint/models/wavex.py#L331

Added line #L331 was not covered by tests
"WXEPOCH or PEPOCH are required if WaveX is being used"
)
else:
self.WXEPOCH.quantity = self._parent.PEPOCH.quantity

def validate_toas(self, toas):
return super().validate_toas(toas)

def wavex_delay(self, toas, delays):
total_delay = np.zeros(toas.ntoas) * u.s
wave_freqs = self.get_prefix_mapping_component("WXFREQ_")
wave_sins = self.get_prefix_mapping_component("WXSIN_")
wave_cos = self.get_prefix_mapping_component("WXCOS_")

base_phase = (
toas.table["tdbld"].data * u.d - self.WXEPOCH.value * u.d - delays.to(u.d)
)
for idx, param in wave_freqs.items():
freq = getattr(self, param).quantity
wxsin = getattr(self, wave_sins[idx]).quantity
wxcos = getattr(self, wave_cos[idx]).quantity
arg = 2.0 * np.pi * freq * base_phase
total_delay += wxsin * np.sin(arg.value) + wxcos * np.cos(arg.value)
return total_delay

# Placeholder for calculations of derivatives
LuluAgazie marked this conversation as resolved.
Show resolved Hide resolved
def d_wavex_delay_d_WXSIN(self, toas, param, delays, acc_delay=None):
par = getattr(self, param)
freq = getattr(self, f"WXFREQ_{int(par.index):04d}").quantity
base_phase = toas.table["tdbld"].data * u.d - self.WXEPOCH.value * u.d
arg = 2.0 * np.pi * freq * base_phase
deriv = np.sin(arg.value)
return deriv * u.s / par.units

def d_wavex_delay_d_WXCOS(self, toas, param, delays, acc_delay=None):
par = getattr(self, param)
freq = getattr(self, f"WXFREQ_{int(par.index):04d}").quantity
base_phase = toas.table["tdbld"].data * u.d - self.WXEPOCH.value * u.d
arg = 2.0 * np.pi * freq * base_phase
deriv = np.cos(arg.value)
return deriv * u.s / par.units
Loading
Loading