├── .circleci └── config.yml ├── .github ├── dependabot.yml ├── release.yml └── workflows │ └── build-and-publish-to-pypi.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── images │ ├── set_grid.png │ ├── set_point_psd.png │ └── set_point_ts.png ├── plot_grid_SET.ipynb ├── plot_point_SET.ipynb └── requirements.txt ├── pyproject.toml ├── requirements.txt ├── src └── pysolid │ ├── __init__.py │ ├── grid.py │ ├── point.py │ └── solid.for └── tests ├── grid.py ├── point.py └── requirements.txt /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | # The python orb contains a set of prepackaged CircleCI configuration you can use repeatedly in your configuration files 5 | # Orb commands and jobs help you with common scripting around a language/tool 6 | # so you dont have to copy and paste it everywhere. 7 | # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python 8 | python: circleci/python@1.2 9 | 10 | workflows: 11 | build-and-test: # This is the name of the workflow, feel free to change it to better match your workflow. 12 | # Inside the workflow, you define the jobs you want to run. 13 | # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 14 | jobs: 15 | - build-and-test 16 | 17 | 18 | jobs: 19 | build-and-test: # This is the name of the job, feel free to change it to better match what you're trying to do! 20 | # These next lines defines a Docker executors: https://circleci.com/docs/2.0/executor-types/ 21 | # You can specify an image from Dockerhub or use one of the convenience images from CircleCI's Developer Hub 22 | # A list of available CircleCI Docker convenience images are available here: https://circleci.com/developer/images/image/cimg/python 23 | # The executor is the environment in which the steps below will be executed - below will use a python 3.9 container 24 | # Change the version below to your required version of python 25 | docker: 26 | - image: cimg/base:current 27 | environment: 28 | CONDA_PREFIX: /root/tools/miniforge 29 | PYSOLID_HOME: /root/tools/PySolid 30 | user: root 31 | working_directory: /root/tools/PySolid 32 | # Checkout the code as the first step. This is a dedicated CircleCI step. 33 | steps: 34 | - checkout 35 | - run: 36 | name: Setting Environment with Miniforge 37 | command: | 38 | apt update 39 | apt-get update --yes && apt-get upgrade --yes 40 | apt-get install --yes wget git 41 | # download and install miniforge 42 | mkdir -p ${HOME}/tools 43 | cd ${HOME}/tools 44 | wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh 45 | bash Miniforge3-Linux-x86_64.sh -b -p ${HOME}/tools/miniforge 46 | ${HOME}/tools/miniforge/bin/mamba init bash 47 | # modify/export env var PATH to BASH_ENV to be shared across run steps 48 | echo 'export PATH=${CONDA_PREFIX}/bin:${PATH}' >> ${BASH_ENV} 49 | 50 | - run: 51 | name: Installing PySolid 52 | no_output_timeout: 10m 53 | command: | 54 | export PYTHONUNBUFFERED=1 55 | # install dependencies and source code 56 | mamba install --verbose --yes fortran-compiler --file ${PYSOLID_HOME}/requirements.txt --file ${PYSOLID_HOME}/tests/requirements.txt 57 | python -m pip install ${PYSOLID_HOME} 58 | 59 | - run: 60 | name: Unit Test 61 | command: | 62 | # run tests 63 | python ${PYSOLID_HOME}/tests/point.py 64 | python ${PYSOLID_HOME}/tests/grid.py 65 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # see doc for possible values 9 | directory: "/" # location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | authors: 6 | - dependabot 7 | - pre-commit-ci 8 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish 📦 to PyPI 2 | 3 | # Build on every branch push, tag push, and pull request change: 4 | on: 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - v* 10 | pull_request: 11 | 12 | jobs: 13 | build_wheels: 14 | name: Build 🐍 wheels 📦 on ${{ matrix.os }} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | # skip building wheel for windows as it's not working yet 20 | os: [ubuntu-latest, macos-13] #windows-2019 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Provide gfortran (macOS-13) 28 | if: runner.os == 'macOS' 29 | run: | 30 | # https://github.com/actions/virtual-environments/issues/2524 31 | # https://github.com/cbg-ethz/dce/blob/master/.github/workflows/pkgdown.yaml 32 | sudo ln -s /usr/local/bin/gfortran-13 /usr/local/bin/gfortran 33 | sudo mkdir /usr/local/gfortran 34 | sudo ln -s /usr/local/Cellar/gcc@13/*/lib/gcc/13 /usr/local/gfortran/lib 35 | gfortran --version 36 | 37 | - name: Provide gfortran (Windows) 38 | if: runner.os == 'Windows' 39 | uses: msys2/setup-msys2@v2 40 | 41 | - name: Tell distutils to use mingw (Windows) 42 | if: runner.os == 'Windows' 43 | run: | 44 | echo "[build]`ncompiler=mingw32" | Out-File -Encoding ASCII ~/pydistutils.cfg 45 | 46 | - name: Build wheels 47 | uses: pypa/cibuildwheel@v2.21.3 48 | env: 49 | # Disable building for PyPy and 32bit. 50 | CIBW_SKIP: pp* *-win32 *-manylinux_i686 51 | CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="13.0" 52 | CIBW_BEFORE_BUILD_MACOS: python -m pip install --upgrade pip 53 | # Package the DLL dependencies in the wheel for windows (done by default for the other platforms). 54 | # delvewheel cannot mangle the libraries, stripping does not work. 55 | CIBW_BEFORE_BUILD_WINDOWS: pip install delvewheel 56 | CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel show {wheel} && delvewheel repair -w {dest_dir} {wheel} --no-mangle-all" 57 | 58 | - uses: actions/upload-artifact@v4 59 | with: 60 | name: artifact-wheels-${{ matrix.os }} 61 | path: ./wheelhouse/*.whl 62 | 63 | build_sdist: 64 | name: Build 🐍 source distribution 📦 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v4 68 | with: 69 | fetch-depth: 0 70 | 71 | - name: Build sdist 72 | run: pipx run build --sdist 73 | 74 | - uses: actions/upload-artifact@v4 75 | with: 76 | name: artifact-source 77 | path: dist/*.tar.gz 78 | 79 | upload_pypi: 80 | name: Upload 📦 to PyPI 81 | needs: [build_wheels, build_sdist] 82 | runs-on: ubuntu-latest 83 | if: github.repository_owner == 'insarlab' && github.event_name == 'push' 84 | steps: 85 | - uses: actions/download-artifact@v4 86 | with: 87 | # unpacks default artifact into dist/ 88 | # if `name: artifact` is omitted, the action will create extra parent dir 89 | path: dist 90 | pattern: artifact-* 91 | merge-multiple: true 92 | - name: Display structure of downloaded files 93 | run: ls -R dist 94 | 95 | - name: Publish developed version 📦 to Test PyPI 96 | uses: pypa/gh-action-pypi-publish@release/v1 97 | with: 98 | user: __token__ 99 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 100 | repository-url: https://test.pypi.org/legacy/ 101 | skip-existing: false 102 | verbose: true 103 | 104 | - name: Publish released version 📦 to PyPI 105 | uses: pypa/gh-action-pypi-publish@release/v1 106 | if: startsWith(github.ref, 'refs/tags/v') 107 | with: 108 | user: __token__ 109 | password: ${{ secrets.PYPI_API_TOKEN }} 110 | verbose: true 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | tests/*/*.png 3 | solid.txt 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | ## C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # Scikit-build temp directory 136 | /_skbuild/ 137 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Based on: 2 | # https://github.com/scikit-build/scikit-build-core/tree/main/docs/examples/getting_started/fortran 3 | 4 | cmake_minimum_required(VERSION 3.17.2...3.29) 5 | project(${SKBUILD_PROJECT_NAME} LANGUAGES C Fortran) 6 | 7 | find_package( 8 | Python 9 | COMPONENTS Interpreter Development.Module NumPy 10 | REQUIRED) 11 | 12 | # F2PY headers 13 | execute_process( 14 | COMMAND "${PYTHON_EXECUTABLE}" -c 15 | "import numpy.f2py; print(numpy.f2py.get_include())" 16 | OUTPUT_VARIABLE F2PY_INCLUDE_DIR 17 | OUTPUT_STRIP_TRAILING_WHITESPACE) 18 | 19 | add_library(fortranobject OBJECT "${F2PY_INCLUDE_DIR}/fortranobject.c") 20 | target_link_libraries(fortranobject PUBLIC Python::NumPy) 21 | target_include_directories(fortranobject PUBLIC "${F2PY_INCLUDE_DIR}") 22 | set_property(TARGET fortranobject PROPERTY POSITION_INDEPENDENT_CODE ON) 23 | 24 | add_custom_command( 25 | OUTPUT solidmodule.c solid-f2pywrappers.f 26 | DEPENDS src/pysolid/solid.for 27 | VERBATIM 28 | COMMAND "${Python_EXECUTABLE}" -m numpy.f2py 29 | "${CMAKE_CURRENT_SOURCE_DIR}/src/pysolid/solid.for" -m solid --lower) 30 | 31 | python_add_library( 32 | solid MODULE "${CMAKE_CURRENT_BINARY_DIR}/solidmodule.c" 33 | "${CMAKE_CURRENT_BINARY_DIR}/solid-f2pywrappers.f" 34 | "${CMAKE_CURRENT_SOURCE_DIR}/src/pysolid/solid.for" WITH_SOABI) 35 | target_link_libraries(solid PRIVATE fortranobject) 36 | 37 | install(TARGETS solid DESTINATION pysolid) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Language](https://img.shields.io/badge/python-3.8%2B-blue.svg?style=flat-square)](https://www.python.org/) 2 | [![CircleCI](https://img.shields.io/circleci/build/github/insarlab/PySolid.svg?logo=circleci&label=tests&style=flat-square)](https://circleci.com/gh/insarlab/PySolid) 3 | [![Conda Download](https://img.shields.io/conda/dn/conda-forge/pysolid?color=green&label=conda%20downloads&style=flat-square)](https://anaconda.org/conda-forge/pysolid) 4 | [![Version](https://img.shields.io/github/v/release/insarlab/PySolid?color=yellow&label=version&style=flat-square)](https://github.com/insarlab/PySolid/releases) 5 | [![License](https://img.shields.io/badge/license-GPLv3+-blue.svg?style=flat-square)](https://github.com/insarlab/PySolid/blob/main/LICENSE) 6 | [![Citation](https://img.shields.io/badge/doi-10.1109%2FTGRS.2022.3168509-blue?style=flat-square)](https://doi.org/10.1109/TGRS.2022.3168509) 7 | 8 | ## PySolid 9 | 10 | The Python based solid Earth tides (PySolid) is a thin Python wrapper of the [`solid.for`](http://geodesyworld.github.io/SOFTS/solid.htm) program (by Dennis Milbert based on [_dehanttideinelMJD.f_](https://iers-conventions.obspm.fr/content/chapter7/software/dehanttideinel/) from V. Dehant, S. Mathews, J. Gipson and C. Bruyninx) to calculate [solid Earth tides](https://en.wikipedia.org/wiki/Earth_tide) in east, north and up directions (section 7.1.1 in the [2010 IERS Conventions](https://www.iers.org/IERS/EN/Publications/TechnicalNotes/tn36.html)). Solid Earth tides introduce large offsets in SAR observations and long spatial wavelength ramps in InSAR observations, as shown in the Sentinel-1 data with regular acquisitions and large swaths ([Yunjun et al., 2022](https://doi.org/10.1109/TGRS.2022.3168509)). 11 | 12 | This is research code provided to you "as is" with NO WARRANTIES OF CORRECTNESS. Use at your own risk. 13 | 14 | ### 1. Install 15 | 16 | PySolid is available on the [conda-forge](https://anaconda.org/conda-forge/pysolid) channel and the main archive of the [Debian](https://tracker.debian.org/pkg/pysolid) GNU/Linux OS. The released version can be installed via `conda` as: 17 | 18 | ```shell 19 | # run "conda update pysolid" to update the installed version 20 | conda install -c conda-forge pysolid 21 | ``` 22 | 23 | or via `apt` (or other package managers) for [Debian-derivative OS](https://wiki.debian.org/Derivatives/Census) users, including [Ubuntu](https://ubuntu.com), as: 24 | 25 | ```shell 26 | apt install python3-pysolid 27 | ``` 28 | 29 |
30 |

Or build from source:

