├── .github └── workflows │ └── buildwheels.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── afterglowpy ├── __init__.py ├── cocoon.py ├── flux.py ├── integrate.c ├── integrate.h ├── interval.c ├── interval.h ├── jetmodule.c ├── offaxis_struct.h ├── offaxis_struct_funcs.c ├── shockEvolution.c ├── shockEvolution.h ├── shockmodule.c └── version.py ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── conf.py │ ├── index.rst │ ├── modules.rst │ ├── modules │ ├── afterglowpy.cocoon.rst │ ├── afterglowpy.flux.rst │ ├── afterglowpy.jet.rst │ ├── afterglowpy.rst │ └── afterglowpy.shock.rst │ └── quickstart.rst ├── examples ├── plotCentroidAndSize.py ├── plotLateTime.py ├── plotLightCurve.py ├── plotMultibandLightCurve.py ├── plotMultibandLightCurveBatch.py ├── plotSpecType.py └── plotSpectrum.py ├── pyproject.toml ├── setup.py └── test ├── __init__.py └── test_flux.py /.github/workflows/buildwheels.yml: -------------------------------------------------------------------------------- 1 | name: Build wheels 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - dev 7 | tags: 8 | - v* 9 | 10 | env: 11 | CIBW_SKIP: cp2* pp* cp35* cp36* cp37* 12 | CIBW_ARCHS_LINUX: auto64 13 | 14 | jobs: 15 | build_wheels: 16 | name: Build wheels on ${{ matrix.os }} 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, windows-latest, macos-13, macos-latest] 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | # - name: Set up Python 25 | # uses: actions/setup-python@v2 26 | # with: 27 | # python-version: '3.9' 28 | 29 | - name: Build wheels 30 | uses: pypa/cibuildwheel@v2.23.0 31 | 32 | - name: Upload wheels 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 36 | path: ./wheelhouse/*.whl 37 | 38 | build_sdist: 39 | name: Build source distribution 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v4 43 | 44 | # - name: Install Python 45 | # uses: actions/setup-python@v4 46 | # with: 47 | # python-version: '3.8' 48 | 49 | - name: Install afterglowpy 50 | run: pipx run build --sdist 51 | 52 | # - name: Build sdist 53 | # run: python setup.py sdist 54 | 55 | - name: Upload sdist 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: cibw-sdist 59 | path: dist/*.tar.gz 60 | 61 | upload_pypi: 62 | needs: [build_wheels, build_sdist] 63 | runs-on: ubuntu-latest 64 | environment: pypi 65 | permissions: 66 | id-token: write 67 | 68 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') 69 | 70 | steps: 71 | - uses: actions/download-artifact@v4 72 | with: 73 | pattern: cibw-* 74 | path: dist 75 | merge-multiple: true 76 | 77 | - uses: pypa/gh-action-pypi-publish@release/v1 78 | with: 79 | user: __token__ 80 | password: ${{ secrets.PYPI_API_TOKEN }} 81 | 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *.so 4 | *.pyc 5 | build/ 6 | dist/ 7 | *egg-info* 8 | *.png 9 | *.json 10 | *.h5 11 | *.pdf 12 | *.txt 13 | scratch/ 14 | wheelhouse 15 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | build: 9 | os: ubuntu-22.04 10 | 11 | tools: 12 | python: "3.12" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/source/conf.py 17 | 18 | python: 19 | install: 20 | - requirements: docs/requirements.txt 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Geoffrey Ryan 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. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include afterglowpy/version.py afterglowpy/*.h 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Numeric GRB Afterglow models 2 | 3 | A Python 3 module to calculate GRB afterglow light curves and spectra. Details of the methods can be found in [Ryan et al 2020](https://ui.adsabs.harvard.edu/abs/2020ApJ...896..166R/abstract) and [Ryan et al 2024](https://ui.adsabs.harvard.edu/abs/2024ApJ...975..131R/abstract). Builds on [van Eerten & MacFadyen 2010](https://arxiv.org/abs/1006.5125) and [van Eerten 2018](https://arxiv.org/abs/1801.01848). This code is under active development. 4 | 5 | Documentation available at 6 | 7 | ## Attribution 8 | 9 | If you use this code in a publication, please refer to the package by name and cite "Ryan, G., van Eerten, H., Piro, L. and Troja, E., Astrophysical Journal *896*, 166 (2020)" [ADS link](https://ui.adsabs.harvard.edu/abs/2020ApJ...896..166R/abstract). Upgrades including centroid motion, size, and the deep Newtonian phase are presented in "Ryan, G., van Eerten, H., Troja, E., Piro, L., O'Connor, B., and Ricci, R., Astrophysical Journal *975*, 131 (2024)" [ADS link](https://ui.adsabs.harvard.edu/abs/2024ApJ...975..131R/abstract). 10 | 11 | ## Acknowledgements 12 | 13 | This work is funded in part by the European Union’s Horizon 2020 Programme under the AHEAD2020 project (grant agreement n. 871158). 14 | 15 | ## Features 16 | 17 | _afterglowpy_ computes synchrotron emission from the forward shock of a relativistic blast wave. It includes: 18 | - Fully trans-relativistic shock evolution through a constant density medium. 19 | - On-the-fly integration over the equal-observer-time slices of the shock surface. 20 | - Approximate prescription for jet spreading. 21 | - Arbitrary viewing angles. 22 | - Angularly structured jets, ie. E(θ) 23 | - Spherical velocity-stratified outflows, ie. E(u) 24 | - Counter-jet emission. 25 | - Deep Newtonian emission. 26 | - Image moments suitable for astrometry: centroid position and image size. 27 | 28 | It has limited support (these should be considered experimental) for: 29 | - Initial energy injection 30 | - Inverse comption spectra 31 | - Early coasting phase 32 | 33 | It does not include (yet): 34 | - External wind medium, ie. n ∝ r-2 35 | - Synchrotron self-absorbtion 36 | - Reverse shock emission 37 | 38 | _afterglowpy_ has been calibrated to the BoxFit code ([van Eerten, van der Horst, & Macfadyen 2011](https://arxiv.org/abs/1110.5089), available at the [Afterglow Library](https://cosmo.nyu.edu/afterglowlibrary/boxfit2011.html)) and produces similar light curves for top hat jets (within 50% when same parameters are used) both on- and off-axis. Its jet models by default do not include an initial coasting phase, which may effect predictions for early observations. 39 | 40 | ## Changelog 41 | 42 | ### New in v0.8.1 43 | - Numpy 2.0 compatibility 44 | - `ignoreBounds` Boolean keyword argument to ignore built-in bounds checking on parameters. 45 | 46 | ### New in v0.8.0 47 | - Image size and position via the `moment` keyword. 48 | - Deep Newtonian spectral evolution at late times via `specType=grb.jet.DeepNewtonian` 49 | 50 | ## Installation/Building 51 | 52 | _afterglowpy_ is available via `pip`: 53 | ```bash 54 | $ pip install afterglowpy 55 | ``` 56 | 57 | _afterglowpy_ is compatible with Numpy v1 and v2, Python 3.8+, and runs on MacOS, Linux, and Windows. 58 | 59 | If you are working on a local copy of this repo and would like to install from source, you can the run the following from the top level directory of the project. 60 | ```bash 61 | $ pip install -e . 62 | ``` 63 | 64 | ## Using 65 | 66 | In your python code, import the library with `import afterglowpy as grb`. 67 | 68 | The main function of interest is`grb.fluxDensity(t, nu, **kwargs)`. See `examples/plotLightCurve.py` for a simple example. 69 | 70 | For jet-like afterglows there are up to 13 required keyword arguments: 71 | 72 | - `jetType` an integer code setting the jet structure. It can be `grb.jet.TopHat`, `grb.jet.Gaussian`, `grb.jet.PowerLawCore`, `grb.jet.GaussianCore`, `grb.jet.Spherical`, or `grb.jet.PowerLaw`. 73 | - `specType` an integer code specifying flags for the emissivity function and spectrum. Can be `grb.jet.SimpleSpec` (basic spectrum with νm and νc), `grb.jet.DeepNewtonian`, `grb.jet.EpsEBar` to interpret `epsilon_e` as ε̅e = εe(p-2)/(p-1), `grb.jet.ICCooling` (simple inverse Compton effects on the cooling frequency, experimental). Multiple options can be combined with the `|` operator. 74 | - `thetaObs` viewing angle in radians 75 | - `E0` on-axis isotropic equivalent energy in erg 76 | - `thetaCore` half-width of the jet core in radians (jetType specific) 77 | - `thetaWing` "wing" truncation angle of the jet, in radians 78 | - `b` power for power-law structure, θ-b 79 | - `n0` Number density of ISM, in cm-3 80 | - `p` Electron distribution power-law index (p>2) 81 | - `epsilon_e` Thermal energy fraction in electrons 82 | - `epsilon_B` Thermal energy fraction in magnetic field 83 | - `xi_N` Fraction of electrons that get accelerated 84 | - `d_L` Luminosity distance in cm 85 | 86 | Optional keyword arguments for all models are: 87 | - `z` redshift (defaults to 0) 88 | - `spread` boolean (defaults to True), whether to allow the jet to spread. 89 | - `counterjet` boolean (defaults to False), whether to include the counterjet 90 | - `moment` array (integer dtype, same shape as t and nu) which sky moment to compute. 91 | - `ignoreBounds` boolean (defaults to False), whether to ignore the built in paramter bounds checking. 92 | - `L0` Fiducial luminosity for energy injection, in erg/s, default 0.0. 93 | - `q` Temporal power-law index for energy injection, default 0.0. 94 | - `ts` Fiducial time-scale for energy injection, in seconds, default 0. 95 | - `tRes` time resolution of shock-evolution scheme, number of sample points per decade in time 96 | - `latRes` latitudinal resolution for structured jets, number of shells per `thetaC` 97 | - `rtol` target relative tolerance of flux integration 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /afterglowpy/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | r""" 3 | =========== 4 | afterglowpy 5 | =========== 6 | 7 | Top-level module for *afterglowpy*, computes light curves and spectra of 8 | Gamma-ray burst (GRB) afterglows. 9 | 10 | This module provides the primary user-facing functions provided by afterglowpy, 11 | useful physical constants, and conversion factors. Internal computations are 12 | performed in the submodules. 13 | 14 | Functions 15 | --------- 16 | 17 | These are the primary interface into *afterglowpy*. If you just want to compute 18 | light curves, spectra, and intensity maps, and are unconcerned with the 19 | underlying algorithm, these are all you need. 20 | 21 | =================== ========================================================= 22 | :func:`fluxDensity` Compute the flux density F_nu of a GRB afterglow. 23 | :func:`intensity` Compute the specific intensity I_nu of a GRB afterglow. 24 | =================== ========================================================= 25 | 26 | Submodules 27 | ----------- 28 | 29 | These submodules perform the internal computations involved with calculating 30 | synchrotron emission from a blast wave: calculating the evolution of a blast 31 | wave with time, computing the synchrotron emissivity, integrating over 32 | equal-observer-time hypersurfaces, and constructing structured jets. 33 | 34 | =========================== =================================================== 35 | :mod:`afterglowpy.shock` Routines for computing evolution of a relativistic 36 | shock 37 | :mod:`afterglowpy.jet` Routines for computing synchrotron emission from a 38 | jet 39 | :mod:`afterglowpy.cocoon` Routines for computing synctrotron emission from a 40 | spherical shell. 41 | =========================== =================================================== 42 | 43 | """ 44 | from .version import __version__ 45 | from . import shock 46 | from . import cocoon 47 | from . import jet 48 | from . import flux 49 | from .flux import fluxDensity, intensity 50 | from .cocoon import (Hz2eV, Msun, c, cgs2mJy, day2sec, eV2Hz, ee, h, hbar, 51 | mJy2cgs, me, mp, parsec, sec2day, sigmaT) 52 | from .jet import (Cone, TopHat, Gaussian, PowerLaw, GaussianCore, PowerLawCore, 53 | Spherical) 54 | 55 | __all__ = ['__version__', 56 | 'shock', 'cocoon', 'jet', 'flux', 'fluxDensity', 'intensity', 57 | 'Hz2eV', 'Msun', 'c', 'cgs2mJy', 'day2sec', 'eV2Hz', 'ee', 'h', 58 | 'hbar', 'mJy2cgs', 59 | 'me', 'mp', 'parsec', 'sec2day', 'sigmaT'] 60 | -------------------------------------------------------------------------------- /afterglowpy/cocoon.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import scipy.integrate as integrate 4 | from . import shock 5 | from . import jet 6 | 7 | c = 2.99792458e10 8 | me = 9.1093897e-28 9 | mp = 1.6726231e-24 10 | h = 6.6260755e-27 11 | hbar = 1.05457266e-27 12 | ee = 4.803e-10 13 | sigmaT = 6.65e-25 14 | 15 | Msun = 1.98892e33 16 | cgs2mJy = 1.0e26 17 | mJy2cgs = 1.0e-26 18 | deg2rad = np.pi/180.0 19 | rad2deg = 180.0/np.pi 20 | day2sec = 86400.0 21 | sec2day = 1.0/day2sec 22 | parsec = 3.0857e18 23 | Hz2eV = 4.13566553853599e-15 24 | eV2Hz = 1.0/Hz2eV 25 | 26 | 27 | def dP(costheta, amu, ate, au, ar, nu, n0, p, epsE, epsB, ksiN, specType): 28 | 29 | mu = costheta 30 | ib = np.searchsorted(amu, mu) 31 | N = amu.shape[0] 32 | if ib <= 0: 33 | ib = 1 34 | elif ib >= N: 35 | ib = N-1 36 | ia = ib-1 37 | 38 | te = ((mu-amu[ia])*ate[ib] + (amu[ib]-mu)*ate[ia]) / (amu[ib]-amu[ia]) 39 | u = au[ia]*math.pow(te/ate[ia], math.log(au[ib]/au[ia]) 40 | / math.log(ate[ib]/ate[ia])) 41 | r = ar[ia]*math.pow(te/ate[ia], math.log(ar[ib]/ar[ia]) 42 | / math.log(ate[ib]/ate[ia])) 43 | 44 | g = math.sqrt(u*u+1) 45 | 46 | us = 4*u*g / math.sqrt(8*u*u+9) 47 | 48 | rho0 = n0 * mp 49 | Msw = rho0 * 4.0/3.0 * np.pi * r**3 50 | 51 | em = jet.emissivity(nu, r, mu, te, u, us, rho0, Msw, p, epsE, 52 | epsB, ksiN, specType) 53 | 54 | return 2*np.pi * em 55 | 56 | 57 | def fluxDensity(t, nu, **kwargs): 58 | 59 | t = np.array(t) 60 | 61 | specType = kwargs['specType'] 62 | uMax = kwargs['uMax'] 63 | uMin = kwargs['uMin'] 64 | Er = kwargs['Er'] 65 | k = kwargs['k'] 66 | MFast_solar = kwargs['MFast_solar'] 67 | n0 = kwargs['n0'] 68 | p = kwargs['p'] 69 | epsilon_e = kwargs['epsilon_e'] 70 | epsilon_B = kwargs['epsilon_B'] 71 | ksiN = kwargs['xi_N'] 72 | dL = kwargs['d_L'] 73 | 74 | # Energy injection variables (off by default) 75 | L0 = kwargs['L0'] if 'L0' in kwargs else 0.0 76 | q = kwargs['q'] if 'q' in kwargs else 0.0 77 | ts = kwargs['ts'] if 'ts' in kwargs else 0.0 78 | 79 | # Numerical integration variables 80 | rtol = kwargs['rtol'] if 'rtol' in kwargs else 1.0e-3 81 | tRes = kwargs['tRes'] if 'tRes' in kwargs else 1000 82 | latRes = kwargs['latRes'] if 'latRes' in kwargs else 0 83 | 84 | # Environment Variables 85 | envType = kwargs['envType'] if 'envType' in kwargs else jet.EnvISM 86 | R0Env = kwargs['R0Env'] if 'R0Env' in kwargs else 1.0e18 87 | kEnv = kwargs['kEnv'] if 'kEnv' in kwargs else 0.0 88 | rho1Env = kwargs['rho1Env'] if 'rho1Env' in kwargs else 1.0 89 | 90 | rho0 = mp * n0 91 | Mej = MFast_solar * Msun 92 | u0 = uMax 93 | g0 = math.sqrt(1+u0*u0) 94 | bes0 = 4*u0*g0 / (4*u0*u0+3) 95 | Rd = math.pow(9*g0*g0*Mej / (4*np.pi*(g0+1)*(4*u0*u0+3)*rho0), 1./3.) 96 | td = Rd / (bes0 * c) 97 | 98 | t0 = min(1.0e-2*td, 5.0e-1 * g0*g0*t.min(), 5.0e-1 * t.min()/(1+bes0)) 99 | t1 = 2. * g0*g0*t.max() 100 | 101 | NT = int(tRes * math.log10(t1/t0)) 102 | # print("{0:.3e} {1:.3e} {2:.3e} {3:d}".format(t0, t1, t1/t0, NT)) 103 | 104 | r0 = bes0*c*t0 105 | 106 | # Vej0 = 4.0/3.0*np.pi*r0*r0*r0 107 | 108 | ate = np.logspace(math.log10(t0), math.log10(t1), num=NT, base=10.0) 109 | 110 | ar, au = shock.shockEvolRK4(ate, r0, uMax, 111 | MFast_solar*Msun, 112 | envType, rho0, R0Env, kEnv, rho1Env, 113 | Er, k, uMin, L0, q, ts) 114 | 115 | P = np.zeros(t.shape) 116 | 117 | wopts = None 118 | 119 | for i in range(len(t)): 120 | amu = c * (ate - t[i]) / ar 121 | 122 | args = (amu, ate, au, ar, nu[i], n0, p, epsilon_e, epsilon_B, ksiN, 123 | specType) 124 | 125 | res = integrate.quad(dP, 0.0, 1.0, args, full_output=1, wopts=wopts, 126 | epsrel=rtol) 127 | P[i] = res[0] 128 | 129 | Fnu = cgs2mJy * P / (4*np.pi*dL*dL) 130 | 131 | return Fnu 132 | -------------------------------------------------------------------------------- /afterglowpy/flux.py: -------------------------------------------------------------------------------- 1 | from . import cocoon 2 | from . import jet 3 | import numpy as np 4 | import warnings 5 | 6 | 7 | def fluxDensity(t, nu, *args, **kwargs): 8 | r""" 9 | Compute the flux density :math:`F_{\nu}` of a GRB afterglow. 10 | 11 | Utiliizes the single shell approximation described in Ryan et al 2020 12 | to compute the synchrotron emission from the forward shock of a blast 13 | wave at the specified times and frequencies. 14 | 15 | ``fluxDensity`` takes many model-dependent parameters which are specified 16 | via keyword arguments. It is recommended to collect these parameters in a 17 | dictionary ``Z`` and call fluxDensity() by:: 18 | 19 | Fnu = fluxDensity(t, nu, **Z) 20 | 21 | Alternately model parameters may be specified as positional arguments, 22 | although this interface is **not recommended** and may be deprecated in 23 | the future. To call a jetted model with positional and keyword arguments:: 24 | 25 | Fnu = fluxDensity(t, nu, jetType, specType, thetaObs, E0, thetaCore, 26 | thetaWing, b, L0, q, ts, n0, p, epsilon_e, epsilon_B, 27 | xi_N, d_L, **Z) 28 | 29 | To call a spherical refreshed shock model with positional arguments:: 30 | 31 | Fnu = fluxDensity(t, nu, jet.Spherical, specType, uMax, uMin, Er, k, 32 | MFast_solar, L0, q, ts, n0, p, epsilon_e, epsilon_B, 33 | xi_N, d_L, **Z) 34 | 35 | 36 | Parameters 37 | ---------- 38 | t : array_like or scalar 39 | Time since burst in observer frame, measured in seconds. 40 | nu : array_like or scalar 41 | Frequency of flux in observer frame, measured in Hz, same size as t. 42 | jetType : int 43 | Code for type of jet. Model codes are available in ``afterglowpy.jet`` 44 | and include: ``jet.TopHat``, ``jet.Cone``, ``jet.Gaussian``, 45 | ``jet.PowerLaw``, ``jet.GaussianCore``, ``jet.PowerLawCore``, and 46 | ``jet.Spherical``. 47 | specType : int 48 | Flags for type of spectrum/emissivity function. Spectrum flags are 49 | available in ``afterglowpy.jet`` and include: 50 | 51 | - ``jet.SimpleSpec`` broken power law with nu_m and nu_c (Ryan+ 2020, 52 | default), 53 | - ``jet.DeepNewtonian`` better handling of late-time emission when 54 | some electrons become non-relativistic (e.g. Sironi+ 2013), 55 | - ``jet.EpsEBar`` interpret the epsilon_e parameter as 56 | :math:`\bar{\epsilon}_e = \epsilon_e (p-2) / (p-1)` 57 | (e.g. Granot & Sari 2002), 58 | - ``jet.ICCooling`` simple inverse-compton contribution to cooling 59 | (experimental). 60 | 61 | Flags can be combined with the | operator. 62 | thetaObs : float 63 | Viewing angle in radians. Jet models only. 64 | E0: float 65 | Isotropic-equivalent energy along the jet axis in ergs. Jet models 66 | only. 67 | thetaCore: float 68 | Half opening angle of jet core in radians. Jet models only. 69 | thetaWing: float 70 | Outer truncation angle of the jet in radians. Ignored by 71 | ``jet.TopHat``, jet models only. 72 | b: float 73 | Power law index of jet angular energy distribution. Only used by 74 | ``jet.PowerLaw`` and ``jet.PowerLawCore``. 75 | n0 : float 76 | Number density of protons in circumburst medium in cm^{-3}. 77 | p : float 78 | Power law index of relativistic electron energy distribution, 79 | generally p > 2. 80 | epsilon_e : float 81 | Fraction of thermal energy in relativistic electrons, epsilon_e <= 1. 82 | epsilon_B : float 83 | Fraction of thermal energy in magnetic field, epsilon_B <= 1. 84 | xi_N : float 85 | Fraction of electrons that get accelerated, xi_N <= 1. 86 | d_L : float 87 | Luminosity distance to burst, in cm. 88 | z : float, optional 89 | Redshift of burst, defaults to 0. 90 | 91 | L0: float, optional 92 | Luminosity of energy injection, in erg/s. Default 0.0. 93 | q: float, optional 94 | Power law index of energy injection: L = L0 (t/t0)^{-q}, t0 = 1 ks. 95 | Default 0.0. 96 | ts: float, optional 97 | Time energy injection ends in burster frame, in seconds. Default 0.0. 98 | g0 : float, optional 99 | EXPERIMENTAL. Initial Lorentz factor of outflow along jet axis, 100 | defaults to -1 (unset, jet has deceleration radius 0). Do not use with 101 | jet spreading enabled. 102 | 103 | uMax : float 104 | Maximum 4-velocity of outflow. Only for spherical models. 105 | uMin : float 106 | Minimum 4-velocity of outflow. Only for spherical models 107 | Er : float 108 | Normalization of outflow's energy distribution in ergs. Only for 109 | spherical models. 110 | E(u>U) = Er * U^{-k} 111 | k : float 112 | Power law index of outflow's energy distribution. Only for spherical 113 | models 114 | MFast_solar : float 115 | Mass of material at u_max in solar masses. Only for spherical models. 116 | 117 | Other Parameters 118 | ---------------- 119 | 120 | spread : {True, False}, optional 121 | Whether to include jet spreading. Defaults to True. 122 | counterjet : {'True', 'False'}, optional 123 | Whether to include counterjet emission. Defaults to False. 124 | ignoreBounds : {'True', 'False'}, optional 125 | Whether to ignore built-in parameter bounds checking. 126 | moment : array_like, optional 127 | An integer array the same shape as the larger of `t` or `nu`. Selects 128 | which image moment to compute. Observer's sky is on the x-y plane, 129 | with the jet propagating in the x-direction. Moments are in terms of 130 | proper length (in cm) Options are: `jet.MOM_0` Flux, default, 131 | `jet.MOM_X` x^1 moment, `jet.MOM_Y` y^1 moment, `jet.MOM_Z` z^1 moment, 132 | `jet.MOM_XX` x^2 moment, `jet.MOM_YY` y^2 moment, `jet.MOM_ZZ`, z^2 133 | moment, `jet.MOM_XY` x^1 y^1, `jet.MOM_YZ`, `jet.MOM_XZ`. 134 | tRes : int, optional 135 | Time resolution, number of points per decade in t, for shock evolution. 136 | Defaults to 1000. 137 | latRes : int, optional 138 | Lateral resolution of structured jet, number of conical sections per 139 | thetaCore-sized interval. Defaults to 5. 140 | intType : int, optional 141 | Integration scheme to use when computing flux. Defaults to 142 | ``jet.Cadre``. Changing this may result in longer run times or larger 143 | than expected numerical errors. 144 | rtolStruct : float, optional 145 | Overall relative tolerance of flux integration for structured jets. 146 | Defaults to 1.0e-2. 147 | rtolTheta : float, optional 148 | Relative tolerance of flux integration over theta within each 149 | conical section. Defaults to 1.0e-2. 150 | rtolPhi : float, optional 151 | Relative tolerance of flux integration over phi within 152 | each conical section. Defaults to 1.0e-2. 153 | NPhi : int, optional 154 | Maximum number of evaluations to perform in phi direction during 155 | numerical integration. Default 1000. 156 | NTheta : int, optional 157 | Maximum number of evaluations to perform in theta direction during 158 | numerical integration. Default 1000. 159 | 160 | Returns 161 | ------- 162 | Fnu: array 163 | The flux density F_nu in the observer frame, same shape as t and nu. 164 | 165 | Raises 166 | ------ 167 | 168 | ValueError 169 | If t, nu are the wrong shape or arguments take illegal values. 170 | """ 171 | 172 | argsDict = parseArgs(args, kwargs) 173 | 174 | # Check Arguments, will raise ValueError if args are bad 175 | t, nu = checkTNu(t, nu) 176 | 177 | jetType = argsDict['jetType'] 178 | 179 | # Check arguments have valid values, raise ValueError if not. 180 | # if ignoreBounds is True, these checks will be skipped. 181 | if jetType == jet.Spherical: 182 | checkCocoonArgs(argsDict) 183 | else: 184 | checkJetArgs(argsDict) 185 | 186 | # arguments are good, full steam ahead! 187 | z = argsDict.pop('z') if 'z' in argsDict else 0.0 188 | 189 | tz = t / (1+z) 190 | nuz = nu * (1+z) 191 | 192 | # Default spreading method 193 | if 'spread' in argsDict: 194 | if argsDict['spread'] is True: 195 | if jetType == -2 and 'thetaCoreGlobal' in argsDict: 196 | argsDict['spread'] = 8 197 | else: 198 | argsDict['spread'] = 7 199 | 200 | # This was a bad idea to add to this function, but is kept for 201 | # backwards compatibility. Please don't use these. 202 | LR = argsDict.pop('LR') if 'LR' in argsDict else 0.0 203 | LO = argsDict.pop('LO') if 'LO' in argsDict else 0.0 204 | LX = argsDict.pop('LX') if 'LX' in argsDict else 0.0 205 | tAdd = argsDict.pop('tAdd') if 'tAdd' in argsDict else 0.0 206 | 207 | # timeA = time.time() 208 | 209 | Fnu = np.empty(tz.shape) 210 | 211 | if jetType == jet.Spherical: 212 | Fnu.flat[:] = cocoon.fluxDensity(tz.flat, nuz.flat, **argsDict) 213 | else: 214 | Fnu.flat[:] = jet.fluxDensity(tz.flat, nuz.flat, **argsDict) 215 | # timeB = time.time() 216 | # print("Eval took: {0:f} s".format(timeB - timeA)) 217 | 218 | # Adding background luminosities. 219 | L_to_flux = cocoon.cgs2mJy / (4*np.pi * argsDict['d_L']**2) 220 | 221 | if LR > 0.0: 222 | rad = (nuz < 3.0e11) & (tz > tAdd) # radio < 300 GHz 223 | Lnu = LR / 1.0e10 # 10 GHz bandwidth 224 | Fnu[rad] += Lnu*L_to_flux 225 | if LO > 0.0: 226 | # 300GHz= 3.0e11) & (nuz < 100*cocoon.eV2Hz) & (tz > tAdd) 228 | Lnu = LO * 2.32478e-5 / cocoon.c # 2324.78 A bandwidth 229 | Fnu[opt] += Lnu*L_to_flux 230 | if LX > 0.0: 231 | xry = (nuz >= 100*cocoon.eV2Hz) & (tz > tAdd) # xray > 100eV 232 | Lnu = LX / (9.7e3 * cocoon.eV2Hz) # 9.7 keV bandwidth 233 | Fnu[xry] += Lnu*L_to_flux 234 | 235 | # K-correct the flux 236 | Fnu *= 1+z 237 | 238 | return Fnu 239 | 240 | 241 | def intensity(theta, phi, t, nu, *args, **kwargs): 242 | r""" 243 | Compute the intensity I_nu of a GRB afterglow. 244 | 245 | Utiliizes the single shell approximation described in Ryan et al 2019 246 | to compute the synchrotron emission from the forward shock of a blast 247 | wave at the specified angular coordinates, times, and frequencies. 248 | 249 | The returned intensity is that emitted by the blast wave, not that 250 | directly observed from Earth. To get the observed flux, integrate over 251 | the surface of the blast wave in the frame of the burst. 252 | 253 | .. math:: 254 | F_\nu = \int \! d\Omega\ I_\nu 255 | 256 | Angular coordinates are in a spherical coordinate system, centered on the 257 | burst, with z-axis aligned on the jet axis. 258 | 259 | ``intensity`` takes many model-dependent parameters which are specified 260 | via keyword arguments. It is recommended to collect these parameters in a 261 | dictionary ``Z`` and call fluxDensity() by:: 262 | 263 | Inu = intensity(theta, phi, t, nu, **Z) 264 | 265 | Alternately model parameters may be specified as positional arguments, 266 | although this interface is **not recommended** and may be deprecated in 267 | the future. To call a jetted model with positional and keyword arguments:: 268 | 269 | Inu = fluxDensity(theta, phi, t, nu, jetType, specType, thetaObs, E0, 270 | thetaCore, thetaWing, b, L0, q, ts, n0, p, epsilon_e, 271 | epsilon_B, xi_N, d_L, **Z) 272 | 273 | This is currently only implemented for jetted models. Do not use with 274 | ``jetType=jet.Spherical``. 275 | 276 | 277 | Parameters 278 | ---------- 279 | theta: array_like or scalar 280 | Polar angle from jet axis in radians. Scalar, or array of same shape 281 | as phi, t, and nu. 282 | phi: array_like or scalar 283 | Azimuthal angle around jet axis in radians. Observer is at phi = 0. 284 | Scalar, or array of same shape as theta, t, and nu. 285 | t : array_like or scalar 286 | Time since burst in observer frame, measured in seconds. 287 | nu : array_like or scalar 288 | Frequency of flux in observer frame, measured in Hz, same size as t. 289 | jetType : int 290 | Code for type of jet. Model codes are available in ``afterglowpy.jet`` 291 | and include: ``jet.TopHat``, ``jet.Cone``, ``jet.Gaussian``, 292 | ``jet.PowerLaw``, ``jet.GaussianCore``, ``jet.PowerLawCore``, and 293 | ``jet.Spherical``. 294 | specType : int 295 | Flags for type of spectrum/emissivity function. Spectrum flags are 296 | available in ``afterglowpy.jet`` and include: 297 | 298 | - ``jet.SimpleSpec`` broken power law with nu_m and nu_c 299 | (Ryan+ 2020, default), 300 | - ``jet.DeepNewtonian`` better handling of late-time emission when 301 | some electrons become non-relativistic (e.g. Sironi+ 2013), 302 | - ``jet.EpsEBar`` interpret the epsilon_e parameter as 303 | :math:`\bar{\epsilon}_e = \epsilon_e (p-2) / (p-1)` 304 | (e.g. Granot & Sari 2002), 305 | - ``jet.ICCooling`` simple inverse-compton contribution to cooling 306 | (experimental). 307 | 308 | Flags can be combined with the | operator. 309 | thetaObs : float 310 | Viewing angle in radians. Jet models only. 311 | E0: float 312 | Isotropic-equivalent energy along the jet axis in ergs. Jet models 313 | only. 314 | thetaCore: float 315 | Half opening angle of jet core in radians. Jet models only. 316 | thetaWing: float 317 | Outer truncation angle of the jet in radians. Ignored by 318 | ``jet.TopHat``, jet models only. 319 | b: float 320 | Power law index of jet angular energy distribution. Only used by 321 | ``jet.PowerLaw`` and ``jet.PowerLawCore``. 322 | n0 : float 323 | Number density of protons in circumburst medium in cm^{-3}. 324 | p : float 325 | Power law index of relativistic electron energy distribution, 326 | generally p > 2. 327 | epsilon_e : float 328 | Fraction of thermal energy in relativistic electrons, epsilon_e <= 1. 329 | epsilon_B : float 330 | Fraction of thermal energy in magnetic field, epsilon_B <= 1. 331 | xi_N : float 332 | Fraction of electrons that get accelerated, xi_N <= 1. 333 | d_L : float 334 | Luminosity distance to burst, in cm. 335 | z : float, optional 336 | Redshift of burst, defaults to 0. 337 | 338 | L0: float, optional 339 | Luminosity of energy injection, in erg/s. Default 0.0. 340 | q: float, optional 341 | Power law index of energy injection: L = L0 (t/t0)^{-q}, t0 = 1 ks. 342 | Default 0.0. 343 | ts: float, optional 344 | Time energy injection ends in burster frame, in seconds. Default 0.0. 345 | g0 : float, optional 346 | EXPERIMENTAL. Initial Lorentz factor of outflow along jet axis, 347 | defaults to -1 (unset, jet has deceleration radius 0). Do not use with 348 | jet spreading enabled. 349 | 350 | Other Parameters 351 | ---------------- 352 | 353 | spread : {True, False}, optional 354 | Whether to include jet spreading. Defaults to True. 355 | counterjet : {'True', 'False'}, optional 356 | Whether to include counterjet emission. Defaults to False. 357 | ignoreBounds : {'True', 'False'}, optional 358 | Whether to ignore built-in parameter bounds checking. 359 | tRes : int, optional 360 | Time resolution, number of points per decade in t, for shock evolution. 361 | Defaults to 1000. 362 | latRes : int, optional 363 | Lateral resolution of structured jet, number of conical sections per 364 | thetaCore-sized interval. Defaults to 5. 365 | intType : int, optional 366 | ``fluxDensity()`` parameter ignored by ``intensity()`` 367 | rtolStruct : float, optional 368 | ``fluxDensity()`` parameter ignored by ``intensity()`` 369 | rtolTheta : float, optional 370 | ``fluxDensity()`` parameter ignored by ``intensity()`` 371 | rtolPhi : float, optional 372 | ``fluxDensity()`` parameter ignored by ``intensity()`` 373 | NPhi : int, optional 374 | ``fluxDensity()`` parameter ignored by ``intensity()`` 375 | NTheta : int, optional 376 | ``fluxDensity()`` parameter ignored by ``intensity()`` 377 | 378 | Returns 379 | ------- 380 | Inu : array 381 | The specific intensity I_nu in the observer frame, same shape as 382 | theta, phi, t, and nu. 383 | 384 | Raises 385 | ------ 386 | 387 | ValueError 388 | If theta, phi, t, nu are the wrong shape or arguments take illegal 389 | values. 390 | """ 391 | 392 | argsDict = parseArgs(args, kwargs) 393 | 394 | # Check Arguments, will raise ValueError if args are bad 395 | theta, phi, t, nu = checkThetaPhiTNu(theta, phi, t, nu) 396 | 397 | jetType = argsDict['jetType'] 398 | 399 | # Check arguments have valid values, raise ValueError if not. 400 | # if ignoreBounds is True, these checks will be skipped. 401 | if jetType == jet.Spherical: 402 | checkCocoonArgs(argsDict) 403 | else: 404 | checkJetArgs(argsDict) 405 | 406 | # arguments are good, full steam ahead! 407 | 408 | z = argsDict.pop('z') if 'z' in argsDict else 0.0 409 | 410 | tz = t / (1+z) 411 | nuz = nu * (1+z) 412 | 413 | # Default spreading method 414 | if 'spread' in argsDict: 415 | if argsDict['spread'] is True: 416 | if jetType == -2 and 'thetaCoreGlobal' in argsDict: 417 | argsDict['spread'] = 8 418 | else: 419 | argsDict['spread'] = 7 420 | 421 | # Intercept background luminosities, then ignore them. 422 | _ = argsDict.pop('LR') if 'LR' in argsDict else 0.0 423 | _ = argsDict.pop('LO') if 'LO' in argsDict else 0.0 424 | _ = argsDict.pop('LX') if 'LX' in argsDict else 0.0 425 | _ = argsDict.pop('tAdd') if 'tAdd' in argsDict else 0.0 426 | 427 | Inu = np.empty(theta.shape) 428 | Inu.flat[:] = jet.intensity(theta.flat, phi.flat, tz.flat, nuz.flat, 429 | **argsDict) 430 | 431 | # K-correct the intensity 432 | # I'm only using the flux correction here, which leaves the angular 433 | # part of the intensity uncorrected. Best be careful. 434 | 435 | Inu *= 1+z 436 | 437 | return Inu 438 | 439 | 440 | def checkTNu(t, nu): 441 | # Make sure t and nu are array_like or castable to an array. 442 | t = np.atleast_1d(t) 443 | nu = np.atleast_1d(nu) 444 | 445 | # Check shapes, if scalars make into right size array 446 | if t.shape != nu.shape: 447 | if t.shape == (1, ): 448 | T = t[0] 449 | t = np.empty(nu.shape) 450 | t[:] = T 451 | elif nu.shape == (1, ): 452 | NU = nu[0] 453 | nu = np.empty(t.shape) 454 | nu[:] = NU 455 | else: 456 | raise ValueError("t and nu must be same shape or scalars") 457 | 458 | return t, nu 459 | 460 | 461 | def checkThetaPhiTNu(theta, phi, t, nu): 462 | # Make sure args are array_like or castable to an array. 463 | theta = np.atleast_1d(theta) 464 | phi = np.atleast_1d(phi) 465 | t = np.atleast_1d(t) 466 | nu = np.atleast_1d(nu) 467 | 468 | shape = (1, ) 469 | if shape != theta.shape: 470 | shape = theta.shape 471 | elif shape != phi.shape: 472 | shape = phi.shape 473 | elif shape != t.shape: 474 | shape = t.shape 475 | else: 476 | shape = nu.shape 477 | 478 | if theta.shape != shape: 479 | if theta.shape == (1, ): 480 | TH = theta[0] 481 | theta = np.empty(shape) 482 | theta[:] = TH 483 | else: 484 | msg = "theta must be scalar or same shape as phi, t, nu" 485 | raise ValueError(msg) 486 | 487 | if phi.shape != shape: 488 | if phi.shape == (1, ): 489 | PH = phi[0] 490 | phi = np.empty(shape) 491 | phi[:] = PH 492 | else: 493 | msg = "phi must be scalar or same shape as theta, t, nu" 494 | raise ValueError(msg) 495 | 496 | if t.shape != shape: 497 | if t.shape == (1, ): 498 | T = t[0] 499 | t = np.empty(shape) 500 | t[:] = T 501 | else: 502 | msg = "t must be scalar or same shape as theta, phi, nu" 503 | raise ValueError(msg) 504 | 505 | if nu.shape != shape: 506 | if nu.shape == (1, ): 507 | NU = nu[0] 508 | nu = np.empty(shape) 509 | nu[:] = NU 510 | else: 511 | msg = "nu must be scalar or same shape as theta, phi, t" 512 | raise ValueError(msg) 513 | 514 | return theta, phi, t, nu 515 | 516 | 517 | def checkJetArgs(argsDict): 518 | 519 | if 'ignoreBounds' in argsDict: 520 | ignore = argsDict.pop('ignoreBounds') 521 | if ignore: 522 | return 523 | 524 | jetType = argsDict['jetType'] 525 | specType = argsDict['specType'] 526 | 527 | theta_obs = argsDict['thetaObs'] 528 | E0 = argsDict['E0'] 529 | theta_c = argsDict['thetaCore'] 530 | n0 = argsDict['n0'] 531 | p = argsDict['p'] 532 | epse = argsDict['epsilon_e'] 533 | epsB = argsDict['epsilon_B'] 534 | xiN = argsDict['xi_N'] 535 | dL = argsDict['d_L'] 536 | 537 | # More-or-less universal bounds 538 | if theta_obs < 0.0 or theta_obs > np.pi: 539 | raise ValueError("theta_obs must be in [0.0, pi]") 540 | if E0 <= 0.0: 541 | raise ValueError("E0 must be positive") 542 | if jetType != jet.Cone and (theta_c <= 0.0 or theta_c > 0.5*np.pi): 543 | raise ValueError("theta_c must be in (0.0, pi/2]") 544 | if jetType == jet.Cone and (theta_c < 0.0 or theta_c > 0.5*np.pi): 545 | raise ValueError("theta_c must be in [0.0, pi/2]") 546 | if n0 <= 0.0: 547 | raise ValueError("n0 must be positive") 548 | if (specType & jet.EpsEBar) == 0 and p <= 2.0: 549 | raise ValueError("p must be in (2, inf)") 550 | if (specType & jet.EpsEBar) != 0 and p <= 1.0: 551 | raise ValueError("p must be in (1, inf)") 552 | if epse <= 0.0 or epse > 1.0: 553 | raise ValueError("epsilon_e must be in (0, 1]") 554 | if epsB <= 0.0 or epsB > 1.0: 555 | raise ValueError("epsilon_B must be in (0, 1]") 556 | if xiN <= 0.0 or xiN > 1.0: 557 | raise ValueError("xi_N must be in (0, 1]") 558 | if dL <= 0.0: 559 | raise ValueError("d_L must be positive") 560 | 561 | # Bounds on optional parameters 562 | 563 | if jetType != jet.TopHat: 564 | if 'thetaWing' not in argsDict: 565 | raise KeyError('This jet type requires thetaWing') 566 | else: 567 | theta_w = argsDict['thetaWing'] 568 | if (theta_w <= 0.0 or theta_w > 0.5*np.pi): 569 | raise ValueError("thetaWing must be in (0.0, pi/2]") 570 | 571 | if jetType == jet.PowerLaw or jetType == jet.PowerLawCore: 572 | if 'b' not in argsDict: 573 | raise KeyError('This jet type requires b') 574 | else: 575 | b = argsDict['b'] 576 | if (b <= 0.0): 577 | raise ValueError("b must be positive") 578 | 579 | # Energy Injection 580 | if 'L0' in argsDict: 581 | L0 = argsDict['L0'] 582 | if L0 < 0.0: 583 | raise ValueError("L0 must be non-negative") 584 | if 'ts' in argsDict: 585 | ts = argsDict['ts'] 586 | if ts < 0.0: 587 | raise ValueError("ts must be non-negative") 588 | 589 | # Additional Luminosity 590 | if 'LR' in argsDict: 591 | LR = argsDict['LR'] 592 | if LR < 0.0: 593 | raise ValueError("LR must be non-negative") 594 | if 'LO' in argsDict: 595 | LO = argsDict['LO'] 596 | if LO < 0.0: 597 | raise ValueError("LO must be non-negative") 598 | if 'LX' in argsDict: 599 | LX = argsDict['LX'] 600 | if LX < 0.0: 601 | raise ValueError("LX must be non-negative") 602 | 603 | if 'z' in argsDict: 604 | if argsDict['z'] < 0.0: 605 | raise ValueError("z must be non-negative") 606 | 607 | # Model Specific bounds 608 | if jetType == jet.Cone and argsDict['thetaCore'] > argsDict['thetaWing']: 609 | raise ValueError("thetaWing must be larger than thetaCore" 610 | "for cone model") 611 | 612 | return 613 | 614 | 615 | def checkCocoonArgs(argsDict): 616 | 617 | if 'ignoreBounds' in argsDict: 618 | ignore = argsDict.pop('ignoreBounds') 619 | if ignore: 620 | return 621 | 622 | for _, x in argsDict.items(): 623 | if not np.isfinite(x): 624 | raise ValueError("All parameters must be finite") 625 | 626 | specType = argsDict['specType'] 627 | 628 | u_max = argsDict['uMax'] 629 | u_min = argsDict['uMin'] 630 | Er = argsDict['Er'] 631 | MFast = argsDict['MFast_solar'] 632 | n0 = argsDict['n0'] 633 | p = argsDict['p'] 634 | epse = argsDict['epsilon_e'] 635 | epsB = argsDict['epsilon_B'] 636 | xiN = argsDict['xi_N'] 637 | dL = argsDict['d_L'] 638 | 639 | if u_max <= 0.0: 640 | raise ValueError("u_max must be positive") 641 | if u_min <= 0.0: 642 | raise ValueError("u_min must be positive") 643 | if Er <= 0.0: 644 | raise ValueError("Er must be positive") 645 | if MFast <= 0.0: 646 | raise ValueError("MFast must be positive") 647 | if n0 <= 0.0: 648 | raise ValueError("n0 must be positive") 649 | if specType != 2 and p <= 2.0: 650 | raise ValueError("p must be in (2, inf)") 651 | if specType == 2 and p <= 1.0: 652 | raise ValueError("p must be in (1, inf)") 653 | if epse <= 0.0 or epse > 1.0: 654 | raise ValueError("epsilon_e must be in (0, 1]") 655 | if epsB <= 0.0 or epsB > 1.0: 656 | raise ValueError("epsilon_B must be in (0, 1]") 657 | if xiN <= 0.0 or xiN > 1.0: 658 | raise ValueError("xi_N must be in (0, 1]") 659 | if dL <= 0.0: 660 | raise ValueError("d_L must be positive") 661 | 662 | if 'z' in argsDict: 663 | if argsDict['z'] < 0.0: 664 | raise ValueError("z must be non-negative") 665 | 666 | # Energy Injection 667 | if 'L0' in argsDict: 668 | L0 = argsDict['L0'] 669 | if L0 < 0.0: 670 | raise ValueError("L0 must be non-negative") 671 | if 'ts' in argsDict: 672 | ts = argsDict['ts'] 673 | if ts < 0.0: 674 | raise ValueError("ts must be non-negative") 675 | 676 | # Additional Luminosity 677 | if 'LR' in argsDict: 678 | LR = argsDict['LR'] 679 | if LR < 0.0: 680 | raise ValueError("LR must be non-negative") 681 | if 'LO' in argsDict: 682 | LO = argsDict['LO'] 683 | if LO < 0.0: 684 | raise ValueError("LO must be non-negative") 685 | if 'LX' in argsDict: 686 | LX = argsDict['LX'] 687 | if LX < 0.0: 688 | raise ValueError("LX must be non-negative") 689 | 690 | return 691 | 692 | 693 | def parseArgs(args, kwargs): 694 | r""" 695 | Parse the arguments to fluxDensity() or intensity(). Supports both 696 | positional and keyword arguments for now. 697 | """ 698 | 699 | argsDict = kwargs.copy() 700 | 701 | # If there were no extra positional arguments, things are easy. 702 | if len(args) == 0: 703 | return argsDict 704 | 705 | # Recommend users not to do this 706 | warnings.warn("Positional argument inferface to afterglowpy" 707 | + " may not be supported in the future. Use keyword" 708 | + " arguments instead, see documentation for details.", 709 | FutureWarning) 710 | 711 | # Now for the fun part 712 | 713 | jetKeys = ['jetType', 'specType', 'thetaObs', 'E0', 'thetaCore', 714 | 'thetaWing', 'b', 'L0', 'q', 'ts', 'n0', 'p', 'epsilon_e', 715 | 'epsilon_B', 'xi_N', 'd_L', 'g0', 'LR', 'LO', 'LX', 'tAdd', 'z', 716 | 'envType', 'R0Env', 'kEnv', 'rho1Env'] 717 | sphKeys = ['jetType', 'specType', 'uMax', 'uMin', 'Er', 718 | 'k', 'MFast_solar', 'L0', 'q', 'ts', 'n0', 'p', 'epsilon_e', 719 | 'epsilon_B', 'xi_N', 'd_L', 'g0', 'LR', 'LO', 'LX', 'tAdd', 'z', 720 | 'envType', 'R0Env', 'kEnv', 'rho1Env'] 721 | 722 | jetType = args[0] 723 | 724 | if jetType == jet.Spherical: 725 | argsDict.update(zip(sphKeys, args)) 726 | else: 727 | argsDict.update(zip(jetKeys, args)) 728 | 729 | return argsDict 730 | -------------------------------------------------------------------------------- /afterglowpy/integrate.h: -------------------------------------------------------------------------------- 1 | #ifndef AFTERGLOWPY_INTEGRATE 2 | #define AFTERGLOWPY_INTEGRATE 3 | 4 | #include "interval.h" 5 | 6 | /* 7 | * Various routines for integrating 1D functions. 8 | * trap() and simp() are fixed stencil implementations of the Trapezoid Rule 9 | * and Simpson's Rule respectively. 10 | * 11 | * romb() is an adaptive Romberg integrator. 12 | * 13 | * trap_adapt() and simp_adapt() are globally adaptive integrators based on 14 | * the Trapezoid and Simpson's rule, respectively. They successively bisect 15 | * the integration domain into subintervals, prioritizing the subintervals 16 | * with largest (estimated) error, until the total absolute error estimate 17 | * is within tolerance. 18 | */ 19 | 20 | /* 21 | * Integration routines 22 | */ 23 | double trap(double (*f)(double, void *), double xa, double xb, int N, 24 | void *args, int (*errf)(void *)); 25 | double simp(double (*f)(double, void *), double xa, double xb, int N, 26 | void *args, int (*errf)(void *)); 27 | double romb(double (*f)(double, void *), double xa, double xb, int N, 28 | double atol, double rtol, void *args, int *Neval, double *eps, 29 | int verbose, int (*errf)(void *), double *pfa, double *pfb); 30 | 31 | double trap_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 32 | double atol, double rtol, void *args, int *Neval, 33 | double *eps, struct Mesh3 *mout, int verbose, 34 | int (*errf)(void *), double *pfa, double *pfb); 35 | double simp_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 36 | double atol, double rtol, void *args, int *Neval, 37 | double *eps, struct Mesh5 *mout, int verbose, 38 | int (*errf)(void *), double *pfa, double *pfb); 39 | double trapNL_adapt(double (*f)(double, void *), double xa, double xb,int Nmax, 40 | double atol, double rtol, void *args, int *Neval, 41 | double *eps, struct Mesh5 *mout, int verbose, 42 | int (*errf)(void *), double *pfa, double *pfb); 43 | double hybrid_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 44 | double atol, double rtol, void *args, int *Neval, 45 | double *eps, int verbose, int (*errf)(void *), 46 | double *pfa, double *pfb); 47 | double cadre_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 48 | double atol, double rtol, void *args, int *Neval, 49 | double *eps, int verbose, int (*errf)(void *), 50 | double *pfa, double *pfb, Mesh9 *mout); 51 | double gk49_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 52 | double atol, double rtol, void *args, int *Neval, 53 | double *eps, int verbose, int (*errf)(void *)); 54 | double gk715_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 55 | double atol, double rtol, void *args, int *Neval, 56 | double *eps, int verbose, int (*errf)(void *)); 57 | double gk1021_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 58 | double atol, double rtol, void *args, int *Neval, 59 | double *eps, int verbose, int (*errf)(void *)); 60 | 61 | /* 62 | * Internal functions for trap_adapt and simp_adapt. 63 | */ 64 | 65 | double m_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 66 | int (*processInterval)(double (*f)(double, void*), void *, 67 | Interval *, int (*errf)(void *)), 68 | int (*splitInterval)(double (*f)(double, void *), void *, 69 | Interval *, Interval *, Interval *, 70 | int (*errf)(void *)), 71 | double atol, double rtol, void *args, int *Neval, 72 | double *eps, struct Mesh *mout, int verbose, 73 | int (*errf)(void *)); 74 | double m3_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 75 | int (*initInterval)(double (*f)(double, void*), void *, 76 | Interval3 *, int (*errf)(void *), 77 | double *pfa, double *pfb), 78 | int (*processInterval)(double (*f)(double, void*), void *, 79 | Interval3 *, int (*errf)(void *)), 80 | int (*splitInterval)(double (*f)(double, void *), void *, 81 | Interval3 *, Interval3 *, Interval3 *, 82 | int (*errf)(void *)), 83 | double atol, double rtol, void *args, int *Neval, 84 | double *eps, struct Mesh3 *mout, int verbose, 85 | int (*errf)(void *), double *pfa, double *pfb); 86 | double m5_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 87 | int (*initInterval)(double (*f)(double, void*), void *, 88 | Interval5 *, int (*errf)(void *), 89 | double *pfa, double *pfb), 90 | int (*processInterval)(double (*f)(double, void*), void *, 91 | Interval5 *, int (*errf)(void *)), 92 | int (*splitInterval)(double (*f)(double, void *), void *, 93 | Interval5 *, Interval5 *, Interval5 *, 94 | int (*errf)(void *)), 95 | double atol, double rtol, void *args, int *Neval, 96 | double *eps, struct Mesh5 *mout, int verbose, 97 | int (*errf)(void *), double *pfa, double *pbf); 98 | double m9_adapt(double (*f)(double, void *), double xa, double xb, int Nmax, 99 | int (*initInterval)(double (*f)(double, void*), void *, 100 | Interval9 *, int (*errf)(void *), 101 | double *pfa, double *pfb), 102 | int (*processInterval)(double (*f)(double, void*), void *, 103 | Interval9 *, int (*errf)(void *)), 104 | int (*splitInterval)(double (*f)(double, void *), void *, 105 | Interval9 *, Interval9 *, Interval9 *, 106 | int (*errf)(void *)), 107 | double atol, double rtol, void *args, int *Neval, 108 | double *eps, struct Mesh9 *mout, int verbose, 109 | int (*errf)(void *), double *pfa, double *pbf); 110 | 111 | int trapInitInterval(double (*f)(double, void *), void *args, Interval3 *i, 112 | int (*errf)(void *), double *pfa, double *pfb); 113 | int trapProcessInterval(double (*f)(double, void *), void *args, Interval3 *i, 114 | int (*errf)(void *)); 115 | int trapSplitInterval(double (*f)(double, void *), void *args, 116 | Interval3 *i0, Interval3 *i1, Interval3 *i2, 117 | int (*errf)(void *)); 118 | 119 | int simpInitInterval(double (*f)(double, void *), void *args, Interval5 *i, 120 | int (*errf)(void *), double *pfa, double *pfb); 121 | int simpProcessInterval(double (*f)(double, void *), void *args, Interval5 *i, 122 | int (*errf)(void *)); 123 | int simpSplitInterval(double (*f)(double, void *), void *args, 124 | Interval5 *i0, Interval5 *i1, Interval5 *i2, 125 | int (*errf)(void *)); 126 | 127 | int trapNLInitInterval(double (*f)(double, void *), void *args, Interval5 *i, 128 | int (*errf)(void *), double *pfa, double *pfb); 129 | int trapNLProcessInterval(double (*f)(double, void *), void *args, 130 | Interval5 *i, int (*errf)(void *)); 131 | int trapNLSplitInterval(double (*f)(double, void *), void *args, 132 | Interval5 *i0, Interval5 *i1, Interval5 *i2, 133 | int (*errf)(void *)); 134 | 135 | int cadreInitInterval(double (*f)(double, void *), void *args, Interval9 *i, 136 | int (*errf)(void *), double *pfa, double *pfb); 137 | int cadreProcessInterval(double (*f)(double, void *), void *args, 138 | Interval9 *i, int (*errf)(void *)); 139 | int cadreSplitInterval(double (*f)(double, void *), void *args, 140 | Interval9 *i0, Interval9 *i1, Interval9 *i2, 141 | int (*errf)(void *)); 142 | 143 | int gk49ProcessInterval(double (*f)(double, void *), void *args, 144 | Interval *i, int (*errf)(void *)); 145 | int gk49SplitInterval(double (*f)(double, void *), void *args, 146 | Interval *i0, Interval *i1, Interval *i2, 147 | int (*errf)(void *)); 148 | 149 | int gk715ProcessInterval(double (*f)(double, void *), void *args, 150 | Interval *i, int (*errf)(void *)); 151 | int gk715SplitInterval(double (*f)(double, void *), void *args, 152 | Interval *i0, Interval *i1, Interval *i2, 153 | int (*errf)(void *)); 154 | 155 | int gk1021ProcessInterval(double (*f)(double, void *), void *args, 156 | Interval *i, int (*errf)(void *)); 157 | int gk1021SplitInterval(double (*f)(double, void *), void *args, 158 | Interval *i0, Interval *i1, Interval *i2, 159 | int (*errf)(void *)); 160 | 161 | int gk_compute(double (*f)(double, void *), void *args, int (*errf)(void *), 162 | double c, double z0, const double xg[], const double xk[], 163 | const double wg[], const double wgk[], int ng, 164 | double *I, double *err); 165 | #endif 166 | -------------------------------------------------------------------------------- /afterglowpy/interval.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "interval.h" 4 | 5 | /* 6 | * Here are 3 helper structs for performing global adaptive integration. 7 | * Each Mesh struct is a priority queue implemented with as a simple 8 | * binary heap. Elements may be added, and the element with the worst error can 9 | * be retrieved. 10 | * 11 | * Each mesh is a container of Intervals. An Interval is a primitive struct 12 | * contains only its left and right bounds, the value of the integral and its 13 | * error over that interval. The Interval3 and Interval5 are identical to 14 | * Interval but contain 3 (or 5) extra slots to contain function memorized 15 | * function values. 16 | * 17 | * The Mesh3 and Mesh5 are priority queues of Interval3s and Interval5s 18 | * respectively. 19 | */ 20 | 21 | /*** Mesh ***/ 22 | 23 | void meshInit(struct Mesh *m) 24 | { 25 | size_t size = 4; 26 | m->totalSize = size; 27 | m->N = 0; 28 | m->heap = (struct Interval *)malloc(size * sizeof(struct Interval)); 29 | } 30 | 31 | void meshFree(struct Mesh *m) 32 | { 33 | m->totalSize = 0; 34 | m->N = 0; 35 | free(m->heap); 36 | m->heap = NULL; 37 | } 38 | 39 | void meshInsert(struct Mesh *m, struct Interval *i) 40 | { 41 | // Resize if necessary 42 | while(m->N >= m->totalSize) 43 | { 44 | m->totalSize *= 2; 45 | m->heap = (struct Interval *)realloc(m->heap, 46 | m->totalSize * sizeof(struct Interval)); 47 | } 48 | // Add interval to end of heap 49 | m->heap[m->N] = *i; 50 | (m->N)++; 51 | 52 | // Restore ordering 53 | meshHeapifyUp(m); 54 | } 55 | 56 | void meshExtract(struct Mesh *m, struct Interval *worst) 57 | { 58 | *worst = m->heap[0]; 59 | 60 | m->heap[0] = m->heap[m->N-1]; 61 | (m->N)--; 62 | 63 | meshHeapifyDown(m); 64 | 65 | } 66 | 67 | double meshTotalIntegral(struct Mesh *m) 68 | { 69 | double I = 0.0; 70 | 71 | size_t i = 0; 72 | for(i=0; iN; i++) 73 | I += m->heap[i].I; 74 | 75 | return I; 76 | } 77 | 78 | double meshTotalError(struct Mesh *m) 79 | { 80 | double err = 0.0; 81 | 82 | size_t i = 0; 83 | for(i=0; iN; i++) 84 | err += m->heap[i].err; 85 | 86 | return err; 87 | } 88 | 89 | void meshHeapifyUp(struct Mesh *m) 90 | { 91 | size_t c = m->N-1; 92 | size_t p = (c-1)/2; 93 | 94 | while(c != 0 && m->heap[p].err < m->heap[c].err) 95 | { 96 | struct Interval tempP = m->heap[p]; 97 | m->heap[p] = m->heap[c]; 98 | m->heap[c] = tempP; 99 | c = p; 100 | p = (c-1)/2; 101 | } 102 | } 103 | 104 | void meshHeapifyDown(struct Mesh *m) 105 | { 106 | size_t p = 0; 107 | size_t c1 = 2*p + 1; 108 | size_t c2 = c1 + 1; 109 | 110 | 111 | while(c1 < m->N) 112 | { 113 | //Find the child with largest error 114 | size_t c = c1; 115 | double e = m->heap[c1].err; 116 | 117 | if(c2 < m->N && m->heap[c2].err > e) 118 | { 119 | c = c2; 120 | e = m->heap[c2].err; 121 | } 122 | 123 | // If the child is already in order then we're done. 124 | if(e <= m->heap[p].err) 125 | break; 126 | 127 | // Otherwise, swap with the child. 128 | struct Interval tempP = m->heap[p]; 129 | m->heap[p] = m->heap[c]; 130 | m->heap[c] = tempP; 131 | p = c; 132 | c1 = 2*p + 1; 133 | c2 = c1 + 1; 134 | } 135 | } 136 | 137 | int meshCheck(struct Mesh *m) 138 | { 139 | size_t p; 140 | for(p=0; p<=(m->N-2)/2; p++) 141 | { 142 | size_t c1 = 2*p+1; 143 | size_t c2 = c1 + 1; 144 | 145 | if(c1 < m->N && m->heap[c1].err > m->heap[p].err) 146 | return 0; 147 | 148 | if(c2 < m->N && m->heap[c2].err > m->heap[p].err) 149 | return 0; 150 | 151 | } 152 | 153 | return 1; 154 | } 155 | 156 | void meshWrite(struct Mesh *m, char **buf) 157 | { 158 | *buf = (char *) malloc((m->N * 4 * 30 + 12) * sizeof(char)); 159 | 160 | size_t i; 161 | int c = sprintf(*buf, "%lu", m->N); 162 | for(i=0; i < m->N; i++) 163 | { 164 | struct Interval *in = &(m->heap[i]); 165 | c += sprintf(*buf + c, " %.16e %.16e %.16e %.16e", 166 | in->a, in->b, in->I, in->err); 167 | } 168 | *buf = (char *) realloc(*buf, (c+1) * sizeof(char)); 169 | } 170 | 171 | /*** Mesh 3 ***/ 172 | 173 | void mesh3Init(struct Mesh3 *m) 174 | { 175 | size_t size = 4; 176 | m->totalSize = size; 177 | m->N = 0; 178 | m->heap = (struct Interval3 *)malloc(size * sizeof(struct Interval3)); 179 | } 180 | 181 | void mesh3Free(struct Mesh3 *m) 182 | { 183 | m->totalSize = 0; 184 | m->N = 0; 185 | free(m->heap); 186 | m->heap = NULL; 187 | } 188 | 189 | void mesh3Insert(struct Mesh3 *m, struct Interval3 *i) 190 | { 191 | // Resize if necessary 192 | while(m->N >= m->totalSize) 193 | { 194 | m->totalSize *= 2; 195 | m->heap = (struct Interval3 *)realloc(m->heap, 196 | m->totalSize * sizeof(struct Interval3)); 197 | } 198 | // Add interval to end of heap 199 | m->heap[m->N] = *i; 200 | (m->N)++; 201 | 202 | // Restore ordering 203 | mesh3HeapifyUp(m); 204 | } 205 | 206 | void mesh3Extract(struct Mesh3 *m, struct Interval3 *worst) 207 | { 208 | *worst = m->heap[0]; 209 | 210 | m->heap[0] = m->heap[m->N-1]; 211 | (m->N)--; 212 | 213 | mesh3HeapifyDown(m); 214 | } 215 | 216 | double mesh3TotalIntegral(struct Mesh3 *m) 217 | { 218 | double I = 0.0; 219 | 220 | size_t i = 0; 221 | for(i=0; iN; i++) 222 | I += m->heap[i].I; 223 | 224 | return I; 225 | } 226 | 227 | double mesh3TotalError(struct Mesh3 *m) 228 | { 229 | double err = 0.0; 230 | 231 | size_t i = 0; 232 | for(i=0; iN; i++) 233 | err += m->heap[i].err; 234 | 235 | return err; 236 | } 237 | 238 | void mesh3HeapifyUp(struct Mesh3 *m) 239 | { 240 | size_t c = m->N-1; 241 | size_t p = (c-1)/2; 242 | 243 | while(c != 0 && m->heap[p].err < m->heap[c].err) 244 | { 245 | struct Interval3 tempP = m->heap[p]; 246 | m->heap[p] = m->heap[c]; 247 | m->heap[c] = tempP; 248 | c = p; 249 | p = (c-1)/2; 250 | } 251 | } 252 | 253 | void mesh3HeapifyDown(struct Mesh3 *m) 254 | { 255 | size_t p = 0; 256 | size_t c1 = 2*p + 1; 257 | size_t c2 = c1 + 1; 258 | 259 | 260 | while(c1 < m->N) 261 | { 262 | //Find the child with largest error 263 | size_t c = c1; 264 | double e = m->heap[c1].err; 265 | 266 | if(c2 < m->N && m->heap[c2].err > e) 267 | { 268 | c = c2; 269 | e = m->heap[c2].err; 270 | } 271 | 272 | // If the child is already in order then we're done. 273 | if(e <= m->heap[p].err) 274 | break; 275 | 276 | // Otherwise, swap with the child. 277 | struct Interval3 tempP = m->heap[p]; 278 | m->heap[p] = m->heap[c]; 279 | m->heap[c] = tempP; 280 | p = c; 281 | c1 = 2*p + 1; 282 | c2 = c1 + 1; 283 | } 284 | } 285 | 286 | int mesh3Check(struct Mesh3 *m) 287 | { 288 | size_t p; 289 | for(p=0; p<=(m->N-2)/2; p++) 290 | { 291 | size_t c1 = 2*p+1; 292 | size_t c2 = c1 + 1; 293 | 294 | if(c1 < m->N && m->heap[c1].err > m->heap[p].err) 295 | return 0; 296 | 297 | if(c2 < m->N && m->heap[c2].err > m->heap[p].err) 298 | return 0; 299 | 300 | } 301 | 302 | return 1; 303 | } 304 | 305 | void mesh3Write(struct Mesh3 *m, char **buf) 306 | { 307 | *buf = (char *) malloc((m->N * 4 * 30 + 12) * sizeof(char)); 308 | 309 | size_t i; 310 | int c = sprintf(*buf, "%lu", m->N); 311 | for(i=0; i < m->N; i++) 312 | { 313 | struct Interval3 *in = &(m->heap[i]); 314 | c += sprintf(*buf + c, " %.16e %.16e %.16e %.16e", 315 | in->a, in->b, in->I, in->err); 316 | } 317 | *buf = (char *) realloc(*buf, (c+1) * sizeof(char)); 318 | } 319 | 320 | /*** Mesh 5 ***/ 321 | 322 | void mesh5Init(struct Mesh5 *m) 323 | { 324 | size_t size = 4; 325 | m->totalSize = size; 326 | m->N = 0; 327 | m->heap = (struct Interval5 *)malloc(size * sizeof(struct Interval5)); 328 | } 329 | 330 | void mesh5Free(struct Mesh5 *m) 331 | { 332 | m->totalSize = 0; 333 | m->N = 0; 334 | free(m->heap); 335 | m->heap = NULL; 336 | } 337 | 338 | void mesh5Insert(struct Mesh5 *m, struct Interval5 *i) 339 | { 340 | // Resize if necessary 341 | while(m->N >= m->totalSize) 342 | { 343 | m->totalSize *= 2; 344 | m->heap = (struct Interval5 *)realloc(m->heap, 345 | m->totalSize * sizeof(struct Interval5)); 346 | } 347 | // Add interval to end of heap 348 | m->heap[m->N] = *i; 349 | (m->N)++; 350 | 351 | // Restore ordering 352 | mesh5HeapifyUp(m); 353 | } 354 | 355 | void mesh5Extract(struct Mesh5 *m, struct Interval5 *worst) 356 | { 357 | *worst = m->heap[0]; 358 | 359 | m->heap[0] = m->heap[m->N-1]; 360 | (m->N)--; 361 | 362 | mesh5HeapifyDown(m); 363 | } 364 | 365 | double mesh5TotalIntegral(struct Mesh5 *m) 366 | { 367 | double I = 0.0; 368 | 369 | size_t i = 0; 370 | for(i=0; iN; i++) 371 | I += m->heap[i].I; 372 | 373 | return I; 374 | } 375 | 376 | double mesh5TotalError(struct Mesh5 *m) 377 | { 378 | double err = 0.0; 379 | 380 | size_t i = 0; 381 | for(i=0; iN; i++) 382 | err += m->heap[i].err; 383 | 384 | return err; 385 | } 386 | 387 | void mesh5HeapifyUp(struct Mesh5 *m) 388 | { 389 | size_t c = m->N-1; 390 | size_t p = (c-1)/2; 391 | 392 | while(c != 0 && m->heap[p].err < m->heap[c].err) 393 | { 394 | struct Interval5 tempP = m->heap[p]; 395 | m->heap[p] = m->heap[c]; 396 | m->heap[c] = tempP; 397 | c = p; 398 | p = (c-1)/2; 399 | } 400 | } 401 | 402 | void mesh5HeapifyDown(struct Mesh5 *m) 403 | { 404 | size_t p = 0; 405 | size_t c1 = 2*p + 1; 406 | size_t c2 = c1 + 1; 407 | 408 | 409 | while(c1 < m->N) 410 | { 411 | //Find the child with largest error 412 | size_t c = c1; 413 | double e = m->heap[c1].err; 414 | 415 | if(c2 < m->N && m->heap[c2].err > e) 416 | { 417 | c = c2; 418 | e = m->heap[c2].err; 419 | } 420 | 421 | // If the child is already in order then we're done. 422 | if(e <= m->heap[p].err) 423 | break; 424 | 425 | // Otherwise, swap with the child. 426 | struct Interval5 tempP = m->heap[p]; 427 | m->heap[p] = m->heap[c]; 428 | m->heap[c] = tempP; 429 | p = c; 430 | c1 = 2*p + 1; 431 | c2 = c1 + 1; 432 | } 433 | } 434 | 435 | int mesh5Check(struct Mesh5 *m) 436 | { 437 | size_t p; 438 | for(p=0; p<=(m->N-2)/2; p++) 439 | { 440 | size_t c1 = 2*p+1; 441 | size_t c2 = c1 + 1; 442 | 443 | if(c1 < m->N && m->heap[c1].err > m->heap[p].err) 444 | return 0; 445 | 446 | if(c2 < m->N && m->heap[c2].err > m->heap[p].err) 447 | return 0; 448 | 449 | } 450 | 451 | return 1; 452 | } 453 | 454 | void mesh5Write(struct Mesh5 *m, char **buf) 455 | { 456 | *buf = (char *) malloc((m->N * 4 * 30 + 12) * sizeof(char)); 457 | 458 | size_t i; 459 | int c = sprintf(*buf, "%lu", m->N); 460 | for(i=0; i < m->N; i++) 461 | { 462 | struct Interval5 *in = &(m->heap[i]); 463 | c += sprintf(*buf + c, " %.16e %.16e %.16e %.16e", 464 | in->a, in->b, in->I, in->err); 465 | } 466 | *buf = (char *) realloc(*buf, (c+1) * sizeof(char)); 467 | } 468 | 469 | /*** Mesh 9 ***/ 470 | 471 | void mesh9Init(struct Mesh9 *m) 472 | { 473 | size_t size = 4; 474 | m->totalSize = size; 475 | m->N = 0; 476 | m->heap = (struct Interval9 *)malloc(size * sizeof(struct Interval9)); 477 | } 478 | 479 | void mesh9Free(struct Mesh9 *m) 480 | { 481 | m->totalSize = 0; 482 | m->N = 0; 483 | if(m->heap != NULL) 484 | { 485 | free(m->heap); 486 | m->heap = NULL; 487 | } 488 | } 489 | 490 | void mesh9Insert(struct Mesh9 *m, struct Interval9 *i) 491 | { 492 | // Resize if necessary 493 | while(m->N >= m->totalSize) 494 | { 495 | m->totalSize *= 2; 496 | m->heap = (struct Interval9 *)realloc(m->heap, 497 | m->totalSize * sizeof(struct Interval9)); 498 | } 499 | // Add interval to end of heap 500 | m->heap[m->N] = *i; 501 | (m->N)++; 502 | 503 | // Restore ordering 504 | mesh9HeapifyUp(m); 505 | } 506 | 507 | void mesh9Extract(struct Mesh9 *m, struct Interval9 *worst) 508 | { 509 | *worst = m->heap[0]; 510 | 511 | m->heap[0] = m->heap[m->N-1]; 512 | (m->N)--; 513 | 514 | mesh9HeapifyDown(m); 515 | } 516 | 517 | double mesh9TotalIntegral(struct Mesh9 *m) 518 | { 519 | double I = 0.0; 520 | 521 | size_t i = 0; 522 | for(i=0; iN; i++) 523 | I += m->heap[i].I; 524 | 525 | return I; 526 | } 527 | 528 | double mesh9TotalError(struct Mesh9 *m) 529 | { 530 | double err = 0.0; 531 | 532 | size_t i = 0; 533 | for(i=0; iN; i++) 534 | err += m->heap[i].err; 535 | 536 | return err; 537 | } 538 | 539 | void mesh9HeapifyUp(struct Mesh9 *m) 540 | { 541 | size_t c = m->N-1; 542 | size_t p = (c-1)/2; 543 | 544 | while(c != 0 && m->heap[p].err < m->heap[c].err) 545 | { 546 | struct Interval9 tempP = m->heap[p]; 547 | m->heap[p] = m->heap[c]; 548 | m->heap[c] = tempP; 549 | c = p; 550 | p = (c-1)/2; 551 | } 552 | } 553 | 554 | void mesh9HeapifyDown(struct Mesh9 *m) 555 | { 556 | size_t p = 0; 557 | size_t c1 = 2*p + 1; 558 | size_t c2 = c1 + 1; 559 | 560 | 561 | while(c1 < m->N) 562 | { 563 | //Find the child with largest error 564 | size_t c = c1; 565 | double e = m->heap[c1].err; 566 | 567 | if(c2 < m->N && m->heap[c2].err > e) 568 | { 569 | c = c2; 570 | e = m->heap[c2].err; 571 | } 572 | 573 | // If the child is already in order then we're done. 574 | if(e <= m->heap[p].err) 575 | break; 576 | 577 | // Otherwise, swap with the child. 578 | struct Interval9 tempP = m->heap[p]; 579 | m->heap[p] = m->heap[c]; 580 | m->heap[c] = tempP; 581 | p = c; 582 | c1 = 2*p + 1; 583 | c2 = c1 + 1; 584 | } 585 | } 586 | 587 | int mesh9Check(struct Mesh9 *m) 588 | { 589 | size_t p; 590 | if(m->N <= 1) 591 | return 1; 592 | 593 | for(p=0; p<=(m->N-2)/2; p++) 594 | { 595 | size_t c1 = 2*p+1; 596 | size_t c2 = c1 + 1; 597 | 598 | if(c1 < m->N && m->heap[c1].err > m->heap[p].err) 599 | return 0; 600 | 601 | if(c2 < m->N && m->heap[c2].err > m->heap[p].err) 602 | return 0; 603 | 604 | } 605 | 606 | return 1; 607 | } 608 | 609 | void mesh9Write(struct Mesh9 *m, char **buf) 610 | { 611 | *buf = (char *) malloc((m->N * 4 * 30 + 12) * sizeof(char)); 612 | 613 | size_t i; 614 | int c = sprintf(*buf, "%lu", m->N); 615 | for(i=0; i < m->N; i++) 616 | { 617 | struct Interval9 *in = &(m->heap[i]); 618 | c += sprintf(*buf + c, " %.16e %.16e %.16e %.16e", 619 | in->a, in->b, in->I, in->err); 620 | } 621 | *buf = (char *) realloc(*buf, (c+1) * sizeof(char)); 622 | } 623 | 624 | void interval9Write(struct Interval9 *i, FILE *stream) 625 | { 626 | fprintf(stream, "(%.3le, %.3le) %.12le +/- %.3le %d\n", 627 | i->a, i->b, i->I, i->err, i->refinement); 628 | fprintf(stream, " [%.6le %.6le %.6le %.6le %.6le %.6le" 629 | " %.6le %.6le %.6le]\n", i->fa, i->fll, i->fl, i->flr, 630 | i->fm, i->frl, i->fr, i->frr, i->fb); 631 | } 632 | -------------------------------------------------------------------------------- /afterglowpy/interval.h: -------------------------------------------------------------------------------- 1 | #ifndef AFTERGLOWPY_INTERVAL 2 | #define AFTERGLOWPY_INTERVAL 3 | 4 | /* 5 | * Here are 3 helper structs for performing global adaptive integration. 6 | * Each Mesh struct is a priority queue implemented with as a simple 7 | * binary heap. Elements may be added, and the element with the worst error can 8 | * be retrieved. 9 | * 10 | * Each mesh is a container of Intervals. An Interval is a primitive struct 11 | * contains only its left and right bounds, the value of the integral and its 12 | * error over that interval. The Interval3 and Interval5 are identical to 13 | * Interval but contain 3 (or 5) extra slots to contain function memorized 14 | * function values. 15 | * 16 | * The Mesh3 and Mesh5 are priority queues of Interval3s and Interval5s 17 | * respectively. 18 | */ 19 | 20 | #undef I 21 | 22 | struct Interval 23 | { 24 | double a; 25 | double b; 26 | double I; 27 | double err; 28 | }; 29 | typedef struct Interval Interval; 30 | 31 | struct Mesh 32 | { 33 | size_t totalSize; 34 | size_t N; 35 | struct Interval *heap; 36 | }; 37 | typedef struct Mesh Mesh; 38 | 39 | struct Interval3 40 | { 41 | double a; 42 | double b; 43 | double I; 44 | double err; 45 | double fa; 46 | double fb; 47 | double fm; 48 | }; 49 | typedef struct Interval3 Interval3; 50 | 51 | struct Mesh3 52 | { 53 | size_t totalSize; 54 | size_t N; 55 | struct Interval3 *heap; 56 | }; 57 | typedef struct Mesh3 Mesh3; 58 | 59 | struct Interval5 60 | { 61 | double a; 62 | double b; 63 | double I; 64 | double err; 65 | double fa; 66 | double fb; 67 | double fl; 68 | double fm; 69 | double fr; 70 | }; 71 | typedef struct Interval5 Interval5; 72 | 73 | struct Mesh5 74 | { 75 | size_t totalSize; 76 | size_t N; 77 | struct Interval5 *heap; 78 | }; 79 | typedef struct Mesh5 Mesh5; 80 | 81 | struct Interval9 82 | { 83 | double a; 84 | double b; 85 | double I; 86 | double err; 87 | double fa; 88 | double fll; 89 | double fl; 90 | double flr; 91 | double fm; 92 | double frl; 93 | double fr; 94 | double frr; 95 | double fb; 96 | int refinement; 97 | }; 98 | typedef struct Interval9 Interval9; 99 | 100 | struct Mesh9 101 | { 102 | size_t totalSize; 103 | size_t N; 104 | struct Interval9 *heap; 105 | }; 106 | typedef struct Mesh9 Mesh9; 107 | 108 | void meshInit(struct Mesh *m); 109 | void meshFree(struct Mesh *m); 110 | void meshInsert(struct Mesh *m, struct Interval *i); 111 | void meshExtract(struct Mesh *m, struct Interval *worst); 112 | double meshTotalIntegral(struct Mesh *m); 113 | double meshTotalError(struct Mesh *m); 114 | void meshHeapifyUp(struct Mesh *m); 115 | void meshHeapifyDown(struct Mesh *m); 116 | int meshCheck(struct Mesh *m); 117 | void meshWrite(struct Mesh *m, char **buf); 118 | 119 | void mesh3Init(struct Mesh3 *m); 120 | void mesh3Free(struct Mesh3 *m); 121 | void mesh3Insert(struct Mesh3 *m, struct Interval3 *i); 122 | void mesh3Extract(struct Mesh3 *m, struct Interval3 *worst); 123 | double mesh3TotalIntegral(struct Mesh3 *m); 124 | double mesh3TotalError(struct Mesh3 *m); 125 | void mesh3HeapifyUp(struct Mesh3 *m); 126 | void mesh3HeapifyDown(struct Mesh3 *m); 127 | int mesh3Check(struct Mesh3 *m); 128 | void mesh3Write(struct Mesh3 *m, char **buf); 129 | 130 | void mesh5Init(struct Mesh5 *m); 131 | void mesh5Free(struct Mesh5 *m); 132 | void mesh5Insert(struct Mesh5 *m, struct Interval5 *i); 133 | void mesh5Extract(struct Mesh5 *m, struct Interval5 *worst); 134 | double mesh5TotalIntegral(struct Mesh5 *m); 135 | double mesh5TotalError(struct Mesh5 *m); 136 | void mesh5HeapifyUp(struct Mesh5 *m); 137 | void mesh5HeapifyDown(struct Mesh5 *m); 138 | int mesh5Check(struct Mesh5 *m); 139 | void mesh5Write(struct Mesh5 *m, char **buf); 140 | 141 | void mesh9Init(struct Mesh9 *m); 142 | void mesh9Free(struct Mesh9 *m); 143 | void mesh9Insert(struct Mesh9 *m, struct Interval9 *i); 144 | void mesh9Extract(struct Mesh9 *m, struct Interval9 *worst); 145 | double mesh9TotalIntegral(struct Mesh9 *m); 146 | double mesh9TotalError(struct Mesh9 *m); 147 | void mesh9HeapifyUp(struct Mesh9 *m); 148 | void mesh9HeapifyDown(struct Mesh9 *m); 149 | int mesh9Check(struct Mesh9 *m); 150 | void mesh9Write(struct Mesh9 *m, char **buf); 151 | 152 | void interval9Write(struct Interval9 *i, FILE *stream); 153 | #endif 154 | -------------------------------------------------------------------------------- /afterglowpy/offaxis_struct.h: -------------------------------------------------------------------------------- 1 | #ifndef AFTERGLOWPY_STRUCT 2 | #define AFTERGLOWPY_STRUCT 3 | 4 | // offaxis.h 5 | 6 | #include 7 | #include 8 | #include 9 | #include "integrate.h" 10 | #include "interval.h" 11 | 12 | #define ERR_CHK_VOID(pars) if(pars->error){ return;} 13 | #define ERR_CHK_INT(pars) if(pars->error){ return 0;} 14 | #define ERR_CHK_DBL(pars) if(pars->error){ return 0.0;} 15 | #define MSG_LEN 4096 16 | #define DUMP_MSG_LEN_MAX 16384 //overkill: 200 lines * 80c per line = 16000 17 | 18 | #ifndef M_PI 19 | #define M_PI 3.14159265358979323846 20 | #endif 21 | 22 | // some physical and mathematical constants 23 | #define PI 3.14159265358979323846 24 | #define v_light 2.99792458e10 // speed of light in cm / s 25 | #define invv_light 3.335640952e-11 // inverse speed of light s / cm 26 | #define m_e 9.1093897e-28 // electron mass in g 27 | #define m_p 1.6726231e-24 // proton mass in g 28 | #define invm_e 1.097768383e27 // inverse electron mass in 1/g 29 | #define invm_p 5.978633202e23 // inverse proton mass in 1/g 30 | #define h_planck 6.6260755e-27 // Planck's constant in erg / s 31 | #define h_bar 1.05457266e-27 // Planck's constant / 2 PI in erg /s 32 | #define k_B 1.380658e-16 // Boltzmann's constant in erg / K 33 | #define e_e 4.803e-10 // electron charge in Gaussian cgs units 34 | #define sigma_T 6.65e-25 // Thomson cross section free electron cm^2 35 | #define cgs2mJy 1e26 // quantity in cgs to mJy 36 | #define mJy2cgs 1e-26 // quantity in mJy to cgs 37 | #define deg2rad 0.017453292 // quantity in degrees to radians 38 | #define rad2deg 57.29577951 // quantity in radians to degrees 39 | #define sec2day 0.000011574 // quantity in seconds to days 40 | #define day2sec 86400 // quantity in days to seconds 41 | #define parsec 3.0857e18 // quantity in parsec to cm 42 | #define Hz2eV 4.13566553853599E-15 43 | #define eV2Hz 2.417991e+14 44 | 45 | #define _cone -2 46 | #define _tophat -1 47 | #define _Gaussian 0 48 | #define _powerlaw_core 1 //has a core as well 49 | #define _Gaussian_core 2 // has a core as well 50 | #define _spherical 3 51 | #define _powerlaw 4 52 | #define _exponential 5 53 | #define _twocomponent 6 54 | #define _exponential2 7 55 | 56 | #define SIMPLE_SPEC 0 57 | #define IC_COOLING_FLAG 1 58 | #define EPS_E_BAR_FLAG 2 59 | #define SSA_SMOOTH_FLAG 4 60 | #define SSA_SHARP_FLAG 8 61 | #define NO_COOLING_FLAG 16 62 | #define DEEP_NEWTONIAN_FLAG 32 63 | #define BULK_BM_FLAG 64 64 | #define FIXED_PL_FLAG 128 65 | 66 | enum{INT_TRAP_FIXED, INT_TRAP_ADAPT, INT_SIMP_FIXED, INT_SIMP_ADAPT, 67 | INT_ROMB_ADAPT, INT_TRAP_NL, INT_HYBRID, INT_CADRE, 68 | INT_GK49_ADAPT, INT_GK715_ADAPT, INT_GK1021_ADAPT, 69 | INT_UNDEFINED}; 70 | 71 | enum{GAMMA_INF, GAMMA_FLAT, GAMMA_EVENMASS, GAMMA_STRUCT}; 72 | 73 | enum{MOM_0, MOM_X, MOM_Y, MOM_Z, MOM_XX, MOM_YY, MOM_ZZ, 74 | MOM_XY, MOM_YZ, MOM_XZ}; 75 | 76 | struct fluxParams 77 | { 78 | double theta; 79 | double phi; 80 | double cp; 81 | double sp; 82 | double ct; 83 | double st; 84 | double cto; 85 | double sto; 86 | 87 | double theta_obs; 88 | double t_obs; 89 | double nu_obs; 90 | double d_L; 91 | long moment; 92 | 93 | double E_iso; 94 | double n_0; 95 | double g_init; 96 | 97 | double p; 98 | double epsilon_E; 99 | double epsilon_B; 100 | double ksi_N; 101 | 102 | double theta_h; 103 | double E_iso_core; 104 | double theta_core; 105 | double theta_wing; 106 | double b; 107 | double E_tot; 108 | double g_core; 109 | double E_core_global; 110 | double theta_core_global; 111 | 112 | int envType; 113 | double R0_env; 114 | double k_env; 115 | double rho1_env; 116 | 117 | double L0_inj; 118 | double q_inj; 119 | double t0_inj; 120 | double ts_inj; 121 | 122 | double current_theta_cone_hi; 123 | double current_theta_cone_low; 124 | double current_theta_b; 125 | double current_theta_a; 126 | double theta_obs_cur; 127 | int tRes; 128 | int latRes; 129 | int spread; 130 | int counterjet; 131 | 132 | int int_type; 133 | double rtol_struct; 134 | double rtol_theta; 135 | double rtol_phi; 136 | int nmax_theta; 137 | int nmax_phi; 138 | 139 | double atol_theta; 140 | 141 | double Rt0; 142 | double Rt1; 143 | double ta; 144 | double tb; 145 | 146 | double C_BMsqrd; 147 | double C_STsqrd; 148 | 149 | double t_NR; 150 | int cur_entry; 151 | 152 | double *t_table; 153 | double *R_table; 154 | double *u_table; 155 | double *th_table; 156 | double *mu_table; 157 | double *cth_table; 158 | double *sth_table; 159 | int table_entries; 160 | 161 | double *t_table_inner; 162 | double *R_table_inner; 163 | double *u_table_inner; 164 | double *th_table_inner; 165 | double *mu_table_inner; 166 | double *cth_table_inner; 167 | double *sth_table_inner; 168 | int table_entries_inner; 169 | 170 | int idx_mu_neg1; 171 | int idx_mu_pos1; 172 | int idx_mu_neg1_inner; 173 | int idx_mu_pos1_inner; 174 | 175 | Mesh9 phi_mesh; 176 | Mesh9 theta_mesh; 177 | 178 | int spec_type; 179 | int gamma_type; 180 | 181 | double (*f_E)(double, void *); 182 | 183 | double *mask; 184 | int nmask; 185 | 186 | long nevals; 187 | 188 | int error; 189 | char *error_msg; 190 | }; 191 | 192 | 193 | double dmin(const double a, const double b); 194 | 195 | 196 | double f_E_tophat(double theta, void *params); 197 | double f_E_Gaussian(double theta, void *params); 198 | double f_E_powerlaw(double theta, void *params); 199 | double f_E_twocomponent(double theta, void *params); 200 | double f_E_exponential(double theta, void *params); 201 | double f_Etot_tophat(void *params); 202 | double f_Etot_Gaussian(void *params); 203 | double f_Etot_powerlaw(void *params); 204 | 205 | void make_R_table(struct fluxParams *pars); 206 | void make_mu_table(struct fluxParams *pars); 207 | double check_t_e(double t_e, double mu, double t_obs, double *mu_table, int N); 208 | int searchSorted(double x, const double *arr, int N); 209 | double interpolateLin(int a, int b, double x, double *X, double *Y, int N); 210 | double interpolateLog(int a, int b, double x, double *X, double *Y, int N); 211 | double find_jet_edge_old(double phi, double cto, double sto, double theta0, 212 | const double *a_mu, const double *a_thj, int N, 213 | int idx_mu_neg1, int idx_mu_pos1, 214 | const double *a_cthj, const double *a_sthj); 215 | double find_jet_edge(double phi, double cto, double sto, double theta0, 216 | const double *a_mu, const double *a_thj, int N, 217 | int idx_mu_neg1, int idx_mu_pos1, 218 | const double *a_cthj, const double *a_sthj); 219 | double costheta_integrand(double a_theta, void* params); // inner integral 220 | double phi_integrand(double a_phi, void* params); // outer integral 221 | double emissivity(double nu, double R, double mu, double te, 222 | double u, double us, double rho0, double Msw, double p, 223 | double epse, double epsB, double ksiN, 224 | int specType); //emissivity of 225 | // a zone. 226 | 227 | void calc_absorption_length(double R, double mu, double delta, 228 | double betaS, double uS, 229 | double *length_back, double *length_front); 230 | double absorption_integral(double Rb, double dR, double taua, double taub, 231 | int order); 232 | double absorption_integral_core(double a, double b, int order); 233 | 234 | double flux(struct fluxParams *pars, double atol); // determine flux for a given t_obs 235 | 236 | double flux_cone(double t_obs, double nu_obs, long moment, 237 | double E_iso, double theta_h, 238 | double theta_cone_low, double theta_cone_hi, 239 | double atol, struct fluxParams *pars); 240 | double intensity(double theta, double phi, double tobs, double nuobs, 241 | double theta_obs, double theta_cone_hi, double theta_cone_low, 242 | struct fluxParams *pars); 243 | void shockVals(double theta, double phi, double tobs, 244 | double *t, double *R, double *u, double *thj, 245 | double theta_obs, double theta_cone_hi, double theta_cone_low, 246 | struct fluxParams *pars); 247 | void intensity_cone(double *theta, double *phi, double *t, double *nu, 248 | double *I, int N, double E_iso_core, 249 | double theta_h_core, double theta_h_wing, 250 | struct fluxParams *pars); 251 | void intensity_struct(double *theta, double *phi, double *t, double *nu, 252 | double *I, int N, 253 | double E_iso_core, 254 | double theta_h_core, double theta_h_wing, 255 | int res_cones, double (*f_E)(double,void *), 256 | struct fluxParams *pars); 257 | void intensity_structCore(double *theta, double *phi, double *t, double *nu, 258 | double *I, int N, 259 | double E_iso_core, 260 | double theta_h_core, double theta_h_wing, 261 | int res_cones, double (*f_E)(double,void *), 262 | struct fluxParams *pars); 263 | void shockVals_cone(double *theta, double *phi, double *tobs, 264 | double *t, double *R, double *u, double *thj, int N, 265 | double E_iso_core, double theta_h_core, double theta_h_wing, 266 | struct fluxParams *pars); 267 | void shockVals_struct(double *theta, double *phi, double *tobs, 268 | double *t, double *R, double *u, double *thj, int N, 269 | double E_iso_core, 270 | double theta_h_core, double theta_h_wing, 271 | int res_cones, double (*f_E)(double,void *), 272 | struct fluxParams *pars); 273 | void shockVals_structCore(double *theta, double *phi, double *tobs, 274 | double *t, double *R, double *u, double *thj, int N, 275 | double E_iso_core, 276 | double theta_h_core, double theta_h_wing, 277 | int res_cones, double (*f_E)(double,void *), 278 | struct fluxParams *pars); 279 | void lc_tophat(double *t, double *nu, double *F, long *moment, int Nt, 280 | double E_iso, double theta_h, struct fluxParams *pars); 281 | void lc_cone(double *t, double *nu, double *F, long *moment, int Nt, 282 | double E_iso, double theta_h, double theta_wing, 283 | struct fluxParams *pars); 284 | void lc_struct(double *t, double *nu, double *F, long *moment, int Nt, 285 | double E_iso_core, 286 | double theta_h_core, double theta_h_wing, 287 | double *theta_c_arr, double *E_iso_arr, 288 | int res_cones, double (*f_E)(double,void *), 289 | struct fluxParams *pars); 290 | void lc_structCore(double *t, double *nu, double *F, long *moment, int Nt, 291 | double E_iso_core, 292 | double theta_h_core, double theta_h_wing, 293 | double *theta_c_arr, double *E_iso_arr, 294 | int res_cones, double (*f_E)(double,void *), 295 | struct fluxParams *pars); 296 | 297 | void calc_flux_density(int jet_type, int spec_type, 298 | double *t, double *nu, double *Fnu, long *moment, 299 | int N, struct fluxParams *fp); 300 | void calc_intensity(int jet_type, int spec_type, double *theta, double *phi, 301 | double *t, double *nu, double *Inu, int N, 302 | struct fluxParams *fp); 303 | void calc_shockVals(int jet_type, double *theta, double *phi, double *tobs, 304 | double *t, double *R, double *u, double *thj, int N, 305 | struct fluxParams *fp); 306 | 307 | void setup_fluxParams(struct fluxParams *pars, 308 | double d_L, 309 | double theta_obs, 310 | double E_iso_core, double theta_core, double theta_wing, 311 | double b, 312 | double L0_inj, double q_inj, double t0_inj, double ts_inj, 313 | double n_0, 314 | double p, 315 | double epsilon_E, 316 | double epsilon_B, 317 | double ksi_N, 318 | double g0, 319 | int envType, double R0_env, double k_env, double rho1_env, 320 | double E_core_global, 321 | double theta_core_global, 322 | double ta, double tb, 323 | int tRes, int latRes, int int_type, 324 | double rtol_struct, double rtol_phi, double rtol_theta, 325 | int nmax_phi, int nmax_theta, 326 | int spec_type, 327 | double *mask, int nmask, 328 | int spread, int counterjet, int gamma_type); 329 | 330 | void set_jet_params(struct fluxParams *pars, double E_iso, double theta_h); 331 | void set_obs_params(struct fluxParams *pars, 332 | double t_obs, double nu_obs, long moment, 333 | double theta_obs_cur, double current_theta_cone_hi, 334 | double current_theta_cone_low); 335 | int check_error(void *params); 336 | void set_error(struct fluxParams *pars, char msg[]); 337 | void free_fluxParams(struct fluxParams *pars); 338 | 339 | #endif 340 | -------------------------------------------------------------------------------- /afterglowpy/shockEvolution.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "offaxis_struct.h" 4 | #include "shockEvolution.h" 5 | 6 | 7 | double shockVel(double u) 8 | { 9 | return 4*u*sqrt((u*u+1) / (8*u*u+9)); 10 | } 11 | 12 | double E_inj(double te, double L0, double q, double ts) 13 | { 14 | if(te < T0_inj) 15 | return L0*te; 16 | 17 | if (te > ts) 18 | te = ts; 19 | 20 | double E = L0*T0_inj; 21 | 22 | if(q == 0.0) 23 | return L0*te; 24 | else if(q == 1.0) 25 | return E + L0*T0_inj*log(te/T0_inj); 26 | else 27 | return E + L0*T0_inj*(pow(te/T0_inj,1-q) - 1.0)/(1-q); 28 | 29 | return 0.0; 30 | } 31 | 32 | double L_inj(double te, double L0, double q, double ts) 33 | { 34 | if(te <= T0_inj) 35 | return L0; 36 | else if(te < ts) 37 | { 38 | if(q == 0.0) 39 | return L0; 40 | else 41 | return L0*pow(te/T0_inj,-q); 42 | } 43 | 44 | return 0.0; 45 | } 46 | 47 | double te_inj(double Ei, double L0, double q, double ts) 48 | { 49 | if(L0*T0_inj >= Ei) 50 | return Ei / L0; 51 | Ei -= L0*T0_inj; 52 | 53 | double te; 54 | 55 | if(q == 0) 56 | te = Ei/L0; 57 | else if (q == 1) 58 | te = T0_inj * exp(Ei/(L0*T0_inj)); 59 | else 60 | te = T0_inj * pow((1-q)*Ei/(L0*T0_inj)+1, 1.0/(1-q)); 61 | 62 | if(te > ts) 63 | return -1.0; 64 | 65 | return te; 66 | } 67 | 68 | double t_inj(double te, double t0, double te0, double L0, double q, double ts) 69 | { 70 | return t0*pow(1 + 16*(te-te0)/((2-q)*t0), 0.25*(2-q)); 71 | } 72 | 73 | 74 | double envDensityPar(double R, struct shockParams *par) 75 | { 76 | return envDensity(R, par->envType, par->rho0_env, par->R0_env, par->k_env, 77 | par->rho1_env); 78 | } 79 | 80 | double envMassPar(double R, struct shockParams *par) 81 | { 82 | return envMass(R, par->envType, par->rho0_env, par->R0_env, par->k_env, 83 | par->rho1_env); 84 | } 85 | 86 | double envRadiusPar(double M, struct shockParams *par) 87 | { 88 | return envRadius(M, par->envType, par->rho0_env, par->R0_env, par->k_env, 89 | par->rho1_env); 90 | } 91 | 92 | double envDensity(double R, int envType, double rho0, double R0, double k, 93 | double rho1) 94 | { 95 | if(envType == ENV_ISM) 96 | return rho0; 97 | else if(envType == ENV_WIND) 98 | return rho0 * R0*R0/(R*R); 99 | else if(envType == ENV_PL) 100 | return rho0 * pow(R/R0, -k); 101 | else if(envType == ENV_STEP) 102 | return R < R0 ? rho0 : rho1; 103 | else 104 | return 0; 105 | } 106 | 107 | double envMass(double R, int envType, double rho0, double R0, double k, 108 | double rho1) 109 | { 110 | if(envType == ENV_ISM) 111 | return 4.0/3.0 * M_PI * rho0 * R*R*R; 112 | else if(envType == ENV_WIND) 113 | return 4.0*M_PI * rho0 * R0*R0 * R; 114 | else if(envType == ENV_PL) 115 | return 4.0*M_PI / (3.0-k) * rho0 * pow(R/R0, 3-k) * R0*R0*R0; 116 | else if (envType == ENV_STEP) 117 | { 118 | if(R < R0) 119 | return 4.0/3.0 * M_PI * rho0 * R*R*R; 120 | else 121 | return 4.0/3.0 * M_PI * (rho0 * R0*R0*R0 122 | + rho1 * (R-R0)*(R-R0)*(R-R0)); 123 | } 124 | else 125 | return 0; 126 | } 127 | 128 | double envRadius(double M, int envType, double rho0, double R0, double k, 129 | double rho1) 130 | { 131 | if(envType == ENV_ISM) 132 | return pow(0.75 * M / (M_PI * rho0), 1.0/3.0); 133 | else if(envType == ENV_WIND) 134 | return M / (4.0*M_PI * rho0 * R0*R0); 135 | else if(envType == ENV_PL) 136 | return R0 * pow((3.0-k) * M / (4.0*M_PI * rho0 * R0*R0*R0), 1.0/(3-k)); 137 | else if(envType == ENV_STEP) 138 | { 139 | double M0 = 4.0/3.0 * M_PI * rho0 * R0*R0*R0; 140 | if(M <= M0) 141 | return pow(0.75 * M / (M_PI * rho0), 1.0/3.0); 142 | else 143 | return R0 + pow(0.75 * (M-M0) / (M_PI * rho1), 1.0/3.0); 144 | } 145 | else 146 | return 0; 147 | } 148 | 149 | 150 | void shockInitDecel(double t0, double *R0, double *u0, void *argv) 151 | { 152 | double *args = (double *)argv; 153 | double E0 = args[0]; 154 | double rho0 = args[2]; 155 | double L0 = args[6]; 156 | double q = args[7]; 157 | double ts = args[8]; 158 | 159 | double dr, R, u; 160 | double c = v_light; 161 | double c5 = c*c*c*c*c; 162 | 163 | //First pass: assume no energy injection 164 | double C = sqrt(9/(16.0*M_PI) * E0/(rho0*c5)); 165 | u = C * pow(t0, -1.5); 166 | dr = 1.0/(u*u*16.0); 167 | R = c*t0*(1-dr); 168 | 169 | if(L0 < 0.0 || ts < 0.0) 170 | { 171 | //printf("No energy injection! E=%.3le\n", E0); 172 | *R0 = R; 173 | *u0 = u; 174 | return; 175 | } 176 | 177 | //Check if energy injection is important 178 | double te = t0*dr; 179 | double Ei = E_inj(te, L0, q, ts); 180 | 181 | if(Ei <= E0) 182 | { 183 | //printf("Energy injection not important! E0=%.3le Ei=%.3le\n", E0, Ei); 184 | *R0 = R; 185 | *u0 = u; 186 | return; 187 | } 188 | 189 | //Second pass: use energy injection solution. 190 | 191 | //Time for transition 192 | double teb = te_inj(E0, L0, q, ts); 193 | double t1 = pow(16*C*C*teb, 0.25); 194 | 195 | //position at transition 196 | double u1 = C * pow(t1, -1.5); 197 | double dr1 = 1.0/(u1*u1*16.0); 198 | 199 | //Account for initial constant Luminosity 200 | if(teb < T0_inj) 201 | { 202 | //Lab time for T0_inj 203 | double t0i = t_inj(T0_inj, t1, teb, L0, 0.0, ts); 204 | 205 | if(t0 < t0i) //Praise be 206 | { 207 | //printf("Early energy injection.\n"); 208 | u = u1*pow(t0/t1, -0.5); 209 | dr = (t1*dr1 + 2*(t0/(u*u)-t1/(u1*u1))/16.0) / t0; 210 | *R0 = c*t0*(1-dr); 211 | *u0 = u; 212 | return; 213 | } 214 | 215 | //Move solution forwards. 216 | u = u1*pow(t0i/t1, -0.5); 217 | dr = (t1*dr1 + 2*(t0i/(u*u)-t1/(u1*u1))/16.0) / t0i; 218 | 219 | t1 = t0i; 220 | u1 = u; 221 | dr1 = dr; 222 | } 223 | 224 | double te1 = dr1*t1; 225 | 226 | //Get lab time when energy injection ends. 227 | double tls = t_inj(ts, t1, te1, L0, q, ts); 228 | if(t0 < tls) 229 | { 230 | //printf("Late energy injection.\n"); 231 | u = u1*pow(t0/t1, -0.5*(2+q)/(2-q)); 232 | dr = (t1*dr1 + (2-q)*(t0/(u*u)-t1/(u1*u1))/16.0) / t0; 233 | 234 | *R0 = c*t0*(1-dr); 235 | *u0 = u; 236 | return; 237 | } 238 | 239 | //Energy injection is over! 240 | //Just use original solution with boosted energy. 241 | //printf("All energy injected.\n"); 242 | Ei = E_inj(ts, L0, q, ts); 243 | C = sqrt(9/(16.0*M_PI) * (E0+Ei)/(rho0*c5)); 244 | u = C * pow(t0, -1.5); 245 | 246 | *R0 = c*t0*(1 - 1/(16*u*u)); 247 | *u0 = u; 248 | } 249 | 250 | void shockInitFind(double t0, double *R0, double *u0, double tRes, void *argv) 251 | { 252 | struct shockParams *par = (struct shockParams *)argv; 253 | if(par->envType == ENV_ISM) 254 | { 255 | shockInitFindISM(t0, R0, u0, tRes, argv); 256 | return; 257 | } 258 | 259 | 260 | double E0 = par->E0; 261 | double Mej = par->Mej; 262 | double c2 = v_light * v_light; 263 | 264 | double t00, R00, u00; 265 | 266 | if(Mej > 0.0) 267 | { 268 | double gm1_coast = E0 / (Mej * c2); 269 | double g_coast = gm1_coast + 1; 270 | double u2_coast = gm1_coast * (gm1_coast + 2); 271 | double be2_coast = u2_coast / (g_coast * g_coast); 272 | 273 | double Msw = 3.0 * E0 / ((4*u2_coast + 3) * be2_coast * c2); 274 | 275 | //Rough deceleration radius 276 | double Rd = envRadiusPar(Msw, par); 277 | 278 | //shock 4-velocity 279 | double u_coast = sqrt(u2_coast); 280 | double uS = shockVel(u_coast); 281 | double beS = uS / sqrt(uS*uS + 1); 282 | double td = Rd / (beS * v_light); 283 | 284 | if(t0 < 0.001*td) 285 | { 286 | *R0 = beS * v_light * t0; 287 | *u0 = u_coast; 288 | return; 289 | } 290 | 291 | t00 = 0.001*td; 292 | R00 = beS * v_light * t00; 293 | u00 = u_coast; 294 | } 295 | else 296 | { 297 | //Setting ref ultra-relativistic velocity 298 | double u_UR = 1000; 299 | double g2_UR = u_UR*u_UR + 1; 300 | double be2_UR = u_UR*u_UR / g2_UR; 301 | 302 | //swept mass at u_UR; 303 | double M_UR = 3.0 * E0 / ((4*u_UR*u_UR + 3) * be2_UR * c2); 304 | 305 | //Rough UR radius 306 | double R_UR = envRadiusPar(M_UR, par); 307 | double rho_UR = envDensityPar(R_UR, par); 308 | 309 | // = 3-k for a power law rho ~ R^-k 310 | double volIdx = 4*M_PI * R_UR*R_UR*R_UR * rho_UR / M_UR; 311 | 312 | double t_UR = R_UR/v_light * (1.0 + 1.0/(4*(1+volIdx) * g2_UR)); 313 | 314 | if(t0 < t_UR) 315 | { 316 | double R_apprx = v_light * t0; 317 | double M0 = envMassPar(R_apprx, par); 318 | double g2 = 0.75 * E0 / (M0 * c2); 319 | *R0 = R_apprx * (1 - 1.0/(4 * (1+volIdx) * g2)); 320 | *u0 = sqrt(g2 - 1.0); 321 | return; 322 | } 323 | 324 | t00 = t_UR; 325 | R00 = R_UR; 326 | u00 = u_UR; 327 | } 328 | 329 | double dt, x[2], x0[2], k1[2], k2[2], k3[2], k4[2]; 330 | 331 | x[0] = R00; 332 | x[1] = u00; 333 | 334 | double t = t00; 335 | double fac = pow(10, 1.0/tRes); 336 | int j; 337 | 338 | while(t < t0) 339 | { 340 | dt = (fac-1)*t; 341 | if(fac*t >= t0) 342 | dt = t0-t; 343 | x0[0] = x[0]; 344 | x0[1] = x[1]; 345 | Rudot2D(t, x, argv, k1); 346 | 347 | for(j=0; j<2; j++) 348 | x[j] = x0[j] + 0.5*dt*k1[j]; 349 | Rudot2D(t, x, argv, k2); 350 | 351 | for(j=0; j<2; j++) 352 | x[j] = x0[j] + 0.5*dt*k2[j]; 353 | Rudot2D(t, x, argv, k3); 354 | 355 | for(j=0; j<2; j++) 356 | x[j] = x0[j] + dt*k3[j]; 357 | Rudot2D(t, x, argv, k4); 358 | 359 | for(j=0; j<2; j++) 360 | x[j] = x0[j] + dt*(k1[j]+2*k2[j]+2*k3[j]+k4[j])/6.0; 361 | t *= fac; 362 | } 363 | 364 | *R0 = x[0]; 365 | *u0 = x[1]; 366 | } 367 | 368 | void shockInitFindISM(double t0, double *R0, double *u0, double tRes, 369 | void *argv) 370 | { 371 | struct shockParams *par = (struct shockParams *)argv; 372 | 373 | double E0 = par->E0; 374 | double Mej = par->Mej; 375 | double rho0 = par->rho0_env; 376 | double L0 = par->L0_inj; 377 | double q = par->q_inj; 378 | double ts = par->ts_inj; 379 | 380 | //printf("shockInitFind: t0=%.6lg E0=%.6lg Mej=%.6lg rho0=%.6lg\n", 381 | // t0, E0, Mej, rho0); 382 | //printf(" L0=%.6lg q=%.6lg ts=%.6lg\n", L0, q, ts); 383 | 384 | double u; 385 | double c = v_light; 386 | double c5 = c*c*c*c*c; 387 | 388 | double C = sqrt(9/(16.0*M_PI) * E0/(rho0*c5)); 389 | double tNR = pow(C, 2.0/3.0); 390 | 391 | //Time for deceleration 392 | double td; 393 | if(Mej > 0.0) 394 | { 395 | // gc, uc, beSc = coasting gamma, u, beta_shock 396 | double gcmo = E0/(Mej *c*c); 397 | double uc = sqrt(gcmo * (gcmo+2)); 398 | double gc = sqrt(1+uc*uc); 399 | double beSc = 4*uc*gc / (4*uc*uc+3); 400 | double Rd = pow(9*gc*gc*Mej / (4*M_PI*(gc+1)*(4*uc*uc+3)*rho0), 1./3.); 401 | td = Rd / (beSc * c); 402 | } 403 | else 404 | { 405 | td = 0.0; 406 | } 407 | 408 | //Time for transition 409 | double ti; 410 | if(L0 > 0 && ts > 0) 411 | { 412 | double tei = te_inj(E0, L0, q, ts); 413 | ti = pow(16*C*C*tei, 0.25); 414 | if(tei < 0.0) 415 | ti = 1.0e20 * tNR; //Arbitrary val 416 | } 417 | else 418 | ti = 1.0e20 * tNR; //Arbitrary val 419 | 420 | //printf(" td=%.6lg ti=%.6lg tNR=%.6lg\n", td, ti, tNR); 421 | 422 | if(t0 < 0.01*tNR && t0 < 0.01*ti && t0 > 100*td) 423 | { 424 | //printf("the EASY way\n"); 425 | u = C*pow(t0, -1.5); 426 | *R0 = c*t0*(1-1/(16*u*u)); 427 | *u0 = u; 428 | return; 429 | } 430 | else if(t0 < 0.01 * td) 431 | { 432 | double gcmo = E0/(Mej *c*c); 433 | double uc = sqrt(gcmo * (gcmo+2)); 434 | double gc = sqrt(1+uc*uc); 435 | double beSc = 4*uc*gc / (4*uc*uc+3); 436 | 437 | *R0 = beSc * c * t0; 438 | *u0 = uc; 439 | return; 440 | } 441 | 442 | double dt, x[2], x0[2], k1[2], k2[2], k3[2], k4[2]; 443 | 444 | double t00, u00, R00; 445 | 446 | if(td > 0.0) 447 | { 448 | double gcmo = E0/(Mej *c*c); 449 | double uc = sqrt(gcmo * (gcmo+2)); 450 | double gc = sqrt(1+uc*uc); 451 | double beSc = 4*uc*gc / (4*uc*uc+3); 452 | 453 | t00 = td= t0) 477 | dt = t0-t; 478 | x0[0] = x[0]; 479 | x0[1] = x[1]; 480 | Rudot2D(t, x, argv, k1); 481 | 482 | for(j=0; j<2; j++) 483 | x[j] = x0[j] + 0.5*dt*k1[j]; 484 | Rudot2D(t, x, argv, k2); 485 | 486 | for(j=0; j<2; j++) 487 | x[j] = x0[j] + 0.5*dt*k2[j]; 488 | Rudot2D(t, x, argv, k3); 489 | 490 | for(j=0; j<2; j++) 491 | x[j] = x0[j] + dt*k3[j]; 492 | Rudot2D(t, x, argv, k4); 493 | 494 | for(j=0; j<2; j++) 495 | x[j] = x0[j] + dt*(k1[j]+2*k2[j]+2*k3[j]+k4[j])/6.0; 496 | t *= fac; 497 | } 498 | 499 | *R0 = x[0]; 500 | *u0 = x[1]; 501 | } 502 | 503 | void Rudot2D(double t, double *x, void *argv, double *xdot) 504 | { 505 | struct shockParams *par = (struct shockParams *)argv; 506 | 507 | double Mej = par->Mej; 508 | double Einj = par->E0_refresh; 509 | double k = par->k_refresh; 510 | double umin = par->umin_refresh; 511 | double L0 = par->L0_inj; 512 | double q = par->q_inj; 513 | double ts = par->ts_inj; 514 | 515 | double R = x[0]; 516 | double u = x[1]; 517 | 518 | double g = sqrt(1+u*u); 519 | double be = u/g; 520 | 521 | double bes = 4*u*g/(4*u*u+3); 522 | 523 | double dRdt = bes * v_light; 524 | 525 | double dEdu = 0.0; 526 | if(Einj > 0.0 && u>umin) 527 | dEdu = -k*Einj*pow(u,-k-1); 528 | 529 | double dEdt = 0.0; 530 | double te = t - R/v_light; 531 | if(L0 > 0.0 && te < ts) 532 | { 533 | double gs2 = (4*u*u+3)*(4*u*u+3) / (8*u*u+9); 534 | dEdt = L_inj(te, L0, q, ts) / (gs2*(1+bes)); // Doppler factor (1-bes) 535 | } 536 | 537 | double rho = envDensityPar(R, par); 538 | double M = envMassPar(R, par); 539 | 540 | double dR_Esh = 4*M_PI/3.0 * (4*u*u+3)*be*be * rho * R*R * v_light*v_light; 541 | double du_Esh = 2.0 * (2*u*u+1)*(2*u*u+3)*u * M * v_light*v_light 542 | / (3.0 * g*g*g*g); 543 | double du_Eej = be * Mej * v_light*v_light; 544 | double dudt = (-dR_Esh * dRdt + dEdt) / (du_Eej + du_Esh - dEdu); 545 | 546 | //double num = -16*M_PI/3.0 * rho0*R*R * be*u*u * v_light 547 | // + dEdt/(v_light*v_light); 548 | //double denom = be*Mej 549 | // + 8*M_PI*rho0*R*R*R*u*(2*u*u+1)*(2*u*u+3)/(9*g*g*g*g) 550 | // - dEdu/(v_light*v_light); 551 | //double dudt = num/denom; 552 | 553 | xdot[0] = dRdt; 554 | xdot[1] = dudt; 555 | } 556 | 557 | void RuThdot3D(double t, double *x, void *argv, double *xdot) 558 | { 559 | struct shockParams *par = (struct shockParams *)argv; 560 | 561 | double Mej = par->Mej; 562 | //double rho0 = par->rho0_env; 563 | double Einj = par->E0_refresh; 564 | double k = par->k_refresh; 565 | double umin = par->umin_refresh; 566 | double L0 = par->L0_inj; 567 | double q = par->q_inj; 568 | double ts = par->ts_inj; 569 | double thC = par->thetaCore; 570 | double th0 = par->theta0; 571 | double thCg = par->thetaCoreGlobal; 572 | int spread = par->spread; 573 | 574 | double R = x[0]; 575 | double u = x[1]; 576 | double th = x[2]; 577 | 578 | double g = sqrt(1+u*u); 579 | double be = u/g; 580 | double bes = 4*u*g/(4*u*u+3); 581 | 582 | double sinth = sin(0.5*th); 583 | double costh = cos(0.5*th); 584 | double om = 2*sinth*sinth; 585 | 586 | double dRdt = 4*u*g/(4*u*u+3) * v_light; 587 | 588 | double rho = envDensityPar(R, par); 589 | double M = envMassPar(R, par); 590 | double env3mk = 4*M_PI*rho*R*R*R / M; 591 | 592 | double dThdt = 0.0; 593 | //TODO: verify spreading procedure 190401 594 | //if(spread && th < 0.5*M_PI && u < 1) 595 | //if(spread && th < 0.5*M_PI && u*3.0*th < 1) 596 | if(spread) 597 | { 598 | if(spread == 1) 599 | { 600 | double Q0 = 2.0; 601 | double Q = sqrt(2.0)*3.0; 602 | 603 | if(th < 0.5*M_PI && Q0*u*thC < 1) 604 | { 605 | double e = u*u/(g+1); // specific internal energy == gamma-1 606 | 607 | //Sound speed from trans-relativistic EoS. 608 | double cs = v_light * sqrt(e*(2+e)*(5+8*e+4*e*e) 609 | / (3*(1+e)*(1+e)*(1+2*e)*(3+2*e))); 610 | double fac = u*thC*Q < 1.0 ? 1.0 : Q*(1-Q0*u*thC) / (Q-Q0); 611 | /* 612 | if(fac < 1.0) 613 | { 614 | double sharp = 3.0; 615 | double f0 = exp(-sharp); 616 | fac = (exp(sharp*(fac-1.0))-f0) / (1.0-f0); 617 | } 618 | */ 619 | //fac = 0.0; 620 | dThdt = fac * cs / (R*g); 621 | } 622 | } 623 | else if(spread == 2) 624 | { 625 | double Q0 = 2.0; 626 | double Q = sqrt(2)*3.0; 627 | 628 | if(th < 0.5*M_PI && Q0*u*th0 < 1) 629 | { 630 | double e = u*u/(g+1); // specific internal energy == gamma-1 631 | 632 | //Sound speed from trans-relativistic EoS. 633 | double cs = v_light * sqrt(e*(2+e)*(5+8*e+4*e*e) 634 | / (3*(1+e)*(1+e)*(1+2*e)*(3+2*e))); 635 | double fac = u*th0*Q < 1.0 ? 1.0 : Q*(1-Q0*u*th0) / (Q-Q0); 636 | /* 637 | if(fac < 1.0) 638 | { 639 | double sharp = 3.0; 640 | double f0 = exp(-sharp); 641 | fac = (exp(sharp*(fac-1.0))-f0) / (1.0-f0); 642 | } 643 | */ 644 | //fac = 0.0; 645 | dThdt = fac * cs / (R*g); 646 | } 647 | } 648 | else if(spread == 3) 649 | { 650 | double Q0 = 2.0; 651 | double Q = sqrt(2)*3.0; 652 | 653 | if(th < 0.5*M_PI && Q0*u*thCg < 1) 654 | { 655 | double e = u*u/(g+1); // specific internal energy == gamma-1 656 | 657 | //Sound speed from trans-relativistic EoS. 658 | double cs = v_light * sqrt(e*(2+e)*(5+8*e+4*e*e) 659 | / (3*(1+e)*(1+e)*(1+2*e)*(3+2*e))); 660 | double fac = u*thCg*Q < 1.0 ? 1.0 : Q*(1-Q0*u*thCg) / (Q-Q0); 661 | /* 662 | if(fac < 1.0) 663 | { 664 | double sharp = 3.0; 665 | double f0 = exp(-sharp); 666 | fac = (exp(sharp*(fac-1.0))-f0) / (1.0-f0); 667 | } 668 | */ 669 | //fac = 0.0; 670 | dThdt = fac * cs / (R*g); 671 | } 672 | } 673 | else if(spread == 4) 674 | { 675 | double bew = 0.5*sqrt((2*u*u+3)/(4*u*u+3))*bes/g; 676 | dThdt = bew * v_light / R; 677 | } 678 | else if(spread == 5) 679 | { 680 | double Q0 = 2.0; 681 | double Q = sqrt(2.0)*3.0; 682 | 683 | if(th < 0.5*M_PI && Q0*u*thC < 1) 684 | { 685 | double bew = 0.5*sqrt((2*u*u+3)/(4*u*u+3))*bes/g; 686 | double fac = u*thC*Q < 1.0 ? 1.0 : Q*(1-Q0*u*thC) / (Q-Q0); 687 | dThdt = fac * bew * v_light / R; 688 | } 689 | } 690 | else if(spread == 6) 691 | { 692 | double Q0 = 2.0; 693 | double Q = sqrt(2.0)*3.0; 694 | 695 | if(th < 0.5*M_PI && Q0*u*thCg < 1) 696 | { 697 | double bew = 0.5*sqrt((2*u*u+3)/(4*u*u+3))*bes/g; 698 | double fac = u*thCg*Q < 1.0 ? 1.0 : Q*(1-Q0*u*thCg) / (Q-Q0); 699 | dThdt = fac * bew * v_light / R; 700 | } 701 | } 702 | else if(spread == 7) 703 | { 704 | double envk = 3 - env3mk; 705 | double Q = sqrt(2.0)*env3mk; 706 | double Q0; 707 | if(envk < 2.0) 708 | Q0 = 0.5 * (2.0 * (2.0 - envk) + 1.4 * envk); 709 | else 710 | Q0 = 1.4 * (3.0 - envk); 711 | 712 | if(th < 0.5*M_PI && Q0*u*thC < 1) 713 | { 714 | double bew = 0.5*sqrt((2*u*u+3)/(4*u*u+3))*bes/g; 715 | double fac = u*thC*Q < 1.0 ? 1.0 : Q*(1-Q0*u*thC) / (Q-Q0); 716 | if (th0 < thC) 717 | fac *= tan(0.5*th0)/tan(0.5*thC); //th0/thC; 718 | dThdt = fac * bew * v_light / R; 719 | } 720 | } 721 | else if(spread == 8) 722 | { 723 | double envk = 3 - env3mk; 724 | double Q = sqrt(2.0)*env3mk; 725 | double Q0; 726 | if(envk < 2.0) 727 | Q0 = 0.5 * (2.0 * (2.0 - envk) + 1.4 * envk); 728 | else 729 | Q0 = 1.4 * (3.0 - envk); 730 | 731 | if(th < 0.5*M_PI && Q0*u*thCg < 1) 732 | { 733 | double bew = 0.5*sqrt((2*u*u+3)/(4*u*u+3))*bes/g; 734 | double fac = u*thCg*Q < 1.0 ? 1.0 : Q*(1-Q0*u*thCg) / (Q-Q0); 735 | if (th0 < thCg) 736 | fac *= tan(0.5*th0)/tan(0.5*thCg); //th0/thC; 737 | dThdt = fac * bew * v_light / R; 738 | } 739 | } 740 | else if(spread == 9) 741 | { 742 | //Fernandez & Lamb 2021 spreading. 743 | if(th < 0.5*M_PI && u*thC < 100) 744 | { 745 | double gs = 1 / sqrt((1-bes)*(1+bes)); 746 | double fac = 1.0; 747 | if (th0 < thC) 748 | fac *= tan(0.5*th0)/tan(0.5*thC); //th0/thC; 749 | dThdt = fac / (gs*gs * th) * dRdt / R; 750 | } 751 | } 752 | else if(spread == 11) 753 | { 754 | double envk = 3 - env3mk; 755 | double Q = sqrt(2.0)*env3mk; 756 | double Q0; 757 | if(envk < 2.0) 758 | Q0 = 0.5 * (2.0 * (2.0 - envk) + 1.4 * envk); 759 | else 760 | Q0 = 1.4 * (3.0 - envk); 761 | 762 | if(th < 0.5*M_PI && Q0*u*thC < 1) 763 | { 764 | double bew = 0.5*sqrt((2*u*u+3)/(4*u*u+3))*bes/g; 765 | double fac = u*thC*Q < 1.0 ? 1.0 : Q*(1-Q0*u*thC) / (Q-Q0); 766 | if (th0 < thC) 767 | fac *= tan(0.5*th0)/tan(0.5*thC); //th0/thC; 768 | double gs = 1.0 / sqrt((1-bes)*(1+bes)); 769 | double dThdt_lum = fac * v_light / (R * g); 770 | dThdt = 4.0 * fac * bew * v_light / R; 771 | if(dThdt > dThdt_lum) 772 | dThdt = dThdt_lum; 773 | } 774 | } 775 | } 776 | 777 | double dEdu = 0.0; 778 | if(Einj > 0.0 && u>umin) 779 | dEdu = -k*Einj*pow(u,-k-1); 780 | 781 | double dEdt = 0.0; 782 | double te = t - R/v_light; 783 | if(L0 > 0.0 && te < ts) 784 | { 785 | double gs2 = (4*u*u+3)*(4*u*u+3) / (8*u*u+9); 786 | dEdt = L_inj(te, L0, q, ts) / (gs2*(1+bes)); // Doppler factor (1-bes) 787 | } 788 | 789 | double c2 = v_light*v_light; 790 | 791 | double dR_Esh = 4*M_PI/3.0 * (4*u*u+3)*be*be * rho * R*R * om * c2; 792 | double du_Esh = 2.0*(2*u*u+1)*(2*u*u+3)*u * M * om * c2 / (3.0 * g*g*g*g); 793 | double dTh_Esh = (4*u*u+3)*be*be * M * 2*sinth*costh * c2 / 3.0; 794 | double du_Eej = be * Mej * c2; 795 | double dudt = (-dR_Esh * dRdt - dTh_Esh * dThdt + dEdt) 796 | / (du_Eej + du_Esh - dEdu); 797 | 798 | /* 799 | double num = -16*M_PI/3.0 * om*rho0*R*R * be*u*u * v_light 800 | -8*M_PI/9.0*rho0*R*R*R*(4*u*u+3)*be*be*sinth*costh*dThdt 801 | + om*dEdt/(v_light*v_light); 802 | double denom = be*Mej 803 | + 8*M_PI*om*rho0*R*R*R*u*(2*u*u+1)*(2*u*u+3)/(9*g*g*g*g) 804 | - dEdu/(v_light*v_light); 805 | double dudt = num/denom; 806 | */ 807 | 808 | xdot[0] = dRdt; 809 | xdot[1] = dudt; 810 | xdot[2] = dThdt; 811 | } 812 | 813 | void shockEvolveRK4(double *t, double *R, double *u, int N, double R0, 814 | double u0, void *args) 815 | { 816 | int i,j; 817 | 818 | double dt, x[2], x0[2], k1[2], k2[2], k3[2], k4[2]; 819 | 820 | R[0] = R0; 821 | u[0] = u0; 822 | 823 | for(i=0; i 0.5*M_PI) 886 | th[i+1] = 0.5*M_PI; 887 | else 888 | th[i+1] = x[2]; 889 | } 890 | } 891 | 892 | void setup_shockParams(struct shockParams *pars, int spread, 893 | double E0, double Mej, 894 | int envType, double rho0_env, double R0_env, 895 | double k_env, double rho1_env, 896 | double L0_inj, double q_inj, double t0_inj, 897 | double ts_inj, 898 | double E0_refresh, double k_refresh, double umin_refresh, 899 | double thetaCore, double theta0, 900 | double thetaCoreGlobal) 901 | { 902 | pars->spread = spread; 903 | pars->E0 = E0; 904 | pars->Mej = Mej; 905 | pars->envType = envType; 906 | pars->rho0_env = rho0_env; 907 | pars->R0_env = R0_env; 908 | pars->k_env = k_env; 909 | pars->rho1_env = rho1_env; 910 | pars->L0_inj = L0_inj; 911 | pars->q_inj = q_inj; 912 | pars->t0_inj = t0_inj; 913 | pars->ts_inj = ts_inj; 914 | pars->E0_refresh = E0_refresh; 915 | pars->k_refresh = k_refresh; 916 | pars->umin_refresh = umin_refresh; 917 | pars->thetaCore = thetaCore; 918 | pars->theta0 = theta0; 919 | pars->thetaCoreGlobal = thetaCoreGlobal; 920 | } 921 | -------------------------------------------------------------------------------- /afterglowpy/shockEvolution.h: -------------------------------------------------------------------------------- 1 | #ifndef AFTERGLOWPY_SHOCK 2 | #define AFTERGLOWPY_SHOCK 3 | 4 | enum{ENV_ISM, ENV_WIND, ENV_PL, ENV_STEP}; 5 | 6 | static const double T0_inj = 1.0e3; 7 | 8 | struct shockParams 9 | { 10 | int spread; 11 | double E0; 12 | double Mej; 13 | double L0_inj; 14 | double q_inj; 15 | double t0_inj; 16 | double ts_inj; 17 | double E0_refresh; 18 | double k_refresh; 19 | double umin_refresh; 20 | double thetaCore; 21 | double theta0; 22 | double thetaCoreGlobal; 23 | int envType; 24 | double rho0_env; 25 | double R0_env; 26 | double k_env; 27 | double rho1_env; 28 | }; 29 | 30 | void setup_shockParams(struct shockParams *pars, int spread, 31 | double E0, double Mej, 32 | int envType, double env_rho0, double R0_env, 33 | double k_env, double rho1_env, 34 | double L0_inj, double q_inj, double t0_inj, 35 | double ts_inj, 36 | double E0_refresh, double k_refresh, double umin_refresh, 37 | double thetaCore, double theta0, 38 | double thetaCoreGlobal); 39 | 40 | double shockVel(double u); 41 | double E_inj(double te, double L0, double q, double ts); 42 | double L_inj(double te, double L0, double q, double ts); 43 | double envDensityPar(double R, struct shockParams *pars); 44 | double envMassPar(double R, struct shockParams *pars); 45 | double envRadiusPar(double M, struct shockParams *pars); 46 | double envDensity(double R, int envType, double rho0, double R0, double k, 47 | double rho1); 48 | double envMass(double R, int envType, double rho0, double R0, double k, 49 | double rho1); 50 | double envRadius(double M, int envType, double rho0, double R0, double k, 51 | double rho1); 52 | void shockInitDecel(double t0, double *R0, double *u0, void *argv); 53 | void shockInitFind(double t0, double *R0, double *u0, double tRes, void *argv); 54 | void shockInitFindISM(double t0, double *R0, double *u0, double tRes, 55 | void *argv); 56 | void Rudot2D(double t, double *x, void *argv, double *xdot); 57 | void RuThdot3D(double t, double *x, void *argv, double *xdot); 58 | void shockEvolveRK4(double *t, double *R, double *u, int N, double R0, 59 | double u0, void *args); 60 | void shockEvolveSpreadRK4(double *t, double *R, double *u, double *th, int N, 61 | double R0, double u0, double th0, void *args); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /afterglowpy/shockmodule.c: -------------------------------------------------------------------------------- 1 | #include 2 | #define NPY_NO_DEPRECATED_API NPY_1_11_API_VERSION 3 | #include 4 | #include "shockEvolution.h" 5 | 6 | static char shock_docstring[] = 7 | "This module calculates evolution of relativistic shocks."; 8 | static char shockEvolRK4_docstring[] = 9 | "Evolve a spherical shock with RK4"; 10 | static char shockEvolSpreadRK4_docstring[] = 11 | "Evolve a conical shock with RK4"; 12 | 13 | static PyObject *error_out(PyObject *m); 14 | static PyObject *shock_shockEvolRK4(PyObject *self, PyObject *args); 15 | static PyObject *shock_shockEvolSpreadRK4(PyObject *self, PyObject *args); 16 | 17 | struct module_state 18 | { 19 | PyObject *error; 20 | }; 21 | #if PY_MAJOR_VERSION >= 3 22 | #define GETSTATE(m) ((struct module_state *) PyModule_GetState(m)) 23 | #else 24 | #define GETSTATE(m) (&_state) 25 | static struct module_state _state; 26 | #endif 27 | 28 | static PyMethodDef shockMethods[] = { 29 | {"shockEvolRK4", shock_shockEvolRK4, METH_VARARGS, shockEvolRK4_docstring}, 30 | {"shockEvolSpreadRK4", shock_shockEvolSpreadRK4, METH_VARARGS, 31 | shockEvolSpreadRK4_docstring}, 32 | {"error_out", (PyCFunction)error_out, METH_NOARGS, NULL}, 33 | {NULL, NULL, 0, NULL}}; 34 | 35 | #if PY_MAJOR_VERSION >= 3 36 | 37 | static int shock_traverse(PyObject *m, visitproc visit, void *arg) 38 | { 39 | Py_VISIT(GETSTATE(m)->error); 40 | return 0; 41 | } 42 | 43 | static int shock_clear(PyObject *m) 44 | { 45 | Py_CLEAR(GETSTATE(m)->error); 46 | return 0; 47 | } 48 | 49 | static struct PyModuleDef shockModule = { 50 | PyModuleDef_HEAD_INIT, 51 | "shock", /* Module Name */ 52 | shock_docstring, 53 | sizeof(struct module_state), 54 | shockMethods, 55 | NULL, 56 | shock_traverse, 57 | shock_clear, 58 | NULL 59 | }; 60 | #define INITERROR return NULL 61 | 62 | PyMODINIT_FUNC PyInit_shock(void) 63 | #else 64 | #define INITERROR return 65 | 66 | void initshock(void) 67 | #endif 68 | { 69 | #if PY_MAJOR_VERSION >= 3 70 | PyObject *module = PyModule_Create(&shockModule); 71 | #else 72 | PyObject *module = Py_InitModule3("shock", shockMethods, shock_docstring); 73 | #endif 74 | if(module == NULL) 75 | INITERROR; 76 | struct module_state *st = GETSTATE(module); 77 | st->error = PyErr_NewException("shock.Error", NULL, NULL); 78 | if(st->error == NULL) 79 | { 80 | Py_DECREF(module); 81 | INITERROR; 82 | } 83 | 84 | //Load numpy stuff! 85 | import_array(); 86 | #if PY_MAJOR_VERSION >= 3 87 | return module; 88 | #endif 89 | } 90 | 91 | static PyObject *error_out(PyObject *m) 92 | { 93 | struct module_state *st = GETSTATE(m); 94 | PyErr_SetString(st->error, "something bad happened"); 95 | return NULL; 96 | } 97 | 98 | static PyObject *shock_shockEvolRK4(PyObject *self, PyObject *args) 99 | { 100 | PyObject *t_obj = NULL; 101 | 102 | double R0, u0; 103 | double Mej, rho0_env, R0_env, k_env, rho1_env, Einj, k, umin, L0, q, ts; 104 | int envType; 105 | 106 | //Parse Arguments 107 | if(!PyArg_ParseTuple(args, "Odd""d""idddd""ddd""ddd", 108 | &t_obj, &R0, &u0, 109 | &Mej, 110 | &envType, &rho0_env, &R0_env, &k_env, &rho1_env, 111 | &Einj, &k, &umin, 112 | &L0, &q, &ts)) 113 | { 114 | PyErr_SetString(PyExc_RuntimeError, "Could not parse arguments."); 115 | return NULL; 116 | } 117 | 118 | //Grab NUMPY arrays 119 | PyArrayObject *t_arr; 120 | t_arr = (PyArrayObject *) PyArray_FROM_OTF(t_obj, NPY_DOUBLE, 121 | NPY_ARRAY_IN_ARRAY); 122 | if(t_arr == NULL) 123 | { 124 | PyErr_SetString(PyExc_RuntimeError, "Could not read input arrays."); 125 | Py_XDECREF(t_arr); 126 | return NULL; 127 | } 128 | 129 | int t_ndim = (int) PyArray_NDIM(t_arr); 130 | if(t_ndim != 1) 131 | { 132 | PyErr_SetString(PyExc_RuntimeError, "Array must be 1-D."); 133 | Py_DECREF(t_arr); 134 | return NULL; 135 | } 136 | 137 | int N = (int)PyArray_DIM(t_arr, 0); 138 | 139 | double *t = (double *)PyArray_DATA(t_arr); 140 | 141 | //Allocate output array 142 | 143 | npy_intp dims[1] = {N}; 144 | PyObject *R_obj = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 145 | PyObject *u_obj = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 146 | 147 | if(R_obj == NULL || u_obj == NULL) 148 | { 149 | PyErr_SetString(PyExc_RuntimeError, "Could not make output arrays."); 150 | Py_DECREF(t_arr); 151 | Py_XDECREF(R_obj); 152 | Py_XDECREF(u_obj); 153 | return NULL; 154 | } 155 | double *R = PyArray_DATA((PyArrayObject *) R_obj); 156 | double *u = PyArray_DATA((PyArrayObject *) u_obj); 157 | 158 | // Evolve the shock! 159 | struct shockParams shock_pars; 160 | setup_shockParams(&shock_pars, 0, 0.0, Mej, 161 | envType, rho0_env, R0_env, k_env, rho1_env, 162 | L0, q, 1.0e3, ts, 163 | Einj, k, umin, 164 | 0.0, 0.0, 0.0); 165 | //double shockArgs[9] = {0.0, Mej, rho0, Einj, k, umin, L0, q, ts}; 166 | shockEvolveRK4(t, R, u, N, R0, u0, &shock_pars); 167 | 168 | // Clean up! 169 | Py_DECREF(t_arr); 170 | 171 | //Build output 172 | PyObject *ret = Py_BuildValue("NN", R_obj, u_obj); 173 | 174 | return ret; 175 | } 176 | 177 | static PyObject *shock_shockEvolSpreadRK4(PyObject *self, PyObject *args) 178 | { 179 | PyObject *t_obj = NULL; 180 | 181 | double R0, u0, th0; 182 | double Mej, rho0_env, R0_env, k_env, rho1_env; 183 | double Einj, k, umin, L0, q, ts, thC; 184 | int envType; 185 | int spread; 186 | 187 | //Parse Arguments 188 | if(!PyArg_ParseTuple(args, "Oddd""d""idddd""ddd""ddd""di", 189 | &t_obj, &R0, &u0, &th0, 190 | &Mej, 191 | &envType, &rho0_env, &R0_env, &k_env, 192 | &rho1_env, 193 | &Einj, &k, &umin, 194 | &L0, &q, &ts, 195 | &thC, &spread)) 196 | { 197 | PyErr_SetString(PyExc_RuntimeError, "Could not parse arguments."); 198 | return NULL; 199 | } 200 | 201 | //Grab NUMPY arrays 202 | PyArrayObject *t_arr; 203 | t_arr = (PyArrayObject *) PyArray_FROM_OTF(t_obj, NPY_DOUBLE, 204 | NPY_ARRAY_IN_ARRAY); 205 | if(t_arr == NULL) 206 | { 207 | PyErr_SetString(PyExc_RuntimeError, "Could not read input arrays."); 208 | Py_XDECREF(t_arr); 209 | return NULL; 210 | } 211 | 212 | int t_ndim = (int) PyArray_NDIM(t_arr); 213 | if(t_ndim != 1) 214 | { 215 | PyErr_SetString(PyExc_RuntimeError, "Array must be 1-D."); 216 | Py_DECREF(t_arr); 217 | return NULL; 218 | } 219 | 220 | int N = (int)PyArray_DIM(t_arr, 0); 221 | 222 | double *t = (double *)PyArray_DATA(t_arr); 223 | 224 | //Allocate output arrays 225 | 226 | npy_intp dims[1] = {N}; 227 | PyObject *R_obj = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 228 | PyObject *u_obj = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 229 | PyObject *th_obj = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 230 | 231 | if(R_obj == NULL || u_obj == NULL || th_obj == NULL) 232 | { 233 | PyErr_SetString(PyExc_RuntimeError, "Could not make output arrays."); 234 | Py_DECREF(t_arr); 235 | Py_XDECREF(R_obj); 236 | Py_XDECREF(u_obj); 237 | Py_XDECREF(th_obj); 238 | return NULL; 239 | } 240 | double *R = PyArray_DATA((PyArrayObject *) R_obj); 241 | double *u = PyArray_DATA((PyArrayObject *) u_obj); 242 | double *th = PyArray_DATA((PyArrayObject *) th_obj); 243 | 244 | // Evolve the shock! 245 | //double shockArgs[12] = {0.0, Mej, rho0, Einj, k, umin, L0, q, ts, thC, th0, 246 | // thC}; 247 | 248 | struct shockParams shock_pars; 249 | setup_shockParams(&shock_pars, spread, 0.0, Mej, 250 | envType, rho0_env, R0_env, k_env, rho1_env, 251 | L0, q, 1.0e3, ts, 252 | Einj, k, umin, 253 | thC, th0, thC); 254 | 255 | shockEvolveSpreadRK4(t, R, u, th, N, R0, u0, th0, &shock_pars); 256 | 257 | // Clean up! 258 | Py_DECREF(t_arr); 259 | 260 | //Build output 261 | PyObject *ret = Py_BuildValue("NNN", R_obj, u_obj, th_obj); 262 | 263 | return ret; 264 | } 265 | -------------------------------------------------------------------------------- /afterglowpy/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | r"""Version info""" 3 | 4 | __short_version__ = '0.8' 5 | __version__ = '0.8.1' 6 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx>=2.2 2 | numpy>=2.0 3 | scipy>=1.0 4 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | 14 | 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../..')) 18 | # from afterglowpy.version import __version__ 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'afterglowpy' 23 | copyright = '2019, Geoffrey Ryan' 24 | author = 'Geoffrey Ryan' 25 | 26 | # The full version, including alpha/beta/rc tags 27 | 28 | with open("../../afterglowpy/version.py", "r") as f: 29 | exec(f.read()) 30 | release = __version__ 31 | version = __version__ 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be 37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 38 | # ones. 39 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # List of patterns, relative to source directory, that match files and 45 | # directories to ignore when looking for source files. 46 | # This pattern also affects html_static_path and html_extra_path. 47 | exclude_patterns = [] 48 | 49 | master_doc = 'index' 50 | autodoc_mock_imports = ['afterglowpy.jet', 'afterglowpy.shock'] 51 | 52 | 53 | # -- Options for HTML output ------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. See the documentation for 56 | # a list of builtin themes. 57 | # 58 | html_theme = 'alabaster' 59 | 60 | # Add any paths that contain custom static files (such as style sheets) here, 61 | # relative to this directory. They are copied after the builtin static files, 62 | # so a file named "default.css" will overwrite the builtin "default.css". 63 | html_static_path = ['_static'] 64 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | afterglowpy 2 | =========== 3 | 4 | *afterglowpy* is a Python package for modelling Gamma-ray burst afterglows. It computes synchrotron radiation from an external shock and is capable of handling both structured jets and off-axis observers. 5 | 6 | For details of the physics, approximations, and numerical implementation please see `Ryan et al. 2020 `_. 7 | 8 | You can install *afterglowpy* with pip:: 9 | 10 | $ pip install afterglowpy 11 | 12 | This documentation is still a little thin. Please read the Quickstart, check out the examples on Github: ``_, and contact the author if you have any questions or issues. 13 | 14 | Attribution 15 | +++++++++++ 16 | 17 | If you use *afterglowpy* in your work, please refer to the package by name and cite the paper `Ryan et al. 2020 `_. 18 | 19 | Contents 20 | ^^^^^^^^ 21 | 22 | .. toctree:: 23 | :maxdepth: 4 24 | 25 | quickstart.rst 26 | modules.rst 27 | 28 | 29 | Indices and tables 30 | ------------------ 31 | 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | API Documentation 2 | ================= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | modules/afterglowpy 8 | -------------------------------------------------------------------------------- /docs/source/modules/afterglowpy.cocoon.rst: -------------------------------------------------------------------------------- 1 | afterglowpy.cocoon module 2 | ========================= 3 | 4 | .. automodule:: afterglowpy.cocoon 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/modules/afterglowpy.flux.rst: -------------------------------------------------------------------------------- 1 | afterglowpy.flux module 2 | ========================= 3 | 4 | .. automodule:: afterglowpy.flux 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/modules/afterglowpy.jet.rst: -------------------------------------------------------------------------------- 1 | afterglowpy.jet module 2 | ========================= 3 | 4 | .. automodule:: afterglowpy.jet 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/modules/afterglowpy.rst: -------------------------------------------------------------------------------- 1 | afterglowpy package 2 | =================== 3 | 4 | .. automodule:: afterglowpy 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | afterglowpy.shock 15 | afterglowpy.jet 16 | afterglowpy.cocoon 17 | afterglowpy.flux 18 | -------------------------------------------------------------------------------- /docs/source/modules/afterglowpy.shock.rst: -------------------------------------------------------------------------------- 1 | afterglowpy.shock module 2 | ========================= 3 | 4 | .. automodule:: afterglowpy.shock 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | *afterglowpy* exposes a single function ``fluxDensity()`` for the purposes of calculating light curves and spectra from GRB afterglows. It takes many arguments, which are grouped as follows: 5 | 6 | 1. ``t``: a 1-D array of observer-frame times, in seconds (s) 7 | 2. ``nu``: a 1-D array, the same shape as ``t``, of observer-frame frequencies, in Hertz (Hz) 8 | 3. ``jetType``: an integer specifying which jet model to use (top-hat: -1, Gaussian: 0, power law: 4). 9 | 4. ``specType``: an integer specifying which spectral model to use. 10 | 5. Model Parameters (keywords): A model-dependent set of parameters, such as E\ :sub:`iso`, n\ :sub:`0`, etc. 11 | 6. Numerical Parameters (keywords): A model-dependent set of keyword arguments specifying numerical settings, such as `tRes` (resolution of time integration), and `spread` (whether or not to include jet spreading). 12 | 13 | A light curve from a top-hat jet may be created with:: 14 | 15 | import numpy as np 16 | import afterglowpy as grb 17 | 18 | # For convenience, place arguments into a dict. 19 | Z = {'jetType': grb.jet.TopHat, # Top-Hat jet 20 | 'specType': grb.jet.SimpleSpec, # Basic Synchrotron Spectrum 21 | 22 | 'thetaObs': 0.05, # Viewing angle in radians 23 | 'E0': 1.0e53, # Isotropic-equivalent energy in erg 24 | 'thetaCore': 0.1, # Half-opening angle in radians 25 | 'n0': 1.0, # circumburst density in cm^{-3} 26 | 'p': 2.2, # electron energy distribution index 27 | 'epsilon_e': 0.1, # epsilon_e 28 | 'epsilon_B': 0.01, # epsilon_B 29 | 'xi_N': 1.0, # Fraction of electrons accelerated 30 | 'd_L': 1.0e28, # Luminosity distance in cm 31 | 'z': 0.55} # redshift 32 | 33 | # Space time points geometrically, from 10^3 s to 10^7 s 34 | t = np.geomspace(1.0e3, 1.0e7, 300) 35 | 36 | # Calculate flux in a single X-ray band (all times have same frequency) 37 | nu = np.empty(t.shape) 38 | nu[:] = 1.0e18 39 | 40 | # Calculate! 41 | 42 | Fnu = grb.fluxDensity(t, nu, **Z) 43 | 44 | ``Fnu`` is now an array, same size as ``t`` and ``nu``, containing the observed flux in mJy at each time and frequency. 45 | 46 | Let's calculate the light curve for a Gaussian jet with the same parameters. A Gaussian jet has an isotropic-energy profile E(theta) = E0 * exp(-0.5*theta^2/thetaCore^2). Now that ``thetaCore`` sets the Gaussian width of the jet core, and we need to provide a sensible value for ``thetaWing`` to truncate the outer regions of the jet:: 47 | 48 | Z['jetType'] = grb.jet.Gaussian # Gaussian jet 49 | 50 | # We'll re-use the Z dict, just add a thetaWing entry 51 | 52 | Z['thetaObs'] = 0.3 # Larger viewing angle, just for fun 53 | Z['thetaWing'] = 4 * Z['thetaCore'] # Setting thetaW 54 | 55 | # Nothing else to change, so calculate! 56 | 57 | Fnu_G = grb.fluxDensity(t, nu, **Z) 58 | 59 | A power law structured jet has E(theta) ~ theta^(-b) for theta >> thetaC. We need to give ``b`` a reasonable value:: 60 | 61 | Z['jetType'] = grb.jet.PowerLaw # power law 62 | 63 | # Set b 64 | Z['b'] = 6.0 # The rough ballpark from RHD simulations 65 | 66 | # Calculate! 67 | 68 | Fnu_PL = grb.fluxDensity(t, nu, **Z) 69 | 70 | There you go! Simple X-ray light curves for three different jet models. 71 | -------------------------------------------------------------------------------- /examples/plotCentroidAndSize.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import afterglowpy as grb 4 | 5 | def computeMoments(t, nu, Z): 6 | 7 | # Make array to store moment options 8 | moment = np.empty(t.shape, dtype=int) 9 | 10 | # compute moment 0 (flux) 11 | moment[:] = grb.jet.MOM_0 12 | Fnu = grb.fluxDensity(t, nu, **Z, moment=moment) 13 | 14 | # compute moment 1 (integral of x_obs * intensity) 15 | moment[:] = grb.jet.MOM_X 16 | FnuX = grb.fluxDensity(t, nu, **Z, moment=moment) 17 | 18 | # compute moment 2 (integral of x_obs^2 * intensity) 19 | moment[:] = grb.jet.MOM_XX 20 | FnuXX = grb.fluxDensity(t, nu, **Z, moment=moment) 21 | 22 | # compute moment 2 (integral of y_obs^2 * intensity) 23 | moment[:] = grb.jet.MOM_YY 24 | FnuYY = grb.fluxDensity(t, nu, **Z, moment=moment) 25 | 26 | # The jet propagates in the x-direction and is axisymmetric, so MOM_Y 27 | # and MOM_XY will always be identically 0 28 | 29 | # Get the intensity-weighted distance measures 30 | X_cm = FnuX / Fnu # in cm 31 | X2_cm = FnuXX / Fnu # in cm^2 32 | Y2_cm = FnuYY / Fnu # in cm^2 33 | 34 | # Convert proper distances to angular distances 35 | dA = Z['d_L'] / (1 + Z['z'])**2 36 | 37 | X_rad = X_cm / dA 38 | X2_rad = X2_cm / dA**2 39 | Y2_rad = Y2_cm / dA**2 40 | 41 | # Convert radians to mas 42 | rad2mas = 1000 * 3600 * 180 / np.pi 43 | x = X_rad * rad2mas 44 | x2 = X2_rad * rad2mas**2 45 | y2 = Y2_rad * rad2mas**2 46 | 47 | # Compute sizes 48 | sig_x = np.sqrt(x2 - x**2) 49 | sig_y = np.sqrt(y2) 50 | 51 | return Fnu, x, sig_x, sig_y 52 | 53 | if __name__ == "__main__": 54 | 55 | 56 | # For convenience, place arguments into a dict. 57 | Z = {'jetType': grb.jet.Gaussian, # Top-Hat jet 58 | 'specType': grb.jet.SimpleSpec, # Basic Synchrotron Spectrum 59 | 60 | 'thetaObs': 0.00, # Viewing angle in radians 61 | 'E0': 1.0e53, # Isotropic-equivalent energy in erg 62 | 'thetaCore': 0.05, # Half-opening angle in radians 63 | 'thetaWing': 0.4, # Half-opening angle in radians 64 | 'n0': 1.0e-3, # circumburst density in cm^{-3} 65 | 'p': 2.2, # electron energy distribution index 66 | 'epsilon_e': 0.1, # epsilon_e 67 | 'epsilon_B': 0.01, # epsilon_B 68 | 'xi_N': 1.0, # Fraction of electrons accelerated 69 | 'd_L': 1.0e26, # Luminosity distance in cm 70 | 'z': 0.01} # redshift 71 | 72 | # Space time points geometrically, from 10^3 s to 10^7 s 73 | t = np.geomspace(1.0e3, 1.0e7, 100) 74 | 75 | # Calculate flux in a single X-ray band (all times have same frequency) 76 | nu = np.empty(t.shape) 77 | nu[:] = 3.0e9 78 | 79 | 80 | # Calculate! 81 | Fnu, x, sig_x, sig_y = computeMoments(t, nu, Z) 82 | 83 | # Try an off-axis one too 84 | Z['thetaObs'] = 6 * Z['thetaCore'] 85 | Fnu_off, x_off, sig_x_off, sig_y_off = computeMoments(t, nu, Z) 86 | 87 | # Plot! 88 | 89 | print("Plotting") 90 | fig, ax = plt.subplots(4, 1, figsize=(6, 12)) 91 | 92 | ax[0].plot(t, Fnu, label='on-axis') 93 | ax[0].plot(t, Fnu_off, label='off-axis') 94 | 95 | ax[1].plot(t, x) 96 | ax[1].plot(t, x_off) 97 | 98 | ax[2].plot(t, sig_x) 99 | ax[2].plot(t, sig_x_off) 100 | 101 | ax[3].plot(t, sig_y) 102 | ax[3].plot(t, sig_y_off) 103 | 104 | ax[0].legend() 105 | 106 | ax[0].set(xscale='log', xlabel=r'$t$ (s)', 107 | yscale='log', ylabel=r'$F_\nu$[$3\times 10^{9}$ Hz] (mJy)') 108 | ax[1].set(xscale='log', xlabel=r'$t$ (s)', 109 | yscale='linear', ylabel=r'centroid offset (mas)') 110 | ax[2].set(xscale='log', xlabel=r'$t$ (s)', 111 | yscale='log', ylabel=r'$\sigma_x$ (mas)') 112 | ax[3].set(xscale='log', xlabel=r'$t$ (s)', 113 | yscale='log', ylabel=r'$\sigma_y$ (mas)') 114 | 115 | fig.tight_layout() 116 | print("Saving figure lc_centroid_and_size.png") 117 | fig.savefig("lc_centroid_and_size.png") 118 | plt.close(fig) 119 | -------------------------------------------------------------------------------- /examples/plotLateTime.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import afterglowpy as grb 4 | 5 | # For convenience, place arguments into a dict. 6 | Z = {'jetType': grb.jet.TopHat, # Top-Hat jet 7 | 'specType': grb.jet.DeepNewtonian, # Deep Newtonian Emission 8 | 9 | 'thetaObs': 0.05, # Viewing angle in radians 10 | 'E0': 1.0e53, # Isotropic-equivalent energy in erg 11 | 'thetaCore': 0.1, # Half-opening angle in radians 12 | 'n0': 1.0, # circumburst density in cm^{-3} 13 | 'p': 2.2, # electron energy distribution index 14 | 'epsilon_e': 0.001, # epsilon_e (low eps_e turns DN on sooner) 15 | 'epsilon_B': 0.001, # epsilon_B 16 | 'xi_N': 1.0, # Fraction of electrons accelerated 17 | 'd_L': 1.0e28, # Luminosity distance in cm 18 | 'z': 0.55, # redshift 19 | 'counterjet': True # Turn on the counter jet 20 | } 21 | 22 | # Space time points geometrically, from 10^4 s to 10^9 s 23 | t = np.geomspace(1.0e4, 1.0e9, 300) 24 | 25 | # Calculate flux in a single radio band (all times have same frequency) 26 | nu = np.empty(t.shape) 27 | nu[:] = 3.0e9 28 | 29 | # Calculate! 30 | 31 | Fnu = grb.fluxDensity(t, nu, **Z) 32 | 33 | # Compute a light curve with counterjet but no deep newtonian 34 | 35 | Z_cj_only = Z.copy() 36 | Z_cj_only['specType'] = grb.jet.SimpleSpec 37 | Fnu_cj_only = grb.fluxDensity(t, nu, **Z_cj_only) 38 | 39 | # Compute a light curve with no counterjet and no deep newtonian 40 | Z_default = Z.copy() 41 | Z_default['specType'] = grb.jet.SimpleSpec 42 | Z_default['counterjet'] = False 43 | Fnu_default = grb.fluxDensity(t, nu, **Z_default) 44 | 45 | 46 | # Plot! 47 | 48 | print("Plotting") 49 | fig, ax = plt.subplots(1, 1) 50 | 51 | ax.plot(t, Fnu, label='Deep Newtonian and counterjet') 52 | ax.plot(t, Fnu_cj_only, label='counterjet') 53 | ax.plot(t, Fnu_default, label='Default (no Deep Newtonian or counterjet)') 54 | ax.legend() 55 | 56 | ax.set(xscale='log', xlabel=r'$t$ (s)', 57 | yscale='log', ylabel=r'$F_\nu$[$3\times 10^{9}$ Hz] (mJy)') 58 | 59 | fig.tight_layout() 60 | print("Saving figure lc_late_time.png") 61 | fig.savefig("lc_late_time.png") 62 | plt.close(fig) 63 | -------------------------------------------------------------------------------- /examples/plotLightCurve.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import afterglowpy as grb 4 | 5 | # For convenience, place arguments into a dict. 6 | Z = {'jetType': grb.jet.TopHat, # Top-Hat jet 7 | 'specType': grb.jet.SimpleSpec, # Basic Synchrotron Emission Spectrum 8 | 9 | 'thetaObs': 0.05, # Viewing angle in radians 10 | 'E0': 1.0e53, # Isotropic-equivalent energy in erg 11 | 'thetaCore': 0.1, # Half-opening angle in radians 12 | 'n0': 1.0, # circumburst density in cm^{-3} 13 | 'p': 2.2, # electron energy distribution index 14 | 'epsilon_e': 0.1, # epsilon_e 15 | 'epsilon_B': 0.01, # epsilon_B 16 | 'xi_N': 1.0, # Fraction of electrons accelerated 17 | 'd_L': 1.0e28, # Luminosity distance in cm 18 | 'z': 0.55} # redshift 19 | 20 | # Space time points geometrically, from 10^3 s to 10^7 s 21 | t = np.geomspace(1.0e3, 1.0e7, 300) 22 | 23 | # Calculate flux in a single X-ray band (all times have same frequency) 24 | nu = np.empty(t.shape) 25 | nu[:] = 1.0e18 26 | 27 | # Calculate! 28 | 29 | Fnu = grb.fluxDensity(t, nu, **Z) 30 | 31 | # Write to a file 32 | 33 | print("Writing lc.txt") 34 | with open("lc.txt", 'w') as f: 35 | f.write("# nu " + str(nu) + '\n') 36 | f.write("# t(s) Fnu(mJy)\n") 37 | for i in range(len(t)): 38 | f.write("{0:.6e} {1:.6e}\n".format(t[i], Fnu[i])) 39 | 40 | # Plot! 41 | 42 | print("Plotting") 43 | fig, ax = plt.subplots(1, 1) 44 | 45 | ax.plot(t, Fnu) 46 | 47 | ax.set(xscale='log', xlabel=r'$t$ (s)', 48 | yscale='log', ylabel=r'$F_\nu$[$10^{18}$ Hz] (mJy)') 49 | 50 | fig.tight_layout() 51 | print("Saving figure lc.png") 52 | fig.savefig("lc.png") 53 | plt.close(fig) 54 | -------------------------------------------------------------------------------- /examples/plotMultibandLightCurve.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import afterglowpy as grb 4 | 5 | # Jet Parameters 6 | Z = {'jetType': grb.jet.Gaussian, # Gaussian jet 7 | 'specType': grb.jet.SimpleSpec, # Basic Synchrotron Emission Spectrum 8 | 9 | 'thetaObs': 0.3, # Viewing angle in radians 10 | 'E0': 1.0e53, # Isotropic-equivalent energy in erg 11 | 'thetaCore': 0.05, # Half-opening angle in radians 12 | 'thetaWing': 0.4, # Outer truncation angle 13 | 'n0': 1.0e-3, # circumburst density in cm^{-3} 14 | 'p': 2.2, # electron energy distribution index 15 | 'epsilon_e': 0.1, # epsilon_e 16 | 'epsilon_B': 0.0001, # epsilon_B 17 | 'xi_N': 1.0, # Fraction of electrons accelerated 18 | 'd_L': 1.36e26, # Luminosity distance in cm 19 | 'z': 0.01} # redshift 20 | 21 | # Time and Frequencies 22 | ta = 1.0e-1 * grb.day2sec 23 | tb = 1.0e3 * grb.day2sec 24 | t = np.geomspace(ta, tb, num=100) 25 | 26 | nuR = 6.0e9 27 | nuO = 1.0e14 28 | nuX = 1.0e18 29 | 30 | # Calculate! 31 | print("Calc Radio") 32 | FnuR = grb.fluxDensity(t, nuR, **Z) 33 | print("Calc Optical") 34 | FnuO = grb.fluxDensity(t, nuO, **Z) 35 | print("Calc X-ray") 36 | FnuX = grb.fluxDensity(t, nuX, **Z) 37 | 38 | # Plot! 39 | print("Plot") 40 | 41 | tday = t * grb.sec2day 42 | 43 | fig, ax = plt.subplots(1, 1) 44 | ax.plot(tday, FnuR, ls='-', label=r'$\nu=6$ GHz') 45 | ax.plot(tday, FnuO, ls='--', label=r'$\nu=10^{14}$ Hz') 46 | ax.plot(tday, FnuX, ls='-.', label=r'$\nu=10^{18}$ Hz') 47 | ax.set_xscale('log') 48 | ax.set_yscale('log') 49 | ax.set_xlabel(r'$t$ (d)') 50 | ax.set_ylabel(r'$F_\nu$ (mJy)') 51 | ax.legend() 52 | fig.tight_layout() 53 | 54 | print("Saving lc_multi.png") 55 | fig.savefig("lc_multi.png") 56 | plt.close(fig) 57 | -------------------------------------------------------------------------------- /examples/plotMultibandLightCurveBatch.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import afterglowpy as grb 4 | 5 | # Jet Parameters 6 | Z = {'jetType': grb.jet.Gaussian, # Gaussian jet 7 | 'specType': grb.jet.SimpleSpec, # Basic Synchrotron Emission Spectrum 8 | 9 | 'thetaObs': 0.3, # Viewing angle in radians 10 | 'E0': 1.0e53, # Isotropic-equivalent energy in erg 11 | 'thetaCore': 0.05, # Half-opening angle in radians 12 | 'thetaWing': 0.4, # Outer truncation angle 13 | 'n0': 1.0e-3, # circumburst density in cm^{-3} 14 | 'p': 2.2, # electron energy distribution index 15 | 'epsilon_e': 0.1, # epsilon_e 16 | 'epsilon_B': 0.0001, # epsilon_B 17 | 'xi_N': 1.0, # Fraction of electrons accelerated 18 | 'd_L': 1.36e26, # Luminosity distance in cm 19 | 'z': 0.01} # redshift 20 | 21 | # Time and Frequencies 22 | Nt = 100 23 | Nnu = 3 24 | t = np.empty((Nt, Nnu)) 25 | nu = np.empty((Nt, Nnu)) 26 | 27 | ta = 1.0e-1 * grb.day2sec 28 | tb = 1.0e3 * grb.day2sec 29 | nuR = 6.0e9 30 | nuO = 1.0e14 31 | nuX = 1.0e18 32 | 33 | t[:, :] = np.geomspace(ta, tb, num=100)[:, None] 34 | nu[:, 0] = nuR 35 | nu[:, 1] = nuO 36 | nu[:, 2] = nuX 37 | 38 | # Calculate! 39 | print("Calculate!") 40 | Fnu = grb.fluxDensity(t, nu, **Z) 41 | 42 | # Plot! 43 | print("Plot") 44 | 45 | tday = t * grb.sec2day 46 | 47 | fig, ax = plt.subplots(1, 1) 48 | ax.plot(tday[:, 0], Fnu[:, 0], ls='-', label=r'$\nu=6$ GHz') 49 | ax.plot(tday[:, 1], Fnu[:, 1], ls='--', label=r'$\nu=10^{14}$ Hz') 50 | ax.plot(tday[:, 2], Fnu[:, 2], ls='-.', label=r'$\nu=10^{18}$ Hz') 51 | ax.set_xscale('log') 52 | ax.set_yscale('log') 53 | ax.set_xlabel(r'$t$ (d)') 54 | ax.set_ylabel(r'$F_\nu$ (mJy)') 55 | ax.legend() 56 | fig.tight_layout() 57 | 58 | print("Saving lc_multi_batch.png") 59 | fig.savefig("lc_multi_batch.png") 60 | plt.close(fig) 61 | -------------------------------------------------------------------------------- /examples/plotSpecType.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import afterglowpy as grb 4 | 5 | # Set up a basic afterglow 6 | Z = dict(jetType=grb.jet.TopHat, 7 | specType=grb.jet.SimpleSpec, 8 | thetaObs=0.0, 9 | E0=1.0e53, 10 | thetaCore=0.1, 11 | n0=1.0, 12 | p=2.5, 13 | epsilon_e=0.1, 14 | epsilon_B=0.01, 15 | xi_N=1.0, 16 | d_L=2.0e28, 17 | z=1.0) 18 | 19 | # Make a second argument dict, with epsilon_e bar activated 20 | # The "epsilon_e" parameter is now treated as "epsilon_e_bar" 21 | # 22 | # Join specTypes with " | " 23 | Z_ebar = Z.copy() 24 | Z_ebar['specType'] = grb.jet.SimpleSpec | grb.jet.EpsEBar 25 | 26 | # Make an SED at 1 day 27 | t = 1.0 * grb.day2sec 28 | nu = np.geomspace(1.0e6, 1.0e20, 200) 29 | 30 | # Compute the flux. 31 | Fnu_p25 = grb.fluxDensity(t, nu, **Z) # epsilon_e = 0.1 32 | Fnu_ebar1_p25 = grb.fluxDensity(t, nu, **Z_ebar) # epsilon_e_bar = 0.1 33 | 34 | # Compute the epsilon_e_bar value that corresponds to the actual epsilon_e 35 | # at p=2.5 36 | eps_e = Z['epsilon_e'] 37 | p = Z['p'] 38 | eps_e_bar = eps_e * (p-2) / (p-1) 39 | 40 | # Update the args and compute the flux 41 | Z_ebar['epsilon_e'] = eps_e_bar 42 | Fnu_ebar_p25 = grb.fluxDensity(t, nu, **Z_ebar) 43 | 44 | # Now compute both models with p = 2.01 (not updating epsilon_e 45 | # or epsilon_e_bar) 46 | Z['p'] = 2.01 47 | Z_ebar['p'] = 2.01 48 | Fnu_p201 = grb.fluxDensity(t, nu, **Z) 49 | Fnu_ebar_p201 = grb.fluxDensity(t, nu, **Z_ebar) 50 | 51 | # Now just for fun, compute flux for p < 2.0. The standard model will 52 | # return an error, so only "ebar" here 53 | 54 | Z_ebar['p'] = 1.5 55 | Fnu_ebar_p15 = grb.fluxDensity(t, nu, **Z_ebar) 56 | 57 | fig, ax = plt.subplots(1, 1) 58 | 59 | ax.plot(nu, Fnu_p25, ls='--', color='C0', lw=2, 60 | label=r'Vanilla $\epsilon_e=0.1$ $p=2.5$') 61 | ax.plot(nu, Fnu_ebar1_p25, ls='-.', color='C0', lw=1, 62 | label=r'Ebar $\bar{\epsilon}_e=0.1$ $p=2.5$') 63 | ax.plot(nu, Fnu_ebar_p25, ls='-', color='C0', lw=1, 64 | label=r'Ebar $\epsilon_e=0.1$ $p=2.5$') 65 | 66 | ax.plot(nu, Fnu_p201, ls='--', color='C1', lw=2, 67 | label=r'Vanilla $\epsilon_e=0.1$ $p=2.01$') 68 | ax.plot(nu, Fnu_ebar_p201, ls='-', color='C1', lw=1, 69 | label=r'Ebar $\epsilon_e=0.1$ $p=2.01$') 70 | 71 | ax.plot(nu, Fnu_ebar_p15, ls='-', color='C2', lw=1, 72 | label=r'Ebar $\epsilon_e=0.1$ $p=1.5$') 73 | 74 | ax.set(xlabel=r'$\nu$ (Hz)', xscale='log', 75 | ylabel=r'$F_\nu$ (mJy)', yscale='log') 76 | 77 | ax.legend() 78 | 79 | figname = "example_ebar.png" 80 | print("Saving", figname) 81 | fig.savefig(figname) 82 | 83 | plt.show() 84 | -------------------------------------------------------------------------------- /examples/plotSpectrum.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import afterglowpy as grb 4 | 5 | Z = {'jetType': grb.jet.TopHat, # Top-Hat jet 6 | 'specType': grb.jet.SimpleSpec, # Basic Synchrotron Emission Spectrum 7 | 8 | 'thetaObs': 0.05, # Viewing angle in radians 9 | 'E0': 1.0e53, # Isotropic-equivalent energy in erg 10 | 'thetaCore': 0.1, # Half-opening angle in radians 11 | 'n0': 1.0, # circumburst density in cm^{-3} 12 | 'p': 2.2, # electron energy distribution index 13 | 'epsilon_e': 0.1, # epsilon_e 14 | 'epsilon_B': 0.01, # epsilon_B 15 | 'xi_N': 1.0, # Fraction of electrons accelerated 16 | 'd_L': 1.0e28, # Luminosity distance in cm 17 | 'z': 0.55} # redshift 18 | 19 | 20 | nua = 1.0e0 # Low Frequencies in Hz 21 | nub = 1.0e20 # High Frequencies in Hz 22 | 23 | t = 1.0 * grb.day2sec # spectrum at 1 day 24 | nu = np.geomspace(nua, nub, num=100) 25 | 26 | print("Calculating") 27 | Fnu = grb.fluxDensity(t, nu, **Z) 28 | 29 | print("Writing spec.txt") 30 | with open("spec.txt", 'w') as f: 31 | f.write("# t " + str(t) + ' (s)\n') 32 | f.write("# nu(Hz) Fnu(mJy)\n") 33 | for i in range(len(nu)): 34 | f.write("{0:.6e} {1:.6e}\n".format(nu[i], Fnu[i])) 35 | 36 | print("Plotting") 37 | fig, ax = plt.subplots(1, 1) 38 | 39 | ax.plot(nu, Fnu) 40 | 41 | ax.set_xscale('log') 42 | ax.set_yscale('log') 43 | ax.set_xlabel(r'$\nu$ (Hz)') 44 | ax.set_ylabel(r'$F_\nu$[1 day] (mJy)') 45 | 46 | fig.tight_layout() 47 | print("Saving figure spec.png") 48 | fig.savefig("spec.png") 49 | plt.close(fig) 50 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | 3 | requires = ["setuptools", 4 | "wheel", 5 | "numpy<2.0,<3; python_version<'3.9'", 6 | "numpy>=2.0,<3; python_version>='3.9' and python_version<'3.13'", 7 | "numpy>=2.1,<3; python_version>='3.13'"] 8 | 9 | build-backend = 'setuptools.build_meta' 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import site 3 | from setuptools import setup, Extension 4 | import numpy as np 5 | # import imp 6 | 7 | # PEP 517 Workaround for edittable user installs 8 | site.ENABLE_USER_SITE = True 9 | 10 | # version = imp.load_source('afterglowpy.version', 'afterglowpy/version.py') 11 | 12 | version = {} 13 | with open("afterglowpy/version.py", "r") as f: 14 | exec(f.read(), version) 15 | 16 | with open("README.md", "r") as f: 17 | long_description = f.read() 18 | 19 | inc = [np.get_include()] 20 | libs = [] 21 | libdirs = [] 22 | 23 | jetsources = ["afterglowpy/jetmodule.c", "afterglowpy/offaxis_struct_funcs.c", 24 | "afterglowpy/integrate.c", "afterglowpy/shockEvolution.c", 25 | "afterglowpy/interval.c"] 26 | jetdepends = ["afterglowpy/offaxis_struct.h", 27 | "afterglowpy/shockEvolution.h", "afterglowpy/interval.h"] 28 | 29 | shocksources = ["afterglowpy/shockmodule.c", "afterglowpy/shockEvolution.c"] 30 | shockdepends = ["afterglowpy/shockEvolution.h", 31 | "afterglowpy/offaxis_struct.h"] 32 | 33 | jetmodule = Extension('afterglowpy.jet', sources=jetsources, include_dirs=inc, 34 | depends=jetdepends, extra_compile_args=['-Wall']) 35 | shockmodule = Extension('afterglowpy.shock', sources=shocksources, 36 | include_dirs=inc, depends=shockdepends, 37 | extra_compile_args=['-Wall']) 38 | 39 | setup( 40 | name='afterglowpy', 41 | version=version['__version__'], 42 | author="Geoffrey Ryan", 43 | author_email="gryan@perimeterinstitute.ca", 44 | description='GRB Afterglow Models', 45 | long_description=long_description, 46 | long_description_content_type='text/markdown', 47 | license='MIT', 48 | url='https://github.com/geoffryan/afterglowpy', 49 | packages=['afterglowpy'], 50 | ext_modules=[jetmodule, shockmodule], 51 | classifiers=[ 52 | "Programming Language :: Python :: 3", 53 | "Programming Language :: C", 54 | "License :: OSI Approved :: MIT License", 55 | "Operating System :: OS Independent", 56 | "Development Status :: 4 - Beta", 57 | "Intended Audience :: Science/Research", 58 | "Topic :: Scientific/Engineering :: Astronomy"], 59 | install_requires=['numpy>=1.13', 'scipy>=0.14'], 60 | extras_require={ 61 | 'docs': ['numpydoc'] 62 | }, 63 | project_urls={ 64 | "Source Code": "https://github.com/geoffryan/afterglowpy", 65 | "Documentation": "https://afterglowpy.readthedocs.io"} 66 | ) 67 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffryan/afterglowpy/9251502434075a96d7086f38605de530350b478b/test/__init__.py -------------------------------------------------------------------------------- /test/test_flux.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import afterglowpy.flux as flux 4 | 5 | 6 | class TestFlux(unittest.TestCase): 7 | 8 | def compareArrayEqualSingle(a1, a2): 9 | return (a1 == a2).all() 10 | 11 | def compareArrayTuple(self, func, argin, out): 12 | res = func(*argin) 13 | self.assertEqual(len(res), len(out)) 14 | 15 | for i, a in enumerate(res): 16 | self.assertTrue((a == out[i]).all()) 17 | 18 | def test_checkTNu(self): 19 | a1_5 = np.arange(5) 20 | a1_4 = np.arange(4) 21 | a2_45 = np.random.rand(4, 5) 22 | a2_46 = np.random.rand(4, 6) 23 | b2_45 = np.random.rand(4, 5) 24 | s1_1 = np.array([3.0]) 25 | s1_4 = np.empty(4) 26 | s1_4[:] = 3.0 27 | s2_45 = np.empty((4, 5)) 28 | s2_45[:] = 3.0 29 | 30 | # Check singleton args 31 | self.compareArrayTuple(flux.checkTNu, (s1_1, s1_1), (s1_1, s1_1)) 32 | self.compareArrayTuple(flux.checkTNu, (3.0, s1_1), (s1_1, s1_1)) 33 | self.compareArrayTuple(flux.checkTNu, (s1_1, 3.0), (s1_1, s1_1)) 34 | self.compareArrayTuple(flux.checkTNu, (3.0, 3.0), (s1_1, s1_1)) 35 | 36 | # Check correct 1d array args 37 | self.compareArrayTuple(flux.checkTNu, (a1_4, a1_4), (a1_4, a1_4)) 38 | self.compareArrayTuple(flux.checkTNu, (s1_1, a1_4), (s1_4, a1_4)) 39 | self.compareArrayTuple(flux.checkTNu, (a1_4, s1_1), (a1_4, s1_4)) 40 | self.compareArrayTuple(flux.checkTNu, (3.0, a1_4), (s1_4, a1_4)) 41 | self.compareArrayTuple(flux.checkTNu, (a1_4, 3.0), (a1_4, s1_4)) 42 | 43 | # Check correct 2d array args 44 | self.compareArrayTuple(flux.checkTNu, (a2_45, b2_45), (a2_45, b2_45)) 45 | self.compareArrayTuple(flux.checkTNu, (s1_1, a2_45), (s2_45, a2_45)) 46 | self.compareArrayTuple(flux.checkTNu, (a2_45, s1_1), (a2_45, s2_45)) 47 | self.compareArrayTuple(flux.checkTNu, (3.0, a2_45), (s2_45, a2_45)) 48 | self.compareArrayTuple(flux.checkTNu, (a2_45, 3.0), (a2_45, s2_45)) 49 | 50 | # Check incorrect args 51 | self.assertRaises(ValueError, flux.checkTNu, a1_4, a1_5) 52 | self.assertRaises(ValueError, flux.checkTNu, a1_4, a2_45) 53 | self.assertRaises(ValueError, flux.checkTNu, a2_45, a2_46) 54 | 55 | def test_checkThetaPhiTNu(self): 56 | a1_4 = np.arange(4) 57 | a1_5 = np.arange(5) 58 | a1_6 = np.arange(6) 59 | a2_45 = np.random.rand(4, 5) 60 | a2_46 = np.random.rand(4, 6) 61 | s1_1 = np.array([3.0]) 62 | s1_4 = np.empty(4) 63 | s1_4[:] = 3.0 64 | s1_5 = np.empty(5) 65 | s1_5[:] = 3.0 66 | s1_6 = np.empty(6) 67 | s1_6[:] = 3.0 68 | s1_7 = np.empty(7) 69 | s1_7[:] = 3.0 70 | s2_45 = np.empty((4, 5)) 71 | s2_45[:] = 3.0 72 | 73 | # Check singleton args 74 | self.compareArrayTuple(flux.checkThetaPhiTNu, 75 | (s1_1, s1_1, s1_1, s1_1), 76 | (s1_1, s1_1, s1_1, s1_1)) 77 | self.compareArrayTuple(flux.checkThetaPhiTNu, 78 | (3.0, s1_1, s1_1, s1_1), 79 | (s1_1, s1_1, s1_1, s1_1)) 80 | self.compareArrayTuple(flux.checkThetaPhiTNu, 81 | (s1_1, 3.0, s1_1, s1_1), 82 | (s1_1, s1_1, s1_1, s1_1)) 83 | self.compareArrayTuple(flux.checkThetaPhiTNu, 84 | (s1_1, s1_1, 3.0, s1_1), 85 | (s1_1, s1_1, s1_1, s1_1)) 86 | self.compareArrayTuple(flux.checkThetaPhiTNu, 87 | (s1_1, s1_1, s1_1, 3.0), 88 | (s1_1, s1_1, s1_1, s1_1)) 89 | self.compareArrayTuple(flux.checkThetaPhiTNu, 90 | (3.0, 3.0, s1_1, s1_1), 91 | (s1_1, s1_1, s1_1, s1_1)) 92 | self.compareArrayTuple(flux.checkThetaPhiTNu, 93 | (3.0, s1_1, 3.0, s1_1), 94 | (s1_1, s1_1, s1_1, s1_1)) 95 | self.compareArrayTuple(flux.checkThetaPhiTNu, 96 | (3.0, s1_1, s1_1, 3.0), 97 | (s1_1, s1_1, s1_1, s1_1)) 98 | self.compareArrayTuple(flux.checkThetaPhiTNu, 99 | (s1_1, 3.0, 3.0, s1_1), 100 | (s1_1, s1_1, s1_1, s1_1)) 101 | self.compareArrayTuple(flux.checkThetaPhiTNu, 102 | (s1_1, 3.0, s1_1, 3.0), 103 | (s1_1, s1_1, s1_1, s1_1)) 104 | self.compareArrayTuple(flux.checkThetaPhiTNu, 105 | (s1_1, s1_1, 3.0, 3.0), 106 | (s1_1, s1_1, s1_1, s1_1)) 107 | self.compareArrayTuple(flux.checkThetaPhiTNu, 108 | (3.0, 3.0, 3.0, s1_1), 109 | (s1_1, s1_1, s1_1, s1_1)) 110 | self.compareArrayTuple(flux.checkThetaPhiTNu, 111 | (3.0, 3.0, s1_1, 3.0), 112 | (s1_1, s1_1, s1_1, s1_1)) 113 | self.compareArrayTuple(flux.checkThetaPhiTNu, 114 | (3.0, s1_1, 3.0, 3.0), 115 | (s1_1, s1_1, s1_1, s1_1)) 116 | self.compareArrayTuple(flux.checkThetaPhiTNu, 117 | (s1_1, 3.0, 3.0, 3.0), 118 | (s1_1, s1_1, s1_1, s1_1)) 119 | 120 | # Check correct 1d array args 121 | self.compareArrayTuple(flux.checkThetaPhiTNu, 122 | (a1_4, a1_4, a1_4, a1_4), 123 | (a1_4, a1_4, a1_4, a1_4)) 124 | self.compareArrayTuple(flux.checkThetaPhiTNu, 125 | (s1_1, a1_4, a1_4, a1_4), 126 | (s1_4, a1_4, a1_4, a1_4)) 127 | self.compareArrayTuple(flux.checkThetaPhiTNu, 128 | (a1_4, s1_4, a1_4, a1_4), 129 | (a1_4, s1_4, a1_4, a1_4)) 130 | self.compareArrayTuple(flux.checkThetaPhiTNu, 131 | (a1_4, a1_4, s1_4, a1_4), 132 | (a1_4, a1_4, s1_4, a1_4)) 133 | self.compareArrayTuple(flux.checkThetaPhiTNu, 134 | (a1_4, a1_4, a1_4, s1_4), 135 | (a1_4, a1_4, a1_4, s1_4)) 136 | self.compareArrayTuple(flux.checkThetaPhiTNu, 137 | (3.0, a1_4, a1_4, a1_4), 138 | (s1_4, a1_4, a1_4, a1_4)) 139 | self.compareArrayTuple(flux.checkThetaPhiTNu, 140 | (a1_4, 3.0, a1_4, a1_4), 141 | (a1_4, s1_4, a1_4, a1_4)) 142 | self.compareArrayTuple(flux.checkThetaPhiTNu, 143 | (a1_4, a1_4, 3.0, a1_4), 144 | (a1_4, a1_4, s1_4, a1_4)) 145 | self.compareArrayTuple(flux.checkThetaPhiTNu, 146 | (a1_4, a1_4, a1_4, 3.0), 147 | (a1_4, a1_4, a1_4, s1_4)) 148 | self.compareArrayTuple(flux.checkThetaPhiTNu, 149 | (a1_4, 3.0, 3.0, 3.0), 150 | (a1_4, s1_4, s1_4, s1_4)) 151 | self.compareArrayTuple(flux.checkThetaPhiTNu, 152 | (3.0, a1_4, 3.0, 3.0), 153 | (s1_4, a1_4, s1_4, s1_4)) 154 | self.compareArrayTuple(flux.checkThetaPhiTNu, 155 | (3.0, 3.0, a1_4, 3.0), 156 | (s1_4, s1_4, a1_4, s1_4)) 157 | self.compareArrayTuple(flux.checkThetaPhiTNu, 158 | (3.0, 3.0, 3.0, a1_4), 159 | (s1_4, s1_4, s1_4, a1_4)) 160 | 161 | # Check correct 2d array args 162 | self.compareArrayTuple(flux.checkThetaPhiTNu, 163 | (a2_45, a2_45, a2_45, a2_45), 164 | (a2_45, a2_45, a2_45, a2_45)) 165 | self.compareArrayTuple(flux.checkThetaPhiTNu, 166 | (s1_1, a2_45, a2_45, a2_45), 167 | (s2_45, a2_45, a2_45, a2_45)) 168 | self.compareArrayTuple(flux.checkThetaPhiTNu, 169 | (a2_45, s2_45, a2_45, a2_45), 170 | (a2_45, s2_45, a2_45, a2_45)) 171 | self.compareArrayTuple(flux.checkThetaPhiTNu, 172 | (a2_45, a2_45, s2_45, a2_45), 173 | (a2_45, a2_45, s2_45, a2_45)) 174 | self.compareArrayTuple(flux.checkThetaPhiTNu, 175 | (a2_45, a2_45, a2_45, s2_45), 176 | (a2_45, a2_45, a2_45, s2_45)) 177 | self.compareArrayTuple(flux.checkThetaPhiTNu, 178 | (3.0, 3.0, a2_45, a2_45), 179 | (s2_45, s2_45, a2_45, a2_45)) 180 | self.compareArrayTuple(flux.checkThetaPhiTNu, 181 | (a2_45, a2_45, 3.0, 3.0), 182 | (a2_45, a2_45, s2_45, s2_45)) 183 | 184 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 185 | a1_4, a1_4, a1_4, a1_5) 186 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 187 | a1_4, a1_4, a1_5, a1_4) 188 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 189 | a1_4, a1_5, a1_4, a1_4) 190 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 191 | a1_5, a1_4, a1_4, a1_4) 192 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 193 | a1_6, a1_4, a1_4, a1_5) 194 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 195 | a1_4, a1_6, a1_5, a1_4) 196 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 197 | a1_4, a1_5, a1_6, a1_4) 198 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 199 | a1_5, a1_4, a1_4, a1_6) 200 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 201 | 3.0, a1_4, a1_4, a1_5) 202 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 203 | a1_4, 3.0, a1_5, a1_4) 204 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 205 | a1_4, a1_5, 3.0, a1_4) 206 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 207 | a1_5, a1_4, a1_4, 3.0) 208 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 209 | a2_45, a2_45, a2_45, a2_46) 210 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 211 | a2_45, a2_45, a2_46, a2_45) 212 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 213 | a2_45, a2_46, a2_45, a2_45) 214 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 215 | a2_46, a2_45, a2_45, a2_45) 216 | self.assertRaises(ValueError, flux.checkThetaPhiTNu, 217 | 3.0, a2_45, a2_45, a1_4) 218 | 219 | def test_checkJetArgs(self): 220 | 221 | EPS = 1.0e-8 222 | Y0 = np.array([0.05, 1.0e53, 0.1, 0.4, 4, 0, 0, 0, 1.0, 2.2, 0.1, 223 | 0.01, 0.99, 1.0e28]) 224 | Z0 = {'z': 0.5} 225 | 226 | Y = Y0.copy() 227 | Z = {} 228 | for k in Z0: 229 | Z[k] = Z0[k] 230 | 231 | models = [-2, -1, 0, 1, 2, 4] 232 | s = 0 233 | 234 | for m in models: 235 | self.assertIsNone(flux.checkJetArgs(m, s, *Y0, **Z)) 236 | 237 | # theta_obs 238 | Y[0] = 0.0 239 | self.assertIsNone(flux.checkJetArgs(m, s, *Y, **Z)) 240 | Y[0] = -0.1 241 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 242 | Y[0] = 0.5*np.pi + EPS 243 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 244 | Y[0] = Y0[0] 245 | 246 | # E0 247 | Y[1] = 0.0 248 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 249 | Y[1] = -1.0 250 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 251 | Y[1] = Y0[1] 252 | 253 | # theta_C 254 | Y[2] = 0.0 255 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 256 | Y[2] = -0.1 257 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 258 | Y[2] = 0.5*np.pi + EPS 259 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 260 | Y[2] = Y0[2] 261 | 262 | # theta_W 263 | if m != -1: 264 | Y[3] = 0.0 265 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 266 | Y[3] = -0.1 267 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 268 | Y[3] = 0.5*np.pi + EPS 269 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 270 | Y[3] = Y0[3] 271 | 272 | # b 273 | if m == 4: 274 | Y[4] = 0.0 275 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 276 | Y[4] = -1.0 277 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 278 | Y[4] = Y0[4] 279 | 280 | # L0 281 | Y[5] = -1.0 282 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 283 | Y[5] = Y0[5] 284 | 285 | # t_s 286 | Y[7] = -1.0 287 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 288 | Y[7] = Y0[7] 289 | 290 | # n0 291 | Y[8] = 0.0 292 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 293 | Y[8] = -1.0 294 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 295 | Y[8] = Y0[8] 296 | 297 | # p 298 | Y[9] = 0.0 299 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 300 | Y[9] = 2.0 301 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 302 | Y[9] = 1.0 303 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 304 | Y[9] = -1.0 305 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 306 | Y[9] = Y0[9] 307 | 308 | s = 2 309 | Y[9] = 2.0 310 | self.assertIsNone(flux.checkJetArgs(m, s, *Y, **Z)) 311 | Y[9] = 1.0 312 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 313 | Y[9] = 0.0 314 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 315 | Y[9] = -1.0 316 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 317 | Y[9] = Y0[9] 318 | s = 0 319 | 320 | # eps_e 321 | Y[10] = 1.0 322 | self.assertIsNone(flux.checkJetArgs(m, s, *Y, **Z)) 323 | Y[10] = 1.0 + EPS 324 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 325 | Y[10] = 0.0 326 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 327 | Y[10] = -1.0 328 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 329 | Y[10] = Y0[10] 330 | 331 | # eps_B 332 | Y[11] = 1.0 333 | self.assertIsNone(flux.checkJetArgs(m, s, *Y, **Z)) 334 | Y[11] = 1.0 + EPS 335 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 336 | Y[11] = 0.0 337 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 338 | Y[11] = -1.0 339 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 340 | Y[11] = Y0[11] 341 | 342 | # xi_N 343 | Y[12] = 1.0 344 | self.assertIsNone(flux.checkJetArgs(m, s, *Y, **Z)) 345 | Y[12] = 1.0 + EPS 346 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 347 | Y[12] = 0.0 348 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 349 | Y[12] = -1.0 350 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 351 | Y[12] = Y0[12] 352 | 353 | # dL 354 | Y[13] = 0.0 355 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 356 | Y[13] = -1.0 357 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 358 | Y[13] = Y0[13] 359 | 360 | # z 361 | Z['z'] = -1.0 362 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 363 | Z['z'] = Z0['z'] 364 | 365 | # specific for cone 366 | m = -2 367 | Y[3] = 0.5*Y[2] 368 | self.assertRaises(ValueError, flux.checkJetArgs, m, s, *Y, **Z) 369 | 370 | def test_checkCocoonArgs(self): 371 | 372 | EPS = 1.0e-8 373 | Y0 = np.array([10.0, 1.0, 1.0e53, 5, 1.0e-5, 0, 0, 0, 1.0, 2.2, 0.1, 374 | 0.01, 0.99, 1.0e28]) 375 | Z0 = {'z': 0.5} 376 | 377 | Y = Y0.copy() 378 | Z = {} 379 | for k in Z0: 380 | Z[k] = Z0[k] 381 | 382 | models = [3] 383 | s = 0 384 | 385 | for m in models: 386 | self.assertIsNone(flux.checkCocoonArgs(m, s, *Y0, **Z)) 387 | 388 | # u_max 389 | Y[0] = 0.0 390 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 391 | Y[0] = -1.0 392 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 393 | Y[0] = Y0[0] 394 | 395 | # u_min 396 | Y[1] = 0.0 397 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 398 | Y[1] = -1.0 399 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 400 | Y[1] = Y0[1] 401 | 402 | # Ei 403 | Y[2] = 0.0 404 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 405 | Y[2] = -0.1 406 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 407 | Y[2] = Y0[2] 408 | 409 | # Mej_solar 410 | Y[4] = 0.0 411 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, 412 | *Y, **Z) 413 | Y[4] = -1.0 414 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, 415 | *Y, **Z) 416 | Y[4] = Y0[4] 417 | 418 | # L0 419 | Y[5] = -1.0 420 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 421 | Y[5] = Y0[5] 422 | 423 | # t_s 424 | Y[7] = -1.0 425 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 426 | Y[7] = Y0[7] 427 | 428 | # n0 429 | Y[8] = 0.0 430 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 431 | Y[8] = -1.0 432 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 433 | Y[8] = Y0[8] 434 | 435 | # p 436 | Y[9] = 0.0 437 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 438 | Y[9] = 2.0 439 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 440 | Y[9] = 1.0 441 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 442 | Y[9] = -1.0 443 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 444 | Y[9] = Y0[9] 445 | 446 | # eps_e 447 | Y[10] = 1.0 448 | self.assertIsNone(flux.checkCocoonArgs(m, s, *Y, **Z)) 449 | Y[10] = 1.0 + EPS 450 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 451 | Y[10] = 0.0 452 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 453 | Y[10] = -1.0 454 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 455 | Y[10] = Y0[10] 456 | 457 | # eps_B 458 | Y[11] = 1.0 459 | self.assertIsNone(flux.checkCocoonArgs(m, s, *Y, **Z)) 460 | Y[11] = 1.0 + EPS 461 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 462 | Y[11] = 0.0 463 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 464 | Y[11] = -1.0 465 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 466 | Y[11] = Y0[11] 467 | 468 | # xi_N 469 | Y[12] = 1.0 470 | self.assertIsNone(flux.checkCocoonArgs(m, s, *Y, **Z)) 471 | Y[12] = 1.0 + EPS 472 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 473 | Y[12] = 0.0 474 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 475 | Y[12] = -1.0 476 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 477 | Y[12] = Y0[12] 478 | 479 | # dL 480 | Y[13] = 0.0 481 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 482 | Y[13] = -1.0 483 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 484 | Y[13] = Y0[13] 485 | 486 | # z 487 | Z['z'] = -1.0 488 | self.assertRaises(ValueError, flux.checkCocoonArgs, m, s, *Y, **Z) 489 | Z['z'] = Z0['z'] 490 | 491 | 492 | if __name__ == "__main__": 493 | unittest.main() 494 | --------------------------------------------------------------------------------