├── libstempo ├── .gitignore ├── __init__.py ├── t2fit-stub.h ├── GWsim-stub.h ├── _find_tempo2.py ├── emcee.py ├── ecc_vs_nharm.txt ├── utils.py ├── data │ ├── B1953+29_NANOGrav_dfg+12.par │ ├── J1909-3744_NANOGrav_dfg+12.par │ └── B1953+29_NANOGrav_dfg+12.tim ├── fit.py ├── eccUtils.py ├── plot.py ├── spharmORFbasis.py ├── multinest.py ├── like.py └── toasim.py ├── .pre-commit-config.yaml ├── tests ├── test_imports.py ├── test_pulsar.py └── test_fakepulsar.py ├── MANIFEST.in ├── install_tempo2.sh ├── LICENSE ├── .gitignore ├── Makefile ├── setup.py ├── README.md ├── pyproject.toml └── .github └── workflows └── ci_tests.yml /libstempo/.gitignore: -------------------------------------------------------------------------------- 1 | _version.py 2 | *.so 3 | *.cpp 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.3.0 4 | hooks: 5 | - id: black 6 | args: ["--config=pyproject.toml", "--check"] 7 | - repo: https://gitlab.com/pycqa/flake8 8 | rev: "3.7.7" 9 | hooks: 10 | - id: flake8 11 | args: ["--config=.flake8"] 12 | -------------------------------------------------------------------------------- /tests/test_imports.py: -------------------------------------------------------------------------------- 1 | def test_imports(): 2 | import libstempo # noqa: F401 3 | import libstempo.like # noqa: F401 4 | import libstempo.emcee # noqa: F401 5 | import libstempo.plot # noqa: F401 6 | import libstempo.toasim # noqa:F401 7 | import libstempo.eccUtils # noqa: F401 8 | import libstempo.spharmORFbasis # noqa: F401 9 | -------------------------------------------------------------------------------- /libstempo/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ._find_tempo2 import find_tempo2_runtime 3 | 4 | 5 | # check to see if TEMPO2 environment variable is set 6 | TEMPO2_RUNTIME = os.getenv("TEMPO2") 7 | 8 | 9 | # if not try to find it and raise error otherwise 10 | if not TEMPO2_RUNTIME: 11 | os.environ["TEMPO2"] = find_tempo2_runtime() 12 | 13 | 14 | from libstempo.libstempo import * # noqa F401,F402,F403 15 | 16 | try: 17 | from ._version import version as __version__ 18 | except ModuleNotFoundError: 19 | __version__ = "" 20 | -------------------------------------------------------------------------------- /libstempo/t2fit-stub.h: -------------------------------------------------------------------------------- 1 | #include "tempo2.h" 2 | 3 | // originally declared in t2fit_stdFitFuncs.h 4 | 5 | double t2FitFunc_zero(struct pulsar *psr,int ipsr,double x,int ipos,param_label label,int k); 6 | void t2UpdateFunc_zero(struct pulsar *psr,int ipsr,param_label label,int k,double val,double err); 7 | 8 | double t2FitFunc_jump(struct pulsar *psr,int ipsr,double x,int ipos,param_label label,int k); 9 | void t2UpdateFunc_jump(struct pulsar *psr,int ipsr,param_label label,int k,double val,double err); 10 | 11 | double t2FitFunc_fdjump(struct pulsar *psr,int ipsr,double x,int ipos,param_label label,int k); 12 | void t2UpdateFunc_fdjump(struct pulsar *psr,int ipsr,param_label label,int k,double val,double err); 13 | 14 | // defined in t2fit.C, not declared in t2fit.h 15 | 16 | void t2fit_fillOneParameterFitInfo(struct pulsar *psr,param_label fit_param,const int k,FitInfo& OUT); 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include setup.py 4 | include install_tempo2.sh 5 | include requirements.txt 6 | include libstempo/__init__.py 7 | include libstempo/libstempo.pyx 8 | include libstempo/libstempo.cpp 9 | include libstempo/GWsim-stub.h 10 | include libstempo/t2fit-stub.h 11 | include libstempo/eccUtils.py 12 | include libstempo/emcee.py 13 | include libstempo/fit.py 14 | include libstempo/like.py 15 | include libstempo/multinest.py 16 | include libstempo/plot.py 17 | include libstempo/spharmORFbasis.py 18 | include libstempo/toasim.py 19 | include libstempo/ecc_vs_nharm.txt 20 | include demo/libstempo-demo.ipynb 21 | include demo/libstempo-toasim-demo.ipynb 22 | include libstempo/data/B1953+29_NANOGrav_dfg+12.par 23 | include libstempo/data/B1953+29_NANOGrav_dfg+12.tim 24 | include libstempo/data/J1909-3744_NANOGrav_dfg+12.par 25 | include libstempo/data/J1909-3744_NANOGrav_dfg+12.tim 26 | include tests/test_fakepulsar.py 27 | include tests/test_imports.py 28 | include tests/test_pulsar.py 29 | -------------------------------------------------------------------------------- /install_tempo2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # get install location 4 | if [ $# -eq 0 ] 5 | then 6 | echo 'No install location defined, using' $HOME'/.local/' 7 | prefix=$HOME/.local/ 8 | else 9 | prefix=$1 10 | echo 'Will install in' $prefix 11 | fi 12 | 13 | # make a destination directory for runtime files 14 | export TEMPO2=$prefix/share/tempo2 15 | mkdir -p $TEMPO2 16 | 17 | curl -O https://bitbucket.org/psrsoft/tempo2/get/2021.07.1-correct.tar.gz 18 | tar zxvf 2021.07.1-correct.tar.gz 19 | 20 | cd psrsoft-tempo2-* 21 | 22 | # remove LT_LIB_DLLOAD from configure.ac 23 | sed_in_place="-i ''" # For macOS 24 | if [[ "$(uname -s)" == "Linux" ]]; then 25 | sed_in_place="-i" # For Linux 26 | fi 27 | sed "$sed_in_place" "s/LT_LIB_DLLOAD//g" "configure.ac" 28 | 29 | ./bootstrap 30 | ./configure --prefix=$prefix 31 | make && make install 32 | cp -r T2runtime/* $TEMPO2 33 | cd .. 34 | 35 | rm -rf psrsoft-tempo2-* 36 | rm -rf 2021.07.1-correct.tar.gz 37 | echo "Set TEMPO2 environment variable to ${TEMPO2} to make things run more smoothly." 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Michele Vallisneri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # text editor tmp files 2 | .*.swp 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | .ipynb_checkpoints/ 62 | 63 | # PyBuilder 64 | target/ 65 | 66 | # pyenv python configuration file 67 | .python-version 68 | 69 | # mac stuff: 70 | .DS_Store 71 | 72 | # virtual environment 73 | .libstempo 74 | 75 | # vscode 76 | .vscode 77 | -------------------------------------------------------------------------------- /libstempo/GWsim-stub.h: -------------------------------------------------------------------------------- 1 | #include "tempo2.h" 2 | 3 | #ifdef HAVE_GWSIM_H 4 | 5 | #define HAVE_GWSIM 1 6 | #include "GWsim.h" 7 | 8 | #else 9 | 10 | #define HAVE_GWSIM 0 11 | 12 | typedef struct gwSrc { 13 | long double theta_g; 14 | long double phi_g; 15 | long double omega_g; 16 | long double phi_polar_g; 17 | long double phase_g; 18 | long double aplus_g; 19 | long double aplus_im_g; 20 | long double across_g; 21 | long double across_im_g; 22 | long double phi_bin; 23 | long double theta_bin; 24 | // long double chirp_mass; 25 | long double inc_bin; 26 | long double dist_bin; 27 | long double h[3][3]; 28 | long double h_im[3][3]; 29 | long double kg[3]; 30 | } gwSrc; 31 | 32 | void setupGW(gwSrc *gw); 33 | void GWbackground(gwSrc *gw,int numberGW,long *idum,long double flo,long double fhi,double gwAmp,double alpha,int loglin); 34 | long double calculateResidualGW(long double *kp,gwSrc *gw,long double time,long double dist); 35 | void setupPulsar_GWsim(long double ra_p,long double dec_p,long double *kp); 36 | 37 | /* Define the dummy anisotropic functions here */ 38 | #warning "Anisotropic GWsim routines not available" 39 | 40 | void GWdipolebackground(gwSrc *gw,int numberGW,long *idum,long double flo,long double fhi, double gwAmp,double alpha,int loglin, double *dipoleamps) { 41 | return; 42 | } 43 | 44 | #endif /* HAVE_GWSIM_H */ 45 | -------------------------------------------------------------------------------- /libstempo/_find_tempo2.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import subprocess 4 | import warnings 5 | from pathlib import Path 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | RUNTIME_DIRS = ("atmosphere", "clock", "earth", "ephemeris", "observatory", "solarWindModel") 10 | HOME = os.getenv("HOME") 11 | 12 | 13 | def find_tempo2_runtime(): 14 | """ 15 | Attempt to find TEMPO2 runtime if TEMPO2 environment variable is not set 16 | """ 17 | 18 | # first check for local install (i.e. from using install_tempo2.sh) 19 | if HOME is not None: 20 | local_path = Path(HOME) / ".local/share/tempo2" 21 | if local_path.exists(): 22 | return str(local_path) 23 | 24 | # if not, check for tempo2 binary in path 25 | try: 26 | out = subprocess.check_output("which tempo2", shell=True) 27 | out = out.decode().strip() 28 | except subprocess.CalledProcessError: 29 | warnings.warn("Could not find tempo2 executable in your path") 30 | else: 31 | 32 | # since this would be in a bin/ directory, navigate back to root and check share/ 33 | share_dir = Path(out).parents[1] / "share" 34 | 35 | if share_dir.exists(): 36 | # loop through all directories in share 37 | for d in share_dir.iterdir(): 38 | if d.is_dir(): 39 | # if this directory contains the runtime dirs then set this to be the runtime dir 40 | dirs = [dd.stem for dd in d.iterdir() if dd.is_dir()] 41 | if all(rd in dirs for rd in RUNTIME_DIRS): 42 | return str(d) 43 | raise RuntimeError("Can't find T2runtime from inspection. Set TEMPO2 environment variable") 44 | -------------------------------------------------------------------------------- /libstempo/emcee.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import numpy as N 4 | 5 | # note: we run emcee as if it was multinest, by remapping parameter ranges to [0,1] 6 | # if we didn't, the logPL function could just be 7 | # 8 | # prior = p0.prior(xs) 9 | # return -N.inf if not prior else math.log(prior) + ll.loglike(xs) 10 | # 11 | # and the initialization would be 12 | # 13 | # init = [p0.remap_list(N.random.random(len(cfg.searchpars))) for i in range(cfg.walkers)] 14 | 15 | 16 | def logPL(ll, p0, xs): 17 | pprior = p0.premap(xs) 18 | 19 | # libstempo.like.Prior mappers are supposed to throw a ValueError 20 | # if they get coordinates out of range 21 | try: 22 | pars = p0.remap(xs) 23 | except ValueError: 24 | return -N.inf 25 | 26 | prior = pprior * p0.prior(pars) 27 | 28 | return -N.inf if not prior else math.log(prior) + ll.loglike(pars) 29 | 30 | 31 | def save(basename, sample, p0, skip): 32 | # save last cloud to resume from it 33 | N.save(basename + "-resume.npy", sample.chain[:, -1, :]) 34 | 35 | # thin out the run 36 | chain, lnprob = sample.chain[:, ::skip, :], sample.lnprobability[:, ::skip] 37 | 38 | N.save(basename + "-lnprob.npy", lnprob) 39 | 40 | N.save(basename + "-chain-unmapped.npy", chain) 41 | # remap parameters to physical ranges 42 | for w in range(chain.shape[0]): 43 | for s in range(chain.shape[1]): 44 | p0.premap(chain[w, s, :]) 45 | chain[w, s, :] = p0.remap_list(chain[w, s, :]) 46 | N.save(basename + "-chain.npy", chain) 47 | 48 | 49 | def merge(data, skip=50, fraction=1.0): 50 | """Merge one every 'skip' clouds into a single emcee population, 51 | using the later 'fraction' of the run.""" 52 | 53 | w, s, d = data.chains.shape 54 | 55 | start = int((1.0 - fraction) * s) 56 | total = int((s - start) / skip) 57 | 58 | return data.chains[:, start::skip, :].reshape((w * total, d)) 59 | 60 | 61 | def cull(data, index, min=None, max=None): 62 | """Sieve an emcee clouds by excluding walkers with search variable 'index' 63 | smaller than 'min' or larger than 'max'.""" 64 | 65 | ret = data 66 | 67 | if min is not None: 68 | ret = ret[ret[:, index] > min, :] 69 | 70 | if max is not None: 71 | ret = ret[ret[:, index] < max, :] 72 | 73 | return ret 74 | -------------------------------------------------------------------------------- /libstempo/ecc_vs_nharm.txt: -------------------------------------------------------------------------------- 1 | 0.001 2 2 | 0.0110808080808 3 3 | 0.0211616161616 3 4 | 0.0312424242424 3 5 | 0.0413232323232 3 6 | 0.051404040404 3 7 | 0.0614848484848 3 8 | 0.0715656565657 4 9 | 0.0816464646465 4 10 | 0.0917272727273 4 11 | 0.101808080808 4 12 | 0.111888888889 4 13 | 0.12196969697 4 14 | 0.132050505051 4 15 | 0.142131313131 4 16 | 0.152212121212 5 17 | 0.162292929293 5 18 | 0.172373737374 5 19 | 0.182454545455 5 20 | 0.192535353535 5 21 | 0.202616161616 5 22 | 0.212696969697 5 23 | 0.222777777778 5 24 | 0.232858585859 5 25 | 0.242939393939 6 26 | 0.25302020202 6 27 | 0.263101010101 6 28 | 0.273181818182 6 29 | 0.283262626263 6 30 | 0.293343434343 6 31 | 0.303424242424 6 32 | 0.313505050505 7 33 | 0.323585858586 7 34 | 0.333666666667 7 35 | 0.343747474747 7 36 | 0.353828282828 7 37 | 0.363909090909 8 38 | 0.37398989899 8 39 | 0.384070707071 8 40 | 0.394151515152 8 41 | 0.404232323232 8 42 | 0.414313131313 9 43 | 0.424393939394 9 44 | 0.434474747475 9 45 | 0.444555555556 9 46 | 0.454636363636 9 47 | 0.464717171717 10 48 | 0.474797979798 10 49 | 0.484878787879 10 50 | 0.49495959596 11 51 | 0.50504040404 11 52 | 0.515121212121 11 53 | 0.525202020202 12 54 | 0.535282828283 12 55 | 0.545363636364 12 56 | 0.555444444444 13 57 | 0.565525252525 13 58 | 0.575606060606 14 59 | 0.585686868687 14 60 | 0.595767676768 15 61 | 0.605848484848 15 62 | 0.615929292929 16 63 | 0.62601010101 16 64 | 0.636090909091 17 65 | 0.646171717172 17 66 | 0.656252525253 18 67 | 0.666333333333 19 68 | 0.676414141414 20 69 | 0.686494949495 21 70 | 0.696575757576 22 71 | 0.706656565657 23 72 | 0.716737373737 24 73 | 0.726818181818 25 74 | 0.736898989899 26 75 | 0.74697979798 27 76 | 0.757060606061 29 77 | 0.767141414141 31 78 | 0.777222222222 33 79 | 0.787303030303 35 80 | 0.797383838384 37 81 | 0.807464646465 39 82 | 0.817545454545 42 83 | 0.827626262626 46 84 | 0.837707070707 49 85 | 0.847787878788 54 86 | 0.857868686869 59 87 | 0.867949494949 65 88 | 0.87803030303 72 89 | 0.888111111111 80 90 | 0.898191919192 90 91 | 0.908272727273 103 92 | 0.918353535354 120 93 | 0.928434343434 142 94 | 0.938515151515 173 95 | 0.948595959596 219 96 | 0.958676767677 296 97 | 0.968757575758 447 98 | 0.978838383838 747 99 | 0.988919191919 1902 100 | 0.999 5201 101 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | try: 7 | from urllib import pathname2url 8 | except: 9 | from urllib.request import pathname2url 10 | 11 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 12 | endef 13 | export BROWSER_PYSCRIPT 14 | 15 | define PRINT_HELP_PYSCRIPT 16 | import re, sys 17 | 18 | for line in sys.stdin: 19 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 20 | if match: 21 | target, help = match.groups() 22 | print("%-20s %s" % (target, help)) 23 | endef 24 | export PRINT_HELP_PYSCRIPT 25 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 26 | 27 | help: 28 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 29 | 30 | init: ## initialize virtual environment and install package 31 | @python3 -m venv .libstempo --prompt libstempo 32 | @./.libstempo/bin/python3 -m pip install -r requirements_dev.txt 33 | @./.libstempo/bin/python3 -m pip install -r requirements.txt 34 | @./.libstempo/bin/python3 -m pre_commit install --install-hooks --overwrite 35 | @./.libstempo/bin/python3 -m pip install -e ."[astropy]" 36 | @echo "Run source .libstempo/bin/activate to activate virtual environment" 37 | 38 | format: 39 | black . 40 | 41 | lint: 42 | black --check . 43 | flake8 . 44 | 45 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 46 | 47 | clean-build: ## remove build artifacts 48 | rm -fr build/ 49 | rm -fr dist/ 50 | rm -fr .eggs/ 51 | find . -name '*.egg-info' -exec rm -fr {} + 52 | find . -name '*.egg' -exec rm -f {} + 53 | 54 | clean-pyc: ## remove Python file artifacts 55 | find . -name '*.pyc' -exec rm -f {} + 56 | find . -name '*.pyo' -exec rm -f {} + 57 | find . -name '*~' -exec rm -f {} + 58 | find . -name '__pycache__' -exec rm -fr {} + 59 | 60 | clean-test: ## remove test and coverage artifacts 61 | rm -fr .tox/ 62 | rm -f .coverage 63 | rm -fr htmlcov/ 64 | 65 | test: lint ## run tests quickly with the default Python 66 | pytest -v --full-trace --cov-config .coveragerc --cov=libstempo tests 67 | 68 | coverage: ## check code coverage quickly with the default Python 69 | coverage run --source libstempo setup.py test 70 | 71 | coverage report -m 72 | coverage html 73 | $(BROWSER) htmlcov/index.html 74 | 75 | release: clean ## package and upload a release 76 | python setup.py sdist upload 77 | python setup.py bdist_wheel upload 78 | 79 | dist: clean ## builds source and wheel package 80 | python setup.py sdist 81 | #python setup.py bdist_wheel 82 | ls -l dist 83 | -------------------------------------------------------------------------------- /libstempo/utils.py: -------------------------------------------------------------------------------- 1 | """Utility functions for noise models.""" 2 | 3 | import numpy as np 4 | 5 | 6 | def quantize_fast(times, flags, dt=1.0): 7 | isort = np.argsort(times) 8 | 9 | bucket_ref = [times[isort[0]]] 10 | bucket_ind = [[isort[0]]] 11 | 12 | for i in isort[1:]: 13 | if times[i] - bucket_ref[-1] < dt and flags[i] != "": 14 | bucket_ind[-1].append(i) 15 | else: 16 | bucket_ref.append(times[i]) 17 | bucket_ind.append([i]) 18 | 19 | # only keep epochs with 2 or more TOAs 20 | bucket_ind = [ind for ind in bucket_ind if len(ind) >= 2] 21 | 22 | avetoas = np.array([np.mean(times[l]) for l in bucket_ind], "d") 23 | U = np.zeros((len(times), len(bucket_ind)), "d") 24 | for i, l in enumerate(bucket_ind): 25 | U[l, i] = 1 26 | 27 | return avetoas, U 28 | 29 | 30 | def create_fourier_design_matrix(t, nmodes, freq=False, Tspan=None, logf=False, fmin=None, fmax=None): 31 | """ 32 | Construct fourier design matrix from eq 11 of Lentati et al, 2013 33 | 34 | :param t: vector of time series in seconds 35 | :param nmodes: number of fourier coefficients to use 36 | :param freq: option to output frequencies 37 | :param Tspan: option to some other Tspan 38 | :param logf: use log frequency spacing 39 | :param fmin: lower sampling frequency 40 | :param fmax: upper sampling frequency 41 | 42 | :return: F: fourier design matrix 43 | :return: f: Sampling frequencies (if freq=True) 44 | """ 45 | 46 | N = len(t) 47 | F = np.zeros((N, 2 * nmodes)) 48 | 49 | if Tspan is not None: 50 | T = Tspan 51 | else: 52 | T = t.max() - t.min() 53 | 54 | # define sampling frequencies 55 | if fmin is not None and fmax is not None: 56 | f = np.linspace(fmin, fmax, nmodes) 57 | else: 58 | f = np.linspace(1 / T, nmodes / T, nmodes) 59 | if logf: 60 | f = np.logspace(np.log10(1 / T), np.log10(nmodes / T), nmodes) 61 | 62 | Ffreqs = np.zeros(2 * nmodes) 63 | Ffreqs[0::2] = f 64 | Ffreqs[1::2] = f 65 | 66 | F[:, ::2] = np.sin(2 * np.pi * t[:, None] * f[None, :]) 67 | F[:, 1::2] = np.cos(2 * np.pi * t[:, None] * f[None, :]) 68 | 69 | if freq: 70 | return F, Ffreqs 71 | else: 72 | return F 73 | 74 | 75 | def powerlaw(f, log10_A=-16, gamma=5): 76 | """Power-law PSD. 77 | 78 | :param f: Sampling frequencies 79 | :param log10_A: log10 of red noise Amplitude [GW units] 80 | :param gamma: Spectral index of red noise process 81 | """ 82 | 83 | fyr = 1 / 3.16e7 84 | return (10**log10_A) ** 2 / 12.0 / np.pi**2 * fyr ** (gamma - 3) * f ** (-gamma) 85 | -------------------------------------------------------------------------------- /libstempo/data/B1953+29_NANOGrav_dfg+12.par: -------------------------------------------------------------------------------- 1 | PSRJ 1953+29 2 | RAJ 19:55:27.8759628 1 0.00002309892672271674 3 | DECJ +29:08:43.46561 1 0.00043991228977249124 4 | F0 163.04791306914016577 1 0.00000000000220012769 5 | F1 -7.9066177927034055828e-16 1 2.6905905384188187028e-19 6 | PEPOCH 54500 7 | POSEPOCH 54500 8 | DMEPOCH 54500 9 | DM 104.497344 10 | PMRA -1.2521181140219140359 1 0.23851922236709943714 11 | PMDEC -3.9246284088569551082 1 0.37941263605095432165 12 | PX -2.7014176132477597432 1 2.40240931724024298077 13 | BINARY BT 14 | PB 117.34909710357026708 1 0.00000012771608926733 15 | T0 46112.476835516711251 1 0.00214174245061728294 16 | A1 31.412690152878882411 1 0.00000057069341436876 17 | OM 29.473514088366953799 1 0.00658322203721885355 18 | ECC 0.00033015843339901621672 1 0.00000003929268673153 19 | START 53945.160376202824409 20 | FINISH 55106.00339836783678 21 | TZRMJD 54531.561662799221295 22 | TZRFRQ 1414 23 | TZRSITE ao 24 | TRES 3.980 25 | EPHVER 5 26 | CLK TT(BIPM2011) 27 | MODE 1 28 | UNITS TDB 29 | T2CMETHOD TEMPO 30 | NE_SW 0.000 31 | CORRECT_TROPOSPHERE N 32 | EPHEM DE405 33 | NITS 1 34 | NTOA 208 35 | CHI2R 1.1826 168 36 | JUMP -chanid asp_1390 1.1126382403811e-06 1 37 | JUMP -chanid asp_1394 -5.014906529641e-07 1 38 | JUMP -chanid asp_1398 -1.3150867548092e-06 1 39 | JUMP -chanid asp_1402 7.2764978073161e-07 1 40 | JUMP -chanid asp_1406 -6.8091690122445e-07 1 41 | JUMP -chanid asp_1410 -2.0328483631389e-07 1 42 | JUMP -chanid asp_1414 6.0242209798968e-08 1 43 | JUMP -chanid asp_1418 -2.994476462069e-08 1 44 | JUMP -chanid asp_1422 -1.0695881963054e-06 1 45 | JUMP -chanid asp_1426 -3.9302466289313e-07 1 46 | JUMP -chanid asp_1430 5.4316904556653e-07 1 47 | JUMP -chanid asp_2318 6.8188907388271e-05 1 48 | JUMP -chanid asp_2322 0.00011156375255597 1 49 | JUMP -chanid asp_2326 8.8466189879867e-05 1 50 | JUMP -chanid asp_2330 0.00010498483642429 1 51 | JUMP -chanid asp_2334 0.00012185832379376 1 52 | JUMP -chanid asp_2338 0.0001224129868434 1 53 | JUMP -chanid asp_2342 8.0666524098171e-05 1 54 | JUMP -chanid asp_2346 8.9217300458606e-05 1 55 | JUMP -chanid asp_2350 9.3491000844517e-05 1 56 | JUMP -chanid asp_2354 0.00010021909168766 1 57 | JUMP -chanid asp_2358 0.00010522359041997 1 58 | JUMP -chanid asp_2362 7.3649283071457e-05 1 59 | JUMP -chanid asp_2366 7.9793858977294e-05 1 60 | JUMP -chanid asp_2370 0.00013076912704035 1 61 | JUMP -chanid asp_2374 0.00013133855185576 1 62 | JUMP -chanid asp_2378 0.00014247877954869 1 63 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import subprocess 4 | import warnings 5 | from pathlib import Path 6 | 7 | import numpy 8 | from setuptools import Extension, setup 9 | 10 | 11 | # we assume that you have either installed tempo2 via install_tempo2.sh in the default location 12 | # or you have installed in the usual /usr/local 13 | # or you have set the TEMPO2_PREFIX environment variable 14 | # or the tempo2 executable is in your path 15 | def _get_tempo2_install_location(): 16 | 17 | # from environment variable 18 | tempo2_environ = os.getenv("TEMPO2_PREFIX") 19 | if tempo2_environ is not None: 20 | return tempo2_environ 21 | 22 | # first check local install 23 | local = Path(os.getenv("HOME")) / ".local" 24 | if (local / "include/tempo2.h").exists(): 25 | return str(local) 26 | 27 | # next try global 28 | glbl = Path("/usr/local") 29 | if (glbl / "include/tempo2.h").exists(): 30 | return str(glbl) 31 | 32 | # if not, check for tempo2 binary in path 33 | try: 34 | out = subprocess.check_output("which tempo2", shell=True) 35 | out = out.decode().strip() 36 | except subprocess.CalledProcessError: 37 | warnings.warn(("tempo2 does not appear to be in your path.")) 38 | else: 39 | # the executable should be in in bin/ so navigate back and check include/ 40 | root_dir = Path(out).parents[1] 41 | if (root_dir / "include/tempo2.h").exists(): 42 | return str(root_dir) 43 | 44 | raise RuntimeError( 45 | """ 46 | Cannot find tempo2 install location. Your options are: 47 | 48 | 1. Use the install_tempo2.sh script without any arguments to install to default location. 49 | 2. Install tempo2 globally in /usr/local 50 | 3. Set the TEMPO2_PREFIX environment variable: 51 | For example, if the tempo2 executable lives in /opt/local/bin: 52 | TEMPO2_PREFIX=/opt/local pip install libstempo 53 | or 54 | export TEMPO2_PREFIX=/opt/local 55 | pip install libstempo 56 | """ 57 | ) 58 | 59 | 60 | TEMPO2 = _get_tempo2_install_location() 61 | 62 | # need rpath links to shared libraries on Linux 63 | if platform.system() == "Linux": 64 | linkArgs = ["-Wl,-R{}/lib".format(TEMPO2)] 65 | else: 66 | linkArgs = [] 67 | 68 | 69 | ext_modules = [ 70 | Extension( 71 | "libstempo.libstempo", 72 | ["libstempo/libstempo.pyx"], 73 | language="c++", 74 | include_dirs=[TEMPO2 + "/include", numpy.get_include()], 75 | libraries=["tempo2", "tempo2pred"], 76 | library_dirs=[TEMPO2 + "/lib"], 77 | extra_compile_args=["-Wno-unused-function"], 78 | extra_link_args=linkArgs, 79 | ), 80 | ] 81 | 82 | # add language level = 3 83 | for e in ext_modules: 84 | e.cython_directives = {"language_level": "3"} 85 | 86 | setup( 87 | ext_modules=ext_modules, 88 | ) 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libstempo 2 | 3 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/vallis/libstempo)](https://github.com/vallis/libstempo/releases/latest) 4 | [![PyPI](https://img.shields.io/pypi/v/libstempo)](https://pypi.org/project/libstempo/) 5 | [![Conda Version](https://img.shields.io/conda/vn/conda-forge/libstempo.svg)](https://anaconda.org/conda-forge/libstempo) 6 | [![libstempo CI tests](https://github.com/vallis/libstempo/actions/workflows/ci_tests.yml/badge.svg)](https://github.com/vallis/libstempo/actions/workflows/ci_tests.yml) 7 | 8 | 9 | [![Python Versions](https://img.shields.io/badge/python-3.7%2C%203.8%2C%203.9%2C%203.10-blue.svg)]() 10 | [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/vallis/libstempo/blob/master/LICENSE) 11 | 12 | `libstempo` is a Python wrapper around the [tempo2](https://bitbucket.org/psrsoft/tempo2/src/master/) pulsar timing package. 13 | 14 | 15 | ## Installation 16 | 17 | ### conda Install 18 | 19 | `libstempo` is installed most simply via [conda](https://docs.conda.io/en/latest/) as the `tempo` dependency 20 | is bundled in the conda recipe. Simply use 21 | ```bash 22 | conda install -c conda-forge libstempo 23 | ``` 24 | 25 | ### pip Install 26 | 27 | To use `libstempo` with pip (or from source), tempo2 must be installed as a prerequisite. Currently there are two recommended methods to do this. 28 | 29 | 1. Install via script. 30 | ```bash 31 | curl -sSL https://raw.githubusercontent.com/vallis/libstempo/master/install_tempo2.sh | sh 32 | ``` 33 | This will install the tempo2 library in a local directory (`$HOME/.local`). This method is recommended if you do not need to use tempo2 directly but just need the installation for `libstempo`. You can also set the path to the install location. For example, to install in `/usr/local`, you could run: 34 | ```bash 35 | # need sudo if installing in a restricted location 36 | curl -sSL https://raw.githubusercontent.com/vallis/libstempo/master/install_tempo2.sh | sudo sh -s /usr/local 37 | ``` 38 | 2. Install via the [instructions](https://bitbucket.org/psrsoft/tempo2/src/master/README.md) on the tempo2 homepage. If this method is used, the `TEMPO2` environment variable will need to be set to use `libstempo`. 39 | 40 | In either case, it is best practice to set the `TEMPO2` environment 41 | variable so that it can be easily discovered by `libstempo`. 42 | 43 | The `libstempo` package can be installed via `pip`: 44 | ```bash 45 | pip install libstempo 46 | ``` 47 | 48 | To use `astropy` for units: 49 | ```bash 50 | pip install libstempo[astropy] 51 | ``` 52 | 53 | If you have installed `tempo2` in a location that is not in your path or not the default from `install_tempo2.sh`, you will need to install 54 | `libstempo` with an environment variable (e.g. if `tempo2` is in `/opt/local/bin`) 55 | ```bash 56 | TEMPO2_PREFIX=/opt/local pip install libstempo 57 | ``` 58 | or 59 | ```bash 60 | export TEMPO2_PREFIX=/opt/local 61 | pip install libstempo 62 | ``` 63 | 64 | ## Usage 65 | 66 | See [Demo Notebook 1](https://github.com/vallis/libstempo/blob/master/demo/libstempo-demo.ipynb) for basic usage and [Demo Notebook 2](https://github.com/vallis/libstempo/blob/master/demo/libstempo-toasim-demo.ipynb) for simulation usage. 67 | -------------------------------------------------------------------------------- /tests/test_pulsar.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import unittest 3 | from pathlib import Path 4 | 5 | import libstempo as t2 6 | import numpy as np 7 | 8 | DATA_PATH = t2.__path__[0] + "/data/" 9 | 10 | TMP_DIR = Path("test_output") 11 | TMP_DIR.mkdir(exist_ok=True) 12 | 13 | try: 14 | NP_LONG_DOUBLE_TYPE = np.float128 15 | except AttributeError: 16 | NP_LONG_DOUBLE_TYPE = np.double 17 | 18 | 19 | class TestDeterministicSignals(unittest.TestCase): 20 | @classmethod 21 | def setUpClass(cls): 22 | cls.psr = t2.tempopulsar( 23 | parfile=DATA_PATH + "/J1909-3744_NANOGrav_dfg+12.par", timfile=DATA_PATH + "/J1909-3744_NANOGrav_dfg+12.tim" 24 | ) 25 | 26 | @classmethod 27 | def tearDownClass(cls): 28 | shutil.rmtree(TMP_DIR) 29 | 30 | def test_attrs(self): 31 | self.assertEqual(self.psr.nobs, 1001) 32 | self.assertEqual(self.psr.name, "1909-3744") 33 | self.assertEqual(len(self.psr.stoas), 1001) 34 | self.assertTrue(np.all(self.psr.stoas > 50000) and np.all(self.psr.stoas < 59000)) 35 | self.assertTrue(np.all(self.psr.toaerrs > 0.01) and np.all(self.psr.toaerrs < 10)) 36 | self.assertTrue(np.all(self.psr.freqs > 700) and np.all(self.psr.freqs < 4000)) 37 | self.assertEqual(self.psr.stoas[0].dtype, NP_LONG_DOUBLE_TYPE) 38 | 39 | def test_toas(self): 40 | self.assertTrue(np.all(self.psr.toas() != self.psr.stoas)) 41 | self.assertTrue(np.allclose(self.psr.toas(), self.psr.stoas, atol=1)) 42 | 43 | def test_residuals(self): 44 | self.assertTrue(np.all(self.psr.residuals() > -2e-5) and np.all(self.psr.residuals() < 1.5e-5)) 45 | 46 | def test_flags(self): 47 | expected = {"B", "be", "bw", "chanid", "fe", "proc", "pta", "tobs"} 48 | self.assertEqual(set(self.psr.flags()), expected) 49 | 50 | def test_radec(self): 51 | self.assertTrue(np.allclose(self.psr["RAJ"].val, 5.0169080674060326785)) 52 | self.assertTrue(np.allclose(self.psr["DECJ"].val, 7.753759525058565179e-10, atol=1)) 53 | 54 | expected = (True, True) 55 | tested = (self.psr["RAJ"].set, self.psr["DECJ"].set) 56 | self.assertEqual(tested, expected) 57 | 58 | def test_fitpars(self): 59 | expected = ("RAJ", "DECJ", "F0", "F1", "PMRA", "PMDEC", "PX", "SINI", "PB", "A1", "TASC", "EPS1", "EPS2", "M2") 60 | fitpars = self.psr.pars() 61 | self.assertEqual(fitpars[:14], expected) 62 | 63 | setpars = self.psr.pars(which="set") 64 | self.assertEqual(len(setpars), 158) 65 | 66 | # different versions of tempo2 define different number of parameters 67 | # allpars = self.psr.pars(which="all") 68 | # self.assertEqual(len(allpars), 4487) 69 | 70 | def test_fit(self): 71 | _ = self.psr.fit() 72 | fitvals = self.psr.vals() 73 | self.assertEqual(len(fitvals), 82) 74 | 75 | def test_designmatrix(self): 76 | dmat = self.psr.designmatrix() 77 | self.assertEqual(dmat.shape, (1001, 83)) 78 | 79 | def test_save_partim(self): 80 | self.psr.savepar(str(TMP_DIR / "tmp.par")) 81 | self.psr.savetim(str(TMP_DIR / "tmp.tim")) 82 | 83 | self.assertTrue((TMP_DIR / "tmp.par").exists()) 84 | self.assertTrue((TMP_DIR / "tmp.tim").exists()) 85 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # to create a new release, do the following: 2 | # install the build and twine packages 3 | # $ pip install build twine 4 | # create a git tag with the new version number, e.g., 5 | # $ git tag -a v3.5.0 -m "Version 3.5.0" 6 | # build the source distribution 7 | # $ python -m build --sdist --outdir . 8 | # Upload to PyPI with: 9 | # $ twine upload --repository libstempo libstempo-*.tar.gz 10 | 11 | [build-system] 12 | requires = [ 13 | "setuptools>=61", 14 | "setuptools_scm[toml]>=6.2", 15 | "wheel", # ephem package likes to have wheel installed 16 | "cython", 17 | 'numpy>=2.0.0; python_version > "3.8"', 18 | 'numpy<2.0; python_version == "3.8"', 19 | ] 20 | build-backend = "setuptools.build_meta" 21 | 22 | [project] 23 | name = "libstempo" 24 | description = "A Python wrapper for tempo2" 25 | authors = [{name = "Michele Vallisneri", email = "vallis@vallis.org"}] 26 | urls = { Homepage = "https://github.com/vallis/libstempo" } 27 | readme = "README.md" 28 | license = "MIT" 29 | license-files = [ "LICENSE" ] 30 | classifiers=[ 31 | "Intended Audience :: Developers", 32 | "Intended Audience :: Science/Research", 33 | "Operating System :: MacOS", 34 | "Operating System :: POSIX :: Linux", 35 | "Programming Language :: Python :: 3", 36 | "Programming Language :: Python :: 3.8", 37 | "Programming Language :: Python :: 3.9", 38 | "Programming Language :: Python :: 3.10", 39 | "Programming Language :: Python :: 3.11", 40 | "Programming Language :: Python :: 3.12", 41 | ] 42 | 43 | dynamic = [ 44 | "version", 45 | ] 46 | 47 | requires-python = ">=3.8, <4" 48 | dependencies = [ 49 | 'numpy<2.0; python_version == "3.8"', 50 | 'numpy; python_version > "3.8"', 51 | "scipy", 52 | "matplotlib", 53 | "ephem", 54 | ] 55 | 56 | [project.optional-dependencies] 57 | astropy = [ 58 | "astropy", 59 | ] 60 | dev = [ 61 | "black", 62 | "flake8", 63 | "Flake8-pyproject", 64 | "pre-commit", 65 | "wheel", 66 | "coverage", 67 | "pytest", 68 | "pytest-cov", 69 | "jupyter", 70 | ] 71 | 72 | [tool.setuptools] 73 | include-package-data = true 74 | 75 | [tool.setuptools.packages.find] 76 | include = [ 77 | "libstempo*", 78 | ] 79 | 80 | [tool.setuptools_scm] 81 | write_to = "libstempo/_version.py" 82 | 83 | [tool.black] 84 | line-length = 120 85 | target_version = ['py39'] 86 | include = '\\.pyi?$' 87 | exclude = ''' 88 | ( 89 | /( 90 | \\.eggs # exclude a few common directories in the 91 | | \\.git # root of the project 92 | | \\.hg 93 | | \\.mypy_cache 94 | | \\.tox 95 | | _build 96 | | buck-out 97 | | build 98 | | dist 99 | | docs 100 | | .libstempo 101 | | libstempo/_version\.py 102 | )/ 103 | ) 104 | ''' 105 | 106 | [tool.flake8] 107 | max-line-length = 120 108 | max-complexity = 45 109 | ignore = [ 110 | "E203", 111 | "W503", # line break before binary operator; conflicts with black 112 | "E722", # bare except ok 113 | "E731", # lambda expressions ok 114 | "E741", # undescriptive variable ok 115 | ] 116 | exclude = [ 117 | ".git", 118 | ".tox", 119 | "__pycache__", 120 | "build", 121 | "dist", 122 | "docs", 123 | ".libstempo", 124 | ] 125 | -------------------------------------------------------------------------------- /.github/workflows/ci_tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: libstempo CI tests 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | release: 12 | types: 13 | - published 14 | 15 | 16 | jobs: 17 | tests: 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | os: [ubuntu-latest, macos-latest] 23 | python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v2 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install tempo2 on mac 33 | if: runner.os == 'macOS' 34 | run: | 35 | brew unlink gcc && brew link gcc 36 | brew install automake libtool 37 | ./install_tempo2.sh 38 | - name: Install tempo2 on linux 39 | if: runner.os == 'Linux' 40 | run: | 41 | ./install_tempo2.sh 42 | - name: Install dependencies and package 43 | run: | 44 | python -m pip install --upgrade pip 45 | pip install -e .[astropy,dev] 46 | - name: Run lint 47 | run: make lint 48 | - name: Test with pytest 49 | run: make test 50 | 51 | 52 | build: 53 | needs: [tests] 54 | name: Build source distribution 55 | runs-on: ubuntu-latest 56 | if: github.event_name == 'release' 57 | steps: 58 | - uses: actions/checkout@v2 59 | - name: Set up Python 60 | uses: actions/setup-python@v2 61 | with: 62 | python-version: '3.10' 63 | - name: Install tempo2 on mac 64 | if: runner.os == 'macOS' 65 | run: | 66 | brew unlink gcc && brew link gcc 67 | brew install automake 68 | ./install_tempo2.sh 69 | - name: Install tempo2 on linux 70 | if: runner.os == 'Linux' 71 | run: | 72 | ./install_tempo2.sh 73 | - name: Build 74 | run: | 75 | python -m pip install --upgrade pip setuptools wheel build 76 | python -m build --sdist --outdir dist 77 | make dist 78 | - name: Test the sdist 79 | run: | 80 | mkdir tmp 81 | cd tmp 82 | python -m venv venv-sdist 83 | venv-sdist/bin/python -m pip install --upgrade pip setuptools 84 | venv-sdist/bin/python -m pip install ../dist/libstempo*.tar.gz 85 | venv-sdist/bin/python -c "import libstempo;print(libstempo.__version__)" 86 | - uses: actions/upload-artifact@v4 87 | with: 88 | name: dist 89 | path: dist/* 90 | 91 | 92 | deploy: 93 | needs: [tests, build] 94 | runs-on: ubuntu-latest 95 | if: github.event_name == 'release' 96 | steps: 97 | - uses: actions/checkout@v2 98 | - name: Set up Python 99 | uses: actions/setup-python@v2 100 | with: 101 | python-version: '3.x' 102 | - name: Install dependencies 103 | run: | 104 | python -m pip install --upgrade pip 105 | pip install setuptools wheel twine 106 | - name: Download wheel/dist from build 107 | uses: actions/download-artifact@v4.1.7 108 | with: 109 | name: dist 110 | path: dist 111 | - name: Build and publish 112 | env: 113 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 114 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 115 | run: | 116 | twine upload dist/* 117 | -------------------------------------------------------------------------------- /libstempo/fit.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import scipy.linalg 3 | import scipy.optimize 4 | 5 | 6 | def chisq(psr, formbats=False): 7 | """Return the total chisq for the current timing solution, 8 | removing noise-averaged mean residual, and ignoring deleted points.""" 9 | 10 | if formbats: 11 | psr.formbats() 12 | 13 | res, err = psr.residuals(removemean=False)[psr.deleted == 0], psr.toaerrs[psr.deleted == 0] 14 | 15 | res -= numpy.sum(res / err**2) / numpy.sum(1 / err**2) 16 | 17 | return numpy.sum(res * res / (1e-12 * err * err)) 18 | 19 | 20 | def dchisq(psr, formbats=False, renormalize=True): 21 | """Return gradient of total chisq for the current timing solution, 22 | after removing noise-averaged mean residual, and ignoring deleted points.""" 23 | 24 | if formbats: 25 | psr.formbats() 26 | 27 | res, err = psr.residuals(removemean=False)[psr.deleted == 0], psr.toaerrs[psr.deleted == 0] 28 | 29 | res -= numpy.sum(res / err**2) / numpy.sum(1 / err**2) 30 | 31 | # bats already updated by residuals(); skip constant-phase column 32 | M = psr.designmatrix(updatebats=False, fixunits=True, fixsigns=True)[psr.deleted == 0, 1:] 33 | 34 | # renormalize design-matrix columns 35 | if renormalize: 36 | norm = numpy.sqrt(numpy.sum(M**2, axis=0)) 37 | M /= norm 38 | else: 39 | norm = 1.0 40 | 41 | # compute chisq derivative, de-renormalize 42 | dr = -2 * numpy.dot(M.T, res / (1e-12 * err**2)) * norm 43 | 44 | return dr 45 | 46 | 47 | def findmin(psr, method="Nelder-Mead", history=False, formbats=False, renormalize=True, bounds={}, **kwargs): 48 | """Use scipy.optimize.minimize to find minimum-chisq timing solution, 49 | passing through all extra options. Resets psr[...].val to the final solution, 50 | and returns the final chisq. Will use chisq gradient if method requires it. 51 | Ignores deleted points.""" 52 | 53 | ctr, err = psr.vals(), psr.errs() 54 | 55 | # to avoid losing precision, we're searching in units of parameter errors 56 | 57 | if numpy.any(err == 0.0): 58 | print("Warning: one or more fit parameters have zero a priori error, and won't be searched.") 59 | 60 | hloc, hval = [], [] 61 | 62 | def func(xs): 63 | psr.vals([c + x * e for x, c, e in zip(xs, ctr, err)]) 64 | 65 | ret = chisq(psr, formbats=formbats) 66 | 67 | if numpy.isnan(ret): 68 | print("Warning: chisq is nan at {0}.".format(psr.vals())) 69 | 70 | if history: 71 | hloc.append(psr.vals()) 72 | hval.append(ret) 73 | 74 | return ret 75 | 76 | def dfunc(xs): 77 | psr.vals([c + x * e for x, c, e in zip(xs, ctr, err)]) 78 | 79 | dc = dchisq(psr, formbats=formbats, renormalize=renormalize) 80 | ret = numpy.array([d * e for d, e in zip(dc, err)], "d") 81 | 82 | return ret 83 | 84 | opts = kwargs.copy() 85 | 86 | if method not in ["Nelder-Mead", "Powell"]: 87 | opts["jac"] = dfunc 88 | 89 | if method in ["L-BFGS-B"]: 90 | opts["bounds"] = [ 91 | (float((bounds[par][0] - ctr[i]) / err[i]), float((bounds[par][1] - ctr[i]) / err[i])) 92 | if par in bounds 93 | else (None, None) 94 | for i, par in enumerate(psr.pars()) 95 | ] 96 | 97 | res = scipy.optimize.minimize(func, [0.0] * len(ctr), method=method, **opts) 98 | 99 | if hasattr(res, "message"): 100 | print(res.message) 101 | 102 | # this will also set parameters to the minloc 103 | minchisq = func(res.x) 104 | 105 | if history: 106 | return minchisq, numpy.array(hval), numpy.array(hloc) 107 | else: 108 | return minchisq 109 | 110 | 111 | def glsfit(psr, renormalize=True): 112 | """Solve local GLS problem using scipy.linalg.cholesky. 113 | Update psr[...].val and psr[...].err from solution. 114 | If renormalize=True, normalize each design-matrix column by its norm.""" 115 | 116 | mask = psr.deleted == 0 117 | res, err = psr.residuals(removemean=False)[mask], psr.toaerrs[mask] 118 | M = psr.designmatrix(updatebats=False, fixunits=True, fixsigns=True)[mask, :] 119 | 120 | C = numpy.diag((err * 1e-6) ** 2) 121 | 122 | if renormalize: 123 | norm = numpy.sqrt(numpy.sum(M**2, axis=0)) 124 | M /= norm 125 | else: 126 | norm = numpy.ones_like(M[0, :]) 127 | 128 | mtcm = numpy.dot(M.T, numpy.dot(numpy.linalg.inv(C), M)) 129 | mtcy = numpy.dot(M.T, numpy.dot(numpy.linalg.inv(C), res)) 130 | 131 | xvar = numpy.linalg.inv(mtcm) 132 | 133 | c = scipy.linalg.cho_factor(mtcm) 134 | xhat = scipy.linalg.cho_solve(c, mtcy) 135 | 136 | sol = psr.vals() 137 | psr.vals(sol + xhat[1:] / norm[1:]) 138 | psr.errs(numpy.sqrt(numpy.diag(xvar)[1:]) / norm[1:]) 139 | 140 | return chisq(psr) 141 | -------------------------------------------------------------------------------- /libstempo/data/J1909-3744_NANOGrav_dfg+12.par: -------------------------------------------------------------------------------- 1 | PSRJ 1909-3744 2 | RAJ 19:09:47.4380322 1 0.00001066218470748522 3 | DECJ -37:44:14.31904 1 0.00043932586302661849 4 | F0 339.3156927586801522 1 0.00000000000356236809 5 | F1 -1.6148002837260219042e-15 1 3.0417933071065248185e-20 6 | PEPOCH 53000 7 | POSEPOCH 53000 8 | DMEPOCH 53000 9 | DM 10.39468 10 | PMRA -9.6131471013527717579 1 0.03058668563424442322 11 | PMDEC -35.535142282332215051 1 0.12725313319872413875 12 | PX 0.089434981481806251577 1 0.23940935414758984789 13 | SINI 0.99887143074446215385 1 0.00068136602544533104 14 | BINARY ELL1 15 | PB 1.5334494513051029191 1 0.00000000002710124722 16 | A1 1.897991023852069233 1 0.00000038817787068657 17 | TASC 53113.950587082067369 1 0.00000002624038807463 18 | EPS1 -3.4172897686213263339e-08 1 0.00000017343170385726 19 | EPS2 -2.6426180555364916833e-07 1 0.00000005600838250388 20 | M2 0.21308376259512886794 1 0.02303114786133724640 21 | START 53292.015535525883024 22 | FINISH 55122.845517685571394 23 | TZRMJD 54218.338114733154768 24 | TZRFRQ 1372 25 | TZRSITE gbt 26 | TRES 0.181 27 | EPHVER 5 28 | DMX_0001 0 29 | DMX_0002 -0.00026660259649978304921 1 0.00006020055358505948 30 | DMX_0003 -0.00059065284874180338237 1 0.00008144099741253972 31 | DMX_0004 -0.00075267150029576306045 1 0.00008512284367543255 32 | DMX_0005 -0.00081078205801421796559 1 0.00008598622726764214 33 | DMX_0006 -0.00094057688388278449972 1 0.00008835932678643132 34 | DMX_0007 -0.00083853592921790342804 1 0.00007180473333978707 35 | DMX_0008 -0.00081896528177834817099 1 0.00007021674221021190 36 | DMX_0009 -0.00080170345026672798516 1 0.00007307476145251503 37 | DMX_0010 -0.0011210356242656047023 1 0.00011200658705129337 38 | DMX_0011 -0.00094068302070258838272 1 0.00009777107476349591 39 | DMX_0012 -0.0011739867959074997463 1 0.00010075999697913395 40 | DMX_0013 -0.001163446874096801241 1 0.00008343519594134365 41 | DMX_0014 -0.0011008525479298384834 1 0.00008281270603580082 42 | DMX_0015 -0.0012000652424711605309 1 0.00009482087182534869 43 | DMX_0016 -0.0011689514229827239794 1 0.00008254043681368759 44 | DMX_0017 -0.0011376394510456758903 1 0.00007947334236749353 45 | DMX_0018 -0.0011429638074958389919 1 0.00007818442142458206 46 | DMX_0019 -0.0013368887413239420278 1 0.00009186981321539782 47 | DMX_0020 -0.0012626639802635974297 1 0.00008858816589512184 48 | DMX_0021 -0.0012785718707937009891 1 0.00007783195989769682 49 | DMX_0022 -0.0013595722715176567661 1 0.00008028699263086770 50 | DMX_0023 -0.0015949745632144933123 1 0.00010803038761242445 51 | DMX_0024 -0.0016173293005995523627 1 0.00009874939791598136 52 | DMX_0025 -0.0015778234105281718639 1 0.00008869997485418948 53 | DMX_0026 -0.0015893226233900235647 1 0.00007410982897711474 54 | DMX_0027 -0.0015400322240445938122 1 0.00007492194120255986 55 | DMX_0028 -0.0016037968104013106133 1 0.00008174689546904196 56 | DMX_0029 -0.0016102010268098140058 1 0.00008803933835383684 57 | DMX_0030 -0.0016826723208923927612 1 0.00007678137330408083 58 | DMX_0031 -0.0015057864276471900037 1 0.00008404838521350010 59 | DMX_0032 -0.0016631529235034278381 1 0.00007241567617478780 60 | DMXR1_0001 53291.530799999999999 61 | DMXR1_0002 53343.400200000000002 62 | DMXR1_0003 53797.9787 63 | DMXR1_0004 53837.8765 64 | DMXR1_0005 53857.822399999999998 65 | DMXR1_0006 53888.744700000000002 66 | DMXR1_0007 53979.492999999999999 67 | DMXR1_0008 54009.406300000000002 68 | DMXR1_0009 54035.3353 69 | DMXR1_0010 54064.253399999999999 70 | DMXR1_0011 54100.1554 71 | DMXR1_0012 54133.063400000000001 72 | DMXR1_0013 54217.840900000000001 73 | DMXR1_0014 54373.407 74 | DMXR1_0015 54428.248200000000001 75 | DMXR1_0016 54525.983699999999999 76 | DMXR1_0017 54551.922799999999999 77 | DMXR1_0018 54577.847899999999999 78 | DMXR1_0019 54640.679400000000001 79 | DMXR1_0020 54706.497299999999999 80 | DMXR1_0021 54764.333600000000001 81 | DMXR1_0022 54794.2347 82 | DMXR1_0023 54820.160299999999999 83 | DMXR1_0024 54850.095500000000001 84 | DMXR1_0025 54880.0214 85 | DMXR1_0026 54912.927600000000002 86 | DMXR1_0027 54941.851600000000001 87 | DMXR1_0028 54975.945399999999999 88 | DMXR1_0029 55003.6859 89 | DMXR1_0030 55065.514900000000001 90 | DMXR1_0031 55094.426500000000001 91 | DMXR1_0032 55118.359100000000002 92 | DMXR2_0001 53293.526800000000001 93 | DMXR2_0002 53356.3294 94 | DMXR2_0003 53800.9818 95 | DMXR2_0004 53839.869199999999999 96 | DMXR2_0005 53859.827999999999999 97 | DMXR2_0006 53890.741399999999999 98 | DMXR2_0007 53982.481000000000002 99 | DMXR2_0008 54014.399100000000001 100 | DMXR2_0009 54037.3186 101 | DMXR2_0010 54073.235199999999999 102 | DMXR2_0011 54102.1492 103 | DMXR2_0012 54135.061699999999998 104 | DMXR2_0013 54220.833400000000001 105 | DMXR2_0014 54375.4102 106 | DMXR2_0015 54430.255500000000001 107 | DMXR2_0016 54529.965499999999999 108 | DMXR2_0017 54553.917400000000001 109 | DMXR2_0018 54581.8386 110 | DMXR2_0019 54642.677499999999998 111 | DMXR2_0020 54711.482900000000001 112 | DMXR2_0021 54769.328399999999998 113 | DMXR2_0022 54797.244599999999998 114 | DMXR2_0023 54828.1587 115 | DMXR2_0024 54856.081199999999999 116 | DMXR2_0025 54886.000800000000002 117 | DMXR2_0026 54916.920500000000001 118 | DMXR2_0027 54944.847000000000001 119 | DMXR2_0028 54978.756699999999999 120 | DMXR2_0029 55005.6843 121 | DMXR2_0030 55073.490600000000001 122 | DMXR2_0031 55101.41 123 | DMXR2_0032 55123.3444 124 | CLK TT(BIPM2011) 125 | MODE 1 126 | UNITS TDB 127 | T2CMETHOD TEMPO 128 | NE_SW 0.000 129 | CORRECT_TROPOSPHERE N 130 | EPHEM DE405 131 | NITS 1 132 | NTOA 1001 133 | CHI2R 1.9545 918 134 | JUMP -chanid gasp_1376 1.1689461818884e-07 1 135 | JUMP -chanid gasp_1380 8.6833553747316e-08 1 136 | JUMP -chanid gasp_1384 -3.691903160802e-08 1 137 | JUMP -chanid gasp_1388 -9.7574173032568e-08 1 138 | JUMP -chanid gasp_1392 -6.4752911496679e-08 1 139 | JUMP -chanid gasp_1396 -7.0533943921807e-08 1 140 | JUMP -chanid gasp_1400 -1.2493198083027e-07 1 141 | JUMP -chanid gasp_1404 -3.6026917916572e-08 1 142 | JUMP -chanid gasp_1408 -2.4167891097568e-08 1 143 | JUMP -chanid gasp_1412 -4.6946839405981e-08 1 144 | JUMP -chanid gasp_1416 -1.0062554197771e-07 1 145 | JUMP -chanid gasp_1420 -1.000305910222e-07 1 146 | JUMP -chanid gasp_1424 -5.2537740906211e-08 1 147 | JUMP -chanid gasp_1428 -9.853387350061e-08 1 148 | JUMP -chanid gasp_1432 -1.4277117785432e-07 1 149 | JUMP -chanid gasp_792 3.9627936223941e-06 1 150 | JUMP -chanid gasp_800 3.8808250600427e-06 1 151 | JUMP -chanid gasp_804 3.8303053226634e-06 1 152 | JUMP -chanid gasp_808 3.724073027373e-06 1 153 | JUMP -chanid gasp_812 3.7041370102185e-06 1 154 | JUMP -chanid gasp_820 3.6111130855805e-06 1 155 | JUMP -chanid gasp_824 3.7363808197839e-06 1 156 | JUMP -chanid gasp_828 3.7013264107374e-06 1 157 | JUMP -chanid gasp_832 3.6407492319539e-06 1 158 | JUMP -chanid gasp_836 3.534324786648e-06 1 159 | JUMP -chanid gasp_840 3.5290756230543e-06 1 160 | JUMP -chanid gasp_844 3.5172184111846e-06 1 161 | JUMP -chanid gasp_848 3.4712407663056e-06 1 162 | JUMP -chanid gasp_852 3.2894097638622e-06 1 163 | JUMP -chanid gasp_856 2.8769200075288e-06 1 164 | JUMP -chanid gasp_860 3.1986088990894e-06 1 165 | JUMP -chanid gasp_864 3.2321653420717e-06 1 166 | JUMP -chanid gasp_868 3.2296297918683e-06 1 167 | JUMP -chanid gasp_872 3.0363058959722e-06 1 168 | JUMP -chanid gasp_876 2.99405593586e-06 1 169 | JUMP -chanid gasp_880 2.890261885834e-06 1 170 | JUMP -chanid gasp_884 3.0454926082367e-06 1 171 | -------------------------------------------------------------------------------- /libstempo/eccUtils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created by stevertaylor and jellis18 3 | Copyright (c) 2015 Stephen R. Taylor and Justin A. Ellis 4 | 5 | Code developed by Stephen R. Taylor and incorporated into libsbempo by 6 | Justin A. Ellis. 7 | 8 | Relevant References are: 9 | 10 | Taylor et al. (2015) [http://adsabs.harvard.edu/abs/2015arXiv150506208T] 11 | Barack and Cutler (2004) [http://adsabs.harvard.edu/abs/2004PhRvD..69h2005B] 12 | 13 | """ 14 | 15 | from pathlib import Path 16 | 17 | import numpy as np 18 | import scipy.constants as sc 19 | import scipy.special as ss 20 | from scipy.integrate import odeint 21 | from scipy.interpolate import interp1d 22 | 23 | SOLAR2S = sc.G / sc.c**3 * 1.98855e30 24 | KPC2S = sc.parsec / sc.c * 1e3 25 | MPC2S = sc.parsec / sc.c * 1e6 26 | 27 | ECC_FILE = Path(__file__).parent / "ecc_vs_nharm.txt" 28 | 29 | 30 | def make_ecc_interpolant(): 31 | """ 32 | Make interpolation function from eccentricity file to 33 | determine number of harmonics to use for a given 34 | eccentricity. 35 | 36 | :returns: interpolant 37 | """ 38 | 39 | fil = np.loadtxt(ECC_FILE) 40 | 41 | return interp1d(fil[:, 0], fil[:, 1]) 42 | 43 | 44 | def get_edot(F, mc, e): 45 | """ 46 | Compute eccentricity derivative from Taylor et al. (2015) 47 | 48 | :param F: Orbital frequency [Hz] 49 | :param mc: Chirp mass of binary [Solar Mass] 50 | :param e: Eccentricity of binary 51 | 52 | :returns: de/dt 53 | 54 | """ 55 | 56 | # chirp mass 57 | mc *= SOLAR2S 58 | 59 | dedt = -304 / (15 * mc) * (2 * np.pi * mc * F) ** (8 / 3) * e * (1 + 121 / 304 * e**2) / ((1 - e**2) ** (5 / 2)) 60 | 61 | return dedt 62 | 63 | 64 | def get_Fdot(F, mc, e): 65 | """ 66 | Compute frequency derivative from Taylor et al. (2015) 67 | 68 | :param F: Orbital frequency [Hz] 69 | :param mc: Chirp mass of binary [Solar Mass] 70 | :param e: Eccentricity of binary 71 | 72 | :returns: dF/dt 73 | 74 | """ 75 | 76 | # chirp mass 77 | mc *= SOLAR2S 78 | 79 | dFdt = ( 80 | 48 81 | / (5 * np.pi * mc**2) 82 | * (2 * np.pi * mc * F) ** (11 / 3) 83 | * (1 + 73 / 24 * e**2 + 37 / 96 * e**4) 84 | / ((1 - e**2) ** (7 / 2)) 85 | ) 86 | 87 | return dFdt 88 | 89 | 90 | def get_gammadot(F, mc, q, e): 91 | """ 92 | Compute gamma dot from Barack and Cutler (2004) 93 | 94 | :param F: Orbital frequency [Hz] 95 | :param mc: Chirp mass of binary [Solar Mass] 96 | :param q: Mass ratio of binary 97 | :param e: Eccentricity of binary 98 | 99 | :returns: dgamma/dt 100 | 101 | """ 102 | 103 | # chirp mass 104 | mc *= SOLAR2S 105 | 106 | # total mass 107 | m = (((1 + q) ** 2) / q) ** (3 / 5) * mc 108 | 109 | dgdt = ( 110 | 6 111 | * np.pi 112 | * F 113 | * (2 * np.pi * F * m) ** (2 / 3) 114 | / (1 - e**2) 115 | * (1 + 0.25 * (2 * np.pi * F * m) ** (2 / 3) / (1 - e**2) * (26 - 15 * e**2)) 116 | ) 117 | 118 | return dgdt 119 | 120 | 121 | def get_coupled_ecc_eqns(y, t, mc, q): 122 | """ 123 | Computes the coupled system of differential 124 | equations from Peters (1964) and Barack & 125 | Cutler (2004). This is a system of three variables: 126 | 127 | F: Orbital frequency [Hz] 128 | e: Orbital eccentricity 129 | gamma: Angle of precession of periastron [rad] 130 | phase0: Orbital phase [rad] 131 | 132 | :param y: Vector of input parameters [F, e, gamma] 133 | :param t: Time [s] 134 | :param mc: Chirp mass of binary [Solar Mass] 135 | :param q: Mass ratio of binary 136 | 137 | :returns: array of derivatives [dF/dt, de/dt, dgamma/dt, dphase/dt] 138 | """ 139 | 140 | F = y[0] 141 | e = y[1] 142 | 143 | dFdt = get_Fdot(F, mc, e) 144 | dedt = get_edot(F, mc, e) 145 | dgdt = get_gammadot(F, mc, q, e) 146 | dphasedt = 2 * np.pi * F 147 | 148 | return np.array([dFdt, dedt, dgdt, dphasedt]) 149 | 150 | 151 | def solve_coupled_ecc_solution(F0, e0, gamma0, phase0, mc, q, t): 152 | """ 153 | Compute the solution to the coupled system of equations 154 | from from Peters (1964) and Barack & Cutler (2004) at 155 | a given time. 156 | 157 | :param F0: Initial orbital frequency [Hz] 158 | :param e0: Initial orbital eccentricity 159 | :param gamma0: Initial angle of precession of periastron [rad] 160 | :param mc: Chirp mass of binary [Solar Mass] 161 | :param q: Mass ratio of binary 162 | :param t: Time at which to evaluate solution [s] 163 | 164 | :returns: (F(t), e(t), gamma(t), phase(t)) 165 | 166 | """ 167 | 168 | y0 = np.array([F0, e0, gamma0, phase0]) 169 | 170 | y, infodict = odeint(get_coupled_ecc_eqns, y0, t, args=(mc, q), full_output=True) 171 | 172 | if infodict["message"] == "Integration successful.": 173 | ret = y 174 | else: 175 | ret = 0 176 | 177 | return ret 178 | 179 | 180 | def get_an(n, mc, dl, F, e): 181 | """ 182 | Compute a_n from Eq. 22 of Taylor et al. (2015). 183 | 184 | :param n: Harmonic number 185 | :param mc: Chirp mass of binary [Solar Mass] 186 | :param dl: Luminosity distance [Mpc] 187 | :param F: Orbital frequency of binary [Hz] 188 | :param e: Orbital Eccentricity 189 | 190 | :returns: a_n 191 | 192 | """ 193 | 194 | # convert to seconds 195 | mc *= SOLAR2S 196 | dl *= MPC2S 197 | 198 | omega = 2 * np.pi * F 199 | 200 | amp = n * mc ** (5 / 3) * omega ** (2 / 3) / dl 201 | 202 | ret = -amp * ( 203 | ss.jn(n - 2, n * e) 204 | - 2 * e * ss.jn(n - 1, n * e) 205 | + (2 / n) * ss.jn(n, n * e) 206 | + 2 * e * ss.jn(n + 1, n * e) 207 | - ss.jn(n + 2, n * e) 208 | ) 209 | 210 | return ret 211 | 212 | 213 | def get_bn(n, mc, dl, F, e): 214 | """ 215 | Compute b_n from Eq. 22 of Taylor et al. (2015). 216 | 217 | :param n: Harmonic number 218 | :param mc: Chirp mass of binary [Solar Mass] 219 | :param dl: Luminosity distance [Mpc] 220 | :param F: Orbital frequency of binary [Hz] 221 | :param e: Orbital Eccentricity 222 | 223 | :returns: b_n 224 | 225 | """ 226 | 227 | # convert to seconds 228 | mc *= SOLAR2S 229 | dl *= MPC2S 230 | 231 | omega = 2 * np.pi * F 232 | 233 | amp = n * mc ** (5 / 3) * omega ** (2 / 3) / dl 234 | 235 | ret = -amp * np.sqrt(1 - e**2) * (ss.jn(n - 2, n * e) - 2 * ss.jn(n, n * e) + ss.jn(n + 2, n * e)) 236 | 237 | return ret 238 | 239 | 240 | def get_cn(n, mc, dl, F, e): 241 | """ 242 | Compute c_n from Eq. 22 of Taylor et al. (2015). 243 | 244 | :param n: Harmonic number 245 | :param mc: Chirp mass of binary [Solar Mass] 246 | :param dl: Luminosity distance [Mpc] 247 | :param F: Orbital frequency of binary [Hz] 248 | :param e: Orbital Eccentricity 249 | 250 | :returns: c_n 251 | 252 | """ 253 | 254 | # convert to seconds 255 | mc *= SOLAR2S 256 | dl *= MPC2S 257 | 258 | omega = 2 * np.pi * F 259 | 260 | amp = 2 * mc ** (5 / 3) * omega ** (2 / 3) / dl 261 | 262 | ret = amp * ss.jn(n, n * e) / (n * omega) 263 | 264 | return ret 265 | 266 | 267 | def calculate_splus_scross(nmax, mc, dl, F, e, t, l0, gamma, gammadot, inc): 268 | """ 269 | Calculate splus and scross summed over all harmonics. 270 | This waveform differs slightly from that in Taylor et al (2015) 271 | in that it includes the time dependence of the advance of periastron. 272 | 273 | :param nmax: Total number of harmonics to use 274 | :param mc: Chirp mass of binary [Solar Mass] 275 | :param dl: Luminosity distance [Mpc] 276 | :param F: Orbital frequency of binary [Hz] 277 | :param e: Orbital Eccentricity 278 | :param t: TOAs [s] 279 | :param l0: Initial eccentric anomoly [rad] 280 | :param gamma: Angle of periastron advance [rad] 281 | :param gammadot: Time derivative of angle of periastron advance [rad/s] 282 | :param inc: Inclination angle [rad] 283 | 284 | """ 285 | 286 | n = np.arange(1, nmax) 287 | 288 | # time dependent amplitudes 289 | an = get_an(n, mc, dl, F, e) 290 | bn = get_bn(n, mc, dl, F, e) 291 | cn = get_cn(n, mc, dl, F, e) 292 | 293 | # time dependent terms 294 | omega = 2 * np.pi * F 295 | gt = gamma + gammadot * t 296 | lt = l0 + omega * t 297 | 298 | # tiled phase 299 | phase1 = n * np.tile(lt, (nmax - 1, 1)).T 300 | phase2 = np.tile(gt, (nmax - 1, 1)).T 301 | phasep = phase1 + 2 * phase2 302 | phasem = phase1 - 2 * phase2 303 | 304 | # intermediate terms 305 | sp = np.sin(phasem) / (n * omega - 2 * gammadot) + np.sin(phasep) / (n * omega + 2 * gammadot) 306 | sm = np.sin(phasem) / (n * omega - 2 * gammadot) - np.sin(phasep) / (n * omega + 2 * gammadot) 307 | cp = np.cos(phasem) / (n * omega - 2 * gammadot) + np.cos(phasep) / (n * omega + 2 * gammadot) 308 | cm = np.cos(phasem) / (n * omega - 2 * gammadot) - np.cos(phasep) / (n * omega + 2 * gammadot) 309 | 310 | splus_n = -0.5 * (1 + np.cos(inc) ** 2) * (an * sp - bn * sm) + (1 - np.cos(inc) ** 2) * cn * np.sin(phase1) 311 | scross_n = np.cos(inc) * (an * cm - bn * cp) 312 | 313 | return np.sum(splus_n, axis=1), np.sum(scross_n, axis=1) 314 | -------------------------------------------------------------------------------- /tests/test_fakepulsar.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import unittest 3 | from pathlib import Path 4 | from astropy.time import Time 5 | 6 | import libstempo as t2 7 | import numpy as np 8 | 9 | from libstempo.toasim import fakepulsar 10 | 11 | DATA_PATH = t2.__path__[0] + "/data/" 12 | 13 | TMP_DIR = Path("test_fake_output") 14 | TMP_DIR.mkdir(exist_ok=True) 15 | 16 | try: 17 | NP_LONG_DOUBLE_TYPE = np.float128 18 | except AttributeError: 19 | NP_LONG_DOUBLE_TYPE = np.double 20 | 21 | 22 | class TestFakePulsar(unittest.TestCase): 23 | @classmethod 24 | def setUpClass(cls): 25 | cls.obstimes = np.arange(53000, 54800, 10, dtype=NP_LONG_DOUBLE_TYPE) 26 | cls.toaerr = 1e-3 27 | cls.freq = 1440.0 28 | cls.observatory = "ao" 29 | cls.parfile = DATA_PATH + "/J1909-3744_NANOGrav_dfg+12.par" 30 | 31 | # create a fake pulsar using fakepulsar 32 | cls.fakepsr = fakepulsar( 33 | parfile=cls.parfile, 34 | obstimes=cls.obstimes, 35 | toaerr=cls.toaerr, 36 | freq=cls.freq, 37 | observatory=cls.observatory, 38 | iters=0, 39 | ) 40 | 41 | # create a fake pulsar using tempopulsar 42 | cls.fakepsrtp = t2.tempopulsar( 43 | parfile=cls.parfile, 44 | toas=cls.obstimes, 45 | toaerrs=cls.toaerr, 46 | observatory=cls.observatory, 47 | obsfreq=cls.freq, 48 | dofit=False, 49 | ) 50 | 51 | @classmethod 52 | def tearDownClass(cls): 53 | shutil.rmtree(TMP_DIR) 54 | 55 | def test_exceptions(self): 56 | """ 57 | Test exceptions when creating a fake pulsar via tempopulsar. 58 | """ 59 | 60 | # pass string rather than number for TOA 61 | with self.assertRaises(TypeError): 62 | t2.tempopulsar( 63 | parfile=self.parfile, 64 | toas="blah", 65 | toaerrs=self.toaerr, 66 | observatory=self.observatory, 67 | obsfreq=self.freq, 68 | ) 69 | 70 | # pass string rather than number for TOA error 71 | with self.assertRaises(TypeError): 72 | t2.tempopulsar( 73 | parfile=self.parfile, 74 | toas=self.obstimes, 75 | toaerrs="blah", 76 | observatory=self.observatory, 77 | obsfreq=self.freq, 78 | ) 79 | 80 | # pass integer rather than string for observatory 81 | with self.assertRaises(TypeError): 82 | t2.tempopulsar( 83 | parfile=self.parfile, 84 | toas=self.obstimes, 85 | toaerrs=self.toaerr, 86 | observatory=0, 87 | obsfreq=self.freq, 88 | ) 89 | 90 | # pass string rather than number for obsfreq 91 | with self.assertRaises(TypeError): 92 | t2.tempopulsar( 93 | parfile=self.parfile, 94 | toas=self.obstimes, 95 | toaerrs=self.toaerr, 96 | observatory=self.observatory, 97 | obsfreq="blah", 98 | ) 99 | 100 | # test exceptions if values are not given 101 | kwargs = { 102 | "parfile": self.parfile, 103 | "toas": self.obstimes, 104 | "toaerrs": self.toaerr, 105 | "observatory": self.observatory, 106 | "obsfreq": self.freq, 107 | } 108 | 109 | for key in ["toaerrs", "observatory", "obsfreq"]: 110 | copykwargs = kwargs.copy() 111 | copykwargs[key] = None 112 | with self.assertRaises(ValueError): 113 | t2.tempopulsar(**copykwargs) 114 | 115 | # test exceptions for inconsistent lengths 116 | for key in ["toaerrs", "observatory", "obsfreq"]: 117 | copykwargs = kwargs.copy() 118 | # set to two value list 119 | copykwargs[key] = [kwargs[key] for _ in range(2)] 120 | with self.assertRaises(ValueError): 121 | t2.tempopulsar(**copykwargs) 122 | 123 | def test_astropy_array(self): 124 | """ 125 | Test passing TOAs as an astropy Time array. 126 | """ 127 | 128 | times = Time(self.obstimes, format="mjd", scale="utc") 129 | 130 | psr = t2.tempopulsar( 131 | parfile=self.parfile, 132 | toas=times, 133 | toaerrs=self.toaerr, 134 | observatory=self.observatory, 135 | obsfreq=self.freq, 136 | ) 137 | 138 | self.assertEqual(len(self.obstimes), psr.nobs) 139 | self.assertTrue(np.all(self.obstimes == self.fakepsr.stoas)) 140 | self.assertTrue(np.all(psr.stoas == self.fakepsr.stoas)) 141 | self.assertEqual(psr.stoas[0].dtype, NP_LONG_DOUBLE_TYPE) 142 | 143 | def test_single_values(self): 144 | """ 145 | Test passing single value TOAs. 146 | """ 147 | 148 | psr = t2.tempopulsar( 149 | parfile=self.parfile, 150 | toas=self.obstimes[0], 151 | toaerrs=self.toaerr, 152 | observatory=self.observatory, 153 | obsfreq=self.freq, 154 | ) 155 | 156 | self.assertEqual(psr.nobs, 1) 157 | self.assertEqual(len(psr.stoas), 1) 158 | self.assertTrue(np.all(self.fakepsr.stoas[0] == psr.stoas[0])) 159 | self.assertEqual(psr.stoas[0].dtype, NP_LONG_DOUBLE_TYPE) 160 | 161 | def test_toa_errs(self): 162 | """ 163 | Test TOA errors are set correctly. 164 | """ 165 | 166 | self.assertTrue(np.all(self.fakepsr.toaerrs == self.toaerr)) 167 | self.assertTrue(np.all(self.fakepsrtp.toaerrs == self.toaerr)) 168 | 169 | def test_observatory(self): 170 | """ 171 | Test observatory values are set correctly. 172 | """ 173 | 174 | self.assertTrue(np.all(self.fakepsr.telescope() == str.encode(self.observatory))) 175 | self.assertTrue(np.all(self.fakepsrtp.telescope() == str.encode(self.observatory))) 176 | 177 | def test_frequency(self): 178 | """ 179 | Test frequency values are set correctly. 180 | """ 181 | 182 | self.assertTrue(np.all(self.fakepsr.freqs == self.freq)) 183 | self.assertTrue(np.all(self.fakepsrtp.freqs == self.freq)) 184 | 185 | def test_sat_parts(self): 186 | """ 187 | Test SAT day and second values are set correctly. 188 | """ 189 | 190 | self.assertTrue(np.all(self.fakepsr.satDay() == self.fakepsrtp.satDay())) 191 | self.assertTrue(np.all(self.fakepsr.satSec() == self.fakepsrtp.satSec())) 192 | 193 | def test_deleted(self): 194 | """ 195 | Test deleted values are equivalent. 196 | """ 197 | 198 | self.assertTrue(np.all(self.fakepsr.deleted == self.fakepsrtp.deleted)) 199 | self.assertTrue(np.all(self.fakepsr.deleted == np.zeros(len(self.obstimes), dtype=np.int32))) 200 | 201 | def test_pulsar_params(self): 202 | """ 203 | Test pulsar parameters have been read in the same in both cases. 204 | """ 205 | 206 | self.assertEqual(self.fakepsr.pars("all"), self.fakepsrtp.pars("all")) 207 | 208 | for key in self.fakepsr.pars("all"): 209 | self.assertEqual(self.fakepsr[key].val, self.fakepsrtp[key].val) 210 | 211 | def test_fake_pulsar(self): 212 | """ 213 | Test fakepulsar function vs passing inputs directly to tempopulsar. 214 | """ 215 | 216 | self.assertEqual(self.fakepsrtp.nobs, len(self.obstimes)) 217 | self.assertEqual(self.fakepsrtp.nobs, self.fakepsr.nobs) 218 | self.assertEqual(self.fakepsrtp.name, "1909-3744") 219 | self.assertEqual(self.fakepsr.name, "1909-3744") 220 | 221 | self.assertTrue(np.all(self.fakepsrtp.stoas == self.obstimes)) 222 | self.assertTrue(np.all(self.fakepsrtp.stoas == self.fakepsr.stoas)) 223 | self.assertTrue(np.all(self.fakepsrtp.toas() == self.fakepsr.toas())) 224 | 225 | # check residuals are the same 226 | self.assertTrue(np.all(self.fakepsrtp.residuals() == self.fakepsr.residuals())) 227 | self.assertTrue(np.all(self.fakepsrtp.phaseresiduals() == self.fakepsr.phaseresiduals())) 228 | 229 | def test_write_tim(self): 230 | """ 231 | Test writing out the .tim file and then reading it back in. 232 | """ 233 | 234 | self.fakepsr.savetim(str(TMP_DIR / "fakepsr.tim")) 235 | self.fakepsrtp.savetim(str(TMP_DIR / "fakepsrtp.tim")) 236 | 237 | self.assertTrue((TMP_DIR / "fakepsr.tim").exists()) 238 | self.assertTrue((TMP_DIR / "fakepsrtp.tim").exists()) 239 | 240 | t2.purgetim(str(TMP_DIR / "fakepsr.tim")) 241 | t2.purgetim(str(TMP_DIR / "fakepsrtp.tim")) 242 | 243 | newfakepsr = t2.tempopulsar(parfile=self.parfile, timfile=str(TMP_DIR / "fakepsr.tim"), dofit=False) 244 | newfakepsrtp = t2.tempopulsar(parfile=self.parfile, timfile=str(TMP_DIR / "fakepsrtp.tim"), dofit=False) 245 | 246 | self.assertEqual(newfakepsrtp.nobs, len(self.obstimes)) 247 | self.assertEqual(newfakepsrtp.nobs, newfakepsr.nobs) 248 | self.assertEqual(newfakepsrtp.name, "1909-3744") 249 | self.assertEqual(newfakepsr.name, "1909-3744") 250 | 251 | self.assertTrue(np.all(newfakepsrtp.stoas == self.obstimes)) 252 | self.assertTrue(np.all(newfakepsrtp.stoas == self.fakepsrtp.stoas)) 253 | self.assertTrue(np.all(newfakepsrtp.toas() == self.fakepsrtp.toas())) 254 | self.assertTrue(np.all(newfakepsr.stoas == self.fakepsrtp.stoas)) 255 | self.assertTrue(np.all(newfakepsr.toas() == newfakepsrtp.toas())) 256 | 257 | # check residuals are the same 258 | self.assertTrue(np.all(newfakepsrtp.residuals() == self.fakepsrtp.residuals())) 259 | self.assertTrue(np.all(newfakepsrtp.phaseresiduals() == self.fakepsrtp.phaseresiduals())) 260 | self.assertTrue(np.all(newfakepsrtp.residuals() == newfakepsr.residuals())) 261 | -------------------------------------------------------------------------------- /libstempo/plot.py: -------------------------------------------------------------------------------- 1 | import math 2 | import types 3 | 4 | import matplotlib.pyplot as P 5 | import numpy as N 6 | 7 | 8 | def plotres(psr, deleted=False, group=None, **kwargs): 9 | """Plot residuals, compute unweighted rms residual.""" 10 | 11 | res, t, errs = psr.residuals(), psr.toas(), psr.toaerrs 12 | 13 | if (not deleted) and N.any(psr.deleted != 0): 14 | res, t, errs = res[psr.deleted == 0], t[psr.deleted == 0], errs[psr.deleted == 0] 15 | print("Plotting {0}/{1} nondeleted points.".format(len(res), psr.nobs)) 16 | 17 | meanres = math.sqrt(N.mean(res**2)) / 1e-6 18 | 19 | if group is None: 20 | i = N.argsort(t) 21 | P.errorbar(t[i], res[i] / 1e-6, yerr=errs[i], fmt="x", **kwargs) 22 | else: 23 | if (not deleted) and N.any(psr.deleted): 24 | flagmask = psr.flagvals(group)[~psr.deleted] 25 | else: 26 | flagmask = psr.flagvals(group) 27 | 28 | unique = list(set(flagmask)) 29 | 30 | for flagval in unique: 31 | f = flagmask == flagval 32 | flagres, flagt, flagerrs = res[f], t[f], errs[f] 33 | i = N.argsort(flagt) 34 | P.errorbar(flagt[i], flagres[i] / 1e-6, yerr=flagerrs[i], fmt="x", **kwargs) 35 | 36 | P.legend(unique, numpoints=1, bbox_to_anchor=(1.1, 1.1)) 37 | 38 | P.xlabel("MJD") 39 | P.ylabel("res [us]") 40 | P.title("{0} - rms res = {1:.2f} us".format(psr.name, meanres)) 41 | 42 | 43 | # select parameters by name or number, omit non-existing 44 | def _select(p, pars, select): 45 | sel = [] 46 | for s in select: 47 | if isinstance(s, str) and s in pars: 48 | sel.append(pars.index(s)) 49 | elif isinstance(s, int) and s < p: 50 | sel.append(s) 51 | return len(sel), sel 52 | 53 | 54 | def plothist( 55 | data, 56 | pars=[], 57 | offsets=[], 58 | norms=[], 59 | select=[], 60 | weights={}, 61 | ranges={}, 62 | labels={}, 63 | skip=[], 64 | append=False, 65 | bins=50, 66 | color="k", 67 | linestyle=None, 68 | linewidth=1, 69 | title=None, 70 | ): 71 | if hasattr(data, "data") and not isinstance(data, N.ndarray): 72 | # parse a multinestdata structure 73 | if not pars and hasattr(data, "parnames"): 74 | pars = data.parnames 75 | data = data.data 76 | 77 | p = data.shape[-1] 78 | 79 | if not pars: 80 | pars = map("p{0}".format, range(p)) 81 | 82 | if offsets: 83 | data = data.copy() 84 | 85 | if isinstance(offsets, dict): 86 | for i, par in enumerate(pars): 87 | if par in offsets: 88 | data[:, i] = data[:, i] - offsets[par] 89 | else: 90 | if len(offsets) < p: 91 | offsets = offsets + [0.0] * (p - len(offsets)) 92 | data = data - N.array(offsets) 93 | 94 | if norms: 95 | if len(norms) < p: 96 | norms = norms + [1.0] * (p - len(norms)) 97 | 98 | data = data / norms 99 | 100 | if select: 101 | p, sel = _select(p, pars, select) 102 | data, pars = data[:, sel], [pars[s] for s in sel] 103 | 104 | if weights: 105 | weight = 1 106 | for i, par in enumerate(pars): 107 | if par in weights: 108 | if isinstance(weights[par], types.FunctionType): 109 | weight = weight * N.vectorize(weights[par])(data[:, i]) 110 | else: 111 | weight = weight * weights[par] 112 | else: 113 | weight = None 114 | 115 | # only need lines for multiple plots 116 | # lines = ['dotted','dashdot','dashed','solid'] 117 | 118 | if not append: 119 | P.figure(figsize=(16 * (min(p, 4) / 4.0), 3 * (int((p - 1) / 4) + 1))) 120 | 121 | for i in range(p): 122 | # figure out how big the multiplot needs to be 123 | if type(append) is int: # need this since isinstance(False,int) == True 124 | q = append 125 | elif isinstance(append, (list, tuple)): 126 | q = len(append) 127 | else: 128 | q = p 129 | 130 | # increment subplot index if we're skipping 131 | sp = i + 1 132 | for s in skip: 133 | if i >= s: 134 | sp = sp + 1 135 | 136 | # if we're given the actual parnames of an existing plot, figure out where we fall 137 | if isinstance(append, (list, tuple)): 138 | try: 139 | sp = append.index(pars[i]) + 1 140 | except ValueError: 141 | continue 142 | 143 | P.subplot(int((q - 1) / 4) + 1, min(q, 4), sp) 144 | 145 | if append: 146 | P.hold(True) 147 | 148 | if pars[i] in ranges: 149 | dx = ranges[pars[i]] 150 | P.hist( 151 | data[:, i], 152 | bins=int(bins * (N.max(data[:, i]) - N.min(data[:, i])) / (dx[1] - dx[0])), 153 | weights=weight, 154 | normed=True, 155 | histtype="step", 156 | color=color, 157 | linestyle=linestyle, 158 | linewidth=linewidth, 159 | ) 160 | P.xlim(dx) 161 | else: 162 | P.hist( 163 | data[:, i], 164 | bins=bins, 165 | weights=weight, 166 | normed=True, 167 | histtype="step", 168 | color=color, 169 | linestyle=linestyle, 170 | linewidth=linewidth, 171 | ) 172 | 173 | P.xlabel(labels[pars[i]] if pars[i] in labels else pars[i]) 174 | 175 | # P.ticklabel_format(style='sci',axis='both',scilimits=(-3,4),useoffset='True') 176 | P.locator_params(axis="both", nbins=6) 177 | P.minorticks_on() 178 | 179 | fx = P.ScalarFormatter(useOffset=True, useMathText=True) 180 | fx.set_powerlimits((-3, 4)) 181 | fx.set_scientific(True) 182 | 183 | fy = P.ScalarFormatter(useOffset=True, useMathText=True) 184 | fy.set_powerlimits((-3, 4)) 185 | fy.set_scientific(True) 186 | 187 | P.gca().xaxis.set_major_formatter(fx) 188 | P.gca().yaxis.set_major_formatter(fy) 189 | 190 | P.hold(False) 191 | 192 | if title and not append: 193 | P.suptitle(title) 194 | 195 | P.tight_layout() 196 | 197 | 198 | # to do: should fix this histogram so that the contours are correct 199 | # even for restricted ranges... 200 | def _plotonehist2( 201 | x, 202 | y, 203 | parx, 204 | pary, 205 | smooth=False, 206 | colormap=True, 207 | ranges={}, 208 | labels={}, 209 | bins=50, 210 | levels=3, 211 | weights=None, 212 | color="k", 213 | linewidth=1, 214 | ): 215 | hold = P.ishold() 216 | 217 | hrange = [ 218 | ranges[parx] if parx in ranges else [N.min(x), N.max(x)], 219 | ranges[pary] if pary in ranges else [N.min(y), N.max(y)], 220 | ] 221 | 222 | [h, xs, ys] = N.histogram2d(x, y, bins=bins, normed=True, range=hrange, weights=weights) 223 | if colormap: 224 | P.contourf(0.5 * (xs[1:] + xs[:-1]), 0.5 * (ys[1:] + ys[:-1]), h.T, cmap=P.get_cmap("YlOrBr")) 225 | P.hold(True) 226 | 227 | H, tmp1, tmp2 = N.histogram2d(x, y, bins=bins, range=hrange, weights=weights) 228 | 229 | if smooth: 230 | # only need scipy if we're smoothing 231 | import scipy.ndimage.filters as SNF 232 | 233 | H = SNF.gaussian_filter(H, sigma=1.5 if smooth is True else smooth) 234 | 235 | if weights is None: 236 | H = H / len(x) 237 | else: 238 | H = H / N.sum(H) # I think this is right... 239 | Hflat = -N.sort(-H.flatten()) # sort highest to lowest 240 | cumprob = N.cumsum(Hflat) # sum cumulative probability 241 | 242 | levels = [N.interp(level, cumprob, Hflat) for level in [0.6826, 0.9547, 0.9973][:levels]] 243 | 244 | xs = N.linspace(hrange[0][0], hrange[0][1], bins) 245 | ys = N.linspace(hrange[1][0], hrange[1][1], bins) 246 | 247 | P.contour(xs, ys, H.T, levels, colors=color, linestyles=["-", "--", "-."][: len(levels)], linewidths=linewidth) 248 | P.hold(hold) 249 | 250 | if parx in ranges: 251 | P.xlim(ranges[parx]) 252 | if pary in ranges: 253 | P.ylim(ranges[pary]) 254 | 255 | P.xlabel(labels[parx] if parx in labels else parx) 256 | P.ylabel(labels[pary] if pary in labels else pary) 257 | 258 | P.locator_params(axis="both", nbins=6) 259 | P.minorticks_on() 260 | 261 | fx = P.ScalarFormatter(useOffset=True, useMathText=True) 262 | fx.set_powerlimits((-3, 4)) 263 | fx.set_scientific(True) 264 | 265 | fy = P.ScalarFormatter(useOffset=True, useMathText=True) 266 | fy.set_powerlimits((-3, 4)) 267 | fy.set_scientific(True) 268 | 269 | P.gca().xaxis.set_major_formatter(fx) 270 | P.gca().yaxis.set_major_formatter(fy) 271 | 272 | 273 | def plothist2( 274 | data, 275 | pars=[], 276 | offsets=[], 277 | smooth=False, 278 | colormap=True, 279 | select=[], 280 | ranges={}, 281 | labels={}, 282 | bins=50, 283 | levels=3, 284 | weights=None, 285 | cuts=None, 286 | diagonal=True, 287 | title=None, 288 | color="k", 289 | linewidth=1, 290 | append=False, 291 | ): 292 | if hasattr(data, "data") and not isinstance(data, N.ndarray): 293 | # parse a multinestdata structure 294 | if not pars and hasattr(data, "parnames"): 295 | pars = data.parnames 296 | data = data.data 297 | 298 | m = data.shape[-1] 299 | 300 | if not pars: 301 | pars = map("p{0}".format, range(m)) 302 | 303 | if offsets: 304 | if len(offsets) < m: 305 | offsets = offsets + [0.0] * (m - len(offsets)) 306 | data = data - N.array(offsets) 307 | 308 | if cuts: 309 | for i, par in enumerate(pars): 310 | if par in cuts: 311 | data = data[data[:, i] > cuts[par][0], :] 312 | data = data[data[:, i] < cuts[par][1], :] 313 | 314 | if weights: 315 | weight = 1 316 | for i, par in enumerate(pars): 317 | if par in weights: 318 | if isinstance(weights[par], types.FunctionType): 319 | weight = weight * N.vectorize(weights[par])(data[:, i]) 320 | else: 321 | weight = weight * weights[par] 322 | else: 323 | weight = None 324 | 325 | if select: 326 | m, sel = _select(m, pars, select) 327 | data, pars = data[:, sel], [pars[s] for s in sel] 328 | 329 | if not append: 330 | fs = min((m if diagonal else m - 1) * 4, 16) 331 | P.figure(figsize=(fs, fs)) 332 | 333 | data = data.T 334 | 335 | if diagonal: 336 | for i in range(m): 337 | if not append: 338 | P.subplot(m, m, i * (m + 1) + 1) 339 | 340 | if pars[i] in ranges: 341 | dx = ranges[pars[i]] 342 | P.hist( 343 | data[i], 344 | bins=int(50 * (N.max(data[i]) - N.min(data[i])) / (dx[1] - dx[0])), 345 | weights=weight, 346 | normed=True, 347 | histtype="step", 348 | color="k", 349 | ) 350 | P.xlim(dx) 351 | else: 352 | P.hist(data[i], bins=50, weights=weight, normed=True, histtype="step", color="k") 353 | 354 | P.xlabel(labels[pars[i]] if pars[i] in labels else pars[i]) 355 | P.ticklabel_format(style="sci", axis="both", scilimits=(-2, 2), useoffset="True") 356 | # P.tick_params(labelsize=12) 357 | 358 | for j in range(0, i): 359 | if not append: 360 | P.subplot(m, m, i * m + j + 1) 361 | 362 | _plotonehist2( 363 | data[j], 364 | data[i], 365 | pars[j], 366 | pars[i], 367 | smooth, 368 | colormap, 369 | ranges, 370 | labels, 371 | bins, 372 | levels, 373 | weights=weight, 374 | color=color, 375 | linewidth=linewidth, 376 | ) 377 | else: 378 | for i in range(m - 1): 379 | for j in range(i + 1, m): 380 | if not append: 381 | P.subplot(m - 1, m - 1, (m - 1) * i + j) 382 | 383 | _plotonehist2( 384 | data[j], 385 | data[i], 386 | pars[j], 387 | pars[i], 388 | smooth, 389 | colormap, 390 | ranges, 391 | labels, 392 | bins, 393 | levels, 394 | weights=weight, 395 | color=color, 396 | linewidth=linewidth, 397 | ) 398 | 399 | P.tight_layout() 400 | 401 | if title and not append: 402 | P.suptitle(title) 403 | elif title: 404 | P.title(title) 405 | 406 | # if save: 407 | # P.savefig('figs/{0}-{1}-2.png'.format(psr,flms[0])) 408 | 409 | 410 | def plotgwsrc(gwb): 411 | """ 412 | Plot a GWB source population as a mollweide projection. 413 | """ 414 | theta, phi, omega, polarization = gwb.gw_dist() 415 | 416 | rho = phi - N.pi 417 | eta = 0.5 * N.pi - theta 418 | 419 | # I don't know how to get rid of the RuntimeWarning -- RvH, Oct 10, 2014: 420 | # /Users/vhaaster/env/dev/lib/python2.7/site-packages/matplotlib/projections/geo.py:485: 421 | # RuntimeWarning: invalid value encountered in arcsin theta = np.arcsin(y / np.sqrt(2)) 422 | # old_settings = N.seterr(invalid='ignore') 423 | 424 | P.title("GWB source population") 425 | _ = P.axes(projection="mollweide") 426 | 427 | foo = P.scatter(rho, eta, marker=".", s=1) 428 | # bar = N.seterr(**old_settings) 429 | 430 | return foo 431 | -------------------------------------------------------------------------------- /libstempo/spharmORFbasis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created by stevertaylor 3 | Copyright (c) 2014 Stephen R. Taylor 4 | 5 | Code contributions by Rutger van Haasteren (piccard), Justin Ellis (PAL/PAL2), and Chiara Mingarelli. 6 | 7 | """ 8 | 9 | from cmath import exp 10 | from math import acos, atan, cos, factorial, log, pi, sin, sqrt, tan 11 | 12 | import numpy as np 13 | from scipy import special as sp 14 | 15 | norm = 3.0 / (8 * pi) 16 | c00 = sqrt(4 * pi) 17 | 18 | 19 | def calczeta(phi1, phi2, theta1, theta2): 20 | """ 21 | Calculate the angular separation between position (phi1, theta1) and 22 | (phi2, theta2) 23 | 24 | """ 25 | 26 | zeta = 0.0 27 | 28 | if phi1 == phi2 and theta1 == theta2: 29 | zeta = 0.0 30 | else: 31 | argument = sin(theta1) * sin(theta2) * cos(phi1 - phi2) + cos(theta1) * cos(theta2) 32 | 33 | if argument < -1: 34 | zeta = np.pi 35 | elif argument > 1: 36 | zeta = 0.0 37 | else: 38 | zeta = acos(argument) 39 | 40 | return zeta 41 | 42 | 43 | """ 44 | 45 | Following functions taken from Gair et. al (2014), 46 | involving solutions of integrals to define the ORF for an 47 | arbitrarily anisotropic GW background. 48 | 49 | """ 50 | 51 | 52 | def Fminus00(qq, mm, ll, zeta): 53 | 54 | integrand = 0.0 55 | 56 | for ii in range(0, qq + 1): 57 | for jj in range(mm, ll + 1): 58 | 59 | integrand += ( 60 | (2.0 ** (ii - jj) * (-1.0) ** (qq - ii + jj + mm)) 61 | * ( 62 | factorial(qq) 63 | * factorial(ll + jj) 64 | * (2.0 ** (qq - ii + jj - mm + 1) - (1.0 + cos(zeta)) ** (qq - ii + jj - mm + 1)) 65 | ) 66 | / ( 67 | factorial(ii) 68 | * factorial(qq - ii) 69 | * factorial(jj) 70 | * factorial(ll - jj) 71 | * factorial(jj - mm) 72 | * (qq - ii + jj - mm + 1) 73 | ) 74 | ) 75 | 76 | return integrand 77 | 78 | 79 | def Fminus01(qq, mm, ll, zeta): 80 | 81 | integrand = 0.0 82 | 83 | for ii in range(0, qq + 1): 84 | for jj in range(mm, ll + 1): 85 | 86 | integrand += ( 87 | (2.0 ** (ii - jj) * (-1.0) ** (qq - ii + jj + mm)) 88 | * ( 89 | factorial(qq) 90 | * factorial(ll + jj) 91 | * (2.0 ** (qq - ii + jj - mm + 2) - (1.0 + cos(zeta)) ** (qq - ii + jj - mm + 2)) 92 | ) 93 | / ( 94 | factorial(ii) 95 | * factorial(qq - ii) 96 | * factorial(jj) 97 | * factorial(ll - jj) 98 | * factorial(jj - mm) 99 | * (qq - ii + jj - mm + 2) 100 | ) 101 | ) 102 | 103 | return integrand 104 | 105 | 106 | def Fplus01(qq, mm, ll, zeta): 107 | 108 | integrand = 0.0 109 | 110 | for ii in range(0, qq): 111 | for jj in range(mm, ll + 1): 112 | integrand += ( 113 | (2.0 ** (ii - jj) * (-1.0) ** (ll + qq - ii + jj)) 114 | * ( 115 | factorial(qq) 116 | * factorial(ll + jj) 117 | * (2.0 ** (qq - ii + jj - mm) - (1.0 - cos(zeta)) ** (qq - ii + jj - mm)) 118 | ) 119 | / ( 120 | factorial(ii) 121 | * factorial(qq - ii) 122 | * factorial(jj) 123 | * factorial(ll - jj) 124 | * factorial(jj - mm) 125 | * (qq - ii + jj - mm) 126 | ) 127 | ) 128 | 129 | if mm == ll: 130 | integrand += 0.0 131 | else: 132 | for jj in range(mm + 1, ll + 1): 133 | integrand += ( 134 | (2.0 ** (qq - jj) * (-1.0) ** (ll + jj)) 135 | * (factorial(ll + jj) * (2.0 ** (jj - mm) - (1.0 - cos(zeta)) ** (jj - mm))) 136 | / (factorial(jj) * factorial(ll - jj) * factorial(jj - mm) * (jj - mm)) 137 | ) 138 | 139 | integrand += ((-1.0) ** (ll + mm) * 2.0 ** (qq - mm) * factorial(ll + mm) * log(2.0 / (1.0 - cos(zeta)))) / ( 140 | 1.0 * factorial(mm) * factorial(ll - mm) 141 | ) 142 | 143 | return integrand 144 | 145 | 146 | def Fplus00(qq, mm, ll, zeta): 147 | 148 | integrand = 0.0 149 | 150 | for ii in range(0, qq + 1): 151 | for jj in range(mm, ll + 1): 152 | 153 | integrand += ( 154 | (2.0 ** (ii - jj) * (-1.0) ** (ll + qq - ii + jj)) 155 | * ( 156 | factorial(qq) 157 | * factorial(ll + jj) 158 | * (2.0 ** (qq - ii + jj - mm + 1) - (1.0 - cos(zeta)) ** (qq - ii + jj - mm + 1)) 159 | ) 160 | / ( 161 | factorial(ii) 162 | * factorial(qq - ii) 163 | * factorial(jj) 164 | * factorial(ll - jj) 165 | * factorial(jj - mm) 166 | * (qq - ii + jj - mm + 1) 167 | ) 168 | ) 169 | 170 | return integrand 171 | 172 | 173 | def arbORF(mm, ll, zeta): 174 | 175 | if mm == 0: 176 | 177 | if ll >= 0 and ll <= 2: 178 | 179 | delta = [1.0 + cos(zeta) / 3.0, -(1.0 + cos(zeta)) / 3.0, 2.0 * cos(zeta) / 15.0] 180 | 181 | if zeta == 0.0: 182 | return ( 183 | norm 184 | * 0.5 185 | * sqrt((2.0 * ll + 1.0) * pi) 186 | * (delta[ll] - (1.0 + cos(zeta)) * Fminus00(0, 0, ll, zeta)) 187 | ) 188 | else: 189 | return ( 190 | norm 191 | * 0.5 192 | * sqrt((2.0 * ll + 1.0) * pi) 193 | * ( 194 | delta[ll] 195 | - (1.0 + cos(zeta)) * Fminus00(0, 0, ll, zeta) 196 | - (1.0 - cos(zeta)) * Fplus01(1, 0, ll, zeta) 197 | ) 198 | ) 199 | 200 | else: 201 | if zeta == 0.0: 202 | return norm * 0.5 * sqrt((2.0 * ll + 1.0) * pi) * (-(1.0 + cos(zeta)) * Fminus00(0, 0, ll, zeta)) 203 | else: 204 | return ( 205 | norm 206 | * 0.5 207 | * sqrt((2.0 * ll + 1.0) * pi) 208 | * (-(1.0 + cos(zeta)) * Fminus00(0, 0, ll, zeta) - (1.0 - cos(zeta)) * Fplus01(1, 0, ll, zeta)) 209 | ) 210 | 211 | elif mm == 1: 212 | 213 | if ll == 1 or ll == 2: 214 | 215 | delta = [2.0 * sin(zeta) / 3.0, -2.0 * sin(zeta) / 5.0] 216 | 217 | return ( 218 | norm 219 | * 0.25 220 | * sqrt((2.0 * ll + 1.0) * pi) 221 | * sqrt((1.0 * factorial(ll - 1)) / (1.0 * factorial(ll + 1))) 222 | * ( 223 | delta[ll - 1] 224 | - ((1.0 + cos(zeta)) ** (3.0 / 2.0) / (1.0 - cos(zeta)) ** (1.0 / 2.0)) * Fminus00(1, 1, ll, zeta) 225 | - ((1.0 - cos(zeta)) ** (3.0 / 2.0) / (1.0 + cos(zeta)) ** (1.0 / 2.0)) * Fplus01(2, 1, ll, zeta) 226 | ) 227 | ) 228 | 229 | else: 230 | 231 | return ( 232 | norm 233 | * 0.25 234 | * sqrt((2.0 * ll + 1.0) * pi) 235 | * sqrt((1.0 * factorial(ll - 1)) / (1.0 * factorial(ll + 1))) 236 | * ( 237 | -((1.0 + cos(zeta)) ** (3.0 / 2.0) / (1.0 - cos(zeta)) ** (1.0 / 2.0)) * Fminus00(1, 1, ll, zeta) 238 | - ((1.0 - cos(zeta)) ** (3.0 / 2.0) / (1.0 + cos(zeta)) ** (1.0 / 2.0)) * Fplus01(2, 1, ll, zeta) 239 | ) 240 | ) 241 | 242 | else: 243 | 244 | return ( 245 | -norm 246 | * 0.25 247 | * sqrt((2.0 * ll + 1.0) * pi) 248 | * sqrt((1.0 * factorial(ll - mm)) / (1.0 * factorial(ll + mm))) 249 | * ( 250 | ((1.0 + cos(zeta)) ** (mm / 2.0 + 1) / (1.0 - cos(zeta)) ** (mm / 2.0)) * Fminus00(mm, mm, ll, zeta) 251 | - ((1.0 + cos(zeta)) ** (mm / 2.0) / (1.0 - cos(zeta)) ** (mm / 2.0 - 1.0)) 252 | * Fminus01(mm - 1, mm, ll, zeta) 253 | + ((1.0 - cos(zeta)) ** (mm / 2.0 + 1) / (1.0 + cos(zeta)) ** (mm / 2.0)) 254 | * Fplus01(mm + 1, mm, ll, zeta) 255 | - ((1.0 - cos(zeta)) ** (mm / 2.0) / (1.0 + cos(zeta)) ** (mm / 2.0 - 1.0)) * Fplus00(mm, mm, ll, zeta) 256 | ) 257 | ) 258 | 259 | 260 | def dlmk(l, m, k, theta1): 261 | """ 262 | returns value of d^l_mk as defined in allen, ottewill 97. 263 | Called by Dlmk 264 | 265 | """ 266 | 267 | if m >= k: 268 | 269 | factor = sqrt(factorial(l - k) * factorial(l + m) / factorial(l + k) / factorial(l - m)) 270 | part2 = (cos(theta1 / 2)) ** (2 * l + k - m) * (-sin(theta1 / 2)) ** (m - k) / factorial(m - k) 271 | part3 = sp.hyp2f1(m - l, -k - l, m - k + 1, -((tan(theta1 / 2)) ** 2)) 272 | 273 | return factor * part2 * part3 274 | 275 | else: 276 | 277 | return (-1) ** (m - k) * dlmk(l, k, m, theta1) 278 | 279 | 280 | def Dlmk(l, m, k, phi1, phi2, theta1, theta2): 281 | """ 282 | returns value of D^l_mk as defined in allen, ottewill 97. 283 | 284 | """ 285 | 286 | return ( 287 | exp(complex(0.0, -m * phi1)) * dlmk(l, m, k, theta1) * exp(complex(0.0, -k * gamma(phi1, phi2, theta1, theta2))) 288 | ) 289 | 290 | 291 | def gamma(phi1, phi2, theta1, theta2): 292 | """ 293 | calculate third rotation angle 294 | inputs are angles from 2 pulsars 295 | returns the angle. 296 | 297 | """ 298 | 299 | if phi1 == phi2 and theta1 == theta2: 300 | gamma = 0 301 | else: 302 | gamma = atan( 303 | sin(theta2) * sin(phi2 - phi1) / (cos(theta1) * sin(theta2) * cos(phi1 - phi2) - sin(theta1) * cos(theta2)) 304 | ) 305 | 306 | dummy_arg = ( 307 | cos(gamma) * cos(theta1) * sin(theta2) * cos(phi1 - phi2) 308 | + sin(gamma) * sin(theta2) * sin(phi2 - phi1) 309 | - cos(gamma) * sin(theta1) * cos(theta2) 310 | ) 311 | 312 | if dummy_arg >= 0: 313 | return gamma 314 | else: 315 | return pi + gamma 316 | 317 | 318 | def arbCompFrame_ORF(mm, ll, zeta): 319 | 320 | if zeta == 0.0: 321 | 322 | if ll > 2: 323 | return 0.0 324 | elif ll == 2: 325 | if mm == 0: 326 | # pulsar-term doubling 327 | return 2 * 0.25 * norm * (4.0 / 3) * (sqrt(pi / 5)) * cos(zeta) 328 | else: 329 | return 0.0 330 | elif ll == 1: 331 | if mm == 0: 332 | # pulsar-term doubling 333 | return -2 * 0.5 * norm * (sqrt(pi / 3.0)) * (1.0 + cos(zeta)) 334 | else: 335 | return 0.0 336 | elif ll == 0: 337 | # pulsar-term doubling 338 | return 2.0 * norm * 0.25 * sqrt(pi * 4) * (1 + (cos(zeta) / 3.0)) 339 | 340 | elif zeta == pi: 341 | 342 | if ll > 2: 343 | return 0.0 344 | elif ll == 2 and mm != 0: 345 | return 0.0 346 | elif ll == 1 and mm != 0: 347 | return 0.0 348 | else: 349 | return arbORF(mm, ll, zeta) 350 | 351 | else: 352 | 353 | return arbORF(mm, ll, zeta) 354 | 355 | 356 | def rotated_Gamma_ml(m, l, phi1, phi2, theta1, theta2, gamma_ml): 357 | """ 358 | This function takes any gamma in the computational frame and rotates it to the 359 | cosmic frame. 360 | 361 | """ 362 | 363 | rotated_gamma = 0 364 | 365 | for ii in range(2 * l + 1): 366 | rotated_gamma += Dlmk(l, m, ii - l, phi1, phi2, theta1, theta2).conjugate() * gamma_ml[ii] 367 | 368 | return rotated_gamma 369 | 370 | 371 | def real_rotated_Gammas(m, l, phi1, phi2, theta1, theta2, gamma_ml): 372 | """ 373 | This function returns the real-valued form of the Overlap Reduction Functions, 374 | see Eqs 47 in Mingarelli et al, 2013. 375 | 376 | """ 377 | 378 | if m > 0: 379 | ans = (1.0 / sqrt(2)) * ( 380 | rotated_Gamma_ml(m, l, phi1, phi2, theta1, theta2, gamma_ml) 381 | + (-1) ** m * rotated_Gamma_ml(-m, l, phi1, phi2, theta1, theta2, gamma_ml) 382 | ) 383 | return ans.real 384 | if m == 0: 385 | return rotated_Gamma_ml(0, l, phi1, phi2, theta1, theta2, gamma_ml).real 386 | if m < 0: 387 | ans = (1.0 / sqrt(2) / complex(0.0, 1)) * ( 388 | rotated_Gamma_ml(-m, l, phi1, phi2, theta1, theta2, gamma_ml) 389 | - (-1) ** m * rotated_Gamma_ml(m, l, phi1, phi2, theta1, theta2, gamma_ml) 390 | ) 391 | return ans.real 392 | 393 | 394 | def CorrBasis(psr_locs, lmax): 395 | 396 | corr = [] 397 | 398 | for ll in range(0, lmax + 1): 399 | 400 | mmodes = 2 * ll + 1 # Number of modes for this ll 401 | for mm in range(mmodes): 402 | corr.append(np.zeros((len(psr_locs), len(psr_locs)))) 403 | 404 | for aa in range(len(psr_locs)): 405 | for bb in range(aa, len(psr_locs)): 406 | 407 | plus_gamma_ml = [] # this will hold the list of gammas 408 | # evaluated at a specific value of phi{1,2}, and theta{1,2}. 409 | neg_gamma_ml = [] 410 | gamma_ml = [] 411 | 412 | # Pre-calculate all the gammas so this gets done only once. 413 | # Need all the values to execute rotation codes. 414 | for mm in range(ll + 1): 415 | zeta = calczeta(psr_locs[:, 0][aa], psr_locs[:, 0][bb], psr_locs[:, 1][aa], psr_locs[:, 1][bb]) 416 | 417 | intg_gamma = arbCompFrame_ORF(mm, ll, zeta) 418 | 419 | # just (-1)^m Gamma_ml since this is in the computational frame 420 | neg_intg_gamma = (-1) ** (mm) * intg_gamma 421 | 422 | # all of the gammas from Gamma^-m_l --> Gamma ^m_l 423 | plus_gamma_ml.append(intg_gamma) 424 | 425 | # get the neg m values via complex conjugates 426 | neg_gamma_ml.append(neg_intg_gamma) 427 | 428 | neg_gamma_ml = neg_gamma_ml[1:] # this makes sure we don't have 0 twice 429 | rev_neg_gamma_ml = neg_gamma_ml[::-1] # reverse direction of list, now runs from -m...0 430 | gamma_ml = rev_neg_gamma_ml + plus_gamma_ml 431 | 432 | mindex = len(corr) - mmodes 433 | for mm in range(mmodes): 434 | m = mm - ll 435 | 436 | corr[mindex + mm][aa, bb] = real_rotated_Gammas( 437 | m, ll, psr_locs[:, 0][aa], psr_locs[:, 0][bb], psr_locs[:, 1][aa], psr_locs[:, 1][bb], gamma_ml 438 | ) 439 | 440 | if aa != bb: 441 | corr[mindex + mm][bb, aa] = corr[mindex + mm][aa, bb] 442 | 443 | return corr 444 | -------------------------------------------------------------------------------- /libstempo/multinest.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import re 4 | from ctypes import CFUNCTYPE, POINTER, c_bool, c_double, c_int, c_void_p, cdll, create_string_buffer 5 | 6 | import numpy as N 7 | from numpy.ctypeslib import as_array 8 | 9 | # don't bother with parsing error 10 | try: 11 | lib = cdll.LoadLibrary("libnest3.so") 12 | except: 13 | lib = cdll.LoadLibrary(os.path.dirname(__file__) + "/libnest3.so") 14 | 15 | # if we want to do OS X version detection: 16 | # import platform 17 | # if platform.system() == 'Darwin' 18 | # '.'.join(platform.mac_ver().split('.')[:2]) --> 10.X 19 | 20 | # libstempo.multinest.run borrows heavily from Johannes Buchner's pymultinest; 21 | # it requires MultiNest v3.2 patched with cwrapper.f90 22 | 23 | 24 | def run( 25 | LogLikelihood, 26 | Prior, 27 | n_dims, 28 | n_params=None, 29 | n_clustering_params=None, 30 | wrapped_params=None, 31 | importance_nested_sampling=True, 32 | multimodal=True, 33 | const_efficiency_mode=False, 34 | n_live_points=400, 35 | evidence_tolerance=0.5, 36 | sampling_efficiency=0.8, 37 | n_iter_before_update=100, 38 | null_log_evidence=-1e90, 39 | max_modes=100, 40 | mode_tolerance=-1e90, 41 | outputfiles_basename="./multinest-", 42 | seed=-1, 43 | verbose=False, 44 | resume=True, 45 | context=None, 46 | write_output=True, 47 | log_zero=-1e100, 48 | max_iter=0, 49 | init_MPI=True, 50 | dump_callback=None, 51 | ): 52 | """ 53 | Runs MultiNest 54 | 55 | The most important parameters are the two log-probability functions Prior 56 | and LogLikelihood. They are called by MultiNest. 57 | 58 | Prior should transform the unit cube into the parameter cube. Here 59 | is an example for a uniform prior:: 60 | 61 | def Prior(cube, ndim, nparams): 62 | for i in range(ndim): 63 | cube[i] = cube[i] * 10 * math.pi 64 | 65 | The LogLikelihood function gets this parameter cube and should 66 | return the logarithm of the likelihood. 67 | Here is the example for the eggbox problem:: 68 | 69 | def Loglike(cube, ndim, nparams): 70 | chi = 1. 71 | 72 | for i in range(ndim): 73 | chi *= math.cos(cube[i] / 2.) 74 | return math.pow(2. + chi, 5) 75 | 76 | Some of the parameters are explained below. Otherwise consult the 77 | MultiNest documentation. 78 | 79 | @param importance_nested_sampling: 80 | If True, Multinest will use Importance Nested Sampling (INS). Read http://arxiv.org/abs/1306.2144 81 | for more details on INS. Please read the MultiNest README file before using the INS in MultiNest v3.0. 82 | 83 | @param n_params: 84 | Total no. of parameters, should be equal to ndims in most cases 85 | but if you need to store some additional 86 | parameters with the actual parameters then you need to pass 87 | them through the likelihood routine. 88 | 89 | @param sampling_efficiency: 90 | defines the sampling efficiency. 0.8 and 0.3 are recommended 91 | for parameter estimation & evidence evalutation 92 | respectively. 93 | use 'parameter' or 'model' to select the respective default 94 | values 95 | 96 | @param mode_tolerance: 97 | MultiNest can find multiple modes & also specify which samples belong to which mode. It might be 98 | desirable to have separate samples & mode statistics for modes with local log-evidence value greater than a 99 | particular value in which case Ztol should be set to that value. If there isn't any particularly interesting 100 | Ztol value, then Ztol should be set to a very large negative number (e.g. -1e90). 101 | 102 | @param evidence_tolerance: 103 | A value of 0.5 should give good enough accuracy. 104 | 105 | @param n_clustering_params: 106 | If mmodal is T, MultiNest will attempt to separate out the 107 | modes. Mode separation is done through a clustering 108 | algorithm. Mode separation can be done on all the parameters 109 | (in which case nCdims should be set to ndims) & it 110 | can also be done on a subset of parameters (in which case 111 | nCdims < ndims) which might be advantageous as 112 | clustering is less accurate as the dimensionality increases. 113 | If nCdims < ndims then mode separation is done on 114 | the first nCdims parameters. 115 | 116 | @param null_log_evidence: 117 | If mmodal is T, MultiNest can find multiple modes & also specify 118 | which samples belong to which mode. It might be 119 | desirable to have separate samples & mode statistics for modes 120 | with local log-evidence value greater than a 121 | particular value in which case nullZ should be set to that 122 | value. If there isn't any particulrly interesting 123 | nullZ value, then nullZ should be set to a very large negative 124 | number (e.g. -1.d90). 125 | 126 | @param init_MPI: 127 | initialize MPI routines?, relevant only if compiling with MPI 128 | 129 | @param log_zero: 130 | points with loglike < logZero will be ignored by MultiNest 131 | 132 | @param max_iter: 133 | maximum number of iterations. 0 is unlimited. 134 | 135 | @param write_output: 136 | write output files? This is required for analysis. 137 | 138 | @param dump_callback: 139 | a callback function for dumping the current status 140 | 141 | """ 142 | 143 | if n_params is None: 144 | n_params = n_dims 145 | if n_clustering_params is None: 146 | n_clustering_params = n_dims 147 | if wrapped_params is None: 148 | wrapped_params = [0] * n_dims 149 | 150 | WrappedType = c_int * len(wrapped_params) 151 | wraps = WrappedType(*wrapped_params) 152 | 153 | if sampling_efficiency == "parameter": 154 | sampling_efficiency = 0.8 155 | if sampling_efficiency == "model": 156 | sampling_efficiency = 0.3 157 | 158 | # MV 20130923 159 | 160 | loglike_type = CFUNCTYPE(c_double, POINTER(c_double), c_int, c_int, c_void_p) 161 | 162 | dumper_type = CFUNCTYPE( 163 | c_void_p, 164 | c_int, 165 | c_int, 166 | c_int, 167 | POINTER(c_double), 168 | POINTER(c_double), 169 | POINTER(c_double), 170 | c_double, 171 | c_double, 172 | c_double, 173 | c_void_p, 174 | ) 175 | 176 | if hasattr(LogLikelihood, "loglike") and hasattr(Prior, "remap") and hasattr(Prior, "prior"): 177 | 178 | def loglike(cube, ndim, nparams, nullcontext): 179 | # we're not using context with libstempo.like objects 180 | 181 | pprior = Prior.premap(cube) 182 | 183 | # mappers are supposed to throw a ValueError if they get out of range 184 | try: 185 | pars = Prior.remap(cube) 186 | except ValueError: 187 | return -N.inf 188 | 189 | prior = pprior * Prior.prior(pars) 190 | 191 | return -N.inf if not prior else math.log(prior) + LogLikelihood.loglike(pars) 192 | 193 | else: 194 | 195 | def loglike(cube, ndim, nparams, nullcontext): 196 | # it's actually easier to use the context, if any, at the Python level 197 | # and pass a null pointer to MultiNest... 198 | 199 | args = [cube, ndim, nparams] + ([] if context is None else context) 200 | 201 | if Prior: 202 | Prior(*args) 203 | 204 | return LogLikelihood(*args) 205 | 206 | def dumper(nSamples, nlive, nPar, physLive, posterior, paramConstr, maxLogLike, logZ, logZerr, nullcontext): 207 | 208 | if dump_callback: 209 | # It's not clear to me what the desired PyMultiNest dumper callback 210 | # syntax is... but this should pass back the right numpy arrays, 211 | # without copies. Untested! 212 | pc = as_array(paramConstr, shape=(nPar, 4)) 213 | 214 | dump_callback( 215 | nSamples, 216 | nlive, 217 | nPar, 218 | as_array(physLive, shape=(nPar + 1, nlive)).T, 219 | as_array(posterior, shape=(nPar + 2, nSamples)).T, 220 | (pc[0, :], pc[1, :], pc[2, :], pc[3, :]), # (mean,std,bestfit,map) 221 | maxLogLike, 222 | logZ, 223 | logZerr, 224 | ) 225 | 226 | # MV 20130923: currently we support only multinest 3.2 (24 parameters), 227 | # but it would not be a problem to build up the parameter list dynamically 228 | 229 | lib.run( 230 | c_bool(importance_nested_sampling), 231 | c_bool(multimodal), 232 | c_bool(const_efficiency_mode), 233 | c_int(n_live_points), 234 | c_double(evidence_tolerance), 235 | c_double(sampling_efficiency), 236 | c_int(n_dims), 237 | c_int(n_params), 238 | c_int(n_clustering_params), 239 | c_int(max_modes), 240 | c_int(n_iter_before_update), 241 | c_double(mode_tolerance), 242 | create_string_buffer(outputfiles_basename.encode()), # MV 20130923: need a regular C string 243 | c_int(seed), 244 | wraps, 245 | c_bool(verbose), 246 | c_bool(resume), 247 | c_bool(write_output), 248 | c_bool(init_MPI), 249 | c_double(log_zero), 250 | c_int(max_iter), 251 | loglike_type(loglike), 252 | dumper_type(dumper), 253 | c_void_p(0), 254 | ) 255 | 256 | 257 | class multinestdata(dict): 258 | pass 259 | 260 | 261 | class multinestpar(object): 262 | pass 263 | 264 | 265 | # where are the multinest files? 266 | def _findfiles(multinestrun, dirname, suffix="-post_equal_weights.dat"): 267 | # try chains/multinestrun-... 268 | # chains/multinestrun/multinestrun-... 269 | root = [dirname + "/", dirname + "/" + multinestrun] 270 | 271 | # and if multinestrun is something like pulsar-model, 272 | # try chains/pulsar/model/pulsar-model-... 273 | if "-" in multinestrun: 274 | tokens = multinestrun.split("-")[:-1] 275 | pulsar, model = "-".join(tokens[:-1]), tokens[-1] 276 | root.append(dirname + "/" + pulsar + "/" + model) 277 | 278 | return filter(lambda r: os.path.isfile(r + "/" + multinestrun + suffix), root) 279 | 280 | 281 | def _getcomment(ret, filename): 282 | try: 283 | ret.comment = open(filename, "r").read() 284 | except IOError: 285 | pass 286 | 287 | 288 | def _getmeta(ret, filename): 289 | try: 290 | meta = N.load(filename) 291 | except IOError: 292 | return 293 | 294 | ret.parnames = list(meta["name"]) 295 | ret.tempopars = list(meta["val"]) # somewhat legacy? 296 | ret.tempo = {} 297 | 298 | ml = N.argmax(ret.data[:, -1]) 299 | 300 | for i, par in enumerate(ret.parnames): 301 | ret[par] = multinestpar() 302 | 303 | try: 304 | ret[par].val, ret[par].err = N.mean(ret.data[:, i]) + meta["offset"][i], math.sqrt(N.var(ret.data[:, i])) 305 | ret[par].offset = meta["offset"][i] 306 | except ValueError: 307 | ret[par].val, ret[par].err = N.mean(ret.data[:, i]), math.sqrt(N.var(ret.data[:, i])) 308 | 309 | if "ml" in meta.dtype.names: 310 | ret[par].ml = meta["ml"][i] 311 | else: 312 | ret[par].ml = ret.data[ml, i] + (meta["offset"][i] if "offset" in meta.dtype.names else 0) 313 | 314 | ret.tempo[par] = multinestpar() 315 | ret.tempo[par].val, ret.tempo[par].err = meta["val"][i], meta["err"][i] 316 | 317 | 318 | def load_mcmc(mcrun, dirname="."): 319 | root = _findfiles(mcrun, dirname, "-chain.npy") 320 | 321 | ret = multinestdata() 322 | ret.dirname = root[0] 323 | 324 | alldata = N.load("{0}/{1}-chain.npy".format(root[0], mcrun)) 325 | 326 | # keep all the steps 327 | ret.data = alldata[:, :] 328 | 329 | _getmeta(ret, "{0}/{1}-meta.npy".format(root[0], mcrun)) 330 | _getcomment(ret, "{0}/{1}-comment.txt".format(root[0], mcrun)) 331 | 332 | return ret 333 | 334 | 335 | def load_emcee(emceerun, dirname=".", chains=False): 336 | root = _findfiles(emceerun, dirname, "-chain.npy") 337 | 338 | ret = multinestdata() 339 | ret.dirname = root[0] 340 | 341 | alldata = N.load("{0}/{1}-chain.npy".format(root[0], emceerun)) 342 | 343 | # keep the last iteration of the walker cloud 344 | ret.data = alldata[:, -1, :] 345 | 346 | if chains: 347 | ret.chains = alldata 348 | 349 | _getmeta(ret, "{0}/{1}-meta.npy".format(root[0], emceerun)) 350 | _getcomment(ret, "{0}/{1}-comment.txt".format(root[0], emceerun)) 351 | 352 | return ret 353 | 354 | 355 | def load(multinestrun, dirname="."): 356 | root = _findfiles(multinestrun, dirname, "-post_equal_weights.dat") 357 | 358 | if not root: 359 | # try to find a tar.gz archive 360 | import tarfile 361 | import tempfile 362 | 363 | root = _findfiles(multinestrun, dirname, ".tar.gz") 364 | tar = tarfile.open("{0}/{1}.tar.gz".format(root[0], multinestrun), mode="r|gz") 365 | root = [tempfile.mkdtemp(prefix="/tmp/")] 366 | tar.extractall(path=root[0]) 367 | 368 | ret = multinestdata() 369 | ret.dirname = root[0] 370 | 371 | # get data 372 | ret.data = N.loadtxt("{0}/{1}-post_equal_weights.dat".format(root[0], multinestrun))[:, :-1] 373 | 374 | # get evidence 375 | try: 376 | lines = open("{0}/{1}-stats.dat".format(root[0], multinestrun), "r").readlines() 377 | try: 378 | ret.ev = float(re.search(r"Global Evidence:\s*(\S*)\s*\+/-\s*(\S*)", lines[0]).group(1)) 379 | except: 380 | ret.ev = float(re.search(r"Global Log-Evidence :\s*(\S*)\s*\+/-\s*(\S*)", lines[0]).group(1)) 381 | except IOError: 382 | pass 383 | 384 | # get metadata 385 | _getmeta(ret, "{0}/{1}-meta.npy".format(root[0], multinestrun)) 386 | _getcomment(ret, "{0}/{1}-comment.txt".format(root[0], multinestrun)) 387 | 388 | if root[0][:4] == "/tmp": 389 | import shutil 390 | 391 | shutil.rmtree(root[0]) 392 | 393 | return ret 394 | 395 | 396 | def compress(rootname): 397 | import os 398 | 399 | dirname, filename = os.path.dirname(rootname), os.path.basename(rootname) 400 | 401 | if filename[-1] == "-": 402 | filename = filename[:-1] 403 | 404 | files = [ 405 | filename + "-" + ending 406 | for ending in ( 407 | ".txt", 408 | "phys_live.points", 409 | "stats.dat", 410 | "ev.dat", 411 | "post_equal_weights.dat", 412 | "summary.txt", 413 | "live.points", 414 | "post_separate.dat", 415 | "meta.npy", 416 | "resume.dat", 417 | "comment.txt", 418 | ) 419 | ] 420 | 421 | cd = os.getcwd() 422 | os.chdir(dirname) 423 | 424 | os.system("tar zcf {0}.tar.gz {1}".format(filename, " ".join(files))) 425 | 426 | files_exclude = [filename + "-" + ending for ending in ("IS.iterinfo", "IS.points", "IS.ptprob")] 427 | 428 | for f in files + files_exclude: 429 | if os.path.isfile(f): 430 | os.unlink(f) 431 | 432 | os.chdir(cd) 433 | -------------------------------------------------------------------------------- /libstempo/like.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import math 3 | import re 4 | import sys 5 | import types 6 | from functools import reduce 7 | 8 | import numpy as N 9 | import scipy.special as SS 10 | 11 | day = 24 * 3600 12 | year = 365.25 * day 13 | 14 | 15 | def dot(*args): 16 | return reduce(N.dot, args) 17 | 18 | 19 | def _setuprednoise(pulsar, components=10): 20 | t = pulsar.toas() 21 | minx, maxx = N.min(t), N.max(t) 22 | x = (t - minx) / (maxx - minx) 23 | T = (day / year) * (maxx - minx) 24 | 25 | size = 2 * components 26 | redF = N.zeros((pulsar.nobs, size), "d") 27 | redf = N.zeros(size, "d") 28 | 29 | for i in range(components): 30 | redF[:, 2 * i] = N.cos(2 * math.pi * (i + 1) * x) 31 | redF[:, 2 * i + 1] = N.sin(2 * math.pi * (i + 1) * x) 32 | 33 | redf[2 * i] = redf[2 * i + 1] = (i + 1) / T 34 | 35 | # include the normalization of the power-law prior in the Fourier matrices 36 | norm = year**2 / (12 * math.pi**2 * T) 37 | redF = math.sqrt(norm) * redF 38 | 39 | return redf, redF 40 | 41 | 42 | def _quantize(times, dt=1): 43 | isort = N.argsort(times) 44 | 45 | bucket_ref = [times[isort[0]]] 46 | bucket_ind = [[isort[0]]] 47 | 48 | for i in isort[1:]: 49 | if times[i] - bucket_ref[-1] < dt: 50 | bucket_ind[-1].append(i) 51 | else: 52 | bucket_ref.append(times[i]) 53 | bucket_ind.append([i]) 54 | 55 | t = N.array([N.mean(times[ind]) for ind in bucket_ind], "d") 56 | 57 | U = N.zeros((len(times), len(bucket_ind)), "d") 58 | for i, l in enumerate(bucket_ind): 59 | U[l, i] = 1 60 | 61 | return t, U 62 | 63 | 64 | class Mask(object): 65 | def __init__(self, psr, usedeleted): 66 | self.usedeleted = usedeleted 67 | 68 | if self.usedeleted is False: 69 | self.deleted = psr.deleted 70 | 71 | if not N.any(self.deleted): 72 | self.usedeleted = True 73 | 74 | def __call__(self, array): 75 | if self.usedeleted is True: 76 | return array 77 | else: 78 | if array.ndim == 2: 79 | return array[~self.deleted, :] 80 | else: 81 | return array[~self.deleted] 82 | 83 | 84 | def loglike( 85 | pulsar, 86 | efac=1.0, 87 | equad=None, 88 | jitter=None, 89 | Ared=None, 90 | gammared=None, 91 | marginalize=True, 92 | normalize=True, 93 | redcomponents=10, 94 | usedeleted=True, 95 | ): 96 | """Returns the Gaussian-process likelihood for 'pulsar'. 97 | 98 | The likelihood is evaluated at the current value of the pulsar parameters, 99 | as given by pulsar[parname].val. 100 | 101 | If efac, equad, and/or Ared are set, will compute the likelihood assuming 102 | the corresponding noise model. EFAC multiplies measurement noise; 103 | EQUAD adds in quadrature, and is given in us; red-noise is specified with 104 | the GW-like dimensionless amplitude Ared and exponent gamma, and is 105 | modeled with 'redcomponents' Fourier components. 106 | 107 | If marginalize=True (the default), loglike will marginalize over all the 108 | parameters in pulsar.fitpars, using an M-matrix formulation. 109 | """ 110 | 111 | mask = Mask(pulsar, usedeleted) 112 | 113 | err = 1.0e-6 * mask(pulsar.toaerrs) 114 | Cdiag = (efac * err) ** 2 115 | 116 | if equad: 117 | Cdiag = Cdiag + (1e-6 * equad) ** 2 * N.ones(len(err)) 118 | 119 | if Ared: 120 | redf, F = _setuprednoise(pulsar, redcomponents) 121 | F = mask(F) 122 | phi = Ared**2 * redf ** (-gammared) 123 | 124 | if jitter: 125 | # quantize at 1 second; U plays the role of redF 126 | t, U = _quantize(86400.0 * mask(pulsar.toas()), 1.0) 127 | phi_j = (1e-6 * jitter) ** 2 * N.ones(U.shape[1]) 128 | 129 | # stack the basis arrays if we're also doing red noise 130 | phi = N.hstack((phi, phi_j)) if Ared else phi_j 131 | F = N.hstack((F, U)) if Ared else U 132 | 133 | if Ared or jitter: 134 | # Lentati formulation for correlated noise 135 | invphi = N.diag(1 / phi) 136 | Ninv = N.diag(1 / Cdiag) 137 | NinvF = dot(Ninv, F) # could be accelerated 138 | X = invphi + dot(F.T, NinvF) # invphi + FTNinvF 139 | 140 | Cinv = Ninv - dot(NinvF, N.linalg.inv(X), NinvF.T) 141 | logCdet = N.sum(N.log(Cdiag)) + N.sum(N.log(phi)) + N.linalg.slogdet(X)[1] # check 142 | else: 143 | # noise is all diagonal 144 | Cinv = N.diag(1 / Cdiag) 145 | logCdet = N.sum(N.log(Cdiag)) 146 | 147 | if marginalize: 148 | M = mask(pulsar.designmatrix()) 149 | res = mask(N.array(pulsar.residuals(updatebats=False), "d")) 150 | 151 | CinvM = N.dot(Cinv, M) 152 | A = dot(M.T, CinvM) 153 | 154 | invA = N.linalg.inv(A) 155 | CinvMres = dot(res, CinvM) 156 | 157 | ret = -0.5 * dot(res, Cinv, res) + 0.5 * dot(CinvMres, invA, CinvMres.T) 158 | 159 | if normalize: 160 | ret = ( 161 | ret 162 | - 0.5 * logCdet 163 | - 0.5 * N.linalg.slogdet(A)[1] 164 | - 0.5 * (M.shape[0] - M.shape[1]) * math.log(2.0 * math.pi) 165 | ) 166 | else: 167 | res = mask(N.array(pulsar.residuals(), "d")) 168 | 169 | ret = -0.5 * dot(res, Cinv, res) 170 | 171 | if normalize: 172 | ret = ret - 0.5 * logCdet - 0.5 * len(res) * math.log(2.0 * math.pi) 173 | 174 | return ret 175 | 176 | 177 | standardpriors = { 178 | "ECC": (0, 1), 179 | "log10_efac": (-1, 1), 180 | "efac": (0.1, 10), 181 | "log10_equad": (-2, 2), 182 | "equad": (0.01, 100), 183 | "log10_jitter": (-2, 2), 184 | "jitter": (0.01, 100), 185 | "log10_Ared": (-16, -10), 186 | "gammared": (0, 6), 187 | } 188 | 189 | 190 | # [0,1] -> truncated positive normal 191 | def map_posnormal(x0, sigma): 192 | erfc0 = 0.5 * SS.erfc(x0 / (math.sqrt(2.0) * sigma)) 193 | 194 | def map(x): 195 | if not (0 <= x <= 1.0): 196 | raise ValueError 197 | x = erfc0 + (1.0 - erfc0) * x 198 | return x0 - math.sqrt(2) * sigma * SS.erfinv(1.0 - 2.0 * x) 199 | 200 | return map 201 | 202 | 203 | # [0,1] -> normal distance with positive cut -> PX 204 | def map_invposnormal(x0, sigma): 205 | erfc0 = 0.5 * SS.erfc(x0 / (math.sqrt(2.0) * sigma)) 206 | 207 | def map(x): 208 | if not (0 <= x <= 1.0): 209 | raise ValueError 210 | x = erfc0 + (1.0 - erfc0) * x 211 | r = 1.0 / (x0 - math.sqrt(2) * sigma * SS.erfinv(1.0 - 2.0 * x)) 212 | if r < 0: 213 | raise ValueError 214 | return r 215 | 216 | return map 217 | 218 | 219 | # correct sini sampling 220 | # full mirror mapping is y = -1.0 + 2.0*x, return math.sqrt(1.0 - y**2) 221 | def map_cosi2sini(sini_min, sini_max): 222 | y0, y1 = math.sqrt(1.0 - sini_max**2), math.sqrt(1.0 - sini_min**2) 223 | 224 | def map(x): 225 | if not (0 <= x <= 1.0): 226 | raise ValueError 227 | y = y0 + x * (y1 - y0) 228 | return math.sqrt(1.0 - y**2) 229 | 230 | return map 231 | 232 | 233 | def map_cosi2sini_mirror(): 234 | def map(x): 235 | if not (0 <= x <= 1.0): 236 | raise ValueError 237 | y = -1.0 + 2.0 * x 238 | return math.sqrt(1.0 - y**2) 239 | 240 | return map 241 | 242 | 243 | standardmaps = {"SINI": map_cosi2sini(0, 1)} 244 | 245 | 246 | class tempopar(str): 247 | def __new__(cls, par): 248 | return str.__new__(cls, par) 249 | 250 | # maps [0,1] to parameter range 251 | def map(self, x): 252 | try: 253 | y0, y1 = self.range 254 | 255 | return y0 + x * (y1 - y0) 256 | except AttributeError: 257 | raise AttributeError( 258 | "[ERROR] libstempo.like.tempopar.map: range is undefined for parameter {0}.".format(self) 259 | ) 260 | 261 | @property 262 | def range(self): 263 | return self._range 264 | 265 | @range.setter 266 | def range(self, val): 267 | self._range = val 268 | self.checkpriorvsrange() 269 | 270 | @property 271 | def prior(self): 272 | return self._prior 273 | 274 | @prior.setter 275 | def prior(self, val): 276 | self._prior = val 277 | self.checkpriorvsrange() 278 | 279 | def checkpriorvsrange(self): 280 | # compare range and prior 281 | if ( 282 | hasattr(self, "range") 283 | and hasattr(self, "prior") 284 | and isinstance(self.prior, (tuple, list)) 285 | and ( 286 | self.prior[0] > self.range[0] + getattr(self, "offset", 0) 287 | or self.prior[1] < self.range[1] + getattr(self, "offset", 0) 288 | ) 289 | ): 290 | print( 291 | "[WARNING] libstempo.like.range: prior {0} is narrower than range {1}.".format(self.prior, self.range) 292 | ) 293 | 294 | 295 | def expandranges(parlist): 296 | """Rewrite a list of parameters by expanding ranges (e.g., log10_efac{1-10}) into 297 | individual parameters.""" 298 | 299 | ret = [] 300 | 301 | for par in parlist: 302 | # match anything of the form XXX{number1-number2} 303 | m = re.match(r"(.*)\{([0-9]+)\-([0-9]+)\}", par) 304 | 305 | if m is None: 306 | ret.append(par) 307 | else: 308 | # (these are strings) 309 | root, number1, _ = m.group(1), m.group(2), m.group(3) 310 | 311 | # if number1 begins with 0s, number parameters as 00, 01, 02, ..., 312 | # otherwise go with 0, 1, 2, ... 313 | fmt = "{{0}}{{1:0{0}d}}".format(len(number1)) if number1[0] == "0" else "{0}{1:d}" 314 | 315 | ret = ret + [fmt.format(root, i) for i in range(int(m.group(2)), int(m.group(3)) + 1)] 316 | 317 | return ret 318 | 319 | 320 | # ordering of roots here is important 321 | def _findrange(parlist, roots=["JUMP", "DMXR1_", "DMXR2_", "DMX_", "efac", "log10_efac"]): 322 | """Rewrite a list of parameters name by detecting ranges (e.g., JUMP1, JUMP2, ...) 323 | and compressing them.""" 324 | 325 | rootdict = {root: [] for root in roots} 326 | 327 | res = [] 328 | for par in parlist: 329 | found = False 330 | for root in roots: 331 | if len(par) > len(root) and par[: len(root)] == root: 332 | rootdict[root].append(int(par[len(root) :])) 333 | found = True 334 | if not found: 335 | res.append(par) 336 | 337 | for root in roots: 338 | if rootdict[root]: 339 | if len(rootdict[root]) > 1: 340 | rmin, rmax = min(rootdict[root]), max(rootdict[root]) 341 | res.append( 342 | "{0}{{{1}-{2}}}{3}".format( 343 | root, rmin, rmax, "(incomplete)" if rmax - rmin != len(rootdict[root]) - 1 else "" 344 | ) 345 | ) 346 | else: 347 | res.append("{0}{1}".format(root, rootdict[root][0])) 348 | return res 349 | 350 | 351 | class Prior(dict): 352 | def __init__(self, pulsar, parameters, prefit=False, rangemultiplier=4): 353 | self.searchpars = parameters 354 | self.fitpars = pulsar.fitpars 355 | self.setpars = [par for par in pulsar.setpars if (par not in self.searchpars) and (par not in self.fitpars)] 356 | 357 | for par in parameters: 358 | self[par] = tempopar(par) 359 | 360 | if par in standardpriors: 361 | self[par].prior = standardpriors[par] 362 | 363 | if par in standardmaps: 364 | self[par].map = standardmaps[par] 365 | 366 | # if tempo2 has an error, use it to set the range 367 | if par in pulsar.setpars and pulsar[par].err != 0: 368 | if prefit: 369 | val, err = pulsar.prefit[par].val, pulsar.prefit[par].err 370 | else: 371 | val, err = pulsar[par].val, pulsar[par].err 372 | 373 | if abs(err / val) / sys.float_info.epsilon < 1000: 374 | self[par].offset = val 375 | val = 0.0 376 | 377 | self[par].range = (val - rangemultiplier * err, val + rangemultiplier * err) 378 | elif par in standardpriors: 379 | self[par].range = standardpriors[par] 380 | 381 | @property 382 | def meta(self): 383 | def partuple(par): 384 | return (getattr(self[par], "offset", 0),) 385 | 386 | return N.fromiter((partuple(par) for par in self.searchpars), dtype=[("offset", "f16")]) 387 | 388 | def report(self): 389 | print() 390 | print("==== libstempo.like.Prior report ====") 391 | print(" Search parameters: {0}".format(" ".join(_findrange(self.searchpars)))) 392 | print(" Marginalized parameters: {0}".format(" ".join(_findrange(self.fitpars)))) 393 | print(" Other set parameters: {0}".format(" ".join(_findrange(self.setpars)))) 394 | 395 | ranges = [] 396 | for par in self.searchpars: 397 | if isinstance(self[par].map, types.FunctionType): 398 | ranges.append("") 399 | elif hasattr(self[par], "range"): 400 | ranges.append(str(self[par].range)) 401 | else: 402 | ranges.append("") 403 | 404 | priors = [] 405 | for par in self.searchpars: 406 | if hasattr(self[par], "prior") and isinstance(self[par].prior, types.FunctionType): 407 | priors.append("") 408 | elif hasattr(self[par], "prior"): 409 | priors.append(str(self[par].prior)) 410 | else: 411 | priors.append("") 412 | 413 | offsets = [] 414 | for par in self.searchpars: 415 | if hasattr(self[par], "offset"): 416 | offsets.append(repr(self[par].offset)) 417 | else: 418 | offsets.append("") 419 | 420 | # may want to consider also titles here 421 | # eventually we'll make this into a separate function to handle avgs + vars, etc. 422 | lens = [max(6, max(map(len, ll))) for ll in [self.searchpars, ranges, priors, offsets]] 423 | 424 | print(" Search ranges and priors:") 425 | line = " {{0:{0}s}} | {{1:{1}s}} | {{2:{2}s}} | {{3:{3}s}}".format(*lens) 426 | print(line.format("PAR", "RANGE", "PRIOR", "OFFSET")) 427 | for p in zip(self.searchpars, ranges, priors, offsets): 428 | print(line.format(*p)) 429 | 430 | print() 431 | 432 | # as is, this is a "cube" prior suitable for multinest integration 433 | # it takes transformed parameters, so offsets don't matter 434 | def prior(self, pardict): 435 | # reconstruct a dictionary if we're given a sequence 436 | if not isinstance(pardict, dict): 437 | pardict = {par: pardict[i] for (i, par) in enumerate(self.searchpars)} 438 | 439 | prior = 1.0 440 | 441 | for par in self.searchpars: 442 | # prior is implicitly one for parameters that don't have one 443 | if not hasattr(self[par], "prior"): 444 | continue 445 | 446 | parP = self[par].prior 447 | 448 | if hasattr(parP, "__call__"): 449 | # prior is a function 450 | # currently allows only separable priors since it passes a single argument 451 | prior = prior * parP(pardict[par]) 452 | elif isinstance(parP, (tuple, list)): 453 | # prior is an interval 454 | if parP[0] <= pardict[par] <= parP[1]: 455 | # handle priors with (semi-)infinite prior intervals 456 | if hasattr(self[par], "range") and (parP[0] != -N.inf) and (parP[1] != N.inf): 457 | # compute correct Ockham penalty even if we're restricting the range 458 | prior = prior * float((self[par].range[1] - self[par].range[0]) / (parP[1] - parP[0])) 459 | # implicit else: prior = prior * 1 460 | else: 461 | return 0 462 | else: 463 | # prior is a number 464 | prior = prior * parP 465 | 466 | # shortcircuit null prior 467 | if not prior: 468 | return 0 469 | 470 | return prior 471 | 472 | # remap point in [0,1]^n cube to dictionary of search parameters, using individual map functions 473 | # (also changes xs, as required by multinest, but does not include offsets there) 474 | def remap(self, xs): 475 | pardict = {} 476 | 477 | for i, par in enumerate(self.searchpars): 478 | xs[i] = self[par].map(xs[i]) 479 | pardict[par] = xs[i] + getattr(self[par], "offset", 0) 480 | 481 | return pardict 482 | 483 | def remap_list(self, xs): 484 | return [self[par].map(xs[i]) for i, par in enumerate(self.searchpars)] 485 | 486 | def premap(self, xs): 487 | pprior = 1.0 488 | 489 | for i, par in enumerate(self.searchpars): 490 | if hasattr(self[par], "preprior"): 491 | pprior = pprior * self[par].preprior(xs[i]) 492 | 493 | if hasattr(self[par], "premap"): 494 | xs[i] = self[par].premap(xs[i]) 495 | 496 | return pprior 497 | 498 | 499 | # still incomplete, but the idea is to show the figures that differ between str1 and str2 in bold 500 | def _showdiff(str1, str2): 501 | if "(" in str1 and "(" in str2: 502 | for i in range(min(len(str1), len(str2))): 503 | if str1[i] == "(" or str2[i] == "(" or str1[i] != str2[i]: 504 | break 505 | 506 | return ("\033[1m{0}\033[0m{1}".format(str1[:i], str1[i:]), "\033[1m{0}\033[0m{1}".format(str2[:i], str2[i:])) 507 | else: 508 | return str1, str2 509 | 510 | 511 | def _formatval(val, err, showerr=True): 512 | if N.isnan(val): 513 | # no value 514 | return "nan" 515 | elif val == 0: 516 | # just zero 517 | if err == 0 or N.isnan(err): 518 | return "0" 519 | else: 520 | return "0 +/- %.1e" % err 521 | elif err == 0 or N.isnan(err): 522 | # no error 523 | return "%+.15e" % val 524 | elif abs(val) < err: 525 | # error larger than value (but value is not zero) 526 | if showerr: 527 | return "%+.1e +/- %.1e" % (val, err) 528 | else: 529 | return "%+.1e" % val 530 | 531 | # general case: set the precision of the value at the magnitude of the error 532 | prec = int(math.floor(math.log10(abs(val))) - math.floor(math.log10(abs(err))) + 1) 533 | 534 | # format the value (can't use format() for N.longdouble), then grab the mantissa and exponent 535 | str1 = "%+.*e" % (prec, val) 536 | mantissa, exponent = str1.split("e") 537 | exponent = int(exponent) 538 | 539 | # interject the error, with two digits of precision, if requested 540 | # need to do business with sign because format() does not honor '+' and zero-padding together 541 | if showerr: 542 | errstr = int(abs(err) / (10 ** (exponent - prec)) + 0.5) 543 | return "{0}({1})e{2}{3:02d}".format(mantissa, errstr, "-" if exponent < 0 else "+", abs(exponent)) 544 | else: 545 | return "{0}e{1}{2:02d}".format(mantissa, "-" if exponent < 0 else "+", abs(exponent)) 546 | 547 | 548 | # should be in libstempo.util? 549 | @contextlib.contextmanager 550 | def numpy_seterr(**kwargs): 551 | old_settings = N.seterr(**kwargs) 552 | 553 | try: 554 | yield 555 | finally: 556 | N.seterr(**old_settings) 557 | 558 | 559 | class Loglike(object): 560 | def __init__(self, pulsar, parameters, redcomponents=10): 561 | self.psr, self.searchpars = pulsar, parameters 562 | self.multiefac = sum("efac" in par for par in parameters) > 1 563 | 564 | self.pars = [] 565 | self.efac, self.equad, self.Ared, self.jitter = None, None, None, None 566 | 567 | for par in parameters: 568 | if ("efac" in par) or ("equad" in par) or ("Ared" in par) or ("jitter" in par) or (par == "gammared"): 569 | if par[:6] == "log10_": 570 | setattr(self, par[6:], lambda d, parname=par: 10.0 ** d[parname]) 571 | else: 572 | setattr(self, par, lambda d, parname=par: d[parname]) 573 | else: 574 | # these are the "real" tempo2 parameters 575 | if par not in self.psr: 576 | raise KeyError("[ERROR] libstempo.like.Loglike: parameter {0} unknown.".format(par)) 577 | elif self.psr[par].fit: 578 | raise ValueError( 579 | "[ERROR] libstempo.like.Loglike: trying to set marginalized parameter {0}.".format(par) 580 | ) 581 | else: 582 | self.pars.append(par) 583 | 584 | if self.multiefac: 585 | self.sysflags = list(set(self.psr.flagvals("sys"))) 586 | self.sysflags.sort() 587 | 588 | longest = str(len(self.sysflags) - 1) 589 | self.efacpars = ["efac{0:0{1}d}".format(i, len(longest)) for i in range(len(self.sysflags))] 590 | 591 | self.err2 = [(1.0e-6 * (self.psr.flagvals("sys") == sys)) ** 2 for sys in self.sysflags] 592 | 593 | if N.any([not hasattr(self, efacpar) for efacpar in self.efacpars]): 594 | raise KeyError( 595 | "[ERROR] libstempo.like.Loglike: when multiefac=True, you need to fit (log){0}--{1}.".format( 596 | self.efacpars[0], self.efacpars[-1] 597 | ) 598 | ) 599 | else: 600 | self.err2 = (1.0e-6 * self.psr.toaerrs) ** 2 601 | 602 | if self.equad: 603 | self.ones = N.ones(len(self.psr.toaerrs)) 604 | 605 | if self.Ared: 606 | self.redf, self.redF = _setuprednoise(self.psr, redcomponents) 607 | 608 | if self.jitter: 609 | # quantize at 1 second; U plays the role of redF 610 | t, U = _quantize(86400.0 * pulsar.toas(), 1.0) 611 | 612 | self.ones2 = N.ones(U.shape[1]) # should it be self.twos? :) 613 | # stack the basis arrays if we're also doing red noise 614 | self.redF = N.hstack((self.redF, U)) if self.Ared else U 615 | 616 | self.marginalize = len(self.psr.fitpars) > 0 617 | 618 | if not self.pars: 619 | self.M = self.psr.designmatrix() 620 | self.res = N.array(self.psr.residuals(updatebats=False), "d") 621 | 622 | def partuple(par): 623 | if par in pulsar.setpars: 624 | return (par, pulsar[par].val, pulsar[par].err, pulsar.prefit[par].val, pulsar.prefit[par].err) 625 | else: 626 | return (par, N.nan, N.nan, N.nan, N.nan) 627 | 628 | self.meta = N.fromiter( 629 | (partuple(par) for par in parameters), 630 | dtype=[("name", "a32"), ("val", "f16"), ("err", "f16"), ("pval", "f16"), ("perr", "f16")], 631 | ) 632 | 633 | def __call__(self, pardict): 634 | return self.loglike(pardict) 635 | 636 | def loglike(self, pardict): 637 | # reconstruct a dictionary if we're given a sequence 638 | if not isinstance(pardict, dict): 639 | pardict = {par: pardict[i] for (i, par) in enumerate(self.searchpars)} 640 | 641 | for par in self.pars: 642 | self.psr[par].val = pardict[par] 643 | 644 | if self.multiefac: 645 | Cdiag = sum(getattr(self, self.efacpars[i])(pardict) ** 2 * self.err2[i] for i in range(len(self.sysflags))) 646 | else: 647 | if self.efac: 648 | Cdiag = self.efac(pardict) ** 2 * self.err2 649 | else: 650 | Cdiag = self.err2 651 | 652 | if self.equad: 653 | Cdiag = Cdiag + (1e-6 * self.equad(pardict)) ** 2 * self.ones 654 | 655 | if self.Ared: 656 | invphi = self.Ared(pardict) ** -2 * self.redf ** self.gammared(pardict) 657 | 658 | if self.jitter: 659 | invphi_j = (1e-6 * self.jitter(pardict)) ** -2 * self.ones2 660 | invphi = N.hstack((invphi, invphi_j)) if self.Ared else invphi_j 661 | 662 | if self.Ared or self.jitter: 663 | Ninv = N.diag(1 / Cdiag) 664 | NinvF = dot(Ninv, self.redF) 665 | X = N.diag(invphi) + dot(self.redF.T, NinvF) 666 | 667 | Cinv = Ninv - dot(NinvF, N.linalg.inv(X), NinvF.T) 668 | logCdet = N.sum(N.log(Cdiag)) - N.sum(N.log(invphi)) + N.linalg.slogdet(X)[1] # check 669 | else: 670 | Cinv = N.diag(1 / Cdiag) 671 | logCdet = N.sum(N.log(Cdiag)) 672 | 673 | if self.marginalize: 674 | if not self.pars: 675 | # tempo2 parameters don't change, so use cached 676 | M, res = self.M, self.res 677 | else: 678 | M = self.psr.designmatrix() 679 | res = N.array(self.psr.residuals(updatebats=False), "d") 680 | 681 | CinvM = N.dot(Cinv, M) 682 | A = dot(M.T, CinvM) 683 | 684 | invA = N.linalg.inv(A) 685 | CinvMres = dot(res, CinvM) 686 | 687 | # TO DO: should check that we don't need factors of 2*pi for the F formalism 688 | loglike = ( 689 | -0.5 * dot(res, Cinv, res) 690 | + 0.5 * dot(CinvMres, invA, CinvMres.T) 691 | - 0.5 * logCdet 692 | - 0.5 * N.linalg.slogdet(A)[1] 693 | - 0.5 * (M.shape[0] - M.shape[1]) * math.log(2.0 * math.pi) 694 | ) 695 | else: 696 | res = N.array(self.psr.residuals(), "d") 697 | 698 | loglike = -0.5 * dot(res, Cinv, res) - 0.5 * logCdet - 0.5 * len(res) * math.log(2.0 * math.pi) 699 | 700 | return loglike 701 | 702 | # currently not reporting on MCMC ML 703 | # also report ML and RMS at the end of the parameters 704 | def report(self, data): 705 | tempo = [_formatval(data.tempo[par].val, data.tempo[par].err) for par in self.searchpars] 706 | mcmc = [_formatval(data[par].val, data[par].err) for par in self.searchpars] 707 | 708 | delta = [ 709 | _formatval(data[par].val - data.tempo[par].val, max(data[par].err, data.tempo[par].err), showerr=False) 710 | for par in self.searchpars 711 | ] 712 | 713 | with numpy_seterr(divide="ignore"): 714 | ratio = [("%.1e" % (data[par].err / data.tempo[par].err)) for par in self.searchpars] 715 | fdelta = [ 716 | ("%+.1e" % ((data[par].val - data.tempo[par].val) / data.tempo[par].err)) for par in self.searchpars 717 | ] 718 | 719 | print() 720 | print("==== libstempo.like.Loglike report ====") 721 | 722 | lens = [max(6, max(map(len, ll))) for ll in [self.searchpars, tempo, mcmc, delta, ratio, fdelta]] 723 | 724 | print(" Tempo2 ML values and MCMC conditional means:") 725 | line = " {{0:{0}s}} | {{1:{1}s}} | {{2:{2}s}} | {{3:{3}s}} | {{4:{4}s}} | {{5:{5}s}}".format(*lens) 726 | print(line.format("PAR", "TEMPO2", "MCMC", "DIFF", "ERAT", "BIAS")) 727 | for p in zip(self.searchpars, tempo, mcmc, delta, ratio, fdelta): 728 | print(line.format(*p)) 729 | 730 | print() 731 | -------------------------------------------------------------------------------- /libstempo/data/B1953+29_NANOGrav_dfg+12.tim: -------------------------------------------------------------------------------- 1 | FORMAT 1 2 | 53945.000014.3.000.000.tsum 1386.000 53945.1613762342105 4.972 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1386 3 | 53978.000010.3.000.000.tsum 1386.000 53978.0775363313615 3.033 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1386 4 | 54004.000011.3.000.000.tsum 1386.000 54005.0035461526934 5.427 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1386 5 | 54035.000011.3.000.000.tsum 1386.000 54035.9203489306057 3.392 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1386 6 | 54063.000013.3.000.000.tsum 1386.000 54063.8447069152083 8.851 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1386 7 | 54099.000017.3.000.000.tsum 1386.000 54099.7371560797154 7.304 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1386 8 | 54531.000015.3.000.000.tsum 1386.000 54531.5616628305857 5.247 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1386 9 | 54558.000016.3.000.000.tsum 1386.000 54558.4916578224926 2.918 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1386 10 | 54582.000017.3.000.000.tsum 1386.000 54582.4125104616307 3.885 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1386 11 | 54630.000012.3.000.000.tsum 1386.000 54630.2869535157057 3.898 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1386 12 | 54772.000006.3.000.000.tsum 1386.000 54772.9076879116161 3.499 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1386 13 | 54808.000009.3.000.000.tsum 1386.000 54808.7988084605180 3.654 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1386 14 | 54837.000006.3.000.000.tsum 1386.000 54837.7169643664772 1.927 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1386 15 | 53945.000014.3.000.000.tsum 1390.000 53945.1613762191558 6.404 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1390 16 | 53978.000010.3.000.000.tsum 1390.000 53978.0775363163953 3.716 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1390 17 | 54004.000011.3.000.000.tsum 1390.000 54005.0035461375225 3.597 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1390 18 | 54035.000011.3.000.000.tsum 1390.000 54035.9203489155432 4.411 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1390 19 | 54063.000013.3.000.000.tsum 1390.000 54063.8447069002583 10.016 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1390 20 | 54099.000017.3.000.000.tsum 1390.000 54099.7371560646491 5.594 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1390 21 | 54128.000010.3.000.000.tsum 1390.000 54128.6621502931789 7.140 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1390 22 | 54153.000014.3.000.000.tsum 1390.000 54153.5985998434435 3.796 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 963.858 -pta NANOGrav -proc dfg+12 -chanid asp_1390 23 | 54198.000031.3.000.000.tsum 1390.000 54198.4598753343887 2.664 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1390 24 | 54531.000015.3.000.000.tsum 1390.000 54531.5616628156464 5.525 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1390 25 | 54558.000016.3.000.000.tsum 1390.000 54558.4916578075006 2.437 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1390 26 | 54582.000017.3.000.000.tsum 1390.000 54582.4125104465863 4.476 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1390 27 | 54630.000012.3.000.000.tsum 1390.000 54630.2869535007736 3.484 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1390 28 | 54772.000006.3.000.000.tsum 1390.000 54772.9076878965217 2.264 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1390 29 | 54808.000009.3.000.000.tsum 1390.000 54808.7988084454346 5.423 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1390 30 | 54837.000006.3.000.000.tsum 1390.000 54837.7169643514874 2.834 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1390 31 | 53945.000014.3.000.000.tsum 1394.000 53945.1613762043076 5.422 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1394 32 | 53978.000010.3.000.000.tsum 1394.000 53978.0775363014147 3.023 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1394 33 | 54004.000011.3.000.000.tsum 1394.000 54005.0035461227559 7.322 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1394 34 | 54035.000011.3.000.000.tsum 1394.000 54035.9203489007746 3.589 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1394 35 | 54063.000013.3.000.000.tsum 1394.000 54063.8447068853018 6.942 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1394 36 | 54099.000017.3.000.000.tsum 1394.000 54099.7371560497846 6.468 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1394 37 | 54128.000010.3.000.000.tsum 1394.000 54128.6621502781574 9.419 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1394 38 | 54153.000014.3.000.000.tsum 1394.000 54153.5985998286918 2.626 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 963.858 -pta NANOGrav -proc dfg+12 -chanid asp_1394 39 | 54198.000031.3.000.000.tsum 1394.000 54198.4598753194933 3.205 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1394 40 | 54531.000015.3.000.000.tsum 1394.000 54531.5616628006527 6.841 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1394 41 | 54558.000016.3.000.000.tsum 1394.000 54558.4916577926573 2.720 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1394 42 | 54582.000017.3.000.000.tsum 1394.000 54582.4125104317603 4.143 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1394 43 | 54630.000012.3.000.000.tsum 1394.000 54630.2869534858531 4.014 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1394 44 | 54772.000006.3.000.000.tsum 1394.000 54772.9076878816798 2.772 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1394 45 | 54808.000009.3.000.000.tsum 1394.000 54808.7988084305719 6.869 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1394 46 | 54837.000006.3.000.000.tsum 1394.000 54837.7169643365889 2.247 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1394 47 | 54998.000024.3.000.000.tsum 1394.000 54998.2973995678748 9.295 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1394 48 | 55042.000018.3.000.000.tsum 1394.000 55042.1774805798880 6.608 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1394 49 | 55088.000005.3.000.000.tsum 1394.000 55088.0501500010815 3.610 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1394 50 | 55088.000007.3.000.000.tsum 1394.000 55088.0608528016932 7.689 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1394 51 | 55105.000020.3.000.000.tsum 1394.000 55105.9892356128293 4.521 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1394 52 | 53945.000014.3.000.000.tsum 1398.000 53945.1613762606510 5.111 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1398 53 | 53978.000010.3.000.000.tsum 1398.000 53978.0775363576957 3.508 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1398 54 | 54004.000011.3.000.000.tsum 1398.000 54005.0035461789956 8.814 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1398 55 | 54035.000011.3.000.000.tsum 1398.000 54035.9203489568781 5.954 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1398 56 | 54063.000013.3.000.000.tsum 1398.000 54063.8447069415658 8.219 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1398 57 | 54099.000017.3.000.000.tsum 1398.000 54099.7371561061478 7.183 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1398 58 | 54128.000010.3.000.000.tsum 1398.000 54128.6621502634534 10.987 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1398 59 | 54153.000014.3.000.000.tsum 1398.000 54153.5985998139982 3.681 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 963.858 -pta NANOGrav -proc dfg+12 -chanid asp_1398 60 | 54198.000031.3.000.000.tsum 1398.000 54198.4598753757051 3.399 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1398 61 | 54531.000015.3.000.000.tsum 1398.000 54531.5616628569413 4.386 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1398 62 | 54558.000016.3.000.000.tsum 1398.000 54558.4916578488786 2.814 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1398 63 | 54582.000017.3.000.000.tsum 1398.000 54582.4125104879206 3.849 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1398 64 | 54630.000012.3.000.000.tsum 1398.000 54630.2869535420228 4.158 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1398 65 | 54772.000006.3.000.000.tsum 1398.000 54772.9076879379015 2.915 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1398 66 | 54808.000009.3.000.000.tsum 1398.000 54808.7988084866602 8.763 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1398 67 | 54837.000006.3.000.000.tsum 1398.000 54837.7169643928647 2.568 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1398 68 | 54882.000011.3.000.000.tsum 1398.000 54882.6367963618330 4.535 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1398 69 | 54967.000011.3.000.000.tsum 1398.000 54967.3838623890254 5.355 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1398 70 | 54998.000024.3.000.000.tsum 1398.000 54998.2973995530746 11.555 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1398 71 | 55042.000018.3.000.000.tsum 1398.000 55042.1774805649974 10.690 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1398 72 | 55088.000005.3.000.000.tsum 1398.000 55088.0501499863049 4.031 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1398 73 | 55088.000007.3.000.000.tsum 1398.000 55088.0608527871860 10.233 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1398 74 | 55105.000020.3.000.000.tsum 1398.000 55105.9892355981713 5.344 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1398 75 | 53945.000014.3.000.000.tsum 1402.000 53945.1613762459027 4.853 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1402 76 | 53978.000010.3.000.000.tsum 1402.000 53978.0775363431150 3.825 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1402 77 | 54004.000011.3.000.000.tsum 1402.000 54005.0035461643130 6.228 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1402 78 | 54035.000011.3.000.000.tsum 1402.000 54035.9203489422734 4.422 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1402 79 | 54063.000013.3.000.000.tsum 1402.000 54063.8447069269237 7.777 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1402 80 | 54099.000017.3.000.000.tsum 1402.000 54099.7371560913449 7.603 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1402 81 | 54128.000010.3.000.000.tsum 1402.000 54128.6621503197834 5.304 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1402 82 | 54153.000014.3.000.000.tsum 1402.000 54153.5985998702461 2.244 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 963.858 -pta NANOGrav -proc dfg+12 -chanid asp_1402 83 | 54198.000031.3.000.000.tsum 1402.000 54198.4598753610969 2.158 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1402 84 | 54531.000015.3.000.000.tsum 1402.000 54531.5616628423577 3.629 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1402 85 | 54558.000016.3.000.000.tsum 1402.000 54558.4916578341844 3.623 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1402 86 | 54582.000017.3.000.000.tsum 1402.000 54582.4125104732864 3.347 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1402 87 | 54630.000012.3.000.000.tsum 1402.000 54630.2869535273764 4.465 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1402 88 | 54772.000006.3.000.000.tsum 1402.000 54772.9076879232424 2.539 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1402 89 | 54808.000009.3.000.000.tsum 1402.000 54808.7988084721814 5.330 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1402 90 | 54837.000006.3.000.000.tsum 1402.000 54837.7169643781799 1.858 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1402 91 | 54882.000011.3.000.000.tsum 1402.000 54882.6367963472754 7.424 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1402 92 | 54967.000011.3.000.000.tsum 1402.000 54967.3838623743678 4.517 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1402 93 | 54998.000024.3.000.000.tsum 1402.000 54998.2973995383851 9.126 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1402 94 | 55042.000018.3.000.000.tsum 1402.000 55042.1774805505394 8.231 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1402 95 | 55088.000005.3.000.000.tsum 1402.000 55088.0501499717076 2.573 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1402 96 | 55088.000007.3.000.000.tsum 1402.000 55088.0608527723507 21.280 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1402 97 | 55105.000020.3.000.000.tsum 1402.000 55105.9892355834311 4.985 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1402 98 | 53945.000014.3.000.000.tsum 1406.000 53945.1613762313548 6.668 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1406 99 | 53978.000010.3.000.000.tsum 1406.000 53978.0775363284935 2.854 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1406 100 | 54004.000011.3.000.000.tsum 1406.000 54005.0035461497372 5.548 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1406 101 | 54035.000011.3.000.000.tsum 1406.000 54035.9203489279041 5.513 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1406 102 | 54063.000013.3.000.000.tsum 1406.000 54063.8447069123223 8.162 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1406 103 | 54099.000017.3.000.000.tsum 1406.000 54099.7371560769263 7.066 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1406 104 | 54128.000010.3.000.000.tsum 1406.000 54128.6621503053889 7.233 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1406 105 | 54153.000014.3.000.000.tsum 1406.000 54153.5985998557630 2.609 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 963.858 -pta NANOGrav -proc dfg+12 -chanid asp_1406 106 | 54198.000031.3.000.000.tsum 1406.000 54198.4598753466472 3.109 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1406 107 | 54531.000015.3.000.000.tsum 1406.000 54531.5616628277740 6.438 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1406 108 | 54558.000016.3.000.000.tsum 1406.000 54558.4916578197241 3.156 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1406 109 | 54582.000017.3.000.000.tsum 1406.000 54582.4125104587703 3.298 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1406 110 | 54630.000012.3.000.000.tsum 1406.000 54630.2869535129791 3.521 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1406 111 | 54772.000006.3.000.000.tsum 1406.000 54772.9076879087341 2.683 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1406 112 | 54808.000009.3.000.000.tsum 1406.000 54808.7988084576947 4.934 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1406 113 | 54837.000006.3.000.000.tsum 1406.000 54837.7169643636995 1.552 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1406 114 | 54882.000011.3.000.000.tsum 1406.000 54882.6367963326331 6.003 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1406 115 | 54967.000011.3.000.000.tsum 1406.000 54967.3838623598456 5.229 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1406 116 | 54998.000024.3.000.000.tsum 1406.000 54998.2973995240431 6.973 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1406 117 | 55042.000018.3.000.000.tsum 1406.000 55042.1774805360301 6.988 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1406 118 | 55088.000005.3.000.000.tsum 1406.000 55088.0501499572484 3.213 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1406 119 | 55088.000007.3.000.000.tsum 1406.000 55088.0608527574713 21.020 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1406 120 | 55105.000020.3.000.000.tsum 1406.000 55105.9892355689140 3.887 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1406 121 | 53945.000014.3.000.000.tsum 1410.000 53945.1613762169079 5.335 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1410 122 | 53978.000010.3.000.000.tsum 1410.000 53978.0775363142129 3.744 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1410 123 | 54004.000011.3.000.000.tsum 1410.000 54005.0035461354850 4.219 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1410 124 | 54035.000011.3.000.000.tsum 1410.000 54035.9203489135190 5.427 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1410 125 | 54063.000013.3.000.000.tsum 1410.000 54063.8447068979026 11.664 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1410 126 | 54099.000017.3.000.000.tsum 1410.000 54099.7371560623436 7.417 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1410 127 | 54128.000010.3.000.000.tsum 1410.000 54128.6621502910143 7.195 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1410 128 | 54153.000014.3.000.000.tsum 1410.000 54153.5985998413443 2.096 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 963.858 -pta NANOGrav -proc dfg+12 -chanid asp_1410 129 | 54198.000031.3.000.000.tsum 1410.000 54198.4598753322930 2.519 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1410 130 | 54531.000015.3.000.000.tsum 1410.000 54531.5616628134226 5.174 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1410 131 | 54558.000016.3.000.000.tsum 1410.000 54558.4916578053556 2.257 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1410 132 | 54582.000017.3.000.000.tsum 1410.000 54582.4125104443746 4.001 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1410 133 | 54630.000012.3.000.000.tsum 1410.000 54630.2869534985233 5.281 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1410 134 | 54772.000006.3.000.000.tsum 1410.000 54772.9076878943975 2.177 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1410 135 | 54808.000009.3.000.000.tsum 1410.000 54808.7988084432983 4.748 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1410 136 | 54837.000006.3.000.000.tsum 1410.000 54837.7169643492750 1.461 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1410 137 | 54882.000011.3.000.000.tsum 1410.000 54882.6367963893658 4.676 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1410 138 | 54967.000011.3.000.000.tsum 1410.000 54967.3838624164023 4.973 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1410 139 | 54998.000024.3.000.000.tsum 1410.000 54998.2973995806269 6.216 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1410 140 | 55042.000018.3.000.000.tsum 1410.000 55042.1774805925781 5.959 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1410 141 | 55088.000005.3.000.000.tsum 1410.000 55088.0501500137834 3.641 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1410 142 | 55088.000007.3.000.000.tsum 1410.000 55088.0608528144289 11.667 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1410 143 | 55105.000020.3.000.000.tsum 1410.000 55105.9892356255110 3.422 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1410 144 | 53945.000014.3.000.000.tsum 1414.000 53945.1613762028241 4.452 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1414 145 | 53978.000010.3.000.000.tsum 1414.000 53978.0775362999340 3.071 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1414 146 | 54004.000011.3.000.000.tsum 1414.000 54005.0035461212011 5.137 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1144.581 -pta NANOGrav -proc dfg+12 -chanid asp_1414 147 | 54035.000011.3.000.000.tsum 1414.000 54035.9203488991858 4.803 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1024.099 -pta NANOGrav -proc dfg+12 -chanid asp_1414 148 | 54063.000013.3.000.000.tsum 1414.000 54063.8447068839132 9.413 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1414 149 | 54099.000017.3.000.000.tsum 1414.000 54099.7371560482437 3.579 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.411 -pta NANOGrav -proc dfg+12 -chanid asp_1414 150 | 54128.000010.3.000.000.tsum 1414.000 54128.6621502766866 9.162 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1414 151 | 54153.000014.3.000.000.tsum 1414.000 54153.5985998270937 3.088 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 963.858 -pta NANOGrav -proc dfg+12 -chanid asp_1414 152 | 54198.000031.3.000.000.tsum 1414.000 54198.4598753179780 3.351 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1414 153 | 54531.000015.3.000.000.tsum 1414.000 54531.5616627991984 7.104 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.206 -pta NANOGrav -proc dfg+12 -chanid asp_1414 154 | 54558.000016.3.000.000.tsum 1414.000 54558.4916577910585 2.961 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1414 155 | 54582.000017.3.000.000.tsum 1414.000 54582.4125104301855 4.075 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.617 -pta NANOGrav -proc dfg+12 -chanid asp_1414 156 | 54630.000012.3.000.000.tsum 1414.000 54630.2869534840576 5.045 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 843.375 -pta NANOGrav -proc dfg+12 -chanid asp_1414 157 | 54772.000006.3.000.000.tsum 1414.000 54772.9076878801090 2.189 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1566.269 -pta NANOGrav -proc dfg+12 -chanid asp_1414 158 | 54808.000009.3.000.000.tsum 1414.000 54808.7988084291119 4.333 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 481.929 -pta NANOGrav -proc dfg+12 -chanid asp_1414 159 | 54837.000006.3.000.000.tsum 1414.000 54837.7169643350062 2.305 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1807.233 -pta NANOGrav -proc dfg+12 -chanid asp_1414 160 | 54882.000011.3.000.000.tsum 1414.000 54882.6367963750036 4.505 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1414 161 | 54967.000011.3.000.000.tsum 1414.000 54967.3838624021878 4.895 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1414 162 | 54998.000024.3.000.000.tsum 1414.000 54998.2973995663658 9.925 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1414 163 | 55042.000018.3.000.000.tsum 1414.000 55042.1774805783457 3.121 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1414 164 | 55088.000005.3.000.000.tsum 1414.000 55088.0501499995493 3.818 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1414 165 | 55088.000007.3.000.000.tsum 1414.000 55088.0608528000769 6.052 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1414 166 | 55105.000020.3.000.000.tsum 1414.000 55105.9892356111874 4.706 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1414 167 | 54882.000011.3.000.000.tsum 1418.000 54882.6367963609314 2.944 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1418 168 | 54967.000011.3.000.000.tsum 1418.000 54967.3838623879916 4.997 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1418 169 | 54998.000024.3.000.000.tsum 1418.000 54998.2973995521702 9.831 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1418 170 | 55042.000018.3.000.000.tsum 1418.000 55042.1774805640493 8.442 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1418 171 | 55088.000005.3.000.000.tsum 1418.000 55088.0501499854063 3.598 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1418 172 | 55088.000007.3.000.000.tsum 1418.000 55088.0608527859318 4.396 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1418 173 | 55105.000020.3.000.000.tsum 1418.000 55105.9892355970995 7.873 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1418 174 | 54882.000011.3.000.000.tsum 1422.000 54882.6367963468277 6.993 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1422 175 | 54967.000011.3.000.000.tsum 1422.000 54967.3838623740909 6.048 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1422 176 | 54998.000024.3.000.000.tsum 1422.000 54998.2973995380803 10.111 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1422 177 | 55042.000018.3.000.000.tsum 1422.000 55042.1774805501816 8.606 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1422 178 | 55088.000005.3.000.000.tsum 1422.000 55088.0501499714742 7.952 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1422 179 | 55088.000007.3.000.000.tsum 1422.000 55088.0608527721489 15.268 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1422 180 | 55105.000020.3.000.000.tsum 1422.000 55105.9892355828052 11.009 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1422 181 | 54882.000011.3.000.000.tsum 1426.000 54882.6367963329403 2.711 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1506.212 -pta NANOGrav -proc dfg+12 -chanid asp_1426 182 | 54967.000011.3.000.000.tsum 1426.000 54967.3838623601102 4.423 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 1265.218 -pta NANOGrav -proc dfg+12 -chanid asp_1426 183 | 54998.000024.3.000.000.tsum 1426.000 54998.2973995243105 9.122 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1426 184 | 55042.000018.3.000.000.tsum 1426.000 55042.1774805363545 12.911 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1426 185 | 55088.000005.3.000.000.tsum 1426.000 55088.0501499575366 4.751 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1426 186 | 55088.000007.3.000.000.tsum 1426.000 55088.0608527579932 10.482 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1426 187 | 55105.000020.3.000.000.tsum 1426.000 55105.9892355691364 6.077 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1426 188 | 54998.000024.3.000.000.tsum 1430.000 54998.2973995816131 8.549 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1430 189 | 55042.000018.3.000.000.tsum 1430.000 55042.1774805934081 8.559 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 602.485 -pta NANOGrav -proc dfg+12 -chanid asp_1430 190 | 55088.000005.3.000.000.tsum 1430.000 55088.0501500146714 6.019 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1430 191 | 55088.000007.3.000.000.tsum 1430.000 55088.0608528150693 14.606 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 301.242 -pta NANOGrav -proc dfg+12 -chanid asp_1430 192 | 55105.000020.3.000.000.tsum 1430.000 55105.9892356263974 3.589 ao -fe L_wide -be ASP -B L -bw 4.0 -tobs 903.727 -pta NANOGrav -proc dfg+12 -chanid asp_1430 193 | 54998.000022.3.000.000.tsum 2318.000 54998.2842570582037 21.960 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2318 194 | 54998.000022.3.000.000.tsum 2322.000 54998.2842570544864 33.298 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2322 195 | 54998.000022.3.000.000.tsum 2326.000 54998.2842570515550 31.324 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2326 196 | 54998.000022.3.000.000.tsum 2330.000 54998.2842571191645 23.904 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2330 197 | 54998.000022.3.000.000.tsum 2334.000 54998.2842571158033 29.130 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2334 198 | 54998.000022.3.000.000.tsum 2338.000 54998.2842571126472 37.373 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2338 199 | 54998.000022.3.000.000.tsum 2342.000 54998.2842571099968 26.398 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2342 200 | 54998.000022.3.000.000.tsum 2346.000 54998.2842571067803 26.047 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2346 201 | 54998.000022.3.000.000.tsum 2350.000 54998.2842571036292 14.111 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2350 202 | 54998.000022.3.000.000.tsum 2354.000 54998.2842571004655 13.938 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2354 203 | 54998.000022.3.000.000.tsum 2358.000 54998.2842570973672 11.983 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2358 204 | 55105.000022.3.000.000.tsum 2358.000 55106.0023983678384 29.621 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 602.287 -pta NANOGrav -proc dfg+12 -chanid asp_2358 205 | 54998.000022.3.000.000.tsum 2362.000 54998.2842570946483 34.422 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2362 206 | 54998.000022.3.000.000.tsum 2366.000 54998.2842570915381 22.530 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2366 207 | 55105.000022.3.000.000.tsum 2370.000 55106.0023983586082 15.244 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 602.287 -pta NANOGrav -proc dfg+12 -chanid asp_2370 208 | 54998.000022.3.000.000.tsum 2374.000 54998.2842570849094 24.103 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2374 209 | 54998.000022.3.000.000.tsum 2378.000 54998.2842570817872 22.522 ao -fe S_low -be ASP -B S -bw 4.0 -tobs 903.431 -pta NANOGrav -proc dfg+12 -chanid asp_2378 210 | -------------------------------------------------------------------------------- /libstempo/toasim.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | 4 | import numpy as N 5 | import scipy.interpolate as interp 6 | 7 | from . import eccUtils as eu 8 | from . import libstempo 9 | from . import spharmORFbasis as anis 10 | 11 | try: 12 | import ephem 13 | except: 14 | print("Warning: cannot find the ephem package, needed for createGWB.") 15 | 16 | from libstempo.libstempo import GWB 17 | 18 | # get interpolant for eccentric binaries 19 | ecc_interp = eu.make_ecc_interpolant() 20 | 21 | day = 24 * 3600 22 | year = 365.25 * day 23 | DMk = 4.15e3 # Units MHz^2 cm^3 pc sec 24 | 25 | 26 | def add_gwb(psr, dist=1, ngw=1000, seed=None, flow=1e-8, fhigh=1e-5, gwAmp=1e-20, alpha=-0.66, logspacing=True): 27 | """Add a stochastic background from inspiraling binaries, using the tempo2 28 | code that underlies the GWbkgrd plugin. 29 | 30 | Here 'dist' is the pulsar distance [in kpc]; 'ngw' is the number of binaries, 31 | 'seed' (a negative integer) reseeds the GWbkgrd pseudorandom-number-generator, 32 | 'flow' and 'fhigh' [Hz] determine the background band, 'gwAmp' and 'alpha' 33 | determine its amplitude and exponent, and setting 'logspacing' to False 34 | will use linear spacing for the individual sources. 35 | 36 | It is also possible to create a background object with 37 | 38 | gwb = GWB(ngw,seed,flow,fhigh,gwAmp,alpha,logspacing) 39 | 40 | then call the method gwb.add_gwb(pulsar[i],dist) repeatedly to get a 41 | consistent background for multiple pulsars. 42 | 43 | Returns the GWB object 44 | """ 45 | 46 | gwb = GWB(ngw, seed, flow, fhigh, gwAmp, alpha, logspacing) 47 | gwb.add_gwb(psr, dist) 48 | 49 | return gwb 50 | 51 | 52 | def add_dipole_gwb( 53 | psr, 54 | dist=1, 55 | ngw=1000, 56 | seed=None, 57 | flow=1e-8, 58 | fhigh=1e-5, 59 | gwAmp=1e-20, 60 | alpha=-0.66, 61 | logspacing=True, 62 | dipoleamps=None, 63 | dipoledir=None, 64 | dipolemag=None, 65 | ): 66 | """Add a stochastic background from inspiraling binaries distributed 67 | according to a pure dipole distribution, using the tempo2 68 | code that underlies the GWdipolebkgrd plugin. 69 | 70 | The basic use is identical to that of 'add_gwb': 71 | Here 'dist' is the pulsar distance [in kpc]; 'ngw' is the number of binaries, 72 | 'seed' (a negative integer) reseeds the GWbkgrd pseudorandom-number-generator, 73 | 'flow' and 'fhigh' [Hz] determine the background band, 'gwAmp' and 'alpha' 74 | determine its amplitude and exponent, and setting 'logspacing' to False 75 | will use linear spacing for the individual sources. 76 | 77 | Additionally, the dipole component can be specified by using one of two 78 | methods: 79 | 1) Specify the dipole direction as three dipole amplitudes, in the vector 80 | dipoleamps 81 | 2) Specify the direction of the dipole as a magnitude dipolemag, and a vector 82 | dipoledir=[dipolephi, dipoletheta] 83 | 84 | It is also possible to create a background object with 85 | 86 | gwb = GWB(ngw,seed,flow,fhigh,gwAmp,alpha,logspacing) 87 | 88 | then call the method gwb.add_gwb(pulsar[i],dist) repeatedly to get a 89 | consistent background for multiple pulsars. 90 | 91 | Returns the GWB object 92 | """ 93 | 94 | gwb = GWB(ngw, seed, flow, fhigh, gwAmp, alpha, logspacing, dipoleamps, dipoledir, dipolemag) 95 | gwb.add_gwb(psr, dist) 96 | 97 | return gwb 98 | 99 | 100 | def _geti(x, i): 101 | return x[i] if isinstance(x, (tuple, list, N.ndarray)) else x 102 | 103 | 104 | def fakepulsar(parfile, obstimes, toaerr, freq=1440.0, observatory="AXIS", flags="", iters=3): 105 | """Returns a libstempo tempopulsar object corresponding to a noiseless set 106 | of observations for the pulsar specified in 'parfile', with observations 107 | happening at times (MJD) given in the array (or list) 'obstimes', with 108 | measurement errors given by toaerr (us). 109 | 110 | A new timfile can then be saved with pulsar.savetim(). Re the other parameters: 111 | - 'toaerr' needs to be either a common error, or a list of errors 112 | of the same length of 'obstimes'; 113 | - 'freq' can be either a common observation frequency in MHz, or a list; 114 | it defaults to 1440; 115 | - 'observatory' can be either a common observatory name, or a list; 116 | it defaults to the IPTA MDC 'AXIS'; 117 | - 'flags' can be a string (such as '-sys EFF.EBPP.1360') or a list of strings; 118 | it defaults to an empty string; 119 | - 'iters' is the number of iterative removals of computed residuals from TOAs 120 | (which is how the fake pulsar is made...)""" 121 | 122 | import tempfile 123 | 124 | outfile = tempfile.NamedTemporaryFile(delete=False) 125 | 126 | outfile.write(b"FORMAT 1\n") 127 | outfile.write(b"MODE 1\n") 128 | 129 | obsname = "fake_" + os.path.basename(parfile) 130 | if obsname[-4:] == ".par": 131 | obsname = obsname[:-4] 132 | 133 | for i, t in enumerate(obstimes): 134 | outfile.write( 135 | "{0} {1} {2} {3} {4} {5}\n".format( 136 | obsname, _geti(freq, i), t, _geti(toaerr, i), _geti(observatory, i), _geti(flags, i) 137 | ).encode("ascii") 138 | ) 139 | 140 | timfile = outfile.name 141 | outfile.close() 142 | 143 | pulsar = libstempo.tempopulsar(parfile, timfile, dofit=False) 144 | 145 | for i in range(iters): 146 | pulsar.stoas[:] -= pulsar.residuals() / 86400.0 147 | pulsar.formbats() 148 | 149 | os.remove(timfile) 150 | 151 | return pulsar 152 | 153 | 154 | def make_ideal(psr): 155 | """Adjust the TOAs so that the residuals to zero, then refit.""" 156 | 157 | psr.stoas[:] -= psr.residuals() / 86400.0 158 | psr.fit() 159 | 160 | 161 | def add_efac(psr, efac=1.0, flagid=None, flags=None, seed=None): 162 | """Add nominal TOA errors, multiplied by `efac` factor. 163 | Optionally take a pseudorandom-number-generator seed.""" 164 | 165 | if seed is not None: 166 | N.random.seed(seed) 167 | 168 | # default efacvec 169 | efacvec = N.ones(psr.nobs) 170 | 171 | # check that efac is scalar if flags is None 172 | if flags is None: 173 | if not N.isscalar(efac): 174 | raise ValueError("ERROR: If flags is None, efac must be a scalar") 175 | else: 176 | efacvec = N.ones(psr.nobs) * efac 177 | 178 | if flags is not None and flagid is not None and not N.isscalar(efac): 179 | if len(efac) == len(flags): 180 | for ct, flag in enumerate(flags): 181 | ind = flag == N.array(psr.flagvals(flagid)) 182 | efacvec[ind] = efac[ct] 183 | 184 | psr.stoas[:] += efacvec * psr.toaerrs * (1e-6 / day) * N.random.randn(psr.nobs) 185 | 186 | 187 | def add_equad(psr, equad, flagid=None, flags=None, seed=None): 188 | """Add quadrature noise of rms `equad` [s]. 189 | Optionally take a pseudorandom-number-generator seed.""" 190 | 191 | if seed is not None: 192 | N.random.seed(seed) 193 | 194 | # default equadvec 195 | equadvec = N.zeros(psr.nobs) 196 | 197 | # check that equad is scalar if flags is None 198 | if flags is None: 199 | if not N.isscalar(equad): 200 | raise ValueError("ERROR: If flags is None, equad must be a scalar") 201 | else: 202 | equadvec = N.ones(psr.nobs) * equad 203 | 204 | if flags is not None and flagid is not None and not N.isscalar(equad): 205 | if len(equad) == len(flags): 206 | for ct, flag in enumerate(flags): 207 | ind = flag == N.array(psr.flagvals(flagid)) 208 | equadvec[ind] = equad[ct] 209 | 210 | psr.stoas[:] += (equadvec / day) * N.random.randn(psr.nobs) 211 | 212 | 213 | def quantize(times, dt=1): 214 | bins = N.arange(N.min(times), N.max(times) + dt, dt) 215 | indices = N.digitize(times, bins) # indices are labeled by "right edge" 216 | counts = N.bincount(indices, minlength=len(bins) + 1) 217 | 218 | bign, smalln = len(times), N.sum(counts > 0) 219 | 220 | t = N.zeros(smalln, "d") 221 | U = N.zeros((bign, smalln), "d") 222 | 223 | j = 0 224 | for i, c in enumerate(counts): 225 | if c > 0: 226 | U[indices == i, j] = 1 227 | t[j] = N.mean(times[indices == i]) 228 | j = j + 1 229 | 230 | return t, U 231 | 232 | 233 | def quantize_fast(times, flags=None, dt=1.0): 234 | isort = N.argsort(times) 235 | 236 | bucket_ref = [times[isort[0]]] 237 | bucket_ind = [[isort[0]]] 238 | 239 | for i in isort[1:]: 240 | if times[i] - bucket_ref[-1] < dt: 241 | bucket_ind[-1].append(i) 242 | else: 243 | bucket_ref.append(times[i]) 244 | bucket_ind.append([i]) 245 | 246 | avetoas = N.array([N.mean(times[ind]) for ind in bucket_ind], "d") 247 | if flags is not None: 248 | aveflags = N.array([flags[ind[0]] for ind in bucket_ind]) 249 | 250 | U = N.zeros((len(times), len(bucket_ind)), "d") 251 | for i, l in enumerate(bucket_ind): 252 | U[l, i] = 1 253 | 254 | if flags is not None: 255 | return avetoas, aveflags, U 256 | else: 257 | return avetoas, U 258 | 259 | 260 | # check that the two versions match 261 | # t, U = quantize(N.array(psr.toas(),'d'),dt=1) 262 | # t2, U2 = quantize_fast(N.array(psr.toas(),'d'),dt=1) 263 | # print N.sum((t - t2)**2), N.all(U == U2) 264 | 265 | 266 | def add_jitter(psr, ecorr, flagid=None, flags=None, coarsegrain=0.1, seed=None): 267 | """Add correlated quadrature noise of rms `ecorr` [s], 268 | with coarse-graining time `coarsegrain` [days]. 269 | Optionally take a pseudorandom-number-generator seed.""" 270 | 271 | if seed is not None: 272 | N.random.seed(seed) 273 | 274 | if flags is None: 275 | t, U = quantize_fast(N.array(psr.toas(), "d"), dt=coarsegrain) 276 | elif flags is not None and flagid is not None: 277 | t, f, U = quantize_fast(N.array(psr.toas(), "d"), N.array(psr.flagvals(flagid)), dt=coarsegrain) 278 | 279 | # default jitter value 280 | ecorrvec = N.zeros(len(t)) 281 | 282 | # check that jitter is scalar if flags is None 283 | if flags is None: 284 | if not N.isscalar(ecorr): 285 | raise ValueError("ERROR: If flags is None, jitter must be a scalar") 286 | else: 287 | ecorrvec = N.ones(len(t)) * ecorr 288 | 289 | if flags is not None and flagid is not None and not N.isscalar(ecorr): 290 | if len(ecorr) == len(flags): 291 | for ct, flag in enumerate(flags): 292 | ind = flag == N.array(f) 293 | ecorrvec[ind] = ecorr[ct] 294 | 295 | psr.stoas[:] += (1 / day) * N.dot(U * ecorrvec, N.random.randn(U.shape[1])) 296 | 297 | 298 | def add_rednoise(psr, A, gamma, components=10, tspan=None, seed=None): 299 | """ 300 | Add red noise with P(f) = A^2 / (12 pi^2) (f year)^-gamma, 301 | using `components` Fourier bases. 302 | Optionally take a pseudorandom-number-generator seed. 303 | :param psr: libstempo pulsar object. 304 | :param A: Power law red noise amplitude at 1/yr. 305 | :param gamma: Power law red noise spectral index. 306 | :param components: Number of frequency components to use in red noise 307 | injection. 308 | :param tspan: Time span to use for red noise injection [days]. Frequencies 309 | are injected from 1/tspan to components/tspan. Time span of pulsar used 310 | when not furnished. 311 | :param seed: Random number seed for reproducibility. 312 | """ 313 | 314 | if seed is not None: 315 | N.random.seed(seed) 316 | 317 | t = psr.toas() 318 | minx, maxx = N.min(t), N.max(t) 319 | if tspan is None: 320 | x = (t - minx) / (maxx - minx) 321 | T = (day / year) * (maxx - minx) 322 | else: 323 | x = (t - minx) / tspan 324 | T = (day / year) * tspan 325 | 326 | size = 2 * components 327 | F = N.zeros((psr.nobs, size), "d") 328 | f = N.zeros(size, "d") 329 | 330 | for i in range(components): 331 | F[:, 2 * i] = N.cos(2 * math.pi * (i + 1) * x) 332 | F[:, 2 * i + 1] = N.sin(2 * math.pi * (i + 1) * x) 333 | 334 | f[2 * i] = f[2 * i + 1] = (i + 1) / T 335 | 336 | norm = A**2 * year**2 / (12 * math.pi**2 * T) 337 | prior = norm * f ** (-gamma) 338 | 339 | y = N.sqrt(prior) * N.random.randn(size) 340 | psr.stoas[:] += (1.0 / day) * N.dot(F, y) 341 | 342 | 343 | def add_dm(psr, A, gamma, components=10, seed=None): 344 | """Add DM variations with P(f) = A^2 / (12 pi^2) (f year)^-gamma, 345 | using `components` Fourier bases. 346 | Optionally take a pseudorandom-number-generator seed.""" 347 | 348 | if seed is not None: 349 | N.random.seed(seed) 350 | 351 | t = psr.toas() 352 | v = DMk / psr.freqs**2 353 | 354 | minx, maxx = N.min(t), N.max(t) 355 | x = (t - minx) / (maxx - minx) 356 | T = (day / year) * (maxx - minx) 357 | 358 | size = 2 * components 359 | F = N.zeros((psr.nobs, size), "d") 360 | f = N.zeros(size, "d") 361 | 362 | for i in range(components): 363 | F[:, 2 * i] = N.cos(2 * math.pi * (i + 1) * x) 364 | F[:, 2 * i + 1] = N.sin(2 * math.pi * (i + 1) * x) 365 | 366 | f[2 * i] = f[2 * i + 1] = (i + 1) / T 367 | 368 | norm = A**2 * year**2 / (12 * math.pi**2 * T) 369 | prior = norm * f ** (-gamma) 370 | 371 | y = N.sqrt(prior) * N.random.randn(size) 372 | psr.stoas[:] += (1.0 / day) * v * N.dot(F, y) 373 | 374 | 375 | def add_line(psr, f, A, offset=0.5): 376 | """ 377 | Add a line of frequency `f` [Hz] and amplitude `A` [s], 378 | with origin at a fraction `offset` through the dataset. 379 | """ 380 | 381 | t = psr.toas() 382 | t0 = offset * (N.max(t) - N.min(t)) 383 | sine = A * N.cos(2 * math.pi * f * day * (t - t0)) 384 | 385 | psr.stoas[:] += sine / day 386 | 387 | 388 | def add_glitch(psr, epoch, amp): 389 | """ 390 | Like pulsar term BWM event, but now differently parameterized: just an 391 | amplitude (not log-amp) parameter, and an epoch. [source: piccard] 392 | 393 | :param psr: pulsar object 394 | :param epoch: TOA time (MJD) the burst hits the earth 395 | :param amp: amplitude of the glitch 396 | """ 397 | 398 | # Define the heaviside function 399 | heaviside = lambda x: 0.5 * (N.sign(x) + 1) 400 | 401 | # Glitches are spontaneous spin-up events. 402 | # Thus TOAs will be advanced, and resiudals will be negative. 403 | 404 | psr.stoas[:] -= amp * heaviside(psr.toas() - epoch) * (psr.toas() - epoch) * 86400.0 405 | 406 | 407 | def add_cgw( 408 | psr, 409 | gwtheta, 410 | gwphi, 411 | mc, 412 | dist, 413 | fgw, 414 | phase0, 415 | psi, 416 | inc, 417 | pdist=1.0, 418 | pphase=None, 419 | psrTerm=True, 420 | evolve=True, 421 | phase_approx=False, 422 | tref=0, 423 | ): 424 | """ 425 | Function to create GW-induced residuals from a SMBMB as 426 | defined in Ellis et. al 2012,2013. Tries to be smart about it... 427 | 428 | :param psr: pulsar object 429 | :param gwtheta: Polar angle of GW source in celestial coords [radians] 430 | :param gwphi: Azimuthal angle of GW source in celestial coords [radians] 431 | :param mc: Chirp mass of SMBMB [solar masses] 432 | :param dist: Luminosity distance to SMBMB [Mpc] 433 | :param fgw: Frequency of GW (twice the orbital frequency) [Hz] 434 | :param phase0: Initial Phase of GW source [radians] 435 | :param psi: Polarization of GW source [radians] 436 | :param inc: Inclination of GW source [radians] 437 | :param pdist: Pulsar distance to use other than those in psr [kpc] 438 | :param pphase: Use pulsar phase to determine distance [radian] 439 | :param psrTerm: Option to include pulsar term [boolean] 440 | :param evolve: Option to exclude evolution [boolean] 441 | :param tref: Fidicuial time at which initial parameters are referenced 442 | 443 | :returns: Vector of induced residuals 444 | """ 445 | 446 | # convert units 447 | mc *= eu.SOLAR2S # convert from solar masses to seconds 448 | dist *= eu.MPC2S # convert from Mpc to seconds 449 | 450 | # define initial orbital frequency 451 | w0 = N.pi * fgw 452 | phase0 /= 2 # orbital phase 453 | w053 = w0 ** (-5 / 3) 454 | 455 | # define variable for later use 456 | cosgwtheta, cosgwphi = N.cos(gwtheta), N.cos(gwphi) 457 | singwtheta, singwphi = N.sin(gwtheta), N.sin(gwphi) 458 | sin2psi, cos2psi = N.sin(2 * psi), N.cos(2 * psi) 459 | incfac1, incfac2 = 0.5 * (3 + N.cos(2 * inc)), 2 * N.cos(inc) 460 | 461 | # unit vectors to GW source 462 | m = N.array([singwphi, -cosgwphi, 0.0]) 463 | n = N.array([-cosgwtheta * cosgwphi, -cosgwtheta * singwphi, singwtheta]) 464 | omhat = N.array([-singwtheta * cosgwphi, -singwtheta * singwphi, -cosgwtheta]) 465 | 466 | # various factors invloving GW parameters 467 | fac1 = 256 / 5 * mc ** (5 / 3) * w0 ** (8 / 3) 468 | fac2 = 1 / 32 / mc ** (5 / 3) 469 | fac3 = mc ** (5 / 3) / dist 470 | 471 | # pulsar location 472 | if "RAJ" and "DECJ" in psr.pars(): 473 | ptheta = N.pi / 2 - psr["DECJ"].val 474 | pphi = psr["RAJ"].val 475 | elif "ELONG" and "ELAT" in psr.pars(): 476 | fac = 180.0 / N.pi 477 | coords = ephem.Equatorial(ephem.Ecliptic(str(psr["ELONG"].val * fac), str(psr["ELAT"].val * fac))) 478 | 479 | ptheta = N.pi / 2 - float(repr(coords.dec)) 480 | pphi = float(repr(coords.ra)) 481 | 482 | # use definition from Sesana et al 2010 and Ellis et al 2012 483 | phat = N.array([N.sin(ptheta) * N.cos(pphi), N.sin(ptheta) * N.sin(pphi), N.cos(ptheta)]) 484 | 485 | fplus = 0.5 * (N.dot(m, phat) ** 2 - N.dot(n, phat) ** 2) / (1 + N.dot(omhat, phat)) 486 | fcross = (N.dot(m, phat) * N.dot(n, phat)) / (1 + N.dot(omhat, phat)) 487 | cosMu = -N.dot(omhat, phat) 488 | 489 | # get values from pulsar object 490 | toas = psr.toas() * 86400 - tref 491 | if pphase is not None: 492 | pd = pphase / (2 * N.pi * fgw * (1 - cosMu)) / eu.KPC2S 493 | else: 494 | pd = pdist 495 | 496 | # convert units 497 | pd *= eu.KPC2S # convert from kpc to seconds 498 | 499 | # get pulsar time 500 | tp = toas - pd * (1 - cosMu) 501 | 502 | # evolution 503 | if evolve: 504 | # calculate time dependent frequency at earth and pulsar 505 | omega = w0 * (1 - fac1 * toas) ** (-3 / 8) 506 | omega_p = w0 * (1 - fac1 * tp) ** (-3 / 8) 507 | 508 | # calculate time dependent phase 509 | phase = phase0 + fac2 * (w053 - omega ** (-5 / 3)) 510 | phase_p = phase0 + fac2 * (w053 - omega_p ** (-5 / 3)) 511 | 512 | # use approximation that frequency does not evlolve over observation time 513 | elif phase_approx: 514 | # frequencies 515 | omega = w0 516 | omega_p = w0 * (1 + fac1 * pd * (1 - cosMu)) ** (-3 / 8) 517 | 518 | # phases 519 | phase = phase0 + omega * toas 520 | phase_p = phase0 + fac2 * (w053 - omega_p ** (-5 / 3)) + omega_p * toas 521 | 522 | # no evolution 523 | else: 524 | # monochromatic 525 | omega = w0 526 | omega_p = omega 527 | 528 | # phases 529 | phase = phase0 + omega * toas 530 | phase_p = phase0 + omega * tp 531 | 532 | # define time dependent coefficients 533 | At = N.sin(2 * phase) * incfac1 534 | Bt = N.cos(2 * phase) * incfac2 535 | At_p = N.sin(2 * phase_p) * incfac1 536 | Bt_p = N.cos(2 * phase_p) * incfac2 537 | 538 | # now define time dependent amplitudes 539 | alpha = fac3 / omega ** (1 / 3) 540 | alpha_p = fac3 / omega_p ** (1 / 3) 541 | 542 | # define rplus and rcross 543 | rplus = alpha * (At * cos2psi + Bt * sin2psi) 544 | rcross = alpha * (-At * sin2psi + Bt * cos2psi) 545 | rplus_p = alpha_p * (At_p * cos2psi + Bt_p * sin2psi) 546 | rcross_p = alpha_p * (-At_p * sin2psi + Bt_p * cos2psi) 547 | 548 | # residuals 549 | if psrTerm: 550 | res = fplus * (rplus_p - rplus) + fcross * (rcross_p - rcross) 551 | else: 552 | res = -fplus * rplus - fcross * rcross 553 | 554 | psr.stoas[:] += res / 86400 555 | 556 | 557 | def add_ecc_cgw( 558 | psr, 559 | gwtheta, 560 | gwphi, 561 | mc, 562 | dist, 563 | F, 564 | inc, 565 | psi, 566 | gamma0, 567 | e0, 568 | l0, 569 | q, 570 | nmax=100, 571 | nset=None, 572 | pd=None, 573 | periEv=True, 574 | psrTerm=True, 575 | tref=0, 576 | check=True, 577 | useFile=True, 578 | ): 579 | """ 580 | Simulate GW from eccentric SMBHB. Waveform models from 581 | Taylor et al. (2015) and Barack and Cutler (2004). 582 | 583 | WARNING: This residual waveform is only accurate if the 584 | GW frequency is not significantly evolving over the 585 | observation time of the pulsar. 586 | 587 | :param psr: pulsar object 588 | :param gwtheta: Polar angle of GW source in celestial coords [radians] 589 | :param gwphi: Azimuthal angle of GW source in celestial coords [radians] 590 | :param mc: Chirp mass of SMBMB [solar masses] 591 | :param dist: Luminosity distance to SMBMB [Mpc] 592 | :param F: Orbital frequency of SMBHB [Hz] 593 | :param inc: Inclination of GW source [radians] 594 | :param psi: Polarization of GW source [radians] 595 | :param gamma0: Initial angle of periastron [radians] 596 | :param e0: Initial eccentricity of SMBHB 597 | :param l0: Initial mean anomaly [radians] 598 | :param q: Mass ratio of SMBHB 599 | :param nmax: Number of harmonics to use in waveform decomposition 600 | :param nset: Fix the number of harmonics to be injected 601 | :param pd: Pulsar distance [kpc] 602 | :param periEv: Evolve the position of periapsis [boolean] 603 | :param psrTerm: Option to include pulsar term [boolean] 604 | :param tref: Fiducial time at which initial parameters are referenced [s] 605 | :param check: Check if frequency evolves significantly over obs. time 606 | :param useFile: Use pre-computed table of number of harmonics vs eccentricity 607 | 608 | :returns: Vector of induced residuals 609 | """ 610 | 611 | # define variable for later use 612 | cosgwtheta, cosgwphi = N.cos(gwtheta), N.cos(gwphi) 613 | singwtheta, singwphi = N.sin(gwtheta), N.sin(gwphi) 614 | sin2psi, cos2psi = N.sin(2 * psi), N.cos(2 * psi) 615 | 616 | # unit vectors to GW source 617 | m = N.array([singwphi, -cosgwphi, 0.0]) 618 | n = N.array([-cosgwtheta * cosgwphi, -cosgwtheta * singwphi, singwtheta]) 619 | omhat = N.array([-singwtheta * cosgwphi, -singwtheta * singwphi, -cosgwtheta]) 620 | 621 | # pulsar location 622 | if "RAJ" and "DECJ" in psr.pars(): 623 | ptheta = N.pi / 2 - psr["DECJ"].val 624 | pphi = psr["RAJ"].val 625 | elif "ELONG" and "ELAT" in psr.pars(): 626 | fac = 180.0 / N.pi 627 | coords = ephem.Equatorial(ephem.Ecliptic(str(psr["ELONG"].val * fac), str(psr["ELAT"].val * fac))) 628 | 629 | ptheta = N.pi / 2 - float(repr(coords.dec)) 630 | pphi = float(repr(coords.ra)) 631 | 632 | # use definition from Sesana et al 2010 and Ellis et al 2012 633 | phat = N.array([N.sin(ptheta) * N.cos(pphi), N.sin(ptheta) * N.sin(pphi), N.cos(ptheta)]) 634 | 635 | fplus = 0.5 * (N.dot(m, phat) ** 2 - N.dot(n, phat) ** 2) / (1 + N.dot(omhat, phat)) 636 | fcross = (N.dot(m, phat) * N.dot(n, phat)) / (1 + N.dot(omhat, phat)) 637 | cosMu = -N.dot(omhat, phat) 638 | 639 | # get values from pulsar object 640 | toas = N.double(psr.toas()) * 86400 - tref 641 | 642 | if check: 643 | # check that frequency is not evolving significantly over obs. time 644 | y = eu.solve_coupled_ecc_solution(F, e0, gamma0, l0, mc, q, N.array([0.0, toas.max()])) 645 | 646 | # initial and final values over observation time 647 | Fc0, ec0, gc0, phic0 = y[0, :] 648 | Fc1, ec1, gc1, phic1 = y[-1, :] 649 | 650 | # observation time 651 | Tobs = 1 / (toas.max() - toas.min()) 652 | 653 | if N.abs(Fc0 - Fc1) > 1 / Tobs: 654 | print("WARNING: Frequency is evolving over more than one frequency bin.") 655 | print("F0 = {0}, F1 = {1}, delta f = {2}".format(Fc0, Fc1, 1 / Tobs)) 656 | 657 | # get gammadot for earth term 658 | if periEv: 659 | gammadot = 0.0 660 | else: 661 | gammadot = eu.get_gammadot(F, mc, q, e0) 662 | 663 | if nset is not None: 664 | nharm = nset 665 | elif useFile: 666 | if e0 > 0.001 and e0 < 0.999: 667 | nharm = min(int(ecc_interp(e0)), nmax) + 1 668 | elif e0 < 0.001: 669 | nharm = 3 670 | else: 671 | nharm = nmax 672 | else: 673 | nharm = nmax 674 | 675 | # earth term ##### 676 | splus, scross = eu.calculate_splus_scross(nharm, mc, dist, F, e0, toas, l0, gamma0, gammadot, inc) 677 | 678 | # pulsar term ##### 679 | if psrTerm: 680 | # convert units 681 | pd *= eu.KPC2S # convert from kpc to seconds 682 | 683 | # get pulsar time 684 | tp = toas - pd * (1 - cosMu) 685 | 686 | # solve coupled system of equations to get pulsar term values 687 | y = eu.solve_coupled_ecc_solution(F, e0, gamma0, l0, mc, q, N.array([0.0, tp.min()])) 688 | 689 | # get pulsar term values 690 | if N.any(y): 691 | Fp, ep, gp, lp = y[-1, :] 692 | 693 | # get gammadot at pulsar term 694 | gammadotp = eu.get_gammadot(Fp, mc, q, ep) 695 | 696 | if useFile: 697 | if ep > 0.001 and ep < 0.999: 698 | nharm = min(int(ecc_interp(ep)), nmax) 699 | elif ep < 0.001: 700 | nharm = 3 701 | else: 702 | nharm = nmax 703 | else: 704 | nharm = nmax 705 | 706 | splusp, scrossp = eu.calculate_splus_scross(nharm, mc, dist, Fp, ep, toas, lp, gp, gammadotp, inc) 707 | 708 | rr = (fplus * cos2psi - fcross * sin2psi) * (splusp - splus) + (fplus * sin2psi + fcross * cos2psi) * ( 709 | scrossp - scross 710 | ) 711 | 712 | else: 713 | rr = N.zeros(len(toas)) 714 | 715 | else: 716 | rr = -(fplus * cos2psi - fcross * sin2psi) * splus - (fplus * sin2psi + fcross * cos2psi) * scross 717 | 718 | psr.stoas[:] += rr / 86400 719 | 720 | 721 | def extrap1d(interpolator): 722 | """ 723 | Function to extend an interpolation function to an 724 | extrapolation function. 725 | 726 | :param interpolator: scipy interp1d object 727 | 728 | :returns ufunclike: extension of function to extrapolation 729 | """ 730 | 731 | xs = interpolator.x 732 | ys = interpolator.y 733 | 734 | def pointwise(x): 735 | if x < xs[0]: 736 | return ys[0] # +(x-xs[0])*(ys[1]-ys[0])/(xs[1]-xs[0]) 737 | elif x > xs[-1]: 738 | return ys[-1] # +(x-xs[-1])*(ys[-1]-ys[-2])/(xs[-1]-xs[-2]) 739 | else: 740 | return interpolator(x) 741 | 742 | def ufunclike(xs): 743 | return N.array(list(map(pointwise, N.array(xs)))) 744 | 745 | return ufunclike 746 | 747 | 748 | def createGWB( 749 | psr, 750 | Amp, 751 | gam, 752 | noCorr=False, 753 | seed=None, 754 | turnover=False, 755 | clm=[N.sqrt(4.0 * N.pi)], 756 | lmax=0, 757 | f0=1e-9, 758 | beta=1, 759 | power=1, 760 | userSpec=None, 761 | npts=600, 762 | howml=10, 763 | ): 764 | """ 765 | Function to create GW-induced residuals from a stochastic GWB as defined 766 | in Chamberlin, Creighton, Demorest, et al. (2014). 767 | 768 | :param psr: pulsar object for single pulsar 769 | :param Amp: Amplitude of red noise in GW units 770 | :param gam: Red noise power law spectral index 771 | :param noCorr: Add red noise with no spatial correlations 772 | :param seed: Random number seed 773 | :param turnover: Produce spectrum with turnover at frequency f0 774 | :param clm: coefficients of spherical harmonic decomposition of GW power 775 | :param lmax: maximum multipole of GW power decomposition 776 | :param f0: Frequency of spectrum turnover 777 | :param beta: Spectral index of power spectram for f << f0 778 | :param power: Fudge factor for flatness of spectrum turnover 779 | :param userSpec: User-supplied characteristic strain spectrum 780 | (first column is freqs, second is spectrum) 781 | :param npts: Number of points used in interpolation 782 | :param howml: Lowest frequency is 1/(howml * T) 783 | 784 | :returns: list of residuals for each pulsar 785 | """ 786 | 787 | if seed is not None: 788 | N.random.seed(seed) 789 | 790 | # number of pulsars 791 | Npulsars = len(psr) 792 | 793 | # gw start and end times for entire data set 794 | start = N.min([p.toas().min() * 86400 for p in psr]) - 86400 795 | stop = N.max([p.toas().max() * 86400 for p in psr]) + 86400 796 | 797 | # duration of the signal 798 | dur = stop - start 799 | 800 | # get maximum number of points 801 | if npts is None: 802 | # default to cadence of 2 weeks 803 | npts = dur / (86400 * 14) 804 | 805 | # make a vector of evenly sampled data points 806 | ut = N.linspace(start, stop, npts) 807 | 808 | # time resolution in days 809 | dt = dur / npts 810 | 811 | # compute the overlap reduction function 812 | if noCorr: 813 | ORF = N.diag(N.ones(Npulsars) * 2) 814 | else: 815 | psrlocs = N.zeros((Npulsars, 2)) 816 | 817 | for ii in range(Npulsars): 818 | if "RAJ" and "DECJ" in psr[ii].pars(): 819 | psrlocs[ii] = N.double(psr[ii]["RAJ"].val), N.double(psr[ii]["DECJ"].val) 820 | elif "ELONG" and "ELAT" in psr[ii].pars(): 821 | fac = 180.0 / N.pi 822 | # check for B name 823 | if "B" in psr[ii].name: 824 | epoch = "1950" 825 | else: 826 | epoch = "2000" 827 | coords = ephem.Equatorial( 828 | ephem.Ecliptic(str(psr[ii]["ELONG"].val * fac), str(psr[ii]["ELAT"].val * fac)), epoch=epoch 829 | ) 830 | psrlocs[ii] = float(repr(coords.ra)), float(repr(coords.dec)) 831 | 832 | psrlocs[:, 1] = N.pi / 2.0 - psrlocs[:, 1] 833 | anisbasis = N.array(anis.CorrBasis(psrlocs, lmax)) 834 | ORF = sum(clm[kk] * anisbasis[kk] for kk in range(len(anisbasis))) 835 | ORF *= 2.0 836 | 837 | # Define frequencies spanning from DC to Nyquist. 838 | # This is a vector spanning these frequencies in increments of 1/(dur*howml). 839 | f = N.arange(0, 1 / (2 * dt), 1 / (dur * howml)) 840 | f[0] = f[1] # avoid divide by 0 warning 841 | Nf = len(f) 842 | 843 | # Use Cholesky transform to take 'square root' of ORF 844 | M = N.linalg.cholesky(ORF) 845 | 846 | # Create random frequency series from zero mean, unit variance, Gaussian distributions 847 | w = N.zeros((Npulsars, Nf), complex) 848 | for ll in range(Npulsars): 849 | w[ll, :] = N.random.randn(Nf) + 1j * N.random.randn(Nf) 850 | 851 | # strain amplitude 852 | if userSpec is None: 853 | f1yr = 1 / 3.16e7 854 | alpha = -0.5 * (gam - 3) 855 | hcf = Amp * (f / f1yr) ** (alpha) 856 | if turnover: 857 | si = alpha - beta 858 | hcf /= (1 + (f / f0) ** (power * si)) ** (1 / power) 859 | 860 | elif userSpec is not None: 861 | freqs = userSpec[:, 0] 862 | if len(userSpec[:, 0]) != len(freqs): 863 | raise ValueError("Number of supplied spectral points does not match number of frequencies!") 864 | else: 865 | fspec_in = interp.interp1d(N.log10(freqs), N.log10(userSpec[:, 1]), kind="linear") 866 | fspec_ex = extrap1d(fspec_in) 867 | hcf = 10.0 ** fspec_ex(N.log10(f)) 868 | 869 | C = 1 / 96 / N.pi**2 * hcf**2 / f**3 * dur * howml 870 | 871 | # inject residuals in the frequency domain 872 | Res_f = N.dot(M, w) 873 | for ll in range(Npulsars): 874 | Res_f[ll] = Res_f[ll] * C ** (0.5) # rescale by frequency dependent factor 875 | Res_f[ll, 0] = 0 # set DC bin to zero to avoid infinities 876 | Res_f[ll, -1] = 0 # set Nyquist bin to zero also 877 | 878 | # Now fill in bins after Nyquist (for fft data packing) and take inverse FT 879 | Res_f2 = N.zeros((Npulsars, 2 * Nf - 2), complex) 880 | Res_t = N.zeros((Npulsars, 2 * Nf - 2)) 881 | Res_f2[:, 0:Nf] = Res_f[:, 0:Nf] 882 | Res_f2[:, Nf : (2 * Nf - 2)] = N.conj(Res_f[:, (Nf - 2) : 0 : -1]) 883 | Res_t = N.real(N.fft.ifft(Res_f2) / dt) 884 | 885 | # shorten data and interpolate onto TOAs 886 | Res = N.zeros((Npulsars, npts)) 887 | res_gw = [] 888 | for ll in range(Npulsars): 889 | Res[ll, :] = Res_t[ll, 10 : (npts + 10)] 890 | f = interp.interp1d(ut, Res[ll, :], kind="linear") 891 | res_gw.append(f(psr[ll].toas() * 86400)) 892 | 893 | # return res_gw 894 | ct = 0 895 | for p in psr: 896 | p.stoas[:] += res_gw[ct] / 86400.0 897 | ct += 1 898 | 899 | 900 | def computeORFMatrix(psr): 901 | """ 902 | Compute ORF matrix. 903 | 904 | :param psr: List of pulsar object instances 905 | 906 | :returns: Matrix that has the ORF values for every pulsar 907 | pair with 2 on the diagonals to account for the 908 | pulsar term. 909 | 910 | """ 911 | 912 | # begin loop over all pulsar pairs and calculate ORF 913 | npsr = len(psr) 914 | ORF = N.zeros((npsr, npsr)) 915 | phati = N.zeros(3) 916 | phatj = N.zeros(3) 917 | ptheta = [N.pi / 2 - p["DECJ"].val for p in psr] 918 | pphi = [p["RAJ"].val for p in psr] 919 | for ll in range(0, npsr): 920 | phati[0] = N.cos(pphi[ll]) * N.sin(ptheta[ll]) 921 | phati[1] = N.sin(pphi[ll]) * N.sin(ptheta[ll]) 922 | phati[2] = N.cos(ptheta[ll]) 923 | 924 | for kk in range(0, npsr): 925 | phatj[0] = N.cos(pphi[kk]) * N.sin(ptheta[kk]) 926 | phatj[1] = N.sin(pphi[kk]) * N.sin(ptheta[kk]) 927 | phatj[2] = N.cos(ptheta[kk]) 928 | 929 | if ll != kk: 930 | xip = (1.0 - N.sum(phati * phatj)) / 2.0 931 | ORF[ll, kk] = 3.0 * (1.0 / 3.0 + xip * (N.log(xip) - 1.0 / 6.0)) 932 | else: 933 | ORF[ll, kk] = 2.0 934 | 935 | return ORF 936 | --------------------------------------------------------------------------------