diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index 6b6b77f6..00000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[bumpversion] -current_version = 1.0.3 -commit = True -tag = False - -[bumpversion:file:setup.py] - -[bumpversion:file:docs/conf.py] - -[bumpversion:file:src/apexpy/__init__.py] - diff --git a/.cookiecutterrc b/.cookiecutterrc deleted file mode 100644 index f42ed2b0..00000000 --- a/.cookiecutterrc +++ /dev/null @@ -1,34 +0,0 @@ -# This file exists so you can easily regenerate your project. -# -# Unfortunately cookiecutter can't use this right away so -# you have to copy this file to ~/.cookiecutterrc - -default_context: - - appveyor: 'yes' - c_extension_optional: 'no' - c_extension_support: 'yes' - codacy: 'yes' - codeclimate: 'yes' - codecov: 'yes' - command_line_interface: 'no' - coveralls: 'yes' - distribution_name: 'apexpy' - email: 'cmeeren@gmail.com' - full_name: 'Christer van der Meeren' - github_username: 'cmeeren' - landscape: 'yes' - package_name: 'apexpy' - project_name: 'AACGM-v2 Python library' - project_short_description: '"A Python wrapper for modified apex coordinates"' - release_date: '2015-11-18' - repo_name: 'apexpy' - requiresio: 'yes' - scrutinizer: 'yes' - sphinx_theme: 'readthedocs' - test_matrix_configurator: 'yes' - test_runner: 'pytest' - travis: 'yes' - version: '0.1.0' - website: 'https://github.com/cmeeren/apexpy' - year: '2015' diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 772866d2..00000000 --- a/.coveragerc +++ /dev/null @@ -1,12 +0,0 @@ -[paths] -source = src - -[run] -branch = True -source = src -parallel = true - -[report] -show_missing = true -precision = 2 -omit = *migrations* diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ab87bce1..bae46180 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,8 +1,8 @@ --- name: Bug report about: Create a report to help us improve -title: '' -labels: '' +title: 'BUG: ' +labels: 'bug' assignees: '' --- @@ -25,8 +25,9 @@ If applicable, add screenshots to help explain your problem. **Computer (please complete the following information):** - OS: [e.g. iOS] - - Version [e.g. 22] -- Python version [e.g. 3.6] + - Python version [e.g. 3.6] + - Compiler with version [e.g., gfortran 47] + - Apexpy version/branch [e.g., 1.1.0] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..541a67ee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: 'ENH: ' +labels: 'enhancement' +assignees: '' +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always +frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. Keep the scope as +narrow as possible, to make it easier to implement + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've +considered. + +**Additional context** +Add any other context or screenshots about the feature request here. For context, the apexpy version/branch you're working on, your system detail, your python version, and your compiler (with version) may or may not be relevant. + +**Reminders** +This is a volunteer-driven project. Code contributions are welcome, as is help +testing new code. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 244d9314..c4506019 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,8 +1,8 @@ --- name: Question about: Ask a question (and help us improve our documentation) -title: '' -labels: '' +title: 'Question: ' +labels: 'question' assignees: '' --- @@ -25,8 +25,9 @@ If applicable, add screenshots to help explain your question. **Desktop (please complete the following information):** - OS: [e.g. iOS] - - Version [e.g. 22] - Python version [e.g. 3.6] + - Compiler with version [e.g., gfortran 22] + - Apexpy version/branch [e.g., 1.1.0] **Additional context** Add any other context about the problem here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..99382153 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,47 @@ +# Description + +Please include a summary of the change and which issue is fixed. Please also +include relevant motivation and context. List any dependencies that are required +for this change. Please see ``CONTRIBUTING.rst`` for more guidelines. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- Bug fix (non-breaking change which fixes an issue) +- New feature (non-breaking change which adds functionality) +- Breaking change (fix or feature that would cause existing functionality + to not work as expected) +- This change requires a documentation update +- Release candidate + +# How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide +instructions so we can reproduce. Please also list any relevant details for +your test configuration + +- Test A +- Test B + +**Test Configuration**: +* Operating system: (e.g., Hal) +* Python version number: (e.g., 3.7) +* Compiler with version number: (e.g. gfortran 9.0) +* Any details about your local setup that are relevant (e.g., apexpy version/branch) + +# Checklist: + +- [ ] Make sure you are merging into the ``develop`` (not ``main``) branch +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules +- [ ] Add a note to ``Changelog.rst``, summarising the changes +- [ ] Add yourself to ``AUTHORS.rst`` and ``.zenodo.json`` diff --git a/.gitignore b/.gitignore index fc800807..a2a7645b 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,8 @@ docs/_build *.exe *.out *.app + +# Local f2py wrappers +src/fortranapex/fortranapex-f2pywrappers.f* +src/fortranapex/fortranapex-f2pywrappers2.f* +src/fortranapex/fortranapexmodule.c diff --git a/.landscape.yaml b/.landscape.yaml deleted file mode 100644 index 3129377d..00000000 --- a/.landscape.yaml +++ /dev/null @@ -1,7 +0,0 @@ -max-line-length: 140 -doc-warnings: yes -ignore-paths: - - docs - - ci - - tests - - setup.py \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index cbdd12f4..5ae23185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: python python: - - '2.7' - - '3.4' - - '3.5' - - '3.6' + - "2.7" + - "3.6" + - "3.7" + - "3.8" + - "3.9" sudo: false @@ -22,22 +23,21 @@ before_install: - python --version - uname -a - lsb_release -a - - pip install numpy + - pip install numpy coveralls pytest-cov tox-travis install: - - pip install coveralls pytest-cov coverage codecov - python setup.py clean --all build_ext --force --inplace - - python setup.py develop - - pip install tox-travis + - python setup.py install script: - - tox + - | + if [ $TRAVIS_PYTHON_VERSION == "3.7" ]; then + tox -e docs + fi + - tox -e $TRAVIS_PYTHON_VERSION + after_success: - - coverage combine - - coverage report - - coveralls --merge=extension-coveralls.json [] - - coveralls - - codecov + - coveralls --rcfile=setup.cfg notifications: email: diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..5bbd04b5 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,51 @@ +{ + "license": { + "id": "MIT" + }, + "notes": "When referencing this package, please cite both the package DOI and the Apex Coordinates journal article: Emmert, J. T., A. D. Richmond, and D. P. Drob (2010), A computationally compact representation of Magnetic-Apex and Quasi-Dipole coordinates with smooth base vectors, J. Geophys. Res., 115(A8), A08322, doi:10.1029/2010JA015326.", + "references": [ + "Emmert, J. T., A. D. Richmond, and D. P. Drob (2010), A computationally compact representation of Magnetic-Apex and Quasi-Dipole coordinates with smooth base vectors, J. Geophys. Res., 115(A8), A08322, doi:10.1029/2010JA015326.", + "Richmond, A. D. (1995), Ionospheric Electrodynamics Using Magnetic Apex Coordinates, Journal of geomagnetism and geoelectricity, 47(2), 191–212, doi:10.5636/jgg.47.191." + ], + "keywords": [ + "Magnetic Apex Coordinates", + "Quasi Dipole Coordinates", + "MLT", + "Magnetic Local Time", + "Conversion", + "Coordinate Conversion", + "Converting", + "Ionosphere", + "Magnetic Field", + "Space Physics", + "Heliophysics" + ], + "creators": [ + { + "orcid": "0000-0002-8043-0953", + "name": "Christer van der Meeren" + }, + { + "orcid": "0000-0001-5028-4943", + "name": "Karl M. Laundal" + }, + { + "orcid": "0000-0001-8875-9326", + "affiliation": "Naval Research Laboratory", + "name": "Angeline G. Burrell" + }, + { + "orcid": "0000-0002-3487-3630", + "name": "Gregory Starr" + }, + { + "orcid": "0000-0002-4621-3453", + "name": "Ashton S. Reimer" + }, + { + "orcid": "0000-0001-7955-4441", + "affiliation": "GFZ German Research Centre for Geosciences", + "name": "Achim Morschhauser" + } + ] +} diff --git a/AUTHORS.rst b/AUTHORS.rst index cb8f921d..72f92cc5 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -7,7 +7,9 @@ This python wrapper is made by: * Karl M. Laundal * Christer van der Meeren * Angeline G. Burrell (maintainer) +* Gregory Starr * Ashton Reimer +* Achim Morschhauser Fortran code by Emmert et al. [2010] [1]_. Quasi-dipole and modified apex coordinates are defined by Richmond [1995] [2]_. The code uses diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c8556c52..a5c5af8a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,19 @@ Changelog ========= +1.1.0 (2021-03-05) +------------------ +* Adapted Fortran to read IRGF coefficients from a file (updated to IGRF-13) +* Improved the subsol routine to allow array input +* Improved PEP8 compliance +* Added some missing docstrings to unit tests +* Fixed AppVeyor test environment +* Updated python test versions +* Updated community and package documentation +* Fixed bug where NaNs caused array input to crash +* Fixed bug in quasi-dipole to apex conversion at equator +* Removed duplicate CI services + 1.0.4 (2019-04-05) ---------------------------------------- * Updated installation instructions diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index bc71d7c5..f90b1be5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,71 +1,109 @@ -============ Contributing ============ -Bug reports, feature suggestions and other contributions are greatly appreciated! While I can't promise to implement everything, I will always try to respond in a timely manner. +Bug reports, feature suggestions and other contributions are greatly +appreciated! While I can't promise to implement everything, I will always try +to respond in a timely manner. Short version -============= +------------- -* Submit bug reports and feature requests at `GitHub `_ +* Submit bug reports and feature requests at + `GitHub `_ * Make pull requests to the ``develop`` branch Bug reports -=========== +----------- -When `reporting a bug `_ please include: +When `reporting a bug `_ please +include: * Your operating system name and version * Any details about your local setup that might be helpful in troubleshooting * Detailed steps to reproduce the bug Feature requests and feedback -============================= +----------------------------- -The best way to send feedback is to file an issue at `GitHub `_. +The best way to send feedback is to file an issue at +`GitHub `_. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. -* Remember that this is a volunteer-driven project, and that code contributions are welcome :) +* Remember that this is a volunteer-driven project, and that code contributions + are welcome :) Development -=========== +----------- To set up `apexpy` for local development: -1. `Fork apexpy on GitHub `_. +1. `Fork apexpy on GitHub `_. 2. Clone your fork locally:: git clone git@github.com:your_name_here/apexpy.git -3. Create a branch for local development:: +3. Create a branch for local development based off of the ``develop`` branch:: - git checkout -b name-of-your-bugfix-or-feature + git checkout -b name-of-your-bugfix-or-feature origin/develop - Now you can make your changes locally. Add tests for bugs and new features in the relevant test file in the ``tests`` directory. The tests are run with ``py.test`` and can be written as normal functions (starting with ``test_``) containing a standard ``assert`` statement for testing output. + Now you can make your changes locally. Add tests for bugs and new features + in the relevant test file in the ``tests`` directory. The tests are run with + ``pytest`` and can be written as normal functions (starting with ``test_``) + containing a standard ``assert`` statement for testing output. -4. When you're done making changes, run ``py.test`` locally if you can:: +4. When you're done making changes, run ``pytest`` locally if you can:: - py.test + python -m pytest 5. Commit your changes and push your branch to GitHub:: git add . - git commit -m "Brief description of your changes" + git commit -m "ACRONYM: Brief description of your changes" git push origin name-of-your-bugfix-or-feature -6. Submit a pull request through the GitHub website. Pull requests should be made to the ``develop`` branch. The continuous integration (CI) testing servers will automatically test the whole codebase, including your changes, for multiple versions of Python on both Windows and Linux. + The project now uses the `NumPy acronyms `_ + for development workflow in the commit messages. + +6. Submit a pull request through the GitHub website. Pull requests should be + made to the ``develop`` branch. The continuous integration (CI) testing + servers will automatically test the whole codebase, including your changes, + for multiple versions of Python on both Windows and Linux. Pull Request Guidelines ------------------------ +^^^^^^^^^^^^^^^^^^^^^^^ -If you need some code review or feedback while you're developing the code, just make a pull request. +If you need some code review or feedback while you're developing the code, just +make a pull request. For merging, you should: 1. Include passing tests for your changes 2. Update/add documentation if relevant 3. Add a note to ``CHANGELOG.rst`` about the changes -4. Add yourself to ``AUTHORS.rst`` +4. Add yourself to ``AUTHORS.rst`` and ``.zenodo.json`` with your + `ORCiD `_ + +Style Guidelines +^^^^^^^^^^^^^^^^ + +In general, apexpy follows PEP8 and numpydoc guidelines. PyTest is used to run +the unit and integration tests, flake8 checks for style, and sphinx-build +performs documentation tests. However, there are certain additional style +elements that have been settled on to ensure the project maintains a consistent +coding style: + +- Line breaks should occur before a binary operator (ignoring flake8 W503) +- Preferably break long lines on open parentheses instead of using backslashes +- Use no more than 80 characters per line +- Several dependent packages have common nicknames, including: + + * ``import datetime as dt`` + * ``import numpy as np`` + +- Provide tests with informative failure statements and descriptive, one-line + docstrings. + +apexpy is working on modernizing its code style to adhere to these guidelines. diff --git a/MANIFEST.in b/MANIFEST.in index e69b26a5..785181c0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,17 +3,14 @@ graft src graft ci graft tests -include .bumpversion.cfg -include .coveragerc -include .cookiecutterrc -include pytest.ini - include AUTHORS.rst include CHANGELOG.rst include CONTRIBUTING.rst include LICENSE include README.rst +include CODE_OF_CONDUCT.md +include .zenodo.json -include tox.ini .travis.yml appveyor.yml .codeclimate.yml .landscape.yaml +include tox.ini .travis.yml appveyor.yml .codeclimate.yml pyproject.toml global-exclude *.py[cod~] __pycache__ *.so *.dylib diff --git a/README.rst b/README.rst index 7331ea1f..93a22d35 100644 --- a/README.rst +++ b/README.rst @@ -7,20 +7,23 @@ Overview This is a Python wrapper for the Apex fortran library by Emmert et al. [2010] [1]_, which allows converting between geodetic, modified apex, and quasi-dipole coordinates as well as getting modified apex and -quasi-dipole base vectors (Richmond [1995] [2]_). MLT calculations are also -included. The package is free software (MIT license). +quasi-dipole base vectors (Richmond [1995] [2]_). The geodetic system used here +is WGS84. MLT calculations are also included. The package is free software +(MIT license). Quick start =========== -Install (requires NumPy before installation):: +Install from PyPI using ``pip``:: pip install apexpy -This assumes that the same version of libgfortran is installed in the same location as when the pip wheel was built. If not, you may have trouble importing apexpy and you will have to build apexpy yourself using:: +This assumes that the same version of libgfortran is installed in the same +location as when the pip wheel was built (if a wheel was used). If not, you may +have trouble importing apexpy. If you run into trouble, try the command:: + + pip install --no-binary :apexpy: apexpy - pip install --global-option='build_ext' apexpy - which requires both libgfortran and gfortran to be installed on your system. Conversion is done by creating an ``Apex`` object and using its methods to @@ -32,20 +35,20 @@ perform the desired calculations. Some simple examples:: >>> # geo to apex, scalar input >>> mlat, mlon = A.convert(60, 15, 'geo', 'apex', height=300) >>> print("{:.12f}, {:.12f}".format(mlat, mlon)) - 57.469573974609, 93.633583068848 + 57.477310180664, 93.590156555176 >>> # apex to geo, array input >>> glat, glon = A.convert([90, -90], 0, 'apex', 'geo', height=0) >>> print(["{:.12f}, {:.12f}".format(ll, glon[i]) for i,ll in enumerate(glat)]) - ['83.099594116211, -84.594589233398', '-74.388267517090, 125.714927673340'] + ['83.103820800781, -84.526657104492', '-74.388252258301, 125.736274719238'] >>> # geo to MLT >>> import datetime as dt >>> mlat, mlt = A.convert(60, 15, 'geo', 'mlt', datetime=dt.datetime(2015, 2, 10, 18, 0, 0)) >>> print("{:.12f}, {:.12f}".format(mlat, mlt)) - 56.590423583984, 19.108103879293 + 56.598316192627, 19.107861709595 >>> # can also convert magnetic longitude to mlt >>> mlt = A.mlon2mlt(120, dt.datetime(2015, 2, 10, 18, 0, 0)) >>> print("{:.2f}".format(mlt)) - 20.89 + 20.90 If you don't know or use Python, you can also use the command line. See details in the full documentation. @@ -77,10 +80,9 @@ Badges * - docs - |docs| * - tests - - | |travis| |appveyor| |requires| - | |coveralls| |codecov| - | |landscape| |codeclimate| - | |scrutinizer| |codacy| + - | |travis| |appveyor| + | |coveralls| |requires| + | |codeclimate| |scrutinizer| |codacy| * - package - | |version| |supported-versions| | |wheel| |supported-implementations| @@ -89,37 +91,30 @@ Badges :target: https://readthedocs.org/projects/apexpy :alt: Documentation Status -.. |travis| image:: https://travis-ci.org/aburrell/apexpy.svg?branch=master +.. |travis| image:: https://travis-ci.org/aburrell/apexpy.svg?branch=main :alt: Travis-CI Build Status :target: https://travis-ci.org/aburrell/apexpy -.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/aburrell/apexpy?branch=master&svg=true +.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/aburrell/apexpy?branch=main&svg=true :alt: AppVeyor Build Status :target: https://ci.appveyor.com/project/aburrell/apexpy -.. |requires| image:: https://requires.io/github/aburrell/apexpy/requirements.svg?branch=master - :alt: Requirements Status - :target: https://requires.io/github/aburrell/apexpy/requirements/?branch=master - -.. |coveralls| image:: https://coveralls.io/repos/github/aburrell/apexpy/badge.svg?branch=master - :alt: Coverage Status - :target: https://coveralls.io/github/aburrell/apexpy?branch=master +.. |requires| image:: https://requires.io/github/aburrell/apexpy/requirements.svg?branch=main + :alt: Requirements Status + :target: https://requires.io/github/aburrell/apexpy/requirements/?branch=main -.. |codecov| image:: https://codecov.io/github/aburrell/apexpy/coverage.svg?branch=master +.. |coveralls| image:: https://coveralls.io/repos/github/aburrell/apexpy/badge.svg?branch=main :alt: Coverage Status - :target: https://codecov.io/github/aburrell/apexpy + :target: https://coveralls.io/github/aburrell/apexpy?branch=main -.. |landscape| image:: https://landscape.io/github/aburrell/apexpy/master/landscape.svg?style=flat - :target: https://landscape.io/github/aburrell/apexpy/master - :alt: Code Quality Status +.. |codacy| image:: https://api.codacy.com/project/badge/Grade/7d4c1a6c60e747ca95cdf97746c39cda + :alt: Codacy Badge + :target: https://app.codacy.com/gh/aburrell/apexpy?utm_source=github.com&utm_medium=referral&utm_content=aburrell/apexpy&utm_campaign=Badge_Grade -.. |codacy| image:: https://img.shields.io/codacy/af7fdf6be28841f283dfdbc1c01fa82a.svg?style=flat - :target: https://www.codacy.com/app/aburrell/apexpy - :alt: Codacy Code Quality Status - -.. |codeclimate| image:: https://codeclimate.com/github/cmeeren/apexpy/badges/gpa.svg +.. |codeclimate| image:: https://api.codeclimate.com/v1/badges/da1d972dee790da595f8/maintainability.svg :target: https://codeclimate.com/github/aburrell/apexpy :alt: CodeClimate Quality Status + .. |version| image:: https://img.shields.io/pypi/v/apexpy.svg?style=flat :alt: PyPI Package latest release :target: https://pypi.python.org/pypi/apexpy @@ -140,7 +135,7 @@ Badges :alt: Supported implementations :target: https://pypi.python.org/pypi/apexpy -.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/aburrell/apexpy/master.svg?style=flat +.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/aburrell/apexpy/main.svg?style=flat :alt: Scrutinizer Status :target: https://scrutinizer-ci.com/g/aburrell/apexpy/ diff --git a/appveyor.yml b/appveyor.yml index dd562deb..2d36f949 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,79 +2,84 @@ version: '{branch}-{build}' build: off configuration: Release environment: - global: - WITH_COMPILER: 'cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd' matrix: + - TESTENV: 'check' + PYTHON_VERSION: '3.7' + MINICONDA_HOME: 'C:\Miniconda' + TESTSCRIPT: 'python setup.py check --strict --metadata --restructuredtext && check-manifest && flake8 src tests' + INSTALL_EXTRA_DEPS: 'pip install docutils check-manifest flake8 readme pygments sphinx_rtd_theme' - TESTENV: '2.7-nocover-64' - INSTALL_LIBPYTHON: libpython PYTHON_VERSION: '2.7' MINICONDA_HOME: C:\Miniconda-x64 - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - TESTENV: '2.7-nocover-32' - INSTALL_LIBPYTHON: libpython PYTHON_VERSION: '2.7' MINICONDA_HOME: C:\Miniconda - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - - TESTENV: '3.4-nocover-64' - INSTALL_LIBPYTHON: libpython - PYTHON_VERSION: '3.4' + - TESTENV: '3.6-nocover-64' + PYTHON_VERSION: '3.6' MINICONDA_HOME: C:\Miniconda-x64 - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - - TESTENV: '3.4-nocover-32' - INSTALL_LIBPYTHON: libpython - PYTHON_VERSION: '3.4' + - TESTENV: '3.6-nocover-32' + PYTHON_VERSION: '3.6' MINICONDA_HOME: C:\Miniconda - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - - TESTENV: '3.5-nocover-64' - PYTHON_VERSION: '3.5' + - TESTENV: '3.7-nocover-64' + PYTHON_VERSION: '3.7' MINICONDA_HOME: C:\Miniconda-x64 - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - - TESTENV: '3.5-nocover-32' - PYTHON_VERSION: '3.5' + - TESTENV: '3.7-nocover-32' + PYTHON_VERSION: '3.7' MINICONDA_HOME: C:\Miniconda - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - - TESTENV: '3.6-nocover-64' - INSTALL_LIBPYTHON: libpython - PYTHON_VERSION: '3.6' + - TESTENV: '3.8-nocover-64' + PYTHON_VERSION: '3.8' MINICONDA_HOME: C:\Miniconda-x64 - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - - TESTENV: '3.6-nocover-32' - INSTALL_LIBPYTHON: libpython - PYTHON_VERSION: '3.6' + - TESTENV: '3.8-nocover-32' + PYTHON_VERSION: '3.8' MINICONDA_HOME: C:\Miniconda - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' + + - TESTENV: '3.9-nocover-64' + PYTHON_VERSION: '3.9' + MINICONDA_HOME: C:\Miniconda-x64 + TESTSCRIPT: 'python -m pytest' + + - TESTENV: '3.9-nocover-32' + PYTHON_VERSION: '3.9' + MINICONDA_HOME: C:\Miniconda + TESTSCRIPT: 'python -m pytest' - - TESTENV: 'check' - INSTALL_LIBPYTHON: libpython - PYTHON_VERSION: '2.7' - MINICONDA_HOME: 'C:\Miniconda' - TESTSCRIPT: 'python setup.py check --strict --metadata --restructuredtext && check-manifest && flake8 src tests' - INSTALL_EXTRA_DEPS: 'pip install docutils check-manifest flake8 readme pygments' init: - ps: echo $env:TESTENV install: - - set PATH=%PATH%;C:\msys64\mingw64\bin + # This quickly fails the job if there is another pipeline queued ahead of it + - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` + https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` + Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` + throw "There are newer queued builds for this pull request, failing early." } - '%MINICONDA_HOME%\Scripts\conda config --set always_yes yes' - '%MINICONDA_HOME%\Scripts\conda update -q conda' - - '%MINICONDA_HOME%\Scripts\conda create -q -p C:\pythontest python=%PYTHON_VERSION% %INSTALL_LIBPYTHON% pytest numpy' - # remove file which conflicts with installation of package 'readme' on case-insensitive file systems + - '%MINICONDA_HOME%\Scripts\conda create -q -p C:\pythontest python=%PYTHON_VERSION% pytest numpy libpython' + # Remove file which conflicts with installation of package 'readme' on case-insensitive file systems - del C:\pythontest\Lib\site-packages\README - # to stop the unnecessary "Only MS compiler supported with gfortran on win64" error on some configurations (numpy 1.9?) - - python ci\fix-compiler-error.py - '%MINICONDA_HOME%\Scripts\activate C:\pythontest' - - where python - - where pip + - '%MINICONDA_HOME%\Scripts\conda install -q -c msys2 m2w64-toolchain' + - python -m pip install --upgrade pip - '%INSTALL_EXTRA_DEPS%' - - python setup.py clean --all build_ext --force --inplace --compiler=mingw32 - - python setup.py develop + - python setup.py config_fc + - python setup.py config --compiler=mingw32 --fcompiler=gfortran + - python setup.py install + test_script: - '%TESTSCRIPT%' @@ -82,7 +87,7 @@ after_test: - python setup.py sdist bdist_wheel deploy_script: - # if tagged commit, build/upload wheel + # If tagged commit, build/upload wheel - IF "%APPVEYOR_REPO_TAG%"=="true" IF NOT "%TESTENV%"=="check" ( pip install twine && python setup.py register && @@ -91,13 +96,7 @@ deploy_script: artifacts: - path: dist\* -matrix: - allow_failures: - - PYTHON_VERSION: '3.5' - - TESTENV: '2.7-nocover-64' - - TESTENV: '3.5-nocover-64' - - TESTENV: '3.6-nocover-64' - - TESTENV: 'check' + ### To enable remote debugging uncomment this: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/ci/bootstrap.py b/ci/bootstrap.py index 466bb8fe..db5c7db1 100644 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -42,7 +42,7 @@ tox_environments = {} for (alias, conf) in matrix.from_file(join(base_path, "setup.cfg")).items(): python = conf["python_versions"] - deps = conf["dependencies"] + deps = conf["dependencies"] if "dependencies" in conf.keys() else "" if "coverage_flags" in conf: cover = {"false": False, "true": True}[conf["coverage_flags"].lower()] if "environment_variables" in conf: diff --git a/ci/templates/.travis.yml b/ci/templates/.travis.yml index cbdd12f4..e7404641 100644 --- a/ci/templates/.travis.yml +++ b/ci/templates/.travis.yml @@ -1,10 +1,9 @@ language: python python: - - '2.7' - - '3.4' - - '3.5' - - '3.6' +{% for env, config in tox_environments|dictsort %}{% if not config.cover %} + - "{{ config.python[-3:] }}" +{% endif %}{% endfor %} sudo: false @@ -22,22 +21,21 @@ before_install: - python --version - uname -a - lsb_release -a - - pip install numpy + - pip install numpy coveralls pytest-cov tox-travis install: - - pip install coveralls pytest-cov coverage codecov - python setup.py clean --all build_ext --force --inplace - - python setup.py develop - - pip install tox-travis + - python setup.py install script: - - tox + - | + if [ $TRAVIS_PYTHON_VERSION == "3.7" ]; then + tox -e docs + fi + - tox -e $TRAVIS_PYTHON_VERSION + after_success: - - coverage combine - - coverage report - - coveralls --merge=extension-coveralls.json [] - - coveralls - - codecov + - coveralls --rcfile=setup.cfg notifications: email: diff --git a/ci/templates/appveyor.yml b/ci/templates/appveyor.yml index 0e6f4542..817f71d1 100644 --- a/ci/templates/appveyor.yml +++ b/ci/templates/appveyor.yml @@ -2,55 +2,46 @@ version: '{branch}-{build}' build: off configuration: Release environment: - global: - WITH_COMPILER: 'cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd' matrix: -{% for env, config in tox_environments|dictsort %}{% if config.python in ('python2.6', 'python2.7', 'python3.4', 'python3.5', 'python3.6') and not config.cover %} + - TESTENV: 'check' + PYTHON_VERSION: '3.7' + MINICONDA_HOME: 'C:\Miniconda' + TESTSCRIPT: 'python setup.py check --strict --metadata --restructuredtext && check-manifest && flake8 src tests' + INSTALL_EXTRA_DEPS: 'pip install docutils check-manifest flake8 readme pygments sphinx_rtd_theme' +{% for env, config in tox_environments|dictsort %}{% if not config.cover %} - TESTENV: '{{ env }}-64' - {%- if config.python != 'python3.5' %} - - INSTALL_LIBPYTHON: libpython - {%- endif %} - PYTHON_VERSION: '{{ config.python[-3:] }}' MINICONDA_HOME: C:\Miniconda-x64 - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' - TESTENV: '{{ env }}-32' - {%- if config.python != 'python3.5' %} - - INSTALL_LIBPYTHON: libpython - {%- endif %} - PYTHON_VERSION: '{{ config.python[-3:] }}' MINICONDA_HOME: C:\Miniconda - TESTSCRIPT: 'py.test' + TESTSCRIPT: 'python -m pytest' {% endif %}{% endfor %} - - TESTENV: 'check' - INSTALL_LIBPYTHON: libpython - PYTHON_VERSION: '2.7' - MINICONDA_HOME: 'C:\Miniconda' - TESTSCRIPT: 'python setup.py check --strict --metadata --restructuredtext && check-manifest && flake8 src tests' - INSTALL_EXTRA_DEPS: 'pip install docutils check-manifest flake8 readme pygments' init: - ps: echo $env:TESTENV install: - - set PATH=%PATH%;C:\msys64\mingw64\bin + # This quickly fails the job if there is another pipeline queued ahead of it + - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` + https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` + Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` + throw "There are newer queued builds for this pull request, failing early." } - '%MINICONDA_HOME%\Scripts\conda config --set always_yes yes' - '%MINICONDA_HOME%\Scripts\conda update -q conda' - - '%MINICONDA_HOME%\Scripts\conda create -q -p C:\pythontest python=%PYTHON_VERSION% %INSTALL_LIBPYTHON% pytest numpy' - # remove file which conflicts with installation of package 'readme' on case-insensitive file systems + - '%MINICONDA_HOME%\Scripts\conda create -q -p C:\pythontest python=%PYTHON_VERSION% pytest numpy libpython' + # Remove file which conflicts with installation of package 'readme' on case-insensitive file systems - del C:\pythontest\Lib\site-packages\README - # to stop the unnecessary "Only MS compiler supported with gfortran on win64" error on some configurations (numpy 1.9?) - - python ci\fix-compiler-error.py - '%MINICONDA_HOME%\Scripts\activate C:\pythontest' - - where python - - where pip + - '%MINICONDA_HOME%\Scripts\conda install -q -c msys2 m2w64-toolchain' + - python -m pip install --upgrade pip - '%INSTALL_EXTRA_DEPS%' - - python setup.py clean --all build_ext --force --inplace --compiler=mingw32 - - python setup.py develop + - python setup.py config_fc + - python setup.py config --compiler=mingw32 --fcompiler=gfortran + - python setup.py install + test_script: - '%TESTSCRIPT%' @@ -58,7 +49,7 @@ after_test: - python setup.py sdist bdist_wheel deploy_script: - # if tagged commit, build/upload wheel + # If tagged commit, build/upload wheel - IF "%APPVEYOR_REPO_TAG%"=="true" IF NOT "%TESTENV%"=="check" ( pip install twine && python setup.py register && @@ -67,13 +58,7 @@ deploy_script: artifacts: - path: dist\* -matrix: - allow_failures: - - PYTHON_VERSION: '3.5' - - TESTENV: '2.7-nocover-64' - - TESTENV: '3.5-nocover-64' - - TESTENV: '3.6-nocover-64' - - TESTENV: 'check' + ### To enable remote debugging uncomment this: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/ci/templates/tox.ini b/ci/templates/tox.ini index dbfcdb41..57ae29b4 100644 --- a/ci/templates/tox.ini +++ b/ci/templates/tox.ini @@ -23,22 +23,11 @@ commands = #conda install -q -p {toxworkdir}\{envdir} numpy python setup.py clean --all build_ext --force --inplace # python setup.py develop - {posargs:py.test -vv --ignore=src --doctest-glob='*.rst'} - -[testenv:spell] -setenv = - SPELLCHECK=1 -commands = - sphinx-build -b spelling docs dist/docs -skip_install = true -usedevelop = true -deps = - -r{toxinidir}/docs/requirements.txt - sphinxcontrib-spelling - pyenchant + python -m pytest {posargs: -vv --ignore=src/fortranapex --doctest-glob='*.rst'} [testenv:docs] deps = + sphinx_rtd_theme -r{toxinidir}/docs/requirements.txt commands = sphinx-build {posargs:-E} -b html docs dist/docs @@ -56,19 +45,21 @@ passenv = * [testenv:check] -basepython = python3.4 deps = docutils check-manifest flake8 - readme + numpy pygments + readme + twine skip_install = true usedevelop = false commands = - python setup.py check --strict --metadata --restructuredtext + python setup.py sdist check-manifest {toxinidir} - flake8 src tests + flake8 --ignore=F401,W503 src tests + twine check dist/* [testenv:coveralls] deps = @@ -78,19 +69,7 @@ usedevelop = false commands = coverage combine coverage report - coveralls --merge=extension-coveralls.json [] - -[testenv:codecov] -deps = - codecov -skip_install = true -usedevelop = false -commands = - coverage combine - coverage report - coverage xml --ignore-errors - codecov [] - + coveralls --rcfile=setup.cfg --merge=extension-coveralls.json [] [testenv:extension-coveralls] deps = @@ -98,10 +77,9 @@ deps = skip_install = true usedevelop = false commands = - coveralls --build-root=. --include=src --dump=extension-coveralls.json [] + coveralls --rcfile=setup.cfg --build-root=. --include=src --dump=extension-coveralls.json [] [testenv:report] -basepython = python3.4 deps = coverage skip_install = true usedevelop = false @@ -131,7 +109,7 @@ setenv = usedevelop = true commands = python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} + python -m pytest {posargs:--cov --cov-report=term-missing -vv --doctest-glob='*.rst'} {% endif %} {% if config.cover or config.deps %} deps = @@ -144,4 +122,12 @@ deps = {{ dep }} {% endfor %} +{% if not config.cover %} +[testenv:{{ config.python[-3:] }}-buildonly-nocover] +basepython = {env:TOXPYTHON:{{ config.python }}} +deps = +skip_install = true +commands = +{% endif %} + {% endfor %} diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..28012895 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = src/apexpy +SOURCEDIR = . +BUILDDIR = .build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index 664bb38b..d2a2aec9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import os +import re extensions = [ @@ -11,10 +12,6 @@ 'sphinx.ext.napoleon', 'sphinx.ext.extlinks' ] -if os.getenv('SPELLCHECK'): - extensions += 'sphinxcontrib.spelling', - spelling_show_suggestions = True - spelling_lang = 'en_US' source_suffix = '.rst' master_doc = 'index' @@ -22,7 +19,13 @@ year = u'2015' author = u'Christer van der Meeren, Angeline G. Burrell' copyright = '{0}, {1}'.format(year, author) -version = release = u'1.0.3' +# Get version number from __init__.py +here = os.path.abspath(os.path.dirname(__file__)) +regex = "(?<=__version__..\s)\S+" +with open(os.path.join(here,'../src/apexpy/__init__.py'),'r', encoding='utf-8') as f: + text = f.read() +match = re.findall(regex,text) +version = release = match[0].strip("'") # on_rtd is whether we are on readthedocs.org on_rtd = os.environ.get('READTHEDOCS', None) == 'True' diff --git a/docs/contributing.rst b/docs/contributing.rst index e582053e..d1310bed 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -1 +1,4 @@ +.. _contributing: + .. include:: ../CONTRIBUTING.rst + diff --git a/docs/index.rst b/docs/index.rst index 40f35b5e..6ed104f3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,7 @@ Contents usage reference/index contributing + maintenance authors changelog diff --git a/docs/installation.rst b/docs/installation.rst index 579b6789..8d30bdb2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,32 +1,67 @@ -============ +.. _installation: + Installation ============ This package requires NumPy, which you can install alone or as a part of SciPy. -`Some Python distributions `_ come with NumPy/SciPy pre-installed. For Python distributions -without NumPy/SciPy, Windows/Mac users should install -`pre-compiled binaries of NumPy/SciPy `_, and Linux users may have NumPy/SciPy -available in `their repositories `_. +`Some Python distributions `_ +come with NumPy/SciPy pre-installed. For Python distributions without +NumPy/SciPy, Windows/Mac users should install +`pre-compiled binaries of NumPy/SciPy `_, and Linux users may have +NumPy/SciPy available in +`their repositories `_. + +When you have NumPy, you may use either PyPI or GitHub to install this package. -When you have NumPy, install this package at the command line using + +.. _installation-pip: + +PyPI +---- +This is the most straightforward option! From the command line use ``pip`` [1]_:: pip install apexpy -This assumes that the same version of libgfortran is installed in the same location as when the pip wheel was built. If not, you may have trouble importing apexpy and you will have to build apexpy yourself using: +In the event that you run into issues, you can get around this problem by using +``pip`` [1]_:: -pip install --global-option='build_ext' apexpy + pip install --no-binary :apexpy: apexpy which requires both libgfortran and gfortran to be installed on your system. +This is the default option for Linux, and so should not be an issue there. On +Windows with the Mingw32 compiler, you might find `this information `_ +useful for helping build apexpy. The package has been tested with the following setups (others might work, too): -* Windows (64 bit Python), Linux (64 bit), and Mac (64 bit) -* Python 2.7, 3.5, 3.6, (and 3.4 on Linux/Mac [2]_) +* Windows (32/64 bit Python), Linux (64 bit), and Mac (64 bit) +* Python 2.7, 3.6, 3.7, 3.8, 3.9 + + +.. _installation-cmd: + +GitHub +------ +If you are indending on modifying or contributing to apexpy, it's easier to +install apexpy by forking the repository and installing it locally or within +a virtual environment. After clonining the fork (see :ref:`contributing`), +you may install by:: + + cd apexpy + python setup.py develop --user +or with ``pip``:: + + cd apexpy + pip install -e . + +Another benefit of installing apexpy from the command line is specifying the +fortran compiler you would like to use. By default, apexpy uses +`numpy`'s `f2py`, but you can change this using the global `--compiler` flag when running the `python setup.py install` command. +However, if using an Intel compiler, you will need to +uncomment a line at the top of ``src/fortranapex/igrf.f90`` to ensure all +necessary libraries are imported. .. [1] pip is included with Python 2 from v2.7.9 and Python 3 from v3.4. If you don't have pip, - `get it here `_. -.. [2] I do not know how to compile the Fortran extension on Windows in a - manner that is compatible with the omitted python versions. If you get - it working, let me know! + `get it here `_. diff --git a/docs/maintenance.rst b/docs/maintenance.rst new file mode 100644 index 00000000..3c3435e7 --- /dev/null +++ b/docs/maintenance.rst @@ -0,0 +1,57 @@ +Package Maintenance +=================== + +Updating IGRF +------------- + +The `International Geomagnetic Reference Field `_ +is regularly updated to reflect the most recent changes to the Terrestrial +magnetic field. apexpy currently uses IRGF-13 coefficients, which are provided +in the ``apexpy/src/apexpy/igrf13coeff.txt`` file. To change or update the +magnetic field coefficients used by apexpy, you need to update the python code. +Assuming your new coefficient file has the same format, the process is simple: + +1. Clone the repository or your fork of the repository (see :ref:`contributing`) +2. Update ``apexpy/src/apexpy/apex.py`` variable ``igrf_fn`` by setting + it equal to the new target filename +3. Install the python package from the command line + (see :ref:`installation-cmd`) + +Updating ``apexsh.dat`` +----------------------- + +After updating the IGRF coefficients file the ``apexsh.dat`` file needs to be +rebuilt. This file is what makes apexpy performant. For more details, see +Emmert et al. [2010] [1]_. + +Updating ``apexsh.dat`` is done by modifying and building the fortran source code +in the ``apexpy/src/fortranapex`` directory. Working in that directory: + +1. Make sure a copy of the latest IGRF coefficient file is present in the + selfsame directory. +2. Modify the ``igrffilein`` in ``checkapexsh.f90`` to the name of the IGRF + coefficient file (``igrf13coeff.txt``, for example). +3. Modify ``checkapexsh.f90`` by adding the next 5 year epoch to the + ``epochgrid`` variable and updating the ``nepochgrid`` variable as + necessary. For example, if the newest IGRF coefficients are good up to 2025 + and ``epochgrid`` only has up to the year 2020, then add 2025 to + ``epochgrid`` and then increment ``nepochgrid`` by 1. +4. Build the ``apextest`` binary by running the ``make`` command. +5. Execute the ``apextest`` binary to generate the new ``apexsh.dat`` file +4. Copy the new ``apexsh.dat`` file to the ``apexpy/src/apexpy`` directory. + +Updating tests and style standards +----------------------------------- + +apexpy is in the process of updating unit and integration tests to reduce code +duplication and implementing cleaner style standards. Additionally, some parts +of the fortran code adhere to older coding standards and raise warnings when +compiled with newer compilers. If you would like to assist in these efforts +(help would be appreciated), please discuss your potential contribution with +the current maintainer to ensure a minimal duplication of effort. + + +.. [1] Emmert, J. T., A. D. Richmond, and D. P. Drob (2010), + A computationally compact representation of Magnetic-Apex + and Quasi-Dipole coordinates with smooth base vectors, + J. Geophys. Res., 115(A8), A08322, :doi:`10.1029/2010JA015326`. diff --git a/docs/reference/fortranapex.rst b/docs/reference/fortranapex.rst index 31526761..2b0d7fd8 100644 --- a/docs/reference/fortranapex.rst +++ b/docs/reference/fortranapex.rst @@ -1,9 +1,13 @@ apexpy.fortranapex ================== -This module is the interface to the apex Fortran library by Emmert et al. [2010] [1]_. The interface is not documented. Use :class:`apexpy.Apex` for all conversions and calculations. You can find some documentation of the actual Fortran library in the source file `apexsh.f90 `_. +This module is the interface to the apex Fortran library by +Emmert et al. [2010] [1]_. The interface is not documented. +Use :class:`apexpy.Apex` for all conversions and calculations. You can find +some documentation of the actual Fortran library in the source file +`apexsh.f90 `_. .. [1] Emmert, J. T., A. D. Richmond, and D. P. Drob (2010), A computationally compact representation of Magnetic-Apex and Quasi-Dipole coordinates with smooth base vectors, - J. Geophys. Res., 115(A8), A08322, :doi:`10.1029/2010JA015326`. \ No newline at end of file + J. Geophys. Res., 115(A8), A08322, :doi:`10.1029/2010JA015326`. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..83de03ad --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "wheel", "Cython", "numpy"] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 77a53936..00000000 --- a/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -log_cli=true -log_level=NOTSET -addopts = -vv --ignore=src --ignore=ci --ignore=docs --doctest-modules --doctest-glob='*.rst' \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 0fb509ae..48318f0e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,93 @@ +[metadata] +name = apexpy +version = 1.1.0 +license = MIT +description = "A Python wrapper for Apex coordinates" +long_description = file: README.rst, CHANGELOG.rst +long_description_content_type = text/x-rst +author = Crister van der Meeren, et al. +author_email = angeline.burrell@nrll.navy.mil +url = https://github.com/aburrell/apexpy +keywords = + apex + modified apex + quasi-dipole + quasi dipole + coordinates + magnetic coordinates + mlt + magnetic local time + conversion + converting +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Science/Research + License :: OSI Approved :: MIT License + Operating System :: Unix + Operating System :: POSIX + Operating System :: Microsoft :: Windows + Operating System :: MacOS :: MacOS X + Programming Language :: Python + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: Implementation :: CPython + Topic :: Scientific/Engineering :: Physics + Topic :: Utilities + +[options] +zip_safe = False +package_dir = + =src +install_requires = numpy +include_package_data = True +include_entry_points = True + +[options.entry_points] +console_scripts = + apexpy = apexpy.__main__:main + +[options.package_data] +apexpy = + apexsh.dat + igrf13coeffs.txt + [aliases] release = register clean --all sdist +[bumpversion] +current_version = 1.1.0 +commit = True +tag = True + +[bumpversion:file:src/apexpy/__init__.py] + +[coverage:paths] +source = src + +[coverage:run] +branch = True +relative_files = True +include = */apexpy/* + */tests/* +source = src +parallel = True + +[coverage:report] +show_missing = True +precision = 2 +omit = *migrations* + [flake8] -max-line-length = 140 -exclude = tests/*,*/migrations/*,*/south_migrations/* +max-line-length = 80 +exclude = */migrations/*,*/south_migrations/* -[pytest] +[tool:pytest] +log_cli = true +log_level = NOTSET norecursedirs = .git .tox @@ -18,23 +100,30 @@ python_files = test_*.py *_test.py tests.py +# The options below do not allow whitespace between the flag and the equalitys addopts = - -rxEfsw - --strict - --ignore=docs/conf.py - --ignore=setup.py - --ignore=ci - --ignore=.eggs - --doctest-modules - --doctest-glob=\*.rst - --tb=short + -vv + --ignore=src + --ignore=ci + --ignore=docs + --doctest-modules + --doctest-glob='*.rst' + -rxEfsw + --ignore=docs/conf.py + --ignore=setup.py + --ignore=.eggs + --tb=short +flake8-ignore = + *.py W503 + docs/conf.py ALL + src/apexpy/__init__.py F401 [isort] -line_length=120 -known_first_party=apexpy -default_section=THIRDPARTY -length_sort=1 -multi_line_output=0 +line_length = 80 +known_first_party = apexpy +default_section = THIRDPARTY +length_sort = 1 +multi_line_output = 0 [matrix] # This is the configuration for the `./bootstrap.py` script. @@ -59,16 +148,10 @@ multi_line_output=0 python_versions = 2.7 - 3.4 - 3.5 3.6 - -dependencies = -# 1.4: Django==1.4.16 !python_versions[3.*] -# 1.5: Django==1.5.11 -# 1.6: Django==1.6.8 -# 1.7: Django==1.7.1 !python_versions[2.6] -# Deps commented above are provided as examples. That's what you would use in a Django project. + 3.7 + 3.8 + 3.9 coverage_flags = : true diff --git a/setup.py b/setup.py index 45249ee9..fce16a1f 100644 --- a/setup.py +++ b/setup.py @@ -2,17 +2,12 @@ # -*- encoding: utf-8 -*- from __future__ import absolute_import -import io -import re from glob import glob from os import path, environ - -from setuptools import find_packages +from setuptools import setup, find_packages # Include extensions only when not on readthedocs.org if environ.get('READTHEDOCS', None) == 'True': - from setuptools import setup - from distutils.core import Extension extensions = [] else: from numpy.distutils.core import setup, Extension @@ -20,73 +15,14 @@ Extension(name='apexpy.fortranapex', sources=['src/fortranapex/magfld.f', 'src/fortranapex/apex.f', 'src/fortranapex/makeapexsh.f90', + 'src/fortranapex/igrf.f90', 'src/fortranapex/apexsh.f90', - 'src/fortranapex/checkapexsh.f90'])] - -def read(*names, **kwargs): - return io.open( - path.join(path.dirname(__file__), *names), - encoding=kwargs.get('encoding', 'utf8') - ).read() + 'src/fortranapex/checkapexsh.f90', + 'src/fortranapex/fortranapex.pyf'])] +setup_kwargs = {'py_modules': [path.splitext(path.basename(pp))[0] + for pp in glob('src/*.py')], + 'ext_modules': extensions, + 'packages': find_packages(where='src')} -if __name__ == "__main__": - setup( - name='apexpy', - version='1.0.3', - license='MIT', - description='A Python wrapper for Apex coordinates', - long_description='%s\n%s' % (read('README.rst'), - re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', - read('CHANGELOG.rst'))), - author='Christer van der Meeren; Angeline G. Burrell', - author_email='agb073000@utdallas.edu', - url='https://github.com/aburrell/apexpy', - packages=find_packages('src'), - package_dir={'': 'src'}, - py_modules=[path.splitext(path.basename(pp))[0] - for pp in glob('src/*.py')], - package_data={'apexpy': ['apexsh.dat']}, - zip_safe=False, - classifiers=[ - # complete classifier list: - # http://pypi.python.org/pypi?%3Aaction=list_classifiers - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Operating System :: Unix', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Utilities', - ], - keywords=[ - 'apex', - 'modified apex', - 'quasi-dipole', - 'quasi dipole', - 'coordinates', - 'magnetic coordinates', - 'mlt', - 'magnetic local time', - 'conversion', - 'converting', - ], - install_requires=[ - 'numpy', - ], - ext_modules=extensions, - entry_points={ - 'console_scripts': [ - 'apexpy = apexpy.__main__:main', - ] - }, - ) +setup(**setup_kwargs) diff --git a/src/apexpy/__init__.py b/src/apexpy/__init__.py index dc8fef3b..a4e21bbb 100644 --- a/src/apexpy/__init__.py +++ b/src/apexpy/__init__.py @@ -2,14 +2,15 @@ from .apex import Apex, ApexHeightError from . import helpers -# below try..catch required for autodoc to work on readthedocs + +# Below try..catch required for autodoc to work on readthedocs try: from . import fortranapex except ImportError: - print("ERROR: fortranapex module could not be imported. " + - "apexpy probably won't work") + print("".join(["ERROR: fortranapex module could not be imported. ", + "apexpy probably won't work"])) -__version__ = "1.0.3" +__version__ = "1.1.0" __all__ = ['Apex', 'fortranapex', 'helpers', 'ApexHeightError'] diff --git a/src/apexpy/__main__.py b/src/apexpy/__main__.py index 8b4eb9b3..7539b488 100644 --- a/src/apexpy/__main__.py +++ b/src/apexpy/__main__.py @@ -56,16 +56,17 @@ def main(): if 'mlt' in [args.source, args.dest] and len(args.date) < 14: desc = 'full date/time YYYYMMDDHHMMSS required for MLT calculations' raise ValueError(desc) - if 9 <= len(args.date) <= 13: - desc = 'full date/time must be given as YYYYMMDDHHMMSS, not ' + \ - 'YYYYMMDDHHMMSS'[:len(args.date)] + if 9 <= len(args.date) and len(args.date) <= 13: + desc = 'full date/time must be given as YYYYMMDDHHMMSS, not ' \ + + 'YYYYMMDDHHMMSS'[:len(args.date)] raise ValueError(desc) datetime = dt.datetime.strptime(args.date, - '%Y%m%d%H%M%S'[:len(args.date)-2]) + '%Y%m%d%H%M%S'[:len(args.date) - 2]) A = apexpy.Apex(date=datetime, refh=args.refh) lats, lons = A.convert(array[:, 0], array[:, 1], args.source, args.dest, args.height, datetime=datetime) np.savetxt(args.file_out, np.column_stack((lats, lons)), fmt='%.8f') + if __name__ == '__main__': sys.exit(main()) diff --git a/src/apexpy/apex.py b/src/apexpy/apex.py index 90eba187..a7ccd777 100644 --- a/src/apexpy/apex.py +++ b/src/apexpy/apex.py @@ -2,28 +2,31 @@ from __future__ import division, print_function, absolute_import -import os -import warnings import datetime as dt - import numpy as np +import os +import warnings from . import helpers -# below try..catch required for autodoc to work on readthedocs +# Below try..catch required for autodoc to work on readthedocs try: from . import fortranapex as fa -except: - print("ERROR: fortranapex module could not be imported. apexpy probably" - " won't work") - +except ImportError as err: + warnings.warn("".join(["fortranapex module could not be imported, so ", + "apexpy probably won't work. Make sure you have ", + "a gfortran compiler. Wheels installation ", + "assumes your compiler lives in /opt/local/bin"])) + raise err # make sure invalid warnings are always shown -warnings.filterwarnings('always', message='.*set to -9999 where*', +warnings.filterwarnings('always', message='.*set to NaN where*', module='apexpy.apex') class ApexHeightError(ValueError): + """Specialized error type definition + """ pass @@ -32,7 +35,7 @@ class Apex(object): calculations. Parameters - ========== + ---------- date : float, :class:`dt.date`, or :class:`dt.datetime`, optional Determines which IGRF coefficients are used in conversions. Uses current date as default. If float, use decimal year. @@ -43,7 +46,7 @@ class Apex(object): Path to custom coefficient file Attributes - ========== + ---------- year : float Decimal year used for the IGRF model refh : float @@ -52,11 +55,13 @@ class Apex(object): Path to coefficient file Notes - ===== - The calculations use IGRF-12 with coefficients from 1900 to 2020 [1]_. + ----- + The calculations use IGRF-13 with coefficients from 1900 to 2025 [1]_. + + The geodetic reference ellipsoid is WGS84. References - ========== + ---------- .. [1] Thébault, E. et al. (2015), International Geomagnetic Reference Field: the 12th generation, Earth, Planets and Space, 67(1), 79, @@ -64,28 +69,38 @@ class Apex(object): """ - def __init__(self, date=None, refh=0, datafile=None): + def __init__(self, date=None, refh=0, datafile=None, fortranlib=None): if datafile is None: datafile = os.path.join(os.path.dirname(__file__), 'apexsh.dat') + if fortranlib is None: + fortranlib = fa.__file__ + self.RE = 6371.009 # mean Earth radius - self.set_refh(refh) # reference height + self.set_refh(refh) # reference height if date is None: - self.year = helpers.toYearFraction(dt.datetime.now()) + self.year = helpers.toYearFraction(dt.datetime.utcnow()) else: try: - # convert date/datetime object to decimal year + # Convert date/datetime object to decimal year self.year = helpers.toYearFraction(date) - except: - # failed so date is probably int/float, use directly + except AttributeError: + # Failed while finding datetime attribute, so + # date is probably an int or float; use directly self.year = date if not os.path.isfile(datafile): - raise IOError('Datafile does not exist: {}'.format(datafile)) + raise IOError('Data file does not exist: {}'.format(datafile)) + + if not os.path.isfile(fortranlib): + raise IOError('Fortran library does not exist: {}'.format( + fortranlib)) self.datafile = datafile + self.fortranlib = fortranlib + self.set_epoch(self.year) # vectorize fortran functions @@ -93,12 +108,12 @@ def __init__(self, date=None, refh=0, datafile=None): lambda glat, glon, height: fa.apxg2q(glat, (glon + 180) % 360 - 180, height, 0)[:2], 3, 2) self._geo2apex = np.frompyfunc( - lambda glat, glon, height: fa.apxg2all(glat, (glon + 180) % 360 - - 180, height, self.refh, + lambda glat, glon, height: fa.apxg2all(glat, (glon + 180) % 360 + - 180, height, self.refh, 0)[2:4], 3, 2) self._geo2apexall = np.frompyfunc( - lambda glat, glon, height: fa.apxg2all(glat, (glon + 180) % 360 - - 180, height, self.refh, + lambda glat, glon, height: fa.apxg2all(glat, (glon + 180) % 360 + - 180, height, self.refh, 1), 3, 14) self._qd2geo = np.frompyfunc( lambda qlat, qlon, height, precision: fa.apxq2g(qlat, (qlon + 180) @@ -111,13 +126,14 @@ def __init__(self, date=None, refh=0, datafile=None): # vectorize other nonvectorized functions self._apex2qd = np.frompyfunc(self._apex2qd_nonvectorized, 3, 2) self._qd2apex = np.frompyfunc(self._qd2apex_nonvectorized, 3, 2) + self._get_babs = np.frompyfunc(self._get_babs_nonvectorized, 3, 1) def convert(self, lat, lon, source, dest, height=0, datetime=None, - precision=1e-10, ssheight=50*6371): + precision=1e-10, ssheight=50 * 6371): """Converts between geodetic, modified apex, quasi-dipole and MLT. Parameters - ========== + ---------- lat : array_like Latitude lon : array_like @@ -147,7 +163,7 @@ def convert(self, lat, lon, source, dest, height=0, datetime=None, prevents the South-Atlantic Anomaly (SAA) from influencing the MLT. Returns - ======= + ------- lat : ndarray or float Converted latitude (if converting to MLT, output latitude is apex) lat : ndarray or float @@ -206,7 +222,7 @@ def geo2apex(self, glat, glon, height): """Converts geodetic to modified apex coordinates. Parameters - ========== + ---------- glat : array_like Geodetic latitude glon : array_like @@ -215,7 +231,7 @@ def geo2apex(self, glat, glon, height): Altitude in km Returns - ======= + ------- alat : ndarray or float Modified apex latitude alon : ndarray or float @@ -227,9 +243,13 @@ def geo2apex(self, glat, glon, height): alat, alon = self._geo2apex(glat, glon, height) - if np.any(np.float64(alat) == -9999): - warnings.warn('Apex latitude set to -9999 where undefined ' + if np.any(alat == -9999): + warnings.warn('Apex latitude set to NaN where undefined ' '(apex height may be < reference height)') + if np.isscalar(alat): + alat = np.nan + else: + alat[alat == -9999] = np.nan # if array is returned, dtype is object, so convert to float return np.float64(alat), np.float64(alon) @@ -238,7 +258,7 @@ def apex2geo(self, alat, alon, height, precision=1e-10): """Converts modified apex to geodetic coordinates. Parameters - ========== + ---------- alat : array_like Modified apex latitude alon : array_like @@ -254,7 +274,7 @@ def apex2geo(self, alat, alon, height, precision=1e-10): within the specified precision. Returns - ======= + ------- glat : ndarray or float Geodetic latitude glon : ndarray or float @@ -277,7 +297,7 @@ def geo2qd(self, glat, glon, height): """Converts geodetic to quasi-dipole coordinates. Parameters - ========== + ---------- glat : array_like Geodetic latitude glon : array_like @@ -286,7 +306,7 @@ def geo2qd(self, glat, glon, height): Altitude in km Returns - ======= + ------- qlat : ndarray or float Quasi-dipole latitude qlon : ndarray or float @@ -305,7 +325,7 @@ def qd2geo(self, qlat, qlon, height, precision=1e-10): """Converts quasi-dipole to geodetic coordinates. Parameters - ========== + ---------- qlat : array_like Quasi-dipole latitude qlon : array_like @@ -321,7 +341,7 @@ def qd2geo(self, qlat, qlon, height, precision=1e-10): within the specified precision. Returns - ======= + ------- glat : ndarray or float Geodetic latitude glon : ndarray or float @@ -373,12 +393,13 @@ def _apex2qd_nonvectorized(self, alat, alon, height): # allow for values that are close hA = height else: - estr = 'height {:.3g} is > apex height '.format(np.max(height)) - estr += '{:.3g} for alat {:.3g}'.format(hA, alat) + estr = 'height {:.3g} is > apex height'.format(np.max(height))\ + + ' {:.3g} for alat {:.3g}'.format(hA, alat) raise ApexHeightError(estr) - qlat = np.sign(alat) * np.degrees(np.arccos(np.sqrt((self.RE + height) / - (self.RE + hA)))) + salat = np.sign(alat) if alat != 0 else 1 + qlat = salat * np.degrees(np.arccos(np.sqrt((self.RE + height) / + (self.RE + hA)))) return qlat, qlon @@ -386,7 +407,7 @@ def apex2qd(self, alat, alon, height): """Converts modified apex to quasi-dipole coordinates. Parameters - ========== + ---------- alat : array_like Modified apex latitude alon : array_like @@ -395,14 +416,14 @@ def apex2qd(self, alat, alon, height): Altitude in km Returns - ======= + ------- qlat : ndarray or float Quasi-dipole latitude qlon : ndarray or float Quasi-dipole longitude Raises - ====== + ------ ApexHeightError if `height` > apex height @@ -418,7 +439,7 @@ def _qd2apex_nonvectorized(self, qlat, qlon, height): qlat = helpers.checklat(qlat, name='qlat') alon = qlon - hA = self.get_apex(qlat, height) # apex height + hA = self.get_apex(qlat, height) # apex height if hA < self.refh: if np.isclose(hA, self.refh, rtol=0, atol=1e-5): @@ -429,9 +450,9 @@ def _qd2apex_nonvectorized(self, qlat, qlon, height): estr += '({:.3g}) for qlat {:.3g}'.format(self.refh, qlat) raise ApexHeightError(estr) - alat = np.sign(qlat) * np.degrees(np.arccos(np.sqrt((self.RE + - self.refh) / - (self.RE + hA)))) + sqlat = np.sign(qlat) if qlat != 0 else 1 + alat = sqlat * np.degrees(np.arccos(np.sqrt((self.RE + self.refh) / + (self.RE + hA)))) return alat, alon @@ -439,7 +460,7 @@ def qd2apex(self, qlat, qlon, height): """Converts quasi-dipole to modified apex coordinates. Parameters - ========== + ---------- qlat : array_like Quasi-dipole latitude qlon : array_like @@ -448,14 +469,14 @@ def qd2apex(self, qlat, qlon, height): Altitude in km Returns - ======= + ------- alat : ndarray or float Modified apex latitude alon : ndarray or float Modified apex longitude Raises - ====== + ------ ApexHeightError if apex height < reference height @@ -466,14 +487,14 @@ def qd2apex(self, qlat, qlon, height): # if array is returned, the dtype is object, so convert to float return np.float64(alat), np.float64(alon) - def mlon2mlt(self, mlon, datetime, ssheight=50*6371): + def mlon2mlt(self, mlon, datetime, ssheight=50 * 6371): """Computes the magnetic local time at the specified magnetic longitude and UT. Parameters - ========== + ---------- mlon : array_like - Magnetic longitude (apex and quasi-dipole longitude are always + Magnetic longitude (apex and quasi-dipole longitude are always equal) datetime : :class:`datetime.datetime` Date and time @@ -484,12 +505,12 @@ def mlon2mlt(self, mlon, datetime, ssheight=50*6371): prevents the South-Atlantic Anomaly (SAA) from influencing the MLT. Returns - ======= + ------- mlt : ndarray or float Magnetic local time [0, 24) Notes - ===== + ----- To compute the MLT, we find the apex longitude of the subsolar point at the given time. Then the MLT of the given point will be computed from the separation in magnetic longitude from this point (1 hour = 15 @@ -500,14 +521,14 @@ def mlon2mlt(self, mlon, datetime, ssheight=50*6371): ssalat, ssalon = self.geo2apex(ssglat, ssglon, ssheight) # np.float64 will ensure lists are converted to arrays - return (180 + np.float64(mlon) - ssalon)/15 % 24 + return (180 + np.float64(mlon) - ssalon) / 15 % 24 - def mlt2mlon(self, mlt, datetime, ssheight=50*6371): + def mlt2mlon(self, mlt, datetime, ssheight=50 * 6371): """Computes the magnetic longitude at the specified magnetic local time and UT. Parameters - ========== + ---------- mlt : array_like Magnetic local time datetime : :class:`datetime.datetime` @@ -519,13 +540,13 @@ def mlt2mlon(self, mlt, datetime, ssheight=50*6371): prevents the South-Atlantic Anomaly (SAA) from influencing the MLT. Returns - ======= + ------- mlon : ndarray or float Magnetic longitude [0, 360) (apex and quasi-dipole longitude are always equal) Notes - ===== + ----- To compute the magnetic longitude, we find the apex longitude of the subsolar point at the given time. Then the magnetic longitude of the given point will be computed from the separation in magnetic local time @@ -536,7 +557,7 @@ def mlt2mlon(self, mlt, datetime, ssheight=50*6371): ssalat, ssalon = self.geo2apex(ssglat, ssglon, ssheight) # np.float64 will ensure lists are converted to arrays - return (15*np.float64(mlt) - 180 + ssalon + 360) % 360 + return (15 * np.float64(mlt) - 180 + ssalon + 360) % 360 def map_to_height(self, glat, glon, height, newheight, conjugate=False, precision=1e-10): @@ -544,7 +565,7 @@ def map_to_height(self, glat, glon, height, newheight, conjugate=False, or conjugate hemisphere. Parameters - ========== + ---------- glat : array_like Geodetic latitude glon : array_like @@ -565,7 +586,7 @@ def map_to_height(self, glat, glon, height, newheight, conjugate=False, within the specified precision. Returns - ======= + ------- newglat : ndarray or float Geodetic latitude of mapped point newglon : ndarray or float @@ -576,7 +597,7 @@ def map_to_height(self, glat, glon, height, newheight, conjugate=False, into geo2qd (APXG2Q) Notes - ===== + ----- The mapping is done by converting glat/glon/height to modified apex lat/lon, and converting back to geographic using newheight (if conjugate, use negative apex latitude when converting back) @@ -597,15 +618,15 @@ def map_to_height(self, glat, glon, height, newheight, conjugate=False, def _map_EV_to_height(self, alat, alon, height, newheight, X, EV): # make sure X is array of correct shape - if(not (np.ndim(X) == 1 and np.size(X) == 3) and - not (np.ndim(X) == 2 and np.shape(X)[0] == 3)): + if (not (np.ndim(X) == 1 and np.size(X) == 3) and not ( + np.ndim(X) == 2 and np.shape(X)[0] == 3)): # raise ValueError because if passing e.g. a (6,) ndarray the # reshape below will work even though the input is invalid raise ValueError(EV + ' must be (3, N) or (3,) ndarray') - X = np.reshape(X, (3, np.size(X)//3)) + X = np.reshape(X, (3, np.size(X) // 3)) - _, _, _, _, _, _, d1, d2, _, e1, e2, _ = self.basevectors_apex(alat, \ - alon, height, coords='apex') + _, _, _, _, _, _, d1, d2, _, e1, e2, _ = self.basevectors_apex( + alat, alon, height, coords='apex') if EV == 'E': v1 = e1 @@ -615,14 +636,14 @@ def _map_EV_to_height(self, alat, alon, height, newheight, X, EV): v2 = d2 # make sure v1 and v2 have shape (3, N) - v1 = np.reshape(v1, (3, v1.size//3)) - v2 = np.reshape(v2, (3, v2.size//3)) + v1 = np.reshape(v1, (3, v1.size // 3)) + v2 = np.reshape(v2, (3, v2.size // 3)) - X1 = np.sum(X*v1, axis=0) # E dot e1 or V dot d1 - X2 = np.sum(X*v2, axis=0) # E dot e2 or V dot d2 + X1 = np.sum(X * v1, axis=0) # E dot e1 or V dot d1 + X2 = np.sum(X * v2, axis=0) # E dot e2 or V dot d2 - _, _, _, _, _, _, d1, d2, _, e1, e2, _ = self.basevectors_apex(alat, \ - alon, newheight, coords='apex') + _, _, _, _, _, _, d1, d2, _, e1, e2, _ = self.basevectors_apex( + alat, alon, newheight, coords='apex') if EV == 'E': v1 = d1 @@ -632,10 +653,10 @@ def _map_EV_to_height(self, alat, alon, height, newheight, X, EV): v2 = e2 # make sure v1 and v2 have shape (3, N) - v1 = np.reshape(v1, (3, v1.size//3)) - v2 = np.reshape(v2, (3, v2.size//3)) + v1 = np.reshape(v1, (3, v1.size // 3)) + v2 = np.reshape(v2, (3, v2.size // 3)) - X_mapped = X1[np.newaxis, :]*v1 + X2[np.newaxis, :]*v2 + X_mapped = X1[np.newaxis, :] * v1 + X2[np.newaxis, :] * v2 return np.squeeze(X_mapped) @@ -645,7 +666,7 @@ def map_E_to_height(self, alat, alon, height, newheight, E): It is assumed that the electric field is perpendicular to B. Parameters - ========== + ---------- alat : (N,) array_like or float Modified apex latitude alon : (N,) array_like or float @@ -659,13 +680,12 @@ def map_E_to_height(self, alat, alon, height, newheight, E): north, and up components Returns - ======= + ------- E : (3, N) or (3,) ndarray The electric field at `newheight` (geodetic east, north, and up components) """ - return self._map_EV_to_height(alat, alon, height, newheight, E, 'E') def map_V_to_height(self, alat, alon, height, newheight, V): @@ -674,7 +694,7 @@ def map_V_to_height(self, alat, alon, height, newheight, V): It is assumed that the electric field is perpendicular to B. Parameters - ========== + ---------- alat : (N,) array_like or float Modified apex latitude alon : (N,) array_like or float @@ -688,7 +708,7 @@ def map_V_to_height(self, alat, alon, height, newheight, V): east, north, and up components Returns - ======= + ------- V : (3, N) or (3,) ndarray The electric drift velocity at `newheight` (geodetic east, north, and up components) @@ -706,7 +726,7 @@ def basevectors_qd(self, lat, lon, height, coords='geo', precision=1e-10): north. Parameters - ========== + ---------- lat : (N,) array_like or float Latitude lon : (N,) array_like or float @@ -727,12 +747,12 @@ def basevectors_qd(self, lat, lon, height, coords='geo', precision=1e-10): passed through APXG2Q). Returns - ======= + ------- f1 : (2, N) or (2,) ndarray f2 : (2, N) or (2,) ndarray References - ========== + ---------- .. [2] Richmond, A. D. (1995), Ionospheric Electrodynamics Using Magnetic Apex Coordinates, Journal of geomagnetism and geoelectricity, 47(2), 191–212, :doi:`10.5636/jgg.47.191`. @@ -765,20 +785,15 @@ def basevectors_apex(self, lat, lon, height, coords='geo', precision=1e-10): north, and up (only east and north for `f1` and `f2`). Parameters - ========== - lat, lon : (N,) array_like or float - Latitude + ---------- lat : (N,) array_like or float + Latitude + lon : (N,) array_like or float Longitude height : (N,) array_like or float Altitude in km coords : {'geo', 'apex', 'qd'}, optional Input coordinate system - return_all : bool, optional - Will also return f3, g1, g2, and g3, and f1 and f2 have 3 components - (the last component is zero). Requires `lat`, `lon`, and `height` - to be broadcast to 1D (at least one of the parameters must be 1D - and the other two parameters must be 1D or 0D). precision : float, optional Precision of output (degrees) when converting to geo. A negative value of this argument produces a low-precision calculation of @@ -791,12 +806,11 @@ def basevectors_apex(self, lat, lon, height, coords='geo', precision=1e-10): passed through APXG2Q). Returns - ======= - f1, f2 : (2, N) or (2,) ndarray + ------- f3, g1, g2, g3, d1, d2, d3, e1, e2, e3 : (3, N) or (3,) ndarray - Note - ==== + Notes + ----- `f3`, `g1`, `g2`, and `g3` are not part of the Fortran code by Emmert et al. [2010] [5]_. They are calculated by this Python library according to the following equations in @@ -808,7 +822,7 @@ def basevectors_apex(self, lat, lon, height, coords='geo', precision=1e-10): * `f3`: Eqn. 6.8 References - ========== + ---------- .. [4] Richmond, A. D. (1995), Ionospheric Electrodynamics Using Magnetic Apex Coordinates, Journal of geomagnetism and @@ -844,14 +858,14 @@ def basevectors_apex(self, lat, lon, height, coords='geo', precision=1e-10): e3 = np.vstack(e3).T # make sure arrays are 2D - f1 = f1.reshape((2, f1.size//2)) - f2 = f2.reshape((2, f2.size//2)) - d1 = d1.reshape((3, d1.size//3)) - d2 = d2.reshape((3, d2.size//3)) - d3 = d3.reshape((3, d3.size//3)) - e1 = e1.reshape((3, e1.size//3)) - e2 = e2.reshape((3, e2.size//3)) - e3 = e3.reshape((3, e3.size//3)) + f1 = f1.reshape((2, f1.size // 2)) + f2 = f2.reshape((2, f2.size // 2)) + d1 = d1.reshape((3, d1.size // 3)) + d2 = d2.reshape((3, d2.size // 3)) + d3 = d3.reshape((3, d3.size // 3)) + e1 = e1.reshape((3, e1.size // 3)) + e2 = e2.reshape((3, e2.size // 3)) + e3 = e3.reshape((3, e3.size // 3)) # compute f3, g1, g2, g3 F1 = np.vstack((f1, np.zeros_like(f1[0]))) @@ -859,28 +873,29 @@ def basevectors_apex(self, lat, lon, height, coords='geo', precision=1e-10): F = np.cross(F1.T, F2.T).T[-1] cosI = helpers.getcosIm(alat) k = np.array([0, 0, 1], dtype=np.float64).reshape((3, 1)) - g1 = ((self.RE + np.float64(height)) / (self.RE + self.refh))**(3/2) \ - * d1 / F - g2 = -1.0 / (2.0 * F * np.tan(np.radians(qlat))) * \ - (k + ((self.RE + np.float64(height)) / (self.RE + self.refh)) - * d2 / cosI) - g3 = k*F + g1 = ((self.RE + np.float64(height)) + / (self.RE + self.refh)) ** (3 / 2) * d1 / F + g2 = -1.0 / (2.0 * F * np.tan(np.radians(qlat))) * ( + k + ((self.RE + np.float64(height)) + / (self.RE + self.refh)) * d2 / cosI) + g3 = k * F f3 = np.cross(g1.T, g2.T).T if np.any(alat == -9999): - warnings.warn(('Base vectors g, d, e, and f3 set to -9999 where ' - 'apex latitude is undefined (apex height may be < ' - 'reference height)')) - f3 = np.where(alat == -9999, -9999, f3) - g1 = np.where(alat == -9999, -9999, g1) - g2 = np.where(alat == -9999, -9999, g2) - g3 = np.where(alat == -9999, -9999, g3) - d1 = np.where(alat == -9999, -9999, d1) - d2 = np.where(alat == -9999, -9999, d2) - d3 = np.where(alat == -9999, -9999, d3) - e1 = np.where(alat == -9999, -9999, e1) - e2 = np.where(alat == -9999, -9999, e2) - e3 = np.where(alat == -9999, -9999, e3) + warnings.warn(''.join(['Base vectors g, d, e, and f3 set to NaN ', + 'where apex latitude is undefined (apex ', + 'height may be < reference height)'])) + mask = alat == -9999 + f3 = np.where(mask, np.nan, f3) + g1 = np.where(mask, np.nan, g1) + g2 = np.where(mask, np.nan, g2) + g3 = np.where(mask, np.nan, g3) + d1 = np.where(mask, np.nan, d1) + d2 = np.where(mask, np.nan, d2) + d3 = np.where(mask, np.nan, d3) + e1 = np.where(mask, np.nan, e1) + e2 = np.where(mask, np.nan, e2) + e3 = np.where(mask, np.nan, e3) return tuple(np.squeeze(x) for x in [f1, f2, f3, g1, g2, g3, d1, d2, d3, e1, e2, e3]) @@ -905,7 +920,7 @@ def get_apex(self, lat, height=None): if height is None: height = self.refh - cos_lat_squared = np.cos(np.radians(lat))**2 + cos_lat_squared = np.cos(np.radians(lat)) ** 2 apex_height = (self.RE + height) / cos_lat_squared - self.RE return apex_height @@ -914,28 +929,124 @@ def set_epoch(self, year): """Updates the epoch for all subsequent conversions. Parameters - ========== + ---------- year : float Decimal year """ - - fa.loadapxsh(self.datafile, np.float(year)) - self.year = year + # f2py + self.year = np.float64(year) + fa.loadapxsh(self.datafile, self.year) + igrf_fn = os.path.join(os.path.dirname(__file__), 'igrf13coeffs.txt') + if not os.path.exists(igrf_fn): + raise OSError("File {} does not exist".format(igrf_fn)) + fa.cofrm(self.year, igrf_fn) def set_refh(self, refh): """Updates the apex reference height for all subsequent conversions. Parameters - ========== + ---------- refh : float Apex reference height in km Notes - ===== + ----- The reference height is the height to which field lines will be mapped, and is only relevant for conversions involving apex (not quasi-dipole). """ - self.refh = refh + + def _get_babs_nonvectorized(self, glat, glon, height): + bnorth, beast, bdown, babs = fa.feldg(1, glat, glon, height) + # BABS is in guass, so convert to tesla + return babs / 10000.0 + + def get_babs(self, glat, glon, height): + """Returns the magnitude of the IGRF magnetic field in tesla. + + Parameters + ---------- + glat : array_like + Geodetic latitude + glon : array_like + Geodetic longitude + height : array_like + Altitude in km + + Returns + ------- + babs : ndarray or float + Magnitude of the IGRF magnetic field + + """ + + babs = self._get_babs(glat, glon, height) + + # if array is returned, the dtype is object, so convert to float + return np.float64(babs) + + def bvectors_apex(self, lat, lon, height, coords='geo', precision=1e-10): + """Returns the magnetic field vectors in apex coordinates. + + The apex magnetic field vectors described by Richmond [1995] [4]_ and + Emmert et al. [2010] [5]_, specfically the Be3 and Bd3 components. The + vector components are geodetic east, north, and up. + + Parameters + ---------- + lat : (N,) array_like or float + Latitude + lon : (N,) array_like or float + Longitude + height : (N,) array_like or float + Altitude in km + coords : {'geo', 'apex', 'qd'}, optional + Input coordinate system + precision : float, optional + Precision of output (degrees) when converting to geo. A negative + value of this argument produces a low-precision calculation of + geodetic lat/lon based only on their spherical harmonic + representation. + A positive value causes the underlying Fortran routine to iterate + until feeding the output geo lat/lon into geo2qd (APXG2Q) reproduces + the input QD lat/lon to within the specified precision (all + coordinates being converted to geo are converted to QD first and + passed through APXG2Q). + + Returns + ------- + Be3: (1, N) or (1,) ndarray + e3 : (3, N) or (3,) ndarray + Bd3: (1, N) or (1,) ndarray + d3 : (3, N) or (3,) ndarray + + Notes + ----- + Be3 is not equivalent to the magnitude of the IGRF magnitude, but is + instead equal to the IGRF magnitude divided by a scaling factor, D. + Similarly, Bd3 is the IGRF magnitude multiplied by D. + + See Richmond, A. D. (1995) [4]_ equations 3.13 and 3.14 + + References + ---------- + Richmond, A. D. (1995) [4]_ + Emmert, J. T. et al. (2010) [5]_ + + """ + glat, glon = self.convert(lat, lon, coords, 'geo', height=height, + precision=precision) + + babs = self.get_babs(glat, glon, height) + + _, _, _, _, _, _, d1, d2, d3, _, _, e3 = self.basevectors_apex( + glat, glon, height, coords='geo') + d1_cross_d2 = np.cross(d1.T, d2.T).T + D = np.sqrt(np.sum(d1_cross_d2 ** 2, axis=0)) + + Be3 = babs / D + Bd3 = babs * D + + return Be3, e3, Bd3, d3 diff --git a/src/apexpy/apexsh.dat b/src/apexpy/apexsh.dat index 10284f5d..5cd18099 100644 Binary files a/src/apexpy/apexsh.dat and b/src/apexpy/apexsh.dat differ diff --git a/src/apexpy/helpers.py b/src/apexpy/helpers.py index 3f362a58..76a47b42 100644 --- a/src/apexpy/helpers.py +++ b/src/apexpy/helpers.py @@ -8,86 +8,73 @@ import datetime as dt import numpy as np + def checklat(lat, name='lat'): """Makes sure the latitude is inside [-90, 90], clipping close values (tolerance 1e-4). Parameters - ========== - lat : array_like + ---------- + lat : array-like latitude name : str, optional parameter name to use in the exception message Returns - ======= + ------- lat : ndarray or float Same as input where values just outside the range have been clipped to [-90, 90] Raises - ====== + ------ ValueError if any values are too far outside the range [-90, 90] """ + if np.any(np.abs(lat) > 90 + 1e-5): + raise ValueError(name + ' must be in [-90, 90]') - if np.all(np.float64(lat) >= -90) and np.all(np.float64(lat) <= 90): - return lat - - if np.isscalar(lat): - if lat > 90 and np.isclose(lat, 90, rtol=0, atol=1e-4): - lat = 90 - return lat - elif lat < -90 and np.isclose(lat, -90, rtol=0, atol=1e-4): - lat = -90 - return lat - else: - lat = np.float64(lat) # make sure we have an array, not list - lat[(lat > 90) & (np.isclose(lat, 90, rtol=0, atol=1e-4))] = 90 - lat[(lat < -90) & (np.isclose(lat, -90, rtol=0, atol=1e-4))] = -90 - if np.all(lat >= -90) and np.all(lat <= 90): - return lat - - # we haven't returned yet, so raise exception - raise ValueError(name + ' must be in [-90, 90]') + return np.clip(lat, -90.0, 90.0) def getsinIm(alat): """Computes sinIm from modified apex latitude. Parameters - ========== - alat : array_like + ---------- + alat : array-like Modified apex latitude Returns - ======= + ------- sinIm : ndarray or float """ alat = np.float64(alat) - return 2*np.sin(np.radians(alat))/np.sqrt(4 - 3*np.cos(np.radians(alat))**2) + return 2 * np.sin(np.radians(alat)) / np.sqrt(4 - 3 + * np.cos(np.radians(alat))**2) def getcosIm(alat): """Computes cosIm from modified apex latitude. Parameters - ========== - alat : array_like + ---------- + alat : array-like Modified apex latitude Returns - ======= + ------- cosIm : ndarray or float """ alat = np.float64(alat) - return np.cos(np.radians(alat))/np.sqrt(4 - 3*np.cos(np.radians(alat))**2) + return np.cos(np.radians(alat)) / np.sqrt(4 - 3 + * np.cos(np.radians(alat))**2) def toYearFraction(date): @@ -95,16 +82,16 @@ def toYearFraction(date): year. Parameters - ========== + ---------- date : :class:`datetime.date` or :class:`datetime.datetime` Returns - ======= + ------- year : float Decimal year Notes - ===== + ----- The algorithm is taken from http://stackoverflow.com/a/6451892/2978652 """ @@ -114,11 +101,11 @@ def sinceEpoch(date): return time.mktime(date.timetuple()) year = date.year startOfThisYear = dt.datetime(year=year, month=1, day=1) - startOfNextYear = dt.datetime(year=year+1, month=1, day=1) + startOfNextYear = dt.datetime(year=year + 1, month=1, day=1) yearElapsed = sinceEpoch(date) - sinceEpoch(startOfThisYear) yearDuration = sinceEpoch(startOfNextYear) - sinceEpoch(startOfThisYear) - fraction = yearElapsed/yearDuration + fraction = yearElapsed / yearDuration return date.year + fraction @@ -127,36 +114,37 @@ def gc2gdlat(gclat): """Converts geocentric latitude to geodetic latitude using WGS84. Parameters - ========== - gclat : array_like + --------- + gclat : array-like Geocentric latitude Returns - ======= + ------- gdlat : ndarray or float Geodetic latitude """ WGS84_e2 = 0.006694379990141317 # WGS84 first eccentricity squared - return np.rad2deg(-np.arctan(np.tan(np.deg2rad(gclat))/(WGS84_e2 - 1))) + return np.rad2deg(-np.arctan(np.tan(np.deg2rad(gclat)) / (WGS84_e2 - 1))) def subsol(datetime): """Finds subsolar geocentric latitude and longitude. Parameters - ========== - datetime : :class:`datetime.datetime` + ---------- + datetime : :class:`datetime.datetime` or :class:`numpy.ndarray[datetime64]` + Date and time in UTC (naive objects are treated as UTC) Returns - ======= + ------- sbsllat : float Latitude of subsolar point sbsllon : float Longitude of subsolar point Notes - ===== + ----- Based on formulas in Astronomical Almanac for the year 1996, p. C24. (U.S. Government Printing Office, 1994). Usable for years 1601-2100, inclusive. According to the Almanac, results are good to at least 0.01 @@ -170,22 +158,35 @@ def subsol(datetime): by K. Laundal. """ - # convert to year, day of year and seconds since midnight - year = datetime.year - doy = datetime.timetuple().tm_yday - ut = datetime.hour * 3600 + datetime.minute * 60 + datetime.second + # Convert to year, day of year and seconds since midnight + if isinstance(datetime, dt.datetime): + year = np.asanyarray([datetime.year]) + doy = np.asanyarray([datetime.timetuple().tm_yday]) + ut = np.asanyarray([datetime.hour * 3600 + datetime.minute * 60 + + datetime.second]) + elif isinstance(datetime, np.ndarray): + # This conversion works for datetime of wrong precision or unit epoch + times = datetime.astype('datetime64[s]') + year_floor = times.astype('datetime64[Y]') + day_floor = times.astype('datetime64[D]') + year = year_floor.astype(int) + 1970 + doy = (day_floor - year_floor).astype(int) + 1 + ut = (times.astype('datetime64[s]') - day_floor).astype(float) + else: + raise ValueError("input must be datetime.datetime or numpy array") - if not 1601 <= year <= 2100: + if not (np.all(1601 <= year) and np.all(year <= 2100)): raise ValueError('Year must be in [1601, 2100]') yr = year - 2000 - nleap = int(np.floor((year - 1601.0) / 4.0)) + nleap = np.floor((year - 1601.0) / 4.0).astype(int) nleap -= 99 - if year <= 1900: - ncent = int(np.floor((year - 1601.0) / 100.0)) + mask_1900 = year <= 1900 + if np.any(mask_1900): + ncent = np.floor((year[mask_1900] - 1601.0) / 100.0).astype(int) ncent = 3 - ncent - nleap = nleap + ncent + nleap[mask_1900] = nleap[mask_1900] + ncent l0 = -79.549 + (-0.238699 * (yr - 4.0 * nleap) + 3.08514e-2 * nleap) g0 = -2.472 + (-0.2558905 * (yr - 4.0 * nleap) - 3.79617e-2 * nleap) @@ -215,12 +216,15 @@ def subsol(datetime): # Equation of time (degrees): etdeg = lmean - alpha - nrot = round(etdeg / 360.0) + nrot = np.round(etdeg / 360.0) etdeg = etdeg - 360.0 * nrot - # Subsolar longitude: - sslon = 180.0 - (ut / 240.0 + etdeg) # Earth rotates one degree every 240 s. - nrot = round(sslon / 360.0) + # Subsolar longitude calculation. Earth rotates one degree every 240 s. + sslon = 180.0 - (ut / 240.0 + etdeg) + nrot = np.round(sslon / 360.0) sslon = sslon - 360.0 * nrot + # Return a single value from the output if the input was a single value + if isinstance(datetime, dt.datetime): + return sslat[0], sslon[0] return sslat, sslon diff --git a/src/apexpy/igrf13coeffs.txt b/src/apexpy/igrf13coeffs.txt new file mode 100644 index 00000000..9b362e06 --- /dev/null +++ b/src/apexpy/igrf13coeffs.txt @@ -0,0 +1,199 @@ +# 13th Generation International Geomagnetic Reference Field Schmidt semi-normalised spherical harmonic coefficients, degree n=1,13 +# in units nanoTesla for IGRF and definitive DGRF main-field models (degree n=1,8 nanoTesla/year for secular variation (SV)) +c/s deg ord IGRF IGRF IGRF IGRF IGRF IGRF IGRF IGRF IGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF IGRF SV +g/h n m 1900.0 1905.0 1910.0 1915.0 1920.0 1925.0 1930.0 1935.0 1940.0 1945.0 1950.0 1955.0 1960.0 1965.0 1970.0 1975.0 1980.0 1985.0 1990.0 1995.0 2000.0 2005.0 2010.0 2015.0 2020.0 2020-25 +g 1 0 -31543 -31464 -31354 -31212 -31060 -30926 -30805 -30715 -30654 -30594 -30554 -30500 -30421 -30334 -30220 -30100 -29992 -29873 -29775 -29692 -29619.4 -29554.63 -29496.57 -29441.46 -29404.8 5.7 +g 1 1 -2298 -2298 -2297 -2306 -2317 -2318 -2316 -2306 -2292 -2285 -2250 -2215 -2169 -2119 -2068 -2013 -1956 -1905 -1848 -1784 -1728.2 -1669.05 -1586.42 -1501.77 -1450.9 7.4 +h 1 1 5922 5909 5898 5875 5845 5817 5808 5812 5821 5810 5815 5820 5791 5776 5737 5675 5604 5500 5406 5306 5186.1 5077.99 4944.26 4795.99 4652.5 -25.9 +g 2 0 -677 -728 -769 -802 -839 -893 -951 -1018 -1106 -1244 -1341 -1440 -1555 -1662 -1781 -1902 -1997 -2072 -2131 -2200 -2267.7 -2337.24 -2396.06 -2445.88 -2499.6 -11.0 +g 2 1 2905 2928 2948 2956 2959 2969 2980 2984 2981 2990 2998 3003 3002 2997 3000 3010 3027 3044 3059 3070 3068.4 3047.69 3026.34 3012.20 2982.0 -7.0 +h 2 1 -1061 -1086 -1128 -1191 -1259 -1334 -1424 -1520 -1614 -1702 -1810 -1898 -1967 -2016 -2047 -2067 -2129 -2197 -2279 -2366 -2481.6 -2594.50 -2708.54 -2845.41 -2991.6 -30.2 +g 2 2 924 1041 1176 1309 1407 1471 1517 1550 1566 1578 1576 1581 1590 1594 1611 1632 1663 1687 1686 1681 1670.9 1657.76 1668.17 1676.35 1677.0 -2.1 +h 2 2 1121 1065 1000 917 823 728 644 586 528 477 381 291 206 114 25 -68 -200 -306 -373 -413 -458.0 -515.43 -575.73 -642.17 -734.6 -22.4 +g 3 0 1022 1037 1058 1084 1111 1140 1172 1206 1240 1282 1297 1302 1302 1297 1287 1276 1281 1296 1314 1335 1339.6 1336.30 1339.85 1350.33 1363.2 2.2 +g 3 1 -1469 -1494 -1524 -1559 -1600 -1645 -1692 -1740 -1790 -1834 -1889 -1944 -1992 -2038 -2091 -2144 -2180 -2208 -2239 -2267 -2288.0 -2305.83 -2326.54 -2352.26 -2381.2 -5.9 +h 3 1 -330 -357 -389 -421 -445 -462 -480 -494 -499 -499 -476 -462 -414 -404 -366 -333 -336 -310 -284 -262 -227.6 -198.86 -160.40 -115.29 -82.1 6.0 +g 3 2 1256 1239 1223 1212 1205 1202 1205 1215 1232 1255 1274 1288 1289 1292 1278 1260 1251 1247 1248 1249 1252.1 1246.39 1232.10 1225.85 1236.2 3.1 +h 3 2 3 34 62 84 103 119 133 146 163 186 206 216 224 240 251 262 271 284 293 302 293.4 269.72 251.75 245.04 241.9 -1.1 +g 3 3 572 635 705 778 839 881 907 918 916 913 896 882 878 856 838 830 833 829 802 759 714.5 672.51 633.73 581.69 525.7 -12.0 +h 3 3 523 480 425 360 293 229 166 101 43 -11 -46 -83 -130 -165 -196 -223 -252 -297 -352 -427 -491.1 -524.72 -537.03 -538.70 -543.4 0.5 +g 4 0 876 880 884 887 889 891 896 903 914 944 954 958 957 957 952 946 938 936 939 940 932.3 920.55 912.66 907.42 903.0 -1.2 +g 4 1 628 643 660 678 695 711 727 744 762 776 792 796 800 804 800 791 782 780 780 780 786.8 797.96 808.97 813.68 809.5 -1.6 +h 4 1 195 203 211 218 220 216 205 188 169 144 136 133 135 148 167 191 212 232 247 262 272.6 282.07 286.48 283.54 281.9 -0.1 +g 4 2 660 653 644 631 616 601 584 565 550 544 528 510 504 479 461 438 398 361 325 290 250.0 210.65 166.58 120.49 86.3 -5.9 +h 4 2 -69 -77 -90 -109 -134 -163 -195 -226 -252 -276 -278 -274 -278 -269 -266 -265 -257 -249 -240 -236 -231.9 -225.23 -211.03 -188.43 -158.4 6.5 +g 4 3 -361 -380 -400 -416 -424 -426 -422 -415 -405 -421 -408 -397 -394 -390 -395 -405 -419 -424 -423 -418 -403.0 -379.86 -356.83 -334.85 -309.4 5.2 +h 4 3 -210 -201 -189 -173 -153 -130 -109 -90 -72 -55 -37 -23 3 13 26 39 53 69 84 97 119.8 145.15 164.46 180.95 199.7 3.6 +g 4 4 134 146 160 178 199 217 234 249 265 304 303 290 269 252 234 216 199 170 141 122 111.3 100.00 89.40 70.38 48.0 -5.1 +h 4 4 -75 -65 -55 -51 -57 -70 -90 -114 -141 -178 -210 -230 -255 -269 -279 -288 -297 -297 -299 -306 -303.8 -305.36 -309.72 -329.23 -349.7 -5.0 +g 5 0 -184 -192 -201 -211 -221 -230 -237 -241 -241 -253 -240 -229 -222 -219 -216 -218 -218 -214 -214 -214 -218.8 -227.00 -230.87 -232.91 -234.3 -0.3 +g 5 1 328 328 327 327 326 326 327 329 334 346 349 360 362 358 359 356 357 355 353 352 351.4 354.41 357.29 360.14 363.2 0.5 +h 5 1 -210 -193 -172 -148 -122 -96 -72 -51 -33 -12 3 15 16 19 26 31 46 47 46 46 43.8 42.72 44.58 46.98 47.7 0.0 +g 5 2 264 259 253 245 236 226 218 211 208 194 211 230 242 254 262 264 261 253 245 235 222.3 208.95 200.26 192.35 187.8 -0.6 +h 5 2 53 56 57 58 58 58 60 64 71 95 103 110 125 128 139 148 150 150 154 165 171.9 180.25 189.01 196.98 208.3 2.5 +g 5 3 5 -1 -9 -16 -23 -28 -32 -33 -33 -20 -20 -23 -26 -31 -42 -59 -74 -93 -109 -118 -130.4 -136.54 -141.05 -140.94 -140.7 0.2 +h 5 3 -33 -32 -33 -34 -38 -44 -53 -64 -75 -67 -87 -98 -117 -126 -139 -152 -151 -154 -153 -143 -133.1 -123.45 -118.06 -119.14 -121.2 -0.6 +g 5 4 -86 -93 -102 -111 -119 -125 -131 -136 -141 -142 -147 -152 -156 -157 -160 -159 -162 -164 -165 -166 -168.6 -168.05 -163.17 -157.40 -151.2 1.3 +h 5 4 -124 -125 -126 -126 -125 -122 -118 -115 -113 -119 -122 -121 -114 -97 -91 -83 -78 -75 -69 -55 -39.3 -19.57 -0.01 15.98 32.3 3.0 +g 5 5 -16 -26 -38 -51 -62 -69 -74 -76 -76 -82 -76 -69 -63 -62 -56 -49 -48 -46 -36 -17 -12.9 -13.55 -8.03 4.30 13.5 0.9 +h 5 5 3 11 21 32 43 51 58 64 69 82 80 78 81 81 83 88 92 95 97 107 106.3 103.85 101.04 100.12 98.9 0.3 +g 6 0 63 62 62 61 61 61 60 59 57 59 54 47 46 45 43 45 48 53 61 68 72.3 73.60 72.78 69.55 66.0 -0.5 +g 6 1 61 60 58 57 55 54 53 53 54 57 57 57 58 61 64 66 66 65 65 67 68.2 69.56 68.69 67.57 65.5 -0.3 +h 6 1 -9 -7 -5 -2 0 3 4 4 4 6 -1 -9 -10 -11 -12 -13 -15 -16 -16 -17 -17.4 -20.33 -20.90 -20.61 -19.1 0.0 +g 6 2 -11 -11 -11 -10 -10 -9 -9 -8 -7 6 4 3 1 8 15 28 42 51 59 68 74.2 76.74 75.92 72.79 72.9 0.4 +h 6 2 83 86 89 93 96 99 102 104 105 100 99 96 99 100 100 99 93 88 82 72 63.7 54.75 44.18 33.30 25.1 -1.6 +g 6 3 -217 -221 -224 -228 -233 -238 -242 -246 -249 -246 -247 -247 -237 -228 -212 -198 -192 -185 -178 -170 -160.9 -151.34 -141.40 -129.85 -121.5 1.3 +h 6 3 2 4 5 8 11 14 19 25 33 16 33 48 60 68 72 75 71 69 69 67 65.1 63.63 61.54 58.74 52.8 -1.3 +g 6 4 -58 -57 -54 -51 -46 -40 -32 -25 -18 -25 -16 -8 -1 4 2 1 4 4 3 -1 -5.9 -14.58 -22.83 -28.93 -36.2 -1.4 +h 6 4 -35 -32 -29 -26 -22 -18 -16 -15 -15 -9 -12 -16 -20 -32 -37 -41 -43 -48 -52 -58 -61.2 -63.53 -66.26 -66.64 -64.5 0.8 +g 6 5 59 57 54 49 44 39 32 25 18 21 12 7 -2 1 3 6 14 16 18 19 16.9 14.58 13.10 13.14 13.5 0.0 +h 6 5 36 32 28 23 18 13 8 4 0 -16 -12 -12 -11 -8 -6 -4 -2 -1 1 1 0.7 0.24 3.02 7.35 8.9 0.0 +g 6 6 -90 -92 -95 -98 -101 -103 -104 -106 -107 -104 -105 -107 -113 -111 -112 -111 -108 -102 -96 -93 -90.4 -86.36 -78.09 -70.85 -64.7 0.9 +h 6 6 -69 -67 -65 -62 -57 -52 -46 -40 -33 -39 -30 -24 -17 -7 1 11 17 21 24 36 43.8 50.94 55.40 62.41 68.1 1.0 +g 7 0 70 70 71 72 73 73 74 74 74 70 65 65 67 75 72 71 72 74 77 77 79.0 79.88 80.44 81.29 80.6 -0.1 +g 7 1 -55 -54 -54 -54 -54 -54 -54 -53 -53 -40 -55 -56 -56 -57 -57 -56 -59 -62 -64 -72 -74.0 -74.46 -75.00 -75.99 -76.7 -0.2 +h 7 1 -45 -46 -47 -48 -49 -50 -51 -52 -52 -45 -35 -50 -55 -61 -70 -77 -82 -83 -80 -69 -64.6 -61.14 -57.80 -54.27 -51.5 0.6 +g 7 2 0 0 1 2 2 3 4 4 4 0 2 2 5 4 1 1 2 3 2 1 0.0 -1.65 -4.55 -6.79 -8.2 0.0 +h 7 2 -13 -14 -14 -14 -14 -14 -15 -17 -18 -18 -17 -24 -28 -27 -27 -26 -27 -27 -26 -25 -24.2 -22.57 -21.20 -19.53 -16.9 0.6 +g 7 3 34 33 32 31 29 27 25 23 20 0 1 10 15 13 14 16 21 24 26 28 33.3 38.73 45.24 51.82 56.5 0.7 +h 7 3 -10 -11 -12 -12 -13 -14 -14 -14 -14 2 0 -4 -6 -2 -4 -5 -5 -2 0 4 6.2 6.82 6.54 5.59 2.2 -0.8 +g 7 4 -41 -41 -40 -38 -37 -35 -34 -33 -31 -29 -40 -32 -32 -26 -22 -14 -12 -6 -1 5 9.1 12.30 14.00 15.07 15.8 0.1 +h 7 4 -1 0 1 2 4 5 6 7 7 6 10 8 7 6 8 10 16 20 21 24 24.0 25.35 24.96 24.45 23.5 -0.2 +g 7 5 -21 -20 -19 -18 -16 -14 -12 -11 -9 -10 -7 -11 -7 -6 -2 0 1 4 5 4 6.9 9.37 10.46 9.32 6.4 -0.5 +h 7 5 28 28 28 28 28 29 29 29 29 28 36 28 23 26 23 22 18 17 17 17 14.8 10.93 7.03 3.27 -2.2 -1.1 +g 7 6 18 18 18 19 19 19 18 18 17 15 5 9 17 13 13 12 11 10 9 8 7.3 5.42 1.64 -2.88 -7.2 -0.8 +h 7 6 -12 -12 -13 -15 -16 -17 -18 -19 -20 -17 -18 -20 -18 -23 -23 -23 -23 -23 -23 -24 -25.4 -26.32 -27.61 -27.50 -27.2 0.1 +g 7 7 6 6 6 6 6 6 6 6 5 29 19 18 8 1 -2 -5 -2 0 0 -2 -1.2 1.94 4.92 6.61 9.8 0.8 +h 7 7 -22 -22 -22 -22 -22 -21 -20 -19 -19 -22 -16 -18 -17 -12 -11 -12 -10 -7 -4 -6 -5.8 -4.64 -3.28 -2.32 -1.8 0.3 +g 8 0 11 11 11 11 11 11 11 11 11 13 22 11 15 13 14 14 18 21 23 25 24.4 24.80 24.41 23.98 23.7 0.0 +g 8 1 8 8 8 8 7 7 7 7 7 7 15 9 6 5 6 6 6 6 5 6 6.6 7.62 8.21 8.89 9.7 0.1 +h 8 1 8 8 8 8 8 8 8 8 8 12 5 10 11 7 7 6 7 8 10 11 11.9 11.20 10.84 10.04 8.4 -0.2 +g 8 2 -4 -4 -4 -4 -3 -3 -3 -3 -3 -8 -4 -6 -4 -4 -2 -1 0 0 -1 -6 -9.2 -11.73 -14.50 -16.78 -17.6 -0.1 +h 8 2 -14 -15 -15 -15 -15 -15 -15 -15 -14 -21 -22 -15 -14 -12 -15 -16 -18 -19 -19 -21 -21.5 -20.88 -20.03 -18.26 -15.3 0.6 +g 8 3 -9 -9 -9 -9 -9 -9 -9 -9 -10 -5 -1 -14 -11 -14 -13 -12 -11 -11 -10 -9 -7.9 -6.88 -5.59 -3.16 -0.5 0.4 +h 8 3 7 7 6 6 6 6 5 5 5 -12 0 5 7 9 6 4 4 5 6 8 8.5 9.83 11.83 13.18 12.8 -0.2 +g 8 4 1 1 1 2 2 2 2 1 1 9 11 6 2 0 -3 -8 -7 -9 -12 -14 -16.6 -18.11 -19.34 -20.56 -21.1 -0.1 +h 8 4 -13 -13 -13 -13 -14 -14 -14 -15 -15 -7 -21 -23 -18 -16 -17 -19 -22 -23 -22 -23 -21.5 -19.71 -17.41 -14.60 -11.7 0.5 +g 8 5 2 2 2 3 4 4 5 6 6 7 15 10 10 8 5 4 4 4 3 9 9.1 10.17 11.61 13.33 15.3 0.4 +h 8 5 5 5 5 5 5 5 5 5 5 2 -8 3 4 4 6 6 9 11 12 15 15.5 16.22 16.71 16.16 14.9 -0.3 +g 8 6 -9 -8 -8 -8 -7 -7 -6 -6 -5 -10 -13 -7 -5 -1 0 0 3 4 4 6 7.0 9.36 10.85 11.76 13.7 0.3 +h 8 6 16 16 16 16 17 17 18 18 19 18 17 23 23 24 21 18 16 14 12 11 8.9 7.61 6.96 5.69 3.6 -0.4 +g 8 7 5 5 5 6 6 7 8 8 9 7 5 6 10 11 11 10 6 4 2 -5 -7.9 -11.25 -14.05 -15.98 -16.5 -0.1 +h 8 7 -5 -5 -5 -5 -5 -5 -5 -5 -5 3 -4 -4 1 -3 -6 -10 -13 -15 -16 -16 -14.9 -12.76 -10.74 -9.10 -6.9 0.5 +g 8 8 8 8 8 8 8 8 8 7 7 2 -1 9 8 4 3 1 -1 -4 -6 -7 -7.0 -4.87 -3.54 -2.02 -0.3 0.4 +h 8 8 -18 -18 -18 -18 -19 -19 -19 -19 -19 -11 -17 -13 -20 -17 -16 -17 -15 -11 -10 -4 -2.1 -0.06 1.64 2.26 2.8 0.0 +g 9 0 8 8 8 8 8 8 8 8 8 5 3 4 4 8 8 7 5 5 4 4 5.0 5.58 5.50 5.33 5.0 0.0 +g 9 1 10 10 10 10 10 10 10 10 10 -21 -7 9 6 10 10 10 10 10 9 9 9.4 9.76 9.45 8.83 8.4 0.0 +h 9 1 -20 -20 -20 -20 -20 -20 -20 -20 -21 -27 -24 -11 -18 -22 -21 -21 -21 -21 -20 -20 -19.7 -20.11 -20.54 -21.77 -23.4 0.0 +g 9 2 1 1 1 1 1 1 1 1 1 1 -1 -4 0 2 2 2 1 1 1 3 3.0 3.58 3.45 3.02 2.9 0.0 +h 9 2 14 14 14 14 14 14 14 15 15 17 19 12 12 15 16 16 16 15 15 15 13.4 12.69 11.51 10.76 11.0 0.0 +g 9 3 -11 -11 -11 -11 -11 -11 -12 -12 -12 -11 -25 -5 -9 -13 -12 -12 -12 -12 -12 -10 -8.4 -6.94 -5.27 -3.22 -1.5 0.0 +h 9 3 5 5 5 5 5 5 5 5 5 29 12 7 2 7 6 7 9 9 11 12 12.5 12.67 12.75 11.74 9.8 0.0 +g 9 4 12 12 12 12 12 12 12 11 11 3 10 2 1 10 10 10 9 9 9 8 6.3 5.01 3.13 0.67 -1.1 0.0 +h 9 4 -3 -3 -3 -3 -3 -3 -3 -3 -3 -9 2 6 0 -4 -4 -4 -5 -6 -7 -6 -6.2 -6.72 -7.14 -6.74 -5.1 0.0 +g 9 5 1 1 1 1 1 1 1 1 1 16 5 4 4 -1 -1 -1 -3 -3 -4 -8 -8.9 -10.76 -12.38 -13.20 -13.2 0.0 +h 9 5 -2 -2 -2 -2 -2 -2 -2 -3 -3 4 2 -2 -3 -5 -5 -5 -6 -6 -7 -8 -8.4 -8.16 -7.42 -6.88 -6.3 0.0 +g 9 6 -2 -2 -2 -2 -2 -2 -2 -2 -2 -3 -5 1 -1 -1 0 -1 -1 -1 -2 -1 -1.5 -1.25 -0.76 -0.10 1.1 0.0 +h 9 6 8 8 8 8 9 9 9 9 9 9 8 10 9 10 10 10 9 9 9 8 8.4 8.10 7.97 7.79 7.8 0.0 +g 9 7 2 2 2 2 2 2 3 3 3 -4 -2 2 -2 5 3 4 7 7 7 10 9.3 8.76 8.43 8.68 8.8 0.0 +h 9 7 10 10 10 10 10 10 10 11 11 6 8 7 8 10 11 11 10 9 8 5 3.8 2.92 2.14 1.04 0.4 0.0 +g 9 8 -1 0 0 0 0 0 0 0 1 -3 3 2 3 1 1 1 2 1 1 -2 -4.3 -6.66 -8.42 -9.06 -9.3 0.0 +h 9 8 -2 -2 -2 -2 -2 -2 -2 -2 -2 1 -11 -6 0 -4 -2 -3 -6 -7 -7 -8 -8.2 -7.73 -6.08 -3.89 -1.4 0.0 +g 9 9 -1 -1 -1 -1 -1 -1 -2 -2 -2 -4 8 5 -1 -2 -1 -2 -5 -5 -6 -8 -8.2 -9.22 -10.08 -10.54 -11.9 0.0 +h 9 9 2 2 2 2 2 2 2 2 2 8 -7 5 5 1 1 1 2 2 2 3 4.8 6.01 7.01 8.44 9.6 0.0 +g 10 0 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -8 -3 1 -2 -3 -3 -4 -4 -3 -3 -2.6 -2.17 -1.94 -2.01 -1.9 0.0 +g 10 1 -4 -4 -4 -4 -4 -4 -4 -4 -4 11 4 -5 -3 -3 -3 -3 -4 -4 -4 -6 -6.0 -6.12 -6.24 -6.26 -6.2 0.0 +h 10 1 2 2 2 2 2 2 2 2 2 5 13 -4 4 2 1 1 1 1 2 1 1.7 2.19 2.73 3.28 3.4 0.0 +g 10 2 2 2 2 2 2 2 2 2 2 1 -1 -1 4 2 2 2 2 3 2 2 1.7 1.42 0.89 0.17 -0.1 0.0 +h 10 2 1 1 1 1 1 1 1 1 1 1 -2 0 1 1 1 1 0 0 1 0 0.0 0.10 -0.10 -0.40 -0.2 0.0 +g 10 3 -5 -5 -5 -5 -5 -5 -5 -5 -5 2 13 2 0 -5 -5 -5 -5 -5 -5 -4 -3.1 -2.35 -1.07 0.55 1.7 0.0 +h 10 3 2 2 2 2 2 2 2 2 2 -20 -10 -8 0 2 3 3 3 3 3 4 4.0 4.46 4.71 4.55 3.6 0.0 +g 10 4 -2 -2 -2 -2 -2 -2 -2 -2 -2 -5 -4 -3 -1 -2 -1 -2 -2 -2 -2 -1 -0.5 -0.15 -0.16 -0.55 -0.9 0.0 +h 10 4 6 6 6 6 6 6 6 6 6 -1 2 -2 2 6 4 4 6 6 6 5 4.9 4.76 4.44 4.40 4.8 0.0 +g 10 5 6 6 6 6 6 6 6 6 6 -1 4 7 4 4 6 5 5 5 4 4 3.7 3.06 2.45 1.70 0.7 0.0 +h 10 5 -4 -4 -4 -4 -4 -4 -4 -4 -4 -6 -3 -4 -5 -4 -4 -4 -4 -4 -4 -5 -5.9 -6.58 -7.22 -7.92 -8.6 0.0 +g 10 6 4 4 4 4 4 4 4 4 4 8 12 4 6 4 4 4 3 3 3 2 1.0 0.29 -0.33 -0.67 -0.9 0.0 +h 10 6 0 0 0 0 0 0 0 0 0 6 6 1 1 0 0 -1 0 0 0 -1 -1.2 -1.01 -0.96 -0.61 -0.1 0.0 +g 10 7 0 0 0 0 0 0 0 0 0 -1 3 -2 1 0 1 1 1 1 1 2 2.0 2.06 2.13 2.13 1.9 0.0 +h 10 7 -2 -2 -2 -2 -2 -2 -2 -1 -1 -4 -3 -3 -1 -2 -1 -1 -1 -1 -2 -2 -2.9 -3.47 -3.95 -4.16 -4.3 0.0 +g 10 8 2 2 2 1 1 1 1 2 2 -3 2 6 -1 2 0 0 2 2 3 5 4.2 3.77 3.09 2.33 1.4 0.0 +h 10 8 4 4 4 4 4 4 4 4 4 -2 6 7 6 3 3 3 4 4 3 1 0.2 -0.86 -1.99 -2.85 -3.4 0.0 +g 10 9 2 2 2 2 3 3 3 3 3 5 10 -2 2 2 3 3 3 3 3 1 0.3 -0.21 -1.03 -1.80 -2.4 0.0 +h 10 9 0 0 0 0 0 0 0 0 0 0 11 -1 0 0 1 1 0 0 -1 -2 -2.2 -2.31 -1.97 -1.12 -0.1 0.0 +g 10 10 0 0 0 0 0 0 0 0 0 -2 3 0 0 0 -1 -1 0 0 0 0 -1.1 -2.09 -2.80 -3.59 -3.8 0.0 +h 10 10 -6 -6 -6 -6 -6 -6 -6 -6 -6 -2 8 -3 -7 -6 -4 -5 -6 -6 -6 -7 -7.4 -7.93 -8.31 -8.72 -8.8 0.0 +g 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.7 2.95 3.05 3.00 3.0 0.0 +g 11 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.7 -1.60 -1.48 -1.40 -1.4 0.0 +h 11 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.26 0.13 0.00 0.0 0.0 +g 11 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.9 -1.88 -2.03 -2.30 -2.5 0.0 +h 11 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.3 1.44 1.67 2.11 2.5 0.0 +g 11 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.5 1.44 1.65 2.08 2.3 0.0 +h 11 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.77 -0.66 -0.60 -0.6 0.0 +g 11 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 -0.31 -0.51 -0.79 -0.9 0.0 +h 11 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.6 -2.27 -1.76 -1.05 -0.4 0.0 +g 11 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.29 0.54 0.58 0.3 0.0 +h 11 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.9 0.90 0.85 0.76 0.6 0.0 +g 11 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.7 -0.79 -0.79 -0.70 -0.7 0.0 +h 11 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.7 -0.58 -0.39 -0.20 -0.2 0.0 +g 11 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.53 0.37 0.14 -0.1 0.0 +h 11 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.8 -2.69 -2.51 -2.12 -1.7 0.0 +g 11 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.7 1.80 1.79 1.70 1.4 0.0 +h 11 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -1.08 -1.27 -1.44 -1.6 0.0 +g 11 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.16 0.12 -0.22 -0.6 0.0 +h 11 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.2 -1.58 -2.11 -2.57 -3.0 0.0 +g 11 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.2 0.96 0.75 0.44 0.2 0.0 +h 11 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.9 -1.90 -1.94 -2.01 -2.0 0.0 +g 11 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4.0 3.99 3.75 3.49 3.1 0.0 +h 11 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -1.39 -1.86 -2.34 -2.6 0.0 +g 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.2 -2.15 -2.12 -2.09 -2.0 0.0 +g 12 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.3 -0.29 -0.21 -0.16 -0.1 0.0 +h 12 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.55 -0.87 -1.08 -1.2 0.0 +g 12 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0.21 0.30 0.46 0.5 0.0 +h 12 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.23 0.27 0.37 0.5 0.0 +g 12 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.9 0.89 1.04 1.23 1.3 0.0 +h 12 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.5 2.38 2.13 1.75 1.4 0.0 +g 12 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.38 -0.63 -0.89 -1.2 0.0 +h 12 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.6 -2.63 -2.49 -2.19 -1.8 0.0 +g 12 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.9 0.96 0.95 0.85 0.7 0.0 +h 12 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.61 0.49 0.27 0.1 0.0 +g 12 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.5 -0.30 -0.11 0.10 0.3 0.0 +h 12 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.40 0.59 0.72 0.8 0.0 +g 12 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.46 0.52 0.54 0.5 0.0 +h 12 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0 0.01 0.00 -0.09 -0.2 0.0 +g 12 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.3 -0.35 -0.39 -0.37 -0.3 0.0 +h 12 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0 0.02 0.13 0.29 0.6 0.0 +g 12 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.36 -0.37 -0.43 -0.5 0.0 +h 12 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.28 0.27 0.23 0.2 0.0 +g 12 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 0.08 0.21 0.22 0.1 0.0 +h 12 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.87 -0.86 -0.89 -0.9 0.0 +g 12 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.49 -0.77 -0.94 -1.1 0.0 +h 12 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.34 -0.23 -0.16 0.0 0.0 +g 12 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.08 0.04 -0.03 -0.3 0.0 +h 12 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0.88 0.87 0.72 0.5 0.0 +g 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.16 -0.09 -0.02 0.1 0.0 +g 13 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.88 -0.89 -0.92 -0.9 0.0 +h 13 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.76 -0.87 -0.88 -0.9 0.0 +g 13 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.30 0.31 0.42 0.5 0.0 +h 13 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0.33 0.30 0.49 0.6 0.0 +g 13 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.28 0.42 0.63 0.7 0.0 +h 13 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.8 1.72 1.66 1.56 1.4 0.0 +g 13 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.43 -0.45 -0.42 -0.3 0.0 +h 13 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.54 -0.59 -0.50 -0.4 0.0 +g 13 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.3 1.18 1.08 0.96 0.8 0.0 +h 13 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.0 -1.07 -1.14 -1.24 -1.3 0.0 +g 13 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.37 -0.31 -0.19 0.0 0.0 +h 13 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 -0.04 -0.07 -0.10 -0.1 0.0 +g 13 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.75 0.78 0.81 0.8 0.0 +h 13 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.63 0.54 0.42 0.3 0.0 +g 13 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.26 -0.18 -0.13 0.0 0.0 +h 13 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.21 0.10 -0.04 -0.1 0.0 +g 13 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.35 0.38 0.38 0.4 0.0 +h 13 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0.53 0.49 0.48 0.5 0.0 +g 13 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 -0.05 0.02 0.08 0.1 0.0 +h 13 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.38 0.44 0.48 0.5 0.0 +g 13 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.4 0.41 0.42 0.46 0.5 0.0 +h 13 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.22 -0.25 -0.30 -0.4 0.0 +g 13 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0 -0.10 -0.26 -0.35 -0.5 0.0 +h 13 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.5 -0.57 -0.53 -0.43 -0.4 0.0 +g 13 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 -0.18 -0.26 -0.36 -0.4 0.0 +h 13 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.82 -0.79 -0.71 -0.6 0.0 diff --git a/src/fortranapex/Makefile b/src/fortranapex/Makefile index bebb81ea..97ef22f4 100644 --- a/src/fortranapex/Makefile +++ b/src/fortranapex/Makefile @@ -2,18 +2,24 @@ # Ver. 1.0. 27/8-10 - Based on Makefile for Einar Stiansen. -FC = gfortran -#FC = mpif90 +# If using Intel compilers, uncomment a line at the top of the igrf.f90 file +FC = gfortran # mpif90, ifort LD = $(FC) LDFLAGS = #-ipo FFLAGS = -O3 -fPIC #-ipo -check bounds -check pointer -check uninit +SFLAGS = -static #-libgfortran -static-libgcc (AGB, static should work?) +STATIC = 0 + +ifneq ($(STATIC),0) + LDFLAGS += $(SFLAGS) +endif #MKL_INCLUDE = -I/global/apps/intel/mkl/10.1.0.015/include/ LIBS = #-L/global/apps/intel/mkl/10.1.0.015/ -shared-intel -Wl,--start-group /global/apps/intel/mkl/10.1.0.015/lib/em64t/libmkl_intel_lp6\4.a /global/apps/intel/mkl/10.1.0.015/lib/em64t/libmkl_core.a PROG = apextest -OBJS = checkapexsh.o apexsh.o makeapexsh.o apex.o magfld.o +OBJS = igrf.o checkapexsh.o apexsh.o makeapexsh.o apex.o magfld.o $(PROG) : $(OBJS) $(LD) $(LDFLAGS) $(OBJS) -o $(PROG) $(LIBS) @@ -21,6 +27,9 @@ $(PROG) : $(OBJS) apexsh.o : apexsh.f90 $(FC) -c $(FFLAGS) $< +igrf.o : igrf.f90 + $(FC) -c $(FFLAGS) $< + checkapexsh.o : checkapexsh.f90 $(FC) -c $(FFLAGS) $< diff --git a/src/fortranapex/apex.f b/src/fortranapex/apex.f index 5ec65cbe..9c87ac45 100644 --- a/src/fortranapex/apex.f +++ b/src/fortranapex/apex.f @@ -1,6 +1,6 @@ C FILE NAME: apex.f - SUBROUTINE APEX (DATE,DLAT,DLON,ALT, + SUBROUTINE APEX (DATE,IGRFFILEIN,DLAT,DLON,ALT, + A,ALAT,ALON,BMAG,XMAG,YMAG,ZMAG,V) C Calculate apex radius, latitude, longitude; and magnetic field and C scalar magnetic potential. @@ -46,6 +46,8 @@ SUBROUTINE APEX (DATE,DLAT,DLON,ALT, C - Refine FNDAPX to insure |Bdown/Btot| < 1.E-6 at apex C Nov 2009: Change definition of earth's mean radius (RE) from 6371.2 C to the WGS84 value (6371.0088), by J.T. Emmert, NRL +C Feb 2021: Modified by Ashton Reimer to pass IGRF coefficients file +C to the COFRM subroutine call. C C------------------------------------------------------------------------------ C Reference Spheroid Change March 2004 @@ -106,7 +108,8 @@ SUBROUTINE APEX (DATE,DLAT,DLON,ALT, PARAMETER (RE = 6371.0088, DTOR = .01745329251994330) COMMON /DIPOLE/ COLAT,ELON,VP,CTP,STP - CALL COFRM (DATE) + CHARACTER(LEN=1000), intent(in) :: IGRFFILEIN + CALL COFRM (DATE,IGRFFILEIN) CALL DYPOL (CLATP,POLON,VPOL) COLAT = CLATP CTP = COS(CLATP*DTOR) diff --git a/src/fortranapex/checkapexsh.f90 b/src/fortranapex/checkapexsh.f90 index 37ef8858..5580f771 100644 --- a/src/fortranapex/checkapexsh.f90 +++ b/src/fortranapex/checkapexsh.f90 @@ -12,14 +12,22 @@ ! vectors, J. Geophys. Res., 115, Axxxxx, doi:10.1029/2010JA015326, 2010. ! !******************************************************************************* +! +! HISTORY (blame): +! +! 25 Feb 2021: Modified by Ashton Reimer to pass IGRF coefficients file to the +! makeapxsh subroutine call. +! +!******************************************************************************* program checkapexsh implicit none - integer(4), parameter :: nepochgrid=25 + integer(4), parameter :: nepochgrid=26 integer(4) :: lmax=3, nmmax=6 - character(128) :: apexshfile='apexsh.dat' + character(1000) :: apexshfile='apexsh.dat' + character(len=1000) :: igrffilein='igrf13coeffs.txt' real(4) :: epochgrid(0:nepochgrid-1) real(4) :: epoch real(4) :: glat, glon, alt, hr, prec, error @@ -35,8 +43,8 @@ program checkapexsh epochgrid = (/1900.0,1905.0,1910.0,1915.0,1920.0,1925.0,1930.0, & 1935.0,1940.0,1945.0,1950.0,1955.0,1960.0,1965.0, & 1970.0,1975.0,1980.0,1985.0,1990.0,1995.0,2000.0, & - 2005.0,2010.0,2015.0,2020.0/) - call makeapxsh(apexshfile, epochgrid, nepochgrid, lmax, nmmax, nmmax) + 2005.0,2010.0,2015.0,2020.0,2025.0/) + call makeapxsh(apexshfile, igrffilein, epochgrid, nepochgrid, lmax, nmmax, nmmax) !HEIGHT PROFILE OF QD COORDINATES epoch = 2005.0 diff --git a/src/fortranapex/fortranapex.pyf b/src/fortranapex/fortranapex.pyf new file mode 100644 index 00000000..926bfd56 --- /dev/null +++ b/src/fortranapex/fortranapex.pyf @@ -0,0 +1,340 @@ +! -*- f90 -*- +! Note: the context of this file is case sensitive. + +python module fortranapex ! in + interface ! in :fortranapex + subroutine apex(date,igrffilein,dlat,dlon,alt,a,alat,alon,bmag,xmag,ymag,zmag,v) ! in :fortranapex:apex.f + real :: date + character*1000 intent(in) :: filename + real :: dlat + real :: dlon + real :: alt + real :: a + real :: alat + real :: alon + real :: bmag + real :: xmag + real :: ymag + real :: zmag + real :: v + real :: colat + real :: elon + real :: vp + real :: ctp + real :: stp + common /dipole/ colat,elon,vp,ctp,stp + end subroutine apex + subroutine linapx(gdlat,glon,alt,a,alat,alon,xmag,ymag,zmag,f) ! in :fortranapex:apex.f + real :: gdlat + real :: glon + real :: alt + real :: a + real :: alat + real :: alon + real :: xmag + real :: ymag + real :: zmag + real :: f + real :: bx + real :: by + real :: bz + real :: bb + real dimension(3,3) :: yapx + real :: colat + real :: elon + real :: vp + real :: ctp + real :: stp + integer :: nstp + real dimension(3) :: y + real dimension(3) :: yp + real :: sgn + real :: ds + common /fldcomd/ bx,by,bz,bb + common /apxin/ yapx + common /dipole/ colat,elon,vp,ctp,stp + common /itra/ nstp,y,yp,sgn,ds + end subroutine linapx + subroutine itrace(iapx) ! in :fortranapex:apex.f + integer :: iapx + real dimension(3,3) :: yapx + real :: bx + real :: by + real :: bz + real :: bb + integer :: nstp + real dimension(3) :: y + real dimension(3) :: yold + real :: sgn + real :: ds + common /apxin/ yapx + common /fldcomd/ bx,by,bz,bb + common /itra/ nstp,y,yold,sgn,ds + end subroutine itrace + subroutine fndapx(alt,zmag,a,alat,alon) ! in :fortranapex:apex.f + real :: alt + real :: zmag + real :: a + real :: alat + real :: alon + real dimension(3,3) :: yapx + real :: colat + real :: elon + real :: vp + real :: ctp + real :: stp + common /apxin/ yapx + common /dipole/ colat,elon,vp,ctp,stp + end subroutine fndapx + subroutine dipapx(gdlat,gdlon,alt,bnorth,beast,bdown,a,alon) ! in :fortranapex:apex.f + real :: gdlat + real :: gdlon + real :: alt + real :: bnorth + real :: beast + real :: bdown + real :: a + real :: alon + real :: colat + real :: elon + real :: vp + real :: ctp + real :: stp + common /dipole/ colat,elon,vp,ctp,stp + end subroutine dipapx + function fint(x1,x2,x3,y1,y2,y3,xfit) ! in :fortranapex:apex.f + real :: x1 + real :: x2 + real :: x3 + real :: y1 + real :: y2 + real :: y3 + real :: xfit + real :: fint + end function fint + module apxshmodule ! in :fortranapex:apexsh.f90 + integer(kind=4) :: nterm + integer(kind=4) :: nmax + integer(kind=4) :: mmax + integer(kind=4) :: lmax + integer(kind=4) :: nepoch + integer(kind=4) :: ntermsh + integer(kind=4) :: vecflag + real(kind=8), allocatable,dimension(:,:,:) :: coeff0 + real(kind=8), allocatable,dimension(:,:) :: qcoeff0 + real(kind=8), allocatable,dimension(:,:) :: gcoeff0 + real(kind=8), allocatable,dimension(:) :: xqcoeff + real(kind=8), allocatable,dimension(:) :: yqcoeff + real(kind=8), allocatable,dimension(:) :: zqcoeff + real(kind=8), allocatable,dimension(:) :: dxqdrhocoeff + real(kind=8), allocatable,dimension(:) :: dyqdrhocoeff + real(kind=8), allocatable,dimension(:) :: dzqdrhocoeff + real(kind=8), allocatable,dimension(:) :: xgcoeff + real(kind=8), allocatable,dimension(:) :: ygcoeff + real(kind=8), allocatable,dimension(:) :: zgcoeff + real(kind=8), allocatable,dimension(:) :: sh + real(kind=8), allocatable,dimension(:) :: shgradtheta + real(kind=8), allocatable,dimension(:) :: shgradphi + real(kind=8), allocatable,dimension(:) :: polynomq + real(kind=8), allocatable,dimension(:) :: dpolynomq + real(kind=8), allocatable,dimension(:) :: polynomg + real(kind=8), allocatable,dimension(:,:) :: pbar + real(kind=8), allocatable,dimension(:,:) :: vbar + real(kind=8), allocatable,dimension(:,:) :: wbar + real(kind=4), allocatable,dimension(:) :: epochgrid + real(kind=8) :: h + real(kind=8) :: reph + real(kind=8) :: rho + real(kind=8) :: xq + real(kind=8) :: yq + real(kind=8) :: zq + real(kind=8) :: qlat + real(kind=8) :: qlon + real(kind=8) :: sinqlat + real(kind=8) :: cosqlat + real(kind=8) :: cosqlon + real(kind=8) :: sinqlon + real(kind=8) dimension(3) :: xqgrad + real(kind=8) dimension(3) :: yqgrad + real(kind=8) dimension(3) :: zqgrad + real(kind=8) dimension(3) :: qlatgrad + real(kind=8) dimension(3) :: qlongrad + real(kind=8), parameter,optional :: pi=3.14159265358979323846d0 + real(kind=8), parameter,optional,depend(pi) :: dtor=pi/180d0 + real(kind=8), parameter,optional,depend(pi) :: pid2=pi/2d0 + real(kind=8), parameter,optional,depend(pi) :: twopi=2d0*pi + real(kind=8), parameter,optional :: req=6378.1370d0 + real(kind=8), parameter,optional :: eps=1.d0/298.257223563d0 + real(kind=8), parameter,optional,depend(req,eps) :: re=req*(1-eps/3d0) + real(kind=8), parameter,optional,depend(eps) :: ecc2=0.0066943799901413165 + real(kind=4), parameter,optional :: missing=-9999.0 + character(len=1000) :: datafile + real(kind=4) :: epoch + real(kind=4) :: altlastq + real(kind=4) :: altlastg + logical, optional :: loadflag=.true. + end module apxshmodule + subroutine loadapxsh(datafilenew,epochnew) ! in :fortranapex:apexsh.f90 + use apxshmodule + character*1000 intent(in) :: datafilenew + real(kind=4) intent(in) :: epochnew + end subroutine loadapxsh + subroutine allocatearrays ! in :fortranapex:apexsh.f90 + use apxshmodule + end subroutine allocatearrays + subroutine apxg2q(glat,glon,alt,vecflagin,qlatout,qlonout,f1,f2,f) ! in :fortranapex:apexsh.f90 + use apxshmodule + real(kind=4) intent(in) :: glat + real(kind=4) intent(in) :: glon + real(kind=4) intent(in) :: alt + integer(kind=4) intent(in) :: vecflagin + real(kind=4) intent(out) :: qlatout + real(kind=4) intent(out) :: qlonout + real(kind=4) dimension(2),intent(out) :: f1 + real(kind=4) dimension(2),intent(out) :: f2 + real(kind=4) intent(out) :: f + end subroutine apxg2q + subroutine apxg2all(glat,glon,alt,hr,vecflagin,qlatout,qlonout,mlat,mlon,f1,f2,f,d1,d2,d3,d,e1,e2,e3) ! in :fortranapex:apexsh.f90 + use apxshmodule + real(kind=4) intent(in) :: glat + real(kind=4) intent(in) :: glon + real(kind=4) intent(in) :: alt + real(kind=4) intent(in) :: hr + integer(kind=4) intent(in) :: vecflagin + real(kind=4) intent(out) :: qlatout + real(kind=4) intent(out) :: qlonout + real(kind=4) intent(out) :: mlat + real(kind=4) intent(out) :: mlon + real(kind=4) dimension(2),intent(out) :: f1 + real(kind=4) dimension(2),intent(out) :: f2 + real(kind=4) intent(out) :: f + real(kind=4) dimension(3),intent(out) :: d1 + real(kind=4) dimension(3),intent(out) :: d2 + real(kind=4) dimension(3),intent(out) :: d3 + real(kind=4) intent(out) :: d + real(kind=4) dimension(3),intent(out) :: e1 + real(kind=4) dimension(3),intent(out) :: e2 + real(kind=4) dimension(3),intent(out) :: e3 + end subroutine apxg2all + subroutine apxq2g(qlat0,qlon0,alt,prec,glatout,glonout,error) ! in :fortranapex:apexsh.f90 + use apxshmodule + real(kind=4) intent(in) :: qlat0 + real(kind=4) intent(in) :: qlon0 + real(kind=4) intent(in) :: alt + real(kind=4) intent(in) :: prec + real(kind=4) intent(out) :: glatout + real(kind=4) intent(out) :: glonout + real(kind=4) intent(out) :: error + end subroutine apxq2g + subroutine shcalc(theta,phi) ! in :fortranapex:apexsh.f90 + use apxshmodule + real(kind=8) intent(in) :: theta + real(kind=8) intent(in) :: phi + end subroutine shcalc + module alfbasismodule ! in :fortranapex:apexsh.f90 + integer(kind=4) :: nmax0 + integer(kind=4) :: mmax0 + real(kind=8), allocatable,dimension(:,:) :: anm + real(kind=8), allocatable,dimension(:) :: cm + real(kind=8), allocatable,dimension(:,:) :: bnm + real(kind=8), allocatable,dimension(:,:) :: dnm + real(kind=8), allocatable,dimension(:) :: en + real(kind=8), allocatable,dimension(:) :: marr + real(kind=8), allocatable,dimension(:) :: narr + end module alfbasismodule + subroutine alfbasisinit(nmax0in,mmax0in) ! in :fortranapex:apexsh.f90 + use alfbasismodule + integer(kind=4) intent(in) :: nmax0in + integer(kind=4) intent(in) :: mmax0in + end subroutine alfbasisinit + subroutine alfbasis(nmax,mmax,theta,p,v,w) ! in :fortranapex:apexsh.f90 + use alfbasismodule + integer(kind=4) intent(in) :: nmax + integer(kind=4) intent(in) :: mmax + real(kind=8) intent(in) :: theta + real(kind=8) dimension(nmax + 1,mmax + 1),intent(out),depend(nmax,mmax) :: p + real(kind=8) dimension(nmax + 1,mmax + 1),intent(out),depend(nmax,mmax) :: v + real(kind=8) dimension(nmax + 1,mmax + 1),intent(out),depend(nmax,mmax) :: w + end subroutine alfbasis + subroutine cofrm(date,filename) ! in :fortranapex:magfld.f + use igrf + real(kind=4) intent(in) :: date + character*1000 intent(in) :: filename + integer :: nmax + real dimension(255) :: gb + real dimension(225) :: gv + integer, optional :: ichg=-99999 + common /magcof/ nmax,gb,gv,ichg + end subroutine cofrm + subroutine dypol(colat,elon,vp) ! in :fortranapex:magfld.f + real :: colat + real :: elon + real :: vp + integer :: nmax + real dimension(255) :: gb + real dimension(225) :: gv + integer :: ichg + common /magcof/ nmax,gb,gv,ichg + end subroutine dypol + subroutine feldg(ienty,glat,glon,alt,bnrth,beast,bdown,babs) ! in :fortranapex:magfld.f + integer intent(in) :: ienty + real intent(in) :: glat + real intent(in) :: glon + real intent(in) :: alt + real intent(out) :: bnrth + real intent(out) :: beast + real intent(out) :: bdown + real intent(out) :: babs + integer :: nmax + real dimension(255) :: gb + real dimension(225) :: gv + integer :: ichg + common /magcof/ nmax,gb,gv,ichg + end subroutine feldg + subroutine gd2cart(gdlat,glon,alt,x,y,z) ! in :fortranapex:magfld.f + real :: gdlat + real :: glon + real :: alt + real :: x + real :: y + real :: z + end subroutine gd2cart + subroutine convrt(i,gdlat,alt,x1,x2) ! in :fortranapex:magfld.f + integer :: i + real :: gdlat + real :: alt + real :: x1 + real :: x2 + end subroutine convrt + subroutine makeapxsh(datafilein,igrffilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) ! in :fortranapex:makeapexsh.f90 + use apxshmodule + character*128 intent(in) :: datafilein + character(len=1000), intent(in) :: igrffilein + real(kind=4) dimension(31),intent(in) :: epochgridin + integer(kind=4) intent(in) :: nepochin + integer(kind=4) intent(in) :: lmaxin + integer(kind=4) intent(in) :: mmaxin + integer(kind=4) intent(in) :: nmaxin + integer(kind=4) :: nmaxigrf + real(kind=4) dimension(255) :: gb + common /magcof/ nmaxigrf,gb + end subroutine makeapxsh + subroutine choldc(a,n,np,p) ! in :fortranapex:makeapexsh.f90 + real(kind=8) dimension(np,np),intent(inout) :: a + integer(kind=4) intent(in) :: n + integer(kind=4), optional,intent(in),check(shape(a,0)==np),depend(a) :: np=shape(a,0) + real(kind=8) dimension(n),intent(out),depend(n) :: p + end subroutine choldc + subroutine cholsl(a,n,np,p,b,x) ! in :fortranapex:makeapexsh.f90 + real(kind=8) dimension(np,np),intent(in) :: a + integer(kind=4), optional,intent(in),check(len(p)>=n),depend(p) :: n=len(p) + integer(kind=4), optional,intent(in),check(shape(a,0)==np),depend(a) :: np=shape(a,0) + real(kind=8) dimension(n),intent(in) :: p + real(kind=8) dimension(n),intent(in),depend(n) :: b + real(kind=8) dimension(n),intent(out),depend(n) :: x + end subroutine cholsl + end interface +end python module fortranapex + +! This file was auto-generated with f2py (version:2). +! See http://cens.ioc.ee/projects/f2py2e/ diff --git a/src/fortranapex/igrf.f90 b/src/fortranapex/igrf.f90 new file mode 100644 index 00000000..dcaba09b --- /dev/null +++ b/src/fortranapex/igrf.f90 @@ -0,0 +1,132 @@ +! Uncomment below if using an Intel compiler +! use ifport + +module igrf + + implicit none + +contains + + subroutine read_igrf(filename_in,GYR,HYR,GT,HT,NEPO,NGHT,EPOCH,NMXE) + + implicit none + + real*8,allocatable,intent(inout) :: GYR(:,:,:),HYR(:,:,:) + real*8,allocatable,intent(inout) :: GT(:,:),HT(:,:) + real*4,allocatable,intent(inout) :: EPOCH(:),NMXE(:) + integer, intent(out) :: NEPO,NGHT + character(len=*),intent(in) :: filename_in + + character(len=10000) :: s + character(len=100) :: junk + integer :: state, i, offset, pos,o + integer :: num_sh, L_max + integer :: num_epochs + integer :: l,m,e + real*8,allocatable :: g(:,:) + integer,allocatable :: nm(:,:) + + ! Get number of Gauss coefficients + !num_sh = NGHT-2*sqrt(real(NGHT)) + !write(*,*) num_sh + + ! Open IGRF file + open(unit=100, file=filename_in,status='old',iostat=state) + if (state /= 0) then + stop "File open error" + end if + + ! Skip comment lines + do + read(unit=100,fmt='(A)') s + if (s(1:1) .ne. '#') exit + enddo + + ! Read epochs + num_epochs=count([(s(i:i+3),i=1,len_trim(s))].eq.'IGRF') + num_epochs=count([(s(i:i+3),i=1,len_trim(s))].eq.'DGRF')+num_epochs + allocate(EPOCH(1:num_epochs)) + allocate(NMXE(1:num_epochs)) + + ! Read epochs + read(100,*,iostat=state) s + do i=1,num_epochs + EPOCH(i) = 1900+(i-1)*5.0d0 + enddo + + ! Number of coefficients + do i=1,num_epochs + if (EPOCH(i) .ge. 2000.0d0) then + NMXE(i) = 13 + elseif (EPOCH(i) .ge. 1900.0d0) then + NMXE(i) = 10 + else + write(*,*) 'ERROR: Epoch unavailable!' + exit + endif + enddo + + ! Save file position + offset = ftell(100) + + ! Get the number of lines (coefficients) + num_sh = 0 + do + read(unit=100,fmt=*,iostat=state) + if (state < 0) exit + num_sh = num_sh+1 + enddo + L_max = sqrt(num_sh+1.0d0)-1 + close(100) + + ! Restore file position, must re-open after reaching EOF + open(unit=100, file=filename_in,status='old',iostat=state) + call fseek(100,offset,0,state) + + ! Assign the variables for Gauss coefficients + allocate(g(1:num_sh,1:num_epochs)) + allocate(nm(1:num_sh,2)) + + ! Read coefficients + do i=1,num_sh + read(100,*,iostat=state) s,nm(i,1),nm(i,2),g(i,:) + if (state < 0) exit + enddo + close(100) + + ! Assign the return values + NGHT = (sqrt(num_sh+1.0)+1)**2 + NEPO = num_epochs + + ! Assign the Gauss coefficients + allocate(GYR(NGHT,NGHT,NEPO),GT(NGHT,NGHT)) + allocate(HYR(NGHT,NGHT,NEPO),HT(NGHT,NGHT)) + GYR=0.0d0 + GT =0.0d0 + HYR=0.0d0 + HT =0.0d0 + do e=1,NEPO+1 + do l=1,L_max + if (e .le. NEPO) then + GYR(l+1,1,e) = g(l**2,e) + else + GT(l+1,1) = g(l**2,e) + endif + do m=1,l + if (m .le. l) then + pos = 1 + m*(L_max+2) + l + if (e .le. NEPO) then + GYR(l+1,m+1,e)=g(l**2+2*m-1,e) + HYR(l+1,m+1,e)=g(l**2+2*m ,e) + else + GT(l+1,m+1)=g(l**2+2*m-1,e) + HT(l+1,m+1)=g(l**2+2*m ,e) + endif + endif + enddo + enddo + enddo + return + end subroutine read_igrf + +end module igrf diff --git a/src/fortranapex/magfld.f b/src/fortranapex/magfld.f index a391bbb5..52dda6f1 100644 --- a/src/fortranapex/magfld.f +++ b/src/fortranapex/magfld.f @@ -1,6 +1,6 @@ C FILE NAME: magfld.f - SUBROUTINE COFRM (DATE) + SUBROUTINE COFRM (DATE, FILENAME) C Define the International Geomagnetic Reference Field (IGRF) as a C scalar potential field using a truncated series expansion with C Schmidt semi-normalized associated Legendre functions of degree n and @@ -10,6 +10,7 @@ SUBROUTINE COFRM (DATE) C C INPUTS: C DATE = yyyy.fraction (UT) +C FILENAME = filename for IGRF coefficient file C OUTPUTS (in comnon block MAGCOF): C NMAX = Maximum order of spherical harmonic coefficients used C GB = Coefficients for magnetic field calculation @@ -95,1341 +96,44 @@ SUBROUTINE COFRM (DATE) C C Jan. 2010 (Maute) update with IGRF11 (same instructions as Sep. 2005 C comment - - DOUBLE PRECISION F,F0 +C +C May 2020 (Achim Morschhauser): Update with routine to read +C IGRF coefficients file directly. +C + use igrf +C +c implicit none +C + REAL(4) DATE,DATEL + REAL(4) F,F0 + REAL(4), ALLOCATABLE :: EPOCH(:),NMXE(:) COMMON /MAGCOF/ NMAX,GB(255),GV(225),ICHG DATA ICHG /-99999/ C NEPO = Number of epochs C NGH = Single dimensioned array size of 2D version (GYR or HYR) C NGHT = Single dimensioned array size of 2D version (GT or HT) - PARAMETER (NEPO = 24, NGH = 225*NEPO, NGHT = 225) - DIMENSION GYR(15,15,NEPO), HYR(15,15,NEPO), EPOCH(NEPO), - + GT (15,15), HT (15,15), NMXE(NEPO), - + GY1D(NGH), HY1D(NGH), - + GT1D(NGHT), HT1D(NGHT) - EQUIVALENCE (GYR(1,1,1),GY1D(1)), (HYR(1,1,1),HY1D(1)), - + (GT (1,1), GT1D(1)), (HT (1,1), HT1D(1)) +!! PARAMETER (NEPO = 24, NGH = 225*NEPO, NGHT = 225) + INTEGER NEPO,NGHT,NGH + REAL*8, ALLOCATABLE :: GYR(:,:,:),HYR(:,:,:) + REAL*8, ALLOCATABLE :: GT(:,:),HT(:,:) + + CHARACTER(LEN=1000) :: FILENAME - SAVE DATEL, EPOCH, NMXE, GYR, HYR, GT, HT, GY1D, HY1D, GT1D, HT1D - DATA DATEL /-999./, - + EPOCH / 1900, 1905, 1910, 1915, 1920, 1925, 1930, 1935, 1940, - + 1945, 1950, 1955, 1960, 1965, 1970, 1975, 1980, 1985, - + 1990, 1995, 2000, 2005, 2010, 2015/, - + NMXE / 10, 10, 10, 10, 10, 10, 10, 10, 10, - + 10, 10, 10, 10, 10, 10, 10, 10, 10, - + 10, 10, 13, 13, 13, 13/ - -C g(n,m) for 1900 -C Fields across a line are (degree) n=1,13; lines are (order) m=0,13 as indicated -C in column 6; e.g., for 1965 g(n=3,m=0) = 1297 or g(n=6,m=6) = -111 -C -C 1 2 3 4 5 6 7 8 9 -C 10 11 12 13 (n) - DATA (GY1D(I),I=1,145) /0, - O -31543, -677, 1022, 876, -184, 63, 70, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2298, 2905, -1469, 628, 328, 61, -55, 8, 10, - + -4, 0, 0, 0, 3*0, - 2 924, 1256, 660, 264, -11, 0, -4, 1, - + 2, 0, 0, 0, 4*0, - 3 572, -361, 5, -217, 34, -9, -11, - + -5, 0, 0, 0, 5*0, - 4 134, -86, -58, -41, 1, 12, - + -2, 0, 0, 0, 6*0, - 5 -16, 59, -21, 2, 1, - + 6, 0, 0, 0, 7*0, - 6 -90, 18, -9, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 5, 2, - + 0, 0, 0, 0, 9*0, - 8 8, -1, - + 2, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=146,225) / - + 2, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1905 - DATA (GY1D(I),I=226,370) /0, - O -31464, -728, 1037, 880, -192, 62, 70, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2298, 2928, -1494, 643, 328, 60, -54, 8, 10, - + -4, 0, 0, 0, 3*0, - 2 1041, 1239, 653, 259, -11, 0, -4, 1, - + 2, 0, 0, 0, 4*0, - 3 635, -380, -1, -221, 33, -9, -11, - + -5, 0, 0, 0, 5*0, - 4 146, -93, -57, -41, 1, 12, - + -2, 0, 0, 0, 6*0, - 5 -26, 57, -20, 2, 1, - + 6, 0, 0, 0, 7*0, - 6 -92, 18, -8, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 5, 2, - + 0, 0, 0, 0, 9*0, - 8 8, 0, - + 2, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=371,450) / - + 2, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1910 - DATA (GY1D(I),I=451,595) /0, - O -31354, -769, 1058, 884, -201, 62, 71, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2297, 2948, -1524, 660, 327, 58, -54, 8, 10, - + -4, 0, 0, 0, 3*0, - 2 1176, 1223, 644, 253, -11, 1, -4, 1, - + 2, 0, 0, 0, 4*0, - 3 705, -400, -9, -224, 32, -9, -11, - + -5, 0, 0, 0, 5*0, - 4 160, -102, -54, -40, 1, 12, - + -2, 0, 0, 0, 6*0, - 5 -38, 54, -19, 2, 1, - + 6, 0, 0, 0, 7*0, - 6 -95, 18, -8, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 5, 2, - + 0, 0, 0, 0, 9*0, - 8 8, 0, - + 2, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=596,675) / - + 2, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1915 - DATA (GY1D(I),I=676,820) /0, - O -31212, -802, 1084, 887, -211, 61, 72, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2306, 2956, -1559, 678, 327, 57, -54, 8, 10, - + -4, 0, 0, 0, 3*0, - 2 1309, 1212, 631, 245, -10, 2, -4, 1, - + 2, 0, 0, 0, 4*0, - 3 778, -416, -16, -228, 31, -9, -11, - + -5, 0, 0, 0, 5*0, - 4 178, -111, -51, -38, 2, 12, - + -2, 0, 0, 0, 6*0, - 5 -51, 49, -18, 3, 1, - + 6, 0, 0, 0, 7*0, - 6 -98, 19, -8, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 6, 2, - + 0, 0, 0, 0, 9*0, - 8 8, 0, - + 1, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=821,900) / - + 2, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1920 - DATA (GY1D(I),I=901,1045) /0, - O -31060, -839, 1111, 889, -221, 61, 73, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2317, 2959, -1600, 695, 326, 55, -54, 7, 10, - + -4, 0, 0, 0, 3*0, - 2 1407, 1205, 616, 236, -10, 2, -3, 1, - + 2, 0, 0, 0, 4*0, - 3 839, -424, -23, -233, 29, -9, -11, - + -5, 0, 0, 0, 5*0, - 4 199, -119, -46, -37, 2, 12, - + -2, 0, 0, 0, 6*0, - 5 -62, 44, -16, 4, 1, - + 6, 0, 0, 0, 7*0, - 6 -101, 19, -7, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 6, 2, - + 0, 0, 0, 0, 9*0, - 8 8, 0, - + 1, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=1046,1125) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1925 - DATA (GY1D(I),I=1126,1270) /0, - O -30926, -893, 1140, 891, -230, 61, 73, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2318, 2969, -1645, 711, 326, 54, -54, 7, 10, - + -4, 0, 0, 0, 3*0, - 2 1471, 1202, 601, 226, -9, 3, -3, 1, - + 2, 0, 0, 0, 4*0, - 3 881, -426, -28, -238, 27, -9, -11, - + -5, 0, 0, 0, 5*0, - 4 217, -125, -40, -35, 2, 12, - + -2, 0, 0, 0, 6*0, - 5 -69, 39, -14, 4, 1, - + 6, 0, 0, 0, 7*0, - 6 -103, 19, -7, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 7, 2, - + 0, 0, 0, 0, 9*0, - 8 8, 0, - + 1, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=1271,1350) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1930 - DATA (GY1D(I),I=1351,1495) /0, - O -30805, -951, 1172, 896, -237, 60, 74, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2316, 2980, -1692, 727, 327, 53, -54, 7, 10, - + -4, 0, 0, 0, 3*0, - 2 1517, 1205, 584, 218, -9, 4, -3, 1, - + 2, 0, 0, 0, 4*0, - 3 907, -422, -32, -242, 25, -9, -12, - + -5, 0, 0, 0, 5*0, - 4 234, -131, -32, -34, 2, 12, - + -2, 0, 0, 0, 6*0, - 5 -74, 32, -12, 5, 1, - + 6, 0, 0, 0, 7*0, - 6 -104, 18, -6, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 8, 3, - + 0, 0, 0, 0, 9*0, - 8 8, 0, - + 1, 0, 0, 0, 10*0, - 9 -2/ - DATA (GY1D(I),I=1496,1575) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1935 - DATA (GY1D(I),I=1576,1720) /0, - O -30715, -1018, 1206, 903, -241, 59, 74, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2306, 2984, -1740, 744, 329, 53, -53, 7, 10, - + -4, 0, 0, 0, 3*0, - 2 1550, 1215, 565, 211, -8, 4, -3, 1, - + 2, 0, 0, 0, 4*0, - 3 918, -415, -33, -246, 23, -9, -12, - + -5, 0, 0, 0, 5*0, - 4 249, -136, -25, -33, 1, 11, - + -2, 0, 0, 0, 6*0, - 5 -76, 25, -11, 6, 1, - + 6, 0, 0, 0, 7*0, - 6 -106, 18, -6, -2, - + 4, 0, 0, 0, 8*0, - 7 6, 8, 3, - + 0, 0, 0, 0, 9*0, - 8 7, 0, - + 2, 0, 0, 0, 10*0, - 9 -2/ - DATA (GY1D(I),I=1721,1800) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1940 - DATA (GY1D(I),I=1801,1945) /0, - O -30654, -1106, 1240, 914, -241, 57, 74, 11, 8, - + -3, 0, 0, 0, 2*0, - 1 -2292, 2981, -1790, 762, 334, 54, -53, 7, 10, - + -4, 0, 0, 0, 3*0, - 2 1566, 1232, 550, 208, -7, 4, -3, 1, - + 2, 0, 0, 0, 4*0, - 3 916, -405, -33, -249, 20, -10, -12, - + -5, 0, 0, 0, 5*0, - 4 265, -141, -18, -31, 1, 11, - + -2, 0, 0, 0, 6*0, - 5 -76, 18, -9, 6, 1, - + 6, 0, 0, 0, 7*0, - 6 -107, 17, -5, -2, - + 4, 0, 0, 0, 8*0, - 7 5, 9, 3, - + 0, 0, 0, 0, 9*0, - 8 7, 1, - + 2, 0, 0, 0, 10*0, - 9 -2/ - DATA (GY1D(I),I=1946,2025) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1945 - DATA (GY1D(I),I=2026,2170) /0, - O -30594, -1244, 1282, 944, -253, 59, 70, 13, 5, - + -3, 0, 0, 0, 2*0, - 1 -2285, 2990, -1834, 776, 346, 57, -40, 7, -21, - + 11, 0, 0, 0, 3*0, - 2 1578, 1255, 544, 194, 6, 0, -8, 1, - + 1, 0, 0, 0, 4*0, - 3 913, -421, -20, -246, 0, -5, -11, - + 2, 0, 0, 0, 5*0, - 4 304, -142, -25, -29, 9, 3, - + -5, 0, 0, 0, 6*0, - 5 -82, 21, -10, 7, 16, - + -1, 0, 0, 0, 7*0, - 6 -104, 15, -10, -3, - + 8, 0, 0, 0, 8*0, - 7 29, 7, -4, - + -1, 0, 0, 0, 9*0, - 8 2, -3, - + -3, 0, 0, 0, 10*0, - 9 -4/ - DATA (GY1D(I),I=2171,2250) / - + 5, 0, 0, 0, 11*0, - O -2, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1950 - DATA (GY1D(I),I=2251,2395) /0, - O -30554, -1341, 1297, 954, -240, 54, 65, 22, 3, - + -8, 0, 0, 0, 2*0, - 1 -2250, 2998, -1889, 792, 349, 57, -55, 15, -7, - + 4, 0, 0, 0, 3*0, - 2 1576, 1274, 528, 211, 4, 2, -4, -1, - + -1, 0, 0, 0, 4*0, - 3 896, -408, -20, -247, 1, -1, -25, - + 13, 0, 0, 0, 5*0, - 4 303, -147, -16, -40, 11, 10, - + -4, 0, 0, 0, 6*0, - 5 -76, 12, -7, 15, 5, - + 4, 0, 0, 0, 7*0, - 6 -105, 5, -13, -5, - + 12, 0, 0, 0, 8*0, - 7 19, 5, -2, - + 3, 0, 0, 0, 9*0, - 8 -1, 3, - + 2, 0, 0, 0, 10*0, - 9 8/ - DATA (GY1D(I),I=2396,2475) / - + 10, 0, 0, 0, 11*0, - O 3, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1955 - DATA (GY1D(I),I=2476,2620) /0, - O -30500, -1440, 1302, 958, -229, 47, 65, 11, 4, - + -3, 0, 0, 0, 2*0, - 1 -2215, 3003, -1944, 796, 360, 57, -56, 9, 9, - + -5, 0, 0, 0, 3*0, - 2 1581, 1288, 510, 230, 3, 2, -6, -4, - + -1, 0, 0, 0, 4*0, - 3 882, -397, -23, -247, 10, -14, -5, - + 2, 0, 0, 0, 5*0, - 4 290, -152, -8, -32, 6, 2, - + -3, 0, 0, 0, 6*0, - 5 -69, 7, -11, 10, 4, - + 7, 0, 0, 0, 7*0, - 6 -107, 9, -7, 1, - + 4, 0, 0, 0, 8*0, - 7 18, 6, 2, - + -2, 0, 0, 0, 9*0, - 8 9, 2, - + 6, 0, 0, 0, 10*0, - 9 5/ - DATA (GY1D(I),I=2621,2700) / - + -2, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1960 - DATA (GY1D(I),I=2701,2845) /0, - O -30421, -1555, 1302, 957, -222, 46, 67, 15, 4, - + 1, 0, 0, 0, 2*0, - 1 -2169, 3002, -1992, 800, 362, 58, -56, 6, 6, - + -3, 0, 0, 0, 3*0, - 2 1590, 1289, 504, 242, 1, 5, -4, 0, - + 4, 0, 0, 0, 4*0, - 3 878, -394, -26, -237, 15, -11, -9, - + 0, 0, 0, 0, 5*0, - 4 269, -156, -1, -32, 2, 1, - + -1, 0, 0, 0, 6*0, - 5 -63, -2, -7, 10, 4, - + 4, 0, 0, 0, 7*0, - 6 -113, 17, -5, -1, - + 6, 0, 0, 0, 8*0, - 7 8, 10, -2, - + 1, 0, 0, 0, 9*0, - 8 8, 3, - + -1, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=2846,2925) / - + 2, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1965 - DATA (GY1D(I),I=2926,3070) /0, - O -30334, -1662, 1297, 957, -219, 45, 75, 13, 8, - + -2, 0, 0, 0, 2*0, - 1 -2119, 2997, -2038, 804, 358, 61, -57, 5, 10, - + -3, 0, 0, 0, 3*0, - 2 1594, 1292, 479, 254, 8, 4, -4, 2, - + 2, 0, 0, 0, 4*0, - 3 856, -390, -31, -228, 13, -14, -13, - + -5, 0, 0, 0, 5*0, - 4 252, -157, 4, -26, 0, 10, - + -2, 0, 0, 0, 6*0, - 5 -62, 1, -6, 8, -1, - + 4, 0, 0, 0, 7*0, - 6 -111, 13, -1, -1, - + 4, 0, 0, 0, 8*0, - 7 1, 11, 5, - + 0, 0, 0, 0, 9*0, - 8 4, 1, - + 2, 0, 0, 0, 10*0, - 9 -2/ - DATA (GY1D(I),I=3071,3150) / - + 2, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1970 - DATA (GY1D(I),I=3151,3295) /0, - O -30220, -1781, 1287, 952, -216, 43, 72, 14, 8, - + -3, 0, 0, 0, 2*0, - 1 -2068, 3000, -2091, 800, 359, 64, -57, 6, 10, - + -3, 0, 0, 0, 3*0, - 2 1611, 1278, 461, 262, 15, 1, -2, 2, - + 2, 0, 0, 0, 4*0, - 3 838, -395, -42, -212, 14, -13, -12, - + -5, 0, 0, 0, 5*0, - 4 234, -160, 2, -22, -3, 10, - + -1, 0, 0, 0, 6*0, - 5 -56, 3, -2, 5, -1, - + 6, 0, 0, 0, 7*0, - 6 -112, 13, 0, 0, - + 4, 0, 0, 0, 8*0, - 7 -2, 11, 3, - + 1, 0, 0, 0, 9*0, - 8 3, 1, - + 0, 0, 0, 0, 10*0, - 9 -1/ - DATA (GY1D(I),I=3296,3375) / - + 3, 0, 0, 0, 11*0, - O -1, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1975 - DATA (GY1D(I),I=3376,3520) /0, - O -30100, -1902, 1276, 946, -218, 45, 71, 14, 7, - + -3, 0, 0, 0, 2*0, - 1 -2013, 3010, -2144, 791, 356, 66, -56, 6, 10, - + -3, 0, 0, 0, 3*0, - 2 1632, 1260, 438, 264, 28, 1, -1, 2, - + 2, 0, 0, 0, 4*0, - 3 830, -405, -59, -198, 16, -12, -12, - + -5, 0, 0, 0, 5*0, - 4 216, -159, 1, -14, -8, 10, - + -2, 0, 0, 0, 6*0, - 5 -49, 6, 0, 4, -1, - + 5, 0, 0, 0, 7*0, - 6 -111, 12, 0, -1, - + 4, 0, 0, 0, 8*0, - 7 -5, 10, 4, - + 1, 0, 0, 0, 9*0, - 8 1, 1, - + 0, 0, 0, 0, 10*0, - 9 -2/ - DATA (GY1D(I),I=3521,3600) / - + 3, 0, 0, 0, 11*0, - O -1, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1980 - DATA (GY1D(I),I=3601,3745) /0, - O -29992, -1997, 1281, 938, -218, 48, 72, 18, 5, - + -4, 0, 0, 0, 2*0, - 1 -1956, 3027, -2180, 782, 357, 66, -59, 6, 10, - + -4, 0, 0, 0, 3*0, - 2 1663, 1251, 398, 261, 42, 2, 0, 1, - + 2, 0, 0, 0, 4*0, - 3 833, -419, -74, -192, 21, -11, -12, - + -5, 0, 0, 0, 5*0, - 4 199, -162, 4, -12, -7, 9, - + -2, 0, 0, 0, 6*0, - 5 -48, 14, 1, 4, -3, - + 5, 0, 0, 0, 7*0, - 6 -108, 11, 3, -1, - + 3, 0, 0, 0, 8*0, - 7 -2, 6, 7, - + 1, 0, 0, 0, 9*0, - 8 -1, 2, - + 2, 0, 0, 0, 10*0, - 9 -5/ - DATA (GY1D(I),I=3746,3825) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1985 - DATA (GY1D(I),I=3826,3970) /0, - O -29873, -2072, 1296, 936, -214, 53, 74, 21, 5, - + -4, 0, 0, 0, 2*0, - 1 -1905, 3044, -2208, 780, 355, 65, -62, 6, 10, - + -4, 0, 0, 0, 3*0, - 2 1687, 1247, 361, 253, 51, 3, 0, 1, - + 3, 0, 0, 0, 4*0, - 3 829, -424, -93, -185, 24, -11, -12, - + -5, 0, 0, 0, 5*0, - 4 170, -164, 4, -6, -9, 9, - + -2, 0, 0, 0, 6*0, - 5 -46, 16, 4, 4, -3, - + 5, 0, 0, 0, 7*0, - 6 -102, 10, 4, -1, - + 3, 0, 0, 0, 8*0, - 7 0, 4, 7, - + 1, 0, 0, 0, 9*0, - 8 -4, 1, - + 2, 0, 0, 0, 10*0, - 9 -5/ - DATA (GY1D(I),I=3971,4050) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1990 - DATA (GY1D(I),I=4051,4195) /0, - O -29775, -2131, 1314, 939, -214, 61, 77, 23, 4, - + -3, 0, 0, 0, 2*0, - 1 -1848, 3059, -2239, 780, 353, 65, -64, 5, 9, - + -4, 0, 0, 0, 3*0, - 2 1686, 1248, 325, 245, 59, 2, -1, 1, - + 2, 0, 0, 0, 4*0, - 3 802, -423, -109, -178, 26, -10, -12, - + -5, 0, 0, 0, 5*0, - 4 141, -165, 3, -1, -12, 9, - + -2, 0, 0, 0, 6*0, - 5 -36, 18, 5, 3, -4, - + 4, 0, 0, 0, 7*0, - 6 -96, 9, 4, -2, - + 3, 0, 0, 0, 8*0, - 7 0, 2, 7, - + 1, 0, 0, 0, 9*0, - 8 -6, 1, - + 3, 0, 0, 0, 10*0, - 9 -6/ - DATA (GY1D(I),I=4196,4275) / - + 3, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 1995 - DATA (GY1D(I),I=4276,4420) /0, - O -29692, -2200, 1335, 940, -214, 68, 77, 25, 4, - + -3, 0, 0, 0, 2*0, - 1 -1784, 3070, -2267, 780, 352, 67, -72, 6, 9, - + -6, 0, 0, 0, 3*0, - 2 1681, 1249, 290, 235, 68, 1, -6, 3, - + 2, 0, 0, 0, 4*0, - 3 759, -418, -118, -170, 28, -9, -10, - + -4, 0, 0, 0, 5*0, - 4 122, -166, -1, 5, -14, 8, - + -1, 0, 0, 0, 6*0, - 5 -17, 19, 4, 9, -8, - + 4, 0, 0, 0, 7*0, - 6 -93, 8, 6, -1, - + 2, 0, 0, 0, 8*0, - 7 -2, -5, 10, - + 2, 0, 0, 0, 9*0, - 8 -7, -2, - + 5, 0, 0, 0, 10*0, - 9 -8/ - DATA (GY1D(I),I=4421,4500) / - + 1, 0, 0, 0, 11*0, - O 0, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C g(n,m) for 2000 - DATA (GY1D(I),I=4501,4645) /0, - O-29619.4,-2267.7,1339.6, 932.3,-218.8, 72.3, 79.0, 24.4, 5.0, - + -2.6, 2.7, -2.2, -0.2, 2*0, - 1 -1728.2, 3068.4,-2288.0, 786.8, 351.4, 68.2, -74.0, 6.6, 9.4, - + -6.0, -1.7, -0.3, -0.9, 3*0, - 2 1670.9,1252.1, 250.0, 222.3, 74.2, 0.0, -9.2, 3.0, - + 1.7, -1.9, 0.2, 0.3, 4*0, - 3 714.5,-403.0,-130.4,-160.9, 33.3, -7.9, -8.4, - + -3.1, 1.5, 0.9, 0.1, 5*0, - 4 111.3,-168.6, -5.9, 9.1, -16.6, 6.3, - + -0.5, -0.1, -0.2, -0.4, 6*0, - 5 -12.9, 16.9, 6.9, 9.1, -8.9, - + 3.7, 0.1, 0.9, 1.3, 7*0, - 6 -90.4, 7.3, 7.0, -1.5, - + 1.0, -0.7, -0.5, -0.4, 8*0, - 7 -1.2, -7.9, 9.3, - + 2.0, 0.7, 0.3, 0.7, 9*0, - 8 -7.0, -4.3, - + 4.2, 1.7, -0.3, -0.4, 10*0, - 9 -8.2/ - DATA (GY1D(I),I=4646,4725) / - + 0.3, 0.1, -0.4, 0.3, 11*0, - O -1.1, 1.2, -0.1, -0.1, 12*0, - 1 4.0, -0.2, 0.4, 13*0, - 2 -0.4, 0.0, 14*0, - 3 0.1, 16*0/ -C g(n,m) for 2005 - DATA (GY1D(I),I=4726,4870) /0, - O-29554.63,-2337.24,1336.30,920.55,-227.00, 73.60, 79.88, 24.80, - + 5.58, -2.17, 2.95, -2.15, -0.16, 2*0, - 1-1669.05,3047.69,-2305.83,797.96,354.41, 69.56,-74.46, 7.62, - + 9.76, -6.12, -1.60, -0.29, -0.88, 3*0, - 2 1657.76,1246.39,210.65,208.95, 76.74, -1.65,-11.73, - + 3.58, 1.42, -1.88, 0.21, 0.30, 4*0, - 3 672.51,-379.86,-136.54,-151.34, 38.73, -6.88, - + -6.94, -2.35, 1.44, 0.89, 0.28, 5*0, - 4 100.00,-168.05,-14.58, 12.30,-18.11, 5.01, - + -0.15, -0.31, -0.38, -0.43, 6*0, - 5 -13.55, 14.58, 9.37, 10.17,-10.76, - + 3.06, 0.29, 0.96, 1.18, 7*0, - 6 -86.36, 5.42, 9.36, -1.25, - + 0.29, -0.79, -0.30, -0.37, 8*0, - 7 1.94,-11.25, 8.76, - + 2.06, 0.53, 0.46, 0.75, 9*0, - 8 -4.87, -6.66, - + 3.77, 1.80, -0.35, -0.26, 10*0, - 9 -9.22/ - DATA (GY1D(I),I=4871,4950) / - + -0.21, 0.16, -0.36, 0.35, 11*0, - O -2.09, 0.96, 0.08, -0.05, 12*0, - 1 3.99, -0.49, 0.41, 13*0, - 2 -0.08, -0.10, 14*0, - 3 -0.18, 16*0/ -C g(n,m) for 2010 - DATA (GY1D(I),I=4951,5095) /0, - O -29496.57,-2396.06,1339.85,912.66, -230.87,72.78,80.44,24.41,5.5, - + -1.94, 3.05, -2.12, -0.09, 2*0, - 1 -1586.42, 3026.34, -2326.54,808.97,357.29,68.69, -75.0,8.21,9.45, - + -6.24, -1.48, -0.21, -0.89, 3*0, - 2 1668.17, 1232.1, 166.58, 200.26, 75.92, -4.55, -14.5, 3.45, - + 0.89, -2.03, 0.3, 0.31, 4*0, - 3 633.73, -356.83, -141.05, -141.4, 45.24, -5.59, -5.27, - + -1.07, 1.65, 1.04, 0.42, 5*0, - 4 89.4, -163.17, -22.83, 14.0, -19.34, 3.13, - + -0.16, -0.51, -0.63, -0.45, 6*0, - 5 -8.03, 13.1, 10.46, 11.61, -12.38, - + 2.45, 0.54, 0.95, 1.08, 7*0, - 6 -78.09, 1.64, 10.85, -0.76, - + -0.33, -0.79, -0.11, -0.31, 8*0, - 7 4.92, -14.05, 8.43, - + 2.13, 0.37, 0.52, 0.78, 9*0, - 8 -3.54, -8.42, - + 3.09, 1.79, -0.39, -0.18, 10*0, - 9 -10.08/ - DATA (GY1D(I),I=5096,5175) / - + -1.03, 0.12, -0.37, 0.38, 11*0, - O -2.8, 0.75, 0.21, 0.02, 12*0, - 1 3.75, -0.77, 0.42, 13*0, - 2 0.04, -0.26, 14*0, - 3 -0.26, 16*0/ -C g(n,m) for 2015 - DATA (GY1D(I),I=5176,5320) /0, - O -29442.0, -2445.1, 1350.7, 907.6, -232.6, 70.0, 81.6, 24.2, 5.4, - + -1.9, 3.1, -1.9, 0.0, 2*0, - 1 -1501.0, 3012.9, -2352.3, 813.7, 360.1, 67.7, -76.1, 8.8, 8.8, - + -6.3, -1.5, -0.2, -0.9, 3*0, - 2 1676.7, 1225.6, 120.4, 192.4, 72.7, -6.8, -16.9, 3.1, - + 0.1, -2.3, 0.4, 0.4, 4*0, - 3 582.0, -334.9, -140.9, -129.9, 51.8, -3.2, -3.3, - + 0.5, 2.0, 1.2, 0.5, 5*0, - 4 70.4, -157.5, -28.9, 15.0, -20.6, 0.7, - + -0.5, -0.8, -0.8, -0.5, 6*0, - 5 4.1, 13.2, 9.4, 13.4, -13.3, - + 1.8, 0.6, 0.9, 1.0, 7*0, - 6 -70.9, -2.8, 11.7, -0.1, - + -0.7, -0.7, 0.1, -0.2, 8*0, - 7 6.8, -15.9, 8.7, - + 2.1, 0.2, 0.5, 0.8, 9*0, - 8 -2.0, -9.1, - + 2.4, 1.7, -0.3, -0.1, 10*0, - 9 -10.5/ - DATA (GY1D(I),I=5321,5400) / - + -1.8, -0.2, -0.4, 0.3, 11*0, - O -3.6, 0.4, 0.2, 0.1, 12*0, - 1 3.5, -0.9, 0.5, 13*0, - 2 0.0, -0.4, 14*0, - 3 -0.3, 16*0/ -C h(n,m) for 1900 - DATA (HY1D(I),I=1,145) /16*0, - 1 5922, -1061, -330, 195, -210, -9, -45, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 1121, 3, -69, 53, 83, -13, -14, 14, - + 1, 0, 0, 0, 4*0, - 3 523, -210, -33, 2, -10, 7, 5, - + 2, 0, 0, 0, 5*0, - 4 -75, -124, -35, -1, -13, -3, - + 6, 0, 0, 0, 6*0, - 5 3, 36, 28, 5, -2, - + -4, 0, 0, 0, 7*0, - 6 -69, -12, 16, 8, - + 0, 0, 0, 0, 8*0, - 7 -22, -5, 10, - + -2, 0, 0, 0, 9*0, - 8 -18, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=146,225) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1905 - DATA (HY1D(I),I=226,370) /16*0, - 1 5909, -1086, -357, 203, -193, -7, -46, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 1065, 34, -77, 56, 86, -14, -15, 14, - + 1, 0, 0, 0, 4*0, - 3 480, -201, -32, 4, -11, 7, 5, - + 2, 0, 0, 0, 5*0, - 4 -65, -125, -32, 0, -13, -3, - + 6, 0, 0, 0, 6*0, - 5 11, 32, 28, 5, -2, - + -4, 0, 0, 0, 7*0, - 6 -67, -12, 16, 8, - + 0, 0, 0, 0, 8*0, - 7 -22, -5, 10, - + -2, 0, 0, 0, 9*0, - 8 -18, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=371,450) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1910 - DATA (HY1D(I),I=451,595) /16*0, - 1 5898, -1128, -389, 211, -172, -5, -47, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 1000, 62, -90, 57, 89, -14, -15, 14, - + 1, 0, 0, 0, 4*0, - 3 425, -189, -33, 5, -12, 6, 5, - + 2, 0, 0, 0, 5*0, - 4 -55, -126, -29, 1, -13, -3, - + 6, 0, 0, 0, 6*0, - 5 21, 28, 28, 5, -2, - + -4, 0, 0, 0, 7*0, - 6 -65, -13, 16, 8, - + 0, 0, 0, 0, 8*0, - 7 -22, -5, 10, - + -2, 0, 0, 0, 9*0, - 8 -18, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=596,675) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1915 - DATA (HY1D(I),I=676,820) /16*0, - 1 5875, -1191, -421, 218, -148, -2, -48, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 917, 84, -109, 58, 93, -14, -15, 14, - + 1, 0, 0, 0, 4*0, - 3 360, -173, -34, 8, -12, 6, 5, - + 2, 0, 0, 0, 5*0, - 4 -51, -126, -26, 2, -13, -3, - + 6, 0, 0, 0, 6*0, - 5 32, 23, 28, 5, -2, - + -4, 0, 0, 0, 7*0, - 6 -62, -15, 16, 8, - + 0, 0, 0, 0, 8*0, - 7 -22, -5, 10, - + -2, 0, 0, 0, 9*0, - 8 -18, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=821,900) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1920 - DATA (HY1D(I),I=901,1045) /16*0, - 1 5845, -1259, -445, 220, -122, 0, -49, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 823, 103, -134, 58, 96, -14, -15, 14, - + 1, 0, 0, 0, 4*0, - 3 293, -153, -38, 11, -13, 6, 5, - + 2, 0, 0, 0, 5*0, - 4 -57, -125, -22, 4, -14, -3, - + 6, 0, 0, 0, 6*0, - 5 43, 18, 28, 5, -2, - + -4, 0, 0, 0, 7*0, - 6 -57, -16, 17, 9, - + 0, 0, 0, 0, 8*0, - 7 -22, -5, 10, - + -2, 0, 0, 0, 9*0, - 8 -19, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=1046,1125) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1925 - DATA (HY1D(I),I=1126,1270) /16*0, - 1 5817, -1334, -462, 216, -96, 3, -50, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 728, 119, -163, 58, 99, -14, -15, 14, - + 1, 0, 0, 0, 4*0, - 3 229, -130, -44, 14, -14, 6, 5, - + 2, 0, 0, 0, 5*0, - 4 -70, -122, -18, 5, -14, -3, - + 6, 0, 0, 0, 6*0, - 5 51, 13, 29, 5, -2, - + -4, 0, 0, 0, 7*0, - 6 -52, -17, 17, 9, - + 0, 0, 0, 0, 8*0, - 7 -21, -5, 10, - + -2, 0, 0, 0, 9*0, - 8 -19, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=1271,1350) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1930 - DATA (HY1D(I),I=1351,1495) /16*0, - 1 5808, -1424, -480, 205, -72, 4, -51, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 644, 133, -195, 60, 102, -15, -15, 14, - + 1, 0, 0, 0, 4*0, - 3 166, -109, -53, 19, -14, 5, 5, - + 2, 0, 0, 0, 5*0, - 4 -90, -118, -16, 6, -14, -3, - + 6, 0, 0, 0, 6*0, - 5 58, 8, 29, 5, -2, - + -4, 0, 0, 0, 7*0, - 6 -46, -18, 18, 9, - + 0, 0, 0, 0, 8*0, - 7 -20, -5, 10, - + -2, 0, 0, 0, 9*0, - 8 -19, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=1496,1575) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1935 - DATA (HY1D(I),I=1576,1720) /16*0, - 1 5812, -1520, -494, 188, -51, 4, -52, 8, -20, - + 2, 0, 0, 0, 3*0, - 2 586, 146, -226, 64, 104, -17, -15, 15, - + 1, 0, 0, 0, 4*0, - 3 101, -90, -64, 25, -14, 5, 5, - + 2, 0, 0, 0, 5*0, - 4 -114, -115, -15, 7, -15, -3, - + 6, 0, 0, 0, 6*0, - 5 64, 4, 29, 5, -3, - + -4, 0, 0, 0, 7*0, - 6 -40, -19, 18, 9, - + 0, 0, 0, 0, 8*0, - 7 -19, -5, 11, - + -1, 0, 0, 0, 9*0, - 8 -19, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=1721,1800) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1940 - DATA (HY1D(I),I=1801,1945) /16*0, - 1 5821, -1614, -499, 169, -33, 4, -52, 8, -21, - + 2, 0, 0, 0, 3*0, - 2 528, 163, -252, 71, 105, -18, -14, 15, - + 1, 0, 0, 0, 4*0, - 3 43, -72, -75, 33, -14, 5, 5, - + 2, 0, 0, 0, 5*0, - 4 -141, -113, -15, 7, -15, -3, - + 6, 0, 0, 0, 6*0, - 5 69, 0, 29, 5, -3, - + -4, 0, 0, 0, 7*0, - 6 -33, -20, 19, 9, - + 0, 0, 0, 0, 8*0, - 7 -19, -5, 11, - + -1, 0, 0, 0, 9*0, - 8 -19, -2, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=1946,2025) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1945 - DATA (HY1D(I),I=2026,2170) /16*0, - 1 5810, -1702, -499, 144, -12, 6, -45, 12, -27, - + 5, 0, 0, 0, 3*0, - 2 477, 186, -276, 95, 100, -18, -21, 17, - + 1, 0, 0, 0, 4*0, - 3 -11, -55, -67, 16, 2, -12, 29, - + -20, 0, 0, 0, 5*0, - 4 -178, -119, -9, 6, -7, -9, - + -1, 0, 0, 0, 6*0, - 5 82, -16, 28, 2, 4, - + -6, 0, 0, 0, 7*0, - 6 -39, -17, 18, 9, - + 6, 0, 0, 0, 8*0, - 7 -22, 3, 6, - + -4, 0, 0, 0, 9*0, - 8 -11, 1, - + -2, 0, 0, 0, 10*0, - 9 8/ - DATA (HY1D(I),I=2171,2250) / - + 0, 0, 0, 0, 11*0, - O -2, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1950 - DATA (HY1D(I),I=2251,2395) /16*0, - 1 5815, -1810, -476, 136, 3, -1, -35, 5, -24, - + 13, 0, 0, 0, 3*0, - 2 381, 206, -278, 103, 99, -17, -22, 19, - + -2, 0, 0, 0, 4*0, - 3 -46, -37, -87, 33, 0, 0, 12, - + -10, 0, 0, 0, 5*0, - 4 -210, -122, -12, 10, -21, 2, - + 2, 0, 0, 0, 6*0, - 5 80, -12, 36, -8, 2, - + -3, 0, 0, 0, 7*0, - 6 -30, -18, 17, 8, - + 6, 0, 0, 0, 8*0, - 7 -16, -4, 8, - + -3, 0, 0, 0, 9*0, - 8 -17, -11, - + 6, 0, 0, 0, 10*0, - 9 -7/ - DATA (HY1D(I),I=2396,2475) / - + 11, 0, 0, 0, 11*0, - O 8, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1955 - DATA (HY1D(I),I=2476,2620) /16*0, - 1 5820, -1898, -462, 133, 15, -9, -50, 10, -11, - + -4, 0, 0, 0, 3*0, - 2 291, 216, -274, 110, 96, -24, -15, 12, - + 0, 0, 0, 0, 4*0, - 3 -83, -23, -98, 48, -4, 5, 7, - + -8, 0, 0, 0, 5*0, - 4 -230, -121, -16, 8, -23, 6, - + -2, 0, 0, 0, 6*0, - 5 78, -12, 28, 3, -2, - + -4, 0, 0, 0, 7*0, - 6 -24, -20, 23, 10, - + 1, 0, 0, 0, 8*0, - 7 -18, -4, 7, - + -3, 0, 0, 0, 9*0, - 8 -13, -6, - + 7, 0, 0, 0, 10*0, - 9 5/ - DATA (HY1D(I),I=2621,2700) / - + -1, 0, 0, 0, 11*0, - O -3, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1960 - DATA (HY1D(I),I=2701,2845) /16*0, - 1 5791, -1967, -414, 135, 16, -10, -55, 11, -18, - + 4, 0, 0, 0, 3*0, - 2 206, 224, -278, 125, 99, -28, -14, 12, - + 1, 0, 0, 0, 4*0, - 3 -130, 3, -117, 60, -6, 7, 2, - + 0, 0, 0, 0, 5*0, - 4 -255, -114, -20, 7, -18, 0, - + 2, 0, 0, 0, 6*0, - 5 81, -11, 23, 4, -3, - + -5, 0, 0, 0, 7*0, - 6 -17, -18, 23, 9, - + 1, 0, 0, 0, 8*0, - 7 -17, 1, 8, - + -1, 0, 0, 0, 9*0, - 8 -20, 0, - + 6, 0, 0, 0, 10*0, - 9 5/ - DATA (HY1D(I),I=2846,2925) / - + 0, 0, 0, 0, 11*0, - O -7, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1965 - DATA (HY1D(I),I=2926,3070) /16*0, - 1 5776, -2016, -404, 148, 19, -11, -61, 7, -22, - + 2, 0, 0, 0, 3*0, - 2 114, 240, -269, 128, 100, -27, -12, 15, - + 1, 0, 0, 0, 4*0, - 3 -165, 13, -126, 68, -2, 9, 7, - + 2, 0, 0, 0, 5*0, - 4 -269, -97, -32, 6, -16, -4, - + 6, 0, 0, 0, 6*0, - 5 81, -8, 26, 4, -5, - + -4, 0, 0, 0, 7*0, - 6 -7, -23, 24, 10, - + 0, 0, 0, 0, 8*0, - 7 -12, -3, 10, - + -2, 0, 0, 0, 9*0, - 8 -17, -4, - + 3, 0, 0, 0, 10*0, - 9 1/ - DATA (HY1D(I),I=3071,3150) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1970 - DATA (HY1D(I),I=3151,3295) /16*0, - 1 5737, -2047, -366, 167, 26, -12, -70, 7, -21, - + 1, 0, 0, 0, 3*0, - 2 25, 251, -266, 139, 100, -27, -15, 16, - + 1, 0, 0, 0, 4*0, - 3 -196, 26, -139, 72, -4, 6, 6, - + 3, 0, 0, 0, 5*0, - 4 -279, -91, -37, 8, -17, -4, - + 4, 0, 0, 0, 6*0, - 5 83, -6, 23, 6, -5, - + -4, 0, 0, 0, 7*0, - 6 1, -23, 21, 10, - + 0, 0, 0, 0, 8*0, - 7 -11, -6, 11, - + -1, 0, 0, 0, 9*0, - 8 -16, -2, - + 3, 0, 0, 0, 10*0, - 9 1/ - DATA (HY1D(I),I=3296,3375) / - + 1, 0, 0, 0, 11*0, - O -4, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1975 - DATA (HY1D(I),I=3376,3520) /16*0, - 1 5675, -2067, -333, 191, 31, -13, -77, 6, -21, - + 1, 0, 0, 0, 3*0, - 2 -68, 262, -265, 148, 99, -26, -16, 16, - + 1, 0, 0, 0, 4*0, - 3 -223, 39, -152, 75, -5, 4, 7, - + 3, 0, 0, 0, 5*0, - 4 -288, -83, -41, 10, -19, -4, - + 4, 0, 0, 0, 6*0, - 5 88, -4, 22, 6, -5, - + -4, 0, 0, 0, 7*0, - 6 11, -23, 18, 10, - + -1, 0, 0, 0, 8*0, - 7 -12, -10, 11, - + -1, 0, 0, 0, 9*0, - 8 -17, -3, - + 3, 0, 0, 0, 10*0, - 9 1/ - DATA (HY1D(I),I=3521,3600) / - + 1, 0, 0, 0, 11*0, - O -5, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1980 - DATA (HY1D(I),I=3601,3745) /16*0, - 1 5604, -2129, -336, 212, 46, -15, -82, 7, -21, - + 1, 0, 0, 0, 3*0, - 2 -200, 271, -257, 150, 93, -27, -18, 16, - + 0, 0, 0, 0, 4*0, - 3 -252, 53, -151, 71, -5, 4, 9, - + 3, 0, 0, 0, 5*0, - 4 -297, -78, -43, 16, -22, -5, - + 6, 0, 0, 0, 6*0, - 5 92, -2, 18, 9, -6, - + -4, 0, 0, 0, 7*0, - 6 17, -23, 16, 9, - + 0, 0, 0, 0, 8*0, - 7 -10, -13, 10, - + -1, 0, 0, 0, 9*0, - 8 -15, -6, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=3746,3825) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1985 - DATA (HY1D(I),I=3826,3970) /16*0, - 1 5500, -2197, -310, 232, 47, -16, -83, 8, -21, - + 1, 0, 0, 0, 3*0, - 2 -306, 284, -249, 150, 88, -27, -19, 15, - + 0, 0, 0, 0, 4*0, - 3 -297, 69, -154, 69, -2, 5, 9, - + 3, 0, 0, 0, 5*0, - 4 -297, -75, -48, 20, -23, -6, - + 6, 0, 0, 0, 6*0, - 5 95, -1, 17, 11, -6, - + -4, 0, 0, 0, 7*0, - 6 21, -23, 14, 9, - + 0, 0, 0, 0, 8*0, - 7 -7, -15, 9, - + -1, 0, 0, 0, 9*0, - 8 -11, -7, - + 4, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=3971,4050) / - + 0, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1990 - DATA (HY1D(I),I=4051,4195) /16*0, - 1 5406, -2279, -284, 247, 46, -16, -80, 10, -20, - + 2, 0, 0, 0, 3*0, - 2 -373, 293, -240, 154, 82, -26, -19, 15, - + 1, 0, 0, 0, 4*0, - 3 -352, 84, -153, 69, 0, 6, 11, - + 3, 0, 0, 0, 5*0, - 4 -299, -69, -52, 21, -22, -7, - + 6, 0, 0, 0, 6*0, - 5 97, 1, 17, 12, -7, - + -4, 0, 0, 0, 7*0, - 6 24, -23, 12, 9, - + 0, 0, 0, 0, 8*0, - 7 -4, -16, 8, - + -2, 0, 0, 0, 9*0, - 8 -10, -7, - + 3, 0, 0, 0, 10*0, - 9 2/ - DATA (HY1D(I),I=4196,4275) / - + -1, 0, 0, 0, 11*0, - O -6, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 1995 - DATA (HY1D(I),I=4276,4420) /16*0, - 1 5306, -2366, -262, 262, 46, -17, -69, 11, -20, - + 1, 0, 0, 0, 3*0, - 2 -413, 302, -236, 165, 72, -25, -21, 15, - + 0, 0, 0, 0, 4*0, - 3 -427, 97, -143, 67, 4, 8, 12, - + 4, 0, 0, 0, 5*0, - 4 -306, -55, -58, 24, -23, -6, - + 5, 0, 0, 0, 6*0, - 5 107, 1, 17, 15, -8, - + -5, 0, 0, 0, 7*0, - 6 36, -24, 11, 8, - + -1, 0, 0, 0, 8*0, - 7 -6, -16, 5, - + -2, 0, 0, 0, 9*0, - 8 -4, -8, - + 1, 0, 0, 0, 10*0, - 9 3/ - DATA (HY1D(I),I=4421,4500) / - + -2, 0, 0, 0, 11*0, - O -7, 0, 0, 0, 12*0, - 1 0, 0, 0, 13*0, - 2 0, 0, 14*0, - 3 0, 16*0/ -C h(n,m) for 2000 - DATA (HY1D(I),I=4501,4645) /16*0, - 1 5186.1,-2481.6,-227.6, 272.6, 43.8, -17.4, -64.6, 11.9, -19.7, - + 1.7, 0.1, -0.4, -0.9, 3*0, - 2 -458.0, 293.4,-231.9, 171.9, 63.7, -24.2, -21.5, 13.4, - + 0.0, 1.3, 0.3, 0.2, 4*0, - 3 -491.1, 119.8,-133.1, 65.1, 6.2, 8.5, 12.5, - + 4.0, -0.9, 2.5, 1.8, 5*0, - 4 -303.8, -39.3, -61.2, 24.0, -21.5, -6.2, - + 4.9, -2.6, -2.6, -0.4, 6*0, - 5 106.3, 0.7, 14.8, 15.5, -8.4, - + -5.9, 0.9, 0.7, -1.0, 7*0, - 6 43.8, -25.4, 8.9, 8.4, - + -1.2, -0.7, 0.3, -0.1, 8*0, - 7 -5.8, -14.9, 3.8, - + -2.9, -2.8, 0.0, 0.7, 9*0, - 8 -2.1, -8.2, - + 0.2, -0.9, 0.0, 0.3, 10*0, - 9 4.8/ - DATA (HY1D(I),I=4646,4725) / - + -2.2, -1.2, 0.3, 0.6, 11*0, - O -7.4, -1.9, -0.9, 0.3, 12*0, - 1 -0.9, -0.4, -0.2, 13*0, - 2 0.8, -0.5, 14*0, - 3 -0.9, 16*0/ -C h(n,m) for 2005 - DATA (HY1D(I),I=4726,4870) /16*0, - 1 5077.99,-2594.50,-198.86,282.07, 42.72,-20.33,-61.14, 11.20, - + -20.11, 2.19, 0.26, -0.55, -0.76, 3*0, - 2 -515.43,269.72,-225.23,180.25, 54.75,-22.57,-20.88,12.69, - + 0.10, 1.44, 0.23, 0.33, 4*0, - 3 -524.72,145.15,-123.45, 63.63, 6.82, 9.83,12.67, - + 4.46, -0.77, 2.38, 1.72, 5*0, - 4 -305.36,-19.57,-63.53, 25.35,-19.71,-6.72, - + 4.76, -2.27, -2.63, -0.54, 6*0, - 5 103.85, 0.24, 10.93, 16.22, -8.16, - + -6.58, 0.90, 0.61, -1.07, 7*0, - 6 50.94,-26.32, 7.61, 8.10, - + -1.01, -0.58, 0.40, -0.04, 8*0, - 7 -4.64,-12.76, 2.92, - + -3.47, -2.69, 0.01, 0.63, 9*0, - 8 -0.06, -7.73, - + -0.86, -1.08, 0.02, 0.21, 10*0, - 9 6.01/ - DATA (HY1D(I),I=4871,4950) / - + -2.31, -1.58, 0.28, 0.53, 11*0, - O -7.93, -1.90, -0.87, 0.38, 12*0, - 1 -1.39, -0.34, -0.22, 13*0, - 2 0.88, -0.57, 14*0, - 3 -0.82, 16*0/ -C h(n,m) for 2010 - DATA (HY1D(I),I=4951,5095) /16*0, - 1 4944.26, -2708.54,-160.4,286.48,44.58,-20.9, -57.8,10.84, -20.54, - + 2.73, 0.13, -0.87, -0.87, 3*0, - 2 -575.73, 251.75, -211.03, 189.01, 44.18, -21.2, -20.03, 11.51, - + -0.1, 1.67, 0.27, 0.3, 4*0, - 3 -537.03, 164.46, -118.06, 61.54, 6.54, 11.83, 12.75, - + 4.71, -0.66, 2.13, 1.66, 5*0, - 4 -309.72, -0.01, -66.26, 24.96, -17.41, -7.14, - + 4.44, -1.76, -2.49, -0.59, 6*0, - 5 101.04, 3.02, 7.03, 16.71, -7.42, - + -7.22, 0.85, 0.49, -1.14, 7*0, - 6 55.4, -27.61, 6.96, 7.97, - + -0.96, -0.39, 0.59, -0.07, 8*0, - 7 -3.28, -10.74, 2.14, - + -3.95, -2.51, 0.0, 0.54, 9*0, - 8 1.64, -6.08, - + -1.99, -1.27, 0.13, 0.1, 10*0, - 9 7.01/ - DATA (HY1D(I),I=5096,5175) / - + -1.97, -2.11, 0.27, 0.49, 11*0, - O -8.31, -1.94, -0.86, 0.44, 12*0, - 1 -1.86, -0.23, -0.25, 13*0, - 2 0.87, -0.53, 14*0, - 3 -0.79, 16*0/ -C h(n,m) for 2015 - DATA (HY1D(I),I=5176,5320) /16*0, - 1 4797.1, -2845.6, -115.3, 283.3, 47.3, -20.8, -54.1, 10.1, -21.6, - + 3.2, -0.1, -1.1, -0.9, 3*0, - 2 -641.9, 244.9, -188.7, 197, 33.2, -19.5, -18.3, 10.8, - + -0.4, 2.0, 0.4, 0.4, 4*0, - 3 -538.4, 180.9, -119.3, 58.9, 5.7, 13.3, 11.8, - + 4.6, -0.7, 1.9, 1.6, 5*0, - 4 -329.5, 16.0, -66.7, 24.4, -14.6, -6.8, - + 4.4, -1.1, -2.2, -0.5, 6*0, - 5 100.2, 7.3, 3.4, 16.2, -6.9, - + -7.9, 0.8, 0.3, -1.2, 7*0, - 6 62.6, -27.4, 5.7, 7.8, - + -0.6, -0.2, 0.7, -0.1, 8*0, - 7 -2.2, -9.1, 1.0, - + -4.2, -2.2, -0.1, 0.4, 9*0, - 8 2.1, -4.0, - + -2.8, -1.4, 0.3, -0.1, 10*0, - 9 8.4/ - DATA (HY1D(I),I=5321,5400) / - + -1.2, -2.5, 0.2, 0.4, 11*0, - O -8.7, -2.0, -0.9, 0.5, 12*0, - 1 -2.4, -0.1, -0.3, 13*0, - 2 0.7, -0.4, 14*0, - 3 -0.8, 16*0/ -C Secular variation rates are nominally okay through 2020 - DATA (GT1D(I),I=1,145) /0, - O 10.3, -8.7, 3.4, -0.7, -0.2, -0.3, 0.3, 0.2, 0.0, - + 0.0, 0.0, 0.0, 0.0, 2*0, - 1 18.1, -3.3, -5.5, 0.2, 0.5, -0.1, -0.2, 0.0, 0.0, - + 0.0, 0.0, 0.0, 0.0, 3*0, - 2 2.1, -0.7, -9.1, -1.3, -0.7, -0.5, -0.6, 0.0, - + 0.0, 0.0, 0.0, 0.0, 4*0, - 3 -10.1, 4.1, -0.1, 2.1, 1.3, 0.5, 0.0, - + 0.0, 0.0, 0.0, 0.0, 5*0, - 4 -4.3, 1.4, -1.2, 0.1, -0.2, 0.0, - + 0.0, 0.0, 0.0, 0.0, 6*0, - 5 3.9, 0.3, -0.6, 0.4, 0.0, - + 0.0, 0.0, 0.0, 0.0, 7*0, - 6 1.6, -0.8, 0.1, 0.0, - + 0.0, 0.0, 0.0, 0.0, 8*0, - 7 0.2, -0.4, 0.0, - + 0.0, 0.0, 0.0, 0.0, 9*0, - 8 0.3, 0.0, - + 0.0, 0.0, 0.0, 0.0, 10*0, - 9 0.0/ - DATA (GT1D(I),I=146,225) / - + 0.0, 0.0, 0.0, 0.0, 11*0, - O 0.0, 0.0, 0.0, 0.0, 12*0, - 1 0.0, 0.0, 0.0, 13*0, - 2 0.0, 0.0, 14*0, - 3 0.0, 16*0/ - DATA (HT1D(I),I=1,145) /16*0, - 1 -26.6, -27.4, 8.2, -1.3, 0.6, 0.0, 0.8, -0.3, 0.0, - + 0.0, 0.0, 0.0, 0.0, 3*0, - 2 -14.1, -0.4, 5.3, 1.7, -2.1, 0.4, 0.3, 0.0, - + 0.0, 0.0, 0.0, 0.0, 4*0, - 3 1.8, 2.9, -1.2, -0.7, -0.2, 0.1, 0.0, - + 0.0, 0.0, 0.0, 0.0, 5*0, - 4 -5.2, 3.4, 0.2, -0.3, 0.5, 0.0, - + 0.0, 0.0, 0.0, 0.0, 6*0, - 5 0.0, 0.9, -0.6, -0.2, 0.0, - + 0.0, 0.0, 0.0, 0.0, 7*0, - 6 1.0, 0.1, -0.3, 0.0, - + 0.0, 0.0, 0.0, 0.0, 8*0, - 7 -0.2, 0.3, 0.0, - + 0.0, 0.0, 0.0, 0.0, 9*0, - 8 0.0, 0.0, - + 0.0, 0.0, 0.0, 0.0, 10*0, - 9 0.0/ - DATA (HT1D(I),I=146,225) / - + 0.0, 0.0, 0.0, 0.0, 11*0, - O 0.0, 0.0, 0.0, 0.0, 12*0, - 1 0.0, 0.0, 0.0, 13*0, - 2 0.0, 0.0, 14*0, - 3 0.0, 16*0/ + SAVE DATEL, GYR, HYR, GT, HT, NEPO, EPOCH, NGHT, NMXE + DATA DATEL /-999./ C Do not need to load new coefficients if date has not changed ICHG = 0 IF (DATE .EQ. DATEL) GO TO 300 DATEL = DATE ICHG = 1 + +c Load coefficients + if (.not. allocated(GYR)) then + call read_igrf(FILENAME,GYR,HYR,GT,HT,NEPO,NGHT,EPOCH,NMXE) + endif + NGH=NGHT*NEPO C Trap out of range date: IF (DATE .LT. EPOCH(1)) GO TO 9100 @@ -1441,6 +145,7 @@ SUBROUTINE COFRM (DATE) 100 CONTINUE 110 CONTINUE + NGH=NGHT*NEPO NMAX = NMXE(IY) TIME = DATE T = TIME-EPOCH(IY) @@ -1478,9 +183,7 @@ SUBROUTINE COFRM (DATE) GV(I) = GB(I) / RNN GV(I1) = GB(I1) / RNN 200 I = I+2 - 300 CONTINUE - RETURN C Error trap diagnostics: diff --git a/src/fortranapex/makeapexsh.f90 b/src/fortranapex/makeapexsh.f90 index 0bfd485c..784444b6 100644 --- a/src/fortranapex/makeapexsh.f90 +++ b/src/fortranapex/makeapexsh.f90 @@ -1,39 +1,50 @@ -!*************************************************************************************************** +!******************************************************************************* ! ! File Name: makeapexsh.f90 ! Authors: John Emmert, Art Richmond ! Date: 11/13/2009 ! Version: 1.0 -! Description: Creates and saves spherical harmonic expansion coefficients for QD coordinate -! conversion. -! References: Richmond, A. D., Ionospheric Electrodynamics Using Magnetic Apex Coordinates, -! J. Geomag. Geoelectr., 47, 191-212, 1995. -! Emmert, J. T., A. D. Richmond, and D. P. Drob, A computationally compact -! representation of Magnetic-Apex and Quasi-Dipole coordinates with smooth base -! vectors, J. Geophys. Res., 115, Axxxxx, doi:10.1029/2010JA015326, 2010. +! Description: Creates and saves spherical harmonic expansion coefficients for +! QD coordinate conversion. +! References: Richmond, A. D., Ionospheric Electrodynamics Using Magnetic Apex +! Coordinates, J. Geomag. Geoelectr., 47, 191-212, 1995. +! Emmert, J. T., A. D. Richmond, and D. P. Drob, A computationally +! compact representation of Magnetic-Apex and Quasi-Dipole +! coordinates with smooth base vectors, J. Geophys. Res., 115, +! Axxxxx, doi:10.1029/2010JA015326, 2010. ! -!*************************************************************************************************** +!******************************************************************************* +! +! HISTORY (blame): +! +! 25 Feb 2021: Modified by Ashton Reimer to pass IGRF coefficients file to the +! COFRM and APEX subroutine calls. +! +!******************************************************************************* ! ! MAKEAPXSH ! Computes and saves harmonic coefficients for coordinate conversions. ! -! CALL MAKEAPXSH (DATAFILE, EPOCHS, NEPOCHS, L, M, N) +! CALL MAKEAPXSH (DATAFILE, IGRFFILE, EPOCHS, NEPOCHS, L, M, N) ! ! INPUT ARGUMENTS -! DATAFILE Name of output data file that will contain the conversion coefficients -! EPOCHS Array of ordered epochs (decimal years, yyyy.y) for which coefficients are to be -! computed. +! DATAFILE Name of output data file that will contain the conversion +! coefficients +! IGRFFILE Name of the input IGRF coefficients file +! EPOCHS Array of ordered epochs (decimal years, yyyy.y) for which +! coefficients are to be computed. ! NEPOCHS Number of elements in EPOCHS. ! L Maximum order of vertical polynomial expansion. ! M Maximum order of spherical harmonic expansion. -! N Maximum degree of spherical harmonic expansion. N must be equal or greater than M. +! N Maximum degree of spherical harmonic expansion. N must be equal +! or greater than M. ! ! DEPENDENCIES ! apex.f, magfld.f, apexsh.f90 ! -!*************************************************************************************************** +!******************************************************************************* -subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) +subroutine makeapxsh(datafilein, igrffilein, epochgridin, nepochin, lmaxin, mmaxin, nmaxin) use apxshmodule @@ -42,6 +53,7 @@ subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) COMMON /MAGCOF/ NMAXIGRF, GB character(128), intent(in) :: datafilein + character(len=1000), intent(in) :: igrffilein real(4), intent(in) :: epochgridin(0:30) integer(4), intent(in) :: nmaxin, mmaxin, lmaxin, nepochin @@ -51,16 +63,19 @@ subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) integer(4) :: l, ish, i, j, Rpt(0:2) real(4) :: glon, glat, alt, lonspace, latspace, lat0 real(8) :: rhospace - real(8) :: norm1, norm2, cosmplat, sinmplat, cosmplon, sinmplon + real(8) :: norm1, norm2, cosmplat, sinmplat, cosmplon + real(8) :: sinmplon real(8) :: thetag, phig, thetaq, phiq, latwgt real(8) :: xq0, yq0, zq0, xg, yg, zg real(8), allocatable :: altgrid(:), altfn(:,:), shg(:) - real(8), allocatable :: Gg(:,:), Gq(:,:), Fg(:), Fq(:), cfdiag(:), coefftemp(:) - real(8), allocatable :: Dxq(:), Dyq(:), Dzq(:), Dxg(:), Dyg(:), Dzg(:) + real(8), allocatable :: Gg(:,:), Gq(:,:), Fg(:), Fq(:), cfdiag(:) + real(8), allocatable :: coefftemp(:) + real(8), allocatable :: Dxq(:), Dyq(:), Dzq(:), Dxg(:), Dyg(:) + real(8), allocatable :: Dzg(:) integer(4) :: NMAXIGRF real(4) :: GB(1:255) - real(4) :: A,ALAT,ALON,dum1,dum2,dum3,dum4,dum5 + real(4) :: A, ALAT, ALON, dum1, dum2, dum3, dum4, dum5 external COFRM, APEX @@ -116,7 +131,7 @@ subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) !RETRIEVE IGRF, COMPUTE DIPOLE ROTATION PARAMETERS, !AND SET THE CORRESPONDING EXPANSION COEFFICIENTS - call COFRM(epochgrid(iepoch)) + call COFRM(epochgrid(iepoch),igrffilein) sinmplat = dble(GB(2) / sqrt(GB(2)*GB(2) + GB(3)*GB(3) + GB(4)*GB(4))) cosmplat = dsqrt(1 - sinmplat*sinmplat) sinmplon = dble(GB(4) / sqrt(GB(3)*GB(3) + GB(4)*GB(4))) @@ -135,13 +150,15 @@ subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) latwgt = dsin(thetag) do ilon = 0, nlon-1 - !COMPUTE SPHERICAL HARMONICS OF CURRENT GEODETIC LONGITUDE AND LATITUDE + !COMPUTE SPHERICAL HARMONICS OF CURRENT GEODETIC LONGITUDE AND + !LATITUDE glon = lonspace*real(ilon) - 180E0 phig = dble(glon) * dtor call shcalc(thetag,phig) shg = sh - !COMPUTE REFERENCE (DIPOLE) MAGNETIC LATITUDE AND LONGITUDE OF CURRENT LOCATION + !COMPUTE REFERENCE (DIPOLE) MAGNETIC LATITUDE AND LONGITUDE OF + !CURRENT LOCATION xq0 = dot_product(coeff0(Rpt,iepoch,0),shg(Rpt)) yq0 = dot_product(coeff0(Rpt,iepoch,1),shg(Rpt)) zq0 = dot_product(coeff0(Rpt,iepoch,2),shg(Rpt)) @@ -151,14 +168,15 @@ subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) !COMPUTE QD LATITUDE AND LONGITUDE OF CURRENT LOCATION alt = sngl(altgrid(ialt)) - call APEX(epochgrid(iepoch),glat,glon,alt,A,ALAT,ALON,dum1,dum2,dum3,dum4,dum5) + call APEX(epochgrid(iepoch),igrffilein,glat,glon,alt,A,ALAT,ALON,dum1,dum2,dum3,dum4,dum5) cosqlat = dsqrt( (Re + altgrid(ialt)) / (Re + Req*(dble(A)-1D0)) ) if (cosqlat .gt. 1D0) cosqlat = 1D0 phiq = dble(ALON) * dtor thetaq = dasin(cosqlat) if (ALAT .lt. 0) thetaq = pi - thetaq - !COMPUTE RESIDUAL QD COORDINATES (FITTING DATA FOR GEODETIC TO QD TRANSFORMATION) + !COMPUTE RESIDUAL QD COORDINATES (FITTING DATA FOR GEODETIC TO QD + !TRANSFORMATION) xq = cosqlat*dcos(phiq) - xq0 yq = cosqlat*dsin(phiq) - yq0 zq = dcos(thetaq) - zq0 @@ -166,7 +184,8 @@ subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) !COMPUTE SPHERICAL HARMONICS OF CURRENT QD LATITUDE AND LONGITUDE call shcalc(thetaq,phiq) - !COMPUTE RESIDUAL GD COORDINATES (FITTING DATA FOR QD TO GEODETIC TRANSFORMATION) + !COMPUTE RESIDUAL GD COORDINATES (FITTING DATA FOR QD TO GEODETIC + !TRANSFORMATION) xg = latwgt*dcos(phig) - dot_product(coeff0(Rpt,iepoch,3),sh(Rpt)) yg = latwgt*dsin(phig) - dot_product(coeff0(Rpt,iepoch,4),sh(Rpt)) zg = dcos(thetag) - dot_product(coeff0(Rpt,iepoch,5),sh(Rpt)) @@ -217,15 +236,15 @@ subroutine makeapxsh(datafilein,epochgridin,nepochin,lmaxin,mmaxin,nmaxin) !WRITE COEFFICIENTS TO OUTPUT FILE open(unit=iun, file=trim(datafilein), form='unformatted') - write(iun), nepoch, nmax, mmax, lmax, nterm - write(iun), epochgrid, coeff0 + write(iun) nepoch, nmax, mmax, lmax, nterm + write(iun) epochgrid, coeff0 close(iun) return end subroutine makeapxsh -!*************************************************************************************************** +!******************************************************************************* subroutine choldc(a,n,np,p) @@ -252,7 +271,7 @@ subroutine choldc(a,n,np,p) end subroutine choldc -!*************************************************************************************************** +!******************************************************************************* subroutine cholsl(a,n,np,p,b,x) @@ -280,4 +299,4 @@ subroutine cholsl(a,n,np,p,b,x) end subroutine cholsl -!*************************************************************************************************** +!******************************************************************************* diff --git a/src/fortranapex/readme.txt b/src/fortranapex/readme.txt index 99262664..8c7894aa 100644 --- a/src/fortranapex/readme.txt +++ b/src/fortranapex/readme.txt @@ -38,3 +38,22 @@ field lines. 5. 2010ja015326-txts05.txt magfld.f: Fortran-77 code for evaluating IGRF. + +############################################## + +DEV NOTES: + +The above text is copy-pasted from the auxiliary materials for the Emmert et al. (2010) paper. + +In February 2021, the magfld.f source was modified so that it could read IGRF coefficients from the text file instead of from hard-coded constants. Specifically the COFRM subroutine was modified. This also required modifying APEX subroutine in apex.f, the makeapxsh subroutine in makeapexsh.f90, and the checkapexsh program in checkapexsh.f90. + + +MAINTENANCE NOTE: + +After updating the IGRF coefficients file, we need to rebuild the apexsh.dat file. This is done by: + +1) Adding the next 5 year epoch to "epochgrid" and updating the "nepochgrid" variable in checkapexsh.f90 + For example, if epochgrid has up to the year 2020 and the newest IGRF coefficients are good up to 2025, we should add 2025 to epochgrid and then increment nepochgrid by 1. +2) checkapexsh.f90 also expects the IGRF coefficient file to be in the same directory with the name "igrf13coeffs.txt", so you may also need to update the "igrffilein" variable as well. +3) Building the "apextest" binary using the "make" command. +4) Copying the resulting "apexsh.dat" file to the apexpy/src/apexpy directory. diff --git a/tests/test_Apex.py b/tests/test_Apex.py index 1ce815a1..9414b02e 100644 --- a/tests/test_Apex.py +++ b/tests/test_Apex.py @@ -3,27 +3,27 @@ from __future__ import division, absolute_import, unicode_literals import datetime as dt -import warnings - +import itertools import numpy as np -import pytest from numpy.testing import assert_allclose +import os +import pytest from apexpy import fortranapex as fa from apexpy import Apex, ApexHeightError, helpers -############################################################################## -# NOTE: whenever function outputs are tested against hard-coded numbers, # -# the test results (numbers) were obtained by running the code that is # -# tested. Therefore these tests below only check that nothing changes when # -# refactoring etc., and not if the results are actually correct # -############################################################################## +# ---------------------------------------------------------------------------- +# NOTE: whenever function outputs are tested against hard-coded numbers, the +# test results (numbers) were obtained by running the code that is tested. +# Therefore these tests below only check that nothing changes when refactoring, +# etc., and not if the results are actually correct. +# ----------------------------------------------------------------------------- -###============================================================================ -### Test initiating the Apex class -###============================================================================ +# ============================================================================ +# Test initiating the Apex class +# ============================================================================ def test_init_defaults(): @@ -57,9 +57,9 @@ def test_init_datafile_IOError(): Apex(date=2015, datafile='foo/path/to/datafile.blah') -###============================================================================ -### Test the low-level interfaces to the fortran wrappers -###============================================================================ +# ============================================================================ +# Test the low-level interfaces to the fortran wrappers +# ============================================================================ def test__geo2qd_scalar(): apex_out = Apex(date=2000, refh=300) @@ -93,7 +93,7 @@ def test__geo2qd_longitude(): apex_out._geo2qd(60, 180, 100)) for i in range(-5, 5): for lat in [0, 30, 60, 90]: - assert_allclose(apex_out._geo2qd(lat, 15+i*360, 100), + assert_allclose(apex_out._geo2qd(lat, 15 + i * 360, 100), fa.apxg2q(lat, 15, 100, 0)[:2]) @@ -129,7 +129,7 @@ def test__geo2apex_longitude(): apex_out._geo2apex(60, 180, 100)) for i in range(-5, 5): for lat in [0, 30, 60, 90]: - assert_allclose(apex_out._geo2apex(lat, 15+i*360, 100), + assert_allclose(apex_out._geo2apex(lat, 15 + i * 360, 100), fa.apxg2all(lat, 15, 100, 300, 0)[2:4]) @@ -157,7 +157,7 @@ def test__geo2apexall_array(): assert_allclose(ret[i].astype(float), np.array([[ret1[i], ret2[i]], [ret3[i], ret4[i]]], dtype=float)) - except: + except ValueError: # ret[i] is array of arrays assert_allclose(ret[i][0, 0], ret1[i]) assert_allclose(ret[i][0, 1], ret2[i]) @@ -177,7 +177,7 @@ def test__qd2geo_scalar(): def test__qd2geo_array(): apex_out = Apex(date=2000, refh=300) lats, lons, errs = apex_out._qd2geo([[0, 30], [60, 90]], 15, - [[100, 200], [300, 400]], 1e-2) + [[100, 200], [300, 400]], 1e-2) lat1, lon1, err1 = fa.apxq2g(0, 15, 100, 1e-2) lat2, lon2, err2 = fa.apxq2g(30, 15, 200, 1e-2) lat3, lon3, err3 = fa.apxq2g(60, 15, 300, 1e-2) @@ -200,7 +200,7 @@ def test__qd2geo_longitude(): apex_out._qd2geo(60, 180, 100, 1e-2)) for i in range(-5, 5): for lat in [0, 30, 60, 90]: - assert_allclose(apex_out._qd2geo(lat, 15+i*360, 100, 1e-2), + assert_allclose(apex_out._qd2geo(lat, 15 + i * 360, 100, 1e-2), fa.apxq2g(lat, 15, 100, 1e-2)) @@ -240,13 +240,13 @@ def test__basevec_longitude(): apex_out._basevec(60, 180, 100)) for i in range(-5, 5): for lat in [0, 30, 60, 90]: - assert_allclose(apex_out._basevec(lat, 15+i*360, 100), + assert_allclose(apex_out._basevec(lat, 15 + i * 360, 100), fa.apxg2q(lat, 15, 100, 1)[2:4]) -###============================================================================ -### Test the convert() method -###============================================================================ +# ============================================================================ +# Test the convert() method +# ============================================================================ def test_convert_geo2apex(): @@ -310,11 +310,21 @@ def test_convert_qd2apex(): apex_out.qd2apex(60, 15, height=100)) +def test_convert_qd2apex_at_equator(): + """Test the quasi-dipole to apex conversion at the magnetic equator.""" + apex_out = Apex(date=2000, refh=80) + elat, elon = apex_out.convert(lat=0.0, lon=0, source='qd', dest='apex', + height=120.0) + clat, clon = apex_out.convert(lat=0.001, lon=0, source='qd', dest='apex', + height=120.0) + assert_allclose([elat, elon], [clat, clon], atol=1e-4) + + def test_convert_qd2mlt(): datetime = dt.datetime(2000, 3, 9, 14, 25, 58) apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.convert(60, 15, 'qd', 'mlt', height=100, - datetime=datetime, ssheight=2e5)[1], + datetime=datetime, ssheight=2e5)[1], apex_out.mlon2mlt(15, datetime, ssheight=2e5)) @@ -333,7 +343,7 @@ def test_convert_mlt2apex(): datetime = dt.datetime(2000, 3, 9, 14, 25, 58) apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.convert(60, 15, 'mlt', 'apex', height=100, - datetime=datetime, ssheight=2e5), + datetime=datetime, ssheight=2e5), (60, apex_out.mlt2mlon(15, datetime, ssheight=2e5))) @@ -356,7 +366,7 @@ def test_convert_invalid_lat(): apex_out.convert(90, 0, 'geo', 'geo') apex_out.convert(-90, 0, 'geo', 'geo') - assert_allclose(apex_out.convert(90+1e-5, 0, 'geo', 'apex'), + assert_allclose(apex_out.convert(90 + 1e-5, 0, 'geo', 'apex'), apex_out.convert(90, 0, 'geo', 'apex'), rtol=0, atol=1e-8) @@ -368,9 +378,30 @@ def test_convert_invalid_transformation(): apex_out.convert(0, 0, 'geo', 'foobar') -###============================================================================ -### Test the geo2apex() method -###============================================================================ +coord_names = ['geo', 'apex', 'qd'] + + +@pytest.mark.parametrize('transform', itertools.product(coord_names, + coord_names)) +def test_convert_withnan(transform): + """Test Apex.convert success with NaN input.""" + num_nans = 5 + in_lat = np.arange(0, 10, dtype=float) + in_lat[:num_nans] = np.nan + in_lon = np.arange(0, 10, dtype=float) + in_lon[:num_nans] = np.nan + src, dest = transform + apex_out = Apex(date=2000, refh=80) + out_lat, out_lon = apex_out.convert(in_lat, in_lon, src, dest, height=120) + assert np.all(np.isnan(out_lat[:num_nans])) + assert np.all(np.isnan(out_lon[:num_nans])) + assert np.all(np.isfinite(out_lat[num_nans:])) + assert np.all(np.isfinite(out_lat[num_nans:])) + + +# ============================================================================ +# Test the geo2apex() method +# ============================================================================ def test_geo2apex(): @@ -397,22 +428,24 @@ def test_geo2apex_invalid_lat(): apex_out.geo2apex(90, 0, 0) apex_out.geo2apex(-90, 0, 0) - assert_allclose(apex_out.geo2apex(90+1e-5, 0, 0), + assert_allclose(apex_out.geo2apex(90 + 1e-5, 0, 0), apex_out.geo2apex(90, 0, 0), rtol=0, atol=1e-8) -def test_geo2apex_undefined_warning(): +def test_geo2apex_undefined_warning(recwarn): + """Test warning and fill values for an undefined location.""" apex_out = Apex(date=2000, refh=10000) - with warnings.catch_warnings(record=True) as w: - ret = apex_out.geo2apex(0, 0, 0) - assert ret[0] == -9999 - assert issubclass(w[-1].category, UserWarning) - assert 'set to -9999 where' in str(w[-1].message) + ret = apex_out.geo2apex(0, 0, 0) + assert np.isnan(ret[0]) + assert len(recwarn) == 1 + assert issubclass(recwarn[-1].category, UserWarning) + assert 'set to NaN where' in str(recwarn[-1].message) -###============================================================================ -### Test the apex2geo() method -###============================================================================ + +# ============================================================================ +# Test the apex2geo() method +# ============================================================================ def test_apex2geo(): @@ -420,7 +453,8 @@ def test_apex2geo(): lat, lon, error = apex_out.apex2geo(60, 15, 100, precision=1e-2) assert_allclose((lat, lon, error), apex_out.qd2geo(*apex_out.apex2qd(60, 15, 100), height=100, - precision=1e-2)) + precision=1e-2)) + assert type(lat) != np.ndarray assert type(lon) != np.ndarray assert type(error) != np.ndarray @@ -442,13 +476,13 @@ def test_apex2geo_invalid_lat(): apex_out.apex2geo(90, 0, 0, 1e-2) apex_out.apex2geo(-90, 0, 0, 1e-2) - assert_allclose(apex_out.apex2geo(90+1e-5, 0, 0, 1e-2), + assert_allclose(apex_out.apex2geo(90 + 1e-5, 0, 0, 1e-2), apex_out.apex2geo(90, 0, 0, 1e-2), rtol=0, atol=1e-8) -###============================================================================ -### Test the geo2qd() method -###============================================================================ +# ============================================================================ +# Test the geo2qd() method +# ============================================================================ def test_geo2qd(): @@ -475,13 +509,13 @@ def test_geo2qd_invalid_lat(): apex_out.geo2qd(90, 0, 0) apex_out.geo2qd(-90, 0, 0) - assert_allclose(apex_out.geo2qd(90+1e-5, 0, 0), apex_out.geo2qd(90, 0, 0), + assert_allclose(apex_out.geo2qd(90 + 1e-5, 0, 0), apex_out.geo2qd(90, 0, 0), rtol=0, atol=1e-8) -###============================================================================ -### Test the qd2geo() method -###============================================================================ +# ============================================================================ +# Test the qd2geo() method +# ============================================================================ def test_qd2geo(): @@ -509,13 +543,13 @@ def test_qd2geo_invalid_lat(): apex_out.qd2geo(90, 0, 0, precision=1e-2) apex_out.qd2geo(-90, 0, 0, precision=1e-2) - assert_allclose(apex_out.qd2geo(90+1e-5, 0, 0, 1e-2), + assert_allclose(apex_out.qd2geo(90 + 1e-5, 0, 0, 1e-2), apex_out.qd2geo(90, 0, 0, 1e-2), rtol=0, atol=1e-8) -###============================================================================ -### Test the apex2qd() method -###============================================================================ +# ============================================================================ +# Test the apex2qd() method +# ============================================================================ def test_apex2qd(): @@ -543,13 +577,13 @@ def test_apex2qd_invalid_lat(): apex_out.apex2qd(90, 0, 0) apex_out.apex2qd(-90, 0, 0) - assert_allclose(apex_out.apex2qd(90+1e-5, 0, 0), apex_out.apex2qd(90, 0, 0), - rtol=0, atol=1e-8) + assert_allclose(apex_out.apex2qd(90 + 1e-5, 0, 0), + apex_out.apex2qd(90, 0, 0), rtol=0, atol=1e-8) def test_apex2qd_apexheight_close(): apex_out = Apex(date=2000, refh=300) - apex_out.apex2qd(0, 15, 300+1e-6) + apex_out.apex2qd(0, 15, 300 + 1e-6) def test_apex2qd_apexheight_over(): @@ -558,9 +592,9 @@ def test_apex2qd_apexheight_over(): apex_out.apex2qd(0, 15, 301) -###============================================================================ -### Test the qd2apex() method -###============================================================================ +# ============================================================================ +# Test the qd2apex() method +# ============================================================================ def test_qd2apex(): @@ -588,13 +622,13 @@ def test_qd2apex_invalid_lat(): apex_out.qd2apex(90, 0, 0) apex_out.qd2apex(-90, 0, 0) - assert_allclose(apex_out.qd2apex(90+1e-5, 0, 0), apex_out.qd2apex(90, 0, 0), - rtol=0, atol=1e-8) + assert_allclose(apex_out.qd2apex(90 + 1e-5, 0, 0), + apex_out.qd2apex(90, 0, 0), rtol=0, atol=1e-8) def test_qd2apex_apexheight_close(): apex_out = Apex(date=2000, refh=300) - assert_allclose(apex_out.qd2apex(0, 15, 300-1e-5), + assert_allclose(apex_out.qd2apex(0, 15, 300 - 1e-5), apex_out.qd2apex(0, 15, 300)) @@ -604,9 +638,9 @@ def test_qd2apex_apexheight_over(): apex_out.qd2apex(0, 15, 299) -###============================================================================ -### Test mlon2mlt() -###============================================================================ +# ============================================================================ +# Test mlon2mlt() +# ============================================================================ def test_mlon2mlt_scalar(): @@ -619,7 +653,7 @@ def test_mlon2mlt_scalar(): def test_mlon2mlt_ssheight(): apex_out = Apex(date=2000, refh=300) mlt = apex_out.mlon2mlt(0, dt.datetime(2000, 2, 3, 4, 5, 6), - ssheight=50*2000) + ssheight=50 * 2000) assert_allclose(mlt, 23.026712036132814) @@ -633,7 +667,7 @@ def test_mlon2mlt_1Darray(): def test_mlon2mlt_2Darray(): apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.mlon2mlt([[0, 180], [0, 180]], - dt.datetime(2000, 2, 3, 4, 5, 6)), + dt.datetime(2000, 2, 3, 4, 5, 6)), [[23.019261, 11.019261], [23.019261, 11.019261]], rtol=1e-4) @@ -650,35 +684,35 @@ def test_mlon2mlt_offset(): assert_allclose(apex_out.mlon2mlt(0, date), apex_out.mlon2mlt(-15, date) + 1) assert_allclose(apex_out.mlon2mlt(0, date), - apex_out.mlon2mlt(-10*15, date) + 10) + apex_out.mlon2mlt(-10 * 15, date) + 10) def test_mlon2mlt_range(): apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.mlon2mlt(range(0, 361, 30), - dt.datetime(2000, 2, 3, 4, 5, 6)), + dt.datetime(2000, 2, 3, 4, 5, 6)), [23.01963, 1.01963, 3.01963, 5.01963, 7.01963, 9.01963, 11.01963, 13.01963, 15.01963, 17.01963, 19.01963, 21.01963, 23.01963], rtol=1e-4) -###============================================================================ -### Test mlt2mlon() -###============================================================================ +# ============================================================================ +# Test mlt2mlon() +# ============================================================================ def test_mlt2mlon_scalar(): apex_out = Apex(date=2000, refh=300) mlt = apex_out.mlt2mlon(0, dt.datetime(2000, 2, 3, 4, 5, 6)) - assert_allclose(mlt, 14.705551147460938) + assert_allclose(mlt, 14.705535888671875) assert type(mlt) != np.ndarray def test_mlt2mlon_ssheight(): apex_out = Apex(date=2000, refh=300) mlt = apex_out.mlt2mlon(0, dt.datetime(2000, 2, 3, 4, 5, 6), - ssheight=50*2000) + ssheight=50 * 2000) assert_allclose(mlt, 14.599319458007812) @@ -692,7 +726,7 @@ def test_mlt2mlon_1Darray(): def test_mlt2mlon_2Darray(): apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.mlt2mlon([[0, 12], [0, 12]], - dt.datetime(2000, 2, 3, 4, 5, 6)), + dt.datetime(2000, 2, 3, 4, 5, 6)), [[14.705551, 194.705551], [14.705551, 194.705551]], rtol=1e-4) @@ -715,16 +749,16 @@ def test_mlt2mlon_offset(): def test_mlt2mlon_range(): apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.mlt2mlon(range(0, 25, 2), - dt.datetime(2000, 2, 3, 4, 5, 6)), + dt.datetime(2000, 2, 3, 4, 5, 6)), [14.705551, 44.705551, 74.705551, 104.705551, 134.705551, 164.705551, 194.705551, 224.705551, 254.705551, 284.705551, 314.705551, 344.705551, 14.705551], rtol=1e-4) -###============================================================================ -### Test mlt/mlon back and forth -###============================================================================ +# ============================================================================ +# Test mlt/mlon back and forth +# ============================================================================ def test_mlon2mlt2mlon(): @@ -747,50 +781,52 @@ def test_mlt2mlon2mlt(): assert_allclose(apex_out.mlt2mlon(apex_out.mlon2mlt(360, date), date), 0) -###============================================================================ -### Test the map_to_height() method -###============================================================================ +# ============================================================================ +# Test the map_to_height() method +# ============================================================================ def test_map_to_height(): apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.map_to_height(60, 15, 100, 10000, conjugate=False, - precision=1e-10), - (31.841459274291992, 17.916629791259766, 0)) + precision=1e-10), + (31.841466903686523, 17.916635513305664, + 1.7075473124350538e-6)) assert_allclose(apex_out.map_to_height(30, 170, 100, 500, conjugate=False, - precision=1e-2), - (25.727252960205078, 169.60546875, 0.00017655163537710905)) + precision=1e-2), + (25.727270126342773, 169.60546875, 0.00017573432705830783)) def test_map_to_height_same_height(): apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.map_to_height(60, 15, 100, 100, conjugate=False, - precision=1e-10), - (60, 15, 3.4150946248701075e-6), rtol=1e-5) + precision=1e-10), + (60.0, 15.000003814697266, 0.0), rtol=1e-5) def test_map_to_height_conjugate(): + """Test results of map_to_height using conjugacy.""" apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.map_to_height(60, 15, 100, 10000, conjugate=True, - precision=1e-10), - (-25.424892425537109, 27.310417175292969, - 1.2074182222931995e-6)) + precision=1e-10), + (-25.424888610839844, 27.310426712036133, + 1.2074182222931995e-6), atol=1e-6) assert_allclose(apex_out.map_to_height(30, 170, 100, 500, conjugate=True, - precision=1e-2), + precision=1e-2), (-13.76642894744873, 164.24259948730469, - 0.00056820799363777041)) + 0.00056820799363777041), atol=1e-6) def test_map_to_height_vectorization(): apex_out = Apex(date=2000, refh=300) assert_allclose(apex_out.map_to_height([60, 60], 15, 100, 100), - ([60]*2, [15]*2, [3.4150946248701075e-6]*2), rtol=1e-5) + ([60] * 2, [15.00000381] * 2, [0] * 2), rtol=1e-5) assert_allclose(apex_out.map_to_height(60, [15, 15], 100, 100), - ([60]*2, [15]*2, [3.4150946248701075e-6]*2), rtol=1e-5) + ([60] * 2, [15.00000381] * 2, [0] * 2), rtol=1e-5) assert_allclose(apex_out.map_to_height(60, 15, [100, 100], 100), - ([60]*2, [15]*2, [3.4150946248701075e-6]*2), rtol=1e-5) + ([60] * 2, [15.00000381] * 2, [0] * 2), rtol=1e-5) assert_allclose(apex_out.map_to_height(60, 15, 100, [100, 100]), - ([60]*2, [15]*2, [3.4150946248701075e-6]*2), rtol=1e-5) + ([60] * 2, [15.00000381] * 2, [0] * 2), rtol=1e-5) def test_map_to_height_ApexHeightError(): @@ -799,19 +835,19 @@ def test_map_to_height_ApexHeightError(): apex_out.map_to_height(0, 15, 100, 10000) -###============================================================================ -### Test the map_E_to_height() method -###============================================================================ +# ============================================================================ +# Test the map_E_to_height() method +# ============================================================================ def test_map_E_to_height(): apex_out = Apex(date=2000, refh=300) - out_60_15_100_500 = [0.7115211, 2.3562392, 0.57259707] - out_60_15_100_500_234 = [1.560284, 3.439154, 0.782339] - out_60_15_100_1000 = [0.677964, 2.089811, 0.558601] - out_60_15_200_500 = [0.723773, 2.427366, 0.590826] - out_60_30_100_500 = [0.686265, 2.375296, 0.600594] - out_70_15_100_500 = [0.727605, 2.180817, 0.291414] + out_60_15_100_500 = [0.71152183, 2.35624876, 0.57260784] + out_60_15_100_500_234 = [1.56028502, 3.43916636, 0.78235384] + out_60_15_100_1000 = [0.67796492, 2.08982134, 0.55860785] + out_60_15_200_500 = [0.72377397, 2.42737471, 0.59083726] + out_60_30_100_500 = [0.68626344, 2.37530133, 0.60060124] + out_70_15_100_500 = [0.72760378, 2.18082305, 0.29141979] # scalar assert_allclose(apex_out.map_E_to_height(60, 15, 100, 500, [1, 2, 3]), @@ -829,48 +865,49 @@ def test_map_E_to_height(): # vectorize lat assert_allclose(apex_out.map_E_to_height([60, 70], 15, 100, 500, - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_70_15_100_500]).T, rtol=1e-5) # vectorize lon assert_allclose(apex_out.map_E_to_height(60, [15, 30], 100, 500, - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_60_30_100_500]).T, rtol=1e-5) # vectorize height assert_allclose(apex_out.map_E_to_height(60, 15, [100, 200], 500, - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_60_15_200_500]).T, rtol=1e-5) # vectorize newheight assert_allclose(apex_out.map_E_to_height(60, 15, 100, [500, 1000], - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_60_15_100_1000]).T, rtol=1e-5) # vectorize E assert_allclose(apex_out.map_E_to_height(60, 15, 100, 500, - np.array([[1, 2, 3], [2, 3, 4]]).T), + np.array([[1, 2, 3], + [2, 3, 4]]).T), np.array([out_60_15_100_500, out_60_15_100_500_234]).T, rtol=1e-5) -###============================================================================ -### Test the map_V_to_height() method -###============================================================================ +# ============================================================================ +# Test the map_V_to_height() method +# ============================================================================ def test_map_V_to_height(): apex_out = Apex(date=2000, refh=300) - out_60_15_100_500 = [0.819719, 2.845114, 0.695437] - out_60_15_100_500_234 = [1.830277, 4.14345, 0.947624] - out_60_15_100_1000 = [0.924577, 3.149964, 0.851343] - out_60_15_200_500 = [0.803882, 2.793206, 0.682839] - out_60_30_100_500 = [0.761412, 2.878837, 0.736549] - out_70_15_100_500 = [0.846819, 2.592572, 0.347919] + out_60_15_100_500 = [0.81971957, 2.84512495, 0.69545001] + out_60_15_100_500_234 = [1.83027746, 4.14346436, 0.94764179] + out_60_15_100_1000 = [0.92457698, 3.14997661, 0.85135187] + out_60_15_200_500 = [0.80388262, 2.79321504, 0.68285158] + out_60_30_100_500 = [0.76141245, 2.87884673, 0.73655941] + out_70_15_100_500 = [0.84681866, 2.5925821, 0.34792655] # scalar assert_allclose(apex_out.map_V_to_height(60, 15, 100, 500, [1, 2, 3]), @@ -888,25 +925,25 @@ def test_map_V_to_height(): # vectorize lat assert_allclose(apex_out.map_V_to_height([60, 70], 15, 100, 500, - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_70_15_100_500]).T, rtol=1e-5) # vectorize lon assert_allclose(apex_out.map_V_to_height(60, [15, 30], 100, 500, - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_60_30_100_500]).T, rtol=1e-5) # vectorize height assert_allclose(apex_out.map_V_to_height(60, 15, [100, 200], 500, - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_60_15_200_500]).T, rtol=1e-5) # vectorize newheight assert_allclose(apex_out.map_V_to_height(60, 15, 100, [500, 1000], - np.array([[1, 2, 3]]*2).T), + np.array([[1, 2, 3]] * 2).T), np.array([out_60_15_100_500, out_60_15_100_1000]).T, rtol=1e-5) @@ -918,9 +955,9 @@ def test_map_V_to_height(): rtol=1e-5) -###============================================================================ -### Test basevectors_qd() -###============================================================================ +# ============================================================================ +# Test basevectors_qd() +# ============================================================================ # test coords @@ -946,8 +983,8 @@ def test_basevectors_qd_scalar_qd(): precision=1e-2), apex_out._basevec(glat, glon, 100)) -# test shapes and vectorization of arguments +# test shapes and vectorization of arguments def test_basevectors_qd_scalar_shape(): apex_out = Apex(date=2000, refh=300) ret = apex_out.basevectors_qd(60, 15, 100) @@ -981,9 +1018,9 @@ def test_basevectors_qd_array(): assert_allclose(f2[:, 1], f2_lat30) -###============================================================================ -### Test basevectors_apex() -###============================================================================ +# ============================================================================ +# Test basevectors_apex() +# ============================================================================ # test against return from _geo2apexall for different coords @@ -1207,36 +1244,30 @@ def test_basevectors_apex_delta(): e = [e1, e2, e3] for i, j in [(i, j) for i in range(3) for j in range(3)]: delta = 1 if i == j else 0 - assert_allclose(np.sum(f[i]*g[j]), delta, rtol=0, atol=1e-5) - assert_allclose(np.sum(d[i]*e[j]), delta, rtol=0, atol=1e-5) + assert_allclose(np.sum(f[i] * g[j]), delta, rtol=0, atol=1e-5) + assert_allclose(np.sum(d[i] * e[j]), delta, rtol=0, atol=1e-5) -def test_basevectors_apex_invalid_scalar(): +def test_basevectors_apex_invalid_scalar(recwarn): + """Test warning and fill values for calculating base vectors with bad value. + """ apex_out = Apex(date=2000, refh=10000) - with warnings.catch_warnings(record=True) as w: - (f1, f2, f3, g1, g2, g3, d1, d2, d3, e1, e2, - e3) = apex_out.basevectors_apex(0, 0, 0) - assert issubclass(w[-1].category, UserWarning) - assert 'set to -9999 where' in str(w[-1].message) - - invalid = [-9999, -9999, -9999] - assert not np.allclose(f1, invalid[:2]) - assert not np.allclose(f2, invalid[:2]) - assert_allclose(f3, invalid) - assert_allclose(g1, invalid) - assert_allclose(g2, invalid) - assert_allclose(g3, invalid) - assert_allclose(d1, invalid) - assert_allclose(d2, invalid) - assert_allclose(d3, invalid) - assert_allclose(e1, invalid) - assert_allclose(e2, invalid) - assert_allclose(e3, invalid) - - -###============================================================================ -### Test the get_apex() method -###============================================================================ + base_vecs = apex_out.basevectors_apex(0, 0, 0) + + assert issubclass(recwarn[-1].category, UserWarning) + assert 'set to NaN where' in str(recwarn[-1].message) + + invalid = np.ones(3) * np.nan + for i, bvec in enumerate(base_vecs): + if i < 2: + assert not np.allclose(bvec, invalid[:2]) + else: + assert_allclose(bvec, invalid) + + +# ============================================================================ +# Test the get_apex() method +# ============================================================================ def test_get_apex(): @@ -1254,16 +1285,17 @@ def test_get_apex_invalid_lat(): apex_out.get_apex(90) apex_out.get_apex(-90) - assert_allclose(apex_out.get_apex(90+1e-5), apex_out.get_apex(90), + assert_allclose(apex_out.get_apex(90 + 1e-5), apex_out.get_apex(90), rtol=0, atol=1e-8) -###============================================================================ -### Test the set_epoch() method -###============================================================================ +# ============================================================================ +# Test the set_epoch() method +# ============================================================================ def test_set_epoch(): + """Test successful setting of Apex epoch.""" apex_out = Apex(date=2000.2, refh=300) assert_allclose(apex_out.year, 2000.2) ret_2000_2_py = apex_out._geo2apex(60, 15, 100) @@ -1284,9 +1316,32 @@ def test_set_epoch(): assert_allclose(ret_2000_8_py, ret_2000_8_apex) -###============================================================================ -### Test the set_refh() method -###============================================================================ +@pytest.fixture() +def igrf_file(): + # Ensure the coefficient file exists + original_file = os.path.join(os.path.dirname(helpers.__file__), + 'igrf13coeffs.txt') + tmp_file = "temp_coeff.txt" + assert os.path.isfile(original_file) + # Move the coefficient file + os.rename(original_file, tmp_file) + yield original_file + # Move the coefficient file back + os.rename(tmp_file, original_file) + + +def test_set_epoch_file_error(igrf_file): + """Test raises OSError when IGRF coefficient file is missing.""" + # Test missing coefficient file failure + with pytest.raises(OSError) as oerr: + Apex(date=2000.2, refh=300) + error_string = "File {:} does not exist".format(igrf_file) + assert str(oerr.value).startswith(error_string) + + +# ============================================================================ +# Test the set_refh() method +# ============================================================================ def test_set_refh(): @@ -1301,5 +1356,52 @@ def test_set_refh(): assert_allclose(ret_500, fa.apxg2all(60, 15, 100, 500, 0)[2:4]) +# ============================================================================ +# Test the get_babs() method +# ============================================================================ + + +def test_get_babs(): + inputs = [[[80], [100], [300]], [range(50, 90, 8), range(0, 360, 80), + [300] * 5], [90.0, 0, 1000]] + temp1 = np.array([4.22045410e-05, 5.15672743e-05, 4.98150200e-05, + 5.06769359e-05, 4.91028428e-05]) + expected = [[5.1303124427795412e-05], temp1, [3.793962299823761e-05]] + + apex_out = Apex(date=2018.1, refh=0) + for i in range(len(inputs)): + outputs = apex_out.get_babs(*inputs[i]) + if isinstance(outputs, np.float64): + outputs = [outputs] + for j, output in enumerate(outputs): + assert_allclose(output, expected[i][j], rtol=0, atol=1e-5) + + +# ============================================================================ +# Test the bvectors_apex() method +# ============================================================================ + + +def test_bvectors_apex(): + inputs = [[80, 81], [100, 120], [100, 200]] + + expected = (np.array([5.95166171e-05, 5.95958974e-05]), + np.array([[0.0191583, 0.0020023], + [0.03547136, 0.03392595], + [-0.9412518, -0.8991005]]), + np.array([5.28257734e-05, 4.82450628e-05]), + np.array([[0.02158486, 0.00247339], + [0.03996412, 0.04190787], + [-1.0604696, -1.110636]])) + + apex_out = Apex(date=2018.1, refh=0) + + outputs = apex_out.bvectors_apex(*inputs, coords='geo', precision=1e-10) + for i, output in enumerate(outputs): + for j in range(output.size): + assert_allclose(output.ravel()[j], expected[i].ravel()[j], rtol=0, + atol=1e-5) + + if __name__ == '__main__': pytest.main() diff --git a/tests/test_cmd.py b/tests/test_cmd.py index 35f84059..f81c1d21 100644 --- a/tests/test_cmd.py +++ b/tests/test_cmd.py @@ -8,105 +8,111 @@ import subprocess os.chdir(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) -outfile = 'tests/output.txt' +outfile = os.path.join('tests', 'output.txt') +infile = os.path.join('tests', 'test_convert.txt') +singlefile = os.path.join('tests', 'test_convert_single_line.txt') + def setup_function(function): try: os.remove(outfile) - except: + except OSError: pass + teardown_function = setup_function def test_module_invocation(): pipe = subprocess.Popen(['python', '-m', 'apexpy', 'geo', 'apex', '2015', - '--height', '300', '-i', 'tests/test_convert.txt', - '-o', outfile]) + '--height', '300', '-i', infile, '-o', outfile]) pipe.communicate() pipe.wait() + assert os.path.isfile(outfile) data = np.loadtxt(outfile) - np.testing.assert_allclose(data, [[57.469547, 93.639816], - [58.522701, 94.044762], - [59.571465, 94.477257]], rtol=1e-4) + np.testing.assert_allclose(data, [[57.47145462, 93.62657928], + [58.52458191, 94.03150177], + [59.57331467, 94.46398163]], rtol=1e-4) def test_convert_YYYY(): pipe = subprocess.Popen(['python', '-m', 'apexpy', 'geo', 'apex', '2015', - '--height', '300', - '-i', 'tests/test_convert.txt', '-o', outfile]) + '--height', '300', '-i', infile, '-o', outfile]) pipe.communicate() pipe.wait() assert os.path.isfile(outfile) data = np.loadtxt(outfile) - np.testing.assert_allclose(data, [[57.469547, 93.639816], - [58.522701, 94.044762], - [59.571465, 94.477257]], rtol=1e-4) + np.testing.assert_allclose(data, [[57.47145462, 93.62657928], + [58.52458191, 94.03150177], + [59.57331467, 94.46398163]], rtol=1e-4) def test_convert_YYYYMM(): pipe = subprocess.Popen(['python', '-m', 'apexpy', 'geo', 'apex', '201501', - '--height', '300', '-i', 'tests/test_convert.txt', - '-o', outfile]) + '--height', '300', '-i', infile, '-o', outfile]) pipe.communicate() pipe.wait() assert os.path.isfile(outfile) data = np.loadtxt(outfile) - np.testing.assert_allclose(data, [[57.469547, 93.639816], - [58.522701, 94.044762], - [59.571465, 94.477257]], rtol=1e-4) + np.testing.assert_allclose(data, [[57.47145462, 93.62657928], + [58.52458191, 94.03150177], + [59.57331467, 94.46398163]], rtol=1e-4) def test_convert_YYYYMMDD(): pipe = subprocess.Popen(['python', '-m', 'apexpy', 'geo', 'apex', '20150101', '--height', '300', '-i', - 'tests/test_convert.txt', '-o', outfile]) + infile, '-o', outfile]) pipe.communicate() pipe.wait() assert os.path.isfile(outfile) data = np.loadtxt(outfile) - np.testing.assert_allclose(data, [[57.469547, 93.639816], - [58.522701, 94.044762], - [59.571465, 94.477257]], rtol=1e-4) + np.testing.assert_allclose(data, [[57.47145462, 93.62657928], + [58.52458191, 94.03150177], + [59.57331467, 94.46398163]], rtol=1e-4) def test_convert_YYYYMMDDHHMMSS(): pipe = subprocess.Popen(['python', '-m', 'apexpy', 'geo', 'apex', '20150101000000', '--height', '300', '-i', - 'tests/test_convert.txt', '-o', outfile]) + infile, '-o', outfile]) pipe.communicate() pipe.wait() assert os.path.isfile(outfile) data = np.loadtxt(outfile) - np.testing.assert_allclose(data, [[57.469547, 93.639816], - [58.522701, 94.044762], - [59.571465, 94.477257]], rtol=1e-4) + np.testing.assert_allclose(data, [[57.47145462, 93.62657928], + [58.52458191, 94.03150177], + [59.57331467, 94.46398163]], rtol=1e-4) def test_convert_single_line(): pipe = subprocess.Popen(['python', '-m', 'apexpy', 'geo', 'apex', '20150101000000', '--height', '300', '-i', - 'tests/test_convert_single_line.txt', '-o', - outfile]) + singlefile, '-o', outfile]) pipe.communicate() pipe.wait() assert os.path.isfile(outfile) data = np.loadtxt(outfile) - np.testing.assert_allclose(data, [57.469547, 93.639816], rtol=1e-4) + np.testing.assert_allclose(data, [57.47145462, 93.62657928], rtol=1e-4) def test_convert_stdin_stdout(): - pipe = subprocess.Popen('echo 60 15 | apexpy geo apex 2015 --height 300', - shell=True, stdout=subprocess.PIPE) + """ Test use of pipe input to command-line call + """ + pipe = subprocess.Popen( + 'echo 60 15 | python -m apexpy geo apex 2015 --height 300', + shell=True, stdout=subprocess.PIPE) stdout, _ = pipe.communicate() pipe.wait() np.testing.assert_allclose(np.array(stdout.split(b' '), dtype=float), - [57.469547, 93.639816], rtol=1e-4) + [57.47145462, 93.62657928], rtol=1e-4) def test_convert_refh(): - pipe = subprocess.Popen('echo 60 15 | apexpy geo apex 2000 --height 100 --refh=300', shell=True, stdout=subprocess.PIPE) + pipe = subprocess.Popen( + 'echo 60 15 | python -m apexpy geo apex 2000 --height 100 --refh=300', + shell=True, stdout=subprocess.PIPE) stdout, _ = pipe.communicate() pipe.wait() np.testing.assert_allclose(np.array(stdout.split(b' '), dtype=float), @@ -116,8 +122,7 @@ def test_convert_refh(): def test_convert_mlt(): pipe = subprocess.Popen(['python', '-m', 'apexpy', 'geo', 'mlt', '20150101000000', '--height', '300', '-i', - 'tests/test_convert_single_line.txt', '-o', - outfile]) + singlefile, '-o', outfile]) pipe.communicate() pipe.wait() assert os.path.isfile(outfile) diff --git a/tests/test_fortranapex.py b/tests/test_fortranapex.py index 56f27799..666d15ae 100644 --- a/tests/test_fortranapex.py +++ b/tests/test_fortranapex.py @@ -1,59 +1,67 @@ # -*- coding: utf-8 -*- +from numpy.testing import assert_allclose import os - import pytest -from numpy.testing import assert_allclose import apexpy from apexpy import fortranapex as fa -############################################################################## -# NOTE: the test results (numbers) were obtained by running the code that is # -# tested, therefore the tests below only check that nothing changes when # -# refactoring etc., and not if the results are actually correct # -############################################################################## - - -fa.loadapxsh(os.path.join(os.path.dirname(apexpy.__file__), 'apexsh.dat'), 2000) +# ---------------------------------------------------------------------------- +# NOTE: the test results (numbers) were obtained by running the code that is +# tested, therefore the tests below only check that nothing changes when +# refactoring etc., and not if the results are actually correct +# ---------------------------------------------------------------------------- def test_apxg2q(): - + """Test fortran apex geographic to quasi-dipole + """ + fa.loadapxsh(os.path.join(os.path.dirname(apexpy.__file__), 'apexsh.dat'), + 2000) qlat, qlon, f1, f2, f = fa.apxg2q(60, 15, 100, 1) assert_allclose(qlat, 56.531288146972656) assert_allclose(qlon, 94.1068344116211) - assert_allclose(f1, [1.07978308, 0.10027108], rtol=1e-6) + assert_allclose(f1, [1.079783, 0.10027137], rtol=1e-6) assert_allclose(f2, [-0.24546318, 0.90718889], rtol=1e-6) assert_allclose(f, 1.0041800737380981) def test_apxg2all(): - qlat, qlon, mlat, mlon, f1, f2, f, d1, d2, d3, d, e1, e2, e3 = fa.apxg2all(60, 15, 100, 300, 1) - assert_allclose(qlat, 56.531288146972656) - assert_allclose(qlon, 94.1068344116211) - assert_allclose(mlat, 55.94841766357422) - assert_allclose(mlon, 94.1068344116211) - assert_allclose(f1, [1.07978308, 0.10027108], rtol=1e-6) + fa.loadapxsh(os.path.join(os.path.dirname(apexpy.__file__), 'apexsh.dat'), + 2000) + qlat, qlon, mlat, mlon, f1, f2, f, d1, d2, d3, d, e1, e2, e3 = fa.apxg2all( + 60, 15, 100, 300, 1) + assert_allclose(qlat, 56.531288146972656, rtol=1e-6) + assert_allclose(qlon, 94.1068344116211, rtol=1e-6) + assert_allclose(mlat, 55.94841766357422, rtol=1e-6) + assert_allclose(mlon, 94.1068344116211, rtol=1e-6) + assert_allclose(f1, [1.079783, 0.10027137], rtol=1e-6) assert_allclose(f2, [-0.24546318, 0.90718889], rtol=1e-6) - assert_allclose(f, 1.0041800737380981) - assert_allclose(d1, [0.94957006, 0.2569305, 0.09049489], rtol=1e-6) - assert_allclose(d2, [0.10011058, -1.07805467, -0.33892459], rtol=1e-6) - assert_allclose(d3, [0.00865366, 0.27327025, -0.86666465], rtol=1e-6) - assert_allclose(d, 1.1003910303115845) - assert_allclose(e1, [1.02692986, 0.08382936, 0.03668636], rtol=1e-6) + assert_allclose(f, 1.0041800737380981, rtol=1e-6) + assert_allclose(d1, [0.9495701, 0.25693053, 0.09049474], rtol=1e-6) + assert_allclose(d2, [0.10011087, -1.0780545, -0.33892432], rtol=1e-6) + assert_allclose(d3, [0.00865356, 0.27327004, -0.8666646], rtol=1e-6) + assert_allclose(d, 1.100391149520874, rtol=1e-6) + assert_allclose(e1, [1.0269295, 0.08382964, 0.03668632], rtol=1e-6) assert_allclose(e2, [0.24740215, -0.82374191, -0.25726584], rtol=1e-6) - assert_allclose(e3, [0.01047837, 0.33089212, -1.04940987], rtol=1e-6) + assert_allclose(e3, [0.01047826, 0.33089194, -1.04941], rtol=1e-6) def test_apxq2g(): + """ Test fortran quasi-dipole to geographic + """ + fa.loadapxsh(os.path.join(os.path.dirname(apexpy.__file__), 'apexsh.dat'), + 2000) glat, glon, error = fa.apxq2g(60, 15, 100, 1e-2) - assert_allclose(glat, 50.97946548461914) - assert_allclose(glon, -66.16902923583984) - assert_allclose(error, 0.00010020843910751864) + assert_allclose([glat, glon, error], + [50.97946548461914, -66.16902923583984, + 0.00010020843910751864], atol=1e-6) def test_g2q2d(): + fa.loadapxsh(os.path.join(os.path.dirname(apexpy.__file__), 'apexsh.dat'), + 2000) for lat in [0, 30, 60, 89]: for lon in [-179, -90, 0, 90, 179]: qlat, qlon, _, _, _ = fa.apxg2q(lat, lon, 100, 0) @@ -63,6 +71,8 @@ def test_g2q2d(): def test_apxq2g_lowprecision(): + fa.loadapxsh(os.path.join(os.path.dirname(apexpy.__file__), 'apexsh.dat'), + 2000) glat, glon, error = fa.apxq2g(60, 15, 100, -1) assert_allclose(glat, 51.00891876220703) assert_allclose(glon, -66.11973571777344) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 97d4cd44..6725e55d 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -11,62 +11,71 @@ from apexpy import helpers -############################################################################## -# NOTE: whenever function outputs are tested against hard-coded numbers, # -# the test results (numbers) were obtained by running the code that is # -# tested. Therefore these tests below only check that nothing changes when # -# refactoring etc., and not if the results are actually correct # -############################################################################## +# ---------------------------------------------------------------------------- +# NOTE: whenever function outputs are tested against hard-coded numbers, the +# test results (numbers) were obtained by running the code that is tested. +# Therefore these tests below only check that nothing changes when refactoring, +# etc., and not if the results are actually correct +# ---------------------------------------------------------------------------- -###============================================================================ -### Test checklat -###============================================================================ +# ============================================================================ +# Test checklat +# ============================================================================ -def test_checklat_scalar(): - assert helpers.checklat(90) == 90 - assert helpers.checklat(0) == 0 - assert helpers.checklat(-90) == -90 +@pytest.mark.parametrize('lat', [(90), (0), (-90), (np.nan)]) +def test_checklat_scalar(lat): + """Test good latitude check with scalars.""" + if np.isnan(lat): + assert np.isnan(helpers.checklat(lat)) + else: + assert helpers.checklat(lat) == lat - assert helpers.checklat(90+1e-5) == 90 - assert helpers.checklat(-90-1e-5) == -90 - assert type(helpers.checklat(0.)) == float - assert type(helpers.checklat(0)) == int - assert type(helpers.checklat(90+1e-5)) == int +@pytest.mark.parametrize('lat', [(90 + 1e-5), (-90 - 1e-5)]) +def test_checklat_scalar_clip(lat): + """Test good latitude check with scalars just beyond the lat limits.""" + assert helpers.checklat(lat) == np.sign(lat) * np.floor(abs(lat)) - with pytest.raises(ValueError): - helpers.checklat(90+1e-4) - with pytest.raises(ValueError): - helpers.checklat(-90-1e-4) +@pytest.mark.parametrize('in_args,msg', + [([90 + 1e-4], "lat must be in"), + ([-90 - 1e-4, 'glat'], "glat must be in"), + ([[-90 - 1e-5, -90, 0, 90, 90 + 1e-4], 'glat'], + "glat must be in"), + ([[-90 - 1e-4, -90, np.nan, np.nan, 90 + 1e-5]], + 'lat must be in')]) +def test_checklat_error(in_args, msg): + """Test bad latitude raises ValueError with appropriate message.""" + with pytest.raises(ValueError) as verr: + helpers.checklat(*in_args) -def test_checklat_message(): - with pytest.raises(ValueError) as excinfo: - helpers.checklat(100) - assert str(excinfo.value).startswith('lat must be in') - with pytest.raises(ValueError) as excinfo: - helpers.checklat(100, name='glat') - assert str(excinfo.value).startswith('glat') + assert str(verr.value).startswith(msg) def test_checklat_array(): - assert_allclose(helpers.checklat([-90-1e-5, -90, 0, 90, 90+1e-5]), + """Test good latitude with finite values.""" + assert_allclose(helpers.checklat([-90 - 1e-5, -90, 0, 90, 90 + 1e-5]), np.array([-90, -90, 0, 90, 90]), rtol=0, atol=1e-8) - assert type(helpers.checklat([0])) == list - assert type(helpers.checklat(np.array([0]))) == np.ndarray + return - with pytest.raises(ValueError): - helpers.checklat([-90-1e-4, -90, 0, 90, 90+1e-5]) - with pytest.raises(ValueError): - helpers.checklat([-90-1e-5, -90, 0, 90, 90+1e-4]) +def test_checklat_array_withnan(): + """Test good latitude input mixed with NaNs.""" + in_lat = np.array([-90 - 1e-5, -90, 0, 90, 90 + 1e-5, np.nan, np.nan]) + fin_mask = np.isfinite(in_lat) + out_lat = helpers.checklat(in_lat) + assert_allclose(np.array([-90, -90, 0, 90, 90]), out_lat[fin_mask], + rtol=0, atol=1e-8) -###============================================================================ -### Test getsinIm -###============================================================================ + assert np.all(np.isnan(out_lat[~fin_mask])) + + +# ============================================================================ +# Test getsinIm +# ============================================================================ def test_getsinIm_scalar(): assert_allclose(helpers.getsinIm(60), 0.96076892283052284) @@ -85,9 +94,9 @@ def test_getsinIm_2Darray(): [0.96076892283052284, 0.33257924500670238]]) -###============================================================================ -### Test getcosIm -###============================================================================ +# ============================================================================ +# Test getcosIm +# ============================================================================ def test_getcosIm_scalar(): @@ -107,9 +116,9 @@ def test_getcosIm_2Darray(): [0.27735009811261463, 0.94307531289434765]]) -###============================================================================ -### Test toYearFraction -###============================================================================ +# ============================================================================ +# Test toYearFraction +# ============================================================================ def test_toYearFraction(): @@ -124,9 +133,9 @@ def test_toYearFraction(): 2005.943624682902) -###============================================================================ -### Test gc2gdlat -###============================================================================ +# ============================================================================ +# Test gc2gdlat +# ============================================================================ def test_gc2gdlat(): @@ -136,9 +145,9 @@ def test_gc2gdlat(): assert_allclose(helpers.gc2gdlat(60), 60.166364190170931) -###============================================================================ -### Test subsol -###============================================================================ +# ============================================================================ +# Test subsol +# ============================================================================ def test_subsol(): assert_allclose(helpers.subsol(dt.datetime(2005, 2, 3, 4, 5, 6)), @@ -155,5 +164,42 @@ def test_subsol(): assert_allclose(helpers.subsol(dt.datetime(2100, 12, 31, 23, 59, 59)), (-23.021061422069053, -179.23129780639425)) + +def datetime64_to_datetime(dt64): + """Convert numpy datetime64 object to a datetime datetime object. + + Notes + ----- + Works outside 32 bit int second range of 1970 + + """ + year_floor = dt64.astype('datetime64[Y]') + month_floor = dt64.astype('datetime64[M]') + day_floor = dt64.astype('datetime64[D]') + year = year_floor.astype(int) + 1970 + month = (month_floor - year_floor).astype('timedelta64[M]').astype(int) + 1 + day = (day_floor - month_floor).astype('timedelta64[D]').astype(int) + 1 + return dt.datetime(year, month, day) + + +def test_subsol_array(): + """Verify subsolar point calculation using an array of np.datetime64. + + Notes + ----- + Tested by ensuring the array of np.datetime64 is equivalent to converting + using single dt.datetime values + + """ + dates = np.arange(np.datetime64("1601"), np.datetime64("2100"), + np.timedelta64(100, 'D')).astype('datetime64[s]') + sslat, sslon = helpers.subsol(dates) + for i, date in enumerate(dates): + datetime = datetime64_to_datetime(date) + true_sslat, true_sslon = helpers.subsol(datetime) + assert sslat[i] == true_sslat + assert sslon[i] == true_sslon + + if __name__ == '__main__': pytest.main() diff --git a/tox.ini b/tox.ini index 36546b36..6e4f4d6a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,14 @@ envlist = check, 2.7, 2.7-nocover, - 3.4, - 3.4-nocover, - 3.5, - 3.5-nocover, 3.6, 3.6-nocover, + 3.7, + 3.7-nocover, + 3.8, + 3.8-nocover, + 3.9, + 3.9-nocover, report, docs @@ -28,22 +30,11 @@ commands = #conda install -q -p {toxworkdir}\{envdir} numpy python setup.py clean --all build_ext --force --inplace # python setup.py develop - {posargs:py.test -vv --ignore=src --doctest-glob='*.rst'} - -[testenv:spell] -setenv = - SPELLCHECK=1 -commands = - sphinx-build -b spelling docs dist/docs -skip_install = true -usedevelop = true -deps = - -r{toxinidir}/docs/requirements.txt - sphinxcontrib-spelling - pyenchant + python -m pytest {posargs: -vv --ignore=src/fortranapex --doctest-glob='*.rst'} [testenv:docs] deps = + sphinx_rtd_theme -r{toxinidir}/docs/requirements.txt commands = sphinx-build {posargs:-E} -b html docs dist/docs @@ -61,19 +52,21 @@ passenv = * [testenv:check] -basepython = python3.4 deps = docutils check-manifest flake8 - readme + numpy pygments + readme + twine skip_install = true usedevelop = false commands = - python setup.py check --strict --metadata --restructuredtext + python setup.py sdist check-manifest {toxinidir} - flake8 src tests + flake8 --ignore=F401,W503 src tests + twine check dist/* [testenv:coveralls] deps = @@ -83,19 +76,7 @@ usedevelop = false commands = coverage combine coverage report - coveralls --merge=extension-coveralls.json [] - -[testenv:codecov] -deps = - codecov -skip_install = true -usedevelop = false -commands = - coverage combine - coverage report - coverage xml --ignore-errors - codecov [] - + coveralls --rcfile=setup.cfg --merge=extension-coveralls.json [] [testenv:extension-coveralls] deps = @@ -103,10 +84,9 @@ deps = skip_install = true usedevelop = false commands = - coveralls --build-root=. --include=src --dump=extension-coveralls.json [] + coveralls --rcfile=setup.cfg --build-root=. --include=src --dump=extension-coveralls.json [] [testenv:report] -basepython = python3.4 deps = coverage skip_install = true usedevelop = false @@ -129,16 +109,23 @@ setenv = usedevelop = true commands = python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} + python -m pytest {posargs:--cov --cov-report=term-missing -vv --doctest-glob='*.rst'} deps = {[testenv]deps} pytest-cov + [testenv:2.7-nocover] basepython = {env:TOXPYTHON:python2.7} -[testenv:3.4] -basepython = {env:TOXPYTHON:python3.4} +[testenv:2.7-buildonly-nocover] +basepython = {env:TOXPYTHON:python2.7} +deps = +skip_install = true +commands = + +[testenv:3.6] +basepython = {env:TOXPYTHON:python3.6} setenv = {[testenv]setenv} WITH_COVERAGE=yes @@ -146,16 +133,23 @@ setenv = usedevelop = true commands = python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} + python -m pytest {posargs:--cov --cov-report=term-missing -vv --doctest-glob='*.rst'} deps = {[testenv]deps} pytest-cov -[testenv:3.4-nocover] -basepython = {env:TOXPYTHON:python3.4} -[testenv:3.5] -basepython = {env:TOXPYTHON:python3.5} +[testenv:3.6-nocover] +basepython = {env:TOXPYTHON:python3.6} + +[testenv:3.6-buildonly-nocover] +basepython = {env:TOXPYTHON:python3.6} +deps = +skip_install = true +commands = + +[testenv:3.7] +basepython = {env:TOXPYTHON:python3.7} setenv = {[testenv]setenv} WITH_COVERAGE=yes @@ -163,16 +157,23 @@ setenv = usedevelop = true commands = python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} + python -m pytest {posargs:--cov --cov-report=term-missing -vv --doctest-glob='*.rst'} deps = {[testenv]deps} pytest-cov -[testenv:3.5-nocover] -basepython = {env:TOXPYTHON:python3.5} -[testenv:3.6] -basepython = {env:TOXPYTHON:python3.6} +[testenv:3.7-nocover] +basepython = {env:TOXPYTHON:python3.7} + +[testenv:3.7-buildonly-nocover] +basepython = {env:TOXPYTHON:python3.7} +deps = +skip_install = true +commands = + +[testenv:3.8] +basepython = {env:TOXPYTHON:python3.8} setenv = {[testenv]setenv} WITH_COVERAGE=yes @@ -180,11 +181,42 @@ setenv = usedevelop = true commands = python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} + python -m pytest {posargs:--cov --cov-report=term-missing -vv --doctest-glob='*.rst'} deps = {[testenv]deps} pytest-cov -[testenv:3.6-nocover] -basepython = {env:TOXPYTHON:python3.6} + +[testenv:3.8-nocover] +basepython = {env:TOXPYTHON:python3.8} + +[testenv:3.8-buildonly-nocover] +basepython = {env:TOXPYTHON:python3.8} +deps = +skip_install = true +commands = + +[testenv:3.9] +basepython = {env:TOXPYTHON:python3.9} +setenv = + {[testenv]setenv} + WITH_COVERAGE=yes + PY_CCOV=-coverage +usedevelop = true +commands = + python setup.py clean --all build_ext --force --inplace + python -m pytest {posargs:--cov --cov-report=term-missing -vv --doctest-glob='*.rst'} +deps = + {[testenv]deps} + pytest-cov + + +[testenv:3.9-nocover] +basepython = {env:TOXPYTHON:python3.9} + +[testenv:3.9-buildonly-nocover] +basepython = {env:TOXPYTHON:python3.9} +deps = +skip_install = true +commands =