├── .github ├── pull_request_template.md └── workflows │ └── python-package.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── Dockerfile ├── JOSS-paper ├── paper.bib └── paper.md ├── LICENSE ├── README.rst ├── examples ├── Makefile ├── airy.cpp ├── airy.py ├── burst.cpp ├── cosmology.ipynb ├── images │ ├── airy.png │ ├── bingo-singlek-k1e-5-ref.txt │ ├── bingo-singlek-k1e-5.txt │ ├── cambridge.png │ ├── caplogo.svg │ ├── cmb.png │ ├── kavli.jpg │ ├── rkwkb-single-k1e-5.txt │ ├── singlek-kd-bd-2a.txt │ └── singlek-kd-bg-2a-ref.txt ├── introduction_to_pyoscode.ipynb ├── plot_burst.py ├── pyoscode_scipy.ipynb └── test-rk.py ├── include └── oscode │ ├── interpolator.hpp │ ├── rksolver.hpp │ ├── solver.hpp │ ├── system.hpp │ └── wkbsolver.hpp ├── pyoscode ├── __init__.py ├── _pyoscode.cpp ├── _pyoscode.hpp ├── _python.hpp ├── docs │ ├── Makefile │ ├── requirements.txt │ └── source │ │ ├── conf.py │ │ ├── index.rst │ │ ├── introduction.rst │ │ ├── oscode.rst │ │ └── pyoscode.rst └── images │ ├── airy-example.png │ ├── burst-example.png │ ├── gamma.png │ ├── omega.png │ ├── oscillator.gif │ ├── oscillator.png │ └── spectra.gif ├── pyproject.toml ├── requirements.txt ├── setup.py └── tests ├── test_airy.py └── test_arrays.py /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 4 | 5 | Fixes # (issue) 6 | 7 | # Checklist: 8 | 9 | - [ ] I have performed a self-review of my own code 10 | - [ ] New and existing unit tests pass locally with my changes (`pytest`) 11 | - [ ] I have added tests that prove my fix is effective or that my feature works 12 | - [ ] Update version number 13 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | pull_request: 5 | push: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | id-token: write 10 | pages: write 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | python-version: ["3.x"] 18 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | submodules: recursive 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install -r requirements.txt 32 | pip install . 33 | - name: Install testing packages 34 | run: | 35 | # pip install flake8 36 | # pip install pydocstyle 37 | pip install pytest 38 | pip install pytest-cov pytest-xdist codecov 39 | - name: Test with pytest 40 | run: | 41 | pytest --cov=pyoscode tests 42 | - name: Upload coverage to codecov 43 | uses: codecov/codecov-action@v2 44 | 45 | # Install doxygen 46 | - name: Doxygen install 47 | uses: ssciwr/doxygen-install@v1 48 | 49 | # Build documentation 50 | - name: Docs 51 | run: | 52 | cd pyoscode/docs 53 | make html 54 | 55 | # # Upload the book's HTML as an artifact 56 | # - name: Upload artifact 57 | # uses: actions/upload-pages-artifact@v3 58 | # with: 59 | # path: "./pyoscode/docs/_build/html" 60 | # 61 | # # Deploy the book's HTML to GitHub Pages 62 | # - name: Deploy to GitHub Pages 63 | # id: deployment 64 | # uses: actions/deploy-pages@v4 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !/examples/*.py 2 | !/examples/*.ipynb 3 | !/examples/images/** 4 | !/JOSS-paper/* 5 | /examples/* 6 | !/examples/*.cpp 7 | .eggs/* 8 | .vscode/* 9 | bin/* 10 | build/* 11 | dist/* 12 | env/* 13 | lib/* 14 | lib64 15 | pyoscode.egg-info/* 16 | pyoscode/__pycache__/* 17 | pyvenv.cfg 18 | tests/__pycache__/* 19 | _pyoscode.cpython-* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/eigen"] 2 | path = deps/eigen 3 | url = https://gitlab.com/libeigen/eigen 4 | branch = 3.4 5 | -------------------------------------------------------------------------------- /.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 | build: 6 | os: ubuntu-22.04 7 | tools: 8 | python: "3.10" 9 | # You can also specify other tool versions: 10 | # nodejs: "20" 11 | # rust: "1.70" 12 | # golang: "1.20" 13 | 14 | # Required 15 | version: 2 16 | 17 | # Build documentation in the docs/ directory with Sphinx 18 | sphinx: 19 | configuration: pyoscode/docs/source/conf.py 20 | 21 | # Build documentation with MkDocs 22 | #mkdocs: 23 | # configuration: mkdocs.yml 24 | 25 | # Optionally build your docs in additional formats such as PDF 26 | #formats: 27 | # - pdf 28 | 29 | # Optionally set the version of Python and requirements required to build your docs 30 | python: 31 | install: 32 | - requirements: pyoscode/docs/requirements.txt 33 | 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | git python3-pip 5 | RUN pip3 install --no-cache --upgrade pip && \ 6 | pip3 install --no-cache notebook && \ 7 | pip3 install scipy numpy matplotlib ipywidgets jupyter_contrib_nbextensions \ 8 | hide_code cite2c RISE && \ 9 | jupyter contrib nbextension install --user && \ 10 | jupyter nbextension enable hide_code --user --py && \ 11 | python3 -m cite2c.install 12 | 13 | #RUN git clone https://github.com/eigenteam/eigen-git-mirror 14 | #ENV CPLUS_INCLUDE_PATH=${PWD}/eigen-git-mirror/:${CPLUS_INCLUDE_PATH} 15 | #RUN echo $CPLUS_INCLUDE_PATH 16 | RUN mkdir oscode 17 | RUN git clone --single-branch --recursive --branch master https://github.com/fruzsinaagocs/oscode oscode/ 18 | 19 | RUN cd oscode && \ 20 | python3 setup.py install 21 | 22 | # create user with a home directory 23 | ARG NB_USER 24 | ARG NB_UID 25 | ENV USER ${NB_USER} 26 | ENV HOME /home/${NB_USER} 27 | 28 | RUN adduser --disabled-password \ 29 | --gecos "Default user" \ 30 | --uid ${NB_UID} \ 31 | ${NB_USER} 32 | WORKDIR ${HOME} 33 | USER ${USER} 34 | 35 | # Make sure the contents of our repo are in ${HOME} 36 | COPY . ${HOME} 37 | USER root 38 | RUN chown -R ${NB_UID} ${HOME} 39 | USER ${NB_USER} 40 | -------------------------------------------------------------------------------- /JOSS-paper/paper.bib: -------------------------------------------------------------------------------- 1 | @article{scipy, 2 | author = {Virtanen, Pauli and Gommers, Ralf and Oliphant, Travis E. and 3 | Haberland, Matt and Reddy, Tyler and Cournapeau, David and 4 | Burovski, Evgeni and Peterson, Pearu and Weckesser, Warren and 5 | Bright, Jonathan and {van der Walt}, St{\'e}fan J. and 6 | Brett, Matthew and Wilson, Joshua and Millman, K. Jarrod and 7 | Mayorov, Nikolay and Nelson, Andrew R. J. and Jones, Eric and 8 | Kern, Robert and Larson, Eric and Carey, C J and 9 | Polat, {\.I}lhan and Feng, Yu and Moore, Eric W. and 10 | {VanderPlas}, Jake and Laxalde, Denis and Perktold, Josef and 11 | Cimrman, Robert and Henriksen, Ian and Quintero, E. A. and 12 | Harris, Charles R. and Archibald, Anne M. and 13 | Ribeiro, Ant{\^o}nio H. and Pedregosa, Fabian and 14 | {van Mulbregt}, Paul and {SciPy 1.0 Contributors}}, 15 | title = {{{SciPy} 1.0: Fundamental Algorithms for Scientific 16 | Computing in Python}}, 17 | journal = {Nature Methods}, 18 | year = {2020}, 19 | volume = {17}, 20 | pages = {261--272}, 21 | adsurl = {https://rdcu.be/b08Wh}, 22 | doi = {10.1038/s41592-019-0686-2}, 23 | } 24 | 25 | @article{oscode, 26 | title = {Efficient method for solving highly oscillatory ordinary differential equations with applications to physical systems}, 27 | author = {Agocs, F. J. and Handley, W. J. and Lasenby, A. N. and Hobson, M. P.}, 28 | journal = {Physical Review Research}, 29 | volume = {2}, 30 | issue = {1}, 31 | pages = {013030}, 32 | numpages = {16}, 33 | year = {2020}, 34 | month = {Jan}, 35 | publisher = {American Physical Society}, 36 | doi = {10.1103/PhysRevResearch.2.013030}, 37 | url = {https://link.aps.org/doi/10.1103/PhysRevResearch.2.013030} 38 | } 39 | 40 | @article{beyond-rkwkb, 41 | title = {{Beyond the Runge-Kutta-Wentzel-Kramers-Brillouin method}}, 42 | author = {Bamber, Jamie and Handley, Will}, 43 | journal = {Physical Review D}, 44 | volume = {101}, 45 | issue = {4}, 46 | pages = {043517}, 47 | numpages = {13}, 48 | year = {2020}, 49 | month = {Feb}, 50 | publisher = {American Physical Society}, 51 | doi = {10.1103/PhysRevD.101.043517}, 52 | url = {https://link.aps.org/doi/10.1103/PhysRevD.101.043517} 53 | } 54 | 55 | @article{pps-curved, 56 | title = {Primordial power spectra for curved inflating universes}, 57 | author = {Handley, Will}, 58 | journal = {Physical Review D}, 59 | volume = {100}, 60 | issue = {12}, 61 | pages = {123517}, 62 | numpages = {8}, 63 | year = {2019}, 64 | month = {Dec}, 65 | publisher = {American Physical Society}, 66 | doi = {10.1103/PhysRevD.100.123517}, 67 | url = {https://link.aps.org/doi/10.1103/PhysRevD.100.123517} 68 | } 69 | 70 | @article{qic, 71 | title = {Quantum initial conditions for inflation and canonical invariance}, 72 | author = {Agocs, F. J. and Hergt, L. T. and Handley, W. J. and Lasenby, A. N. and Hobson, M. P.}, 73 | journal = {Physical Review D}, 74 | volume = {102}, 75 | issue = {2}, 76 | pages = {023507}, 77 | numpages = {20}, 78 | year = {2020}, 79 | month = {Jul}, 80 | publisher = {American Physical Society}, 81 | doi = {10.1103/PhysRevD.102.023507}, 82 | url = {https://link.aps.org/doi/10.1103/PhysRevD.102.023507} 83 | } 84 | 85 | @article{petzold, 86 | author = {Petzold, Linda R.}, 87 | title = {An Efficient Numerical Method for Highly Oscillatory Ordinary Differential Equations}, 88 | journal = {SIAM Journal on Numerical Analysis}, 89 | volume = {18}, 90 | number = {3}, 91 | pages = {455-479}, 92 | year = {1981}, 93 | doi = {10.1137/0718030}, 94 | URL = {https://doi.org/10.1137/0718030}, 95 | eprint = {https://doi.org/10.1137/0718030} 96 | } 97 | 98 | @article{petzold-review, 99 | title={Numerical solution of highly oscillatory ordinary differential equations}, 100 | volume={6}, 101 | DOI={10.1017/S0962492900002750}, 102 | journal={Acta Numerica}, 103 | publisher={Cambridge University Press}, 104 | author={Petzold, Linda R. and Jay, Laurent O. and Yen, Jeng}, 105 | year={1997}, 106 | pages={437–483} 107 | } 108 | 109 | @article{bremer, 110 | title = {{On the numerical solution of second order ordinary differential equations in the high-frequency regime}}, 111 | journal = {Applied {and} Computational Harmonic Analysis}, 112 | volume = {44}, 113 | number = {2}, 114 | pages = {312 - 349}, 115 | year = {2018}, 116 | issn = {1063-5203}, 117 | doi = {10.1016/j.acha.2016.05.002}, 118 | url = {http://www.sciencedirect.com/science/article/pii/S1063520316300185}, 119 | author = {Bremer, J.}, 120 | } 121 | 122 | @article{condon-et-al-circuits, 123 | issn = {0332-1649}, 124 | doi = {10.1108/03321640910999897}, 125 | journal = {{COMPEL - The international journal for computation {and} mathematics 126 | in electrical {and} electronic engineering}}, 127 | pages = {1607--1618}, 128 | volume = {28}, 129 | publisher = {Boole Press Limited}, 130 | number = {6}, 131 | year = {2009}, 132 | url = {https://doi.org/10.1108/03321640910999897}, 133 | title = {On numerical methods for highly oscillatory problems in circuit simulation}, 134 | author = {Condon, M. and Deaño, A. and Iserles, A. and Maczyński, K.}, 135 | } 136 | 137 | @book{deano-integrals, 138 | author = {Deaño, Alfredo and Huybrechs, Daan and Iserles, Arieh}, 139 | title = {Computing Highly Oscillatory Integrals}, 140 | publisher = {{Society for Industrial {and} Applied Mathematics}}, 141 | year = {2017}, 142 | doi = {10.1137/1.9781611975123}, 143 | address = {Philadelphia, PA}, 144 | edition = {}, 145 | URL = {https://epubs.siam.org/doi/abs/10.1137/1.9781611975123}, 146 | eprint = {https://epubs.siam.org/doi/pdf/10.1137/1.9781611975123} 147 | } 148 | 149 | @article{condon-deano-iserles, 150 | title={Asymptotic solvers for oscillatory systems of differential equations}, 151 | volume={53}, 152 | DOI={10.1007/bf03322583}, 153 | number={1}, 154 | journal={SeMA Journal}, 155 | author={Condon, M. and Deaño, A. and Iserles, A.}, 156 | year={2011}, 157 | pages={79–101} 158 | } 159 | 160 | @misc{bremer-code, 161 | author = {Bremer, J.}, 162 | title = {{Phase functions: Fortran 90 code for solving highly oscillatory 163 | ordinary differential equations in O(1) time}}, 164 | year = {2018}, 165 | publisher = {GitHub}, 166 | journal = {GitHub repository}, 167 | url = {https://github.com/JamesCBremerJr/Phase-functions} 168 | } 169 | 170 | @misc{dense-output, 171 | title={Dense output for highly oscillatory numerical solutions}, 172 | author={Agocs, F. J. and Hobson, M. P. and Handley, W. J. and Lasenby, A. N.}, 173 | year={2020}, 174 | eprint={2007.05013}, 175 | archivePrefix={arXiv}, 176 | primaryClass={physics.comp-ph} 177 | } 178 | 179 | @misc{rkwkb-handley, 180 | title={{The Runge-Kutta-Wentzel-Kramers-Brillouin Method}}, 181 | author={Handley, W. J. and Lasenby, A. N. and Hobson, M. P.}, 182 | year={2016}, 183 | eprint={1612.02288}, 184 | archivePrefix={arXiv}, 185 | primaryClass={physics.comp-ph} 186 | } 187 | -------------------------------------------------------------------------------- /JOSS-paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '(py)oscode: fast solutions of oscillatory ODEs' 3 | tags: 4 | - Python 5 | - C++ 6 | - numerical methods 7 | - ordinary differential equations 8 | authors: 9 | - name: Fruzsina Julia Agocs 10 | orcid: 0000-0002-1763-5884 11 | affiliation: "1, 2" # (Multiple affiliations must be quoted) 12 | affiliations: 13 | - name: Astrophysics Group, Cavendish Laboratory, J. J. Thomson Avenue, Cambridge, CB3 0HE, UK 14 | index: 1 15 | - name: Kavli Institute for Cosmology, Madingley Road, Cambridge, CB3 0HA, UK 16 | index: 2 17 | date: 7 October 2020 18 | bibliography: paper.bib 19 | 20 | --- 21 | 22 | # Summary 23 | 24 | Oscillatory differential equations are ubiquitous in physics, chemistry and beyond. They arise in 25 | quantum mechanics, electrical circuitry, suspension systems, molecular dynamics, 26 | and in models of gravitational and electromagnetic waves. 27 | The numerical solution of such systems however can be a computational bottleneck when tackled with conventional methods 28 | available from numerical libraries. 29 | 30 | We present `(py)oscode`, a general-purpose numerical routine for solving a class of highly 31 | oscillatory ordinary differential equations (ODEs) efficiently. The package has 32 | been designed to solve equations which describe a single harmonic oscillator 33 | with a time-dependent frequency and damping term, i.e. are of the form 34 | \begin{equation}\label{eq:eom} 35 | y'' + 2\gamma(x) y' + \omega^2(x) y = 0. 36 | \end{equation} 37 | The frequency $\omega(x)$ and damping $\gamma(x)$ terms do not need 38 | to be explicit functions of $x$ (they can instead be e.g. the result of another 39 | numerical solution of an ODE), as they are supplied as sequences $\omega_j, 40 | \gamma_j$ evaluated at $x_i \leq x_j \leq x_f$, where $(x_i, x_f)$ is the 41 | integration range. 42 | 43 | `(py)oscode` is written in C++, but comes with a Python wrapper. 44 | Its Python interface was designed to be similar to those included in `SciPy`'s [@scipy] numerical ODE solution 45 | modules. This is demonstrated in the example below whose output is shown in 46 | \autoref{fig:airy}. 47 | 48 | ```python 49 | import numpy as np 50 | import scipy.special as sp 51 | import pyoscode 52 | 53 | # Set up the Airy equation as an example: y'' + xy = 0 54 | xs = np.linspace(0,40.0,5000) 55 | ws = np.sqrt(xs) 56 | gs = np.zeros_like(xs) 57 | # Initial conditions 58 | xi = 1.0 59 | xf = 40.0 60 | yi = sp.airy(-xi)[0] 61 | dyi = -sp.airy(-xi)[1] 62 | # Get dense output at the following points 63 | t_eval = np.linspace(15,35,600) 64 | # Solve the equation 65 | solution = pyoscode.solve(xs, ws, gs, xi, xf, yi, dyi, t_eval=t_eval) 66 | ``` 67 | 68 | ![Numerical solution of the Airy equation, $y'' + xy = 0$, with `pyoscode`. The 69 | increase in step-size of `pyoscode`'s internal steps (orange dots) is due to the 70 | algorithm switching from using the RK method to the WKB approximation in the presence of high-frequency 71 | oscillations. The orange segment shows dense output, the solution at these 72 | points was computed at no additional evaluations of terms in the differential 73 | equation. \label{fig:airy}](../examples/images/airy.png) 74 | 75 | # Statement of need 76 | 77 | Even if the terms in \autoref{eq:eom} change slowly, if the frequency of 78 | oscillations in the solution is high enough, standard numerical methods struggle 79 | to solve such equations quickly. Traditional methods have to trace every 80 | oscillation in the solution, taking many steps in $x$ at an enormous 81 | computational cost. The algorithm underlying `(py)oscode`, published in 82 | @oscode and based on @rkwkb-handley, can detect when the solution is oscillatory and switch to a method 83 | based on an analytic approximation (Wentzel--Kramers--Brillouin, WKB) suited for 84 | oscillatory functions, otherwise using a Runge--Kutta (RK) method. Using the WKB 85 | approximation allows the algorithm to skip over several wavelengths of 86 | oscillation in a single step, reducing the number of steps taken drastically. It 87 | adaptively updates its step-size to keep the local numerical error within a 88 | user-specified tolerance. `(py)oscode` is capable of producing a solution estimate 89 | at an arbitrary value of $x$, not just at its internal steps, therefore it can 90 | be used to generate a "continuous" solution, or dense output [@dense-output]. 91 | 92 | # Related research and software 93 | 94 | `(py)oscode`'s development was motivated by the need for a significantly more 95 | efficient solver for the evolution of early-universe quantum fluctuations. These 96 | perturbations are thought to have been stretched to macroscopic scales by a 97 | phase of accelerated expansion of the universe (cosmic inflation), to later become the 98 | large-scale structure we see today. To understand the origins of structure it 99 | is therefore essential to model the perturbations and understand the physics 100 | involved in inflation. `(py)oscode` has been used to speed up the numerical evolution of quantum 101 | fluctuations in the early universe, enabling the exploration of models beyond 102 | the standard model of cosmology [@pps-curved]. It served as inspiration for 103 | other numerical methods aiming to extend the range of oscillatory ODEs to solve 104 | [@beyond-rkwkb]. 105 | 106 | The efficient solution of oscillatory ODEs is a long-standing 107 | numerical analysis problem with many existing methods to handle certain 108 | sub-classes of equations. Examples include successful methods by Petzold [@petzold], reviewed in @petzold-review with many references therein, 109 | Iserles et al. [@condon-deano-iserles; @deano-integrals; @condon-et-al-circuits], and Bremer [@bremer], with code available from @bremer-code. 110 | 111 | # Acknowledgements 112 | 113 | I thank Lukas Hergt for invaluable discussions during the early development of 114 | `(py)oscode` and his ongoing support. Construction of the algorithm would not have been possible 115 | without the help and guidance of Will Handley, Mike Hobson, and Anthony Lasenby. 116 | I was supported by the Science and Technology Facilities Council (STFC). 117 | 118 | # References 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Fruzsina Julia Agocs 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | oscode: Oscillatory ordinary differential equation solver 3 | ======================================================================== 4 | 5 | .. image:: https://codecov.io/gh/fruzsinaagocs/oscode/branch/joss-paper/graph/badge.svg 6 | :target: https://codecov.io/gh/fruzsinaagocs/oscode 7 | :alt: codecov status 8 | .. image:: https://github.com/fruzsinaagocs/oscode/actions/workflows/python-package.yml/badge.svg 9 | :target: https://github.com/fruzsinaagocs/oscode/actions 10 | :alt: GH workflow status 11 | .. image:: https://readthedocs.org/projects/oscode/badge/?version=latest 12 | :target: https://oscode.readthedocs.io/en/latest/?badge=latest 13 | :alt: Documentation Status 14 | .. image:: https://badges.gitter.im/oscode-help/community.svg 15 | :target: https://gitter.im/oscode-help/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge 16 | :alt: Chat on gitter 17 | .. image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg 18 | :target: https://opensource.org/licenses/BSD-3-Clause 19 | :alt: BSD 3-clause license 20 | .. image:: https://img.shields.io/pypi/dm/pyoscode?color=indigo 21 | :target: https://pypi.org/project/pyoscode/ 22 | :alt: PyPI downloads 23 | .. image:: https://joss.theoj.org/papers/d4c9396ef9b2b595e2f3881a4f8a7cda/status.svg 24 | :target: https://joss.theoj.org/papers/d4c9396ef9b2b595e2f3881a4f8a7cda 25 | :alt: JOSS status 26 | .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4322958.svg 27 | :target: https://doi.org/10.5281/zenodo.4322958 28 | :alt: Zenodo doi 29 | 30 | | 31 | | 32 | 33 | .. contents:: 34 | :local: 35 | 36 | | 37 | 38 | About 39 | ----- 40 | 41 | Oscode is a C++ tool with a Python interface that solves **osc**\illatory 42 | **o**\rdinary **d**\ifferential **e**\quations efficiently. It is designed to 43 | deal with equations of the form 44 | 45 | .. image:: 46 | https://github.com/fruzsinaagocs/oscode/raw/master/pyoscode/images/oscillator.png 47 | 48 | where |gamma| (friction term) and |omega| (frequency) can be given as arrays. 49 | 50 | .. |gamma| image:: https://github.com/fruzsinaagocs/oscode/raw/master/pyoscode/images/gamma.png 51 | 52 | .. |omega| image:: https://github.com/fruzsinaagocs/oscode/raw/master/pyoscode/images/omega.png 53 | 54 | Oscode makes use of an analytic approximation of x(t) embedded in a 55 | stepping procedure to skip over long regions of oscillations, giving a reduction 56 | in computing time. The approximation is valid when the frequency changes slowly 57 | relative to the timescales of integration, it is therefore worth applying when 58 | this condition holds for at least some part of the integration range. 59 | 60 | For the details of the numerical method used by oscode, see Citation_. 61 | 62 | 63 | Installation 64 | ------------ 65 | 66 | Dependencies 67 | ~~~~~~~~~~~~ 68 | 69 | Basic requirements for using the C++ interface: 70 | 71 | - C++11 or later 72 | - `Eigen `__ (a header-only library included in this source) 73 | 74 | The strictly necessary Python dependencies are automatically installed when you use `pip` or the `setup.py`. They are: 75 | 76 | - `numpy `__ 77 | 78 | The *optional* dependencies are: 79 | 80 | - for tests 81 | - `scipy `__ 82 | - `pytest `__ 83 | - for examples/plotting 84 | - `matplotlib `__ 85 | - `scipy `__ 86 | - for generating offline documentation 87 | - `sphinx `__ 88 | - `doxygen `__ 89 | - `breathe `__ 90 | - `exhale `__ 91 | 92 | 93 | Python 94 | ~~~~~~ 95 | 96 | ``pyoscode`` can be installed via pip 97 | 98 | .. code:: bash 99 | 100 | pip install pyoscode 101 | 102 | or via the setup.py 103 | 104 | .. code:: bash 105 | 106 | git clone --recursive https://github.com/fruzsinaagocs/oscode 107 | cd oscode 108 | python setup.py install --user 109 | 110 | or 111 | 112 | .. code:: bash 113 | 114 | git clone --recursive https://github.com/fruzsinaagocs/oscode 115 | cd oscode 116 | pip install . 117 | 118 | You can then import ``pyoscode`` from anywhere. Omit the ``--user`` option if 119 | you wish to install globally or in a virtual environment. If you have any 120 | difficulties, check out the `FAQs - Installation 121 | `__ section below. 122 | 123 | You can check that things are working by running `tests/` (also ran by Travis continuous integration): 124 | 125 | .. code:: bash 126 | 127 | pytest tests/ 128 | 129 | C++ 130 | ~~~ 131 | 132 | ``oscode`` is a header-only C++ package, it requires no installation. 133 | 134 | .. code:: bash 135 | 136 | git clone --recursive https://github.com/fruzsinaagocs/oscode 137 | 138 | and then include the relevant header files in your C++ code: 139 | 140 | .. code:: c 141 | 142 | #include 143 | #include 144 | 145 | 146 | Quick start 147 | ----------- 148 | 149 | Try the following quick examples. They are available in the `examples 150 | `__. 151 | 152 | Python 153 | ~~~~~~ 154 | 155 | :Introduction to pyoscode: |intro_binder| 156 | :Cosmology examples: |cosmology_binder| 157 | :Scipy 2020 lecture notebook: |scipy_binder| 158 | 159 | .. |intro_binder| image:: https://mybinder.org/badge_logo.svg 160 | :target: https://mybinder.org/v2/gh/fruzsinaagocs/oscode/master?filepath=examples/introduction_to_pyoscode.ipynb 161 | 162 | .. |cosmology_binder| image:: https://mybinder.org/badge_logo.svg 163 | :target: https://mybinder.org/v2/gh/fruzsinaagocs/oscode/master?filepath=examples/cosmology.ipynb 164 | 165 | .. |scipy_binder| image:: https://mybinder.org/badge_logo.svg 166 | :target: https://mybinder.org/v2/gh/fruzsinaagocs/oscode/master?filepath=examples/pyoscode_scipy.ipynb 167 | 168 | 169 | .. image:: 170 | https://github.com/fruzsinaagocs/oscode/raw/master/pyoscode/images/spectra.gif 171 | :width: 800 172 | 173 | C++ 174 | ~~~ 175 | 176 | :Introduction to oscode: `examples/burst.cpp` 177 | :To plot results from `burst.cpp`: `examples/plot_burst.py` 178 | 179 | To compile and run: 180 | 181 | .. code:: bash 182 | 183 | cd examples/ 184 | g++ -I../include/ -g -Wall -std=c++11 -c -o burst.o burst.cpp 185 | g++ -I../include/ -g -Wall -std=c++11 -o burst burst.o 186 | ./burst 187 | 188 | 189 | Documentation 190 | ------------- 191 | 192 | Documentation is hosted at `readthedocs `__. 193 | 194 | To build your own local copy of the documentation you can run: 195 | 196 | .. code:: bash 197 | 198 | cd pyoscode/docs 199 | make html 200 | 201 | Citation 202 | -------- 203 | 204 | If you use ``oscode`` to solve equations for a publication, please cite: 205 | 206 | - `Efficient method for solving highly oscillatory ordinary differential equations with applications to physical systems `__, 207 | - `Dense output for highly oscillatory numerical solutions `__ 208 | 209 | Contributing 210 | ------------ 211 | 212 | Any comments and improvements to this project are welcome. You can contribute 213 | by: 214 | 215 | - Opening and `issue `__ to report bugs and propose new features. 216 | - Making a pull request. 217 | 218 | Further help 219 | ------------ 220 | 221 | You can get help by submitting an issue or posting a message on `Gitter `__. 222 | 223 | FAQs 224 | ---- 225 | 226 | Installation 227 | ~~~~~~~~~~~~ 228 | 229 | 1. Eigen import errors: 230 | .. code:: bash 231 | 232 | pyoscode/_pyoscode.hpp:6:10: fatal error: Eigen/Dense: No such file or directory 233 | #include 234 | ^~~~~~~~~~~~~ 235 | 236 | Try explicitly including the location of your Eigen library via the 237 | ``CPLUS_INCLUDE_PATH`` environment variable, for example: 238 | 239 | .. code:: bash 240 | 241 | CPLUS_INCLUDE_PATH=/usr/include/eigen3 python setup.py install --user 242 | # or 243 | CPLUS_INCLUDE_PATH=/usr/include/eigen3 pip install pyoscode 244 | 245 | where ``/usr/include/eigen3`` should be replaced with your system-specific 246 | eigen location. 247 | 248 | Thanks 249 | ------ 250 | 251 | Many thanks to **Will Handley**, **Lukas Hergt**, **Anthony Lasenby**, and **Mike Hobson** for 252 | their support and advice regarding the algorithm behind `oscode`. 253 | There are many packages without which some part of `oscode` (e.g. testing and 254 | examples) wouldn't run as nicely and smoothly, thank you all developers for 255 | making and maintaining these open-source projects. A special thanks goes to the 256 | devs of `exhale `__ for making the beautiful C++ documentation possible. 257 | 258 | 259 | Changelog 260 | --------- 261 | - 1.3.0: 262 | - Remove deprecated C API functions from the Python wrapper, `_pyoscode.cpp`. 263 | - 1.2.0: 264 | - Update the version of Eigen to 3.4.0 265 | - 1.1.2: 266 | - Dense output bug fix at the C++ interface 267 | - 1.1.1: 268 | - Support for mac and Windows OS at CI. 269 | - 1.1.0: 270 | - Users can now define w, g as functions in Python (pyoscode) and call the solver via pyoscode.solve_fn(...) 271 | - 1.0.6: 272 | - Fix issues related to dense output not being correctly generated, e.g. when timepoints at which dense output was asked for are in descending order, etc. 273 | - 1.0.5: 274 | - Fixes related to dense output generation 275 | - Support for w, g to be given as class member functions in C++ 276 | - Switched to GH actions for continuous integration, and fixed code such that unit tests would run again 277 | - Minor tweaks 278 | - 1.0.4: 279 | - set minimally required numpy version: numpy>=1.20.0 280 | - drop Python 2.7 support, instead support 3.8 and 3.9 in addition to 3.7 281 | - 1.0.3: 282 | - paper accepted to JOSS 283 | - 1.0.2: 284 | - Fixed getting correct numpy include directories 285 | - 1.0.1: 286 | - Added `pyproject.toml` to handle build dependencies (numpy) 287 | - 1.0.0: 288 | - Dense output 289 | - Arrays for frequency and damping term need not be evenly spaced 290 | - Automatic C++ documentation on readthedocs 291 | - Eigen included in source for pip installability 292 | - First pip release :) 293 | - 0.1.2: 294 | - Bug that occurred when beginning and end of integration coincided 295 | corrected 296 | - 0.1.1: 297 | - Automatic detection of direction of integration 298 | - 0.1.0: 299 | - Memory leaks at python interface fixed 300 | - C++ documentation added 301 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CXXFLAGS = -g -std=c++11 -O3 #-Wall 3 | inc = include 4 | deps = ../include/system.hpp ../include/rksolver.hpp ../include/wkbsolver.hpp ../include/interpolator.hpp ../include/solver.hpp 5 | 6 | vdp: vdp.o 7 | $(CXX) -I../include/ $(CXXFLAGS) -o $@ $^ 8 | 9 | burst: burst.o 10 | $(CXX) -I../include/ $(CXXFLAGS) -o $@ $^ 11 | 12 | issue13: issue13.o 13 | $(CXX) -I../include/ $(CXXFLAGS) -o $@ $^ 14 | 15 | issue15: issue15.o 16 | $(CXX) -I../include/ $(CXXFLAGS) -o $@ $^ 17 | 18 | airy: airy.o 19 | $(CXX) -I../include/ $(CXXFLAGS) -o $@ $^ 20 | 21 | %.o: %.cpp %.hpp $(deps) 22 | $(CXX) -I../include/ $(CXXFLAGS) -c -o $@ $< 23 | 24 | %.o: %.cpp $(deps) 25 | $(CXX) -I../include/ $(CXXFLAGS) -c -o $@ $< 26 | 27 | clean: 28 | $(RM) *.o 29 | -------------------------------------------------------------------------------- /examples/airy.cpp: -------------------------------------------------------------------------------- 1 | #include "solver.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** Defines the friction term in the ODE to solve 9 | * @param[in] t Value of the independent variable in the ODE 10 | * @returns The value of the friction term at \a t 11 | */ 12 | std::complex g(double t){ 13 | return 0.0; 14 | }; 15 | 16 | /** Defines the frequency term in the ODE to solve 17 | * @param[in] t Value of the independent variable in the ODE 18 | * @returns The value of the frequency term at \a t 19 | */ 20 | std::complex w(double t){ 21 | return std::sqrt(t); 22 | }; 23 | 24 | /** Defines the initial value of the solution of the ODE 25 | * @param[in] t Value of the independent variable in the ODE 26 | * @returns The value of the solution \a x at \a t 27 | */ 28 | std::complex xairy(double t){ 29 | return std::complex(boost::math::airy_ai(-t), boost::math::airy_bi(-t)); 30 | }; 31 | 32 | /** Defines the initial value of the derivative of the solution of the ODE 33 | * @param[in] t Value of the independent variable in the ODE 34 | * @returns The value of the derivative of solution \a dx/dt at \a t 35 | */ 36 | std::complex dxairy(double t){ 37 | return std::complex(-boost::math::airy_ai_prime(-t), -boost::math::airy_bi_prime(-t)); 38 | }; 39 | 40 | /** \brief Routine to call oscode to solve an example ODE, to illustrate basic 41 | * functionality. 42 | * 43 | * Routine to call oscode to solve the example ODE (the "burst" equation): 44 | * \f[ 45 | * \ddot{x} + \fract{n^2-1}{(1+t^2)^2}x = 0 46 | * \f] 47 | * where \f$ n \f$ is a parameter that controls how many oscillations the solution 48 | * \f$ x(t) \f$ will go through. 49 | * 50 | * The routine defines the differential equation in four different ways: 51 | * -# Defines the frequency and friction terms (\f$ \omega(t) \f$ and \f$ 52 | * \gamma(t) \f$) as functions, 53 | * -# Defines the frequency and friction terms as sequences: 54 | * -# as Eigen vectors, 55 | * -# as std::arrays, 56 | * -# as std::vectors, 57 | * and then feeds a pointer to the underlying data array to the class \a de_system representing the ODE. 58 | * 59 | * All four methods are included in this routine, with the last three commented 60 | * out. Test any of the above methods by uncommenting and commenting out all others. 61 | * 62 | * After solving the ODE, this routine prints the solution and its derivative to 63 | * a file called \a output.txt, the contents of which you can plot with the 64 | * Python script \a plot_burst.py, or any other script of your choice. 65 | */ 66 | int main(){ 67 | 68 | std::ofstream f; 69 | /** File to write solution of ODE to */ 70 | std::string output = "airy_output.txt"; 71 | std::string d_output = "airy_dense_output.txt"; 72 | std::complex x0, dx0; 73 | double ti, tf; 74 | 75 | /** Define integration range */ 76 | ti = 1.0; 77 | tf = 1e6; 78 | 79 | /** Define initial conditions */ 80 | x0 = xairy(ti); 81 | dx0 = dxairy(ti); 82 | 83 | /** We'll ask for dense output at the following points */ 84 | 85 | double a = 0.1; 86 | double t_dense_i = 5e1; 87 | std::vector t_dense(200); 88 | std::generate(t_dense.begin(), t_dense.end(), [n = 0, &a, &t_dense_i]() mutable { return n++ * a + t_dense_i; }); 89 | 90 | /** Create differential equation "system" */ 91 | /** Method 1: Give frequency and damping term as functions */ 92 | de_system sys(&w, &g); 93 | 94 | /** Method 2: Give frequency and damping term as std::vectors */ 95 | /** 96 | int N = 20000; 97 | std::vector times_arr(N); 98 | std::vector> ws_arr(N), gs_arr(N); 99 | 100 | for(int i=0; i * ws, * gs; 107 | times = times_arr.data(); 108 | ws = ws_arr.data(); 109 | gs = gs_arr.data(); 110 | de_system sys(times, ws, gs, times, N); 111 | */ 112 | 113 | /** Method 3: Give frequency and damping terms as Eigen::Vectors */ 114 | /** 115 | int N = 20000; 116 | Eigen::VectorXd times_arr = Eigen::VectorXd::Zero(N); 117 | Eigen::VectorXcd ws_arr = Eigen::VectorXcd::Zero(N), gs_arr = Eigen::VectorXcd::Zero(N); 118 | 119 | for(int i=0; i * ws, * gs; 126 | times = times_arr.data(); 127 | ws = ws_arr.data(); 128 | gs = gs_arr.data(); 129 | de_system sys(times, ws, gs, times, N); 130 | */ 131 | 132 | /** Method 4: Define frequency and damping terms as std::arrays */ 133 | /** 134 | int N = 20000; 135 | double times_arr[20000]; 136 | std::complex ws_arr[20000], gs_arr[20000]; 137 | 138 | for(int j=0; j * ws, * gs; 145 | times = times_arr; 146 | ws = ws_arr; 147 | gs = gs_arr; 148 | de_system sys(times, ws, gs, times, N); 149 | */ 150 | 151 | /** Solve the ODE */ 152 | Solution solution(sys, x0, dx0, ti, tf, t_dense); 153 | solution.solve(); 154 | 155 | /** Extract the solution and the types of steps taken by oscode */ 156 | std::list> xs = solution.sol; 157 | std::list ts = solution.times; 158 | std::list types = solution.wkbs; 159 | std::list> dosol = solution.dosol; 160 | int steps = solution.ssteps; 161 | 162 | /** Write result in file */ 163 | f.open(output); 164 | auto it_t = ts.begin(); 165 | auto it_x = xs.begin(); 166 | auto it_ty = types.begin(); 167 | f << "# Internal steps of oscode on the Airy equation" << std::endl; 168 | f << "# t, Re(x), Im(x), step type (0 = RK, 1 = WKB), Re(x_analytic), Im(x_analytic)" << std::endl; 169 | for(int i=0; i<=steps; i++){ 170 | f << *it_t << ", " << std::real(*it_x) << ", " << std::imag(*it_x) << ", " << *it_ty << ", " << boost::math::airy_ai(-*it_t) << ", " << boost::math::airy_bi(-*it_t) << std::endl; 171 | ++it_t; 172 | ++it_x; 173 | ++it_ty; 174 | }; 175 | f.close(); 176 | std::cout << "Wrote internal solver steps to " << output << std::endl; 177 | 178 | f.open(d_output); 179 | auto it_td = t_dense.begin(); 180 | auto it_xd = dosol.begin(); 181 | f << "# Dense output of oscode on the Airy solution" << std::endl; 182 | f << "# t, Re(x), Im(x), Re(x_analytic), Im(x_analytic)" << std::endl; 183 | for(int i=0; i<200; i++){ 184 | f << *it_td << ", " << std::real(*it_xd) << ", " << std::imag(*it_xd) << ", " << boost::math::airy_ai(-*it_td) << ", " << boost::math::airy_bi(-*it_td) << std::endl; 185 | ++it_td; 186 | ++it_xd; 187 | }; 188 | f.close(); 189 | std::cout << "Wrote dense output to " << d_output << std::endl; 190 | 191 | }; 192 | 193 | 194 | -------------------------------------------------------------------------------- /examples/airy.py: -------------------------------------------------------------------------------- 1 | import pyoscode 2 | import numpy as np 3 | from scipy.special import airy 4 | from matplotlib import pyplot as plt 5 | 6 | # Define the frequency and friction term over the range of integration 7 | ts = np.linspace(1,100,5000) 8 | ws = np.sqrt(ts) 9 | gs = np.zeros_like(ws) 10 | 11 | # Define the frequency and friction term as functions 12 | def w(t): 13 | return np.sqrt(t) 14 | 15 | def g(t): 16 | return 0.0 17 | 18 | # Helper function for plotting 19 | def plot_airy(sol): 20 | t = np.asarray(sol['t']) 21 | x = np.asarray(sol['sol']) 22 | dense = np.asarray(sol['x_eval']) 23 | types = np.asarray(sol['types']) 24 | # Plot the solution 25 | ana_t = np.linspace(ti,tf,5000) 26 | ana_x = np.array([airy(-T)[0] + 1j*airy(-T)[2] for T in ana_t]) 27 | plt.plot(ana_t,ana_x,color='black',lw=0.5,label='true solution') 28 | plt.plot(t[types==0],x[types==0],'.',color='C0',label='RK steps') 29 | plt.plot(t[types==1],x[types==1],'.',color='C1',label='WKB steps') 30 | plt.plot(t_eval, dense, color='C1', label='dense output') 31 | plt.legend() 32 | plt.ylim((-1.0,1.0)) 33 | plt.xlabel('t') 34 | plt.ylabel('Ai(-t)') 35 | plt.show() 36 | #plt.savefig('airy-example.png') 37 | 38 | 39 | # Define the range of integration and the initial conditions 40 | ti = 1.0 41 | tf = 100.0 42 | x0 = airy(-ti)[0] + 1j*airy(-ti)[2] 43 | dx0 = -airy(-ti)[1] -1j*airy(-ti)[3] 44 | t_eval = np.linspace(ti+20,ti+40,500) 45 | 46 | # Solve the system: w, g, arrays 47 | # Test forward integration 48 | sol = pyoscode.solve(ts, ws, gs, ti, tf, x0, dx0,t_eval=t_eval) # Uneven grid assumed, even grid given 49 | plot_airy(sol) 50 | 51 | #Solve the system: w, g functions 52 | sol = pyoscode.solve_fn(w, g, ti, tf, x0, dx0, t_eval=t_eval) 53 | plot_airy(sol) 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/burst.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /** A variable to control the number of oscillations in the solution of the burst equation */ 8 | double n = 100.0; 9 | 10 | /** Defines the friction term in the ODE to solve 11 | * @param[in] t Value of the independent variable in the ODE 12 | * @returns The value of the friction term at \a t 13 | */ 14 | std::complex g(double t){ 15 | return 0.0; 16 | }; 17 | 18 | /** Defines the frequency term in the ODE to solve 19 | * @param[in] t Value of the independent variable in the ODE 20 | * @returns The value of the frequency term at \a t 21 | */ 22 | std::complex w(double t){ 23 | return std::pow(n*n - 1.0,0.5)/(1.0 + t*t); 24 | }; 25 | 26 | /** Defines the initial value of the solution of the ODE 27 | * @param[in] t Value of the independent variable in the ODE 28 | * @returns The value of the solution \a x at \a t 29 | */ 30 | std::complex xburst(double t){ 31 | return 100*std::pow(1.0 + t*t, 32 | 0.5)/n*std::complex(std::cos(n*std::atan(t)),std::sin(n*std::atan(t))); 33 | }; 34 | 35 | /** Defines the initial value of the derivative of the solution of the ODE 36 | * @param[in] t Value of the independent variable in the ODE 37 | * @returns The value of the derivative of solution \a dx/dt at \a t 38 | */ 39 | std::complex dxburst(double t){ 40 | return 100/std::pow(1.0 + t*t, 41 | 0.5)/n*(std::complex(t,n)*std::cos(n*std::atan(t)) + 42 | std::complex(-n,t)*std::sin(n*std::atan(t))); 43 | }; 44 | 45 | /** \brief Routine to call oscode to solve an example ODE, to illustrate basic 46 | * functionality. 47 | * 48 | * Routine to call oscode to solve the example ODE (the "burst" equation): 49 | * \f[ 50 | * \ddot{x} + \fract{n^2-1}{(1+t^2)^2}x = 0 51 | * \f] 52 | * where \f$ n \f$ is a parameter that controls how many oscillations the solution 53 | * \f$ x(t) \f$ will go through. 54 | * 55 | * The routine defines the differential equation in four different ways: 56 | * -# Defines the frequency and friction terms (\f$ \omega(t) \f$ and \f$ 57 | * \gamma(t) \f$) as functions, 58 | * -# Defines the frequency and friction terms as sequences: 59 | * -# as Eigen vectors, 60 | * -# as std::arrays, 61 | * -# as std::vectors, 62 | * and then feeds a pointer to the underlying data array to the class \a de_system representing the ODE. 63 | * 64 | * All four methods are included in this routine, with the last three commented 65 | * out. Test any of the above methods by uncommenting and commenting out all others. 66 | * 67 | * After solving the ODE, this routine prints the solution and its derivative to 68 | * a file called \a output.txt, the contents of which you can plot with the 69 | * Python script \a plot_burst.py, or any other script of your choice. 70 | */ 71 | int main(){ 72 | 73 | std::ofstream f; 74 | /** File to write solution of ODE to */ 75 | std::string output = "output.txt"; 76 | std::complex x0, dx0; 77 | double ti, tf; 78 | 79 | /** Define integration range */ 80 | ti = -2*n; 81 | tf = 2*n; 82 | 83 | /** Define initial conditions */ 84 | x0 = xburst(ti); 85 | dx0 = dxburst(ti); 86 | 87 | /** Create differential equation "system" */ 88 | /** Method 1: Give frequency and damping term as functions */ 89 | de_system sys(&w, &g); 90 | 91 | /** Method 2: Give frequency and damping term as std::vectors */ 92 | /** 93 | int N = 20000; 94 | std::vector times_arr(N); 95 | std::vector> ws_arr(N), gs_arr(N); 96 | 97 | for(int i=0; i * ws, * gs; 104 | times = times_arr.data(); 105 | ws = ws_arr.data(); 106 | gs = gs_arr.data(); 107 | de_system sys(times, ws, gs, times, N); 108 | */ 109 | 110 | /** Method 3: Give frequency and damping terms as Eigen::Vectors */ 111 | /** 112 | int N = 20000; 113 | Eigen::VectorXd times_arr = Eigen::VectorXd::Zero(N); 114 | Eigen::VectorXcd ws_arr = Eigen::VectorXcd::Zero(N), gs_arr = Eigen::VectorXcd::Zero(N); 115 | 116 | for(int i=0; i * ws, * gs; 123 | times = times_arr.data(); 124 | ws = ws_arr.data(); 125 | gs = gs_arr.data(); 126 | de_system sys(times, ws, gs, times, N); 127 | */ 128 | 129 | /** Method 4: Define frequency and damping terms as std::arrays */ 130 | /** 131 | int N = 20000; 132 | double times_arr[20000]; 133 | std::complex ws_arr[20000], gs_arr[20000]; 134 | 135 | for(int j=0; j * ws, * gs; 142 | times = times_arr; 143 | ws = ws_arr; 144 | gs = gs_arr; 145 | de_system sys(times, ws, gs, times, N); 146 | */ 147 | 148 | /** Solve the ODE */ 149 | Solution solution(sys, x0, dx0, ti, tf); 150 | solution.solve(); 151 | 152 | /** Extract the solution and the types of steps taken by oscode */ 153 | std::list> xs = solution.sol; 154 | std::list ts = solution.times; 155 | std::list types = solution.wkbs; 156 | int steps = solution.ssteps; 157 | 158 | /** Write result in file */ 159 | f.open(output); 160 | auto it_t = ts.begin(); 161 | auto it_x = xs.begin(); 162 | auto it_ty = types.begin(); 163 | for(int i=0; i<=steps; i++){ 164 | f << *it_t << ", " << std::real(*it_x) << ", " << std::imag(*it_x) << ", " << *it_ty << std::endl; 165 | ++it_t; 166 | ++it_x; 167 | ++it_ty; 168 | }; 169 | f.close(); 170 | std::cout << "Wrote results to " << output << std::endl; 171 | }; 172 | 173 | 174 | -------------------------------------------------------------------------------- /examples/images/airy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/examples/images/airy.png -------------------------------------------------------------------------------- /examples/images/bingo-singlek-k1e-5.txt: -------------------------------------------------------------------------------- 1 | 5.39341 -821104 2 | 5.39846 -715724 3 | 5.40527 -309893 4 | 5.4134 319393 5 | 5.42058 715331 6 | 5.42962 724652 7 | 5.43751 304669 8 | 5.44648 -342593 9 | 5.45425 -716900 10 | 5.46369 -669801 11 | 5.47156 -250535 12 | 5.48136 403028 13 | 5.48919 714484 14 | 5.49952 589991 15 | 5.50732 164713 16 | 5.51595 -373376 17 | 5.52317 -665348 18 | 5.53423 -598498 19 | 5.54271 -183773 20 | 5.55341 425046 21 | 5.56151 675060 22 | 5.57063 589218 23 | 5.57822 263656 24 | 5.5899 -363258 25 | 5.59886 -643666 26 | 5.61016 -525996 27 | 5.61873 -151950 28 | 5.62837 329311 29 | 5.63642 591124 30 | 5.64876 537492 31 | 5.65825 171912 32 | 5.67021 -369085 33 | 5.67932 -596994 34 | 5.68956 -528039 35 | 5.69814 -242058 36 | 5.71119 309247 37 | 5.7213 565032 38 | 5.73395 475402 39 | 5.74369 150183 40 | 5.75462 -275588 41 | 5.76383 -514730 42 | 5.77762 -483100 43 | 5.78844 -169965 44 | 5.80184 300498 45 | 5.81233 515761 46 | 5.82689 417654 47 | 5.83754 113523 48 | 5.84944 -268047 49 | 5.85927 -471728 50 | 5.8749 -425157 51 | 5.88664 -136151 52 | 5.90179 295944 53 | 5.91312 471698 54 | 5.92599 416874 55 | 5.93669 196044 56 | 5.95331 -238352 57 | 5.96605 -439173 58 | 5.98221 -374540 59 | 5.99461 -125915 60 | 6.00859 203643 61 | 6.02041 392261 62 | 6.03804 379358 63 | 6.052 146921 64 | 6.06919 -210529 65 | 6.08292 -385949 66 | 6.10157 -334204 67 | 6.11567 -112701 68 | 6.13137 177496 69 | 6.14466 344349 70 | 6.16461 335855 71 | 6.18038 134322 72 | 6.19987 -178987 73 | 6.21547 -335265 74 | 6.23663 -296600 75 | 6.25273 -107020 76 | 6.27519 199115 77 | 6.29152 320441 78 | 6.31011 287468 79 | 6.32553 143761 80 | 6.3499 -147259 81 | 6.36855 -286861 82 | 6.39241 -258881 83 | 6.411 -103277 84 | 6.43712 157262 85 | 6.45643 267632 86 | 6.4783 248714 87 | 6.49675 131084 88 | 6.52486 -107025 89 | 6.54711 -232714 90 | 6.57474 -226738 91 | 6.59734 -105688 92 | 6.62724 101535 93 | 6.65109 209580 94 | 6.68276 200701 95 | 6.70769 90534.2 96 | 6.74167 -97110.6 97 | 6.76785 -188027 98 | 6.80447 -173511 99 | 6.83198 -74912.6 100 | 6.86355 63120.9 101 | 6.89077 149627 102 | 6.93021 168109 103 | 6.96332 94131 104 | 7.00209 -33503.6 105 | 7.03721 -123318 106 | 7.07775 -149520 107 | 7.11627 -98780.3 108 | 7.15684 -4670.71 109 | 7.19982 85260.9 110 | 7.23963 124569 111 | 7.28504 108302 112 | 7.32574 54335.2 113 | 7.37671 -27081.7 114 | 7.41941 -78555.5 115 | 7.48306 -99849.5 116 | 7.53747 -70044.7 117 | 7.59738 -12700.6 118 | 7.66555 46525.3 119 | 7.72752 73556 120 | 7.79984 69103.3 121 | 7.8643 42880.9 122 | 7.94749 -75.6285 123 | 8.01839 -29968.5 124 | 8.10803 -49224.1 125 | 8.21127 -47215.2 126 | 8.30527 -31852.7 127 | 8.43667 -5497.75 128 | 8.55909 13363.2 129 | 8.70878 24660.7 130 | 8.88938 25385.9 131 | 9.06998 19739.6 132 | 9.33798 10261.3 133 | 9.67062 2915.26 134 | 9.93596 156.876 135 | 10.1639 -982.855 136 | 10.3678 -1490.53 137 | 10.6284 -1784.42 138 | 10.8813 -1891.36 139 | 11.1263 -1925.2 140 | 11.354 -1932.02 141 | 11.5436 -1930.76 142 | 11.725 -1927.56 143 | 12.0751 -1920.96 144 | 12.4354 -1916.04 145 | 12.747 -1913.4 146 | 13.0613 -1911.81 147 | 13.3732 -1910.89 148 | 13.6857 -1910.38 149 | 13.9982 -1910.09 150 | 14.3109 -1909.93 151 | 14.4971 -1909.87 152 | 14.6834 -1909.83 153 | -------------------------------------------------------------------------------- /examples/images/cambridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/examples/images/cambridge.png -------------------------------------------------------------------------------- /examples/images/cmb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/examples/images/cmb.png -------------------------------------------------------------------------------- /examples/images/kavli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/examples/images/kavli.jpg -------------------------------------------------------------------------------- /examples/images/rkwkb-single-k1e-5.txt: -------------------------------------------------------------------------------- 1 | # Summary: 2 | # total steps taken: 69 3 | # of which successful: 61 4 | # of whichwkb: 5 5 | # time, x, dx, wkb, Ai(-t)+i*Bi(-t) 6 | 5.3934107868204064218 (-821103.81827479717322,0) (827601.97575051547028,82099882.191170230508) 1 (0.10839942658123903618,-0.35380561367769824965) (0.82746549501120025827,0.23568343237577851768) 7 | 6.3934107868204064218 (-253007.35580785892671,160778.19237787715974) (6211323.8793983953074,9213286.9431936778128) 1 (-0.30040055861535291948,0.18860828806674423408) (-0.48888667587930645375,-0.75266773214315119134) 8 | 6.7214211664060687923 (12540.463425311170795,-215093.78167447148007) (-5764311.0248119086027,-118874.59618375485297) 1 (-0.059181727984391120645,0.3452720569672526052) (-0.89779225170530274625,-0.14070742764448621043) 9 | 7.1206714452946631155 (-89813.804611254541669,112794.55834451015107) (2116710.2286921255291,1500541.3977230871096) 1 (0.26636812036638762846,0.21973381736002195463) (-0.57727390082774987512,0.71879205375343691031) 10 | 7.4603050095954959176 (-100263.81086025678087,-21422.128064579566853) (-173508.50925713949255,1304055.9827707863878) 1 (0.33250990652899892197,-0.077024834335047490108) (0.221578654286969573,0.90596647236698801819) 11 | 7.7793138876222629463 (73821.956563576793997,-10240.946306110099613) (-168534.55988415246247,-674287.03221903555095) 0 (0.15064089902400332188,-0.30231503716755109279) (0.84830902919188810873,0.41060104456904544801) 12 | 7.8097234228505358899 (66152.46027330866491,-29185.679587482707575) (-328283.22166126605589,-565658.19043769151904) 0 (0.12433297091317621619,-0.31369806328612603874) (0.88091479994468457893,0.33755020259561985263) 13 | 7.853334412739221726 (48283.449470048275543,-49616.771174415371206) (-474775.55707064620219,-365872.1510513573885) 0 (0.085086746124443321526,-0.32605125276771879106) (0.91671571131253992704,0.22816221048215115652) 14 | 7.898352433916640436 (25373.515976545466401,-61144.883799709074083) (-527522.80397264519706,-147646.47356696336647) 0 (0.04325021394008008585,-0.3336991851317899771) (0.93949019646996578814,0.11104622948482248401) 15 | 7.9453206866352710236 (1049.3919604124894249,-63173.550475176904001) (-495703.11706269491697,54510.162163819244597) 0 (-0.0011245796622470557048,-0.33599096056002880628) (0.94733040624697617549,-0.01372336853041747004) 16 | 7.9943929518099334786 (-21095.596891088753182,-56361.4283143508801) (-398781.31336820044089,212957.88925773458323) 0 (-0.047452908618728639745,-0.33210394365769896341) (0.93780657380324650152,-0.14457753666357645028) 17 | 8.0457594681088462352 (-38173.304188933580008,-42592.412718379724538) (-263089.92213969130535,311778.71480168070411) 0 (-0.09495439098361478647,-0.32119975226373870258) (0.90841098181014678925,-0.27938153884099625746) 18 | 8.0996369992220937917 (-48353.036987686762586,-24553.41909310398114) (-116328.2961609037884,347314.07385934394551) 0 (-0.14259726287179591564,-0.30245421289091745098) (0.8566383136420900879,-0.41526757320866980461) 19 | 8.1562752344079427758 (-51016.824628699927416,-5237.839994722231495) (17228.878542261256371,326597.10070853127399) 0 (-0.18905235615279838979,-0.27510636018111839229) (0.78012160916027650792,-0.5484907562075632681) 20 | 8.2159644867675307722 (-46707.433611384061805,12541.767781640381145) (120112.85165959100414,264231.03649731533369) 0 (-0.23264433847375437514,-0.23852905321539979999) (0.67683207827633740106,-0.67427289396264955545) 21 | 8.2790460909500644959 (-36901.303264710462827,26554.167549230609438) (183270.98794625626761,178525.2471513604396) 0 (-0.2713045139787192972,-0.19232861201291584496) (0.545365824025283219,-0.78664534946317998809) 22 | 8.3459269749946081163 (-23663.890039881574921,35408.558719769505842) (205924.66746125739883,87703.290785853809211) 0 (-0.30253194796710358716,-0.13648274817244385182) (0.38534689046116632483,-0.87830949874701569335) 23 | 8.417101085730363863 (-9257.9818084549406194,38647.620527201077493) (194011.78808536854922,6850.0556254096154589) 0 (-0.32337418675252893596,-0.071528163140589684499) (0.19798354102138721045,-0.94054658541814684103) 24 | 8.4931826475086253936 (4224.8248559285348165,36680.549854361292091) (157755.5617153076164,-53984.530403726006625) 0 (-0.33044529387291304845,0.0011884370417921670364) (-0.013176411424215522805,-0.96322820373041828645) 25 | 8.574961188165119097 (15151.575236391927319,30588.503668274650408) (108970.9558598320582,-90396.376804594969144) 0 (-0.32000786006886405843,0.07917897578949505133) (-0.24123321466071287467,-0.93500605658923408026) 26 | 8.6635002228762836296 (22517.207138674104499,21852.661823209418799) (58661.467534315837838,-103090.21311802789569) 0 (-0.28815706630234605434,0.1583790086620835702) (-0.47458482587763367588,-0.84379541704559590798) 27 | 8.7603345017087441704 (25982.85515236746869,12062.042979386887964) (15303.009260323691706,-96526.49592350742023) 0 (-0.23115580566021518005,0.23256616586579748063) (-0.6950928618617814303,-0.67770222743262920062) 28 | 8.8679298397989310132 (25792.007434085528075,2654.0541051100080949) (-15992.164178680082841,-77174.795273324212758) 0 (-0.14596270764523128216,0.29250903356364865715) (-0.87536832746960502316,-0.42652499195031867707) 29 | 8.9910379077355955246 (22580.138766167408903,-5272.0522955310170801) (-33422.624742133513791,-51766.730976440696395) 0 (-0.030868662476327187599,0.32431555511093945743) (-0.97352741987295532411,-0.08357344909099877206) 30 | 9.129602261469115021 (17517.066128291524365,-10691.217905182365939) (-37667.912257530850184,-27622.636281702612905) 0 (0.10278986400646004939,0.30783145517045051864) (-0.92749870234380105138,0.31906464017242669451) 31 | 9.2708684757514614461 (12426.379714811631857,-13307.749810714434716) (-33550.555608825059608,-10715.160791649428575) 0 (0.22057240094881791492,0.23636665210640583901) (-0.71389102962329720192,0.67809867825505254491) 32 | 9.4118060428079388657 (8178.313304586711638,-14046.391269909518087) (-26563.833820077448763,-758.37610867040348239) 0 (0.29800500071917107592,0.12218738979221653584) (-0.36701769337515166569,0.91765222584443850717) 33 | 9.5520968486588095914 (4948.2471705214211397,-13754.82561323182199) (-19618.889475704781944,4269.9506516548535728) 0 (0.3205979454430690212,-0.013781260728949445982) (0.050982433833601577267,0.9906716137297683078) 34 | 9.6929624375727101437 (2610.9036929149610842,-12985.964266528408189) (-13796.952136276233432,6265.9389327038570627) 0 (0.28359220200651347188,-0.14764223788865676879) (0.46704778070390967626,0.87926926366616997566) 35 | 9.8340584493012244138 (992.97197051234888932,-12067.353703020467947) (-9364.417638081642508,6556.5342040766881837) 0 (0.19275513069888353113,-0.25363946980267360054) (0.80042129014591856251,0.59812392082321730324) 36 | 9.9762887905047090698 (-97.762470124144783767,-11167.75408267163948) (-6163.5833961259277203,6004.7051182896020691) 0 (0.063728804703353930661,-0.31096753985866665282) (0.98394871651640836152,0.19353531764799325465) 37 | 10.120024972391338025 (-813.70127693376684874,-10366.77736118753819) (-3944.0705935035557559,5114.1921496578861479) 0 (-0.079343247034660391304,-0.30618515387893197044) (0.97222386760493817448,-0.26000160627898860621) 38 | 10.265437041856035449 (-1271.2895667451887221,-9692.8437087083893857) (-2455.0051252426819701,4161.2422065032696992) 0 (-0.20731485925265807913,-0.23739159090855832535) (0.75566103239206472431,-0.67010301151316831181) 39 | 10.41255377909079094 (-1555.6914420953694389,-9146.8068728129892406) (-1484.3606938197794989,3282.3984529931294674) 0 (-0.29167293474099004191,-0.11643657838292705009) (0.36877795228970661201,-0.94410763028337962854) 40 | 10.561307024310426783 (-1727.0362105271299242,-8716.1272153985119076) (-868.28856870346851338,2532.8481574318184357) 0 (-0.31131893889942247045,0.031852803952719388469) (-0.11089295469468317401,-1.0111098147480774845) 41 | 10.711562833842522835 (-1826.4930939557309557,-8383.1166728271728061) (-487.36601843907925513,1923.5911991948053128) 0 (-0.25876394420039455779,0.1740302503447270499) (-0.57568231280208270739,-0.84294490811629907423) 42 | 10.86314752519516702 (-1881.4733701242566895,-8129.4520460817511776) (-258.21662015338517904,1444.015501740913578) 0 (-0.14372017592452990264,0.27551670788686977165) (-0.91149898698384956841,-0.4674130518950893598) 43 | 11.015870040678018782 (-1909.7518093464095728,-7938.408049292746) (-124.59109708658473892,1074.8732322518842466) 0 (0.0075487504054938632178,0.30957506464350953435) (-1.0274333203410102033,0.032078106881179291321) 44 | 11.169540022548654079 (-1922.5585068569653231,-7795.7539852890722614) (-49.622271950238612703,795.22660056609652202) 0 (0.15766607666532833631,0.26528028260823566242) (-0.8831621658566978228,0.5329261627710799587) 45 | 11.323980736434375416 (-1926.8017233762716387,-7689.9134666611953435) (-9.7554404407255219667,585.78457985624743287) 0 (0.26741347941334470129,0.15189154399722151756) (-0.50528706564727554618,0.90332414862085652629) 46 | 11.479036699327673787 (-1926.6099971524840839,-7611.7559102613022333) (9.7079068874282548052,430.19848046487516058) 0 (0.30648943241656922964,-0.0021633193869312027091) (0.014001102264844913006,1.0384684222822688771) 47 | 11.634576737502417387 (-1924.3690537188779217,-7554.2370604130937863) (17.731566999256433093,315.28629297465607806) 0 (0.26264847822914572095,-0.15596974554723733175) (0.53769795848448442843,0.89261995347696865277) 48 | 11.790493721892849166 (-1921.3996508534241912,-7512.0086656679195585) (19.652839086801087376,230.75643072218645102) 0 (0.14630160371649139628,-0.26699805552079308724) (0.91998647960375246058,0.49674974968149915311) 49 | 11.946702299135479208 (-1918.3893673539155316,-7481.0572133424448111) (18.564939138926220608,168.74682088474543207) 0 (-0.011204853374828066884,-0.30324729308068876232) (1.0480055117609421433,-0.045074361238642085548) 50 | 12.103135702329417711 (-1915.6610063479424753,-7458.3961201525635261) (16.201343044270693383,123.34058220545985307) 0 (-0.16561979644973645609,-0.25309650211168799316) (0.87716986716238598554,-0.58145984424297358917) 51 | 12.259742382253120851 (-1913.3354566402524597,-7441.8163397618982344) (13.489506435219722036,90.130629970106028281) 0 (-0.2721730024047838703,-0.12970621389980754867) (0.44864334847181608357,-0.9557085154071380817) 52 | 12.416482884724748814 (-1911.4280300873724627,-7429.6908636755797488) (10.890872728225090782,65.857782851986101491) 0 (-0.29886047372013113677,0.031764320243340686545) (-0.11795162831683565774,-1.0525421076033125711) 53 | 12.573327166800410737 (-1909.9038063149944264,-7420.8248778810757358) (8.6057366545955726878,48.123484882436741827) 0 (-0.23662010847665720936,0.18377322335462575875) (-0.65639313653870590137,-0.83544211422640468623) 54 | 12.730252400215757547 (-1908.7082686260002902,-7414.3426886350180212) (6.6942426418608098615,35.168297889837283776) 0 (-0.10304203986160719031,0.28033784455551341575) (-1.0023291094003745627,-0.36217357662333221624) 55 | 12.887241231904242866 (-1907.7833888973364083,-7409.6033151800511405) (5.1462258578546808963,25.704342928621016995) 0 (0.061971731377100401639,0.29124165725139206096) (-1.0443972518265494109,0.22813465783289649247) 56 | 13.044280437569593545 (-1907.0753710756594046,-7406.1379539442223177) (3.9203694299167359816,18.790164036348123489) 0 (0.20775397772244952321,0.21205045828386639983) (-0.76193361033727824427,0.75445734733074487632) 57 | 13.201359895887108564 (-1906.5377750455243131,-7403.6038859102245624) (2.9652874716595754023,13.73807539137963829) 0 (0.28841130215410598625,0.066485940544804222885) (-0.2361246879726885961,1.0492338960294449457) 58 | 13.358471815982843722 (-1906.1322049919347137,-7401.7506223030268302) (2.2301850495699957477,10.04595382545078941) 0 (0.27761180144631586453,-0.10008336097732528636) (0.37101444130578403735,1.0128442395528234332) 59 | 13.515610159400324619 (-1905.8278126361708473,-7400.3950966780012095) (1.6696685210661912357,7.347217346749510547) 0 (0.17777340766253116322,-0.23446551569251658509) (0.86532076732888696391,0.64926474521816934793) 60 | 13.67277020976699653 (-1905.600308543083429,-7399.4035129918420353) (1.2453954044458437345,5.3742299449388193366) 0 (0.020299365503805153615,-0.29268851722614669386) (1.0827029155362697388,0.069715244980866009072) 61 | 13.829948252807984233 (-1905.4308485209874107,-7398.6780758070935917) (0.92611420762757212799,3.9315688361067770984) 0 (-0.14374975201291137483,-0.25480242414956755237) (0.94503381623396220945,-0.53922165303699498562) 62 | 13.987141338744052632 (-1905.3049755028903292,-7398.1472943781991489) (0.68696559217931829622,2.8765126906673836871) 0 (-0.25998127252118330421,-0.13234755450572011082) (0.49035476283892631688,-0.97473417998282296804) 63 | 14.14434710520865579 (-1905.2116936722336504,-7397.7588997083730646) (0.50851737300785260221,2.104806116393703519) 0 (-0.28886712766882555181,0.034464560290463179537) (-0.13472882561089927211,-1.0858505049777296136) 64 | 14.301563644921092333 (-1905.1426964875765862,-7397.474670661124037) (0.3757758064668654252,1.5402748152547907079) 0 (-0.21968990987710301788,0.18947837420128882391) (-0.72043541802467669921,-0.82754348873442407086) 65 | 14.458789406307451131 (-1905.0917429188514234,-7397.2666543421355527) (0.27728609926862363144,1.1272492686582029631) 0 (-0.075175876768279484286,0.27938428714714308443) (-1.0637050661907139215,-0.28103968240258864197) 66 | 14.616023118407794001 (-1905.0541643124718121,-7397.1144046564222663) (0.20436457620482001429,0.82503636518713940884) 0 (0.09532045986988306463,0.27234110534468958242) (-1.0396068924755035301,0.36909385467799399327) 67 | 14.683379366794873988 (-1905.0412625900708008,-7397.062387488920649) (0.17926681058982921191,0.72180313892460812308) 61 (0.16142361922875850255,0.23876131001506123441) (-0.9122045384592982753,0.62265197658049875606) 68 | -------------------------------------------------------------------------------- /examples/images/singlek-kd-bd-2a.txt: -------------------------------------------------------------------------------- 1 | # Summary: 2 | # total steps taken: 108 3 | # of which successful: 78 4 | # of whichwkb: 17 5 | # time, x, dx, wkb, Ai(-t)+i*Bi(-t) 6 | 1.0000085000047143069 (-780849.91910124069545,0) (780818.47214881808031,4099592.8151532257907) 0 (0.53556096963787325205,0.10399235429757051896) (-0.010156014826898270009,0.59237651038345051635) 7 | 1.061816055244710677 (-689265.08190051361453,248376.37419878030778) (2212134.7917415918782,3847213.0829211152159) 0 (0.53514456560763179471,0.067200375737614281846) (0.023966750075759005179,0.59782062895628274024) 8 | 1.1218836016775537434 (-512490.96611996419961,457012.75600943702739) (3661082.0641578892246,2981650.7707228008658) 0 (0.53266178927177132429,0.031183218690173427484) (0.05899340517120911509,0.60103708748512807425) 9 | 1.1759563808488731063 (-283571.64086904248688,582455.98579121287912) (4741546.6608257964253,1549979.9321470125578) 0 (0.52858622150221190594,-0.0013502854058314976444) (0.091966817563412836534,0.60195610817843192564) 10 | 1.2251961833281279635 (-35638.505344550241716,615355.89590650657192) (5219219.2426797039807,-290641.35064717428759) 0 (0.52329611192064684477,-0.030974156242184153892) (0.12306490398995018065,0.60099444168499260854) 11 | 1.2704233874760482159 (197072.88002836398664,557718.95650603086688) (4932380.9256853628904,-2285898.901006153319) 0 (0.51706894391443714021,-0.058104611119692263499) (0.15242656203971416407,0.59847570370300318121) 12 | 1.3122540455894247113 (383474.58684305712814,423106.37667279713787) (3834120.0113198440522,-4118102.3447318868712) 0 (0.51011432286779667677,-0.08306453714492834095) (0.18017373392643817875,0.59465853980370098064) 13 | 1.3511306235373670148 (499691.90096402232302,235203.5696906291123) (2019273.4087409167551,-5456463.9446773845702) 0 (0.50260136282044387546,-0.1060919004416626954) (0.20639216179318112254,0.58975834812605498758) 14 | 1.3873731083430023148 (532773.82530554290861,24661.391189861955354) (-273224.25834551593289,-6021875.9579463731498) 0 (0.49467352783588997012,-0.12736483192750394644) (0.23113993141684813226,0.58396249528635202353) 15 | 1.4212392898190209234 (482808.96967420110013,-175800.50575040906551) (-2692862.1993407821283,-5650738.995540718548) 1 (0.48645108982829349253,-0.1470339609238675016) (0.25447267933675915552,0.57743474332259603354) 16 | 1.4558326784773962803 (348300.60222787340172,-349000.9329844781314) (-5012323.7438462041318,-4169443.1452897926793) 1 (0.47723356831530178157,-0.16687762048955945282) (0.27845352239703963226,0.56962092148898035848) 17 | 1.5256996063786227857 (-95865.357250766392099,-448233.86290777934482) (-6749817.9905657861382,1832805.0945339426398) 1 (0.45608065289153731925,-0.20602495650181615328) (0.3270791992114724378,0.55017332307948496606) 18 | 1.7437534277052431086 (345448.75170612020884,142895.07590524762054) (2988613.1453282544389,-8032995.8465468101203) 1 (0.36846068640683016238,-0.31666573483297977276) (0.47464716352586744375,0.45596558753077337522) 19 | 1.9249939780076021911 (-9735.78090113842336,307391.76417250244413) (10423708.053506212309,67579.796123361695209) 1 (0.27243307671955618865,-0.38914126180523939125) (0.58145822540567904113,0.33784626951571067321) 20 | 2.2061396737428076165 (-10630.271771344363515,233280.05190264145494) (13792342.485716147348,454437.56867830536794) 1 (0.091928126627540310278,-0.4509331002673469535) (0.68751677380345077584,0.090133631465889471612) 21 | 2.3713810398448247341 (-194861.29856557660969,56128.737395777534402) (4814962.8873943686485,15324282.603548204526) 1 (-0.023321692606251834962,-0.45182367726743011271) (0.70029483447079143854,-0.081473456231990801224) 22 | 2.5089821667849490083 (-174460.56502424532664,46294.090936168424378) (5074127.4084696825594,17693411.4665889889) 1 (-0.11841108956699174237,-0.43039902698981918894) (0.67625735742594883515,-0.23012521591180390423) 23 | 2.6700665269077847874 (-142077.14732205771725,76910.200865973034524) (10670975.013076135889,18820541.094172023237) 1 (-0.2222146884243328957,-0.3795129020328220637) (0.60458843302945075582,-0.39988704643872180489) 24 | 2.8769127449660820695 (-88744.509378352115164,124697.93539964448428) (21800254.620687719434,15120945.503989592195) 1 (-0.33192011880785882161,-0.27640494661315800906) (0.44365367054256293899,-0.58954491148521737109) 25 | 3.1127977273538625091 (-174581.43126605448197,-24278.307300835203932) (-4569127.7411082591861,31835629.442773021758) 1 (-0.40677247581110825836,-0.11875577682893599663) (0.17869606480236630008,-0.73035594554305582893) 26 | 3.4136041293466097279 (145714.12279622684582,201576.53451871770085) (32690735.190085213631,-22449156.616264540702) 1 (-0.40022391808244539568,0.10713073737754816306) (-0.22736773558241965554,-0.73446838065892183778) 27 | 4.1564138831598853585 (-296795.94631842081435,308027.26995181344682) (25172615.182686932385,23529101.93914379552) 1 (0.054807669647537783641,0.39090011398997764891) (-0.7953434674992496678,0.13520067765703272511) 28 | 5.2544894552170280377 (103085.93065334936546,-180963.25049470947124) (-5072547.8571829171851,-2592438.0121357883327) 1 (0.21584826059043346613,-0.30352248168594986311) (0.70669367705141750147,0.48095114248039610994) 29 | 5.4173102698462685112 (71231.943923435275792,163701.02831914569833) (3738437.5377817740664,-1750044.9878130566794) 1 (0.088466430480798191605,-0.35888978995653342263) (0.84018390833217226898,0.1896364497405533811) 30 | 5.8802293459411343335 (55608.241960229119286,-98094.380163753841771) (-1500128.6518501287792,-701542.22487303812522) 1 (-0.27432735112750517015,-0.2364562369233383643) (0.56220955636935976418,-0.6757325850319350069) 31 | 6.1911861350695991391 (17771.154148160225304,80811.23167625136557) (849682.30598452454433,-261304.22956940330914) 1 (-0.35704975406928646242,0.018967274817795410496) (-0.06158748518906830699,-0.88822842142423241718) 32 | 6.500626634830134698 (7249.1979292968235313,-60271.16412659578782) (-480349.20469636662165,2179.2243554017300085) 0 (-0.23759704966976233353,0.26138653230618269507) (-0.6759211641904113721,-0.59610671580542773373) 33 | 6.5374333655469651916 (-9547.2903801736756577,-57842.625486779041239) (-427213.59963498893194,125044.69026380724972) 0 (-0.21170778641556395328,0.28214269962435795103) (-0.72986198682532932303,-0.53084799936027571299) 34 | 6.5893181824975712857 (-28905.351680029401905,-47933.028755284001818) (-313742.91383818961913,246509.03899456304498) 0 (-0.17208534587026544371,0.30711899401591347125) (-0.79529784751791077202,-0.43036094059340335161) 35 | 6.6431405829764749171 (-42171.601406358895474,-32723.395575478913088) (-178709.88571214533295,308380.74616524914745) 0 (-0.1277728604759978126,0.32727365633349475571) (-0.84876252979944433186,-0.31722127489721269056) 36 | 6.699704775406583046 (-48427.267108196756453,-14864.738247486013279) (-45785.165493332518963,314366.26511205692077) 0 (-0.078574581178610006238,0.34167151700256254143) (-0.8877528069983451342,-0.19077464580191272336) 37 | 6.7593607388661274271 (-47661.887422158884874,2892.1044557604327565) (65573.28651228833769,274919.0699787408812) 0 (-0.02488833101614544227,0.34892978379147310264) (-0.90854635026774910322,-0.051871884036558492392) 38 | 6.8225095945555267463 (-40851.186120911734179,18152.708288083558728) (143136.61747406318318,205512.55298297468107) 0 (0.032562444726960029784,0.34748398591753509868) (-0.90687801783286059454,0.097790501694068804328) 39 | 6.8895573495153223931 (-29709.138546058475185,29172.106987628420029) (182486.56797633317183,123285.14641341680544) 0 (0.092555029357783549782,0.33562858405385820326) (-0.87802430589486657464,0.2551977109760225626) 40 | 6.960938904423375817 (-16351.319577681779265,35051.239924332876399) (186372.7339615393721,43877.883782407574472) 0 (0.15323644967920385662,0.31162508443854236573) (-0.81706754101272915936,0.41563965268734925917) 41 | 7.0371953829015119197 (-2905.1757731439283816,35774.735049563314533) (162832.01202983877738,-21048.957354130594467) 0 (0.21201920121660933205,0.27383581843319027005) (-0.71923413889686915734,0.57239069111429441428) 42 | 7.1190809601944140539 (8835.1787527288342972,32074.028981315903366) (122561.27731682544982,-65048.156873878928309) 0 (0.26544757875472185793,0.22087506312450200241) (-0.58028506119068890001,0.7162973102074658982) 43 | 7.2076836686115370867 (17626.153965780908038,25176.095710705736565) (76355.521455141177285,-86761.411412578891031) 0 (0.30896826519635395281,0.15183360119243483055) (-0.39710793073247191431,0.83508757391193799702) 44 | 7.3046051455246443496 (22848.037113777736522,16524.370659630127193) (33212.454025505685422,-88843.564225350535708) 0 (0.33657372042918998201,0.066678124712946276742) (-0.16879080781582667115,0.9122971076241461752) 45 | 7.4123837143334476707 (24461.952351097355859,7527.0396456847793161) (-721.55980240323697217,-76448.733753281165264) 0 (0.34025987190406892235,-0.033075756158860519784) (0.10153538655284202741,0.92562054036589136441) 46 | 7.5358534169448212481 (22866.405630378500064,-648.0639961106771807) (-22410.858950588830339,-55610.894921206010622) 0 (0.30881136601534642239,-0.14334247187625798192) (0.40386190584789150027,0.84329578170369468015) 47 | 7.6748718344510491463 (18998.422267730758904,-6787.6661165590576275) (-31088.460140049544862,-33453.151991323116818) 0 (0.23168228537149043733,-0.24735090383295788019) (0.69301647838205182595,0.63402186165991747391) 48 | 7.816167356206999095 (14585.278677600403171,-10251.401053277775645) (-30313.752903394364694,-16638.277695303404471) 0 (0.1186365564194096639,-0.3158222190089587933) (0.88703122958333713299,0.32170282144409773073) 49 | 7.9574043456139706976 (10621.97636093885194,-11774.83974612286147) (-25458.887661537257372,-5844.8025248508292862) 0 (-0.012568929692164047818,-0.33563018684551487736) (0.94667235650467385089,-0.045991687661639552021) 50 | 8.0981156652143972963 (7444.7580091435484064,-12122.639964940366553) (-19708.745898312030477,262.06295774850968883) 0 (-0.14129269744724440305,-0.30308313683749282808) (0.85838723435119901328,-0.41153712559759214606) 51 | 8.2393329782222934909 (5041.8115621165616176,-11844.627748409651758) (-14473.818883886739059,3270.2304312233741257) 0 (-0.24792684842026621594,-0.22224870718990849539) (0.63061490927061791822,-0.718585094752293152) 52 | 8.380810315947643474 (3305.2552362999767865,-11286.006682837467451) (-10257.251342099680187,4394.8451478742672407) 0 (-0.31441429528748376931,-0.10520290195097410646) (0.29527464494063554445,-0.9135912106058972082) 53 | 8.5233087808830774179 (2082.4173051165289507,-10644.720467421260764) (-7072.2190388467333833,4485.5479432352876756) 0 (-0.32877458283756849644,0.030164863579262013449) (-0.097716492355680598103,-0.95920517577918340546) 54 | 8.6671390782111448203 (1240.4681765791642647,-10025.717643120482535) (-4770.3989603350228208,4069.3726068927744564) 0 (-0.28641362357153465901,0.16144031393337635039) (-0.48364354743387644175,-0.83875311885858649941) 55 | 8.8124678169845793718 (671.64426459382150369,-9477.8608848527001101) (-3159.8422551196381391,3455.8877149299305529) 0 (-0.19231052973622106417,0.26498795225371202244) (-0.79226490876541644237,-0.56351168350589797562) 56 | 8.959360584452188192 (293.61544170825249012,-9017.7570766078897577) (-2060.9029441731263432,2813.9030692272999659) 0 (-0.061522154294146709497,0.32021195782349759495) (-0.96038628879259713145,-0.17526552030535860749) 57 | 9.1078036964544164533 (46.043179521906722584,-8644.7147654206291918) (-1326.1560144308755298,2226.033702439449371) 0 (0.082363618326444357209,0.3141146428177947203) (-0.94590806729817300536,0.25722900373297791221) 58 | 9.2577174441612335443 (-113.94029708253077615,-8349.7775660350653197) (-843.19220933089980008,1725.0428172770148194) 0 (0.21100977942960222333,0.24509258856923457626) (-0.74018558791587663048,0.6487655917649984838) 59 | 9.408971292403300879 (-216.05125577746667886,-8120.9181832632593796) (-530.31628368125188899,1316.777200563624092) 0 (0.29695334219910918261,0.12478405191180488853) (-0.37495332913022877408,0.91435808900592552284) 60 | 9.5614020660602019319 (-280.47078377029072271,-7945.8446314130860628) (-330.17578851682230834,993.95982272840183214) 0 (0.31999099295984584357,-0.022992703545774848678) (0.079467422218117561572,0.9890363237283259501) 61 | 9.7148327463897281575 (-320.66194570014226883,-7813.3739315403954606) (-203.57146186104338881,744.07383326622903041) 0 (0.27272798122252200903,-0.16651487808672113533) (0.5261033392485316984,0.84591926267942729734) 62 | 9.8690888106537144608 (-345.46474340769145783,-7713.9782163541112823) (-124.28466986408700734,553.58614625543032162) 0 (0.16360928671274768686,-0.27301908803272478821) (0.86197175615586418829,0.50715423968464368176) 63 | 10.02401001563689853 (-360.60173898945390647,-7639.8769059466449107) (-75.089831796538504705,409.99360635632086769) 0 (0.016227874315217650364,-0.31663688319865684884) (1.0030544162366994421,0.043496895172032458421) 64 | 10.179457071006893898 (-369.7305930337843165,-7584.9010642702924088) (-44.837660245601341558,302.63460616448440987) 0 (-0.13536329389060042239,-0.28535812294244400356) (0.90725549158483842493,-0.4389458941796833269) 65 | 10.335313915718899125 (-375.16262693292327413,-7544.2628270248660556) (-26.40176283280625924,222.84561448588578969) 0 (-0.25449086962943767709,-0.18502036068120633616) (0.588747503779681991,-0.82273918506070631285) 66 | 10.491486920008819439 (-378.34336075738701766,-7514.3040797411968015) (-15.275211539458865673,163.80484295962190799) 0 (-0.31103316257931218347,-0.038955437049751295842) (0.11879030298124612641,-1.0085174050657577105) 67 | 10.647902355916261996 (-380.1681847285348681,-7492.2621273799159098) (-8.6336088705050002545,120.25490763274615347) 0 (-0.2895631738568799074,0.11699794514702578641) (-0.38862006257644821083,-0.94225427144369255927) 68 | 10.804503179513709199 (-381.18659025101112547,-7476.0683218114427291) (-4.7212166412283931294,88.204493082108712088) 0 (-0.19417155455689269483,0.24315400878710385912) (-0.80384045111059043709,-0.63270265488357346761) 69 | 10.961245792627810403 (-381.73260985823941382,-7464.1834340933537533) (-2.4547406630662766958,64.655522946957859176) 0 (-0.048391352314063322548,0.3062522729031326163) (-1.0151559416061346486,-0.15325200394610480337) 70 | 11.118097136240914224 (-382.00722455054562943,-7455.4674295867998808) (-1.1706531716027490653,47.372987283718074991) 0 (0.11013572714895815408,0.28865594797933236126) (-0.96012326770413725097,0.37376240420116957353) 71 | 11.275032252614394679 (-382.12994107980989611,-7449.0787631105440596) (-0.46561172128396577374,34.699708706271678693) 0 (0.23917758987184320474,0.19385571582658475687) (-0.64570580862382365162,0.80750092240298232404) 72 | 11.432032328097070817 (-382.17080689129653592,-7444.3977298842746677) (-0.096479864141159443758,25.411677233007516463) 0 (0.30327167963593826272,0.046470926543299907741) (-0.15051266744930449915,1.0265232264504082771) 73 | 11.58908316758926027 (-382.17013002900267793,-7440.9687795688023471) (0.081882398719995391767,18.607299719125784065) 0 (0.28386061271123524952,-0.11365208701242228428) (0.3930614040574283985,0.9639859319510315494) 74 | 11.746174029286983398 (-382.15053402891442147,-7438.4574491781313554) (0.15508886342029576388,13.623760163318825889) 0 (0.18522947629390121294,-0.24198505396172581561) (0.83336777290438568411,0.62974502234448259674) 75 | 11.903296746982981347 (-382.12426867964512667,-7436.618394007862662) (0.17287747917815848853,9.9744483602718716497) 0 (0.034239971268191407572,-0.30179420531348483125) (1.0420398532154782067,0.11180782681326351113) 76 | 12.060445075129266002 (-382.09760441838363931,-7435.2717537037187867) (0.16356315055491560395,7.3024583877429254386) 0 (-0.12648902288987137754,-0.27504540589517945115) (0.95264617911258642202,-0.44501040398016111777) 77 | 12.217614203083604352 (-382.07344618549825555,-7434.2857312246060246) (0.14281295415518335812,5.3461900185333632862) 0 (-0.25038854286189943243,-0.16841594253773500833) (0.5836055486012702298,-0.8787199491280539565) 78 | 12.37480039602123405 (-382.05286814860994582,-7433.5637780804509021) (0.11893498680102128351,3.9139860865290216907) 0 (-0.30054512156383289545,-0.012292571294518526287) (0.037177439000236539579,-1.0575878863394685414) 79 | 12.532000729810878425 (-382.03599982866035134,-7433.0351817548753388) (0.096038058705632520229,2.8654751051577500576) 0 (-0.26111644185483112723,0.14740314699411380395) (-0.52706335847975949171,-0.92150110031125753718) 80 | 12.689212895142862436 (-382.02252625225969496,-7432.648159230841884) (0.075898912344422017617,2.0978684788890564761) 0 (-0.14292903114582336532,0.26253063783398700171) (-0.93807064178057397719,-0.50400958913870119726) 81 | 12.84643505247484363 (-382.01196124288844658,-7432.3647930477000045) (0.059050318416014900558,1.5359078302338071698) 0 (0.018842846802719050853,0.29740172839551415329) (-1.065656911458969569,0.073326438076665625965) 82 | 13.00366572420171174 (-382.00378949562758635,-7432.1573202081463023) (0.045403433759355932287,1.1244958197151562196) 0 (0.1746901155269496575,0.24030800045130951137) (-0.86326939878688002672,0.6346057003632183946) 83 | 13.160903714087799798 (-381.99753438658092364,-7432.0054134624597282) (0.034594651839788562175,0.82329695827684301612) 0 (0.27578587644183250038,0.1080652283800962038) (-0.38682884690342672629,1.0026152972974387012) 84 | 13.225802612778926104 (-381.99541267779540021,-7431.9552771312419281) (0.030855395800537900597,0.72389405940716899934) 78 (0.29303792162848452119,0.04061136356676589565) (-0.14216561533980900944,1.0665389139934196017) 85 | -------------------------------------------------------------------------------- /examples/plot_burst.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | # Code to plot results from solving the burst equation with oscode in C++, 5 | # written in "output.txt" 6 | n = 100 7 | f = "output.txt" 8 | data = np.genfromtxt(f, delimiter=', ') 9 | t = data[:,0] 10 | x = data[:,1] 11 | types = data[:,3] 12 | # Analytic solution (real part) 13 | ts = np.linspace(-200,200,40000) 14 | xs = 100.0*(1+ts**2)**0.5/100.0*(np.cos(100.0*np.arctan(ts))) 15 | fig, ax = plt.subplots(1,2) 16 | 17 | ax[0].plot(ts,xs,label='true solution',lw=1) 18 | ax[0].plot(t[types==1],x[types==1],'.',color='green',label='WKB steps') 19 | ax[0].plot(t[types==0],x[types==0],'.',color='red',label='RK steps') 20 | ax[0].set_xlabel('t') 21 | ax[0].set_ylabel('Re[x(t)]') 22 | 23 | ax[1].plot(ts,xs,label='true solution',lw=1) 24 | ax[1].plot(t[types==1],x[types==1],'.',color='green',label='WKB steps') 25 | ax[1].plot(t[types==0],x[types==0],'.',color='red',label='RK steps') 26 | ax[1].set_xlim((-2,2)) 27 | ax[1].set_ylim((-10,10)) 28 | ax[1].legend() 29 | 30 | plt.tight_layout() 31 | plt.show() 32 | #plt.savefig('burst-example.png') 33 | -------------------------------------------------------------------------------- /examples/test-rk.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import scipy.special as sp 4 | import re 5 | import math 6 | 7 | pair = re.compile(r'\(([^,\)]+),([^,\)]+)\)') 8 | def parse_pair(s): 9 | return complex(*map(float, pair.match(s.decode('utf-8')).groups())) 10 | 11 | # FIGURE 1, Airy equation 12 | f1 = "example.txt" 13 | data = np.genfromtxt(f1,dtype=complex,converters={1:parse_pair,2:parse_pair},delimiter=";",missing_values="",filling_values=-1.0+0j,comments='#') 14 | times = np.logspace(math.log10(1.0),math.log10(60.0),5000) 15 | 16 | wkbs = data[:,-1] 17 | twkb = data[:,0][wkbs==1] 18 | trk = data[:,0][wkbs==0] 19 | tdense = data[:,0][wkbs==-1] 20 | xwkb = data[:,1][wkbs==1] 21 | xrk = data[:,1][wkbs==0] 22 | xdense = data[:,1][wkbs==-1] 23 | 24 | analytic = np.array([sp.airy(-ti)[0] +1j*sp.airy(-ti)[2] for ti in times]) 25 | 26 | def ana(t): 27 | return np.array([sp.airy(-ti)[0] +1j*sp.airy(-ti)[2] for ti in t]) 28 | 29 | #plt.style.use('dense') 30 | fig,ax=plt.subplots(1,2) 31 | ax[0].plot(times,analytic,label='analytic solution',color='black',lw=0.7) 32 | ax[0].plot(trk,xrk,'.',label='oscode',color="C1",ms=7.0) 33 | #ax[0].plot(twkb,xwkb,'.',color="C1",ms=7.0) 34 | ax[0].plot(tdense,xdense,'.',color="C0",label='oscode dense output') 35 | ax[0].set_ylabel('$x$') 36 | ax[0].set_xlabel('$t$') 37 | ax[0].legend() 38 | 39 | ax[1].plot(trk,abs((xrk-ana(trk))/ana(trk)),'.',label='rk step') 40 | ax[1].plot(tdense,abs((xdense-ana(tdense))/ana(tdense)),'.',label='rk dense') 41 | ax[1].legend() 42 | 43 | plt.tight_layout() 44 | plt.savefig('rk-dense-output-rtol1e-4.pdf') 45 | #plt.show() 46 | 47 | 48 | -------------------------------------------------------------------------------- /include/oscode/interpolator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | template *, 7 | typename InputIt_x = double *> 8 | struct LinearInterpolator { 9 | 10 | public: 11 | int sign_; // denotes direction of integration 12 | double xstart, dx; 13 | X x_; // array of indep. variable 14 | Y y_; // array of dep. variable 15 | int even_; // Bool, true for evenly spaced grids 16 | InputIt_x x_lower_bound, x_upper_bound; 17 | InputIt_x x_lower_it, x_upper_it, x0_it; 18 | double x_lower, x_upper, h; 19 | std::complex y_lower, y_upper; 20 | 21 | LinearInterpolator() { 22 | // Default constructor 23 | } 24 | 25 | LinearInterpolator(X x, Y y, int even) { 26 | // Constructor of struct, sets struct members 27 | 28 | even_ = even; 29 | if (even == 1) { 30 | x_ = x; 31 | y_ = y; 32 | xstart = x[0]; 33 | dx = x[1] - x[0]; 34 | } else { 35 | x_ = x; 36 | y_ = y; 37 | } 38 | } 39 | 40 | void set_interp_start(InputIt_x x_start) { 41 | // Sets iterator pointing to first element of time-array 42 | x0_it = x_start; 43 | } 44 | 45 | void set_interp_bounds(InputIt_x lower_it, InputIt_x upper_it) { 46 | // Sets iterators for lower and upper bounds within which search for 47 | // nearest neighbours is performed for interpolation. 48 | x_lower_bound = lower_it; 49 | x_upper_bound = upper_it; 50 | } 51 | 52 | void update_interp_bounds() { 53 | if (even_ == 0 && sign_ == 1) { 54 | x_lower_bound = x_upper_it; 55 | } else if (even_ == 0 && sign_ == 0) { 56 | x_upper_bound = x_lower_it; 57 | } 58 | } 59 | 60 | void update_interp_bounds_reverse() { x_upper_bound = x_lower_it; } 61 | 62 | std::complex operator()(double x) { 63 | // Does linear interpolation 64 | std::complex result; 65 | 66 | if (even_ == 1) { 67 | int i = int((x - xstart) / dx); 68 | std::complex y0 = y_[i]; 69 | std::complex y1 = y_[i + 1]; 70 | result = (y0 + (y1 - y0) * (x - xstart - dx * i) / dx); 71 | } else { 72 | 73 | x_upper_it = std::upper_bound(x_lower_bound, x_upper_bound, x); 74 | x_lower_it = x_upper_it - 1; 75 | x_lower = *x_lower_it; 76 | x_upper = *x_upper_it; 77 | y_lower = y_[(x_lower_it - x0_it)]; 78 | y_upper = y_[(x_upper_it - x0_it)]; 79 | result = (y_lower * (x_upper - x) + y_upper * (x - x_lower)) / 80 | (x_upper - x_lower); 81 | } 82 | return result; 83 | } 84 | 85 | std::complex expit(double x) { 86 | // Does linear interpolation when the input is ln()-d 87 | 88 | std::complex result; 89 | if (even_ == 1) { 90 | int i = int((x - xstart) / dx); 91 | std::complex y0 = y_[i]; 92 | std::complex y1 = y_[i + 1]; 93 | result = std::exp(y0 + (y1 - y0) * (x - xstart - dx * i) / dx); 94 | } else { 95 | x_upper_it = std::upper_bound(x_lower_bound, x_upper_bound, x); 96 | x_lower_it = x_upper_it - 1; 97 | x_lower = *x_lower_it; 98 | x_upper = *x_upper_it; 99 | y_lower = y_[(x_lower_it - x0_it)]; 100 | y_upper = y_[(x_upper_it - x0_it)]; 101 | result = (y_lower * (x_upper - x) + y_upper * (x - x_lower)) / 102 | (x_upper - x_lower); 103 | result = std::exp(result); 104 | } 105 | return result; 106 | } 107 | 108 | int check_grid_fineness(int N) { 109 | 110 | int success = 1; 111 | std::complex y0, y1, yprev, ynext; 112 | double x0, xprev, xnext; 113 | double err; 114 | 115 | // Check grid fineness here 116 | for (int i = 2; i < N; i += 2) { 117 | if (even_ == 1) { 118 | y0 = y_[i - 1]; 119 | y1 = 0.5 * (y_[i] + y_[i - 2]); 120 | err = std::abs((y1 - y0) / y0); 121 | if (err > 2e-5) { 122 | success = 0; 123 | break; 124 | } 125 | } else { 126 | y0 = y_[i - 1]; 127 | x0 = x_[i - 1]; 128 | xprev = x_[i - 2]; 129 | xnext = x_[i]; 130 | yprev = y_[i - 2]; 131 | ynext = y_[i]; 132 | y1 = (yprev * (xnext - x0) + ynext * (x0 - xprev)) / (xnext - xprev); 133 | err = std::abs((y1 - y0) / y0); 134 | if (err > 2e-5) { 135 | success = 0; 136 | break; 137 | } 138 | } 139 | } 140 | return success; 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /include/oscode/rksolver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** Class that defines a 4 and 5th order Runge-Kutta method. 9 | * 10 | * This is a "bespoke" 11 | * Runge-Kutta formula based on the nodes used in 5th and 6th order 12 | * Gauss-Lobatto integration, as detailed in [1]. 13 | * 14 | * [1] Agocs, F. J., et al. “Efficient Method for Solving Highly Oscillatory 15 | * Ordinary Differential Equations with Applications to Physical Systems.” 16 | * Physical Review Research, vol. 2, no. 1, 2020, 17 | * doi:10.1103/physrevresearch.2.013030. 18 | */ 19 | class RKSolver { 20 | private: 21 | // Frequency and friction term 22 | std::function(double)> w; 23 | std::function(double)> g; 24 | 25 | // Butcher tablaus 26 | Eigen::Matrix butcher_a5; 27 | Eigen::Matrix butcher_a4; 28 | Eigen::Matrix butcher_b5, butcher_c5, dense_b5; 29 | Eigen::Matrix butcher_b4, butcher_c4; 30 | 31 | // Current values of w, g 32 | std::complex wi, gi; 33 | 34 | public: 35 | /** Defines the ODE */ 36 | de_system *de_sys_; 37 | 38 | /** Callable that gives the frequency term in the ODE at a given time */ 39 | // std::function(double)> w; 40 | 41 | // constructors 42 | RKSolver(); 43 | RKSolver(de_system &de_sys); 44 | // grid of ws, gs 45 | /** 6 values of the frequency term per step, evaluated at the nodes of 6th 46 | * order Gauss-Lobatto quadrature 47 | */ 48 | Eigen::Matrix, 6, 1> ws; 49 | /** 6 values of the friction term per step, evaluated at the nodes of 6th 50 | * order Gauss-Lobatto quadrature 51 | */ 52 | Eigen::Matrix, 6, 1> gs; 53 | /** 5 values of the frequency term per step, evaluated at the nodes of 5th 54 | * order Gauss-Lobatto quadrature 55 | */ 56 | Eigen::Matrix, 5, 1> ws5; 57 | /** 5 values of the friction term per step, evaluated at the nodes of 5th 58 | * order Gauss-Lobatto quadrature 59 | */ 60 | Eigen::Matrix, 5, 1> gs5; 61 | 62 | // single step function 63 | Eigen::Matrix, 2, 2> 64 | step(std::complex, std::complex, double, double); 65 | // ODE to solve 66 | Eigen::Matrix, 1, 2> 67 | f(double t, const Eigen::Matrix, 1, 2> &y); 68 | // For dense output 69 | Eigen::Matrix, 6, 2> k5; 70 | Eigen::Matrix, 1, 2> 71 | dense_point(std::complex x, std::complex dx, 72 | const Eigen::Matrix, 6, 2> &k5); 73 | Eigen::Matrix, 7, 2> k_dense; 74 | Eigen::Matrix P_dense; 75 | void dense_step(double t0, double h0, std::complex y0, 76 | std::complex dy0, const std::list &dots, 77 | std::list> &doxs, 78 | std::list> &dodxs); 79 | // Experimental continuous representation of solution 80 | Eigen::Matrix, 7, 1> x_vdm; 81 | }; 82 | 83 | /** Default constructor. */ 84 | RKSolver::RKSolver(){}; 85 | 86 | /** Constructor for the RKSolver class. It sets up the Butcher tableaus for the 87 | * two Runge-Kutta methods (4th and 5th order) used. 88 | * 89 | * @param de_sys[in] the system of first-order equations defining the 90 | * second-order ODE to solve. 91 | */ 92 | RKSolver::RKSolver(de_system &de_sys) { 93 | 94 | de_sys_ = &de_sys; 95 | if (de_sys_->is_interpolated == 0) { 96 | w = de_sys.w; 97 | g = de_sys.g; 98 | } 99 | // Set Butcher tableaus 100 | RKSolver::butcher_a5 << 0.1174723380352676535740, 0, 0, 0, 0, 101 | -0.1862479800651504276304, 0.5436322218248278794734, 0, 0, 0, 102 | -0.6064303885508280518989, 1, 0.2490461467911506000559, 0, 0, 103 | 2.899356540015731406420, -4.368525611566240669139, 104 | 2.133806714786316899991, 0.2178900187289247091542, 0, 105 | 18.67996349995727204273, -28.85057783973131956546, 106 | 10.72053408420926869789, 1.414741756508049078612, 107 | -0.9646615009432702537787; 108 | RKSolver::butcher_a4 << 0.172673164646011428100, 0, 0, 109 | -1.568317088384971429762, 2.395643923738960001662, 0, 110 | -8.769507466172720011410, 10.97821961869480000808, 111 | -1.208712152522079996671; 112 | RKSolver::butcher_b5 << 0.1127557227351729739820, 0, 0.5065579732655351768382, 113 | 0.04830040376995117617928, 0.3784749562978469803166, 114 | -0.04608905606850630731611; 115 | RKSolver::dense_b5 << 0.2089555395, 0., 0.7699501023, 0.009438629906, 116 | -0.003746982422, 0.01540271068; 117 | RKSolver::butcher_c5 << 0, 0.117472338035267653574, 0.357384241759677451843, 118 | 0.642615758240322548157, 0.882527661964732346426, 1; 119 | RKSolver::butcher_b4 << -0.08333333333333333333558, 0.5833333333333333333357, 120 | 0.5833333333333333333356, -0.08333333333333333333558; 121 | RKSolver::butcher_c4 << 0, 0.172673164646011428100, 0.827326835353988571900, 122 | 1; 123 | RKSolver::P_dense << 1., -2.48711376, 2.42525041, -0.82538093, 0., 0., 0., 0., 124 | 0., 3.78546138, -5.54469086, 2.26578746, 0., -0.27734213, 0.74788587, 125 | -0.42224334, 0., -2.94848704, 7.41087391, -4.08391191, 0., 0.50817346, 126 | -1.20070313, 0.64644062, 0., 1.4193081, -3.8386162, 2.4193081; 127 | }; 128 | 129 | /** Turns the second-order ODE into a system of first-order ODEs as follows: 130 | * 131 | * \f[ y = [x, \dot{x}], \f] 132 | * \f[ \dot{y[0]} = y[1], \f] 133 | * \f[ \dot{y[1]} = -\omega^2(t)y[0] -2\gamma(t)y[1]. \f] 134 | * 135 | * @param t[in] time \f$ t \f$ 136 | * @param y[in] vector of unknowns \f$ y = [x, \dot{x}] \f$ 137 | * @returns a vector of the derivative of \f$ y \f$ 138 | */ 139 | Eigen::Matrix, 1, 2> 140 | RKSolver::f(double t, const Eigen::Matrix, 1, 2> &y) { 141 | 142 | // std::cout << "Are we interpolating? " << de_sys_->is_interpolated << 143 | // std::endl; 144 | if (de_sys_->is_interpolated == 1) { 145 | if (de_sys_->islogw_) 146 | wi = de_sys_->Winterp.expit(t); 147 | else 148 | wi = de_sys_->Winterp(t); 149 | if (de_sys_->islogg_) 150 | gi = de_sys_->Ginterp.expit(t); 151 | else 152 | gi = de_sys_->Ginterp(t); 153 | } else { 154 | wi = w(t); 155 | gi = g(t); 156 | } 157 | Eigen::Matrix, 1, 2> result; 158 | result << y[1], -wi * wi * y[0] - 2.0 * gi * y[1]; 159 | return result; 160 | }; 161 | 162 | /** Gives dense output at a single point during the step "for free", i.e. at no 163 | * extra evaluations of \f$ \omega(t), \gamma(t) \f$. This solution is roughly 164 | * mid-way through the step at $\sigma \sim 0.59 \f$, where \f$ \sigma = 0 \f$ 165 | * corresponds to the start, \f$ \sigma = 1 \f$ to the end of the step. 166 | */ 167 | Eigen::Matrix, 1, 2> 168 | RKSolver::dense_point(std::complex x, std::complex dx, 169 | const Eigen::Matrix, 6, 2> &k5) { 170 | 171 | Eigen::Matrix, 1, 2> ydense; 172 | ydense << x, dx; 173 | for (int j = 0; j <= 5; j++) 174 | ydense += 0.5866586817 * dense_b5(j) * k5.row(j); 175 | return ydense; 176 | }; 177 | 178 | /** Calculated dense output at a given set of points during a step after a 179 | * successful Runge-Kutta type step. 180 | */ 181 | void RKSolver::dense_step(double t0, double h0, std::complex y0, 182 | std::complex dy0, 183 | const std::list &dots, 184 | std::list> &doxs, 185 | std::list> &dodxs) { 186 | 187 | int docount = dots.size(); 188 | double h = h0; 189 | double sig, sig2, sig3, sig4; 190 | Eigen::Matrix, 2, 4> Q_dense = 191 | k_dense.transpose() * P_dense; 192 | Eigen::MatrixXd R_dense(4, docount); 193 | Eigen::MatrixXcd Y_dense(2, docount); 194 | 195 | int colcount = 0; 196 | for (auto it = dots.begin(); it != dots.end(); it++) { 197 | // Transform intermediate points to be in (-1,1): 198 | sig = (*it - t0) / h; 199 | sig2 = sig * sig; 200 | sig3 = sig2 * sig; 201 | sig4 = sig3 * sig; 202 | R_dense.col(colcount) << sig, sig2, sig3, sig4; 203 | colcount += 1; 204 | } 205 | Y_dense = Q_dense * R_dense; 206 | auto it = doxs.begin(); 207 | auto dit = dodxs.begin(); 208 | for (int j = 0; j < docount; j++) { 209 | *it = y0 + Y_dense.row(0)(j); 210 | *dit = dy0 + Y_dense.row(1)(j); 211 | it++; 212 | dit++; 213 | } 214 | }; 215 | 216 | /** Computes a single Runge-Kutta type step, and returns the solution and its 217 | * local error estimate. 218 | * 219 | * 220 | */ 221 | Eigen::Matrix, 2, 2> 222 | RKSolver::step(std::complex x0, std::complex dx0, double t0, 223 | double h) { 224 | 225 | Eigen::Matrix, 1, 2> y0, y, y4, y5, delta, k5_i, k4_i; 226 | y4 = Eigen::Matrix, 1, 2>::Zero(); 227 | y0 << x0, dx0; 228 | y5 = y4; 229 | // TODO: resizing of ws5, gs5, insertion 230 | Eigen::Matrix, 4, 2> k4; 231 | Eigen::Matrix, 2, 2> result; 232 | // std::cout << "Set up RK step" << std::endl; 233 | k5.row(0) = h * f(t0, y0); 234 | // std::cout << "Asked for f" << std::endl; 235 | ws(0) = wi; 236 | gs(0) = gi; 237 | for (int s = 1; s <= 5; s++) { 238 | y = y0; 239 | for (int i = 0; i <= (s - 1); i++) 240 | y += butcher_a5(s - 1, i) * k5.row(i); 241 | k5_i = h * f(t0 + butcher_c5(s) * h, y); 242 | k5.row(s) = k5_i; 243 | ws(s) = wi; 244 | gs(s) = gi; 245 | } 246 | k4.row(0) = k5.row(0); 247 | ws5(0) = ws(0); 248 | gs5(0) = gs(0); 249 | for (int s = 1; s <= 3; s++) { 250 | y = y0; 251 | for (int i = 0; i <= (s - 1); i++) 252 | y += butcher_a4(s - 1, i) * k4.row(i); 253 | k4_i = h * f(t0 + butcher_c4(s) * h, y); 254 | k4.row(s) = k4_i; 255 | ws5(s) = wi; 256 | gs5(s) = gi; 257 | } 258 | for (int j = 0; j <= 5; j++) 259 | y5 += butcher_b5(j) * k5.row(j); 260 | for (int j = 0; j <= 3; j++) 261 | y4 += butcher_b4(j) * k4.row(j); 262 | delta = y5 - y4; 263 | y5 += y0; 264 | result << y5, delta; 265 | // Add in missing w, g at t+h/2 266 | ws5(4) = ws5(3); 267 | ws5(3) = ws5(2); 268 | gs5(4) = gs5(3); 269 | gs5(3) = gs5(2); 270 | if (de_sys_->is_interpolated == 1) { 271 | if (de_sys_->islogw_) 272 | ws5(2) = de_sys_->Winterp.expit(t0 + h / 2); 273 | else 274 | ws5(2) = de_sys_->Winterp(t0 + h / 2); 275 | if (de_sys_->islogg_) 276 | gs5(2) = de_sys_->Ginterp.expit(t0 + h / 2); 277 | else 278 | gs5(2) = de_sys_->Ginterp(t0 + h / 2); 279 | } else { 280 | ws5(2) = w(t0 + h / 2); 281 | gs5(2) = g(t0 + h / 2); 282 | } 283 | 284 | // Fill up k_dense matrix for dense output 285 | for (int i = 0; i <= 5; i++) 286 | k_dense.row(i) = k5.row(i); 287 | k_dense.row(6) = h * f(t0 + h, y5); 288 | 289 | // Experimental continuous output 290 | Eigen::Matrix, 1, 4> Q_dense = 291 | (k_dense.transpose() * P_dense).row(0); 292 | x_vdm.tail(6) << Q_dense(0), Q_dense(1), Q_dense(2), Q_dense(3), 0.0, 0.0; 293 | x_vdm(0) = x0; 294 | 295 | return result; 296 | }; 297 | -------------------------------------------------------------------------------- /include/oscode/solver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /** A class to store all information related to a numerical solution run. */ 16 | class Solution { 17 | private: 18 | double t, tf, rtol, atol, h0; 19 | std::complex x, dx; 20 | int order; 21 | const char *fo; 22 | WKBSolver *wkbsolver; 23 | WKBSolver1 wkbsolver1; 24 | WKBSolver2 wkbsolver2; 25 | WKBSolver3 wkbsolver3; 26 | /** A \a de_system object to carry information about the ODE. */ 27 | de_system *de_sys_; 28 | /** These define the event at which integration finishes (currently: when tf 29 | * is reached. */ 30 | double fend, fnext; 31 | /** a boolean encoding the direction of integration: 1/True for forward. */ 32 | bool sign; 33 | 34 | public: 35 | Solution(de_system &de_sys, std::complex x0, std::complex dx0, 36 | double t_i, double t_f, int o = 3, double r_tol = 1e-4, 37 | double a_tol = 0.0, double h_0 = 1, const char *full_output = ""); 38 | 39 | template 40 | Solution(de_system &de_sys, std::complex x0, std::complex dx0, 41 | double t_i, double t_f, const X &do_times, int o = 3, 42 | double r_tol = 1e-4, double a_tol = 0.0, double h_0 = 1, 43 | const char *full_output = ""); 44 | 45 | void solve(); 46 | 47 | /** Object to call RK steps */ 48 | RKSolver rksolver; 49 | 50 | /** Successful, total attempted, and successful WKB steps the solver took, 51 | * respectively */ 52 | int ssteps, totsteps, wkbsteps; 53 | /** Lists to contain the solution and its derivative evaluated at internal 54 | * points taken by the solver (i.e. not dense output) after a run */ 55 | std::list> sol, dsol; 56 | /** List to contain the timepoints at which the solution and derivative are 57 | * internally evaluated by the solver */ 58 | std::list times; 59 | /** List to contain the "type" of each step (RK/WKB) taken internally by the 60 | * solver after a run */ 61 | std::list wkbs; 62 | /** Lists to contain the timepoints at which dense output was evaluated. This 63 | * list will always be sorted in ascending order (with possible duplicates), 64 | * regardless of the order the timepoints were specified upon input. */ 65 | std::list dotimes; 66 | /** Lists to contain the dense output of the solution and its derivative */ 67 | std::list> dosol, dodsol; 68 | /** Iterator to iterate over the dense output timepoints, for when these 69 | * need to be written out to file */ 70 | std::list::iterator dotit; 71 | // Experimental: list to contain continuous representation of the solution 72 | std::list, 7, 1>> sol_vdm; 73 | }; 74 | 75 | /** Constructor for when dense output was not requested. Sets up solution of the 76 | * ODE. 77 | * 78 | * @param[in] de_sys de_system object carrying information about the ODE being 79 | * solved 80 | * @param[in] x0, dx0 initial conditions for the ODE, \f$ x(t) \f$, \f$ 81 | * \frac{dx}{dt} \f$ evaluated at the start of the integration range 82 | * @param[in] t_i start of integration range 83 | * @param[in] t_f end of integration range 84 | * @param[in] o order of WKB approximation to be used 85 | * @param[in] r_tol (local) relative tolerance 86 | * @param[in] a_tol (local) absolute tolerance 87 | * @param[in] h_0 initial stepsize to use 88 | * @param[in] full_output file name to write results to 89 | * 90 | */ 91 | Solution::Solution(de_system &de_sys, std::complex x0, 92 | std::complex dx0, double t_i, double t_f, int o, 93 | double r_tol, double a_tol, double h_0, 94 | const char *full_output) { 95 | 96 | // Make underlying equation system accessible 97 | de_sys_ = &de_sys; 98 | 99 | // Set parameters for solver 100 | x = x0; 101 | dx = dx0; 102 | t = t_i; 103 | tf = t_f; 104 | order = o; 105 | rtol = r_tol; 106 | atol = a_tol; 107 | h0 = h_0; 108 | fo = full_output; 109 | rksolver = RKSolver(*de_sys_); 110 | 111 | // Determine direction of integration, fend>0 and integration ends when 112 | // it crosses zero 113 | if ((t >= tf) && h0 < 0) { 114 | // backwards 115 | fend = t - tf; 116 | fnext = fend; 117 | if (de_sys_->is_interpolated == 1) { 118 | de_sys_->Winterp.sign_ = 0; 119 | de_sys_->Ginterp.sign_ = 0; 120 | } else 121 | sign = 0; 122 | 123 | } else if ((t <= tf) && h0 > 0) { 124 | // forward 125 | fend = tf - t; 126 | fnext = fend; 127 | if (de_sys_->is_interpolated == 1) { 128 | de_sys_->Winterp.sign_ = 1; 129 | de_sys_->Ginterp.sign_ = 1; 130 | } else 131 | sign = 1; 132 | } else { 133 | throw std::logic_error( 134 | "Direction of integration in conflict with direction of initial step, " 135 | "terminating. Please check your values for ti, tf, and h."); 136 | return; 137 | } 138 | 139 | // No dense output desired if this constructor was called, so only output 140 | // answer at t_i and t_f 141 | dotimes.push_back(t_i); 142 | dotimes.push_back(t_f); 143 | dosol.push_back(x0); 144 | dodsol.push_back(dx0); 145 | dotit = dotimes.end(); 146 | 147 | switch (order) { 148 | case 1: 149 | wkbsolver1 = WKBSolver1(*de_sys_, order); 150 | wkbsolver = &wkbsolver1; 151 | break; 152 | case 2: 153 | wkbsolver2 = WKBSolver2(*de_sys_, order); 154 | wkbsolver = &wkbsolver2; 155 | break; 156 | case 3: 157 | wkbsolver3 = WKBSolver3(*de_sys_, order); 158 | wkbsolver = &wkbsolver3; 159 | break; 160 | }; 161 | }; 162 | 163 | /** Constructor for when dense output was requested. Sets up solution of the 164 | * ODE. 165 | * 166 | * @param[in] de_sys de_system object carrying information about the ODE being 167 | * solved 168 | * @param[in] x0, dx0 initial conditions for the ODE, \f$ x(t) \f$, \f$ 169 | * \frac{dx}{dt} \f$ evaluated at the start of the integration range 170 | * @param[in] t_i start of integration range 171 | * @param[in] t_f end of integration range 172 | * @param[in] do_times timepoints at which dense output is to be produced. 173 | * Doesn't need to be sorted, and duplicated are allowed. 174 | * @param[in] o order of WKB approximation to be used 175 | * @param[in] r_tol (local) relative tolerance 176 | * @param[in] a_tol (local) absolute tolerance 177 | * @param[in] h_0 initial stepsize to use 178 | * @param[in] full_output file name to write results to 179 | * 180 | */ 181 | template 182 | Solution::Solution(de_system &de_sys, std::complex x0, 183 | std::complex dx0, double t_i, double t_f, 184 | const X &do_times, int o, double r_tol, double a_tol, 185 | double h_0, const char *full_output) { 186 | 187 | // Make underlying equation system accessible 188 | de_sys_ = &de_sys; 189 | // Set parameters for solver 190 | x = x0; 191 | dx = dx0; 192 | t = t_i; 193 | tf = t_f; 194 | order = o; 195 | rtol = r_tol; 196 | atol = a_tol; 197 | h0 = h_0; 198 | fo = full_output; 199 | rksolver = RKSolver(*de_sys_); 200 | 201 | // Determine direction of integration, fend>0 and integration ends when 202 | // it crosses zero 203 | if ((t >= tf) && h0 < 0) { 204 | // backwards 205 | fend = t - tf; 206 | fnext = fend; 207 | if (de_sys_->is_interpolated == 1) { 208 | de_sys_->Winterp.sign_ = 0; 209 | de_sys_->Ginterp.sign_ = 0; 210 | } else 211 | sign = 0; 212 | } else if ((t <= tf) && h0 > 0) { 213 | // forward 214 | fend = tf - t; 215 | fnext = fend; 216 | if (de_sys_->is_interpolated == 1) { 217 | de_sys_->Winterp.sign_ = 1; 218 | de_sys_->Ginterp.sign_ = 1; 219 | } else 220 | sign = 1; 221 | } else { 222 | throw std::logic_error( 223 | "Direction of integration in conflict with direction of initial step, " 224 | "terminating. Please check your values for ti, tf, and h."); 225 | return; 226 | } 227 | 228 | // Dense output preprocessing: sort and reverse if necessary 229 | int dosize = do_times.size(); 230 | dotimes.resize(dosize); 231 | dosol.resize(dosize); 232 | dodsol.resize(dosize); 233 | 234 | // Copy dense output points to list 235 | auto doit = do_times.begin(); 236 | for (auto it = dotimes.begin(); it != dotimes.end(); it++) { 237 | *it = *doit; 238 | doit++; 239 | } 240 | // Sort to ensure ascending order 241 | dotimes.sort(); 242 | 243 | // Reverse if necessary 244 | if ((de_sys_->is_interpolated == 1 && de_sys_->Winterp.sign_ == 0) || 245 | (de_sys_->is_interpolated == 0 && sign == 0)) { 246 | dotimes.reverse(); 247 | } 248 | 249 | dotit = dotimes.begin(); 250 | switch (order) { 251 | case 1: 252 | wkbsolver1 = WKBSolver1(*de_sys_, order); 253 | wkbsolver = &wkbsolver1; 254 | break; 255 | case 2: 256 | wkbsolver2 = WKBSolver2(*de_sys_, order); 257 | wkbsolver = &wkbsolver2; 258 | break; 259 | case 3: 260 | wkbsolver3 = WKBSolver3(*de_sys_, order); 261 | wkbsolver = &wkbsolver3; 262 | break; 263 | }; 264 | }; 265 | 266 | /** \brief Function to solve the ODE \f$ \ddot{x} + 2\gamma(t)\dot{x} + 267 | * \omega^2(t)x = 0 \f$ for \f$ x(t), \frac{dx}{dt} \f$. 268 | * 269 | * While solving the ODE, this function will populate the \a Solution object 270 | * with the following results: 271 | * 272 | */ 273 | void Solution::solve() { 274 | 275 | int nrk, nwkb1, nwkb2; 276 | // Settings for MS 277 | nrk = 5; 278 | nwkb1 = 2; 279 | nwkb2 = 4; 280 | Eigen::Matrix, 2, 2> rkstep; 281 | Eigen::Matrix, 3, 2> wkbstep; 282 | Eigen::Matrix, 1, 2> rkx, wkbx; 283 | Eigen::Matrix, 1, 2> rkerr, wkberr, truncerr; 284 | Eigen::Matrix errmeasure_rk; 285 | Eigen::Matrix errmeasure_wkb; 286 | double tnext, hnext, h, hrk, hwkb; 287 | double wkbdelta, rkdelta; 288 | std::complex xnext, dxnext; 289 | bool wkb = false; 290 | Eigen::Index maxindex_wkb, maxindex_rk; 291 | h = h0; 292 | tnext = t + h; 293 | // Initialise stats 294 | sol.push_back(x); 295 | dsol.push_back(dx); 296 | times.push_back(t); 297 | wkbs.push_back(false); 298 | ssteps = 0; 299 | totsteps = 0; 300 | wkbsteps = 0; 301 | // Dense output 302 | std::list inner_dotimes; 303 | std::list> inner_dosols, inner_dodsols; 304 | auto it_dosol = dosol.begin(); 305 | auto it_dodsol = dodsol.begin(); 306 | Eigen::Matrix, 1, 2> y_dense_rk; 307 | std::complex x_dense_rk, dx_dense_rk; 308 | // Experimental continuous solution, vandermonde representation 309 | Eigen::Matrix, 7, 1> xvdm; 310 | 311 | while (fend > 0) { 312 | // Check if we are reaching the end of integration 313 | if (fnext < 0) { 314 | h = tf - t; 315 | tnext = tf; 316 | } 317 | 318 | // Keep updating stepsize until step is accepted 319 | while (true) { 320 | // RK step 321 | rkstep = rksolver.step(x, dx, t, h); 322 | rkx << rkstep(0, 0), rkstep(0, 1); 323 | rkerr << rkstep(1, 0), rkstep(1, 1); 324 | // WKB step 325 | wkbstep = wkbsolver->step(x, dx, t, h, rksolver.ws, rksolver.gs, 326 | rksolver.ws5, rksolver.gs5); 327 | wkbx = wkbstep.row(0); 328 | wkberr = wkbstep.row(2); 329 | truncerr = wkbstep.row(1); 330 | // Safety feature for when all wkb steps are 0 (truncer=0), but not 331 | // necessarily in good WKB regime: 332 | truncerr(0) = std::max(1e-10, abs(truncerr(0))); 333 | truncerr(1) = std::max(1e-10, abs(truncerr(1))); 334 | // dominant error calculation 335 | // Error scale measures 336 | errmeasure_rk << std::abs(rkerr(0)) / (std::abs(rkx(0)) * rtol + atol), 337 | std::abs(rkerr(1)) / (std::abs(rkx(1)) * rtol + atol); 338 | errmeasure_wkb << std::abs(truncerr(0)) / 339 | (std::abs(wkbx(0)) * rtol + atol), 340 | std::abs(truncerr(1)) / (std::abs(wkbx(1)) * rtol + atol), 341 | std::abs(wkberr(0)) / (std::abs(wkbx(0)) * rtol + atol), 342 | std::abs(wkberr(1)) / (std::abs(wkbx(1)) * rtol + atol); 343 | rkdelta = std::max(1e-10, errmeasure_rk.maxCoeff(&maxindex_rk)); 344 | if (std::isnan(errmeasure_wkb.maxCoeff()) == false && 345 | std::isinf(std::real(wkbx(0))) == false && 346 | std::isinf(std::imag(wkbx(0))) == false && 347 | std::isinf(std::real(wkbx(1))) == false && 348 | std::isinf(std::imag(wkbx(1))) == false && 349 | std::isnan(std::real(wkbx(0))) == false && 350 | std::isnan(std::imag(wkbx(0))) == false && 351 | std::isnan(std::real(wkbx(1))) == false && 352 | std::isnan(std::imag(wkbx(1))) == false) { 353 | wkbdelta = std::max(1e-10, errmeasure_wkb.maxCoeff(&maxindex_wkb)); 354 | } else { 355 | wkbdelta = std::numeric_limits::infinity(); 356 | } 357 | 358 | // predict next stepsize 359 | hrk = h * std::pow((1.0 / rkdelta), 1.0 / nrk); 360 | if (maxindex_wkb <= 1) 361 | hwkb = h * std::pow(1.0 / wkbdelta, 1.0 / nwkb1); 362 | else 363 | hwkb = h * std::pow(1.0 / wkbdelta, 1.0 / nwkb2); 364 | // choose step with larger predicted stepsize 365 | if (std::abs(hwkb) >= std::abs(hrk)) { 366 | wkb = true; 367 | } else { 368 | wkb = false; 369 | } 370 | if (wkb) { 371 | xnext = wkbx(0); 372 | dxnext = wkbx(1); 373 | // if wkb step chosen, ignore truncation error in 374 | // stepsize-increase 375 | wkbdelta = std::max(1e-10, errmeasure_wkb.tail(2).maxCoeff()); 376 | hnext = h * std::pow(1.0 / wkbdelta, 1.0 / nwkb2); 377 | } else { 378 | xnext = rkx(0); 379 | dxnext = rkx(1); 380 | hnext = hrk; 381 | } 382 | totsteps += 1; 383 | // Checking for too many steps and low acceptance ratio: 384 | if (totsteps % 5000 == 0) { 385 | std::cerr << "Warning: the solver took " << totsteps 386 | << " steps, and may take a while to converge." << std::endl; 387 | if (ssteps / totsteps < 0.05) { 388 | std::cerr << "Warning: the step acceptance ratio is below 5%, the " 389 | "solver may take a while to converge." 390 | << std::endl; 391 | } 392 | } 393 | 394 | // check if chosen step was successful 395 | if (std::abs(hnext) >= std::abs(h)) { 396 | // std::cout << "All dense output points: " << std::endl; 397 | if (dotit != dotimes.end()) { 398 | // std::cout << *dotit << std::endl; 399 | while ((*dotit - t >= 0 && tnext - *dotit >= 0) || 400 | (*dotit - t <= 0 && tnext - *dotit <= 0)) { 401 | inner_dotimes.push_back(*dotit); 402 | dotit++; 403 | } 404 | if (inner_dotimes.size() > 0) { 405 | inner_dosols.resize(inner_dotimes.size()); 406 | inner_dodsols.resize(inner_dotimes.size()); 407 | if (wkb) { 408 | // Dense output after successful WKB step 409 | // std::cout << "Attempting " << 410 | // inner_dosols.size() << " dense 411 | // output points after successful WKB 412 | // step from " << t << " to " << t+h << 413 | // std::endl; 414 | 415 | wkbsolver->dense_step(t, inner_dotimes, inner_dosols, 416 | inner_dodsols); 417 | } else { 418 | // Dense output after successful RK step 419 | for (auto it = inner_dotimes.begin(); it != inner_dotimes.end(); 420 | it++) 421 | rksolver.dense_step(t, h, x, dx, inner_dotimes, inner_dosols, 422 | inner_dodsols); 423 | } 424 | } 425 | } 426 | auto inner_it = inner_dosols.begin(); 427 | auto inner_dit = inner_dodsols.begin(); 428 | while (inner_it != inner_dosols.end() && it_dosol != dosol.end() && 429 | inner_dit != inner_dodsols.end() && it_dodsol != dodsol.end()) { 430 | *it_dosol = *inner_it; 431 | *it_dodsol = *inner_dit; 432 | it_dodsol++; 433 | it_dosol++; 434 | inner_it++; 435 | inner_dit++; 436 | } 437 | inner_dotimes.resize(0); 438 | inner_dosols.resize(0); 439 | inner_dodsols.resize(0); 440 | 441 | // record type of step 442 | if (wkb) { 443 | wkbsteps += 1; 444 | wkbs.push_back(true); 445 | xvdm = wkbsolver->x_vdm; 446 | } else { 447 | wkbs.push_back(false); 448 | xvdm = rksolver.x_vdm; 449 | } 450 | sol.push_back(xnext); 451 | dsol.push_back(dxnext); 452 | sol_vdm.push_back(xvdm); 453 | times.push_back(tnext); 454 | tnext += hnext; 455 | x = xnext; 456 | dx = dxnext; 457 | t += h; 458 | h = hnext; 459 | if (h > 0) { 460 | fend = tf - t; 461 | fnext = tf - tnext; 462 | } else { 463 | fend = t - tf; 464 | fnext = tnext - tf; 465 | } 466 | ssteps += 1; 467 | // Update interpolation bounds 468 | if (de_sys_->is_interpolated == 1) { 469 | de_sys_->Winterp.update_interp_bounds(); 470 | de_sys_->Ginterp.update_interp_bounds(); 471 | } 472 | 473 | break; 474 | } else { 475 | if (wkb) { 476 | if (maxindex_wkb <= 1) { 477 | if (nwkb1 > 1) 478 | hnext = h * std::pow(1.0 / wkbdelta, 1.0 / (nwkb1 - 1)); 479 | else 480 | hnext = 0.95 * h * 1.0 / wkbdelta; 481 | } else 482 | hnext = h * std::pow(1.0 / wkbdelta, 1.0 / (nwkb2 - 1)); 483 | } else 484 | hnext = h * std::pow(1.0 / rkdelta, 1.0 / (nrk - 1)); 485 | h = hnext; 486 | tnext = t + hnext; 487 | if (h > 0) { 488 | fnext = tf - tnext; 489 | } else { 490 | fnext = tnext - tf; 491 | } 492 | } 493 | } 494 | } 495 | 496 | // If integrating backwards, reverse dense output (because it will have been 497 | // reversed at the start) 498 | if (de_sys_->is_interpolated == 1) { 499 | if (de_sys_->Winterp.sign_ == 0) { 500 | dotimes.reverse(); 501 | dosol.reverse(); 502 | dodsol.reverse(); 503 | } 504 | } else { 505 | if (sign == 0) { 506 | dotimes.reverse(); 507 | dosol.reverse(); 508 | dodsol.reverse(); 509 | } 510 | } 511 | 512 | // Write output to file if prompted 513 | if (!(*fo == 0)) { 514 | std::string output(fo); 515 | std::ofstream f; 516 | f.open(output); 517 | f << "# Summary:\n# total steps taken: " + std::to_string(totsteps) + 518 | "\n# of which successful: " + std::to_string(ssteps) + 519 | "\n# of which" + +" wkb: " + std::to_string(wkbsteps) + 520 | "\n# time, sol, dsol, wkb? (type)\n"; 521 | auto it_t = times.begin(); 522 | auto it_w = wkbs.begin(); 523 | auto it_x = sol.begin(); 524 | auto it_dx = dsol.begin(); 525 | for (int i = 0; i <= ssteps; i++) { 526 | f << std::setprecision(15) << *it_t << ";" << std::setprecision(15) 527 | << *it_x << ";" << std::setprecision(15) << *it_dx << ";" << *it_w 528 | << "\n"; 529 | ++it_t; 530 | ++it_x; 531 | ++it_dx; 532 | ++it_w; 533 | } 534 | // print all dense output to file 535 | int dosize = dosol.size(); 536 | auto it_dosol = dosol.begin(); 537 | auto it_dotimes = dotimes.begin(); 538 | auto it_dodsol = dodsol.begin(); 539 | for (int i = 0; i < dosize; i++) { 540 | f << std::setprecision(20) << *it_dotimes << ";" << std::setprecision(20) 541 | << *it_dosol << ";" << *it_dodsol << ";\n"; 542 | ++it_dosol; 543 | ++it_dodsol; 544 | ++it_dotimes; 545 | } 546 | 547 | f.close(); 548 | } 549 | }; 550 | -------------------------------------------------------------------------------- /include/oscode/system.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | /** */ 5 | class de_system { 6 | private: 7 | int even_; 8 | 9 | public: 10 | template 11 | de_system(X &ts, Y &ws, Z &gs, X_it x_it, int size, bool isglogw = false, 12 | bool islogg = false, int even = 0, int check_grid = 0); 13 | de_system(std::complex (*)(double, void *), 14 | std::complex (*)(double, void *), void *); 15 | de_system(std::complex (*)(double), std::complex (*)(double)); 16 | de_system(); 17 | std::function(double)> w; 18 | std::function(double)> g; 19 | LinearInterpolator<> Winterp; 20 | LinearInterpolator<> Ginterp; 21 | bool islogg_, islogw_; 22 | bool grid_fine_enough = 1; 23 | bool is_interpolated; 24 | }; 25 | 26 | /** Default contructor */ 27 | de_system::de_system() {} 28 | 29 | /** Constructor for the case of the user having defined the frequency and 30 | * damping terms as sequences 31 | */ 32 | template 33 | de_system::de_system(X &ts, Y &ws, Z &gs, X_it x_it, int size, bool islogw, 34 | bool islogg, int even, int check_grid) { 35 | 36 | is_interpolated = 1; 37 | even_ = even; 38 | islogg_ = islogg; 39 | islogw_ = islogw; 40 | 41 | /** Set up interpolation on the supplied frequency and damping arrays */ 42 | LinearInterpolator winterp(ts, ws, even_); 43 | LinearInterpolator ginterp(ts, gs, even_); 44 | 45 | Winterp = winterp; 46 | Ginterp = ginterp; 47 | 48 | if (even_ == 0) { 49 | Winterp.set_interp_start(x_it); 50 | Ginterp.set_interp_start(x_it); 51 | Winterp.set_interp_bounds(ts, ts + size - 1); 52 | Ginterp.set_interp_bounds(ts, ts + size - 1); 53 | } 54 | 55 | /** Check if supplied grids are sampled finely enough for the purposes of 56 | * linear interpolation 57 | */ 58 | if (check_grid == 1) { 59 | int w_is_fine = Winterp.check_grid_fineness(size); 60 | int g_is_fine = Ginterp.check_grid_fineness(size); 61 | if (w_is_fine == 1 && g_is_fine == 1) 62 | grid_fine_enough = 1; 63 | else 64 | grid_fine_enough = 0; 65 | } 66 | 67 | /** Bind result of interpolation to a function, this will be called by the 68 | * routines taking RK and WKB steps 69 | */ 70 | if (islogw) 71 | w = std::bind(&LinearInterpolator::expit, Winterp, 72 | std::placeholders::_1); 73 | else 74 | w = std::bind(&LinearInterpolator::operator(), Winterp, 75 | std::placeholders::_1); 76 | if (islogg) 77 | g = std::bind(&LinearInterpolator::expit, Ginterp, 78 | std::placeholders::_1); 79 | else 80 | g = std::bind(&LinearInterpolator::operator(), Ginterp, 81 | std::placeholders::_1); 82 | } 83 | 84 | /** Constructor for the case when the frequency and damping terms have been 85 | * defined as functions 86 | */ 87 | de_system::de_system(std::complex (*W)(double, void *), 88 | std::complex (*G)(double, void *), void *p) { 89 | 90 | is_interpolated = 0; 91 | w = [W, p](double x) { return W(x, p); }; 92 | g = [G, p](double x) { return G(x, p); }; 93 | }; 94 | 95 | /** Constructor for the case when the frequency and damping terms have been 96 | * defined as functions (and there are no additional parameters that the 97 | * function might need) 98 | */ 99 | de_system::de_system(std::complex (*W)(double), 100 | std::complex (*G)(double)) { 101 | 102 | is_interpolated = 0; 103 | w = W; 104 | g = G; 105 | }; 106 | -------------------------------------------------------------------------------- /pyoscode/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import _pyoscode 4 | import numpy 5 | 6 | def solve(ts, ws, gs, ti, tf, x0, dx0, t_eval=[], logw=False, logg=False, order=3, 7 | rtol=1e-4, atol=0.0, h=None, full_output="", even_grid=False, check_grid=False): 8 | """Solve a differential equation with the RKWKB method. 9 | 10 | Parameters 11 | ---------- 12 | ts: numpy.ndarray [float] or list [float] 13 | An array of real numbers representing the values of the independent 14 | variable at which the frequency and friction term are evaluated. 15 | 16 | ws: numpy.ndarray [complex] or list [complex] 17 | An array-like object of real or complex 18 | numbers, representing the values of frequency w at the points given in ts. 19 | 20 | gs: numpy.ndarray [complex] or list [complex] 21 | An array-like object of real or complex numbers representing the values 22 | of the friction term g at the points given in ts. 23 | 24 | ti,tf: float 25 | Start and end of integration range. 26 | 27 | x0, dx0: complex 28 | Initial values of the dependent variable and its derivative. 29 | 30 | t_eval: numpy.ndarray [float] or list [float] 31 | An array of times where the solution is to be returned. 32 | 33 | logw, logg: boolean, optional 34 | If true, the array of frequencies and friction values, respectively, will be 35 | exponentiated (False, False by default). 36 | 37 | order: int, optional 38 | Order of WKB approximation to use, 3 (the highest value) by default. 39 | 40 | rtol, atol: float, optional 41 | Relative and absolute tolerance of the solver, 1e-4 and 0 by default. 42 | Note that atol at the moment is not implemented. 43 | 44 | h: float, optional 45 | Size of the initial step, 1 by default. 46 | 47 | full_output: str , optional 48 | If given, the return dictionary will be written to a file with the supplied 49 | name. 50 | 51 | even_grid: boolean, optional 52 | False by default. Set this to True if the ts array is evenly spaced for 53 | faster interpolation. 54 | 55 | check_grid: boolean, optional 56 | False by default. If True, the fineness of the ws, gs grids will be 57 | checked based on how accurate linear interpolation would be on them, and 58 | a warning will be issued if this accuracy is deemed too low. It's a good 59 | idea to set this to True when solving an equation for the first time. 60 | 61 | Returns 62 | ------- 63 | A dictionary with the following keywords and values: 64 | 65 | sol: list [complex] 66 | A list containing the solution evaluated at timepoints listed under 67 | the 't' keyword. 68 | 69 | dsol: list [complex] 70 | A list containint the first deriv ative of the solution evaluated at 71 | timepoints listed under the 't' keyword. 72 | 73 | t: list [float] 74 | Contains the values of the independent variable where the solver 75 | stepped, i.e. evaluated the solution at. This is not determined by 76 | the user, rather these are the internal steps the solver naturally 77 | takes when integrating. 78 | 79 | types: list [float] 80 | A list of True/False values corresponding to the step types the 81 | solver chose at the timepoints listed under the keyword 't'. If 82 | True, the step was WKB, and RK otherwise. 83 | 84 | x_eval: list [complex] 85 | Values of the solution at the points specified in t_eval. 86 | 87 | dx_eval: list [complex] 88 | Values of the derivative of the solution at the points specified in 89 | t_eval. 90 | 91 | cts_rep: list [list [complex]] 92 | List containing a list of the polynomial coefficients needed to 93 | construct a continuous representation of the solution within each 94 | internal step the algorithm takes. 95 | 96 | """ 97 | # Set direction of integration if initial stepsize, h, not given 98 | if h==None: 99 | h = numpy.sign(tf - ti) 100 | # Handle the case of ti = tf 101 | if h==0: 102 | h=1 103 | 104 | # Run oscode from module library 105 | resdict = _pyoscode.solve(ts, ws, gs, ti, tf, x0, dx0, t_eval=t_eval, logw=logw, logg=logg, 106 | order=order, rtol=rtol, atol=atol, h=h, full_output=full_output, 107 | even_grid=even_grid, check_grid=check_grid) 108 | 109 | return resdict 110 | 111 | def solve_fn(w, g, ti, tf, x0, dx0, t_eval=[], order=3, 112 | rtol=1e-4, atol=0.0, h=None, full_output=""): 113 | """ 114 | Solves the differential equation x'' + 2g(t)x' + w^2(t)y = 0 for y(t) and 115 | y'(t) on an interval (ti, tf) given initial conditions [x(ti), x'(ti)] and 116 | function handles w(t), g(t). 117 | 118 | Parameters 119 | ---------- 120 | w: callable(t) 121 | Computes the frequency at point t, may return a complex number. 122 | 123 | g: callable(t) 124 | Computes the damping term at point t, may return a complex number. 125 | 126 | ti,tf: float 127 | Start and end of integration range. 128 | 129 | x0, dx0: complex 130 | Initial values of the dependent variable and its derivative. 131 | 132 | t_eval: numpy.ndarray [float] or list [float] 133 | An array of times where the solution is to be returned. 134 | 135 | order: int, optional 136 | Order of WKB approximation to use, 3 (the highest value) by default. 137 | 138 | rtol, atol: float, optional 139 | Relative and absolute tolerance of the solver, 1e-4 and 0 by default. 140 | Note that atol at the moment is not implemented. 141 | 142 | h: float, optional 143 | Size of the initial step, 1 by default. 144 | 145 | full_output: str , optional 146 | If given, the return dictionary will be written to a file with the 147 | supplied name. 148 | 149 | Returns 150 | ------- 151 | A dictionary with the following keywords and values: 152 | 153 | sol: list [complex] 154 | A list containing the solution evaluated at timepoints listed under 155 | the 't' keyword. 156 | 157 | dsol: list [complex] 158 | A list containint the first deriv ative of the solution evaluated at 159 | timepoints listed under the 't' keyword. 160 | 161 | t: list [float] 162 | Contains the values of the independent variable where the solver 163 | stepped, i.e. evaluated the solution at. This is not determined by 164 | the user, rather these are the internal steps the solver naturally 165 | takes when integrating. 166 | 167 | types: list [float] 168 | A list of True/False values corresponding to the step types the 169 | solver chose at the timepoints listed under the keyword 't'. If 170 | True, the step was WKB, and RK otherwise. 171 | 172 | x_eval: list [complex] 173 | Values of the solution at the points specified in t_eval. 174 | 175 | dx_eval: list [complex] 176 | Values of the derivative of the solution at the points specified in 177 | t_eval. 178 | 179 | cts_rep: list [list [complex]] 180 | List containing a list of the polynomial coefficients needed to 181 | construct a continuous representation of the solution within each 182 | internal step the algorithm takes. 183 | 184 | """ 185 | 186 | # Set direction of integration if initial stepsize, h, not given 187 | if h==None: 188 | h = numpy.sign(tf - ti) 189 | # Handle the case of ti = tf 190 | if h==0: 191 | h=1 192 | 193 | # Run oscode from module library 194 | resdict = _pyoscode.solve_fn(w, g, ti, tf, x0, dx0, t_eval=t_eval, 195 | order=order, rtol=rtol, atol=atol, h=h, full_output=full_output) 196 | 197 | return resdict 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /pyoscode/_pyoscode.cpp: -------------------------------------------------------------------------------- 1 | #define PY_SSIZE_T_CLEAN 2 | #include "_python.hpp" 3 | #include "_pyoscode.hpp" 4 | #include 5 | #include 6 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | /* Initialise the module */ 13 | #ifdef PYTHON3 14 | PyMODINIT_FUNC PyInit__pyoscode(void){ 15 | import_array(); 16 | return PyModule_Create(&_pyoscodemodule); 17 | } 18 | #else 19 | PyMODINIT_FUNC init_pyoscode(void){ 20 | PyObject *m = Py_InitModule3("_pyoscode", module_methods, module_docstring); 21 | if(m==NULL) 22 | return; 23 | // Load numpy functionality 24 | import_array(); 25 | } 26 | #endif 27 | 28 | /* w callback function */ 29 | static PyObject *w_callback = NULL; 30 | static PyObject *g_callback = NULL; 31 | 32 | static std::complex wfun(double t){ 33 | 34 | std::complex result = 0; 35 | PyObject *arglist = Py_BuildValue("(d)", t); 36 | PyObject *py_result = PyObject_CallObject(w_callback, arglist); 37 | Py_DECREF(arglist); 38 | // Check whether Python w(t) returned an exception 39 | if(py_result == NULL) 40 | return NULL; 41 | // Check if return value was the correct type (complex) 42 | if(PyComplex_Check(py_result) || PyFloat_Check(py_result)){ 43 | result = std::complex(PyComplex_RealAsDouble(py_result), PyComplex_ImagAsDouble(py_result)); 44 | } 45 | Py_DECREF(py_result); 46 | return result; 47 | } 48 | 49 | static std::complex gfun(double t){ 50 | 51 | std::complex result = 0; 52 | PyObject *arglist = Py_BuildValue("(d)", t); 53 | PyObject *py_result = PyObject_CallObject(g_callback, arglist); 54 | Py_DECREF(arglist); 55 | double real, imag; 56 | // Check whether Python w(t) returned an exception 57 | if(py_result == NULL) 58 | return NULL; 59 | // Check if return value was the correct type (complex) 60 | if(PyComplex_Check(py_result) || PyFloat_Check(py_result)) 61 | result = std::complex(PyComplex_RealAsDouble(py_result), PyComplex_ImagAsDouble(py_result)); 62 | Py_DECREF(py_result); 63 | return result; 64 | } 65 | 66 | /* Function to run the solver when w, g are provided as functions */ 67 | static PyObject *_pyoscode_solve_fn(PyObject *self, PyObject *args, PyObject *kwargs){ 68 | 69 | int order=3; 70 | const char* full_output=""; 71 | double ti, tf, rtol, atol, h0; 72 | std::complex x0, dx0; 73 | PyObject *t_evalobj=NULL, *wobj, *gobj; 74 | // Define keywords 75 | static const char *kwlist[] = 76 | {"w","g","ti","tf","x0","dx0","t_eval","order","rtol","atol","h","full_output",NULL}; 77 | // Interpret input arguments 78 | if (!PyArg_ParseTupleAndKeywords(args,kwargs,"OOddDD|Oiddds",const_cast(kwlist),&wobj,&gobj,&ti,&tf,&x0,&dx0,&t_evalobj,&order,&rtol,&atol,&h0,&full_output)) 79 | return NULL; 80 | // Set w, g functions 81 | if(!(PyCallable_Check(wobj) && PyCallable_Check(gobj))){ 82 | PyErr_SetString(PyExc_TypeError, "Parameter must be callable"); 83 | return NULL; 84 | } 85 | // Add reference to new callback 86 | Py_XINCREF(wobj); Py_XINCREF(gobj); 87 | // Dispose of previous callback 88 | Py_XDECREF(w_callback); Py_XDECREF(g_callback); 89 | // Remember new callback 90 | w_callback = wobj; g_callback = gobj; 91 | 92 | // If given, read in t_eval, points at which dense output is requested 93 | PyObject *t_evalarray = NULL; 94 | if(t_evalobj!=NULL){ 95 | t_evalarray = PyArray_FROM_OTF(t_evalobj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY); 96 | } 97 | PyArrayObject *t_evalarray_arr = NULL; 98 | if(t_evalobj!=NULL){ 99 | t_evalarray_arr = reinterpret_cast(t_evalarray); 100 | } 101 | double *t_eval = NULL; 102 | int t_evalsize = 0; 103 | if(t_evalobj!=NULL){ 104 | t_eval = (double*)PyArray_DATA(t_evalarray_arr); 105 | t_evalsize = (int)PyArray_SIZE(t_evalarray_arr); 106 | } 107 | std::list t_evallist; 108 | t_evallist.resize(t_evalsize); 109 | int i=0; 110 | for(auto it=t_evallist.begin(); it!=t_evallist.end(); it++){ 111 | *it = t_eval[i]; 112 | i++; 113 | } 114 | // Checks 115 | try{ 116 | // Check: dense output points must be inside integration range, and 117 | // monotonic 118 | if(t_evalsize!=0){ 119 | if(std::is_sorted(t_evallist.begin(), t_evallist.end()) == false) 120 | throw "The points at which dense output is requested are not in ascending order."; 121 | else if(h0>0 && (t_evallist.front() < ti || t_evallist.back() > tf)){ 122 | throw "The point at which dense output is requested must lie in the integration range (between ti, tf)."; 123 | } 124 | else if(h0<0 && (t_evallist.front() < tf || t_evallist.back() > ti)){ 125 | throw "The point at which dense output is requested must lie in the integration range (between ti, tf)."; 126 | } 127 | } 128 | // Check: direction of integration must match initial stepsize sign 129 | if( ((tf-ti)>0 && h0<0) || ((tf-ti)<0 && h0>0) ) 130 | throw "Direction of integration ( tf-ti ) in conflict with sign of initial step (h)."; 131 | } 132 | catch(const char* errormsg){ 133 | PyErr_SetString(PyExc_TypeError, errormsg); 134 | return (PyObject *) NULL; 135 | } 136 | de_system sys = de_system(&wfun, &gfun); 137 | std::list> sol,dsol; 138 | std::list times; 139 | std::list wkbs; 140 | std::list> x_eval, dx_eval; 141 | std::list,7,1>> cts_rep; 142 | 143 | if(t_evalobj!=NULL){ 144 | Solution solution(sys,x0,dx0,ti,tf,t_evallist,order,rtol,atol,h0,full_output); 145 | solution.solve(); 146 | x_eval = solution.dosol; 147 | dx_eval = solution.dodsol; 148 | sol = solution.sol; 149 | dsol = solution.dsol; 150 | times = solution.times; 151 | wkbs = solution.wkbs; 152 | cts_rep = solution.sol_vdm; 153 | } 154 | else{ 155 | Solution solution(sys,x0,dx0,ti,tf,order,rtol,atol,h0,full_output); 156 | solution.solve(); 157 | x_eval = solution.dosol; 158 | dx_eval = solution.dodsol; 159 | sol = solution.sol; 160 | dsol = solution.dsol; 161 | times = solution.times; 162 | wkbs = solution.wkbs; 163 | cts_rep = solution.sol_vdm; 164 | } 165 | // Build output values 166 | int Ndense = x_eval.size(); 167 | PyObject *pyx_eval = PyList_New(Ndense), *pydx_eval = PyList_New(Ndense); 168 | int Neval = 0; 169 | auto itx_eval = x_eval.begin(); 170 | auto itdx_eval = dx_eval.begin(); 171 | for(int Neval=0; Neval x0, dx0; 231 | PyObject *tsobj, *wsobj, *gsobj, *t_evalobj=NULL; 232 | // Define keywords 233 | static const char *kwlist[] = 234 | {"ts","ws","gs","ti","tf","x0","dx0","t_eval","logw","logg","order","rtol","atol","h","full_output","even_grid","check_grid",NULL}; 235 | 236 | // Interpret input arguments. 237 | if (!PyArg_ParseTupleAndKeywords(args,kwargs,"OOOddDD|Oiiidddsii",const_cast(kwlist),&tsobj,&wsobj,&gsobj,&ti,&tf,&x0,&dx0,&t_evalobj,&islogw,&islogg,&order,&rtol,&atol,&h0,&full_output,&even,&check_grid)) 238 | return NULL; 239 | // Interpret input objects as numpy arrays 240 | PyObject *tsarray = PyArray_FROM_OTF(tsobj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY); 241 | PyObject *wsarray = PyArray_FROM_OTF(wsobj, NPY_CDOUBLE, NPY_ARRAY_IN_ARRAY); 242 | PyObject *gsarray = PyArray_FROM_OTF(gsobj, NPY_CDOUBLE, NPY_ARRAY_IN_ARRAY); 243 | PyObject *t_evalarray = NULL; 244 | if(t_evalobj!=NULL){ 245 | t_evalarray = PyArray_FROM_OTF(t_evalobj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY); 246 | } 247 | // If that didn't work, throw an exception 248 | if(tsarray==NULL || wsarray==NULL || gsarray==NULL){ 249 | Py_XDECREF(tsarray); 250 | Py_XDECREF(wsarray); 251 | Py_XDECREF(gsarray); 252 | return NULL; 253 | } 254 | 255 | // Get pointers to the data as c++-types 256 | PyArrayObject *tsarray_arr = reinterpret_cast(tsarray); 257 | PyArrayObject *wsarray_arr = reinterpret_cast(wsarray); 258 | PyArrayObject *gsarray_arr = reinterpret_cast(gsarray); 259 | PyArrayObject *t_evalarray_arr = NULL; 260 | if(t_evalobj!=NULL){ 261 | t_evalarray_arr = reinterpret_cast(t_evalarray); 262 | } 263 | double *ts = (double*)PyArray_DATA(tsarray_arr); 264 | std::complex *ws = (std::complex*)PyArray_DATA(wsarray_arr); 265 | std::complex *gs = (std::complex*)PyArray_DATA(gsarray_arr); 266 | double *t_eval = NULL; 267 | int t_evalsize = 0; 268 | if(t_evalobj!=NULL){ 269 | t_eval = (double*)PyArray_DATA(t_evalarray_arr); 270 | t_evalsize = (int)PyArray_SIZE(t_evalarray_arr); 271 | } 272 | std::list t_evallist; 273 | t_evallist.resize(t_evalsize); 274 | int i=0; 275 | for(auto it=t_evallist.begin(); it!=t_evallist.end(); it++){ 276 | *it = t_eval[i]; 277 | i++; 278 | } 279 | 280 | // Get array sizes 281 | int tssize = (int)PyArray_SIZE(tsarray_arr); 282 | int wssize = (int)PyArray_SIZE(wsarray_arr); 283 | int gssize = (int)PyArray_SIZE(gsarray_arr); 284 | 285 | try{ 286 | // Check 1: same size arrays, large enough arrays 287 | if(tssize != wssize || wssize != gssize || tssize != gssize) 288 | throw "The sizes of the input arrays (ts, ws, gs) do not match. Please supply arrays of the same size."; 289 | else if(tssize < 2) 290 | throw "The input arrays (ts, ws, gs) have to have at least size 2."; 291 | 292 | // Check 2: ts must be strictly monotonous 293 | double dir = ts[1] - ts[0]; 294 | double diff; 295 | for(int i=1; i 0) 297 | diff = ts[i] - ts[i-1]; 298 | else 299 | diff = ts[i-1] - ts[i]; 300 | if(diff < 0) 301 | throw "The time array (ts) must be strictly monotonous."; 302 | } 303 | 304 | // Check 3: integration limits ti, tf must lie inside ts 305 | if(ti < ts[0] || ti > ts[tssize-1]) 306 | throw "The start of integration, ti, must lie inside the array ts."; 307 | else if(tf < ts[0] || tf > ts[tssize-1]) 308 | throw "The end of integration, tf, must lie inside of the array ts."; 309 | 310 | // Check 4: dense output points must be inside integration range, and 311 | // monotonic 312 | if(t_evalsize!=0){ 313 | if(std::is_sorted(t_evallist.begin(), t_evallist.end()) == false) 314 | throw "The points at which dense output is requested are not in ascending order."; 315 | else if(h0>0 && (t_evallist.front() < ti || t_evallist.back() > tf)){ 316 | throw "The point at which dense output is requested must lie in the integration range (between ti, tf)."; 317 | } 318 | else if(h0<0 && (t_evallist.front() < tf || t_evallist.back() > ti)){ 319 | throw "The point at which dense output is requested must lie in the integration range (between ti, tf)."; 320 | } 321 | } 322 | // Check 5: direction of integration must match initial stepsize sign 323 | if( ((tf-ti)>0 && h0<0) || ((tf-ti)<0 && h0>0) ) 324 | throw "Direction of integration ( tf-ti ) in conflict with sign of initial step (h)."; 325 | 326 | } 327 | catch(const char* errormsg){ 328 | PyErr_SetString(PyExc_TypeError, errormsg); 329 | // Clean up 330 | Py_DECREF(tsarray); 331 | Py_DECREF(wsarray); 332 | Py_DECREF(gsarray); 333 | // if(t_evalobj!=NULL){ 334 | // Py_DECREF(t_evalarray); 335 | // } 336 | return (PyObject *) NULL; 337 | } 338 | 339 | // Call the C++ functions to construct system and solve 340 | de_system sys = de_system(ts,ws,gs,ts,tssize,islogw,islogg,even,check_grid); 341 | if(sys.grid_fine_enough!=1){ 342 | PyErr_WarnEx(PyExc_RuntimeWarning,"One or more of the arrays provided \ 343 | (w, g, logw, logg) may not be fine enough to carry out linear \ 344 | interpolation accurately. This may result in not traversing oscillatory \ 345 | regions of the solution efficiently and numerical inaccuracies. Please \ 346 | consider refining the sampling of the array(s) or switching to a more \ 347 | suitable independent variable.",1); 348 | } 349 | std::list> sol,dsol; 350 | std::list times; 351 | std::list wkbs; 352 | std::list> x_eval, dx_eval; 353 | std::list,7,1>> cts_rep; 354 | 355 | if(t_evalobj!=NULL){ 356 | Solution solution(sys,x0,dx0,ti,tf,t_evallist,order,rtol,atol,h0,full_output); 357 | solution.solve(); 358 | x_eval = solution.dosol; 359 | dx_eval = solution.dodsol; 360 | sol = solution.sol; 361 | dsol = solution.dsol; 362 | times = solution.times; 363 | wkbs = solution.wkbs; 364 | cts_rep = solution.sol_vdm; 365 | } 366 | else{ 367 | Solution solution(sys,x0,dx0,ti,tf,order,rtol,atol,h0,full_output); 368 | solution.solve(); 369 | x_eval = solution.dosol; 370 | dx_eval = solution.dodsol; 371 | sol = solution.sol; 372 | dsol = solution.dsol; 373 | times = solution.times; 374 | wkbs = solution.wkbs; 375 | cts_rep = solution.sol_vdm; 376 | } 377 | // Build output values 378 | int Ndense = x_eval.size(); 379 | PyObject *pyx_eval = PyList_New(Ndense), *pydx_eval = PyList_New(Ndense); 380 | int Neval = 0; 381 | auto itx_eval = x_eval.begin(); 382 | auto itdx_eval = dx_eval.begin(); 383 | for(int Neval=0; Neval 3 | #include "_python.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* Docstrings */ 14 | static char module_docstring[] = 15 | "pyoscode: this module provides an interface for oscode, for solving\ 16 | oscillatory ordinary differential equations with the RKWKB method."; 17 | 18 | static char solve_docstring[] = "Runs the solver with w, g provided as arrays", 19 | solve_fn_docstring[] = "Runs the solver with w, g provided as\ 20 | functions"; 21 | 22 | /* Available functions */ 23 | /* Solve with w, g provided as arrays */ 24 | static PyObject *_pyoscode_solve(PyObject *self, PyObject *args, PyObject *kwargs); 25 | /* Solve with w, g provided as functions */ 26 | static PyObject *_pyoscode_solve_fn(PyObject *self, PyObject *args, PyObject *kwargs); 27 | /* Callback functions */ 28 | static std::complex w(double t); 29 | static std::complex g(double t); 30 | 31 | /* Module interface */ 32 | static PyMethodDef module_methods[] = { 33 | {"solve", (PyCFunction) _pyoscode_solve, METH_VARARGS | METH_KEYWORDS, solve_docstring}, 34 | {"solve_fn", (PyCFunction) _pyoscode_solve_fn, METH_VARARGS | METH_KEYWORDS, solve_fn_docstring}, 35 | {NULL, NULL, 0, NULL} 36 | }; 37 | 38 | #ifdef PYTHON3 39 | static struct PyModuleDef _pyoscodemodule = { 40 | PyModuleDef_HEAD_INIT, 41 | "_pyoscode", 42 | module_docstring, 43 | -1, 44 | module_methods 45 | }; 46 | #endif 47 | -------------------------------------------------------------------------------- /pyoscode/_python.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #if PY_MAJOR_VERSION >=3 4 | #define PYTHON3 5 | #endif 6 | -------------------------------------------------------------------------------- /pyoscode/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /pyoscode/docs/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | sphinx>3.0 3 | numpydoc 4 | coverage 5 | breathe 6 | exhale 7 | mock 8 | sphinx_rtd_theme 9 | -------------------------------------------------------------------------------- /pyoscode/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.append(os.path.abspath('../../../')) 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = u'(py)oscode' 22 | copyright = u'2019, Fruzsina Agocs' 23 | author = u'Fruzsina Agocs' 24 | 25 | # The short X.Y version 26 | version = u'' 27 | # The full version, including alpha/beta/rc tags 28 | release = u'1.0' 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | # 35 | # needs_sphinx = '1.0' 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | 'sphinx.ext.autodoc', 42 | 'sphinx.ext.doctest', 43 | 'sphinx.ext.intersphinx', 44 | 'sphinx.ext.todo', 45 | 'sphinx.ext.coverage', 46 | 'sphinx.ext.mathjax', 47 | 'sphinx.ext.ifconfig', 48 | 'sphinx.ext.viewcode', 49 | 'sphinx.ext.githubpages', 50 | 'numpydoc', 51 | 'breathe', 52 | 'exhale' 53 | ] 54 | 55 | # Set up the breathe extension 56 | breathe_projects = { 57 | "oscode": "./doxyoutput/xml" 58 | } 59 | 60 | breathe_default_project = "oscode" 61 | 62 | # Setup the exhale extension 63 | exhale_args = { 64 | # These arguments are required 65 | "containmentFolder": "./cpp-docs", 66 | "rootFileName": "oscode-reference.rst", 67 | "rootFileTitle": "oscode", 68 | "doxygenStripFromPath": "..", 69 | # Suggested optional arguments 70 | "createTreeView": True, 71 | # TIP: if using the sphinx-bootstrap-theme, you need 72 | # "treeViewIsBootstrap": True, 73 | "exhaleExecutesDoxygen": True, 74 | "exhaleDoxygenStdin": "INPUT = ./../../../include/oscode\nRECURSIVE = NO\nGENERATE_HTML = YES", 75 | "verboseBuild" : True 76 | } 77 | 78 | primary_domain = 'cpp' 79 | 80 | higlhight_language = 'cpp' 81 | 82 | 83 | # Add any paths that contain templates here, relative to this directory. 84 | templates_path = ['_templates'] 85 | 86 | # The suffix(es) of source filenames. 87 | # You can speify multiple suffix as a list of string: 88 | # 89 | # source_suffix = ['.rst', '.md'] 90 | source_suffix = '.rst' 91 | 92 | # The master toctree document. 93 | master_doc = 'index' 94 | 95 | # The language for content autogenerated by Sphinx. Refer to documentation 96 | # for a list of supported languages. 97 | # 98 | # This is also used if you do content translation via gettext catalogs. 99 | # Usually you set "language" from the command line for these cases. 100 | language = None 101 | 102 | # List of patterns, relative to source directory, that match files and 103 | # directories to ignore when looking for source files. 104 | # This pattern also affects html_static_path and html_extra_path. 105 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] 106 | 107 | # The name of the Pygments (syntax highlighting) style to use. 108 | pygments_style = 'sphinx' 109 | 110 | 111 | # -- Options for HTML output ------------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | # 116 | html_theme = 'sphinx_rtd_theme' 117 | 118 | # Theme options are theme-specific and customize the look and feel of a theme 119 | # further. For a list of options available for each theme, see the 120 | # documentation. 121 | # 122 | # html_theme_options = {} 123 | 124 | # Add any paths that contain custom static files (such as style sheets) here, 125 | # relative to this directory. They are copied after the builtin static files, 126 | # so a file named "default.css" will overwrite the builtin "default.css". 127 | html_static_path = ['_static'] 128 | 129 | # Custom sidebar templates, must be a dictionary that maps document names 130 | # to template names. 131 | # 132 | # The default sidebars (for documents that don't match any pattern) are 133 | # defined by theme itself. Builtin themes are using these templates by 134 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 135 | # 'searchbox.html']``. 136 | # 137 | # html_sidebars = {} 138 | 139 | 140 | # -- Options for HTMLHelp output --------------------------------------------- 141 | 142 | # Output file base name for HTML help builder. 143 | htmlhelp_basename = 'oscodedoc' 144 | 145 | 146 | # -- Options for LaTeX output ------------------------------------------------ 147 | 148 | latex_elements = { 149 | # The paper size ('letterpaper' or 'a4paper'). 150 | # 151 | # 'papersize': 'letterpaper', 152 | 153 | # The font size ('10pt', '11pt' or '12pt'). 154 | # 155 | # 'pointsize': '10pt', 156 | 157 | # Additional stuff for the LaTeX preamble. 158 | # 159 | # 'preamble': '', 160 | 161 | # Latex figure (float) alignment 162 | # 163 | # 'figure_align': 'htbp', 164 | } 165 | 166 | # Grouping the document tree into LaTeX files. List of tuples 167 | # (source start file, target name, title, 168 | # author, documentclass [howto, manual, or own class]). 169 | latex_documents = [ 170 | (master_doc, '(py)oscode.tex', u'(py)oscode Documentation', 171 | u'Fruzsina Agocs', 'manual'), 172 | ] 173 | 174 | 175 | # -- Options for manual page output ------------------------------------------ 176 | 177 | # One entry per manual page. List of tuples 178 | # (source start file, name, description, authors, manual section). 179 | man_pages = [ 180 | (master_doc, '(py)oscode', u'(py)oscode Documentation', 181 | [author], 1) 182 | ] 183 | 184 | 185 | # -- Options for Texinfo output ---------------------------------------------- 186 | 187 | # Grouping the document tree into Texinfo files. List of tuples 188 | # (source start file, target name, title, author, 189 | # dir menu entry, description, category) 190 | texinfo_documents = [ 191 | (master_doc, '(py)oscode', u'(py)oscode Documentation', 192 | author, 'oscode', 'Fast solution of oscillatory ODEs', 193 | 'Miscellaneous'), 194 | ] 195 | 196 | 197 | # -- Options for Epub output ------------------------------------------------- 198 | 199 | # Bibliographic Dublin Core info. 200 | epub_title = project 201 | epub_author = author 202 | epub_publisher = author 203 | epub_copyright = copyright 204 | 205 | # The unique identifier of the text. This can be a ISBN number 206 | # or the project homepage. 207 | # 208 | # epub_identifier = '' 209 | 210 | # A unique identification for the text. 211 | # 212 | # epub_uid = '' 213 | 214 | # A list of files that should not be packed into the epub file. 215 | epub_exclude_files = ['search.html'] 216 | 217 | 218 | # -- Extension configuration ------------------------------------------------- 219 | 220 | # -- Options for todo extension ---------------------------------------------- 221 | 222 | # If true, `todo` and `todoList` produce output, else they produce nothing. 223 | todo_include_todos = True 224 | 225 | # To mock out import of modules with C dependencies 226 | import sys 227 | from mock import Mock as MagicMock 228 | 229 | class Mock(MagicMock): 230 | @classmethod 231 | def __getattr__(cls,name): 232 | return MagicMock() 233 | 234 | MOCK_MODULES = ["_pyoscode"] 235 | sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) 236 | -------------------------------------------------------------------------------- /pyoscode/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | (py)oscode's documentation 2 | ========================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | Introduction 9 | Python interface reference 10 | Using the C++ interface 11 | C++ interface reference 12 | 13 | -------------------------------------------------------------------------------- /pyoscode/docs/source/introduction.rst: -------------------------------------------------------------------------------- 1 | .. title:: Introduction 2 | 3 | ======================================================================== 4 | (py)oscode: Oscillatory ordinary differential equation solver 5 | ======================================================================== 6 | 7 | .. image:: https://codecov.io/gh/fruzsinaagocs/oscode/branch/joss-paper/graph/badge.svg 8 | :target: https://codecov.io/gh/fruzsinaagocs/oscode 9 | .. image:: https://travis-ci.org/fruzsinaagocs/oscode.svg?branch=master 10 | :target: https://travis-ci.org/fruzsinaagocs/oscode 11 | :alt: Travis CI build status 12 | .. image:: https://readthedocs.org/projects/oscode/badge/?version=latest 13 | :target: https://oscode.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | .. image:: https://badges.gitter.im/oscode-help/community.svg 16 | :target: https://gitter.im/oscode-help/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge 17 | :alt: Chat on gitter 18 | .. image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg 19 | :target: https://opensource.org/licenses/BSD-3-Clause 20 | :alt: BSD 3-clause license 21 | 22 | | 23 | | 24 | 25 | .. contents:: 26 | :local: 27 | 28 | | 29 | 30 | About 31 | ----- 32 | 33 | ``oscode`` is a C++ tool with a Python interface that solves **osc**\illatory 34 | **o**\rdinary **d**\ifferential **e**\quations efficiently. It is designed to 35 | deal with equations of the form 36 | 37 | .. math:: 38 | 39 | \ddot{x}(t) + 2\gamma(t)\dot{x}(t) + \omega^2(t)x(t) = 0, 40 | 41 | where :math:`\gamma(t)` and :math:`\omega(t)` can be given as arrays. 42 | 43 | 44 | ``oscode`` makes use of an analytic approximation of :math:`x(t)` embedded in a 45 | stepping procedure to skip over long regions of oscillations, giving a reduction 46 | in computing time. The approximation is valid when the frequency 47 | :math:`\omega(t)` changes slowly relative to the timescales of integration, it 48 | is therefore worth applying when this condition holds for at least some part of 49 | the integration range. 50 | 51 | For the details of the numerical method used by ``oscode``, see the Citations 52 | section. 53 | 54 | 55 | Installation 56 | ------------ 57 | 58 | Dependencies 59 | ~~~~~~~~~~~~ 60 | 61 | Basic requirements for using the C++ interface: 62 | 63 | - C++11 or later 64 | - `Eigen `__ (a header-only library included in this source) 65 | 66 | The strictly necessary Python dependencies are automatically installed when you use `pip` or the `setup.py`. They are: 67 | 68 | - `numpy `__ 69 | 70 | The *optional* dependencies are: 71 | 72 | - for tests 73 | - `scipy `__ 74 | - `pytest `__ 75 | - for examples/plotting 76 | - `matplotlib `__ 77 | - `scipy `__ 78 | - for generating offline documentation 79 | - `sphinx `__ 80 | - `doxygen `__ 81 | - `breathe `__ 82 | - `exhale `__ 83 | 84 | 85 | Python 86 | ~~~~~~ 87 | 88 | ``pyoscode`` can be installed via pip 89 | 90 | .. code:: bash 91 | 92 | pip install pyoscode 93 | 94 | or via the setup.py 95 | 96 | .. code:: bash 97 | 98 | git clone https://github.com/fruzsinaagocs/oscode 99 | cd oscode 100 | python setup.py install --user 101 | 102 | You can then import ``pyoscode`` from anywhere. Omit the ``--user`` option if 103 | you wish to install globally or in a virtual environment. If you have any 104 | difficulties, check out the FAQs_ section below. 105 | 106 | You can check that things are working by running `tests/` (also ran by Travis continuous integration): 107 | 108 | .. code:: bash 109 | 110 | pytest tests/ 111 | 112 | 113 | C++ 114 | ~~~ 115 | 116 | ``oscode`` is a header-only C++ package, it requires no installation. 117 | 118 | .. code:: bash 119 | 120 | git clone https://github.com/fruzsinaagocs/oscode 121 | 122 | and then include the relevant header files in your C++ code: 123 | 124 | .. code:: c 125 | 126 | #include "solver.hpp" 127 | #include "system.hpp" 128 | 129 | 130 | Quick start 131 | ----------- 132 | 133 | Try the following quick examples. They are available in the `examples 134 | `__. 135 | 136 | Python 137 | ~~~~~~ 138 | 139 | :Introduction to pyoscode: |intro_binder| 140 | :Cosmology examples: |cosmology_binder| 141 | 142 | .. |intro_binder| image:: https://mybinder.org/badge_logo.svg 143 | :target: https://mybinder.org/v2/gh/fruzsinaagocs/oscode/joss-paper?filepath=examples/introduction_to_pyoscode.ipynb 144 | 145 | .. |cosmology_binder| image:: https://mybinder.org/badge_logo.svg 146 | :target: https://mybinder.org/v2/gh/fruzsinaagocs/oscode/joss-paper?filepath=examples/cosmology.ipynb 147 | 148 | .. image:: 149 | https://github.com/fruzsinaagocs/oscode/raw/master/pyoscode/images/spectra.gif 150 | :width: 800 151 | 152 | 153 | C++ 154 | ~~~ 155 | 156 | :Introduction to oscode: `examples/burst.cpp` 157 | :To plot results from `burst.cpp`: `examples/plot_burst.py` 158 | 159 | To compile and run: 160 | 161 | .. code:: bash 162 | 163 | g++ -g -Wall -std=c++11 -c -o burst.o burst.cpp 164 | g++ -g -Wall -std=c++11 -o burst burst.o 165 | ./burst 166 | 167 | 168 | Documentation 169 | ------------- 170 | 171 | Documentation is hosted at `readthedocs `__. 172 | 173 | To build your own local copy of the documentation you can run: 174 | 175 | .. code:: bash 176 | 177 | cd pyoscode/docs 178 | make html 179 | 180 | Citation 181 | -------- 182 | 183 | If you use ``oscode`` to solve equations for a publication, please cite: 184 | 185 | - `Efficient method for solving highly oscillatory ordinary differential equations with applications to physical systems `__, 186 | - `Dense output for highly oscillatory numerical solutions `__ 187 | 188 | 189 | Contributing 190 | ------------ 191 | 192 | Any comments and improvements to this project are welcome. You can contribute 193 | by: 194 | 195 | - Opening and `issue `__ to report bugs and propose new features. 196 | - Making a pull request. 197 | 198 | Further help 199 | ------------ 200 | 201 | You can get help by submitting an issue or posting a message on `Gitter `__. 202 | 203 | 204 | FAQs 205 | ---- 206 | 207 | Installation 208 | ~~~~~~~~~~~~ 209 | 210 | 1. Eigen import errors: 211 | .. code:: bash 212 | 213 | pyoscode/_pyoscode.hpp:6:10: fatal error: Eigen/Dense: No such file or directory 214 | #include 215 | ^~~~~~~~~~~~~ 216 | 217 | Try explicitly including the location of your Eigen library via the 218 | ``CPLUS_INCLUDE_PATH`` environment variable, for example: 219 | 220 | .. code:: bash 221 | 222 | CPLUS_INCLUDE_PATH=/usr/include/eigen3 python setup.py install --user 223 | # or 224 | CPLUS_INCLUDE_PATH=/usr/include/eigen3 pip install pyoscode 225 | 226 | where ``/usr/include/eigen3`` should be replaced with your system-specific 227 | eigen location. 228 | 229 | 230 | Thanks 231 | ------ 232 | 233 | Many thanks to **Will Handley**, **Lukas Hergt**, **Anthony Lasenby**, and **Mike Hobson** for 234 | their support and advice regarding the algorithm behind `oscode`. 235 | There are many packages without which some part of `oscode` (e.g. testing and 236 | examples) wouldn't run as nicely and smoothly, thank you all developers for 237 | making and maintaining these open-source projects. A special thanks goes to the 238 | devs of `exhale `__ for making the beautiful C++ documentation possible. 239 | 240 | 241 | Changelog 242 | --------- 243 | 244 | - 1.1.2: current version 245 | - Dense output bug fix at the C++ interface 246 | - 1.1.1: 247 | - Support for mac and Windows OS at CI. 248 | - 1.1.0: 249 | - Users can now define w, g as functions in Python (pyoscode) and call the solver via pyoscode.solve_fn(...) 250 | - 1.0.6: 251 | - Fix issues related to dense output not being correctly generated, e.g. when timepoints at which dense output was asked for are in descending order, etc. 252 | - 1.0.5: 253 | - Fixes related to dense output generation 254 | - Support for w, g to be given as class member functions in C++ 255 | - Switched to GH actions for continuous integration, and fixed code such that unit tests would run again 256 | - Minor tweaks 257 | - 1.0.4: 258 | - set minimally required numpy version: numpy>=1.20.0 259 | - drop Python 2.7 support, instead support 3.8 and 3.9 in addition to 3.7 260 | - 1.0.3: 261 | - paper accepted to JOSS 262 | - 1.0.2: 263 | - Fixed getting correct numpy include directories 264 | - 1.0.1: 265 | - Added `pyproject.toml` to handle build dependencies (numpy) 266 | - 1.0.0: 267 | - Dense output 268 | - Arrays for frequency and damping term need not be evenly spaced 269 | - Automatic C++ documentation on readthedocs 270 | - Eigen included in source for pip installability 271 | - First pip release :) 272 | - 0.1.2: 273 | - Bug that occurred when beginning and end of integration coincided 274 | corrected 275 | - 0.1.1: 276 | - Automatic detection of direction of integration 277 | - 0.1.0: 278 | - Memory leaks at python interface fixed 279 | - C++ documentation added 280 | 281 | -------------------------------------------------------------------------------- /pyoscode/docs/source/oscode.rst: -------------------------------------------------------------------------------- 1 | .. title:: oscode (C++ interface) 2 | 3 | ================================ 4 | Using the C++ interface (oscode) 5 | ================================ 6 | 7 | .. sectnum:: 8 | 9 | 10 | Overview 11 | -------- 12 | 13 | This documentation illustrates how one can use ``oscode`` via its C++ interface. 14 | Usage of ``oscode`` involves 15 | 16 | - defining an equation to solve, 17 | - solving the equation, 18 | - and extracting the solution and other statistics about the run. 19 | 20 | The next sections will cover each of these. For a complete reference, see the 21 | :doc:`C++ interface reference` page, and for examples 22 | see the `examples 23 | `__ directory on 24 | GitHub. 25 | 26 | Defining an equation 27 | -------------------- 28 | 29 | The equations ``oscode`` can be used to solve are of the form 30 | 31 | .. math:: 32 | 33 | \ddot{x}(t) + 2\gamma(t)\dot{x}(t) + \omega^2(t)x(t) = 0, 34 | 35 | where :math:`x(t)`, :math:`\gamma(t)`, :math:`\omega(t)` can be complex. We will 36 | call :math:`t` the independent variable, :math:`x` the dependent variable, 37 | :math:`\omega(t)` the frequency term, and :math:`\gamma(t)` the friction or 38 | first-derivative term. 39 | 40 | Defining an equation is via 41 | 42 | - giving the frequency :math:`\omega(t)`, 43 | - giving the first-derivative term :math:`\gamma(t)`, 44 | 45 | Defining the frequency and the first-derivative term can either be done by 46 | giving them as **functions explicitly**, or by giving them as **sequences** evaluated on a grid of :math:`t`. 47 | 48 | :math:`\omega` and :math:`\gamma` as explicit functions 49 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 | 51 | If :math:`\omega` and :math:`\gamma` are closed-form functions of time, then 52 | define them as 53 | 54 | .. code:: c 55 | 56 | #include "solver.hpp" // de_system, Solution defined in here 57 | 58 | std::complex g(double t){ 59 | return 0.0; 60 | }; 61 | 62 | std::complex w(double t){ 63 | return std::pow(9999,0.5)/(1.0 + t*t); 64 | }; 65 | 66 | Then feed them to the solver via the de_system class: 67 | 68 | .. code:: c 69 | 70 | de_system sys(&w, &g); 71 | Solution solution(sys, ...) // other arguments left out 72 | 73 | :math:`\omega` and :math:`\gamma` as time series 74 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 75 | 76 | Sometimes :math:`\omega` and :math:`\gamma` will be results of numerical 77 | integration, and they will have no closed-form functional form. In this case, 78 | they can be specified on a grid, and ``oscode`` will perform linear 79 | interpolation on the given grid to find their values at any timepoint. Because 80 | of this, some important things to **note** are: 81 | 82 | - ``oscode`` will assume the grid of timepoints :math:`\omega` and :math:`\gamma` are **not evenly spaced**. If the grids are evenly sampled, set ``even=true`` in the call for ``de_system()``, this will speed linear interpolation up significantly. 83 | - The timepoints grid needs to be **monotonically increasing**. 84 | - The timepoints grid needs to **include the range of integration** (:math:`t_i`,:math:`t_f`). 85 | - The grids for the timepoints, frequencies, and first-derivative terms have to be the **same size**. 86 | - The speed/efficiency of the solver depends on how accurately it can carry out numerical integrals of the frequency and the first-derivative terms, therefore the **grid fineness** needs to be high enough. (Typically this means that linear interpolation gives a :math:`\omega(t)` value that is accurate to 1 part in :math:`10^{6}` or so.) If you want `oscode` to check whether the grids were sampled finely enough, set ``check_grid=true`` in the call for ``de_system()``. 87 | 88 | To define the grids, use any array-like container which is **contiguous in 89 | memory**, e.g. an ``Eigen::Vector``, ``std::array``, ``std::vector``: 90 | 91 | .. code:: c 92 | 93 | #include "solver.hpp" // de_system, Solution defined in here 94 | 95 | // Create a fine grid of timepoints and 96 | // a grid of values for w, g 97 | N = 10000; 98 | std::vector ts(N); 99 | std::vector> ws(N), gs(N); 100 | 101 | // Fill up the grids 102 | for(int i=0; i ts(N); 130 | std::vector logws(N), gs(N); // Note the log! 131 | 132 | // Fill up the grids 133 | for(int i=0; i g(double t){ 171 | int i; 172 | i=int((t-tstart)/tinc); 173 | std::complex g0 = 0.5*(k*k*A[i] + 3.0 - B[i] + C[i]*k; 174 | std::complex g1 = 0.5*(k*k*A[i+1] + 3.0 - B[i+1] + C[i+1]*k); 175 | return (g0+(g1-g0)*(t-tstart-tinc*i)/tinc); 176 | }; 177 | 178 | 179 | Solving an equation 180 | ------------------- 181 | 182 | Once the equation to be solver has been defined as an instance of the 183 | ``de_system`` class, the following additional information is necessary to solve 184 | it: 185 | 186 | - initial conditions, :math:`x(t_i)` and :math:`\dot{x}(t_f)`, 187 | - the range of integration, from :math:`t_i` and :math:`t_f`, 188 | - (optional) set of timepoints at which dense output is required, 189 | - (optional) order of WKB approximation to use, ``order=3``, 190 | - (optional) relative tolerance, ``rtol=1e-4``, 191 | - (optional) absolute tolerance ``atol=0.0``, 192 | - (optional) initial step ``h_0=1``, 193 | - (optional) output file name ``full_output=""``, 194 | 195 | **Note** the following about the optional arguments: 196 | 197 | - ``rtol``, ``atol`` are tolerances on the local error. The global error in the solution is not guaranteed to stay below these values, but the error per step is. In the RK regime (not oscillatory solution), the global error will rise above the tolerance limits, but in the WKB regime, the global error usually stagnates. 198 | - The initial step should be thought of as an initial estimate of what the first stepsize should be. The solver will determine the largest possible step within the given tolerance limit, and change ``h_0`` if necessary. 199 | - The full output of ``solve()`` will be written to the filename contained in ``full_output``, if specified. 200 | 201 | Here's an example to illustrate usage of all of the above variables: 202 | 203 | .. code:: c 204 | 205 | #include "solver.hpp" // de_system, Solution defined in here 206 | 207 | // Define the system 208 | de_system sys(...) // For args see previous examples 209 | 210 | // Necessary parameters: 211 | // initial conditions 212 | std::complex x0=std::complex(1.0,1.0), dx0=0.0; 213 | // range of integration 214 | double ti=1.0, tf=100.0; 215 | 216 | // Optional parameters: 217 | // dense output will be required at the following points: 218 | int n = 1000; 219 | std::vector t_eval(n); 220 | for(int i=0; i]: the solution at the timepoints specified in ``times``. 248 | - ``dsol`` [std::list of std::complex]: first derivative of the solution at timepoints specified in ``times``. 249 | - ``wkbs`` [std::list of int/bool]: types of steps takes at each timepoint in ``times``. **1** if the step was WKB, **0** if it was RK. 250 | - ``ssteps`` [int]: total number of accepted steps. 251 | - ``totsteps`` [int]: total number of attempted steps (accepted + rejected). 252 | - ``wkbsteps`` [int]: total number of successful WKB steps. 253 | - ``x_eval`` [std::list of std::complex]: dense output, i.e. the solution evaluated at the points specified in the ``t_eval`` optional argument 254 | - ``dx_eval`` [std::list of std::complex]: dense output of the derivative of the solution, evaluted at the points specified in ``t_eval`` optional argument. 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /pyoscode/docs/source/pyoscode.rst: -------------------------------------------------------------------------------- 1 | pyoscode 2 | ======== 3 | 4 | .. automodule:: pyoscode 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /pyoscode/images/airy-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/pyoscode/images/airy-example.png -------------------------------------------------------------------------------- /pyoscode/images/burst-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/pyoscode/images/burst-example.png -------------------------------------------------------------------------------- /pyoscode/images/gamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/pyoscode/images/gamma.png -------------------------------------------------------------------------------- /pyoscode/images/omega.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/pyoscode/images/omega.png -------------------------------------------------------------------------------- /pyoscode/images/oscillator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/pyoscode/images/oscillator.gif -------------------------------------------------------------------------------- /pyoscode/images/oscillator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/pyoscode/images/oscillator.png -------------------------------------------------------------------------------- /pyoscode/images/spectra.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruzsinaagocs/oscode/4dd643b48596138861aca7fa8ad4b75e07f35c38/pyoscode/images/spectra.gif -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["numpy", "setuptools>=40.8.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.20.0 2 | scipy 3 | matplotlib 4 | sphinx 5 | numpydoc 6 | breathe 7 | exhale 8 | sphinx_rtd_theme 9 | mock 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, with_statement, print_function, division 2 | from setuptools import setup, Extension, find_packages 3 | import os 4 | import numpy as np 5 | 6 | def readme(short=False): 7 | with open("README.rst") as f: 8 | if short: 9 | return f.readlines()[1].strip() 10 | else: 11 | return f.read() 12 | 13 | pyoscode_module = Extension( 14 | name="_pyoscode", 15 | sources=["pyoscode/_pyoscode.cpp"], 16 | include_dirs=['include','pyoscode',np.get_include(),'deps/eigen'], 17 | depends=["pyoscode/_python.hpp", "pyoscode/_pyoscode.hpp"], 18 | extra_compile_args=['-std=c++17','-Wall'] 19 | ) 20 | 21 | setup( 22 | name="pyoscode", 23 | version="1.3.0", 24 | description=readme(short=True), 25 | long_description=readme(), 26 | url="https://github.com/fruzsinaagocs/oscode", 27 | project_urls={"Documentation":"https://oscode.readthedocs.io"}, 28 | author="Fruzsina Agocs", 29 | author_email="fruzsina.agocs@colorado.edu", 30 | packages=find_packages(), 31 | install_requires=["numpy"], 32 | extras_require={"examples:":["matplotlib", "scipy", "jupyter"], 33 | "docs":["sphinx","sphinx-rtd-theme","numpydoc"], "testing":["pytest"]}, 34 | setup_requires=["pytest-runner","numpy"], 35 | tests_require=["pytest", "numpy", "scipy"], 36 | include_package_data=True, 37 | license="oscode", 38 | ext_modules=[pyoscode_module], 39 | headers=["pyoscode/_python.hpp", "pyoscode/_pyoscode.hpp"], 40 | include_dirs=[np.get_include()], 41 | keywords="PPS, cosmic inflation, cosmology, oscillatory, ODE", 42 | classifiers=[ 43 | 'Intended Audience :: Developers', 44 | 'Intended Audience :: Science/Research', 45 | 'Natural Language :: English', 46 | 'Programming Language :: Python :: 3.x', 47 | 'Topic :: Scientific/Engineering', 48 | 'Topic :: Scientific/Engineering :: Astronomy', 49 | 'Topic :: Scientific/Engineering :: Physics', 50 | 'Topic :: Scientific/Engineering :: Visualization', 51 | 'Topic :: Scientific/Engineering :: Mathematics' 52 | ], 53 | ) 54 | 55 | -------------------------------------------------------------------------------- /tests/test_airy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pyoscode 3 | from scipy.special import airy 4 | import pytest 5 | 6 | def test_no_integration(): 7 | # Tests the case when ti=tf 8 | ts = np.linspace(1,100,5000) 9 | ws = np.sqrt(ts) 10 | gs = np.zeros_like(ws) 11 | ti = 1.0 12 | tf = ti 13 | x0 = airy(-ti)[0] + 1j*airy(-ti)[2] 14 | dx0 = -airy(-ti)[1] - 1j*airy(-ti)[3] 15 | sol = pyoscode.solve(ts, ws, gs, ti, tf, x0, dx0, even_grid=True) 16 | t = np.asarray(sol['t']) 17 | x = np.asarray(sol['sol']) 18 | dx = np.asarray(sol['dsol']) 19 | assert (x.shape[0] == 1 and dx.shape[0] == 1 and x[0] == x0 and dx[0] == dx0) 20 | 21 | 22 | def test_airy_forward_even(): 23 | # Integration forward on even grid 24 | ts = np.linspace(1,100,5000) 25 | ws = np.sqrt(ts) 26 | gs = np.zeros_like(ws) 27 | ti = 1.0 28 | tf = 100.0 29 | x0 = airy(-ti)[0] + 1j*airy(-ti)[2] 30 | dx0 = -airy(-ti)[1] - 1j*airy(-ti)[3] 31 | t_eval = np.linspace(ti,tf,5000) 32 | sol = pyoscode.solve(ts, ws, gs, ti, tf, x0, dx0, t_eval=t_eval, even_grid=True) 33 | t = np.asarray(sol['t']) 34 | x = np.asarray(sol['sol']) 35 | dense = np.asarray(sol['x_eval']) 36 | dense_d = np.asarray(sol['dx_eval']) 37 | ana_t = np.linspace(ti,tf,5000) 38 | ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in t]) 39 | dense_ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in ana_t]) 40 | dense_ana_dx = np.asarray([-airy(-T)[1]-1j*airy(-T)[3] for T in ana_t]) 41 | dense_error = np.abs((dense-dense_ana_x)/dense_ana_x)/0.01 > 1.0 42 | dense_error_d = np.abs((dense_d-dense_ana_dx)/dense_ana_dx)/0.01 > 1.0 43 | error = np.abs((x-ana_x)/ana_x)/0.01 > 1.0 44 | assert (np.any(dense_error) == False and np.any(dense_error_d) == False and np.any(error) == False ) 45 | 46 | 47 | def test_airy_forward_uneven(): 48 | # Integration forward on uneven grid 49 | ts = np.logspace(0,2,5000) 50 | ws = np.sqrt(ts) 51 | gs = np.zeros_like(ws) 52 | ti = 1.0 53 | tf = 100.0 54 | x0 = airy(-ti)[0] + 1j*airy(-ti)[2] 55 | dx0 = -airy(-ti)[1] - 1j*airy(-ti)[3] 56 | t_eval = np.linspace(ti,tf,5000) 57 | sol = pyoscode.solve(ts, ws, gs, ti, tf, x0, dx0, t_eval=t_eval) 58 | t = np.asarray(sol['t']) 59 | x = np.asarray(sol['sol']) 60 | dense = np.asarray(sol['x_eval']) 61 | dense_d = np.asarray(sol['dx_eval']) 62 | types = np.asarray(sol['types']) 63 | ana_t = np.linspace(ti,tf,5000) 64 | ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in t]) 65 | dense_ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in ana_t]) 66 | dense_ana_dx = np.asarray([-airy(-T)[1]-1j*airy(-T)[3] for T in ana_t]) 67 | dense_error = np.abs((dense-dense_ana_x)/dense_ana_x)/0.01 > 1.0 68 | dense_error_d = np.abs((dense_d-dense_ana_dx)/dense_ana_dx)/0.01 > 1.0 69 | error = np.abs((x-ana_x)/ana_x)/0.01 > 1.0 70 | assert (np.any(dense_error) == False and np.any(dense_error_d) == False and np.any(error) == False ) 71 | 72 | 73 | def test_airy_backward_even(): 74 | # Integration backwards on even grid 75 | ts = np.linspace(1,100,5000) 76 | ws = np.sqrt(ts) 77 | gs = np.zeros_like(ws) 78 | ti = 1.0 79 | tf = 100.0 80 | t_eval = np.linspace(ti,tf,5000) 81 | x0 = airy(-tf)[0] + 1j*airy(-tf)[2] 82 | dx0 = -airy(-tf)[1] - 1j*airy(-tf)[3] 83 | sol = pyoscode.solve(ts, ws, gs, tf, ti, x0, dx0, t_eval=t_eval, even_grid=True) 84 | t = np.asarray(sol['t']) 85 | x = np.asarray(sol['sol']) 86 | dense = np.asarray(sol['x_eval']) 87 | dense_d = np.asarray(sol['dx_eval']) 88 | types = np.asarray(sol['types']) 89 | ana_t = np.linspace(ti,tf,5000) 90 | ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in t]) 91 | dense_ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in ana_t]) 92 | dense_ana_dx = np.asarray([-airy(-T)[1]-1j*airy(-T)[3] for T in ana_t]) 93 | dense_error = np.abs((dense-dense_ana_x)/dense_ana_x)/0.01 > 1.0 94 | dense_error_d = np.abs((dense_d-dense_ana_dx)/dense_ana_dx)/0.01 > 1.0 95 | error = np.abs((x-ana_x)/ana_x)/0.01 > 1.0 96 | assert (np.any(dense_error) == False and np.any(dense_error_d) == False and np.any(error) == False ) 97 | 98 | 99 | 100 | def test_airy_backward_uneven(): 101 | # Integration backwards on uneven grid 102 | ts = np.logspace(0,2,5000) 103 | ws = np.sqrt(ts) 104 | gs = np.zeros_like(ws) 105 | ti = 1.0 106 | tf = 100.0 107 | t_eval = np.linspace(ti,tf,5000) 108 | x0 = airy(-tf)[0] + 1j*airy(-tf)[2] 109 | dx0 = -airy(-tf)[1] - 1j*airy(-tf)[3] 110 | sol = pyoscode.solve(ts, ws, gs, tf, ti, x0, dx0, t_eval=t_eval) 111 | t = np.asarray(sol['t']) 112 | x = np.asarray(sol['sol']) 113 | dense = np.asarray(sol['x_eval']) 114 | dense_d = np.asarray(sol['dx_eval']) 115 | types = np.asarray(sol['types']) 116 | ana_t = t_eval 117 | ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in t]) 118 | dense_ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in ana_t]) 119 | dense_ana_dx = np.asarray([-airy(-T)[1]-1j*airy(-T)[3] for T in ana_t]) 120 | dense_error = np.abs((dense-dense_ana_x)/dense_ana_x)/0.01 > 1.0 121 | dense_error_d = np.abs((dense_d-dense_ana_dx)/dense_ana_dx)/0.01 > 1.0 122 | error = np.abs((x-ana_x)/ana_x)/0.01 > 1.0 123 | assert (np.any(dense_error) == False and np.any(dense_error_d) == False and np.any(error) == False ) 124 | 125 | def test_airy_wgfunctions(): 126 | # w, g are supplied as functions instead of arrays 127 | w = lambda t: np.sqrt(t) 128 | g = lambda t: 0.0 129 | ti = 1.0 130 | tf = 100.0 131 | t_eval = np.linspace(ti,tf,5000) 132 | x0 = airy(-ti)[0] + 1j*airy(-ti)[2] 133 | dx0 = -airy(-ti)[1] - 1j*airy(-ti)[3] 134 | sol = pyoscode.solve_fn(w, g, ti, tf, x0, dx0, t_eval=t_eval) 135 | t = np.asarray(sol['t']) 136 | x = np.asarray(sol['sol']) 137 | dense = np.asarray(sol['x_eval']) 138 | dense_d = np.asarray(sol['dx_eval']) 139 | types = np.asarray(sol['types']) 140 | ana_t = t_eval 141 | ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in t]) 142 | dense_ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in ana_t]) 143 | dense_ana_dx = np.asarray([-airy(-T)[1]-1j*airy(-T)[3] for T in ana_t]) 144 | dense_error = np.abs((dense-dense_ana_x)/dense_ana_x)/0.01 > 1.0 145 | dense_error_d = np.abs((dense_d-dense_ana_dx)/dense_ana_dx)/0.01 > 1.0 146 | error = np.abs((x-ana_x)/ana_x)/0.01 > 1.0 147 | assert (np.any(dense_error) == False and np.any(dense_error_d) == False and np.any(error) == False ) 148 | 149 | def test_airy_wgfunctions_back(): 150 | # w, g are supplied as functions instead of arrays 151 | # Integrating backwards 152 | w = lambda t: np.sqrt(t) 153 | g = lambda t: 0.0 154 | ti = 1.0 155 | tf = 100.0 156 | t_eval = np.linspace(ti,tf,5000) 157 | x0 = airy(-tf)[0] + 1j*airy(-tf)[2] 158 | dx0 = -airy(-tf)[1] - 1j*airy(-tf)[3] 159 | sol = pyoscode.solve_fn(w, g, tf, ti, x0, dx0, t_eval=t_eval) 160 | t = np.asarray(sol['t']) 161 | x = np.asarray(sol['sol']) 162 | dense = np.asarray(sol['x_eval']) 163 | dense_d = np.asarray(sol['dx_eval']) 164 | types = np.asarray(sol['types']) 165 | ana_t = t_eval 166 | ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in t]) 167 | dense_ana_x = np.asarray([airy(-T)[0]+1j*airy(-T)[2] for T in ana_t]) 168 | dense_ana_dx = np.asarray([-airy(-T)[1]-1j*airy(-T)[3] for T in ana_t]) 169 | dense_error = np.abs((dense-dense_ana_x)/dense_ana_x)/0.01 > 1.0 170 | dense_error_d = np.abs((dense_d-dense_ana_dx)/dense_ana_dx)/0.01 > 1.0 171 | error = np.abs((x-ana_x)/ana_x)/0.01 > 1.0 172 | assert (np.any(dense_error) == False and np.any(dense_error_d) == False and np.any(error) == False ) 173 | 174 | 175 | -------------------------------------------------------------------------------- /tests/test_arrays.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pyoscode 3 | import pytest 4 | 5 | 6 | def test_dense_output_sorted(): 7 | # Dense output not sorted 8 | ts = np.linspace(1,50,2000) 9 | ws = np.linspace(1,50,2000) 10 | t_eval = np.linspace(1,50,100) 11 | t_eval = np.flip(t_eval) 12 | gs = np.zeros_like(ws) 13 | ti = 1.0 14 | tf = 50.0 15 | x0 = 1.0 16 | dx0 = 0.0 17 | with pytest.raises(TypeError): 18 | pyoscode.solve(ts,ws,gs,ti,tf,x0,dx0,t_eval=t_eval) 19 | return None; 20 | 21 | def test_dense_output_range(): 22 | # Dense output outside integration range 23 | ts = np.linspace(1,50,2000) 24 | ws = np.linspace(1,50,2000) 25 | gs = np.zeros_like(ws) 26 | t_eval = np.linspace(0,60,1000) 27 | ti = 1.0 28 | tf = 50.0 29 | x0 = 1.0 30 | dx0 = 0.0 31 | with pytest.raises(TypeError): 32 | pyoscode.solve(ts,ws,gs,ti,tf,x0,dx0,t_eval=t_eval) 33 | return None; 34 | 35 | 36 | 37 | def test_different_size(): 38 | # Different size arrays 39 | ts = np.linspace(1,50,2000) 40 | ws = np.linspace(1,50,2001) 41 | gs = np.zeros_like(ws) 42 | ti = 1.0 43 | tf = 50.0 44 | x0 = 1.0 45 | dx0 = 0.0 46 | with pytest.raises(TypeError): 47 | pyoscode.solve(ts,ws,gs,ti,tf,x0,dx0) 48 | return None; 49 | 50 | def test_too_small(): 51 | # Same size arrays but too small 52 | ts = np.linspace(1,50,1) 53 | ws = np.linspace(1,50,1) 54 | gs = np.zeros_like(ws) 55 | ti = 1.0 56 | tf = 50.0 57 | x0 = 1.0 58 | dx0 = 0.0 59 | with pytest.raises(TypeError): 60 | pyoscode.solve(ts,ws,gs,ti,tf,x0,dx0) 61 | return None; 62 | 63 | def test_outside_range_ti(): 64 | # ti outside ts 65 | ts = np.linspace(1,50,1000) 66 | ws = np.linspace(1,50,1000) 67 | gs = np.zeros_like(ws) 68 | ti = 0.5 69 | tf = 50.0 70 | x0 = 1.0 71 | dx0 = 0.0 72 | with pytest.raises(TypeError): 73 | pyoscode.solve(ts,ws,gs,ti,tf,x0,dx0) 74 | return None; 75 | 76 | def test_outside_range_tf(): 77 | # ti outside ts 78 | ts = np.linspace(1,50,1000) 79 | ws = np.linspace(1,50,1000) 80 | gs = np.zeros_like(ws) 81 | ti = 1.0 82 | tf = -5.0 83 | x0 = 1.0 84 | dx0 = 0.0 85 | with pytest.raises(TypeError): 86 | pyoscode.solve(ts,ws,gs,ti,tf,x0,dx0) 87 | return None; 88 | 89 | def test_not_monotonous(): 90 | # ts not strictly monotonous 91 | ts = np.random.rand(1000) 92 | ws = ts 93 | gs = ws 94 | ti = ts[0] 95 | tf = ts[-1] 96 | x0 = 1.0 97 | dx0 = 0.0 98 | with pytest.raises(TypeError): 99 | pyoscode.solve(ts,ws,gs,ti,tf,x0,dx0) 100 | return None; 101 | 102 | 103 | --------------------------------------------------------------------------------