31 | 32 | PySolid relies on a few Python modules as described in [requirements.txt](./requirements.txt) and [NumPy's f2py](https://numpy.org/doc/stable/f2py/) to build the Fortran source code. You could use `conda` to install all the dependencies, including the Fortran compiler, or use your own installed Fortran compiler and `pip` to install the rest. 33 | 34 | ##### a. Download source code 35 | 36 | ```bash 37 | # run "cd PySolid; git pull" to update to the latest development version 38 | git clone https://github.com/insarlab/PySolid.git 39 | ``` 40 | 41 | ##### b. Install dependencies 42 | 43 | ```bash 44 | # option 1: use conda to install dependencies into an existing, activated environment 45 | conda install -c conda-forge fortran-compiler --file PySolid/requirements.txt --file PySolid/tests/requirements.txt 46 | 47 | # option 2: use conda to install dependencies into a new environment, e.g. named "pysolid" 48 | conda create --name pysolid fortran-compiler --file PySolid/requirements.txt --file PySolid/tests/requirements.txt 49 | conda activate pysolid 50 | 51 | # option 3: have a Fortran compiler already installed and use pip to install the dependencies 52 | python -m pip install -r PySolid/requirements.txt -r PySolid/tests/requirements.txt 53 | ``` 54 | 55 | ##### c. Install PySolid 56 | 57 | ```bash 58 | # option 1: use pip to install pysolid into the current environment 59 | python -m pip install ./PySolid 60 | 61 | # option 2: use pip to install pysolid in develop mode (editable) into the current environment 62 | python -m pip install -e ./PySolid 63 | 64 | # option 3: manually compile the Fortran code and setup environment variable 65 | cd PySolid/src/pysolid 66 | f2py -c -m solid solid.for 67 | # Replace with proper path to PySolid main folder 68 | export PYTHONPATH=${PYTHONPATH}:/PySolid/src 69 | ``` 70 | 71 | ##### d. Test the installation 72 | 73 | To test the installation, run the following: 74 | 75 | ```bash 76 | python -c "import pysolid; print(pysolid.__version__)" 77 | python PySolid/tests/grid.py 78 | python PySolid/tests/point.py 79 | ``` 80 |
81 | 82 | ### 2. Usage 83 | 84 | PySolid could compute solid Earth tides in two modes: **point** and **grid**. Both modes produce displacement in east, north and up directions. 85 | 86 | + **Point mode:** compute 1D tides time-series at a specific point for a given time period 87 | + **Grid mode:** compute 2D tides grid at a specific time for a given spatial grid 88 | 89 | #### 2.1 Point Mode [[notebook](./docs/plot_point_SET.ipynb)] 90 | 91 | ```python 92 | import datetime as dt 93 | import pysolid 94 | 95 | # prepare inputs 96 | lat, lon = 34.0, -118.0 # point of interest in degree, Los Angles, CA 97 | step_sec = 60 * 5 # sample spacing in time domain in seconds 98 | dt0 = dt.datetime(2020, 1, 1, 4, 0, 0) # start date and time 99 | dt1 = dt.datetime(2021, 1, 1, 2, 0, 0) # end date and time 100 | 101 | # compute SET via pysolid 102 | dt_out, tide_e, tide_n, tide_u = pysolid.calc_solid_earth_tides_point( 103 | lat, lon, dt0, dt1, 104 | step_sec=step_sec, 105 | display=False, 106 | verbose=False, 107 | ) 108 | 109 | # plot the power spectral density of SET up component 110 | pysolid.plot_power_spectral_density4tides(tide_u, sample_spacing=step_sec) 111 | ``` 112 | 113 |

114 | 115 | 116 |

117 | 118 | #### 2.2 Grid Mode [[notebook](./docs/plot_grid_SET.ipynb)] 119 | 120 | ```python 121 | import datetime as dt 122 | import numpy as np 123 | import pysolid 124 | 125 | # prepare inputs 126 | dt_obj = dt.datetime(2020, 12, 25, 14, 7, 44) 127 | meta = { 128 | 'LENGTH' : 500, # number of rows 129 | 'WIDTH' : 450, # number of columns 130 | 'X_FIRST': -126, # min longitude in degree (upper left corner of the upper left pixel) 131 | 'Y_FIRST': 43, # max laitude in degree (upper left corner of the upper left pixel) 132 | 'X_STEP' : 0.000925926 * 30, # output resolution in degree 133 | 'Y_STEP' : -0.000925926 * 30, # output resolution in degree 134 | } 135 | 136 | # compute SET via pysolid 137 | tide_e, tide_n, tide_u = pysolid.calc_solid_earth_tides_grid( 138 | dt_obj, meta, 139 | display=False, 140 | verbose=True, 141 | ) 142 | 143 | # project SET from ENU to satellite line-of-sight (LOS) direction with positive for motion towards the satellite 144 | # inc_angle : incidence angle of the LOS vector (from ground to radar platform) measured from vertical. 145 | # az_angle : azimuth angle of the LOS vector (from ground to radar platform) measured from the north, with anti-clockwirse as positive. 146 | inc_angle = np.deg2rad(34) # radian, typical value for Sentinel-1 147 | az_angle = np.deg2rad(-102) # radian, typical value for Sentinel-1 descending track 148 | tide_los = ( tide_e * np.sin(inc_angle) * np.sin(az_angle) * -1 149 | + tide_n * np.sin(inc_angle) * np.cos(az_angle) 150 | + tide_u * np.cos(inc_angle)) 151 | ``` 152 | 153 |

154 | 155 |

156 | 157 | ### 3. Citing this work 158 | 159 | + Yunjun, Z., Fattahi, H., Pi, X., Rosen, P., Simons, M., Agram, P., & Aoki, Y. (2022). Range Geolocation Accuracy of C-/L-band SAR and its Implications for Operational Stack Coregistration. _IEEE Trans. Geosci. Remote Sens., 60_, 5227219. [ [doi](https://doi.org/10.1109/TGRS.2022.3168509) \| [arxiv](https://doi.org/10.31223/X5F641) \| [data](https://doi.org/10.5281/zenodo.6360749) \| [notebook](https://github.com/yunjunz/2022-Geolocation) ] 160 | + Milbert, D. (2018), "solid: Solid Earth Tide", [Online]. Available: http://geodesyworld.github.io/SOFTS/solid.htm. Accessd on: 2020-09-06. 161 | -------------------------------------------------------------------------------- /docs/images/set_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insarlab/PySolid/9ac869b05a44057de193c4a71fa2a35891db8b1e/docs/images/set_grid.png -------------------------------------------------------------------------------- /docs/images/set_point_psd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insarlab/PySolid/9ac869b05a44057de193c4a71fa2a35891db8b1e/docs/images/set_point_psd.png -------------------------------------------------------------------------------- /docs/images/set_point_ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insarlab/PySolid/9ac869b05a44057de193c4a71fa2a35891db8b1e/docs/images/set_point_ts.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # extra dependencies required for running notebooks 2 | cartopy 3 | matplotlib 4 | mintpy 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["cmake", "scikit-build-core", "setuptools", "setuptools_scm[toml]>=6.2", "numpy", "wheel"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "pysolid" 7 | description = "A Python wrapper for solid to compute solid Earth tides" 8 | authors = [ 9 | {name="Zhang Yunjun", email="yunjunz@outlook.com"}, 10 | {name="Dennis Milbert"}, 11 | ] 12 | readme = "README.md" 13 | requires-python = ">=3.8" 14 | dependencies = [ 15 | "numpy", 16 | "scipy", 17 | ] 18 | 19 | keywords = ["solid Earth tides", "deformation", "geodesy", "geophysics"] 20 | license = {text = "GPL-3.0-or-later"} 21 | classifiers = [ 22 | "Development Status :: 4 - Beta", 23 | "Intended Audience :: Science/Research", 24 | "Topic :: Scientific/Engineering", 25 | "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", 26 | "Operating System :: OS Independent", 27 | "Programming Language :: Python :: 3", 28 | ] 29 | 30 | # see section: setuptools_scm 31 | # https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#dynamic-metadata 32 | # dependencies will be read from text files 33 | dynamic = ["version"] 34 | 35 | [project.urls] 36 | "Homepage" = "https://github.com/insarlab/PySolid" 37 | "Bug Tracker" = "https://github.com/insarlab/PySolid/issues" 38 | 39 | [tool.setuptools] 40 | include-package-data = true 41 | zip-safe = false 42 | 43 | [tool.setuptools.packages.find] 44 | where = ["src"] 45 | 46 | [tool.setuptools.package-data] 47 | pysolid = ["*.for"] 48 | 49 | [tool.setuptools_scm] 50 | version_scheme = "post-release" 51 | local_scheme = "no-local-version" 52 | 53 | [tool.scikit-build] 54 | metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # for running 2 | numpy 3 | scipy 4 | -------------------------------------------------------------------------------- /src/pysolid/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib.metadata import PackageNotFoundError, version 2 | 3 | # get version info 4 | try: 5 | __version__ = version(__name__) 6 | except PackageNotFoundError: 7 | print('package is not installed!\n' 8 | 'Please follow the installation instructions in the README.md.\n' 9 | 'Or, to just get the version number, use:\n' 10 | ' python -m setuptools_scm') 11 | 12 | 13 | # top-level functions 14 | from pysolid.grid import ( 15 | calc_solid_earth_tides_grid, 16 | plot_solid_earth_tides_grid, 17 | ) 18 | from pysolid.point import ( 19 | TIDES, 20 | calc_solid_earth_tides_point, 21 | plot_solid_earth_tides_point, 22 | plot_power_spectral_density4tides, 23 | ) 24 | 25 | __all__ = [ 26 | '__version__', 27 | 'calc_solid_earth_tides_grid', 28 | 'plot_solid_earth_tides_grid', 29 | 'TIDES', 30 | 'calc_solid_earth_tides_point', 31 | 'plot_solid_earth_tides_point', 32 | 'plot_power_spectral_density4tides', 33 | ] 34 | -------------------------------------------------------------------------------- /src/pysolid/grid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ####################################################################### 3 | # A Python wrapper for solid Earth tides calculation using solid.for. 4 | # Fortran code is originally written by Dennis Milbert, 2018-06-01. 5 | # Available at: http://geodesyworld.github.io/SOFTS/solid.htm. 6 | # Author: Zhang Yunjun, Simran Sangha, Sep 2020 7 | # Copyright 2020, by the California Institute of Technology. 8 | ####################################################################### 9 | # Recommend usage: 10 | # import pysolid 11 | # pysolid.calc_solid_earth_tides_grid() 12 | 13 | 14 | import os 15 | 16 | import numpy as np 17 | from scipy import ndimage 18 | 19 | 20 | ################################## Earth tides - grid mode ################################### 21 | def calc_solid_earth_tides_grid(dt_obj, atr, step_size=1e3, display=False, verbose=True): 22 | """Calculate SET in east/north/up direction for a spatial grid at a given date/time. 23 | 24 | Note that we use step_size to speedup >30 times, by feeding the Fortran code (SET calc and 25 | text file writing) the coarse grid, then resize the output to the same shape as the original 26 | input size. This uses the fact that SET varies slowly in space. Comparison w and w/o step_size 27 | shows a difference in tide_u with max of 5e-8 m, thus negligible. 28 | 29 | Parameters: dt_obj - datetime.datetime object (with precision up to the second) 30 | atr - dict, metadata including the following keys: 31 | LENGTH/WIDTTH 32 | X/Y_FIRST 33 | X/Y_STEP 34 | step_size - float, grid step feeded into the fortran code in meters 35 | to speedup the calculation 36 | display - bool, plot the calculated SET 37 | verbose - bool, print verbose message 38 | Returns: tide_e - 2D np.ndarray, SET in east direction in meters 39 | tide_n - 2D np.ndarray, SET in north direction in meters 40 | tide_u - 2D np.ndarray, SET in up direction in meters 41 | Examples: atr = readfile.read_attribute('geo_velocity.h5') 42 | tide_e, tide_n, tide_u = calc_solid_earth_tides_grid('20180219', atr) 43 | """ 44 | try: 45 | from pysolid.solid import solid_grid 46 | except ImportError: 47 | msg = "Cannot import name 'solid' from 'pysolid'!" 48 | msg += '\n Maybe solid.for is NOT compiled yet.' 49 | msg += '\n Check instruction at: https://github.com/insarlab/PySolid.' 50 | raise ImportError(msg) 51 | 52 | vprint = print if verbose else lambda *args, **kwargs: None 53 | 54 | # location 55 | lat0 = float(atr['Y_FIRST']) 56 | lon0 = float(atr['X_FIRST']) 57 | lat1 = lat0 + float(atr['Y_STEP']) * int(atr['LENGTH']) 58 | lon1 = lon0 + float(atr['X_STEP']) * int(atr['WIDTH']) 59 | 60 | vprint('PYSOLID: ----------------------------------------') 61 | vprint('PYSOLID: datetime: {}'.format(dt_obj.isoformat())) 62 | vprint('PYSOLID: SNWE: {}'.format((lat1, lat0, lon0, lon1))) 63 | 64 | # step size 65 | num_step = int(step_size / 108e3 / abs(float(atr['Y_STEP']))) 66 | num_step = max(1, num_step) 67 | length = np.rint(int(atr['LENGTH']) / num_step - 1e-4).astype(int) 68 | width = np.rint(int(atr['WIDTH']) / num_step - 1e-4).astype(int) 69 | lat_step = float(atr['Y_STEP']) * num_step 70 | lon_step = float(atr['X_STEP']) * num_step 71 | vprint('SOLID : calculate solid Earth tides in east/north/up direction') 72 | vprint('SOLID : shape: {s}, step size: {la:.4f} by {lo:.4f} deg'.format( 73 | s=(length, width), la=lat_step, lo=lon_step)) 74 | 75 | ## calc solid Earth tides 76 | tide_e, tide_n, tide_u = solid_grid(dt_obj.year, dt_obj.month, dt_obj.day, 77 | dt_obj.hour, dt_obj.minute, dt_obj.second, 78 | lat0, lat_step, length, 79 | lon0, lon_step, width) 80 | 81 | # resample to the input size 82 | # via scipy.ndimage.zoom or skimage.transform.resize 83 | if num_step > 1: 84 | in_shape = tide_e.shape 85 | out_shape = (int(atr['LENGTH']), int(atr['WIDTH'])) 86 | vprint('PYSOLID: resize data to the shape of {} using order-1 spline interpolation'.format(out_shape)) 87 | 88 | enu = np.stack([tide_e, tide_n, tide_u]) 89 | zoom_factors = [1, *np.divide(out_shape, in_shape)] 90 | kwargs = dict(order=1, mode="nearest", grid_mode=True) 91 | tide_e, tide_n, tide_u = ndimage.zoom(enu, zoom_factors, **kwargs) 92 | 93 | # plot 94 | if display: 95 | plot_solid_earth_tides_grid(tide_e, tide_n, tide_u, dt_obj) 96 | 97 | return tide_e, tide_n, tide_u 98 | 99 | 100 | ######################################### Plot ############################################### 101 | def plot_solid_earth_tides_grid(tide_e, tide_n, tide_u, dt_obj=None, 102 | out_fig=None, save=False, display=True): 103 | """Plot the solid Earth tides in ENU direction.""" 104 | from matplotlib import pyplot as plt, ticker 105 | 106 | # plot 107 | fig, axs = plt.subplots(nrows=1, ncols=3, figsize=[6, 3], sharex=True, sharey=True) 108 | for ax, data, label in zip(axs.flatten(), 109 | [tide_e, tide_n, tide_u], 110 | ['East', 'North', 'Up']): 111 | im = ax.imshow(data*100, cmap='RdBu') 112 | ax.tick_params(which='both', direction='in', bottom=True, top=True, left=True, right=True) 113 | fig.colorbar(im, ax=ax, orientation='horizontal', label=label+' [cm]', pad=0.1, ticks=ticker.MaxNLocator(3)) 114 | fig.tight_layout() 115 | 116 | # super title 117 | if dt_obj is not None: 118 | axs[1].set_title('solid Earth tides at {}'.format(dt_obj.isoformat()), fontsize=12) 119 | 120 | # output 121 | if out_fig: 122 | save = True 123 | 124 | if save: 125 | if not out_fig: 126 | out_fig = os.path.abspath('SET_grid.png') 127 | print('save figure to {}'.format(out_fig)) 128 | fig.savefig(out_fig, bbox_inches='tight', transparent=True, dpi=300) 129 | 130 | if display: 131 | print('showing...') 132 | plt.show() 133 | else: 134 | plt.close() 135 | 136 | return 137 | -------------------------------------------------------------------------------- /src/pysolid/point.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ####################################################################### 3 | # A Python wrapper for solid Earth tides calculation using solid.for. 4 | # Fortran code is originally written by Dennis Milbert, 2018-06-01. 5 | # Available at: http://geodesyworld.github.io/SOFTS/solid.htm. 6 | # Author: Zhang Yunjun, Jan 2021 7 | # Copyright 2020, by the California Institute of Technology. 8 | ####################################################################### 9 | # Recommend usage: 10 | # import pysolid 11 | # pysolid.calc_solid_earth_tides_point() 12 | 13 | 14 | import collections 15 | import datetime as dt 16 | import os 17 | 18 | import numpy as np 19 | 20 | 21 | ## Tidal constituents 22 | # https://en.wikipedia.org/wiki/Theory_of_tides#Tidal_constituents. Accessed on: 2022-03-07. 23 | # unit: period (hour), speed (deg per hour) 24 | Tag = collections.namedtuple('Tag', 'species symbol period speed doodson_num noaa_order') 25 | TIDES = ( 26 | # Semi-diurnal 27 | Tag('Principal lunar semidiurnal' , r'$M_2$' , 12.4206012 , 28.9841042, 255.555, 1 ), 28 | Tag('Principal solar semidiurnal' , r'$S_2$' , 12.0 , 30.0 , 273.555, 2 ), 29 | Tag('Larger lunar elliptic semidiurnal' , r'$N_2$' , 12.65834751, 28.4397295, 245.655, 3 ), 30 | Tag('Larger lunar evectional' , r'$v_2$' , 12.62600509, 28.5125831, 247.455, 11), 31 | Tag('Variational' , r'$\mu_2$' , 12.8717576 , 27.9682084, 237.555, 13), 32 | Tag('Lunar elliptical semidiurnal second-order', '2"N'+r'$_2$' , 12.90537297, 27.8953548, 235.755, 14), 33 | Tag('Smaller lunar evectional' , r'$\lambda_2$', 12.22177348, 29.4556253, 263.655, 16), 34 | Tag('Larger solar elliptic' , r'$T_2$' , 12.01644934, 29.9589333, 272.555, 27), 35 | Tag('Smaller solar elliptic' , r'$R_2$' , 11.98359564, 30.0410667, 274.555, 28), 36 | Tag('Shallow water semidiurnal' , r'$2SM_2$' , 11.60695157, 31.0158958, 291.555, 31), 37 | Tag('Smaller lunar elliptic semidiurnal' , r'$L_2$' , 12.19162085, 29.5284789, 265.455, 33), 38 | Tag('Lunisolar semidiurnal' , r'$K_2$' , 11.96723606, 30.0821373, 275.555, 35), 39 | 40 | # Diurnal 41 | Tag('Lunar diurnal' , r'$K_1$' , 23.93447213, 15.0410686, 165.555, 4 ), 42 | Tag('Lunar diurnal' , r'$O_1$' , 25.81933871, 13.9430356, 145.555, 6 ), 43 | Tag('Lunar diurnal' , r'$OO_1$', 22.30608083, 16.1391017, 185.555, 15), 44 | Tag('Solar diurnal' , r'$S_1$' , 24.0 , 15.0 , 164.555, 17), 45 | Tag('Smaller lunar elliptic diurnal' , r'$M_1$' , 24.84120241, 14.4920521, 155.555, 18), 46 | Tag('Smaller lunar elliptic diurnal' , r'$J_1$' , 23.09848146, 15.5854433, 175.455, 19), 47 | Tag('Larger lunar evectional diurnal', r'$\rho$', 26.72305326, 13.4715145, 137.455, 25), 48 | Tag('Larger lunar elliptic diurnal' , r'$Q_1$' , 26.868350 , 13.3986609, 135.655, 26), 49 | Tag('Larger elliptic diurnal' , r'$2Q_1$', 28.00621204, 12.8542862, 125.755, 29), 50 | Tag('Solar diurnal' , r'$P_1$' , 24.06588766, 14.9589314, 163.555, 30), 51 | 52 | # Long period 53 | Tag('Lunar monthly' , r'$M_m$' , 661.3111655, 0.5443747, 65.455, 20), # period 27.554631896 days 54 | Tag('Solar semiannual' , r'$S_{sa}$', 4383.076325 , 0.0821373, 57.555, 21), # period 182.628180208 days 55 | Tag('Solar annual' , r'$S_a$' , 8766.15265 , 0.0410686, 56.555, 22), # period 365.256360417 days 56 | Tag('Lunisolar synodic fortnightly' , r'$MS_f$' , 354.3670666, 1.0158958, 73.555, 23), # period 14.765294442 days 57 | Tag('Lunisolar fortnightly' , r'$M_f$' , 327.8599387, 1.0980331, 75.555, 24), # period 13.660830779 days 58 | 59 | # Short period 60 | Tag('Shallow water overtides of principal lunar', r'$M_4$' , 6.210300601, 57.9682084, 455.555, 5 ), 61 | Tag('Shallow water overtides of principal lunar', r'$M_6$' , 4.140200401, 86.9523127, 655.555, 7 ), 62 | Tag('Shallow water terdiurnal' , r'$MK_3$' , 8.177140247, 44.0251729, 365.555, 8 ), 63 | Tag('Shallow water overtides of principal solar', r'$S_4$' , 6.0 , 60.0 , 491.555, 9 ), 64 | Tag('Shallow water quarter diurnal' , r'$MN_4$' , 6.269173724, 57.4238337, 445.655, 10), 65 | Tag('Shallow water overtides of principal solar', r'$S_6$' , 4.0 , 90.0 , np.nan , 12), 66 | Tag('Lunar terdiurnal' , r'$M_3$' , 8.280400802, 43.4761563, 355.555, 32), 67 | Tag('Shallow water terdiurnal' , '2"MK'+r'$_3$', 8.38630265 , 42.9271398, 345.555, 34), 68 | Tag('Shallow water eighth diurnal' , r'$M_8$' , 3.105150301, 115.9364166, 855.555, 36), 69 | Tag('Shallow water quarter diurnal' , r'$MS_4$' , 6.103339275, 58.9841042, 473.555, 37), 70 | ) 71 | 72 | 73 | ################################## Earth tides - point mode ################################## 74 | def calc_solid_earth_tides_point(lat, lon, dt0, dt1, step_sec=60, display=False, verbose=True): 75 | """Calculate SET in east/north/up direction for the given time period at the given point (lat/lon). 76 | 77 | Parameters: lat/lon - float32, latitude/longitude of the point of interest 78 | dt0/1 - datetime.datetime object, start/end date and time 79 | step_sec - int16, time step in seconds 80 | display - bool, plot the calculated SET 81 | verbose - bool, print verbose message 82 | Returns: dt_out - 1D np.ndarray in dt.datetime objects 83 | tide_e - 1D np.ndarray in float32, SET in east direction in meters 84 | tide_n - 1D np.ndarray in float32, SET in north direction in meters 85 | tide_u - 1D np.ndarray in float32, SET in up direction in meters 86 | Examples: dt0 = dt.datetime(2020,11,1,4,0,0) 87 | dt1 = dt.datetime(2020,12,31,2,0,0) 88 | (dt_out, 89 | tide_e, 90 | tide_n, 91 | tide_u) = calc_solid_earth_tides_point(34.0, -118.0, dt0, dt1) 92 | """ 93 | 94 | print('PYSOLID: calculate solid Earth tides in east/north/up direction') 95 | print(f'PYSOLID: lot/lon: {lat}/{lon} degree') 96 | print(f'PYSOLID: start UTC: {dt0.isoformat()}') 97 | print(f'PYSOLID: end UTC: {dt1.isoformat()}') 98 | print(f'PYSOLID: time step: {step_sec} seconds') 99 | 100 | dt_out = [] 101 | tide_e = [] 102 | tide_n = [] 103 | tide_u = [] 104 | 105 | ndays = (dt1.date() - dt0.date()).days + 1 106 | for i in range(ndays): 107 | di = dt0.date() + dt.timedelta(days=i) 108 | if verbose: 109 | print(f'SOLID : {di.isoformat()} {i+1}/{ndays} ...') 110 | 111 | # calc tide_u/n/u for the whole day 112 | (dt_outi, 113 | tide_ei, 114 | tide_ni, 115 | tide_ui) = calc_solid_earth_tides_point_per_day(lat, lon, 116 | date_str=di.strftime('%Y%m%d'), 117 | step_sec=int(step_sec)) 118 | 119 | # flag to mark the first/last datetime 120 | if i == 0: 121 | flag = dt_outi >= dt0 122 | elif i == ndays - 1: 123 | flag = dt_outi <= dt1 124 | else: 125 | flag = np.ones(dt_outi.size, dtype=np.bool_) 126 | 127 | # update 128 | dt_out += dt_outi[flag].tolist() 129 | tide_e += tide_ei[flag].tolist() 130 | tide_n += tide_ni[flag].tolist() 131 | tide_u += tide_ui[flag].tolist() 132 | 133 | # list --> np.ndarray for output 134 | dt_out = np.array(dt_out) 135 | tide_e = np.array(tide_e) 136 | tide_n = np.array(tide_n) 137 | tide_u = np.array(tide_u) 138 | 139 | # plot 140 | if display: 141 | plot_solid_earth_tides_point(dt_out, tide_e, tide_n, tide_u, lalo=[lat, lon]) 142 | 143 | return dt_out, tide_e, tide_n, tide_u 144 | 145 | 146 | def calc_solid_earth_tides_point_per_day(lat, lon, date_str, step_sec=60): 147 | """Calculate solid Earth tides (SET) in east/north/up direction 148 | for one day at the given point (lat/lon). 149 | 150 | Parameters: lat/lon - float32, latitude/longitude of the point of interest 151 | date_str - str, date in YYYYMMDD 152 | step_sec - int16, time step in seconds 153 | Returns: dt_out - 1D np.ndarray in dt.datetime objects 154 | tide_e - 1D np.ndarray in float32, SET in east direction in meters 155 | tide_n - 1D np.ndarray in float32, SET in north direction in meters 156 | tide_u - 1D np.ndarray in float32, SET in up direction in meters 157 | Examples: (dt_out, 158 | tide_e, 159 | tide_n, 160 | tide_u) = calc_solid_earth_tides_point_per_day(34.0, -118.0, '20180219') 161 | """ 162 | try: 163 | from pysolid.solid import solid_point 164 | except ImportError: 165 | msg = "Cannot import name 'solid' from 'pysolid'!" 166 | msg += '\n Maybe solid.for is NOT compiled yet.' 167 | msg += '\n Check instruction at: https://github.com/insarlab/PySolid.' 168 | raise ImportError(msg) 169 | 170 | # calc solid Earth tides 171 | t = dt.datetime.strptime(date_str, '%Y%m%d') 172 | secs, tide_e, tide_n, tide_u = solid_point( 173 | lat, lon, t.year, t.month, t.day, step_sec 174 | ) 175 | 176 | dt_out = [t + dt.timedelta(seconds=sec) for sec in secs] 177 | dt_out = np.array(dt_out) 178 | 179 | return dt_out, tide_e, tide_n, tide_u 180 | 181 | 182 | ######################################### Plot ############################################### 183 | def plot_solid_earth_tides_point(dt_out, tide_e, tide_n, tide_u, lalo=None, 184 | out_fig=None, save=False, display=True): 185 | """Plot the solid Earth tides at one point.""" 186 | from matplotlib import pyplot as plt, dates as mdates 187 | 188 | # plot 189 | fig, axs = plt.subplots(nrows=3, ncols=1, figsize=[6, 4], sharex=True) 190 | for ax, data, label in zip(axs.flatten(), 191 | [tide_e, tide_n, tide_u], 192 | ['East [cm]', 'North [cm]', 'Up [cm]']): 193 | ax.plot(dt_out, data*100) 194 | ax.set_ylabel(label, fontsize=12) 195 | 196 | # axis format 197 | for ax in axs: 198 | ax.tick_params(which='both', direction='out', bottom=True, top=False, left=True, right=True) 199 | ax.xaxis.set_major_locator(mdates.MonthLocator()) 200 | ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) 201 | ax.xaxis.set_minor_locator(mdates.DayLocator()) 202 | 203 | if lalo: 204 | axs[0].set_title('solid Earth tides at (N{}, E{})'.format(lalo[0], lalo[1]), fontsize=12) 205 | fig.tight_layout() 206 | fig.align_ylabels() 207 | 208 | # output 209 | if out_fig: 210 | save = True 211 | 212 | if save: 213 | if not out_fig: 214 | out_fig = os.path.abspath('SET_point.png') 215 | print('save figure to {}'.format(out_fig)) 216 | fig.savefig(out_fig, bbox_inches='tight', transparent=True, dpi=300) 217 | 218 | if display: 219 | print('showing...') 220 | plt.show() 221 | else: 222 | plt.close() 223 | 224 | return 225 | 226 | 227 | def plot_power_spectral_density4tides(tide_ts, sample_spacing, out_fig=None, fig_dpi=300, min_psd=1500): 228 | """Plot the power spectral density (PSD) of tides time-series. 229 | Note: for accurate PSD analysis, a long time-series, e.g. one year, is recommended. 230 | """ 231 | from matplotlib import pyplot as plt, ticker 232 | from scipy import signal 233 | 234 | ## calc PSD 235 | freq, psd = signal.periodogram(tide_ts, fs=1/sample_spacing, scaling='density') 236 | # get rid of zero in the first element 237 | psd = psd[1:] 238 | freq = freq[1:] 239 | period = 1./3600./freq # period (hour) 240 | 241 | ## plot 242 | fig, axs = plt.subplots(nrows=1, ncols=2, figsize=[8,3], sharey=True) 243 | for ax in axs: 244 | ax.plot(period, psd, '.', ms=16, mfc='none', mew=2) 245 | 246 | # axis format 247 | for ax in axs: 248 | ax.tick_params(which='both', direction='out', bottom=True, top=True, left=True, right=True) 249 | ax.xaxis.set_minor_locator(ticker.AutoMinorLocator()) 250 | ax.set_xlabel('Period [hour]') 251 | axs[0].set_xlim(11.3, 13.2) 252 | axs[1].set_xlim(22.0, 28.0) 253 | ax = axs[0] 254 | ax.set_ylabel('Power Spectral Density\n'+r'[$m^2/Hz$]') 255 | ax.set_ylim(0, ymax=axs[0].get_ylim()[1] * 1.1) 256 | #ax.set_ylim(1e-1,5e6); plt.yscale('log') 257 | #ax.yaxis.set_major_locator(ticker.FixedLocator([0,50e3,100e3,150e3])) 258 | #ax.set_yticklabels(['0','50k','100k','150k']) 259 | fig.tight_layout() 260 | 261 | # Tidal constituents 262 | for ax in axs: 263 | add_tidal_constituents(ax, period, psd, min_psd=min_psd) 264 | axs[0].annotate('semi-diurnal', xy=(0.05,0.85), xycoords='axes fraction') 265 | axs[1].annotate('diurnal', xy=(0.05,0.85), xycoords='axes fraction') 266 | 267 | # output 268 | if out_fig: 269 | print('save figure to file:', os.path.abspath(out_fig)) 270 | fig.savefig(out_fig, bbox_inches='tight', transparent=True, dpi=fig_dpi) 271 | plt.show() 272 | 273 | return 274 | 275 | 276 | def add_tidal_constituents(ax, period, psd, min_psd=1500, verbose=False): 277 | """Mark tidal constituents covered in the axes.""" 278 | pmin, pmax = ax.get_xlim() 279 | for tide in TIDES: 280 | if pmin <= tide.period <= pmax: 281 | tide_psd = psd[np.argmin(np.abs(period - tide.period))] 282 | if tide_psd >= min_psd: 283 | ymax = tide_psd / ax.get_ylim()[1] 284 | ax.axvline(x=tide.period, ymax=ymax, color='k', ls='--', lw=1) 285 | ax.annotate(tide.symbol, xy=(tide.period, tide_psd), xytext=(6, 4), textcoords='offset points') 286 | if verbose: 287 | print('tide: speices={}, symbol={}, period={} hours, psd={} m^2/Hz'.format( 288 | tide.species, tide.symbol, tide.period, tide_psd)) 289 | return 290 | -------------------------------------------------------------------------------- /src/pysolid/solid.for: -------------------------------------------------------------------------------- 1 | *** Solid Earth tides (SET) prediction (section 7.1.2 in the 2003 IERS Conventions). 2 | *** The used syntax is mainly f77 with MIL-STD-1753 extension. 3 | *** Author: Dennis Milbert, 2018-06-01. The code is available at: 4 | *** http://geodesyworld.github.io/SOFTS/solid.htm and can be downloaded as: 5 | *** wget http://geodesyworld.github.io/SOFTS/solid.for.txt -O solid.for 6 | *** The code is based on dehanttideinel.f provided by V. Dehant, S. Mathews, 7 | *** J. Gipson and C. Bruyninx. The latest version of dehanttideinel.f and its 8 | *** dependent subroutines can be download from IERS conventions website as: 9 | *** wget -r -l1 --no-parent -R "index.html*" -nH --cut-dirs=3 https://iers-conventions.obspm.fr/content/chapter7/software/dehanttideinel 10 | *** Sep 2020: modify solid() to solid_point/grid() as subroutines, Z. Yunjun and S. Sangha. 11 | *** Apr 2023: return numpy arrays instead of writing txt file, S. Staniewicz. 12 | 13 | subroutine solid_grid(iyr,imo,idy,ihh,imm,iss, 14 | * glad0,steplat,nlat,glod0,steplon,nlon,tide_e,tide_n,tide_u) 15 | 16 | *** calculate solid earth tides (SET) for one spatial grid given the date/time 17 | *** Arguments: iyr/imo/idy/ihh/imm/iss - int, date/time for YYYY/MM/DD/HH/MM/SS 18 | *** glad0/glad1/steplat - float, north(Y_FIRST)/south/step(negative) in deg 19 | *** glod0/glod1/steplon - float, west (X_FIRST)/east /step(positive) in deg 20 | *** Returns: tide_e/tide_n/tide_u - 2D np.ndarray, east/north/up component of SET in m 21 | 22 | implicit double precision(a-h,o-z) 23 | dimension rsun(3),rmoon(3),etide(3),xsta(3) 24 | integer iyr,imo,idy,ihh,imm,iss 25 | integer nlat,nlon 26 | double precision glad0,steplat 27 | double precision glod0,steplon 28 | real(8), intent(out), dimension(nlat,nlon) :: tide_e 29 | real(8), intent(out), dimension(nlat,nlon) :: tide_n 30 | real(8), intent(out), dimension(nlat,nlon) :: tide_u 31 | !***^ leap second table limit flag 32 | logical lflag 33 | common/stuff/rad,pi,pi2 34 | common/comgrs/a,e2 35 | !f2py intent(in) iyr,imo,idy,ihh,imm,iss,glad0,steplat,nlat,glod0,steplon,nlon 36 | !f2py intent(out) tide_e,tide_n,tide_u 37 | 38 | *** constants 39 | 40 | pi=4.d0*datan(1.d0) 41 | pi2=pi+pi 42 | rad=180.d0/pi 43 | 44 | *** grs80 45 | 46 | a=6378137.d0 47 | e2=6.69438002290341574957d-03 48 | 49 | *** input section 50 | 51 | if(iyr.lt.1901.or.iyr.gt.2099) then 52 | print *, 'ERROR: year NOT in [1901-2099]:',iyr 53 | return 54 | endif 55 | 56 | if(imo.lt.1.or.imo.gt.12) then 57 | print *, 'ERROR: month NOT in [1-12]:',imo 58 | return 59 | endif 60 | 61 | if(idy.lt.1.or.idy.gt.31) then 62 | print *, 'ERROR: day NOT in [1-31]:',idy 63 | return 64 | endif 65 | 66 | if(ihh.lt.0.or.ihh.gt.23) then 67 | print *, 'ERROR: hour NOT in [0-23]:',ihh 68 | return 69 | endif 70 | 71 | if(imm.lt.0.or.imm.gt.59) then 72 | print *, 'ERROR: minute NOT in [0-59]:',imm 73 | return 74 | endif 75 | 76 | if(iss.lt.0.or.iss.gt.59) then 77 | print *, 'ERROR: second NOT in [0-59]:',iss 78 | return 79 | endif 80 | 81 | if(glad0.lt.-90.d0.or.glad0.gt.90.d0) then 82 | print *, 'ERROR: lat0 NOT in [-90,+90]:',glad0 83 | return 84 | endif 85 | 86 | if(glod0.lt.-360.d0.or.glod0.gt.360.d0) then 87 | print *, 'ERROR: lon0 NOT in [-360,+360]',glod0 88 | return 89 | endif 90 | 91 | glad1=glad0+nlat*steplat 92 | glod1=glod0+nlon*steplon 93 | 94 | *** loop over the grid 95 | 96 | do ilat=1,nlat 97 | do ilon=1,nlon 98 | 99 | glad = glad0 + (ilat-1)*steplat 100 | glod = glod0 + (ilon-1)*steplon 101 | 102 | *** position of observing point (positive East) 103 | 104 | if(glod.lt. 0.d0) glod=glod+360.d0 105 | if(glod.ge.360.d0) glod=glod-360.d0 106 | 107 | gla0=glad/rad 108 | glo0=glod/rad 109 | eht0=0.d0 110 | call geoxyz(gla0,glo0,eht0,x0,y0,z0) 111 | xsta(1)=x0 112 | xsta(2)=y0 113 | xsta(3)=z0 114 | 115 | 116 | *** here comes the sun (and the moon) (go, tide!) 117 | 118 | !***^ UTC time system 119 | ihr= ihh 120 | imn= imm 121 | sec= iss 122 | call civmjd(iyr,imo,idy,ihr,imn,sec,mjd,fmjd) 123 | !***^ normalize civil time 124 | call mjdciv(mjd,fmjd,iyr,imo,idy,ihr,imn,sec) 125 | call setjd0(iyr,imo,idy) 126 | 127 | !***^ false means flag not raised 128 | !***^ mjd/fmjd in UTC 129 | !***^ mjd/fmjd in UTC 130 | !***^ mjd/fmjd in UTC 131 | lflag=.false. 132 | call sunxyz (mjd,fmjd,rsun,lflag) 133 | call moonxyz(mjd,fmjd,rmoon,lflag) 134 | call detide (xsta,mjd,fmjd,rsun,rmoon,etide,lflag) 135 | xt = etide(1) 136 | yt = etide(2) 137 | zt = etide(3) 138 | 139 | *** determine local geodetic horizon components (topocentric) 140 | 141 | !***^ tide vector 142 | call rge(gla0,glo0,ut,vt,wt,xt, yt, zt) 143 | 144 | call mjdciv(mjd,fmjd +0.001d0/86400.d0, 145 | * iyr,imo,idy,ihr,imn,sec-0.001d0) 146 | 147 | !*** write output respective arrays 148 | tide_e(ilat, ilon) = vt 149 | tide_n(ilat, ilon) = ut 150 | tide_u(ilat, ilon) = wt 151 | 152 | enddo 153 | enddo 154 | 155 | *** end of processing and flag for leap second 156 | 157 | if(lflag) then 158 | print *, 'Mild Warning -- time crossed leap second table' 159 | print *, ' boundaries. Boundary edge value used instead' 160 | endif 161 | 162 | 163 | return 164 | end 165 | 166 | *----------------------------------------------------------------------- 167 | subroutine solid_point(glad,glod,iyr,imo,idy,step_sec, 168 | * secs,tide_e,tide_n,tide_u) 169 | 170 | *** calculate SET at given location for one day with step_sec seconds resolution 171 | *** Arguments: glad/glod - float, latitude/longitude in deg 172 | *** iyr/imo/idy - int, start date/time in UTC 173 | *** step_sec - int, time step in seconds 174 | *** Returns: secs - 1D np.ndarray, seconds since start 175 | *** tide_e/tide_n/tide_u - 1D np.ndarray, east/north/up component of SET in m 176 | 177 | implicit double precision(a-h,o-z) 178 | dimension rsun(3),rmoon(3),etide(3),xsta(3) 179 | double precision glad,glod 180 | integer iyr,imo,idy 181 | integer nloop, step_sec 182 | double precision tdel2 183 | real(8), intent(out), dimension(60*60*24/step_sec) :: secs 184 | real(8), intent(out), dimension(60*60*24/step_sec) :: tide_e 185 | real(8), intent(out), dimension(60*60*24/step_sec) :: tide_n 186 | real(8), intent(out), dimension(60*60*24/step_sec) :: tide_u 187 | !*** leap second table limit flag 188 | logical lflag 189 | common/stuff/rad,pi,pi2 190 | common/comgrs/a,e2 191 | !f2py intent(in) glad,glod,iyr,imo,idy,step_sec 192 | !f2py intent(out) secs,tide_e,tide_n,tide_u 193 | 194 | *** constants 195 | 196 | pi=4.d0*datan(1.d0) 197 | pi2=pi+pi 198 | rad=180.d0/pi 199 | 200 | *** grs80 201 | 202 | a=6378137.d0 203 | e2=6.69438002290341574957d-03 204 | 205 | *** check inputs section 206 | 207 | if(glad.lt.-90.d0.or.glad.gt.90.d0) then 208 | print *, 'ERROR: lat NOT in [-90,+90]:',glad 209 | return 210 | endif 211 | 212 | if(glod.lt.-360.d0.or.glod.gt.360.d0) then 213 | print *, 'ERROR: lon NOT in [-360,+360]:',glod 214 | return 215 | endif 216 | 217 | if(iyr.lt.1901.or.iyr.gt.2099) then 218 | print *, 'ERROR: year NOT in [1901-2099]:',iyr 219 | return 220 | endif 221 | 222 | if(imo.lt.1.or.imo.gt.12) then 223 | print *, 'ERROR: month NOT in [1-12]:',imo 224 | return 225 | endif 226 | 227 | if(idy.lt.1.or.idy.gt.31) then 228 | print *, 'ERROR: day NOT in [1-31]:',idy 229 | return 230 | endif 231 | 232 | 233 | *** position of observing point (positive East) 234 | 235 | if(glod.lt. 0.d0) glod=glod+360.d0 236 | if(glod.ge.360.d0) glod=glod-360.d0 237 | 238 | gla0=glad/rad 239 | glo0=glod/rad 240 | eht0=0.d0 241 | call geoxyz(gla0,glo0,eht0,x0,y0,z0) 242 | xsta(1)=x0 243 | xsta(2)=y0 244 | xsta(3)=z0 245 | 246 | *** here comes the sun (and the moon) (go, tide!) 247 | 248 | !*** UTC time system 249 | ihr= 0 250 | imn= 0 251 | sec=0.d0 252 | call civmjd(iyr,imo,idy,ihr,imn,sec,mjd,fmjd) 253 | !*** normalize civil time 254 | call mjdciv(mjd,fmjd,iyr,imo,idy,ihr,imn,sec) 255 | call setjd0(iyr,imo,idy) 256 | 257 | *** loop over time 258 | 259 | nloop=60*60*24/step_sec 260 | tdel2=1.d0/DFLOAT(nloop) 261 | do iloop=1,nloop 262 | !*** false means flag not raised 263 | !*** mjd/fmjd in UTC 264 | !*** mjd/fmjd in UTC 265 | !*** mjd/fmjd in UTC 266 | lflag=.false. 267 | call sunxyz (mjd,fmjd,rsun,lflag) 268 | call moonxyz(mjd,fmjd,rmoon,lflag) 269 | call detide (xsta,mjd,fmjd,rsun,rmoon,etide,lflag) 270 | xt = etide(1) 271 | yt = etide(2) 272 | zt = etide(3) 273 | 274 | *** determine local geodetic horizon components (topocentric) 275 | 276 | !*** tide vector 277 | call rge(gla0,glo0,ut,vt,wt,xt, yt, zt) 278 | 279 | call mjdciv(mjd,fmjd,iyr,imo,idy,ihr,imn,sec) 280 | 281 | tsec=ihr*3600.d0+imn*60.d0+sec 282 | 283 | secs(iloop) = tsec 284 | tide_e(iloop) = vt 285 | tide_n(iloop) = ut 286 | tide_u(iloop) = wt 287 | 288 | !*** update fmjd for the next round 289 | fmjd=fmjd+tdel2 290 | !*** force 1 sec. granularity 291 | fmjd=(idnint(fmjd*86400.d0))/86400.d0 292 | enddo 293 | 294 | *** end of processing and flag of leap second 295 | 296 | if(lflag) then 297 | print *, 'Mild Warning -- time crossed leap second table' 298 | print *, ' boundaries. Boundary edge value used instead' 299 | endif 300 | 301 | return 302 | end 303 | 304 | *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 305 | subroutine detide(xsta,mjd,fmjd,xsun,xmon,dxtide,lflag) 306 | 307 | *** computation of tidal corrections of station displacements caused 308 | *** by lunar and solar gravitational attraction 309 | *** UTC version 310 | 311 | *** step 1 (here general degree 2 and 3 corrections + 312 | *** call st1idiu + call st1isem + call st1l1) 313 | *** + step 2 (call step2diu + call step2lon + call step2idiu) 314 | *** it has been decided that the step 3 un-correction for permanent tide 315 | *** would *not* be applied in order to avoid jump in the reference frame 316 | *** (this step 3 must added in order to get the mean tide station position 317 | *** and to be conformed with the iag resolution.) 318 | 319 | *** inputs 320 | *** xsta(i),i=1,2,3 -- geocentric position of the station (ITRF/ECEF) 321 | *** xsun(i),i=1,2,3 -- geoc. position of the sun (ECEF) 322 | *** xmon(i),i=1,2,3 -- geoc. position of the moon (ECEF) 323 | *** mjd,fmjd -- modified julian day (and fraction) (in UTC time) 324 | 325 | ****old calling sequence***************************************************** 326 | *** dmjd -- time in mean julian date (including day fraction) 327 | *** fhr=hr+zmin/60.+sec/3600. -- hr in the day 328 | 329 | *** outputs 330 | *** dxtide(i),i=1,2,3 -- displacement vector (ITRF) 331 | *** lflag -- leap second table limit flag, false:flag not raised 332 | 333 | *** author iers 1996 : v. dehant, s. mathews and j. gipson 334 | *** (test between two subroutines) 335 | *** author iers 2000 : v. dehant, c. bruyninx and s. mathews 336 | *** (test in the bernese program by c. bruyninx) 337 | 338 | *** created: 96/03/23 (see above) 339 | *** modified from dehanttideinelMJD.f by Dennis Milbert 2006sep10 340 | *** bug fix regarding fhr (changed calling sequence, too) 341 | *** modified to reflect table 7.5a and b IERS Conventions 2003 342 | *** modified to use TT time system to call step 2 functions 343 | *** sign correction by V.Dehant to match eq.16b, p.81, Conventions 344 | *** applied by Dennis Milbert 2007may05 345 | *** UTC version by Dennis Milbert 2018june01 346 | 347 | implicit double precision(a-h,o-z) 348 | double precision xsta(3),xsun(3),xmon(3),dxtide(3),xcorsta(3) 349 | double precision h20,l20,h3,l3,h2,l2,fmjd 350 | integer mjd 351 | double precision mass_ratio_sun,mass_ratio_moon 352 | logical lflag,leapflag 353 | !*** leap second table limit flag 354 | !*** leap second table limit flag 355 | save /limitflag/ 356 | common/limitflag/leapflag 357 | 358 | *** nominal second degree and third degree love numbers and shida numbers 359 | 360 | data h20/0.6078d0/,l20/0.0847d0/,h3/0.292d0/,l3/0.015d0/ 361 | 362 | *** internal support for new calling sequence 363 | *** first, convert UTC time into TT time (and, bring leapflag into variable) 364 | 365 | leapflag=lflag 366 | !*** UTC time (sec of day) 367 | !*** TT time (sec of day) 368 | !*** TT time (fract. day) 369 | tsecutc =fmjd*86400.d0 370 | tsectt =utc2ttt(tsecutc) 371 | fmjdtt =tsectt/86400.d0 372 | lflag = leapflag 373 | 374 | !*** float MJD in TT 375 | dmjdtt=mjd+fmjdtt 376 | *** commented line was live code in dehanttideinelMJD.f 377 | *** changed on the suggestion of Dr. Don Kim, UNB -- 09mar21 378 | *** Julian date for 2000 January 1 00:00:00.0 UT is JD 2451544.5 379 | *** MJD for 2000 January 1 00:00:00.0 UT is MJD 51544.0 380 | ***** t=(dmjdtt-51545.d0)/36525.d0 381 | !*** days to centuries, TT 382 | !*** days to centuries, TT 383 | t=(dmjdtt-51544.d0)/36525.d0 384 | !*** hours in the day, TT 385 | fhr=(dmjdtt-int(dmjdtt))*24.d0 386 | 387 | *** scalar product of station vector with sun/moon vector 388 | 389 | call sprod(xsta,xsun,scs,rsta,rsun) 390 | call sprod(xsta,xmon,scm,rsta,rmon) 391 | scsun=scs/rsta/rsun 392 | scmon=scm/rsta/rmon 393 | 394 | *** computation of new h2 and l2 395 | 396 | cosphi=dsqrt(xsta(1)*xsta(1) + xsta(2)*xsta(2))/rsta 397 | h2=h20-0.0006d0*(1.d0-3.d0/2.d0*cosphi*cosphi) 398 | l2=l20+0.0002d0*(1.d0-3.d0/2.d0*cosphi*cosphi) 399 | 400 | *** p2-term 401 | 402 | p2sun=3.d0*(h2/2.d0-l2)*scsun*scsun-h2/2.d0 403 | p2mon=3.d0*(h2/2.d0-l2)*scmon*scmon-h2/2.d0 404 | 405 | *** p3-term 406 | 407 | p3sun=5.d0/2.d0*(h3-3.d0*l3)*scsun**3+3.d0/2.d0*(l3-h3)*scsun 408 | p3mon=5.d0/2.d0*(h3-3.d0*l3)*scmon**3+3.d0/2.d0*(l3-h3)*scmon 409 | 410 | *** term in direction of sun/moon vector 411 | 412 | x2sun=3.d0*l2*scsun 413 | x2mon=3.d0*l2*scmon 414 | x3sun=3.d0*l3/2.d0*(5.d0*scsun*scsun-1.d0) 415 | x3mon=3.d0*l3/2.d0*(5.d0*scmon*scmon-1.d0) 416 | 417 | *** factors for sun/moon 418 | 419 | mass_ratio_sun=332945.943062d0 420 | mass_ratio_moon=0.012300034d0 421 | re =6378136.55d0 422 | fac2sun=mass_ratio_sun*re*(re/rsun)**3 423 | fac2mon=mass_ratio_moon*re*(re/rmon)**3 424 | fac3sun=fac2sun*(re/rsun) 425 | fac3mon=fac2mon*(re/rmon) 426 | 427 | *** total displacement 428 | 429 | do i=1,3 430 | dxtide(i)=fac2sun*( x2sun*xsun(i)/rsun + p2sun*xsta(i)/rsta ) + 431 | * fac2mon*( x2mon*xmon(i)/rmon + p2mon*xsta(i)/rsta ) + 432 | * fac3sun*( x3sun*xsun(i)/rsun + p3sun*xsta(i)/rsta ) + 433 | * fac3mon*( x3mon*xmon(i)/rmon + p3mon*xsta(i)/rsta ) 434 | enddo 435 | call zero_vec8(xcorsta) 436 | 437 | *** corrections for the out-of-phase part of love numbers 438 | *** (part h_2^(0)i and l_2^(0)i ) 439 | 440 | *** first, for the diurnal band 441 | 442 | call st1idiu(xsta,xsun,xmon,fac2sun,fac2mon,xcorsta) 443 | dxtide(1)=dxtide(1)+xcorsta(1) 444 | dxtide(2)=dxtide(2)+xcorsta(2) 445 | dxtide(3)=dxtide(3)+xcorsta(3) 446 | 447 | *** second, for the semi-diurnal band 448 | 449 | call st1isem(xsta,xsun,xmon,fac2sun,fac2mon,xcorsta) 450 | dxtide(1)=dxtide(1)+xcorsta(1) 451 | dxtide(2)=dxtide(2)+xcorsta(2) 452 | dxtide(3)=dxtide(3)+xcorsta(3) 453 | 454 | *** corrections for the latitude dependence of love numbers (part l^(1) ) 455 | 456 | call st1l1(xsta,xsun,xmon,fac2sun,fac2mon,xcorsta) 457 | dxtide(1)=dxtide(1)+xcorsta(1) 458 | dxtide(2)=dxtide(2)+xcorsta(2) 459 | dxtide(3)=dxtide(3)+xcorsta(3) 460 | 461 | *** consider corrections for step 2 462 | *** corrections for the diurnal band: 463 | 464 | *** first, we need to know the date converted in julian centuries 465 | 466 | *** this is now handled at top of code (also convert to TT time system) 467 | ***** t=(dmjd-51545.)/36525. 468 | ***** fhr=dmjd-int(dmjd) !*** this is/was a buggy line (day vs. hr) 469 | 470 | *** second, the diurnal band corrections, 471 | *** (in-phase and out-of-phase frequency dependence): 472 | 473 | call step2diu(xsta,fhr,t,xcorsta) 474 | dxtide(1)=dxtide(1)+xcorsta(1) 475 | dxtide(2)=dxtide(2)+xcorsta(2) 476 | dxtide(3)=dxtide(3)+xcorsta(3) 477 | 478 | *** corrections for the long-period band, 479 | *** (in-phase and out-of-phase frequency dependence): 480 | 481 | call step2lon(xsta,fhr,t,xcorsta) 482 | dxtide(1)=dxtide(1)+xcorsta(1) 483 | dxtide(2)=dxtide(2)+xcorsta(2) 484 | dxtide(3)=dxtide(3)+xcorsta(3) 485 | 486 | *** consider corrections for step 3 487 | *----------------------------------------------------------------------- 488 | * The code below is commented to prevent restoring deformation 489 | * due to permanent tide. All the code above removes 490 | * total tidal deformation with conventional Love numbers. 491 | * The code above realizes a conventional tide free crust (i.e. ITRF). 492 | * This does NOT conform to Resolution 16 of the 18th General Assembly 493 | * of the IAG (1983). This resolution has not been implemented by 494 | * the space geodesy community in general (c.f. IERS Conventions 2003). 495 | *----------------------------------------------------------------------- 496 | 497 | *** uncorrect for the permanent tide (only if you want mean tide system) 498 | 499 | *** pi=3.141592654 500 | *** sinphi=xsta(3)/rsta 501 | *** cosphi=dsqrt(xsta(1)**2+xsta(2)**2)/rsta 502 | *** cosla=xsta(1)/cosphi/rsta 503 | *** sinla=xsta(2)/cosphi/rsta 504 | *** dr=-dsqrt(5./4./pi)*h2*0.31460*(3./2.*sinphi**2-0.5) 505 | *** dn=-dsqrt(5./4./pi)*l2*0.31460*3.*cosphi*sinphi 506 | *** dxtide(1)=dxtide(1)-dr*cosla*cosphi+dn*cosla*sinphi 507 | *** dxtide(2)=dxtide(2)-dr*sinla*cosphi+dn*sinla*sinphi 508 | *** dxtide(3)=dxtide(3)-dr*sinphi -dn*cosphi 509 | 510 | return 511 | end 512 | *----------------------------------------------------------------------- 513 | subroutine st1l1(xsta,xsun,xmon,fac2sun,fac2mon,xcorsta) 514 | 515 | *** this subroutine gives the corrections induced by the latitude dependence 516 | *** given by l^(1) in mahtews et al (1991) 517 | 518 | *** input: xsta,xsun,xmon,fac3sun,fac3mon 519 | *** output: xcorsta 520 | 521 | implicit double precision (a-h,o-z) 522 | double precision xsta(3),xsun(3),xmon(3),xcorsta(3) 523 | double precision l1,l1d,l1sd,fac2sun,fac2mon 524 | data l1d/0.0012d0/,l1sd/0.0024d0/ 525 | 526 | rsta=enorm8(xsta) 527 | sinphi=xsta(3)/rsta 528 | cosphi=dsqrt(xsta(1)**2+xsta(2)**2)/rsta 529 | sinla=xsta(2)/cosphi/rsta 530 | cosla=xsta(1)/cosphi/rsta 531 | rmon=enorm8(xmon) 532 | rsun=enorm8(xsun) 533 | 534 | *** for the diurnal band 535 | 536 | l1=l1d 537 | dnsun=-l1*sinphi**2*fac2sun*xsun(3)*(xsun(1)*cosla+xsun(2)*sinla) 538 | * /rsun**2 539 | dnmon=-l1*sinphi**2*fac2mon*xmon(3)*(xmon(1)*cosla+xmon(2)*sinla) 540 | * /rmon**2 541 | desun=l1*sinphi*(cosphi**2-sinphi**2)*fac2sun*xsun(3)* 542 | * (xsun(1)*sinla-xsun(2)*cosla)/rsun**2 543 | demon=l1*sinphi*(cosphi**2-sinphi**2)*fac2mon*xmon(3)* 544 | * (xmon(1)*sinla-xmon(2)*cosla)/rmon**2 545 | de=3.d0*(desun+demon) 546 | dn=3.d0*(dnsun+dnmon) 547 | xcorsta(1)=-de*sinla-dn*sinphi*cosla 548 | xcorsta(2)= de*cosla-dn*sinphi*sinla 549 | xcorsta(3)= dn*cosphi 550 | 551 | *** for the semi-diurnal band 552 | 553 | l1=l1sd 554 | costwola=cosla**2-sinla**2 555 | sintwola=2.d0*cosla*sinla 556 | dnsun=-l1/2.d0*sinphi*cosphi*fac2sun*((xsun(1)**2-xsun(2)**2)* 557 | * costwola+2.d0*xsun(1)*xsun(2)*sintwola)/rsun**2 558 | dnmon=-l1/2.d0*sinphi*cosphi*fac2mon*((xmon(1)**2-xmon(2)**2)* 559 | * costwola+2.d0*xmon(1)*xmon(2)*sintwola)/rmon**2 560 | desun=-l1/2.d0*sinphi**2*cosphi*fac2sun*((xsun(1)**2-xsun(2)**2)* 561 | * sintwola-2.d0*xsun(1)*xsun(2)*costwola)/rsun**2 562 | demon=-l1/2.d0*sinphi**2*cosphi*fac2mon*((xmon(1)**2-xmon(2)**2)* 563 | * sintwola-2.d0*xmon(1)*xmon(2)*costwola)/rmon**2 564 | de=3.d0*(desun+demon) 565 | dn=3.d0*(dnsun+dnmon) 566 | xcorsta(1)=xcorsta(1)-de*sinla-dn*sinphi*cosla 567 | xcorsta(2)=xcorsta(2)+de*cosla-dn*sinphi*sinla 568 | xcorsta(3)=xcorsta(3) +dn*cosphi 569 | 570 | return 571 | end 572 | *----------------------------------------------------------------------- 573 | subroutine step2diu(xsta,fhr,t,xcorsta) 574 | 575 | *** last change: vd 17 may 00 1:20 pm 576 | *** these are the subroutines for the step2 of the tidal corrections. 577 | *** they are called to account for the frequency dependence 578 | *** of the love numbers. 579 | 580 | implicit double precision (a-h,o-z) 581 | double precision xsta(3),xcorsta(3),datdi(9,31) 582 | double precision deg2rad, fhr, t 583 | data deg2rad/0.017453292519943295769d0/ 584 | 585 | *** note, following table is derived from dehanttideinelMJD.f (2000oct30 16:10) 586 | *** has minor differences from that of dehanttideinel.f (2000apr17 14:10) 587 | *** D.M. edited to strictly follow published table 7.5a (2006aug08 13:46) 588 | 589 | *** cf. table 7.5a of IERS conventions 2003 (TN.32, pg.82) 590 | *** columns are s,h,p,N',ps, dR(ip),dR(op),dT(ip),dT(op) 591 | *** units of mm 592 | 593 | data ((datdi(i,j),i=1,9),j=1,31)/ 594 | * -3., 0., 2., 0., 0.,-0.01,-0.01, 0.0 , 0.0, 595 | * -3., 2., 0., 0., 0.,-0.01,-0.01, 0.0 , 0.0, 596 | * -2., 0., 1.,-1., 0.,-0.02,-0.01, 0.0 , 0.0, 597 | *****----------------------------------------------------------------------- 598 | ****** -2., 0., 1., 0., 0.,-0.08,-0.05, 0.01,-0.02, 599 | !*** original entry 600 | !*** table 7.5a 601 | * -2., 0., 1., 0., 0.,-0.08, 0.00, 0.01, 0.01, 602 | *****----------------------------------------------------------------------- 603 | * -2., 2.,-1., 0., 0.,-0.02,-0.01, 0.0 , 0.0, 604 | *****----------------------------------------------------------------------- 605 | ****** -1., 0., 0.,-1., 0.,-0.10,-0.05, 0.0 ,-0.02, 606 | !*** original entry 607 | !*** table 7.5a 608 | * -1., 0., 0.,-1., 0.,-0.10, 0.00, 0.00, 0.00, 609 | *****----------------------------------------------------------------------- 610 | ****** -1., 0., 0., 0., 0.,-0.51,-0.26,-0.02,-0.12, 611 | !*** original entry 612 | !*** table 7.5a 613 | * -1., 0., 0., 0., 0.,-0.51, 0.00,-0.02, 0.03, 614 | *****----------------------------------------------------------------------- 615 | * -1., 2., 0., 0., 0., 0.01, 0.0 , 0.0 , 0.0, 616 | * 0.,-2., 1., 0., 0., 0.01, 0.0 , 0.0 , 0.0, 617 | * 0., 0.,-1., 0., 0., 0.02, 0.01, 0.0 , 0.0, 618 | *****----------------------------------------------------------------------- 619 | ****** 0., 0., 1., 0., 0., 0.06, 0.02, 0.0 , 0.01, 620 | !*** original entry 621 | !*** table 7.5a 622 | * 0., 0., 1., 0., 0., 0.06, 0.00, 0.00, 0.00, 623 | *****----------------------------------------------------------------------- 624 | * 0., 0., 1., 1., 0., 0.01, 0.0 , 0.0 , 0.0, 625 | * 0., 2.,-1., 0., 0., 0.01, 0.0 , 0.0 , 0.0, 626 | !*** table 7.5a 627 | * 1.,-3., 0., 0., 1.,-0.06, 0.00, 0.00, 0.00, 628 | * 1.,-2., 0., 1., 0., 0.01, 0.0 , 0.0 , 0.0, 629 | *****----------------------------------------------------------------------- 630 | ****** 1.,-2., 0., 0., 0.,-1.23,-0.05, 0.06,-0.06, 631 | !*** original entry 632 | !*** table 7.5a 633 | * 1.,-2., 0., 0., 0.,-1.23,-0.07, 0.06, 0.01, 634 | *****----------------------------------------------------------------------- 635 | * 1.,-1., 0., 0.,-1., 0.02, 0.0 , 0.0 , 0.0, 636 | * 1.,-1., 0., 0., 1., 0.04, 0.0 , 0.0 , 0.0, 637 | !*** table 7.5a 638 | * 1., 0., 0.,-1., 0.,-0.22, 0.01, 0.01, 0.00, 639 | *****----------------------------------------------------------------------- 640 | ****** 1., 0., 0., 0., 0.,12.02,-0.45,-0.66, 0.17, 641 | !*** original entry 642 | !*** table 7.5a 643 | * 1., 0., 0., 0., 0.,12.00,-0.78,-0.67,-0.03, 644 | *****----------------------------------------------------------------------- 645 | ****** 1., 0., 0., 1., 0., 1.73,-0.07,-0.10, 0.02, 646 | !*** original entry 647 | !*** table 7.5a 648 | * 1., 0., 0., 1., 0., 1.73,-0.12,-0.10, 0.00, 649 | *****----------------------------------------------------------------------- 650 | * 1., 0., 0., 2., 0.,-0.04, 0.0 , 0.0 , 0.0, 651 | *****----------------------------------------------------------------------- 652 | ****** 1., 1., 0., 0.,-1.,-0.50, 0.0 , 0.03, 0.0, 653 | !*** original entry 654 | !*** table 7.5a 655 | * 1., 1., 0., 0.,-1.,-0.50,-0.01, 0.03, 0.00, 656 | *****----------------------------------------------------------------------- 657 | * 1., 1., 0., 0., 1., 0.01, 0.0 , 0.0 , 0.0, 658 | *****----------------------------------------------------------------------- 659 | ****** 0., 1., 0., 1.,-1.,-0.01, 0.0 , 0.0 , 0.0, 660 | !*** original entry 661 | !*** v.dehant 2007 662 | * 1., 1., 0., 1.,-1.,-0.01, 0.0 , 0.0 , 0.0, 663 | *****----------------------------------------------------------------------- 664 | * 1., 2.,-2., 0., 0.,-0.01, 0.0 , 0.0 , 0.0, 665 | *****----------------------------------------------------------------------- 666 | ****** 1., 2., 0., 0., 0.,-0.12, 0.01, 0.01, 0.0, 667 | !*** original entry 668 | !*** table 7.5a 669 | * 1., 2., 0., 0., 0.,-0.11, 0.01, 0.01, 0.00, 670 | *****----------------------------------------------------------------------- 671 | * 2.,-2., 1., 0., 0.,-0.01, 0.0 , 0.0 , 0.0, 672 | * 2., 0.,-1., 0., 0.,-0.02, 0.02, 0.0 , 0.01, 673 | * 3., 0., 0., 0., 0., 0.0 , 0.01, 0.0 , 0.01, 674 | * 3., 0., 0., 1., 0., 0.0 , 0.01, 0.0 , 0.0/ 675 | 676 | s=218.31664563d0+481267.88194d0*t-0.0014663889d0*t*t 677 | * +0.00000185139d0*t**3 678 | tau=fhr*15.d0+280.4606184d0+36000.7700536d0*t+0.00038793d0*t*t 679 | * -0.0000000258d0*t**3-s 680 | pr=1.396971278*t+0.000308889*t*t+0.000000021*t**3 681 | * +0.000000007*t**4 682 | s=s+pr 683 | h=280.46645d0+36000.7697489d0*t+0.00030322222d0*t*t 684 | * +0.000000020*t**3-0.00000000654*t**4 685 | p=83.35324312d0+4069.01363525d0*t-0.01032172222d0*t*t 686 | * -0.0000124991d0*t**3+0.00000005263d0*t**4 687 | zns=234.95544499d0 +1934.13626197d0*t-0.00207561111d0*t*t 688 | * -0.00000213944d0*t**3+0.00000001650d0*t**4 689 | ps=282.93734098d0+1.71945766667d0*t+0.00045688889d0*t*t 690 | * -0.00000001778d0*t**3-0.00000000334d0*t**4 691 | 692 | *** reduce angles to between 0 and 360 693 | 694 | s= dmod( s,360.d0) 695 | tau=dmod(tau,360.d0) 696 | h= dmod( h,360.d0) 697 | p= dmod( p,360.d0) 698 | zns=dmod(zns,360.d0) 699 | ps= dmod( ps,360.d0) 700 | 701 | rsta=dsqrt(xsta(1)**2+xsta(2)**2+xsta(3)**2) 702 | sinphi=xsta(3)/rsta 703 | cosphi=dsqrt(xsta(1)**2+xsta(2)**2)/rsta 704 | 705 | cosla=xsta(1)/cosphi/rsta 706 | sinla=xsta(2)/cosphi/rsta 707 | zla = datan2(xsta(2),xsta(1)) 708 | do i=1,3 709 | xcorsta(i)=0.d0 710 | enddo 711 | do j=1,31 712 | thetaf=(tau+datdi(1,j)*s+datdi(2,j)*h+datdi(3,j)*p+ 713 | * datdi(4,j)*zns+datdi(5,j)*ps)*deg2rad 714 | dr=datdi(6,j)*2.d0*sinphi*cosphi*sin(thetaf+zla)+ 715 | * datdi(7,j)*2.d0*sinphi*cosphi*cos(thetaf+zla) 716 | dn=datdi(8,j)*(cosphi**2-sinphi**2)*sin(thetaf+zla)+ 717 | * datdi(9,j)*(cosphi**2-sinphi**2)*cos(thetaf+zla) 718 | ***** following correction by V.Dehant to match eq.16b, p.81, 2003 Conventions 719 | ***** de=datdi(8,j)*sinphi*cos(thetaf+zla)+ 720 | de=datdi(8,j)*sinphi*cos(thetaf+zla)- 721 | * datdi(9,j)*sinphi*sin(thetaf+zla) 722 | xcorsta(1)=xcorsta(1)+dr*cosla*cosphi-de*sinla 723 | * -dn*sinphi*cosla 724 | xcorsta(2)=xcorsta(2)+dr*sinla*cosphi+de*cosla 725 | * -dn*sinphi*sinla 726 | xcorsta(3)=xcorsta(3)+dr*sinphi+dn*cosphi 727 | enddo 728 | 729 | do i=1,3 730 | xcorsta(i)=xcorsta(i)/1000.d0 731 | enddo 732 | 733 | return 734 | end 735 | *----------------------------------------------------------------------- 736 | subroutine step2lon(xsta,fhr,t,xcorsta) 737 | 738 | implicit double precision (a-h,o-z) 739 | double precision deg2rad,fhr,t 740 | double precision xsta(3),xcorsta(3),datdi(9,5) 741 | data deg2rad/0.017453292519943295769d0/ 742 | 743 | *** cf. table 7.5b of IERS conventions 2003 (TN.32, pg.82) 744 | *** columns are s,h,p,N',ps, dR(ip),dT(ip),dR(op),dT(op) 745 | *** IERS cols.= s,h,p,N',ps, dR(ip),dR(op),dT(ip),dT(op) 746 | *** units of mm 747 | 748 | data ((datdi(i,j),i=1,9),j=1,5)/ 749 | * 0, 0, 0, 1, 0, 0.47, 0.23, 0.16, 0.07, 750 | * 0, 2, 0, 0, 0, -0.20,-0.12,-0.11,-0.05, 751 | * 1, 0,-1, 0, 0, -0.11,-0.08,-0.09,-0.04, 752 | * 2, 0, 0, 0, 0, -0.13,-0.11,-0.15,-0.07, 753 | * 2, 0, 0, 1, 0, -0.05,-0.05,-0.06,-0.03/ 754 | 755 | s=218.31664563d0+481267.88194d0*t-0.0014663889d0*t*t 756 | * +0.00000185139d0*t**3 757 | pr=1.396971278*t+0.000308889*t*t+0.000000021*t**3 758 | * +0.000000007*t**4 759 | s=s+pr 760 | h=280.46645d0+36000.7697489d0*t+0.00030322222d0*t*t 761 | * +0.000000020*t**3-0.00000000654*t**4 762 | p=83.35324312d0+4069.01363525d0*t-0.01032172222d0*t*t 763 | * -0.0000124991d0*t**3+0.00000005263d0*t**4 764 | zns=234.95544499d0 +1934.13626197d0*t-0.00207561111d0*t*t 765 | * -0.00000213944d0*t**3+0.00000001650d0*t**4 766 | ps=282.93734098d0+1.71945766667d0*t+0.00045688889d0*t*t 767 | * -0.00000001778d0*t**3-0.00000000334d0*t**4 768 | rsta=dsqrt(xsta(1)**2+xsta(2)**2+xsta(3)**2) 769 | sinphi=xsta(3)/rsta 770 | cosphi=dsqrt(xsta(1)**2+xsta(2)**2)/rsta 771 | cosla=xsta(1)/cosphi/rsta 772 | sinla=xsta(2)/cosphi/rsta 773 | 774 | *** reduce angles to between 0 and 360 775 | 776 | s= dmod( s,360.d0) 777 | ***** tau=dmod(tau,360.d0) !*** tau not used here--09jul28 778 | h= dmod( h,360.d0) 779 | p= dmod( p,360.d0) 780 | zns=dmod(zns,360.d0) 781 | ps= dmod( ps,360.d0) 782 | 783 | dr_tot=0.d0 784 | dn_tot=0.d0 785 | do i=1,3 786 | xcorsta(i)=0.d0 787 | enddo 788 | 789 | *** 1 2 3 4 5 6 7 8 9 790 | *** columns are s,h,p,N',ps, dR(ip),dT(ip),dR(op),dT(op) 791 | 792 | do j=1,5 793 | thetaf=(datdi(1,j)*s+datdi(2,j)*h+datdi(3,j)*p+ 794 | * datdi(4,j)*zns+datdi(5,j)*ps)*deg2rad 795 | dr=datdi(6,j)*(3.d0*sinphi**2-1.d0)/2.*cos(thetaf)+ 796 | * datdi(8,j)*(3.d0*sinphi**2-1.d0)/2.*sin(thetaf) 797 | dn=datdi(7,j)*(cosphi*sinphi*2.d0)*cos(thetaf)+ 798 | * datdi(9,j)*(cosphi*sinphi*2.d0)*sin(thetaf) 799 | de=0.d0 800 | dr_tot=dr_tot+dr 801 | dn_tot=dn_tot+dn 802 | xcorsta(1)=xcorsta(1)+dr*cosla*cosphi-de*sinla 803 | * -dn*sinphi*cosla 804 | xcorsta(2)=xcorsta(2)+dr*sinla*cosphi+de*cosla 805 | * -dn*sinphi*sinla 806 | xcorsta(3)=xcorsta(3)+dr*sinphi+dn*cosphi 807 | enddo 808 | 809 | do i=1,3 810 | xcorsta(i)=xcorsta(i)/1000.d0 811 | enddo 812 | 813 | return 814 | end 815 | *----------------------------------------------------------------------- 816 | subroutine st1idiu(xsta,xsun,xmon,fac2sun,fac2mon,xcorsta) 817 | 818 | *** this subroutine gives the out-of-phase corrections induced by 819 | *** mantle inelasticity in the diurnal band 820 | 821 | *** input: xsta,xsun,xmon,fac2sun,fac2mon 822 | *** output: xcorsta 823 | 824 | implicit double precision (a-h,o-z) 825 | double precision xsta(3),xsun(3),xmon(3),xcorsta(3) 826 | double precision fac2sun,fac2mon 827 | data dhi/-0.0025d0/,dli/-0.0007d0/ 828 | 829 | rsta=enorm8(xsta) 830 | sinphi=xsta(3)/rsta 831 | cosphi=dsqrt(xsta(1)**2+xsta(2)**2)/rsta 832 | cos2phi=cosphi**2-sinphi**2 833 | sinla=xsta(2)/cosphi/rsta 834 | cosla=xsta(1)/cosphi/rsta 835 | rmon=enorm8(xmon) 836 | rsun=enorm8(xsun) 837 | drsun=-3.d0*dhi*sinphi*cosphi*fac2sun*xsun(3)*(xsun(1)* 838 | * sinla-xsun(2)*cosla)/rsun**2 839 | drmon=-3.d0*dhi*sinphi*cosphi*fac2mon*xmon(3)*(xmon(1)* 840 | * sinla-xmon(2)*cosla)/rmon**2 841 | dnsun=-3.d0*dli*cos2phi*fac2sun*xsun(3)*(xsun(1)*sinla- 842 | * xsun(2)*cosla)/rsun**2 843 | dnmon=-3.d0*dli*cos2phi*fac2mon*xmon(3)*(xmon(1)*sinla- 844 | * xmon(2)*cosla)/rmon**2 845 | desun=-3.d0*dli*sinphi*fac2sun*xsun(3)* 846 | * (xsun(1)*cosla+xsun(2)*sinla)/rsun**2 847 | demon=-3.d0*dli*sinphi*fac2mon*xmon(3)* 848 | * (xmon(1)*cosla+xmon(2)*sinla)/rmon**2 849 | dr=drsun+drmon 850 | dn=dnsun+dnmon 851 | de=desun+demon 852 | xcorsta(1)=dr*cosla*cosphi-de*sinla-dn*sinphi*cosla 853 | xcorsta(2)=dr*sinla*cosphi+de*cosla-dn*sinphi*sinla 854 | xcorsta(3)=dr*sinphi +dn*cosphi 855 | 856 | return 857 | end 858 | *----------------------------------------------------------------------- 859 | subroutine st1isem(xsta,xsun,xmon,fac2sun,fac2mon,xcorsta) 860 | 861 | *** this subroutine gives the out-of-phase corrections induced by 862 | *** mantle inelasticity in the diurnal band 863 | 864 | *** input: xsta,xsun,xmon,fac2sun,fac2mon 865 | *** output: xcorsta 866 | 867 | implicit double precision (a-h,o-z) 868 | double precision xsta(3),xsun(3),xmon(3),xcorsta(3) 869 | double precision fac2sun, fac2mon 870 | data dhi/-0.0022d0/,dli/-0.0007d0/ 871 | 872 | rsta=enorm8(xsta) 873 | sinphi=xsta(3)/rsta 874 | cosphi=dsqrt(xsta(1)**2+xsta(2)**2)/rsta 875 | sinla=xsta(2)/cosphi/rsta 876 | cosla=xsta(1)/cosphi/rsta 877 | costwola=cosla**2-sinla**2 878 | sintwola=2.d0*cosla*sinla 879 | rmon=enorm8(xmon) 880 | rsun=enorm8(xsun) 881 | drsun=-3.d0/4.d0*dhi*cosphi**2*fac2sun*((xsun(1)**2-xsun(2)**2)* 882 | * sintwola-2.*xsun(1)*xsun(2)*costwola)/rsun**2 883 | drmon=-3.d0/4.d0*dhi*cosphi**2*fac2mon*((xmon(1)**2-xmon(2)**2)* 884 | * sintwola-2.*xmon(1)*xmon(2)*costwola)/rmon**2 885 | dnsun=1.5d0*dli*sinphi*cosphi*fac2sun*((xsun(1)**2-xsun(2)**2)* 886 | * sintwola-2.d0*xsun(1)*xsun(2)*costwola)/rsun**2 887 | dnmon=1.5d0*dli*sinphi*cosphi*fac2mon*((xmon(1)**2-xmon(2)**2)* 888 | * sintwola-2.d0*xmon(1)*xmon(2)*costwola)/rmon**2 889 | desun=-3.d0/2.d0*dli*cosphi*fac2sun*((xsun(1)**2-xsun(2)**2)* 890 | * costwola+2.*xsun(1)*xsun(2)*sintwola)/rsun**2 891 | demon=-3.d0/2.d0*dli*cosphi*fac2mon*((xmon(1)**2-xmon(2)**2)* 892 | * costwola+2.d0*xmon(1)*xmon(2)*sintwola)/rmon**2 893 | dr=drsun+drmon 894 | dn=dnsun+dnmon 895 | de=desun+demon 896 | xcorsta(1)=dr*cosla*cosphi-de*sinla-dn*sinphi*cosla 897 | xcorsta(2)=dr*sinla*cosphi+de*cosla-dn*sinphi*sinla 898 | xcorsta(3)=dr*sinphi+dn*cosphi 899 | 900 | return 901 | end 902 | *----------------------------------------------------------------------- 903 | subroutine sprod(x,y,scal,r1,r2) 904 | 905 | *** computation of the scalar-product of two vectors and their norms 906 | 907 | *** input: x(i),i=1,2,3 -- components of vector x 908 | *** y(i),i=1,2,3 -- components of vector y 909 | *** output: scal -- scalar product of x and y 910 | *** r1,r2 -- lengths of the two vectors x and y 911 | 912 | implicit double precision (a-h,o-z) 913 | double precision x(3),y(3) 914 | double precision r1,r2,scal 915 | 916 | r1=dsqrt(x(1)*x(1) + x(2)*x(2) + x(3)*x(3)) 917 | r2=dsqrt(y(1)*y(1) + y(2)*y(2) + y(3)*y(3)) 918 | scal= x(1)*y(1) + x(2)*y(2) + x(3)*y(3) 919 | 920 | return 921 | end 922 | *----------------------------------------------------------------------- 923 | double precision function enorm8(a) 924 | 925 | *** compute euclidian norm of a vector (of length 3) 926 | 927 | double precision a(3) 928 | 929 | enorm8=dsqrt(a(1)*a(1) + a(2)*a(2) + a(3)*a(3)) 930 | 931 | return 932 | end 933 | *----------------------------------------------------------------------- 934 | subroutine zero_vec8(v) 935 | 936 | *** initialize a vector (of length 3) to zero 937 | 938 | double precision v(3) 939 | 940 | v(1)=0.d0 941 | v(2)=0.d0 942 | v(3)=0.d0 943 | 944 | return 945 | end 946 | *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 947 | subroutine moonxyz(mjd,fmjd,rm,lflag) 948 | 949 | *** get low-precision, geocentric coordinates for moon (ECEF) 950 | *** UTC version 951 | 952 | *** input: mjd/fmjd, is Modified Julian Date (and fractional) in UTC time 953 | *** output: rm, is geocentric lunar position vector [m] in ECEF 954 | *** lflag -- leap second table limit flag, false:flag not raised 955 | *** 1."satellite orbits: models, methods, applications" montenbruck & gill(2000) 956 | *** section 3.3.2, pg. 72-73 957 | *** 2."astronomy on the personal computer, 4th ed." montenbruck & pfleger (2005) 958 | *** section 3.2, pg. 38-39 routine MiniMoon 959 | 960 | implicit double precision(a-h,o-z) 961 | double precision rm(3) 962 | integer mjd 963 | double precision fmjd 964 | logical lflag,leapflag 965 | !*** leap second table limit flag 966 | !*** leap second table limit flag 967 | save /limitflag/ 968 | common/limitflag/leapflag 969 | common/stuff/rad,pi,pi2 970 | 971 | *** use TT for lunar ephemerides 972 | 973 | leapflag=lflag 974 | !*** UTC time (sec of day) 975 | !*** TT time (sec of day) 976 | !*** TT time (fract. day) 977 | tsecutc=fmjd*86400.d0 978 | tsectt =utc2ttt(tsecutc) 979 | fmjdtt =tsectt/86400.d0 980 | lflag = leapflag 981 | 982 | *** julian centuries since 1.5 january 2000 (J2000) 983 | *** (note: also low precision use of mjd --> tjd) 984 | 985 | !*** Julian Date, TT 986 | !*** julian centuries, TT 987 | tjdtt = mjd+fmjdtt+2400000.5d0 988 | t = (tjdtt - 2451545.d0)/36525.d0 989 | 990 | *** el0 -- mean longitude of Moon (deg) 991 | *** el -- mean anomaly of Moon (deg) 992 | *** elp -- mean anomaly of Sun (deg) 993 | *** f -- mean angular distance of Moon from ascending node (deg) 994 | *** d -- difference between mean longitudes of Sun and Moon (deg) 995 | 996 | *** equations 3.47, p.72 997 | 998 | el0=218.31617d0 + 481267.88088d0*t -1.3972*t 999 | el =134.96292d0 + 477198.86753d0*t 1000 | elp=357.52543d0 + 35999.04944d0*t 1001 | f = 93.27283d0 + 483202.01873d0*t 1002 | d =297.85027d0 + 445267.11135d0*t 1003 | 1004 | *** longitude w.r.t. equinox and ecliptic of year 2000 1005 | 1006 | !*** eq 3.48, p.72 1007 | selond=el0 1008 | * +22640.d0/3600.d0*dsin((el )/rad) 1009 | * + 769.d0/3600.d0*dsin((el+el )/rad) 1010 | * - 4586.d0/3600.d0*dsin((el-d-d )/rad) 1011 | * + 2370.d0/3600.d0*dsin((d+d )/rad) 1012 | * - 668.d0/3600.d0*dsin((elp )/rad) 1013 | * - 412.d0/3600.d0*dsin((f+f )/rad) 1014 | * - 212.d0/3600.d0*dsin((el+el-d-d )/rad) 1015 | * - 206.d0/3600.d0*dsin((el+elp-d-d)/rad) 1016 | * + 192.d0/3600.d0*dsin((el+d+d )/rad) 1017 | * - 165.d0/3600.d0*dsin((elp-d-d )/rad) 1018 | * + 148.d0/3600.d0*dsin((el-elp )/rad) 1019 | * - 125.d0/3600.d0*dsin((d )/rad) 1020 | * - 110.d0/3600.d0*dsin((el+elp )/rad) 1021 | * - 55.d0/3600.d0*dsin((f+f-d-d )/rad) 1022 | 1023 | *** latitude w.r.t. equinox and ecliptic of year 2000 1024 | 1025 | !*** temporary term 1026 | q = 412.d0/3600.d0*dsin((f+f)/rad) 1027 | * +541.d0/3600.d0*dsin((elp)/rad) 1028 | 1029 | !*** eq 3.49, p.72 1030 | selatd= 1031 | * +18520.d0/3600.d0*dsin((f+selond-el0+q)/rad) 1032 | * - 526.d0/3600.d0*dsin((f-d-d )/rad) 1033 | * + 44.d0/3600.d0*dsin((el+f-d-d )/rad) 1034 | * - 31.d0/3600.d0*dsin((-el+f-d-d )/rad) 1035 | * - 25.d0/3600.d0*dsin((-el-el+f )/rad) 1036 | * - 23.d0/3600.d0*dsin((elp+f-d-d )/rad) 1037 | * + 21.d0/3600.d0*dsin((-el+f )/rad) 1038 | * + 11.d0/3600.d0*dsin((-elp+f-d-d)/rad) 1039 | 1040 | *** distance from Earth center to Moon (m) 1041 | 1042 | !*** eq 3.50, p.72 1043 | rse= 385000.d0*1000.d0 1044 | * - 20905.d0*1000.d0*dcos((el )/rad) 1045 | * - 3699.d0*1000.d0*dcos((d+d-el )/rad) 1046 | * - 2956.d0*1000.d0*dcos((d+d )/rad) 1047 | * - 570.d0*1000.d0*dcos((el+el )/rad) 1048 | * + 246.d0*1000.d0*dcos((el+el-d-d )/rad) 1049 | * - 205.d0*1000.d0*dcos((elp-d-d )/rad) 1050 | * - 171.d0*1000.d0*dcos((el+d+d )/rad) 1051 | * - 152.d0*1000.d0*dcos((el+elp-d-d)/rad) 1052 | 1053 | *** convert spherical ecliptic coordinates to equatorial cartesian 1054 | 1055 | *** precession of equinox wrt. J2000 (p.71) 1056 | 1057 | !*** degrees 1058 | selond=selond + 1.3972d0*t 1059 | 1060 | *** position vector of moon (mean equinox & ecliptic of J2000) (EME2000, ICRF) 1061 | *** (plus long. advance due to precession -- eq. above) 1062 | 1063 | !*** obliquity of the J2000 ecliptic 1064 | oblir=23.43929111d0/rad 1065 | 1066 | sselat=dsin(selatd/rad) 1067 | cselat=dcos(selatd/rad) 1068 | sselon=dsin(selond/rad) 1069 | cselon=dcos(selond/rad) 1070 | 1071 | !*** meters !*** eq. 3.51, p.72 1072 | !*** meters !*** eq. 3.51, p.72 1073 | !*** meters !*** eq. 3.51, p.72 1074 | t1 = rse*cselon*cselat 1075 | t2 = rse*sselon*cselat 1076 | t3 = rse* sselat 1077 | 1078 | !*** eq. 3.51, p.72 1079 | call rot1(-oblir,t1,t2,t3,rm1,rm2,rm3) 1080 | 1081 | *** convert position vector of moon to ECEF (ignore polar motion/LOD) 1082 | 1083 | !*** sec 2.3.1,p.33 1084 | !*** eq. 2.89, p.37 1085 | call getghar(mjd,fmjd,ghar) 1086 | call rot3(ghar,rm1,rm2,rm3,rm(1),rm(2),rm(3)) 1087 | 1088 | return 1089 | end 1090 | ******************************************************************************** 1091 | subroutine getghar(mjd,fmjd,ghar) 1092 | 1093 | *** convert mjd/fmjd in UTC time to Greenwich hour angle (in radians) 1094 | 1095 | *** "satellite orbits: models, methods, applications" montenbruck & gill(2000) 1096 | *** section 2.3.1, pg. 33 1097 | 1098 | implicit double precision(a-h,o-z) 1099 | integer mjd 1100 | double precision fmjd,ghar 1101 | common/stuff/rad,pi,pi2 1102 | 1103 | *** need UTC to get sidereal time ("astronomy on the personal computer", 4th ed) 1104 | *** (pg.43, montenbruck & pfleger, springer, 2005) 1105 | 1106 | !*** UTC time (sec of day) 1107 | !*** UTC time (fract. day) 1108 | tsecutc=fmjd*86400.d0 1109 | fmjdutc=tsecutc/86400.d0 1110 | 1111 | ***** d = MJD - 51544.5d0 1112 | !*** footnote 1113 | !*** days since J2000 1114 | d =(mjd-51544) + (fmjdutc-0.5d0) 1115 | 1116 | *** greenwich hour angle for J2000 (12:00:00 on 1 Jan 2000) 1117 | 1118 | ***** ghad = 100.46061837504d0 + 360.9856473662862d0*d 1119 | !*** eq. 2.85 (+digits) 1120 | !*** corrn. (+digits) 1121 | ghad = 280.46061837504d0 + 360.9856473662862d0*d 1122 | 1123 | **** normalize to 0-360 and convert to radians 1124 | 1125 | i = ghad/360.d0 1126 | ghar =(ghad-i*360.d0)/rad 1127 | 1 if(ghar.gt.pi2) then 1128 | ghar=ghar-pi2 1129 | go to 1 1130 | endif 1131 | 2 if(ghar.lt.0.d0) then 1132 | ghar=ghar+pi2 1133 | go to 2 1134 | endif 1135 | 1136 | return 1137 | end 1138 | ******************************************************************************** 1139 | subroutine sunxyz(mjd,fmjd,rs,lflag) 1140 | 1141 | *** get low-precision, geocentric coordinates for sun (ECEF) 1142 | 1143 | *** input, mjd/fmjd, is Modified Julian Date (and fractional) in UTC time 1144 | *** output, rs, is geocentric solar position vector [m] in ECEF 1145 | *** lflag -- leap second table limit flag, false:flag not raised 1146 | *** 1."satellite orbits: models, methods, applications" montenbruck & gill(2000) 1147 | *** section 3.3.2, pg. 70-71 1148 | *** 2."astronomy on the personal computer, 4th ed." montenbruck & pfleger (2005) 1149 | *** section 3.2, pg. 39 routine MiniSun 1150 | 1151 | implicit double precision(a-h,o-z) 1152 | double precision rs(3) 1153 | double precision fmjd 1154 | integer mjd 1155 | logical lflag,leapflag 1156 | !*** leap second table limit flag 1157 | !*** leap second table limit flag 1158 | save /limitflag/ 1159 | common/limitflag/leapflag 1160 | common/stuff/rad,pi,pi2 1161 | 1162 | *** mean elements for year 2000, sun ecliptic orbit wrt. Earth 1163 | 1164 | !*** obliquity of the J2000 ecliptic 1165 | obe =23.43929111d0/rad 1166 | sobe=dsin(obe) 1167 | cobe=dcos(obe) 1168 | !*** RAAN + arg.peri. (deg.) 1169 | opod=282.9400d0 1170 | 1171 | *** use TT for solar ephemerides 1172 | 1173 | leapflag=lflag 1174 | !*** UTC time (sec of day) 1175 | !*** TT time (sec of day) 1176 | !*** TT time (fract. day) 1177 | tsecutc=fmjd*86400.d0 1178 | tsectt =utc2ttt(tsecutc) 1179 | fmjdtt =tsectt/86400.d0 1180 | lflag = leapflag 1181 | 1182 | *** julian centuries since 1.5 january 2000 (J2000) 1183 | *** (note: also low precision use of mjd --> tjd) 1184 | 1185 | !*** Julian Date, TT 1186 | !*** julian centuries, TT 1187 | !*** degrees 1188 | !*** radians 1189 | !*** radians 1190 | tjdtt = mjd+fmjdtt+2400000.5d0 1191 | t = (tjdtt - 2451545.d0)/36525.d0 1192 | emdeg = 357.5256d0 + 35999.049d0*t 1193 | em = emdeg/rad 1194 | em2 = em+em 1195 | 1196 | *** series expansions in mean anomaly, em (eq. 3.43, p.71) 1197 | 1198 | !*** m. 1199 | r=(149.619d0-2.499d0*dcos(em)-0.021d0*dcos(em2))*1.d9 1200 | slond=opod + emdeg + (6892.d0*dsin(em)+72.d0*dsin(em2))/3600.d0 1201 | 1202 | *** precession of equinox wrt. J2000 (p.71) 1203 | 1204 | !*** degrees 1205 | slond=slond + 1.3972d0*t 1206 | 1207 | *** position vector of sun (mean equinox & ecliptic of J2000) (EME2000, ICRF) 1208 | *** (plus long. advance due to precession -- eq. above) 1209 | 1210 | !*** radians 1211 | slon =slond/rad 1212 | sslon=dsin(slon) 1213 | cslon=dcos(slon) 1214 | 1215 | !*** meters !*** eq. 3.46, p.71 1216 | !*** meters !*** eq. 3.46, p.71 1217 | !*** meters !*** eq. 3.46, p.71 1218 | rs1 = r*cslon 1219 | rs2 = r*sslon*cobe 1220 | rs3 = r*sslon*sobe 1221 | 1222 | *** convert position vector of sun to ECEF (ignore polar motion/LOD) 1223 | 1224 | !*** sec 2.3.1,p.33 1225 | !*** eq. 2.89, p.37 1226 | call getghar(mjd,fmjd,ghar) 1227 | call rot3(ghar,rs1,rs2,rs3,rs(1),rs(2),rs(3)) 1228 | 1229 | return 1230 | end 1231 | ******************************************************************************** 1232 | subroutine lhsaaz(u,v,w,ra,az,va) 1233 | 1234 | *** determine range,azimuth,vertical angle from local horizon coord. 1235 | 1236 | implicit double precision(a-h,o-z) 1237 | double precision u,v,w,ra,az,va 1238 | 1239 | s2=u*u+v*v 1240 | r2=s2+w*w 1241 | 1242 | s =dsqrt(s2) 1243 | ra=dsqrt(r2) 1244 | 1245 | az=datan2(v,u) 1246 | va=datan2(w,s) 1247 | 1248 | return 1249 | end 1250 | *----------------------------------------------------------------------- 1251 | subroutine geoxyz(gla,glo,eht,x,y,z) 1252 | 1253 | *** convert geodetic lat, long, ellip ht. to x,y,z 1254 | 1255 | implicit double precision(a-h,o-z) 1256 | double precision gla,glo,eht,x,y,z 1257 | common/comgrs/a,e2 1258 | 1259 | sla=dsin(gla) 1260 | cla=dcos(gla) 1261 | w2=1.d0-e2*sla*sla 1262 | w=dsqrt(w2) 1263 | en=a/w 1264 | 1265 | x=(en+eht)*cla*dcos(glo) 1266 | y=(en+eht)*cla*dsin(glo) 1267 | z=(en*(1.d0-e2)+eht)*sla 1268 | 1269 | return 1270 | end 1271 | *----------------------------------------------------------------------- 1272 | subroutine rge(gla,glo,u,v,w,x,y,z) 1273 | 1274 | *** given a rectangular cartesian system (x,y,z) 1275 | *** compute a geodetic h cartesian sys (u,v,w) 1276 | 1277 | implicit double precision(a-h,o-z) 1278 | double precision gla,glo,u,v,w,x,y,z 1279 | 1280 | sb=dsin(gla) 1281 | cb=dcos(gla) 1282 | sl=dsin(glo) 1283 | cl=dcos(glo) 1284 | 1285 | u=-sb*cl*x-sb*sl*y+cb*z 1286 | v=- sl*x+ cl*y 1287 | w= cb*cl*x+cb*sl*y+sb*z 1288 | 1289 | return 1290 | end 1291 | *----------------------------------------------------------------------- 1292 | subroutine rot1(theta,x,y,z,u,v,w) 1293 | 1294 | *** rotate coordinate axes about 1 axis by angle of theta radians 1295 | *** x,y,z transformed into u,v,w 1296 | 1297 | implicit double precision(a-h,o-z) 1298 | double precision theta,x,y,z,u,v,w 1299 | 1300 | s=dsin(theta) 1301 | c=dcos(theta) 1302 | 1303 | u=x 1304 | v=c*y+s*z 1305 | w=c*z-s*y 1306 | 1307 | return 1308 | end 1309 | *----------------------------------------------------------------------- 1310 | subroutine rot3(theta,x,y,z,u,v,w) 1311 | 1312 | *** rotate coordinate axes about 3 axis by angle of theta radians 1313 | *** x,y,z transformed into u,v,w 1314 | 1315 | implicit double precision(a-h,o-z) 1316 | double precision theta,x,y,z,u,v,w 1317 | 1318 | s=dsin(theta) 1319 | c=dcos(theta) 1320 | 1321 | u=c*x+s*y 1322 | v=c*y-s*x 1323 | w=z 1324 | 1325 | return 1326 | end 1327 | ************************************************************************ 1328 | *** time conversion **************************************************** 1329 | ************************************************************************ 1330 | subroutine setjd0(iyr,imo,idy) 1331 | 1332 | *** set the integer part of a modified julian date as epoch, mjd0 1333 | *** the modified julian day is derived from civil time as in civmjd() 1334 | *** allows single number expression of time in seconds w.r.t. mjd0 1335 | 1336 | implicit double precision(a-h,o-z) 1337 | integer y,iyr,imo,idy 1338 | save /mjdoff/ 1339 | common/mjdoff/mjd0 1340 | 1341 | if(iyr.lt.1900) stop 34587 1342 | 1343 | if(imo.le.2) then 1344 | y=iyr-1 1345 | m=imo+12 1346 | else 1347 | y=iyr 1348 | m=imo 1349 | endif 1350 | 1351 | it1=365.25d0*y 1352 | it2=30.6001d0*(m+1) 1353 | mjd=it1+it2+idy-679019 1354 | 1355 | *** now set the epoch for future time computations 1356 | 1357 | mjd0=mjd 1358 | 1359 | return 1360 | end 1361 | subroutine civjts(iyr,imo,idy,ihr,imn,sec,tsec) 1362 | 1363 | *** convert civil date to time in seconds past mjd epoch, mjd0 1364 | *** requires initialization of mjd0 by setjd0() 1365 | 1366 | *** imo in range 1-12, idy in range 1-31 1367 | *** only valid in range mar-1900 thru feb-2100 (leap year protocols) 1368 | *** ref: hofmann-wellenhof, 2nd ed., pg 34-35 1369 | *** adapted from civmjd() 1370 | 1371 | implicit double precision(a-h,o-z) 1372 | integer y,iyr,imo,idy,ihr,imn 1373 | double precision sec,tsec 1374 | save /mjdoff/ 1375 | common/mjdoff/mjd0 1376 | 1377 | if(iyr.lt.1900) stop 34589 1378 | 1379 | if(imo.le.2) then 1380 | y=iyr-1 1381 | m=imo+12 1382 | else 1383 | y=iyr 1384 | m=imo 1385 | endif 1386 | 1387 | it1=365.25d0*y 1388 | it2=30.6001d0*(m+1) 1389 | mjd=it1+it2+idy-679019 1390 | 1391 | tsec=(mjd-mjd0)*86400.d0+3600*ihr+60*imn+sec 1392 | 1393 | return 1394 | end 1395 | subroutine jtsciv(tsec,iyr,imo,idy,ihr,imn,sec) 1396 | 1397 | *** convert time in seconds past mjd0 epoch into civil date 1398 | *** requires initialization of mjd0 by setjd0() 1399 | 1400 | *** imo in range 1-12, idy in range 1-31 1401 | *** only valid in range mar-1900 thru feb-2100 1402 | *** ref: hofmann-wellenhof, 2nd ed., pg 34-35 1403 | *** adapted from mjdciv() 1404 | 1405 | implicit double precision(a-h,o-z) 1406 | integer iyr,imo,idy,ihr,imn 1407 | double precision sec,tsec 1408 | save /mjdoff/ 1409 | common/mjdoff/mjd0 1410 | 1411 | mjd=mjd0+tsec/86400.d0 1412 | *** the following equation preserves significant digits 1413 | fmjd=dmod(tsec,86400.d0)/86400.d0 1414 | 1415 | rjd=mjd+fmjd+2400000.5d0 1416 | ia=(rjd+0.5d0) 1417 | ib=ia+1537 1418 | ic=(ib-122.1d0)/365.25d0 1419 | id=365.25d0*ic 1420 | ie=(ib-id)/30.6001d0 1421 | 1422 | *** the fractional part of a julian day is (fractional mjd + 0.5) 1423 | *** therefore, fractional part of julian day + 0.5 is (fractional mjd) 1424 | 1425 | it1=ie*30.6001d0 1426 | idy=ib-id-it1+fmjd 1427 | it2=ie/14.d0 1428 | imo=ie-1-12*it2 1429 | it3=(7+imo)/10.d0 1430 | iyr=ic-4715-it3 1431 | 1432 | tmp=fmjd*24.d0 1433 | ihr=tmp 1434 | tmp=(tmp-ihr)*60.d0 1435 | imn=tmp 1436 | sec=(tmp-imn)*60.d0 1437 | 1438 | return 1439 | end 1440 | ************************************************************************ 1441 | subroutine civmjd(iyr,imo,idy,ihr,imn,sec,mjd,fmjd) 1442 | 1443 | *** convert civil date to modified julian date 1444 | 1445 | *** imo in range 1-12, idy in range 1-31 1446 | *** only valid in range mar-1900 thru feb-2100 (leap year protocols) 1447 | *** ref: hofmann-wellenhof, 2nd ed., pg 34-35 1448 | *** operation confirmed against table 3.3 values on pg.34 1449 | 1450 | implicit double precision(a-h,o-z) 1451 | integer y,iyr,imo,idy,ihr,imn,mjd 1452 | double precision sec,fmjd 1453 | 1454 | if(iyr.lt.1900) stop 34588 1455 | 1456 | if(imo.le.2) then 1457 | y=iyr-1 1458 | m=imo+12 1459 | else 1460 | y=iyr 1461 | m=imo 1462 | endif 1463 | 1464 | it1=365.25d0*y 1465 | it2=30.6001d0*(m+1) 1466 | mjd=it1+it2+idy-679019 1467 | 1468 | fmjd=(3600*ihr+60*imn+sec)/86400.d0 1469 | 1470 | return 1471 | end 1472 | *----------------------------------------------------------------------- 1473 | subroutine mjdciv(mjd,fmjd,iyr,imo,idy,ihr,imn,sec) 1474 | 1475 | *** convert modified julian date to civil date 1476 | 1477 | *** imo in range 1-12, idy in range 1-31 1478 | *** only valid in range mar-1900 thru feb-2100 1479 | *** ref: hofmann-wellenhof, 2nd ed., pg 34-35 1480 | *** operation confirmed for leap years (incl. year 2000) 1481 | 1482 | implicit double precision(a-h,o-z) 1483 | integer mjd,iyr,imo,idy,ihr,imn 1484 | double precision fmjd,sec 1485 | 1486 | rjd=mjd+fmjd+2400000.5d0 1487 | ia=(rjd+0.5d0) 1488 | ib=ia+1537 1489 | ic=(ib-122.1d0)/365.25d0 1490 | id=365.25d0*ic 1491 | ie=(ib-id)/30.6001d0 1492 | 1493 | *** the fractional part of a julian day is fractional mjd + 0.5 1494 | *** therefore, fractional part of julian day + 0.5 is fractional mjd 1495 | 1496 | it1=ie*30.6001d0 1497 | idy=ib-id-it1+fmjd 1498 | it2=ie/14.d0 1499 | imo=ie-1-12*it2 1500 | it3=(7+imo)/10.d0 1501 | iyr=ic-4715-it3 1502 | 1503 | tmp=fmjd*24.d0 1504 | ihr=tmp 1505 | tmp=(tmp-ihr)*60.d0 1506 | imn=tmp 1507 | sec=(tmp-imn)*60.d0 1508 | 1509 | return 1510 | end 1511 | ************************************************************************ 1512 | *** new supplemental time functions ************************************ 1513 | ************************************************************************ 1514 | double precision function utc2ttt(tutc) 1515 | 1516 | *** convert utc (sec) to terrestrial time (sec) 1517 | 1518 | implicit double precision(a-h,o-z) 1519 | double precision tutc 1520 | 1521 | ttai = utc2tai(tutc) 1522 | utc2ttt= tai2tt(ttai) 1523 | 1524 | return 1525 | end 1526 | *----------------------------------------------------------------------- 1527 | double precision function gps2ttt(tgps) 1528 | 1529 | *** convert gps time (sec) to terrestrial time (sec) 1530 | 1531 | implicit double precision(a-h,o-z) 1532 | double precision tgps 1533 | 1534 | ttai = gps2tai(tgps) 1535 | gps2ttt= tai2tt(ttai) 1536 | 1537 | return 1538 | end 1539 | *----------------------------------------------------------------------- 1540 | double precision function utc2tai(tutc) 1541 | 1542 | *** convert utc (sec) to tai (sec) 1543 | 1544 | implicit double precision(a-h,o-z) 1545 | double precision tutc 1546 | 1547 | utc2tai = tutc - getutcmtai(tutc) 1548 | 1549 | return 1550 | end 1551 | *----------------------------------------------------------------------- 1552 | double precision function getutcmtai(tsec) 1553 | 1554 | *** get utc - tai (s) 1555 | 1556 | ***** "Julian Date Converter" 1557 | ***** http://aa.usno.navy.mil/data/docs/JulianDate.php 1558 | ***** http://www.csgnetwork.com/julianmodifdateconv.html 1559 | 1560 | implicit double precision(a-h,o-z) 1561 | double precision tsec 1562 | !*** upper limit, leap second table, 2025dec28 1563 | !*** lower limit, leap second table, 1972jan01 1564 | parameter(MJDUPPER=61037) 1565 | parameter(MJDLOWER=41317) 1566 | 1567 | !*** leap second table limit flag 1568 | logical leapflag 1569 | save /limitflag/ 1570 | !*** leap second table limit flag 1571 | common/limitflag/leapflag 1572 | 1573 | save /mjdoff/ 1574 | common/mjdoff/mjd0 1575 | 1576 | *** clone for tests (and do any rollover) 1577 | 1578 | ttsec=tsec 1579 | mjd0t=mjd0 1580 | 1581 | 1 if(ttsec.ge.86400.d0) then 1582 | ttsec=ttsec-86400.d0 1583 | mjd0t=mjd0t+1 1584 | go to 1 1585 | endif 1586 | 1587 | 2 if(ttsec.lt.0.d0) then 1588 | ttsec=ttsec+86400.d0 1589 | mjd0t=mjd0t-1 1590 | go to 2 1591 | endif 1592 | 1593 | *** test upper table limit (upper limit set by bulletin C memos) 1594 | 1595 | if(mjd0t.gt.MJDUPPER) then 1596 | !*** true means flag *IS* raised 1597 | !*** return the upper table value 1598 | leapflag =.true. 1599 | getutcmtai= -37.d0 1600 | return 1601 | endif 1602 | 1603 | *** test lower table limit 1604 | 1605 | if(mjd0t.lt.MJDLOWER) then 1606 | !*** true means flag *IS* raised 1607 | !*** return the lower table value 1608 | leapflag=.true. 1609 | getutcmtai= -10.d0 1610 | return 1611 | endif 1612 | 1613 | ***** https://maia.usno.navy.mil/ser7/tai-utc.dat 1614 | *** 1972 JAN 1 =JD 2441317.5 TAI-UTC= 10.0s 1615 | *** 1972 JUL 1 =JD 2441499.5 TAI-UTC= 11.0s 1616 | *** 1973 JAN 1 =JD 2441683.5 TAI-UTC= 12.0s 1617 | *** 1974 JAN 1 =JD 2442048.5 TAI-UTC= 13.0s 1618 | *** 1975 JAN 1 =JD 2442413.5 TAI-UTC= 14.0s 1619 | *** 1976 JAN 1 =JD 2442778.5 TAI-UTC= 15.0s 1620 | *** 1977 JAN 1 =JD 2443144.5 TAI-UTC= 16.0s 1621 | *** 1978 JAN 1 =JD 2443509.5 TAI-UTC= 17.0s 1622 | *** 1979 JAN 1 =JD 2443874.5 TAI-UTC= 18.0s 1623 | *** 1980 JAN 1 =JD 2444239.5 TAI-UTC= 19.0s 1624 | *** 1981 JUL 1 =JD 2444786.5 TAI-UTC= 20.0s 1625 | *** 1982 JUL 1 =JD 2445151.5 TAI-UTC= 21.0s 1626 | *** 1983 JUL 1 =JD 2445516.5 TAI-UTC= 22.0s 1627 | *** 1985 JUL 1 =JD 2446247.5 TAI-UTC= 23.0s 1628 | *** 1988 JAN 1 =JD 2447161.5 TAI-UTC= 24.0s 1629 | *** 1990 JAN 1 =JD 2447892.5 TAI-UTC= 25.0s 1630 | *** 1991 JAN 1 =JD 2448257.5 TAI-UTC= 26.0s 1631 | *** 1992 JUL 1 =JD 2448804.5 TAI-UTC= 27.0s 1632 | *** 1993 JUL 1 =JD 2449169.5 TAI-UTC= 28.0s 1633 | *** 1994 JUL 1 =JD 2449534.5 TAI-UTC= 29.0s 1634 | *** 1996 JAN 1 =JD 2450083.5 TAI-UTC= 30.0s 1635 | *** 1997 JUL 1 =JD 2450630.5 TAI-UTC= 31.0s 1636 | *** 1999 JAN 1 =JD 2451179.5 TAI-UTC= 32.0s 1637 | *** 2006 JAN 1 =JD 2453736.5 TAI-UTC= 33.0s 1638 | *** 2009 JAN 1 =JD 2454832.5 TAI-UTC= 34.0s 1639 | *** 2012 JUL 1 =JD 2456109.5 TAI-UTC= 35.0s 1640 | *** 2015 JUL 1 =JD 2457204.5 TAI-UTC= 36.0s 1641 | *** 2017 JAN 1 =JD 2457754.5 TAI-UTC= 37.0s 1642 | ***** other leap second references at: 1643 | ***** http://hpiers.obspm.fr/eoppc/bul/bulc/Leap_Second_History.dat 1644 | ***** http://hpiers.obspm.fr/eoppc/bul/bulc/bulletinc.dat 1645 | ***** File expires on 28 December 2025 1646 | 1647 | *** test against newest leaps first 1648 | 1649 | if (mjd0t.ge.57754) then 1650 | !*** 2017 JAN 1 = 57754 1651 | tai_utc = 37.d0 1652 | elseif(mjd0t.ge.57204) then 1653 | !*** 2015 JUL 1 = 57204 1654 | tai_utc = 36.d0 1655 | elseif(mjd0t.ge.56109) then 1656 | !*** 2012 JUL 1 = 56109 1657 | tai_utc = 35.d0 1658 | elseif(mjd0t.ge.54832) then 1659 | !*** 2009 JAN 1 = 54832 1660 | tai_utc = 34.d0 1661 | elseif(mjd0t.ge.53736) then 1662 | !*** 2006 JAN 1 = 53736 1663 | tai_utc = 33.d0 1664 | elseif(mjd0t.ge.51179) then 1665 | !*** 1999 JAN 1 = 51179 1666 | tai_utc = 32.d0 1667 | elseif(mjd0t.ge.50630) then 1668 | !*** 1997 JUL 1 = 50630 1669 | tai_utc = 31.d0 1670 | elseif(mjd0t.ge.50083) then 1671 | !*** 1996 JAN 1 = 50083 1672 | tai_utc = 30.d0 1673 | elseif(mjd0t.ge.49534) then 1674 | !*** 1994 JUL 1 = 49534 1675 | tai_utc = 29.d0 1676 | elseif(mjd0t.ge.49169) then 1677 | !*** 1993 JUL 1 = 49169 1678 | tai_utc = 28.d0 1679 | elseif(mjd0t.ge.48804) then 1680 | !*** 1992 JUL 1 = 48804 1681 | tai_utc = 27.d0 1682 | elseif(mjd0t.ge.48257) then 1683 | !*** 1991 JAN 1 = 48257 1684 | tai_utc = 26.d0 1685 | elseif(mjd0t.ge.47892) then 1686 | !*** 1990 JAN 1 = 47892 1687 | tai_utc = 25.d0 1688 | elseif(mjd0t.ge.47161) then 1689 | !*** 1988 JAN 1 = 47161 1690 | tai_utc = 24.d0 1691 | elseif(mjd0t.ge.46247) then 1692 | !*** 1985 JUL 1 = 46247 1693 | tai_utc = 23.d0 1694 | elseif(mjd0t.ge.45516) then 1695 | !*** 1983 JUL 1 = 45516 1696 | tai_utc = 22.d0 1697 | elseif(mjd0t.ge.45151) then 1698 | !*** 1982 JUL 1 = 45151 1699 | tai_utc = 21.d0 1700 | elseif(mjd0t.ge.44786) then 1701 | !*** 1981 JUL 1 = 44786 1702 | tai_utc = 20.d0 1703 | elseif(mjd0t.ge.44239) then 1704 | !*** 1980 JAN 1 = 44239 1705 | tai_utc = 19.d0 1706 | elseif(mjd0t.ge.43874) then 1707 | !*** 1979 JAN 1 = 43874 1708 | tai_utc = 18.d0 1709 | elseif(mjd0t.ge.43509) then 1710 | !*** 1978 JAN 1 = 43509 1711 | tai_utc = 17.d0 1712 | elseif(mjd0t.ge.43144) then 1713 | !*** 1977 JAN 1 = 43144 1714 | tai_utc = 16.d0 1715 | elseif(mjd0t.ge.42778) then 1716 | !*** 1976 JAN 1 = 42778 1717 | tai_utc = 15.d0 1718 | elseif(mjd0t.ge.42413) then 1719 | !*** 1975 JAN 1 = 42413 1720 | tai_utc = 14.d0 1721 | elseif(mjd0t.ge.42048) then 1722 | !*** 1974 JAN 1 = 42048 1723 | tai_utc = 13.d0 1724 | elseif(mjd0t.ge.41683) then 1725 | !*** 1973 JAN 1 = 41683 1726 | tai_utc = 12.d0 1727 | elseif(mjd0t.ge.41499) then 1728 | !*** 1972 JUL 1 = 41499 1729 | tai_utc = 11.d0 1730 | elseif(mjd0t.ge.41317) then 1731 | !*** 1972 JAN 1 = 41317 1732 | tai_utc = 10.d0 1733 | 1734 | *** should never, ever get here 1735 | 1736 | else 1737 | write(*,*) 'FATAL ERROR --' 1738 | write(*,*) 'fell thru tests in gpsleap()' 1739 | stop 66768 1740 | endif 1741 | 1742 | *** return utc - tai (in seconds) 1743 | 1744 | getutcmtai = -tai_utc 1745 | 1746 | return 1747 | end 1748 | *----------------------------------------------------------------------- 1749 | double precision function tai2tt(ttai) 1750 | 1751 | *** convert tai (sec) to terrestrial time (sec) 1752 | 1753 | implicit double precision(a-h,o-z) 1754 | double precision ttai 1755 | 1756 | ***** http://tycho.usno.navy.mil/systime.html 1757 | tai2tt = ttai + 32.184d0 1758 | 1759 | return 1760 | end 1761 | *----------------------------------------------------------------------- 1762 | double precision function gps2tai(tgps) 1763 | 1764 | *** convert gps time (sec) to tai (sec) 1765 | 1766 | implicit double precision(a-h,o-z) 1767 | double precision tgps 1768 | 1769 | ***** http://leapsecond.com/java/gpsclock.htm 1770 | ***** http://tycho.usno.navy.mil/leapsec.html 1771 | gps2tai = tgps + 19.d0 1772 | 1773 | return 1774 | end 1775 | -------------------------------------------------------------------------------- /tests/grid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Author: Zhang Yunjun, Jan 2021 3 | # Copyright 2020, by the California Institute of Technology. 4 | 5 | 6 | import os 7 | import sys 8 | import datetime as dt 9 | 10 | import numpy as np 11 | 12 | import pysolid 13 | 14 | 15 | if __name__ == '__main__': 16 | 17 | # print the file/module path 18 | print('-'*50) 19 | print(os.path.abspath(__file__)) 20 | 21 | # prepare inputs 22 | dt_obj = dt.datetime(2020, 12, 25, 14, 7, 44) 23 | atr = { 24 | 'LENGTH' : 400, 25 | 'WIDTH' : 500, 26 | 'X_FIRST' : -118.2, 27 | 'Y_FIRST' : 33.8, 28 | 'X_STEP' : 0.000833333, 29 | 'Y_STEP' : -0.000833333, 30 | } 31 | 32 | # reference 33 | # calculated based on version 0.3.2.post6 on Jun 24, 2024 34 | # env: macOS with python-3.10, numpy-1.24 35 | # install: manual compilation via f2py 36 | tide_e_80_100 = np.array( 37 | [[0.01628786, 0.01630887, 0.01633078, 0.01635247, 0.01637394], 38 | [0.01633248, 0.01635348, 0.01637538, 0.01639706, 0.01641851], 39 | [0.01638009, 0.01640107, 0.01642296, 0.01644462, 0.01646606], 40 | [0.01642767, 0.01644864, 0.01647052, 0.01649217, 0.01651359], 41 | [0.01647523, 0.01649619, 0.01651805, 0.01653968, 0.01656109]], 42 | ) 43 | tide_n_80_100 = np.array( 44 | [[-0.02406203, -0.02412341, -0.02418807, -0.02425273, -0.0243174 ], 45 | [-0.02407558, -0.02413699, -0.02420168, -0.02426637, -0.02433107], 46 | [-0.02408992, -0.02415136, -0.02421608, -0.02428081, -0.02434554], 47 | [-0.02410413, -0.0241656 , -0.02423036, -0.02429511, -0.02435988], 48 | [-0.02411821, -0.02417972, -0.0242445 , -0.02430929, -0.02437408]], 49 | ) 50 | tide_u_80_100 = np.array( 51 | [[-0.05548462, -0.05533455, -0.05517631, -0.05501789, -0.05485928], 52 | [-0.05529561, -0.0551451 , -0.05498639, -0.0548275 , -0.05466843], 53 | [-0.05509374, -0.05494276, -0.05478355, -0.05462417, -0.05446461], 54 | [-0.05489176, -0.05474031, -0.05458061, -0.05442073, -0.05426067], 55 | [-0.05468968, -0.05453776, -0.05437757, -0.05421719, -0.05405664]], 56 | ) 57 | 58 | # calculate 59 | (tide_e, 60 | tide_n, 61 | tide_u) = pysolid.calc_solid_earth_tides_grid(dt_obj, atr, verbose=True) 62 | 63 | # compare 64 | assert np.allclose(tide_e[::80, ::100], tide_e_80_100) 65 | assert np.allclose(tide_n[::80, ::100], tide_n_80_100) 66 | assert np.allclose(tide_u[::80, ::100], tide_u_80_100) 67 | 68 | # plot 69 | out_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'pic')) 70 | os.makedirs(out_dir, exist_ok=True) 71 | 72 | out_fig = os.path.join(out_dir, 'grid.png') 73 | pysolid.plot_solid_earth_tides_grid( 74 | tide_e, tide_n, tide_u, dt_obj, 75 | out_fig=out_fig, 76 | display=False) 77 | 78 | # open the plotted figures 79 | if sys.platform in ['linux']: 80 | os.system(f'display {out_fig}') 81 | elif sys.platform in ['darwin']: 82 | os.system(f'open {out_fig}') 83 | elif sys.platform.startswith('win'): 84 | os.system(out_fig) 85 | else: 86 | print(f'Unknown OS system ({sys.platform}). Check results in file: {out_fig}.') 87 | -------------------------------------------------------------------------------- /tests/point.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Author: Zhang Yunjun, Jan 2021 3 | # Copyright 2020, by the California Institute of Technology. 4 | 5 | 6 | import os 7 | import sys 8 | import datetime as dt 9 | 10 | import numpy as np 11 | 12 | import pysolid 13 | 14 | 15 | if __name__ == '__main__': 16 | 17 | # print the file/module path 18 | print('-'*50) 19 | print(os.path.abspath(__file__)) 20 | 21 | # prepare inputs 22 | lat, lon = 34.0, -118.0 # Los Angles, CA 23 | dt_obj0 = dt.datetime(2020, 11, 5, 12, 0, 0) 24 | dt_obj1 = dt.datetime(2020, 12, 31, 0, 0, 0) 25 | 26 | # reference 27 | # calculated based on version 0.3.2.post6 on Jun 24, 2024 28 | # env: macOS with python-3.10, numpy-1.24 29 | # install: manual compilation via f2py 30 | dt_out_8000 = np.array( 31 | [dt.datetime(2020, 11, 5, 12, 0), 32 | dt.datetime(2020, 11, 11, 1, 20), 33 | dt.datetime(2020, 11, 16, 14, 40), 34 | dt.datetime(2020, 11, 22, 4, 0), 35 | dt.datetime(2020, 11, 27, 17, 20), 36 | dt.datetime(2020, 12, 3, 6, 40), 37 | dt.datetime(2020, 12, 8, 20, 0), 38 | dt.datetime(2020, 12, 14, 9, 20), 39 | dt.datetime(2020, 12, 19, 22, 40), 40 | dt.datetime(2020, 12, 25, 12, 0)], dtype=object, 41 | ) 42 | tide_e_8000 = np.array( 43 | [-0.02975027, 0.04146837, -0.02690945, -0.00019223, 0.01624152, 44 | 0.0532655 , -0.02140918, -0.05554432, 0.01371739, -0.00516968], 45 | ) 46 | tide_n_8000 = np.array( 47 | [-0.01275229, -0.02834036, 0.00886857, -0.03247227, -0.05237735, 48 | -0.00590791, -0.01990448, -0.01964124, -0.04439581, -0.00410378], 49 | ) 50 | tide_u_8000 = np.array( 51 | [ 0.16008235, -0.05721991, -0.15654693, -0.00041214, 0.03041098, 52 | 0.13082217, -0.1006462 , 0.24870719, -0.02648802, -0.08420228], 53 | ) 54 | 55 | # calculate 56 | (dt_out, 57 | tide_e, 58 | tide_n, 59 | tide_u) = pysolid.calc_solid_earth_tides_point(lat, lon, dt_obj0, dt_obj1, verbose=False) 60 | 61 | # compare 62 | assert all(dt_out[::8000] == dt_out_8000) 63 | assert np.allclose(tide_e[::8000], tide_e_8000) 64 | assert np.allclose(tide_n[::8000], tide_n_8000) 65 | assert np.allclose(tide_u[::8000], tide_u_8000) 66 | 67 | # plot 68 | out_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'pic')) 69 | os.makedirs(out_dir, exist_ok=True) 70 | 71 | out_fig = os.path.join(out_dir, 'point.png') 72 | pysolid.plot_solid_earth_tides_point( 73 | dt_out, tide_e, tide_n, tide_u, 74 | lalo=[lat, lon], 75 | out_fig=out_fig, 76 | display=False) 77 | 78 | # open the saved figure 79 | if sys.platform in ['linux']: 80 | os.system(f'display {out_fig}') 81 | elif sys.platform in ['darwin']: 82 | os.system(f'open {out_fig}') 83 | elif sys.platform.startswith('win'): 84 | os.system(out_fig) 85 | else: 86 | print(f'Unknown OS system ({sys.platform}). Check results in file: {out_fig}.') 87 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | # for packaging and installation 2 | #fortran-compiler # Fortran compiler across platforms through conda-forge channel 3 | meson 4 | pip 5 | setuptools_scm>=6.2 6 | # for testing 7 | matplotlib 8 | --------------------------------------------------------------------------------