├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── eoxmagmod ├── MANIFEST.in ├── README.txt ├── eoxmagmod │ ├── Makefile │ ├── __init__.py │ ├── common.h │ ├── data │ │ ├── CHAOS-7.18_core.shc │ │ ├── CHAOS-7.18_core_extrapolated.shc │ │ ├── CHAOS-7_static.shc │ │ ├── CHAOS-8.1_core.shc │ │ ├── CHAOS-8.1_core_extrapolated.shc │ │ ├── CHAOS-8.1_ionosphere.txt │ │ ├── CHAOS-8.1_static.shc │ │ ├── EMM-720_V3p0_secvar.cof │ │ ├── EMM-720_V3p0_static.cof │ │ ├── IGRF12.shc │ │ ├── IGRF13.shc │ │ ├── IGRF14.shc │ │ ├── LCS-1.shc │ │ ├── MF7.shc │ │ ├── README.txt │ │ ├── SIFM.shc │ │ ├── WMM2015v2.COF │ │ ├── __init__.py │ │ ├── apexsh_1980-2020.txt │ │ ├── apexsh_1980-2025.txt │ │ ├── apexsh_1980-2030.txt │ │ ├── apexsh_1995-2015.txt │ │ └── igrf11coeffs.txt │ ├── dipole.py │ ├── dipole_coords.py │ ├── include │ │ ├── bisect.h │ │ ├── fourier_series.h │ │ ├── geo_conversion.h │ │ ├── interpolation.h │ │ ├── math_aux.h │ │ ├── spherical_harmonics.h │ │ ├── sun_ephemeris.h │ │ └── time_conversion.h │ ├── magnetic_model │ │ ├── __init__.py │ │ ├── coefficients.py │ │ ├── coefficients_mio.py │ │ ├── field_lines.py │ │ ├── loader_emm.py │ │ ├── loader_igrf.py │ │ ├── loader_mio.py │ │ ├── loader_mma.py │ │ ├── loader_shc.py │ │ ├── loader_wmm.py │ │ ├── model.py │ │ ├── model_composed.py │ │ ├── model_mio.py │ │ ├── parser_emm.py │ │ ├── parser_igrf.py │ │ ├── parser_mio.py │ │ ├── parser_mma.py │ │ ├── parser_shc.py │ │ ├── parser_wmm.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── coefficient_loaders.py │ │ │ ├── coefficients.py │ │ │ ├── coefficients_mio.py │ │ │ ├── data │ │ │ │ ├── TEST_CHAOS_MMA.cdf │ │ │ │ ├── TEST_MIO_SHA.txt │ │ │ │ ├── TEST_MMA_SHA_2C.cdf │ │ │ │ ├── TEST_MMA_SHA_2F.cdf │ │ │ │ └── __init__.py │ │ │ ├── field_lines.py │ │ │ ├── mma_merge.py │ │ │ ├── model_loaders.py │ │ │ ├── parser_emm.py │ │ │ ├── parser_igrf.py │ │ │ ├── parser_mio.py │ │ │ ├── parser_mma.py │ │ │ ├── parser_shc.py │ │ │ ├── parser_wmm.py │ │ │ └── util.py │ │ └── util.py │ ├── magnetic_time.py │ ├── py_common.h │ ├── pymm.c │ ├── pymm_aux.h │ ├── pymm_bisect.h │ ├── pymm_cconv.h │ ├── pymm_coord.h │ ├── pymm_fourier2d.h │ ├── pymm_interp.h │ ├── pymm_legendre.h │ ├── pymm_loncossin.h │ ├── pymm_relradpow.h │ ├── pymm_sheval.h │ ├── pymm_sheval2dfs.h │ ├── pymm_shevaltemp.h │ ├── pymm_sphar_common.h │ ├── pymm_sphargrd.h │ ├── pymm_spharpot.h │ ├── pymm_vrot_cart2sph.h │ ├── pymm_vrot_common.h │ ├── pymm_vrot_sph2cart.h │ ├── pymm_vrot_sph2geod.h │ ├── pyqd.c │ ├── pyqd_eval_mlt.h │ ├── pyqd_eval_qdlatlon.h │ ├── pyqd_eval_subsol.h │ ├── pysunpos.c │ ├── pysunpos.h │ ├── pysunpos_original.h │ ├── pytimeconv.c │ ├── pytimeconv_decimal_year_to_mjd2000.h │ ├── pytimeconv_mjd2000_to_decimal_year.h │ ├── pytimeconv_mjd2000_to_year_fraction.h │ ├── quasi_dipole_coordinates.py │ ├── solar_position.py │ ├── tests │ │ ├── __init__.py │ │ ├── data │ │ │ ├── QuasiDipoleTestData.tsv │ │ │ ├── SunPositionTestData.tsv │ │ │ ├── __init__.py │ │ │ ├── chaos_core.py │ │ │ ├── chaos_mma.py │ │ │ ├── generate_quasi_dipole_test_data.py │ │ │ ├── generate_solar_position_test_data.py │ │ │ ├── mio.py │ │ │ ├── mma_external.py │ │ │ ├── mma_internal.py │ │ │ ├── sifm.py │ │ │ └── update_quasi_dipole_test_data.py │ │ ├── dipole.py │ │ ├── dipole_coords.py │ │ ├── magnetic_time.py │ │ ├── pymm_bisect.py │ │ ├── pymm_convert.py │ │ ├── pymm_fourier2d.py │ │ ├── pymm_interp.py │ │ ├── pymm_legendre.py │ │ ├── pymm_loncossin.py │ │ ├── pymm_relradpow.py │ │ ├── pymm_sheval.py │ │ ├── pymm_sheval2dfs.py │ │ ├── pymm_shevaltemp.py │ │ ├── pymm_sphargrd.py │ │ ├── pymm_spharpot.py │ │ ├── pymm_vrot.py │ │ ├── pytimeconv.py │ │ ├── quasi_dipole_coordinates.py │ │ ├── solar_position.py │ │ ├── time_util.py │ │ ├── util.py │ │ └── util_vrotate.py │ ├── time_util.py │ ├── util.py │ └── version.h ├── setup.cfg └── setup.py ├── libcdf ├── Makefile ├── bld.bat ├── build.sh ├── meta.yaml ├── post-link.sh └── pre-unlink.sh └── qdipole ├── .gitignore ├── Makefile.in ├── README ├── apex.f90 ├── apexsh.f90 ├── apexsh_1980-2020.txt ├── apexsh_1980-2025.txt ├── apexsh_1980-2030.txt ├── apexsh_1995-2015.txt ├── bld.bat ├── build.sh ├── configure ├── configure.ac ├── cqdipole.c ├── cqdipole.h ├── eval_mlt.f90 ├── eval_qdlatlon.f90 ├── eval_subsol.f90 ├── makeapexsh.f90 ├── meta.yaml ├── mlt_test.c ├── qdipole_conf.h.in ├── qdipole_info.c ├── qdlatlon_test.c ├── run_dipole_test ├── subsol_test.c ├── test_result.mlt.txt ├── test_result.qdlatlon.apexsh_1980-2020.txt ├── test_result.qdlatlon.apexsh_1980-2025.txt ├── test_result.qdlatlon.apexsh_1980-2030.txt ├── test_result.qdlatlon.apexsh_1995-2015.txt └── test_result.subsol.txt /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | jobs: 4 | testing: 5 | runs-on: ubuntu-20.04 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | include: 10 | - python: '3.6' 11 | - python: '3.7' 12 | - python: '3.8' 13 | - python: '3.9' 14 | - python: '3.10' 15 | - python: '3.11' 16 | #- python: '3.12' # broken - no distutils - test again with SpacePy >= 0.5.0 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python }} 22 | - name: Get packages 23 | run: | 24 | python3 --version 25 | sudo apt-get install gfortran 26 | sudo apt-get install libhdf5-dev 27 | - name: Install 28 | run: | 29 | ( cd libcdf && make build && sudo make install ) 30 | ( cd qdipole && ./configure && make build && sudo make install && make test ) 31 | pip3 install --upgrade pip 32 | pip3 install wheel 33 | pip3 install scipy 34 | pip3 install spacepy --no-build-isolation 35 | pip3 install ./eoxmagmod/ 36 | - name: Scripts 37 | run: | 38 | pip3 list 39 | mkdir -p ./test && cd ./test 40 | pip3 show -f eoxmagmod 41 | python3 -c 'import eoxmagmod' && python3 -m unittest discover -p '[a-z]*.py' -v eoxmagmod 42 | notification: 43 | runs-on: ubuntu-20.04 44 | if: ${{ always() }} 45 | needs: testing 46 | steps: 47 | # send Slack notifications to the eox organization 48 | - name: action-slack 49 | uses: 8398a7/action-slack@v3 50 | with: 51 | status: ${{ needs.testing.result }} 52 | fields: repo,message,commit,author,action,eventName,ref,workflow,job,took 53 | env: 54 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 55 | if: always() 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.pyc 4 | *.exe 5 | MANIFEST 6 | build 7 | dist 8 | *.egg-info 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Magnetic Model 4 | 5 | This repository contains various utilities related to Earth magnetic field 6 | modelling and spherical harmonics. 7 | 8 | The repository contains following directories: 9 | 10 | - `eoxmagmod` - Collection models of the Earth magnetic field - python module 11 | - `qdipole` - Quasi-Dipole apex coordinates evaluation - Fortran code compiled 12 | as a shared library (dependency of the `eoxmagmod` package) 13 | - `libcdf` - [CDF library](https://cdf.gsfc.nasa.gov/) source installation 14 | (dependency of the `eoxmagmod` package) 15 | 16 | ### Installation from Sources 17 | 18 | #### CDF 19 | 20 | ``` 21 | $ cd libcdf/ 22 | $ make build 23 | $ sudo make install 24 | ``` 25 | 26 | By default, the library gets installed in `/usr/local/cdf` directory. 27 | To install it to a different path override the `INSTALLDIR` 28 | variable: 29 | ``` 30 | $ make install INSTALLDIR= 31 | ``` 32 | 33 | #### QDIPOLE 34 | 35 | ``` 36 | $ cd qdipole/ 37 | $ ./configure 38 | $ make build 39 | $ sudo make install 40 | ``` 41 | 42 | #### EOxMagMod 43 | Requires QDIPOLE, CDF libraries + NumPy and SpacePy Python packages 44 | to be installed. 45 | NumPy and SpacePy can be installed using `pip`. 46 | 47 | ``` 48 | $ cd eoxmagmod/ 49 | $ python ./setup.py build 50 | $ sudo python ./setup.py install 51 | ``` 52 | 53 | ### Conda installation 54 | 55 | The package contains the `conda-build` scripts allowing local conda build and 56 | installation following this procedure: 57 | 58 | 1) build the binary dependencies: 59 | ``` 60 | conda install conda-build 61 | conda build ./qdipole 62 | conda build ./libcdf 63 | conda build purge 64 | ``` 65 | Tested on GNU/Linux. Possibly works on other POSIX systems. Does not work on MS 66 | Windows (primarily because of a missing Fortran compiler). 67 | 68 | 2) install the `eoxmagmod` in your conda environment: 69 | ``` 70 | conda activate 71 | conda install --use-local qdipole cdf 72 | conda install numpy scipy matplotlib h5py networkx 73 | conda install gcc_linux-64 # spacepy and eoxmagmod require C compiler 74 | conda install gfortran_linux-64 # spacepy requires Fortran compiler 75 | conda deactivate 76 | conda activate # re-activation is required to update the environment variables 77 | pip install spacepy 78 | pip install ./eoxmagmod 79 | ``` 80 | 81 | The `gfortran_linux-64` and`gcc_linux-64` compilers work on a x86_64 GNU/Linux system. 82 | Other platforms might provide different compilers. 83 | -------------------------------------------------------------------------------- /eoxmagmod/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | recursive-exclude eoxmagmod *.pyc *.o 3 | recursive-include eoxmagmod *.c *.h 4 | recursive-include eoxmagmod/data *.txt *.shc *.cof *.COF 5 | recursive-include eoxmagmod/tests/data *.txt *.tsv 6 | recursive-include eoxmagmod/magnetic_model/tests/data *.txt *.cdf 7 | -------------------------------------------------------------------------------- /eoxmagmod/README.txt: -------------------------------------------------------------------------------- 1 | EOxMagMod 2 | --------- 3 | 4 | Python collection of Earth magnetic field models. It consists of a mixture of 5 | python code and C-library bindings. 6 | 7 | To install run package use the python setup script. For more information run: 8 | python setup.py --help 9 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/Makefile: -------------------------------------------------------------------------------- 1 | CC?=gcc 2 | RM=rm -fv 3 | 4 | INC_MM= 5 | LIB_MM=-lm 6 | INC_QD=-I./include 7 | LIB_QD=-lqdipole -lm 8 | INC_SP=-I./include 9 | LIB_SP=-lm 10 | PYTHON_CONFIG ?=python3-config 11 | INC_NUMPY=-I$(shell python -c 'import numpy; print(numpy.get_include())') 12 | INC_PYTHON=$(shell $(PYTHON_CONFIG) --includes ) 13 | LIB_PYTHON=$(shell $(PYTHON_CONFIG) --ldflags ) 14 | 15 | INCS=$(INC_NUMPY) $(INC_PYTHON) $(INC_MM) $(INC_QD) $(INCLUDE) 16 | LIBS_MM=$(LIB_PYTHON) $(LIB_MM) 17 | LIBS_QD=$(LIB_PYTHON) $(LIB_QD) 18 | LIBS_QD=$(LIB_PYTHON) $(LIB_QD) 19 | 20 | #CCFLAGS= -c -g -DDEBUG=1 -Wall -fPIC $(INCS) 21 | CCFLAGS= -c -O2 -Wall -Wno-unused-function -fPIC $(INCS) 22 | #CCFLAGS= -c -O2 -g -DDEBUG=1 -Wall -fPIC $(INCS) 23 | #CCFLAGS= -c -O2 -Wall -fPIC $(INC) 24 | 25 | HDR_MM=pymm_aux.h pymm_shevaltemp.h pymm_sheval2dfs.h pymm_sheval.h \ 26 | pymm_sphar_common.h pymm_sphargrd.h pymm_spharpot.h \ 27 | pymm_legendre.h pymm_loncossin.h pymm_relradpow.h\ 28 | pymm_vrot_common.h pymm_vrot_sph2cart.h pymm_vrot_sph2geod.h\ 29 | pymm_vrot_cart2sph.h pymm_cconv.h pymm_coord.h pymm_bisect.h\ 30 | pymm_interp.h pymm_fourier2d.h 31 | OBJ_MM=pymm.o 32 | DST_MM=_pymm.so 33 | 34 | HDR_QD=pymm_aux.h pyqd_eval_qdlatlon.h pyqd_eval_mlt.h pyqd_eval_subsol.h 35 | OBJ_QD=pyqd.o 36 | DST_QD=_pyqd.so 37 | 38 | HDR_SP=pymm_aux.h pysunpos.h pysunpos_original.h 39 | OBJ_SP=pysunpos.o 40 | DST_SP=_pysunpos.so 41 | 42 | HDR_TC=pymm_aux.h pytimeconv_mjd2000_to_decimal_year.h pytimeconv_decimal_year_to_mjd2000.h 43 | OBJ_TC=pytimeconv.o 44 | DST_TC=_pytimeconv.so 45 | 46 | # -------------------------------------------- 47 | 48 | all: pymm pyqd pysunpos pytimeconv 49 | 50 | %.o: %.c 51 | $(CC) $< $(CCFLAGS) -o $@ 52 | 53 | %.o: %.cpp 54 | $(CC) $< $(CCFLAGS) -o $@ 55 | 56 | pymm: $(OBJ_MM) 57 | $(CC) -shared -o $(DST_MM) $(OBJ_MM) $(LIBS_MM) 58 | 59 | pyqd: $(OBJ_QD) 60 | $(CC) -shared -o $(DST_QD) $(OBJ_QD) $(LIBS_QD) 61 | 62 | pysunpos: $(OBJ_SP) 63 | $(CC) -shared -o $(DST_SP) $(OBJ_SP) $(LIBS_SP) 64 | 65 | pytimeconv: $(OBJ_TC) 66 | $(CC) -shared -o $(DST_TC) $(OBJ_TC) $(LIBS_TC) 67 | 68 | clean: 69 | $(RM) *.o 70 | 71 | purge: clean 72 | $(RM) $(DST_MM) 73 | 74 | pymm.o: pymm.c $(HDR_MM) 75 | pyqd.o: pyqd.c $(HDR_QD) 76 | pysunpos.o: pysunpos.c $(HDR_SP) 77 | pytimeconv.o: pytimeconv.c $(HDR_TC) 78 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/common.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * common shared definitions 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2018-2022 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #ifndef COMMON_H 31 | #define COMMON_H 32 | 33 | // needed to prevent dual definition 34 | #ifdef _POSIX_C_SOURCE 35 | #undef _POSIX_C_SOURCE 36 | #endif 37 | 38 | // required by Python 39 | #define PY_SSIZE_T_CLEAN 1 40 | 41 | // disable Numpy deprecated API 42 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 43 | 44 | /* maximum allowed output array dimension */ 45 | #define MAX_OUT_ARRAY_NDIM 16 46 | 47 | #endif /* COMMON_H */ 48 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/data/README.txt: -------------------------------------------------------------------------------- 1 | This foldrer contains various data-files included in the package. 2 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/data/__init__.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # data files 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2014 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from os.path import dirname, join 30 | 31 | _DIRNAME = dirname(__file__) 32 | 33 | # magnetic models 34 | WMM_2015 = join(_DIRNAME, 'WMM2015v2.COF') 35 | EMM_2010_STATIC = join(_DIRNAME, 'EMM-720_V3p0_static.cof') 36 | EMM_2010_SECVAR = join(_DIRNAME, 'EMM-720_V3p0_secvar.cof') 37 | CHAOS7_CORE_X18 = join(_DIRNAME, 'CHAOS-7.18_core.shc') 38 | CHAOS7_CORE_X18_PREDICTION = join(_DIRNAME, 'CHAOS-7.18_core_extrapolated.shc') 39 | CHAOS7_CORE_LATEST = CHAOS7_CORE_X18 40 | CHAOS7_CORE_PREDICTION_LATEST = CHAOS7_CORE_X18_PREDICTION 41 | CHAOS7_STATIC = join(_DIRNAME, 'CHAOS-7_static.shc') 42 | CHAOS8_CORE_V1 = join(_DIRNAME, 'CHAOS-8.1_core.shc') 43 | CHAOS8_CORE_V1_PREDICTION = join(_DIRNAME, 'CHAOS-8.1_core_extrapolated.shc') 44 | CHAOS8_STATIC = join(_DIRNAME, 'CHAOS-8.1_static.shc') 45 | CHAOS8_IONOSPHERE = join(_DIRNAME, 'CHAOS-8.1_ionosphere.txt') 46 | CHAOS8_CORE_LATEST = CHAOS8_CORE_V1 47 | CHAOS8_CORE_PREDICTION_LATEST = CHAOS8_CORE_V1_PREDICTION 48 | CHAOS_CORE_LATEST = CHAOS8_CORE_LATEST 49 | CHAOS_CORE_PREDICTION_LATEST = CHAOS8_CORE_PREDICTION_LATEST 50 | CHAOS_STATIC_LATEST = CHAOS8_STATIC 51 | CHAOS_IONOSPHERE_LATEST = CHAOS8_IONOSPHERE 52 | IGRF11 = join(_DIRNAME, 'igrf11coeffs.txt') 53 | IGRF12 = join(_DIRNAME, 'IGRF12.shc') 54 | IGRF13 = join(_DIRNAME, 'IGRF13.shc') 55 | IGRF14 = join(_DIRNAME, 'IGRF14.shc') 56 | IGRF_LATEST = IGRF14 57 | IGRF_LATEST_VERSION = "14" 58 | IGRF_LATEST_SOURCE = "SW_OPER_AUX_IGR_2__19000101T000000_20291231T235959_0104" 59 | SIFM = join(_DIRNAME, 'SIFM.shc') 60 | LCS1 = join(_DIRNAME, 'LCS-1.shc') 61 | MF7 = join(_DIRNAME, 'MF7.shc') 62 | 63 | # magnetic models used by the apex point calculation 64 | APEX_2015 = join(_DIRNAME, 'apexsh_1995-2015.txt') 65 | APEX_2020 = join(_DIRNAME, 'apexsh_1980-2020.txt') 66 | APEX_2025 = join(_DIRNAME, 'apexsh_1980-2025.txt') 67 | APEX_2030 = join(_DIRNAME, 'apexsh_1980-2030.txt') 68 | APEX_LATEST = APEX_2030 69 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/dipole_coords.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Magnetic Dipole Coordinates 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from math import pi, sin, cos 30 | from numpy import array, dot 31 | from eoxmagmod._pymm import ( 32 | convert, vrot_sph2cart, vrot_cart2sph, 33 | GEOCENTRIC_SPHERICAL, GEOCENTRIC_CARTESIAN, 34 | ) 35 | 36 | __all__ = [ 37 | "get_dipole_rotation_matrix", 38 | "convert_to_dipole", 39 | "vrot_from_dipole", 40 | ] 41 | 42 | DEG2RAD = pi / 180.0 43 | 44 | 45 | def get_dipole_rotation_matrix(latitude, longitude): 46 | """ Get rotation matrix for given north pole coordinates. 47 | """ 48 | sin_lat, cos_lat = sin(DEG2RAD*latitude), cos(DEG2RAD*latitude) 49 | sin_lon, cos_lon = sin(DEG2RAD*longitude), cos(DEG2RAD*longitude) 50 | return array([ 51 | [sin_lat*cos_lon, -sin_lon, cos_lat*cos_lon], 52 | [sin_lat*sin_lon, cos_lon, cos_lat*sin_lon], 53 | [-cos_lat, 0, sin_lat], 54 | ]) 55 | 56 | 57 | def convert_to_dipole(coords, lat_ngp, lon_ngp, 58 | coord_type_in=GEOCENTRIC_SPHERICAL): 59 | """ Convert coordinates (by default geocentric spherical) 60 | to dipole coordinates defined by the given latitude and longitude 61 | of the geomagnetic pole. 62 | The dipole coordinates are a simple rotated coordinate frame in which 63 | the North pole (dipole latitude == 0) is aligned with the geomagnetic pole 64 | and the prime meridian (dipole longitude == 0) is the meridian 65 | passing trough the geomagnetic pole. 66 | """ 67 | rotation_matrix = get_dipole_rotation_matrix(lat_ngp, lon_ngp) 68 | coords = convert(coords, coord_type_in, GEOCENTRIC_CARTESIAN) 69 | coords = dot(coords, rotation_matrix) 70 | coords = convert(coords, GEOCENTRIC_CARTESIAN, GEOCENTRIC_SPHERICAL) 71 | return coords 72 | 73 | 74 | def vrot_from_dipole(vectors, lat_ngp, lon_ngp, lat_dipole, lon_dipole, 75 | lat_out=None, lon_out=None, 76 | coord_type_out=GEOCENTRIC_CARTESIAN): 77 | """ Rotate vectors from dipole (NEC) coordinate frame to the 78 | Cartesian (XYZ) (default), geocentric spherical or geodetic (NEC) 79 | coordinate frame. 80 | """ 81 | rotation_matrix = get_dipole_rotation_matrix(lat_ngp, lon_ngp).transpose() 82 | vectors = vrot_sph2cart(vectors, lat_dipole, lon_dipole) 83 | vectors = dot(vectors, rotation_matrix) 84 | if coord_type_out == GEOCENTRIC_CARTESIAN: 85 | # coordinates are already in the Cartesian coordinates 86 | return vectors 87 | return vrot_cart2sph(vectors, lat_out, lon_out) 88 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/include/bisect.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file math_aux.h 3 | * @author Martin Paces 4 | * @brief Bisection interval search. 5 | * 6 | * This file contains definitions of auxiliary mathematical subroutines 7 | * to be used all over the whole program. 8 | * 9 | * Copyright (C) 2022 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #ifndef BISECT_H 31 | #define BISECT_H 1 32 | 33 | #include 34 | #include 35 | 36 | /** 37 | * Common function type of the bisect functions. 38 | */ 39 | typedef ptrdiff_t (*f_bisect)(const double x, const double* v, const size_t n); 40 | 41 | 42 | /** 43 | * @brief Find interval the given value fits in using the bisection method. 44 | * 45 | * Perform bisect search of an interval from the given sorted array of 46 | * nodes v of size n, defining n-1 intervals. 47 | * 48 | * Function returns the index i of the interval the value x fits in so that 49 | * v[i] <= x < v[i+1]. 50 | * 51 | * The index is set to -1 if x < v[-1] or n is 0. 52 | * The index is set to n-1 if x >= v[n-1] or x is Nan. 53 | */ 54 | 55 | static ptrdiff_t bisect_right(const double x, const double *v, const size_t n) 56 | { 57 | ptrdiff_t i_low, i_high; 58 | 59 | if ((n < 1)||!(x >= v[0])) { 60 | // no intervals (n == 0), x is below the lower bound or NaN 61 | return -1; 62 | } 63 | if (x >= v[n-1]) { 64 | // x is above the upper bound 65 | return n - 1; 66 | } 67 | 68 | // proceed with the halving of the intervals 69 | 70 | i_low = 0; 71 | i_high = n - 2; 72 | 73 | while (i_low < i_high) { 74 | ptrdiff_t i = i_low + 1 + (i_high - i_low) / 2; 75 | if (x >= v[i]) { 76 | i_low = i; 77 | } else { 78 | i_high = i - 1; 79 | } 80 | } 81 | 82 | return i_low; 83 | } 84 | 85 | 86 | /** 87 | * @brief Find interval the given value fits in using the bisection method. 88 | * 89 | * Perform bisect search of an interval from the given sorted array of 90 | * nodes v of size n, defining n-1 intervals. 91 | * 92 | * Function returns the index i of the interval the value x fits in so that 93 | * v[i] < x <= v[i+1]. 94 | * 95 | * The index is set to -1 if x <= 0 v[-1] or n is 0. 96 | * The index is set to n-1 if x > v[n-1] or x is NaN. 97 | */ 98 | 99 | static ptrdiff_t bisect_left(const double x, const double *v, const size_t n) 100 | { 101 | ptrdiff_t i_low, i_high; 102 | 103 | if ((n < 1)||!(x > v[0])) { 104 | // no intervals (n == 0), x is below the lower bound or NaN 105 | return -1; 106 | } 107 | if (x > v[n-1]) { 108 | // x is above the upper bound 109 | return n - 1; 110 | } 111 | 112 | // proceed with the halving of the intervals 113 | 114 | i_low = 0; 115 | i_high = n - 2; 116 | 117 | while (i_low < i_high) { 118 | ptrdiff_t i = i_low + 1 + (i_high - i_low) / 2; 119 | if (x <= v[i]) { 120 | i_high = i - 1; 121 | } else { 122 | i_low = i; 123 | } 124 | } 125 | 126 | return i_high; 127 | } 128 | 129 | #endif /*BISECT_H*/ 130 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/include/interpolation.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file math_aux.h 3 | * @author Martin Paces 4 | * @brief Data interpolation 5 | * 6 | * This file contains definitions of auxiliary mathematical subroutines 7 | * to be used all over the whole program. 8 | * 9 | * Copyright (C) 2022 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #ifndef INTERPOLATION_H 31 | #define INTERPOLATION_H 1 32 | 33 | #include 34 | #include 35 | 36 | #define MAX_SPLINE_ORDER 6 37 | 38 | #define CLIP(v, v_min, v_max) ((v)<(v_min)?(v_min):((v)>(v_max)?(v_max):(v))) 39 | 40 | /** 41 | * Common interpolation basis structures (interval index and basis functions). 42 | */ 43 | 44 | typedef struct { 45 | ptrdiff_t i0; 46 | ptrdiff_t i; 47 | size_t order; 48 | double b[MAX_SPLINE_ORDER]; 49 | } INTERP_BASIS; 50 | 51 | 52 | /** 53 | * @brief common interpolation basis function type 54 | * 55 | */ 56 | 57 | typedef INTERP_BASIS (*f_get_interp_basis) (const double x, const double *v, const size_t n); 58 | 59 | /** 60 | * @brief common interpolation evaluation function type 61 | * 62 | */ 63 | 64 | typedef double (*f_interp_eval)(INTERP_BASIS *basis, const double *v); 65 | 66 | /** 67 | * @brief Get constant interpolation basis 68 | * 69 | */ 70 | 71 | static INTERP_BASIS get_interp0_basis (const double x, const double *v, const size_t n) 72 | { 73 | ptrdiff_t idx0 = bisect_right(x, v, n); 74 | ptrdiff_t idx = CLIP(idx0, 0, n-1); 75 | 76 | INTERP_BASIS basis = {idx0, idx, 1, {1.0}}; 77 | 78 | return basis; 79 | } 80 | 81 | /** 82 | * @brief Get linear interpolation basis (interval index and basis functions) 83 | * 84 | * First, perform bisect search of an interval from the given sorted array of 85 | * nodes v of size n, defining n-1 intervals. 86 | * 87 | * Once found, calculate the linear interpolation basis functions. 88 | * For x outside the lower and upper bounds the basis functions are 89 | * extrapolated. 90 | */ 91 | 92 | static INTERP_BASIS get_interp1_basis (const double x, const double *v, const size_t n) 93 | { 94 | ptrdiff_t idx0 = bisect_right(x, v, n); 95 | ptrdiff_t idx = CLIP(idx0, 0, n-2); 96 | double alpha = (x - v[idx]) / (v[idx+1] - v[idx]); 97 | 98 | INTERP_BASIS basis = {idx0, idx, 2, {1.0 - alpha, alpha}}; 99 | 100 | return basis; 101 | } 102 | 103 | /** 104 | * @brief Get first derivative of linear interpolation basis (interval index 105 | * and basis functions) 106 | * 107 | * First, perform bisect search of an interval from the given sorted array of 108 | * nodes v of size n, defining n-1 intervals. 109 | * 110 | * Once found, calculate the first derivative linear interpolation basis 111 | * functions. For x outside the lower and upper bounds the basis functions are 112 | * extrapolated. 113 | */ 114 | 115 | static INTERP_BASIS get_interp1d1_basis (const double x, const double *v, const size_t n) 116 | { 117 | ptrdiff_t idx0 = bisect_right(x, v, n); 118 | ptrdiff_t idx = CLIP(idx0, 0, n-2); 119 | double alpha = 1.0 / (v[idx+1] - v[idx]); 120 | 121 | INTERP_BASIS basis = {idx0, idx, 2, {-alpha, alpha}}; 122 | 123 | return basis; 124 | } 125 | 126 | /** 127 | * @brief Evaluate constant interpolation. 128 | */ 129 | 130 | static double interp0_eval(INTERP_BASIS *basis, const double *v) { 131 | return v[basis->i]; 132 | } 133 | 134 | /** 135 | * @brief Evaluate linear interpolation. 136 | */ 137 | 138 | static double interp1_eval(INTERP_BASIS *basis, const double *v) { 139 | return (basis->b[0]*v[basis->i] + basis->b[1]*v[basis->i+1]); 140 | } 141 | 142 | #undef CLIP 143 | 144 | #endif /*INTERPOLATION_H*/ 145 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/include/math_aux.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file math_aux.h 3 | * @author Martin Paces 4 | * @brief Auxiliary mathematical subroutines. 5 | * 6 | * This file contains definitions of auxiliary mathematical subroutines 7 | * to be used all over the whole program. 8 | * 9 | * Copyright (C) 2014 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #ifndef MATH_AUX_H 31 | #define MATH_AUX_H 1 32 | 33 | #include 34 | 35 | /** 36 | * @brief Evaluate quadratic norm of a 3D vector. 37 | * v = (x, y, z) 38 | * l = |v| = sqrt(x^2 + y^2 + z^2) 39 | */ 40 | static double norm3d(double x, double y, double z) 41 | { 42 | return sqrt(x*x + y*y + z*z); 43 | } 44 | 45 | /** 46 | * @brief Evaluate quadratic norm of a 2D vector. 47 | * v = (x, y) 48 | * l = |v| = sqrt(x^2 + y^2) 49 | */ 50 | static double norm2d(double x, double y) 51 | { 52 | return sqrt(x*x + y*y); 53 | } 54 | 55 | /** 56 | * @brief Evaluate derivative of a quadratic norm of a 3D vector. 57 | * l = |v| 58 | * dl = d(|v|) 59 | * v = (x, y, z) 60 | * dv = (dx, dy, dz) 61 | * if |v| >= 0: 62 | * dl = (v/|v|)*dv = (x*dx + y*dy + z*dz)/l 63 | * if |v| == 0: 64 | * dl = |dv| = sqrt(dx^2 + dy^2 + dz^2) 65 | */ 66 | static double dnorm3d(double x, double y, double z, double l, 67 | double dx, double dy, double dz) 68 | { 69 | if (l > 0.0) 70 | return (x*dx + y*dy + z*dz)/l; 71 | else if (l == 0.0) 72 | return norm3d(dx, dy, dz); 73 | else 74 | return NAN; 75 | } 76 | 77 | /** 78 | * @brief Evaluate derivative of a quadratic norm of a 2D vector. 79 | * l = |v| 80 | * dl = d(|v|) 81 | * v = (x, y) 82 | * dv = (dx, dy) 83 | * if |v| >= 0: 84 | * dl = (v/|v|)*dv = (x*dx + y*dy)/l 85 | * if |v| == 0: 86 | * dl = |dv| = sqrt(dx^2 + dy^2) 87 | */ 88 | static double dnorm2d(double x, double y, double l, double dx, double dy) 89 | { 90 | if (l > 0.0) 91 | return (x*dx + y*dy)/l; 92 | else if (l == 0.0) 93 | return norm2d(dx, dy); 94 | else 95 | return NAN; 96 | } 97 | 98 | /** 99 | * @brief 2D vector rotation. 100 | * 101 | * x' = x*cos(a) - y*sin(a) 102 | * y' = x*sin(a) + y*cos(a) 103 | */ 104 | static void rot2d(double *x_out, double *y_out, double x, double y, double a_sin, double a_cos) 105 | { 106 | *x_out = x*a_cos - y*a_sin; 107 | *y_out = x*a_sin + y*a_cos; 108 | } 109 | 110 | #endif /*MATH_AUX_H*/ 111 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ESA-VirES/MagneticModel/1d4e14f66a4d01aebd39b77d9506cf650c8218cf/eoxmagmod/eoxmagmod/magnetic_model/__init__.py -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/coefficients_mio.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Spherical Harmonic Coefficients specific to Swarm MIO model. 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from math import pi 30 | from collections import namedtuple 31 | from numpy import asarray, zeros 32 | from .coefficients import SparseSHCoefficients, coeff_size 33 | from ..time_util import ( 34 | mjd2000_to_year_fraction as mjd2000_to_year_fraction_default, 35 | ) 36 | from .._pymm import fourier2d 37 | 38 | __all__ = [ 39 | "SparseSHCoefficientsMIO", 40 | ] 41 | 42 | SCALE_SEASONAL = 2*pi 43 | SCALE_DIURNAL = 2*pi/24. 44 | 45 | DegreeRanges = namedtuple("DegreeRanges", ["pmin", "pmax", "smin", "smax"]) 46 | 47 | 48 | class SparseSHCoefficientsMIO(SparseSHCoefficients): 49 | """ Time dependent 2D Fourier series Swarm MIO coefficients. 50 | Parameters: 51 | indices - array if the nm indices 52 | coefficients - gh or qs coefficients 53 | ps_extent - (pmin, pmax, smin, smax) tuple 54 | is_internal - set False for an external model 55 | """ 56 | 57 | def get_f2fs_coeff_set(self, **parameters): 58 | """ Return coefficient set which can be passed to sheval2dfs. """ 59 | _, coeff, nm_, _ = self.subset_degree( 60 | parameters.get("min_degree", -1), 61 | parameters.get("max_degree", -1), 62 | ) 63 | return ( 64 | coeff, nm_, 65 | self.ps_extent.smin, self.ps_extent.pmin, 66 | SCALE_SEASONAL, SCALE_DIURNAL 67 | ) 68 | 69 | def __init__(self, indices, coefficients, ps_extent, 70 | mjd2000_to_year_fraction=mjd2000_to_year_fraction_default, 71 | **kwargs): 72 | SparseSHCoefficients.__init__(self, indices, coefficients, **kwargs) 73 | pmin, pmax, smin, smax = ps_extent 74 | if pmin > pmax or smin > smax: 75 | raise Exception(f"Invalid ps_extent {ps_extent}!") 76 | self.ps_extent = DegreeRanges(pmin, pmax, smin, smax) 77 | self.mjd2000_to_year_fraction = mjd2000_to_year_fraction 78 | 79 | def __call__(self, time, mut, **parameters): 80 | time = asarray(time) 81 | mut = asarray(mut) 82 | degree, coeff, _, index = self.subset_degree( 83 | parameters.get("min_degree", -1), parameters.get("max_degree", -1) 84 | ) 85 | coeff_full = zeros((*time.shape, coeff_size(degree), 2)) 86 | coeff_full[..., index[:, 0], index[:, 1]] = fourier2d( 87 | self.mjd2000_to_year_fraction(time), mut, 88 | coeff, self.ps_extent.smin, self.ps_extent.pmin, 89 | SCALE_SEASONAL, SCALE_DIURNAL 90 | ) 91 | return coeff_full, degree 92 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/loader_emm.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # EMM model loader 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from .model import SphericalHarmomicGeomagneticModel 30 | from .coefficients import ( 31 | SparseSHCoefficientsTimeDependentDecimalYear, 32 | SparseSHCoefficientsConstant, 33 | CombinedSHCoefficients, 34 | ) 35 | from .parser_emm import combine_emm_coefficients, parse_emm_file 36 | 37 | __all__ = ["load_model_emm", "load_coeff_emm"] 38 | 39 | 40 | def load_model_emm(path_static, path_secvar): 41 | """ Load model from a EMM coefficient files. """ 42 | return SphericalHarmomicGeomagneticModel( 43 | load_coeff_emm(path_static, path_secvar) 44 | ) 45 | 46 | 47 | def load_coeff_emm(path_static, path_secvar): 48 | """ Load coefficients from a EMM coefficient files. """ 49 | 50 | with open(path_static, encoding="ascii") as file_static: 51 | with open(path_secvar, encoding="ascii") as file_secvar: 52 | data_variable, data_constant = combine_emm_coefficients( 53 | parse_emm_file(file_static), 54 | parse_emm_file(file_secvar), 55 | ) 56 | 57 | return CombinedSHCoefficients( 58 | SparseSHCoefficientsTimeDependentDecimalYear( 59 | data_variable["nm"], data_variable["gh"], data_variable["t"], 60 | ), 61 | SparseSHCoefficientsConstant( 62 | data_constant["nm"], data_constant["gh"][:, 0] 63 | ) 64 | ) 65 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/loader_igrf.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # IGRF file format model loader 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from .model import SphericalHarmomicGeomagneticModel 30 | from .coefficients import SparseSHCoefficientsTimeDependentDecimalYear 31 | from .parser_igrf import parse_igrf_file 32 | 33 | __all__ = ["load_model_igrf", "load_coeff_igrf"] 34 | 35 | 36 | def load_model_igrf(path): 37 | """ Load model from an IGRF coefficient file. 38 | 39 | This loader can be used load to load the models in the original IGRF 40 | coefficient file format. 41 | 42 | Starting by IGRF12, the eoxmagmod package contains IGRF models 43 | converted to the common SHC format which are loaded by the SHC file loader. 44 | """ 45 | return SphericalHarmomicGeomagneticModel(load_coeff_igrf(path)) 46 | 47 | 48 | def load_coeff_igrf(path): 49 | """ Load coefficients from an IGRF file. """ 50 | with open(path, encoding="ascii") as file_in: 51 | data = parse_igrf_file(file_in) 52 | 53 | return SparseSHCoefficientsTimeDependentDecimalYear( 54 | data["nm"], data["gh"], data["t"] 55 | ) 56 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/loader_wmm.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # WMM model loader 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from .model import SphericalHarmomicGeomagneticModel 30 | from .coefficients import SparseSHCoefficientsTimeDependentDecimalYear 31 | from .parser_wmm import parse_wmm_file 32 | 33 | __all__ = ["load_model_wmm", "load_coeff_wmm"] 34 | 35 | 36 | def load_model_wmm(path): 37 | """ Load model from a WMM COF file. """ 38 | return SphericalHarmomicGeomagneticModel(load_coeff_wmm(path)) 39 | 40 | 41 | def load_coeff_wmm(path): 42 | """ Load coefficients from a WMM COF file. """ 43 | with open(path, encoding="ascii") as file_in: 44 | data = parse_wmm_file(file_in) 45 | 46 | return SparseSHCoefficientsTimeDependentDecimalYear( 47 | data["nm"], data["gh"], data["t"] 48 | ) 49 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/parser_igrf.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # IGRF format parser 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all 19 | # copies of this Software or works derived from this Software. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | # THE SOFTWARE. 28 | #------------------------------------------------------------------------------- 29 | 30 | from numpy import array 31 | 32 | __all__ = ["parse_igrf_file"] 33 | 34 | IGRF_EXTRAPOLATION_PERIOD = 5.0 # years 35 | 36 | 37 | def parse_igrf_file(file_in): 38 | """ Parse IGRF file format and return a dictionary containing the parsed 39 | model data. 40 | """ 41 | lines = strip_igrf_comments(file_in) 42 | data = {} 43 | data["labels"] = parse_igrf_header(next(lines)) 44 | data["t"] = parse_igrf_times(next(lines)) 45 | data["nm"], data["gh"] = parse_igrf_coefficients(lines) 46 | data["degree_min"] = data["nm"][:, 0].min() 47 | data["degree_max"] = data["nm"][:, 0].max() 48 | return data 49 | 50 | 51 | def parse_igrf_coefficients(lines): 52 | """ Parse IGRF coefficients. """ 53 | nm_idx = [] 54 | coeff = [] 55 | for line in lines: 56 | fields = line.split() 57 | label, n_idx, m_idx = fields[0], int(fields[1]), int(fields[2]) 58 | if label == "h": 59 | m_idx = -m_idx 60 | nm_idx.append((n_idx, m_idx)) 61 | coeff.append([float(v) for v in fields[3:]]) 62 | coeff = array(coeff) 63 | coeff[:, -1] = coeff[:, -2] + coeff[:, -1] * IGRF_EXTRAPOLATION_PERIOD 64 | return array(nm_idx), coeff 65 | 66 | 67 | def parse_igrf_times(line): 68 | """ Parse SHC times. """ 69 | times = [float(v) for v in line.split()[3:-1]] 70 | times.append(times[-1] + IGRF_EXTRAPOLATION_PERIOD) 71 | return array(times) 72 | 73 | 74 | def parse_igrf_header(line): 75 | """ Parse IGRF header with the column labels. """ 76 | return line.split() 77 | 78 | 79 | def strip_igrf_comments(file_in): 80 | """ Strip initial comments and empty lines from a text file stream. """ 81 | for line in file_in: 82 | line = line.partition("#")[0].strip() 83 | if line: 84 | yield line 85 | break 86 | for line in file_in: 87 | line = line.strip() 88 | yield line 89 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/parser_mio.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Swarm MIO_SHA_2* product file format parser 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from numpy import array 30 | from .parser_shc import _strip_shc_comments 31 | 32 | __all__ = ["parse_swarm_mio_file"] 33 | 34 | 35 | def parse_swarm_mio_file(file_in): 36 | """ Parse Swarm MIO_SHA_2* product file format and return a dictionary 37 | containing the parsed model data. 38 | """ 39 | lines = _strip_shc_comments(file_in) 40 | data = parse_swarm_mio_header(next(lines)) 41 | data["nm"], data["qs"], data["gh"] = parse_swarm_mio_coefficients(lines, data) 42 | data["degree_min"] = data["nm"][:, 0].min() 43 | data["degree_max"] = data["nm"][:, 0].max() 44 | return data 45 | 46 | 47 | def parse_swarm_mio_coefficients(lines, data): 48 | """ Parse the Swarm MIO_SHA_2* coefficients. """ 49 | nm_idx = [] 50 | coeff = [] 51 | for line in lines: 52 | fields = line.split() 53 | nm_idx.append([int(v) for v in fields[:2]]) 54 | coeff.append([float(v) for v in fields[2:]]) 55 | nm_idx = array(nm_idx) 56 | coeff = array(coeff) 57 | 58 | # shape of the 2D Fourier series coefficients arrays 59 | coeff_shape = ( 60 | len(coeff), data["smax"] - data["smin"] + 1, 61 | data["pmax"] - data["pmin"] + 1, 2 62 | ) 63 | coeff = coeff.reshape(coeff_shape) 64 | 65 | # split internal and external coefficients 66 | size = nm_idx.shape[0] >> 1 67 | if (nm_idx[:size] != nm_idx[size:]).any(): 68 | raise ValueError("Mismatch between the QS and HG coefficients!") 69 | 70 | return nm_idx[:size], coeff[:size], coeff[size:] 71 | 72 | 73 | def parse_swarm_mio_header(line): 74 | """ Parse the Swarm MIO_SHA_2* file header. """ 75 | fields = line.split() 76 | colat_ngp = float(fields[6]) 77 | lat_ngp = float(fields[7]) 78 | return { 79 | "nmax": int(fields[0]), 80 | "mmax": int(fields[1]), 81 | "pmin": int(fields[2]), 82 | "pmax": int(fields[3]), 83 | "smin": int(fields[4]), 84 | "smax": int(fields[5]), 85 | "theta_NGP": colat_ngp, 86 | "phi_NGP": lat_ngp, 87 | "lat_NGP": 90.0 - colat_ngp, 88 | "lon_NGP": 180 - ((180 - lat_ngp) % 360), 89 | "height": float(fields[8]), 90 | "wolf_ratio": float(fields[9]), 91 | } 92 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/parser_shc.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # SHC format parser 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from numpy import inf, array 30 | 31 | __all__ = ["parse_shc_file", "parse_shc_header"] 32 | 33 | 34 | def parse_shc_file(file_in): 35 | """ Parse SHC file and return a dictionary containing the parsed model data. 36 | """ 37 | lines = _strip_shc_comments(file_in) 38 | data = _parse_shc_header(lines) 39 | data["nm"], data["gh"] = _parse_shc_coefficients(lines) 40 | return data 41 | 42 | 43 | def parse_shc_header(file_in): 44 | """ Parse SHC file header and return a dictionary containing the parsed 45 | model info. 46 | """ 47 | lines = _strip_shc_comments(file_in) 48 | return _parse_shc_header(lines) 49 | 50 | 51 | def _parse_shc_header(lines): 52 | data = _parse_shc_header_line(next(lines)) 53 | data["t"] = times = _parse_shc_times(next(lines)) 54 | if "validity_start" not in data: 55 | data["validity_start"] = times.min() if times.size > 1 else -inf 56 | if "validity_end" not in data: 57 | data["validity_end"] = data["t"].max() if times.size > 1 else +inf 58 | return data 59 | 60 | 61 | def _parse_shc_coefficients(lines): 62 | """ Parse SHC coefficients. """ 63 | nm_index = [] 64 | coefficients = [] 65 | for line in lines: 66 | fields = line.split() 67 | nm_index.append([int(v) for v in fields[:2]]) 68 | coefficients.append([float(v) for v in fields[2:]]) 69 | return array(nm_index), array(coefficients) 70 | 71 | 72 | def _parse_shc_times(line): 73 | """ Parse SHC times. """ 74 | return array([float(v) for v in line.split()]) 75 | 76 | 77 | def _parse_shc_header_line(line): 78 | """ Parse the SHC file header. """ 79 | fields = line.split() 80 | header = { 81 | "degree_min": int(fields[0]), 82 | "degree_max": int(fields[1]), 83 | "ntime": int(fields[2]), 84 | "spline_order": int(fields[3]), 85 | "nstep": int(fields[4]), 86 | } 87 | if fields[5:7]: 88 | header["validity_start"] = float(fields[5]) 89 | header["validity_end"] = float(fields[6]) 90 | return header 91 | 92 | 93 | def _strip_shc_comments(file_in): 94 | """ Strip initial comments and empty lines from a text file stream. """ 95 | for line in file_in: 96 | line = line.partition("#")[0].strip() 97 | if line: 98 | yield line 99 | break 100 | for line in file_in: 101 | line = line.strip() 102 | yield line 103 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/parser_wmm.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # World Magnetic Model file format parser 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | import re 30 | from numpy import array 31 | 32 | __all__ = ["parse_wmm_file"] 33 | 34 | RE_TERMINATING_LINE = re.compile(r'^9+$') 35 | WMM_VALIDITY_PERIOD = 5.0 # years 36 | 37 | 38 | def parse_wmm_file(file_in): 39 | """ Parse World Magnetic Model COF file format and return a dictionary 40 | containing the parsed model data. 41 | """ 42 | lines = file_in 43 | data = parse_wmm_header(next(lines)) 44 | data["nm"], data["gh"], data["t"] = parse_wmm_coefficients(lines, data) 45 | data["degree_min"] = data["nm"][:, 0].min() 46 | data["degree_max"] = data["nm"][:, 0].max() 47 | return data 48 | 49 | 50 | def parse_wmm_coefficients(lines, data): 51 | """ Parse the WMM COF coefficients. """ 52 | nm_index = [] 53 | coeff = [] 54 | 55 | # parse coefficients and convert them to a sparse structure 56 | for line in lines: 57 | fields = line.split() 58 | if len(fields) == 1 and RE_TERMINATING_LINE.match(fields[0]): 59 | break 60 | n_idx, m_idx = int(fields[0]), int(fields[1]) 61 | coef_g, coef_h, coef_dg, coef_dh = [float(v) for v in fields[2:6]] 62 | if coef_g != 0 or coef_dg != 0: 63 | nm_index.append((n_idx, m_idx)) 64 | coeff.append((coef_g, coef_dg)) 65 | if m_idx > 0 and (coef_h != 0 or coef_dh != 0): 66 | nm_index.append((n_idx, -m_idx)) 67 | coeff.append((coef_h, coef_dh)) 68 | 69 | # convert (t0, dt) to (t0, t1) 70 | epoch = data["epoch"] 71 | times = array([epoch, epoch + WMM_VALIDITY_PERIOD]) 72 | nm_index = array(nm_index) 73 | coeff = array(coeff) 74 | coeff[:, 1] = coeff[:, 0] + coeff[:, 1]*WMM_VALIDITY_PERIOD 75 | 76 | return nm_index, coeff, times 77 | 78 | 79 | def parse_wmm_header(line): 80 | """ Parse the WMM COF file header. """ 81 | fields = line.split() 82 | return { 83 | "epoch": float(fields[0]), 84 | "name": fields[1], 85 | "version": fields[2], 86 | } 87 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ESA-VirES/MagneticModel/1d4e14f66a4d01aebd39b77d9506cf650c8218cf/eoxmagmod/eoxmagmod/magnetic_model/tests/__init__.py -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/data/TEST_CHAOS_MMA.cdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ESA-VirES/MagneticModel/1d4e14f66a4d01aebd39b77d9506cf650c8218cf/eoxmagmod/eoxmagmod/magnetic_model/tests/data/TEST_CHAOS_MMA.cdf -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/data/TEST_MMA_SHA_2C.cdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ESA-VirES/MagneticModel/1d4e14f66a4d01aebd39b77d9506cf650c8218cf/eoxmagmod/eoxmagmod/magnetic_model/tests/data/TEST_MMA_SHA_2C.cdf -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/data/TEST_MMA_SHA_2F.cdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ESA-VirES/MagneticModel/1d4e14f66a4d01aebd39b77d9506cf650c8218cf/eoxmagmod/eoxmagmod/magnetic_model/tests/data/TEST_MMA_SHA_2F.cdf -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/data/__init__.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Test data 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from os.path import dirname, join 30 | 31 | SWARM_MIO_SHA_2_TEST_DATA = join(dirname(__file__), "TEST_MIO_SHA.txt") 32 | SWARM_MMA_SHA_2C_TEST_DATA = join(dirname(__file__), "TEST_MMA_SHA_2C.cdf") 33 | SWARM_MMA_SHA_2F_TEST_DATA = join(dirname(__file__), "TEST_MMA_SHA_2F.cdf") 34 | CHAOS_MMA_TEST_DATA = join(dirname(__file__), "TEST_CHAOS_MMA.cdf") 35 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/parser_emm.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # EMM format parser - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | # pylint: disable=missing-docstring 29 | 30 | from unittest import TestCase, main 31 | from numpy.testing import assert_allclose 32 | from numpy import abs as aabs 33 | from eoxmagmod.magnetic_model.parser_emm import ( 34 | combine_emm_coefficients, parse_emm_file, EMM_VALIDITY_PERIOD, 35 | ) 36 | from eoxmagmod.data import EMM_2010_STATIC, EMM_2010_SECVAR 37 | 38 | 39 | class TestEMMParser(TestCase): 40 | 41 | @staticmethod 42 | def parse(filename): 43 | with open(filename, encoding="utf8") as file_in: 44 | return parse_emm_file(file_in) 45 | 46 | def _assert_valid(self, data, expected_data): 47 | tested_data = { 48 | key: data[key] for key in expected_data 49 | } 50 | self.assertEqual(tested_data, expected_data) 51 | self.assertEqual(data["t"].size, data["gh"].shape[1]) 52 | self.assertEqual(data["nm"].shape[0], data["gh"].shape[0]) 53 | self.assertEqual(data["nm"].shape[1], 2) 54 | self.assertEqual(data["nm"][..., 0].min(), data["degree_min"]) 55 | self.assertEqual(data["nm"][..., 0].max(), data["degree_max"]) 56 | self.assertTrue(aabs(data["nm"][..., 1]).max() <= data["degree_max"]) 57 | 58 | def _assert_valid_variable(self, data, expected_data): 59 | self._assert_valid(data, expected_data) 60 | assert_allclose( 61 | data["t"], [data["epoch"], data["epoch"] + EMM_VALIDITY_PERIOD] 62 | ) 63 | 64 | def _assert_valid_constant(self, data, expected_data): 65 | self._assert_valid(data, expected_data) 66 | assert_allclose(data["t"], [data["epoch"]]) 67 | 68 | def test_parse_emm_file_emm2010_static(self): 69 | data = self.parse(EMM_2010_STATIC) 70 | self._assert_valid_constant(data, { 71 | "epoch": 2010.0, 72 | "degree_min": 1, 73 | "degree_max": 740, 74 | }) 75 | 76 | def test_parse_emm_file_emm2010_secvar(self): 77 | data = self.parse(EMM_2010_SECVAR) 78 | self._assert_valid_constant(data, { 79 | "epoch": 2010.0, 80 | "degree_min": 1, 81 | "degree_max": 16, 82 | }) 83 | 84 | def test_combine_emm_coefficients_emm2010(self): 85 | data_variable, data_constant = combine_emm_coefficients( 86 | self.parse(EMM_2010_STATIC), self.parse(EMM_2010_SECVAR) 87 | ) 88 | self._assert_valid_variable(data_variable, { 89 | "epoch": 2010.0, 90 | "degree_min": 1, 91 | "degree_max": 16, 92 | }) 93 | self._assert_valid_constant(data_constant, { 94 | "epoch": 2010.0, 95 | "degree_min": 17, 96 | "degree_max": 739, 97 | }) 98 | 99 | 100 | if __name__ == "__main__": 101 | main() 102 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/parser_igrf.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # IGRF format parser - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all 19 | # copies of this Software or works derived from this Software. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | # THE SOFTWARE. 28 | #------------------------------------------------------------------------------- 29 | #pylint: disable=missing-docstring 30 | 31 | from unittest import TestCase, main 32 | from numpy import abs as aabs 33 | from eoxmagmod.magnetic_model.parser_igrf import parse_igrf_file 34 | from eoxmagmod.data import IGRF11 35 | 36 | 37 | class TestIGRFParser(TestCase): 38 | 39 | @staticmethod 40 | def parse(filename): 41 | with open(filename, encoding="utf8") as file_in: 42 | return parse_igrf_file(file_in) 43 | 44 | def _assert_valid(self, data, expected_data): 45 | tested_data = { 46 | key: data[key] for key in expected_data 47 | } 48 | self.assertEqual(tested_data, expected_data) 49 | self.assertEqual(data["t"].size, data["gh"].shape[1]) 50 | self.assertEqual(data["nm"].shape[0], data["gh"].shape[0]) 51 | self.assertEqual(data["nm"].shape[1], 2) 52 | self.assertEqual(data["nm"][..., 0].min(), data["degree_min"]) 53 | self.assertEqual(data["nm"][..., 0].max(), data["degree_max"]) 54 | self.assertTrue(aabs(data["nm"][..., 1]).max() <= data["degree_max"]) 55 | 56 | def test_parse_igrf_file_igrf11(self): 57 | data = self.parse(IGRF11) 58 | self._assert_valid(data, { 59 | "degree_min": 1, 60 | "degree_max": 13, 61 | }) 62 | 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/parser_wmm.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # World Magnetic Model file format parser - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | # pylint: disable=missing-docstring 29 | 30 | from unittest import TestCase, main 31 | 32 | try: 33 | # Python 2 34 | from StringIO import StringIO 35 | except ImportError: 36 | from io import StringIO 37 | 38 | from numpy.testing import assert_allclose 39 | from numpy import abs as aabs 40 | from eoxmagmod.magnetic_model.parser_wmm import ( 41 | parse_wmm_file, WMM_VALIDITY_PERIOD, 42 | ) 43 | from eoxmagmod.data import WMM_2015 44 | 45 | 46 | class TestWMMParser(TestCase): 47 | 48 | @staticmethod 49 | def parse(filename): 50 | with open(filename, encoding="utf8") as file_in: 51 | return parse_wmm_file(file_in) 52 | 53 | def _assert_valid(self, data, expected_data): 54 | tested_data = { 55 | key: data[key] for key in expected_data 56 | } 57 | assert_allclose( 58 | data["t"], [data["epoch"], data["epoch"] + WMM_VALIDITY_PERIOD] 59 | ) 60 | self.assertEqual(tested_data, expected_data) 61 | self.assertEqual(data["t"].size, data["gh"].shape[1]) 62 | self.assertEqual(data["nm"].shape[0], data["gh"].shape[0]) 63 | self.assertEqual(data["nm"].shape[1], 2) 64 | self.assertEqual(data["nm"][..., 0].min(), data["degree_min"]) 65 | self.assertEqual(data["nm"][..., 0].max(), data["degree_max"]) 66 | self.assertTrue(aabs(data["nm"][..., 1]).max() <= data["degree_max"]) 67 | 68 | def test_parse_wmm_test_sample(self): 69 | data = parse_wmm_file(StringIO(TEST_PRODUCT)) 70 | assert_allclose(data["nm"], [ 71 | (10, 1), (10, -2), (11, 3), (11, -4), 72 | (12, 5), (12, -6), (13, 7), (13, -7), 73 | ]) 74 | assert_allclose(data["gh"], [ 75 | (1, 1), (1, 1), (0, 5), (0, 5), 76 | (1, 6), (1, 6), (1, 6), (1, 6), 77 | ]) 78 | self._assert_valid(data, { 79 | "name": "WMM-9999", 80 | "version": "DD/MM/YYYY", 81 | "epoch": 9999.0, 82 | "degree_min": 10, 83 | "degree_max": 13, 84 | }) 85 | 86 | def test_parse_wmm_file_wmm2015(self): 87 | data = self.parse(WMM_2015) 88 | self._assert_valid(data, { 89 | "name": "WMM-2015v2", 90 | "version": "09/18/2018", 91 | "epoch": 2015.0, 92 | "degree_min": 1, 93 | "degree_max": 12, 94 | }) 95 | 96 | 97 | TEST_PRODUCT = ("""\ 98 | 9999.0 WMM-9999 DD/MM/YYYY 99 | 9 0 0.0 1.0 0.0 1.0 100 | 9 1 0.0 0.0 0.0 0.0 101 | 10 1 1.0 0.0 0.0 0.0 102 | 10 2 0.0 1.0 0.0 0.0 103 | 11 3 0.0 0.0 1.0 0.0 104 | 11 4 0.0 0.0 0.0 1.0 105 | 12 5 1.0 0.0 1.0 0.0 106 | 12 6 0.0 1.0 0.0 1.0 107 | 13 7 1.0 1.0 1.0 1.0 108 | 999999999999999999999999999999999999999999999999 109 | 999999999999999999999999999999999999999999999999 110 | """) 111 | 112 | 113 | if __name__ == "__main__": 114 | main() 115 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_model/tests/util.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # shared utilities test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | # pylint: disable=missing-docstring 29 | 30 | from unittest import TestCase, main 31 | from hashlib import md5 32 | from eoxmagmod.magnetic_model.util import parse_file 33 | from eoxmagmod.magnetic_model.tests.data import SWARM_MIO_SHA_2_TEST_DATA 34 | 35 | 36 | class TestUtil(TestCase): 37 | 38 | @staticmethod 39 | def _check_sum(file_in): 40 | md5sum = md5() 41 | md5sum.update(file_in.read().encode("UTF-8")) 42 | return md5sum.hexdigest() 43 | 44 | def test_parse_file(self): 45 | filename = SWARM_MIO_SHA_2_TEST_DATA 46 | data_file_name = parse_file(self._check_sum, filename) 47 | with open(filename, encoding="ascii") as file_in: 48 | data_file_object = parse_file(self._check_sum, file_in) 49 | self.assertEqual(data_file_name, data_file_object) 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/magnetic_time.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Magnetic time calculations 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from math import pi 30 | from numpy import sin, cos, arctan2 31 | from .solar_position import sunpos 32 | 33 | __all__ = ["mjd2000_to_magnetic_universal_time"] 34 | 35 | DEG2RAD = pi/180.0 36 | RAD2HOUR = 12.0/pi 37 | 38 | 39 | def mjd2000_to_magnetic_universal_time(mjd2000, lat_ngp, lon_ngp, 40 | lat_sol=None, lon_sol=None): 41 | """ Evaluate magnetic universal time for the given MJD2000 and 42 | coordinates of the North Geomagnetic Pole. 43 | 44 | The magnetic universal time is geometrically equivalent 45 | to the magnetic dipole longitude of the sub-solar point. 46 | 47 | Function allows specification of user defined sub-solar 48 | latitude and longitude lat_sol and lon_sol in degrees 49 | overriding the default Sun model. 50 | """ 51 | if lat_sol is None or lon_sol is None: 52 | lat_sol, lon_sol = get_subsol(mjd2000) 53 | 54 | return _mjd2000_to_magnetic_universal_time( 55 | mjd2000, lat_ngp, lon_ngp, lat_sol, lon_sol 56 | ) 57 | 58 | 59 | def get_subsol(mjd2000): 60 | """ Calculate sub-solar point coordinates for the given MJD2000 time. """ 61 | declination, _, hour_angle, _, _ = sunpos(mjd2000, 0, 0, rad=0) 62 | return declination, -hour_angle 63 | 64 | 65 | def _mjd2000_to_magnetic_universal_time(mjd2000, lat_ngp, lon_ngp, 66 | lat_sol, lon_sol): 67 | latr_sol = DEG2RAD * lat_sol 68 | latr_ngp = DEG2RAD * lat_ngp 69 | dif_lonr = DEG2RAD * (lon_sol - lon_ngp) 70 | 71 | tmp_y = cos(latr_sol) 72 | tmp_x = sin(latr_ngp)*tmp_y*cos(dif_lonr) - cos(latr_ngp)*sin(latr_sol) 73 | tmp_y *= sin(dif_lonr) 74 | 75 | return RAD2HOUR * (pi - arctan2(tmp_y, tmp_x)) 76 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/py_common.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * common shared Python definitions Python 2/3 compatibility 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2018 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #ifndef PY_COMMON 31 | #define PY_COMMON 32 | 33 | #include 34 | 35 | /* checking the Python version */ 36 | 37 | #if PY_MAJOR_VERSION == 3 38 | #if PY_MINOR_VERSION < 4 39 | #error "Non-supported Python minor version!" 40 | #endif 41 | #else 42 | #error "Non-supported Python major version!" 43 | #endif 44 | 45 | /* Python dictionary operations */ 46 | 47 | static void set_dict_item_str(PyObject *dict, const char * key, PyObject *value) 48 | { 49 | PyDict_SetItemString(dict, key, value); 50 | Py_DECREF(value); 51 | } 52 | 53 | static void set_dict_item_str_long(PyObject *dict, const char * key, long value) 54 | { 55 | set_dict_item_str(dict, key, PyLong_FromLong(value)); 56 | } 57 | 58 | static void set_dict_item_str_double(PyObject *dict, const char * key, double value) 59 | { 60 | set_dict_item_str(dict, key, PyFloat_FromDouble(value)); 61 | } 62 | 63 | static void set_dict_item_str_str(PyObject *dict, const char * key, const char *value) 64 | { 65 | set_dict_item_str(dict, key, PyUnicode_FromString(value)); 66 | } 67 | 68 | /* Python module initialization */ 69 | 70 | static struct PyModuleDef module_definition = { 71 | PyModuleDef_HEAD_INIT, NULL, NULL, -1, NULL, NULL, NULL, NULL, NULL 72 | }; 73 | 74 | PyObject* init_python_module( 75 | const char *name, 76 | const char *doc, 77 | PyMethodDef *methods 78 | ) 79 | { 80 | PyObject *module = NULL; 81 | module_definition.m_name = name; 82 | module_definition.m_doc = doc; 83 | module_definition.m_methods = methods; 84 | module = PyModule_Create(&module_definition); 85 | return module; 86 | } 87 | 88 | #endif /* PY_COMMON */ 89 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pymm_coord.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Geomagnetic Model - C python bindings - coordinate systems definitions 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2014 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #ifndef PYMM_COORD_H 31 | #define PYMM_COORD_H 32 | 33 | typedef enum { 34 | CT_INVALID = -1, 35 | CT_GEODETIC_ABOVE_WGS84 = 0, 36 | CT_GEOCENTRIC_SPHERICAL = 1, 37 | CT_GEOCENTRIC_CARTESIAN = 2 38 | } COORD_TYPE; 39 | 40 | /* 41 | * Check the coordinate type. 42 | */ 43 | static COORD_TYPE _check_coord_type(int ct, const char *label) 44 | { 45 | switch (ct) 46 | { 47 | case CT_GEODETIC_ABOVE_WGS84: 48 | return CT_GEODETIC_ABOVE_WGS84; 49 | case CT_GEOCENTRIC_SPHERICAL: 50 | return CT_GEOCENTRIC_SPHERICAL; 51 | case CT_GEOCENTRIC_CARTESIAN: 52 | return CT_GEOCENTRIC_CARTESIAN; 53 | default: 54 | PyErr_Format(PyExc_ValueError, "Invalid coordinate type '%s'!", label); 55 | return CT_INVALID; 56 | } 57 | } 58 | 59 | #endif /* PYMM_COORD_H */ 60 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pymm_sphar_common.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Geomagnetic Model - C python bindings - spherical-harmonics evaluation 4 | * - common subroutines and definitions 5 | * 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2022 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | 31 | #ifndef PYMM_SPHAR_COMMON_H 32 | #define PYMM_SPHAR_COMMON_H 1 33 | 34 | #include "pymm_aux.h" 35 | 36 | #ifndef MAX_DEGREE 37 | #define MAX_DEGREE 2147483647 38 | #endif 39 | 40 | 41 | // compare dimension from two arrays - return 1 if not equal 42 | int _compare_dimensions(npy_intp *dims1, npy_intp *dims2, npy_intp ndim) 43 | { 44 | npy_intp i; 45 | 46 | for (i = 0; i < ndim; ++i) 47 | { 48 | if (dims1[i] != dims2[i]) 49 | return 1; 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | 56 | // extract largest common array shape 57 | void _extract_common_shape( 58 | int *arg_idx_out, npy_intp *ndim_out, npy_intp **dims_out, 59 | PyArrayObject **arrays, npy_intp *arrays_ndim, int narrays 60 | ) 61 | { 62 | 63 | int arg_idx = -1; 64 | npy_intp ndim = 0; 65 | npy_intp *dims = NULL; 66 | 67 | if (narrays > 0) 68 | { 69 | int i; 70 | 71 | arg_idx = 0; 72 | ndim = PyArray_NDIM(arrays[0]) - arrays_ndim[0]; 73 | dims = PyArray_DIMS(arrays[0]); 74 | 75 | for (i = 1; i < narrays; ++i) 76 | { 77 | npy_intp ndim_tmp = PyArray_NDIM(arrays[i]) - arrays_ndim[i]; 78 | 79 | if (ndim < ndim_tmp) 80 | { 81 | arg_idx = i; 82 | ndim = ndim_tmp; 83 | dims = PyArray_DIMS(arrays[i]); 84 | } 85 | } 86 | } 87 | 88 | *arg_idx_out = arg_idx; 89 | *ndim_out = ndim; 90 | *dims_out = dims; 91 | } 92 | 93 | 94 | // extract maximum degree from an array of possible values 95 | void _get_max_degree( 96 | int *arg_idx_out, npy_intp *max_degree_out, npy_intp degrees[], int ndegrees 97 | ) 98 | { 99 | npy_intp max_degree = -1; 100 | int arg_idx = -1; 101 | 102 | if (ndegrees > 0) 103 | { 104 | int i; 105 | arg_idx = 0; 106 | max_degree = degrees[0]; 107 | 108 | for (i = 1; i < ndegrees; ++i) 109 | { 110 | if (degrees[i] < max_degree) 111 | { 112 | arg_idx = i; 113 | max_degree = degrees[i]; 114 | } 115 | } 116 | } 117 | 118 | *arg_idx_out = arg_idx; 119 | *max_degree_out = max_degree; 120 | } 121 | 122 | 123 | npy_intp _size_to_degree(npy_intp size) 124 | { 125 | npy_intp degree = (npy_intp)(0.5 *(sqrt(8*size + 1) - 3)); 126 | if (((degree+3)*(degree+2))/2 == size) 127 | return degree + 1; 128 | return degree; 129 | } 130 | 131 | #endif /*PYMM_SPHAR_COMMON_H*/ 132 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pymm_vrot_common.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Geomagnetic Model - C python bindings - vector rotation - common 4 | * (i.e., vector coordinate system transformation) 5 | * 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2014-2022 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | 31 | #ifndef PYMM_VROT_COMMON_H 32 | #define PYMM_VROT_COMMON_H 33 | 34 | #include "pymm_aux.h" 35 | 36 | /* checking */ 37 | 38 | static int _vrot_arr_check( 39 | PyArrayObject *arr_ref, PyArrayObject *arr_checked, 40 | const char *label_ref, const char *label_checked 41 | ) 42 | { 43 | if (PyArray_NDIM(arr_checked) > 0) 44 | { 45 | int d; 46 | 47 | if (PyArray_NDIM(arr_ref) != PyArray_NDIM(arr_checked)+1) 48 | { 49 | PyErr_Format(PyExc_ValueError, "Dimension mismatch between '%s' " 50 | "and '%s'!", label_ref, label_checked); 51 | return 1; 52 | } 53 | 54 | for (d = 0; d < (PyArray_NDIM(arr_ref)-1); ++d) 55 | { 56 | if (PyArray_DIM(arr_ref, d) != PyArray_DIM(arr_checked, d)) 57 | { 58 | PyErr_Format(PyExc_ValueError, "Shape mismatch between '%s' " 59 | "and '%s'!", label_ref, label_checked); 60 | return 1; 61 | } 62 | } 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | #endif /* PYMM_VROT_COMMON_H */ 69 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pyqd.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Magnetic Quasi Dipole Coordinates - C python bindings 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2015-2022 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #include "common.h" /* common definitions - to be included before Python.h */ 31 | #include 32 | #include 33 | 34 | /* module version */ 35 | #include "version.h" 36 | 37 | /* common python utilities */ 38 | #include "py_common.h" 39 | 40 | /* Quasi-Dipole coordinates evaluation */ 41 | #include "pyqd_eval_qdlatlon.h" 42 | 43 | /* Magnetic Local Time coordinates evaluation */ 44 | #include "pyqd_eval_mlt.h" 45 | 46 | /* sub-solar point coordinates evaluation */ 47 | #include "pyqd_eval_subsol.h" 48 | 49 | /* qdipole API */ 50 | #include "qdipole/cqdipole.h" 51 | 52 | /*---------------------------------------------------------------------------*/ 53 | /* module's doc string */ 54 | 55 | #define DOC_PYQD \ 56 | "A library calculating magnetic coordinated (e.g., QD and MLT) and related parameters." 57 | 58 | /*---------------------------------------------------------------------------*/ 59 | /*define module's methods */ 60 | static PyMethodDef pyqd_methods[] = 61 | { 62 | {"eval_qdlatlon", (PyCFunction)eval_qdlatlon, METH_VARARGS|METH_KEYWORDS, DOC_EVAL_QDLATLON}, 63 | {"eval_mlt", (PyCFunction)eval_mlt, METH_VARARGS|METH_KEYWORDS, DOC_EVAL_MLT}, 64 | {"eval_subsol", (PyCFunction)eval_subsol, METH_VARARGS|METH_KEYWORDS, DOC_EVAL_SUBSOL}, 65 | {NULL, NULL, 0, NULL} /* Sentinel - DO NOT REMOVE! */ 66 | } ; 67 | 68 | /*---------------------------------------------------------------------------*/ 69 | /* module initialization */ 70 | 71 | static PyObject* init_module(void) 72 | { 73 | PyObject *module = init_python_module("_pyqd", DOC_PYQD, pyqd_methods); 74 | if (NULL == module) 75 | goto exit; 76 | 77 | PyObject *dict = PyModule_GetDict(module); 78 | if (NULL == dict) 79 | goto exit; 80 | 81 | /* add module specific exception */ 82 | //PyDict_SetItemString(dict, "QDError", PyErr_NewException("_pyqd.QDError", NULL, NULL)); 83 | 84 | /* metadata */ 85 | set_dict_item_str_str(dict, "__author__", "Martin Paces (martin.paces@eox.at)"); 86 | set_dict_item_str_str(dict, "__copyright__", "Copyright (C) 2015-2024 EOX IT Services GmbH"); 87 | set_dict_item_str_str(dict, "__licence__", "EOX licence (MIT style)"); 88 | set_dict_item_str_str(dict, "__version__", VERSION); 89 | set_dict_item_str_str(dict, "QDIPOLE_VERSION", get_qdipole_version()); 90 | 91 | exit: 92 | return module; 93 | } 94 | 95 | /*---------------------------------------------------------------------------*/ 96 | 97 | PyObject* PyInit__pyqd(void) 98 | { 99 | import_array(); 100 | return init_module(); 101 | } 102 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pyqd_eval_mlt.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Magnetic Quasi Dipole Coordinates - C python bindings 4 | * - Magnetic Local Time evaluation 5 | * 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2016-2024 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | 31 | #ifndef PYQD_EVAL_MLT_H 32 | #define PYQD_EVAL_MLT_H 33 | 34 | #include "pymm_aux.h" 35 | #include "qdipole/cqdipole.h" 36 | 37 | /* Python function definition */ 38 | 39 | #define DOC_EVAL_MLT "\n"\ 40 | " mlt = eval_mlt(qdlon, time, fname)\n"\ 41 | " Inputs:\n"\ 42 | " qdlon - quasi-dipole longitudes(s).\n"\ 43 | " time - MJD2000 time(s)\n"\ 44 | " Outputs:\n"\ 45 | " mlt - magnetic local times (s).\n"\ 46 | "" 47 | 48 | static PyObject* eval_mlt(PyObject *self, PyObject *args, PyObject *kwdict) 49 | { 50 | int status; 51 | static char *keywords[] = {"qdlon", "time", NULL}; 52 | 53 | PyObject *obj_qdlon = NULL; // gclon object 54 | PyObject *obj_time = NULL; // time object 55 | PyArrayObject *arr_qdlon = NULL; // qdlon array 56 | PyArrayObject *arr_time = NULL; // time array 57 | PyArrayObject *arr_mlt = NULL; // mlt array 58 | PyObject *retval = NULL; 59 | 60 | // parse input arguments 61 | if (!PyArg_ParseTupleAndKeywords( 62 | args, kwdict, "OO:eval_mlt", keywords, &obj_qdlon, &obj_time 63 | )) 64 | goto exit; 65 | 66 | #define NPY_REQ (NPY_ARRAY_ALIGNED|NPY_ARRAY_C_CONTIGUOUS) 67 | 68 | // cast the objects to arrays 69 | if (NULL == (arr_qdlon = _get_as_double_array(obj_qdlon, 0, 1, NPY_REQ, keywords[1]))) 70 | goto exit; 71 | 72 | if (NULL == (arr_time = _get_as_double_array(obj_time, 0, 1, NPY_REQ, keywords[3]))) 73 | goto exit; 74 | 75 | // check the dimensions 76 | npy_intp ndim = PyArray_NDIM(arr_qdlon); 77 | npy_intp *dims = PyArray_DIMS(arr_qdlon); 78 | 79 | if(_check_arr_dims_all_eq(arr_time, ndim, dims, keywords[3])) 80 | goto exit; 81 | 82 | // create the output arrays 83 | if (NULL == (arr_mlt = (PyArrayObject*) PyArray_EMPTY(ndim, dims, NPY_DOUBLE, 0))) 84 | goto exit; 85 | 86 | // evaluate the output values 87 | status = c_eval_mlt( 88 | (double*) PyArray_DATA(arr_mlt), 89 | (double*) PyArray_DATA(arr_qdlon), 90 | (double*) PyArray_DATA(arr_time), 91 | ndim == 0 ? 1 : dims[0] 92 | ); 93 | 94 | if (status) { 95 | PyErr_Format( 96 | PyExc_RuntimeError, 97 | "Call to c_eval_mlt() failed with an error! error_code = %d", status 98 | ); 99 | goto exit; 100 | } 101 | 102 | retval = (PyObject*) arr_mlt; 103 | 104 | exit: 105 | 106 | // decrease reference counters to the arrays 107 | if (arr_qdlon) Py_DECREF(arr_qdlon); 108 | if (arr_time) Py_DECREF(arr_time); 109 | if (!retval && arr_mlt) Py_DECREF(arr_mlt); 110 | 111 | return retval; 112 | } 113 | 114 | #endif /* PYQD_EVAL_MLT_H */ 115 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pyqd_eval_subsol.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Magnetic Quasi Dipole Coordinates - C python bindings 4 | * - sub-solar point evaluation 5 | * 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2016-2024 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | 31 | #ifndef PYQD_EVAL_SUBSOL_H 32 | #define PYQD_EVAL_SUBSOL_H 33 | 34 | #include "pymm_aux.h" 35 | #include "qdipole/cqdipole.h" 36 | 37 | /* Python function definition */ 38 | 39 | #define DOC_EVAL_SUBSOL "\n"\ 40 | " gdlat, gdlon = eval_subsol(time)\n"\ 41 | " Inputs:\n"\ 42 | " time - MJD2000 time(s)\n"\ 43 | " Outputs:\n"\ 44 | " gdlat - sub-solar point latitude(s).\n"\ 45 | " gdlon - sub-solar point longitudes(s).\n"\ 46 | "" 47 | 48 | static PyObject* eval_subsol(PyObject *self, PyObject *args, PyObject *kwdict) 49 | { 50 | int status; 51 | static char *keywords[] = {"time", NULL}; 52 | 53 | PyObject *obj_time = NULL; // time object 54 | PyArrayObject *arr_time = NULL; // time array 55 | PyArrayObject *arr_gdlat = NULL; // gdlat array 56 | PyArrayObject *arr_gdlon = NULL; // gdlon array 57 | PyObject *retval = NULL; 58 | 59 | // parse input arguments 60 | if (!PyArg_ParseTupleAndKeywords( 61 | args, kwdict, "O:eval_subsol", keywords, &obj_time 62 | )) 63 | goto exit; 64 | 65 | #define NPY_REQ (NPY_ARRAY_ALIGNED|NPY_ARRAY_C_CONTIGUOUS) 66 | 67 | // cast the objects to arrays 68 | if (NULL == (arr_time = _get_as_double_array(obj_time, 0, 1, NPY_REQ, keywords[0]))) 69 | goto exit; 70 | 71 | // create the output arrays 72 | npy_intp ndim = PyArray_NDIM(arr_time); 73 | npy_intp *dims = PyArray_DIMS(arr_time); 74 | 75 | if (NULL == (arr_gdlat = (PyArrayObject*) PyArray_EMPTY(ndim, dims, NPY_DOUBLE, 0))) 76 | goto exit; 77 | 78 | if (NULL == (arr_gdlon = (PyArrayObject*) PyArray_EMPTY(ndim, dims, NPY_DOUBLE, 0))) 79 | goto exit; 80 | 81 | // evaluate the output values 82 | status = c_eval_subsol( 83 | (double*) PyArray_DATA(arr_gdlat), 84 | (double*) PyArray_DATA(arr_gdlon), 85 | (double*) PyArray_DATA(arr_time), 86 | ndim == 0 ? 1 : dims[0] 87 | ); 88 | 89 | if (status) { 90 | PyErr_Format( 91 | PyExc_RuntimeError, 92 | "Call to c_eval_subsol() failed with an error! error_code = %d", status 93 | ); 94 | goto exit; 95 | } 96 | 97 | if (NULL == (retval = Py_BuildValue("NN", (PyObject*) arr_gdlat, (PyObject*) arr_gdlon))) 98 | goto exit; 99 | 100 | exit: 101 | 102 | // decrease reference counters to the arrays 103 | if (arr_time) Py_DECREF(arr_time); 104 | if (!retval) 105 | { 106 | if (arr_gdlat) Py_DECREF(arr_gdlat); 107 | if (arr_gdlon) Py_DECREF(arr_gdlon); 108 | } 109 | 110 | return retval; 111 | } 112 | 113 | #endif /* PYQD_EVAL_SUBSOL_H */ 114 | 115 | 116 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pysunpos.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Solar position 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2017-2022 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #include "common.h" /* common definitions - to be included before Python.h */ 31 | #include 32 | #include 33 | 34 | /* module version */ 35 | #include "version.h" 36 | 37 | /* common python utilities */ 38 | #include "py_common.h" 39 | 40 | /* Sun ephemeris algorithms */ 41 | #include "pysunpos.h" 42 | #include "pysunpos_original.h" 43 | 44 | /*---------------------------------------------------------------------------*/ 45 | /* module's doc string */ 46 | 47 | #define DOC_PYSUNPOS \ 48 | "This module provides bindings to the solar model." 49 | 50 | /*---------------------------------------------------------------------------*/ 51 | /*define module's methods */ 52 | static PyMethodDef pysunpos_methods[] = 53 | { 54 | {"sunpos", (PyCFunction)pysunpos_sunpos, METH_VARARGS|METH_KEYWORDS, DOC_SUNPOS}, 55 | {"sunpos_original", (PyCFunction)pysunpos_sunpos_original, METH_VARARGS|METH_KEYWORDS, DOC_SUNPOS_ORIGINAL}, 56 | {NULL, NULL, 0, NULL} /* Sentinel - DO NOT REMOVE! */ 57 | } ; 58 | 59 | /*---------------------------------------------------------------------------*/ 60 | 61 | static PyObject* init_module(void) 62 | { 63 | PyObject *module = init_python_module("_pysunpos", DOC_PYSUNPOS, pysunpos_methods); 64 | if (NULL == module) 65 | goto exit; 66 | 67 | PyObject *dict = PyModule_GetDict(module); 68 | if (NULL == dict) 69 | goto exit; 70 | 71 | /* module metadata */ 72 | set_dict_item_str_str(dict, "__author__", "Martin Paces (martin.paces@eox.at)"); 73 | set_dict_item_str_str(dict, "__copyright__", "Copyright (C) 2017-2022 EOX IT Services GmbH"); 74 | set_dict_item_str_str(dict, "__licence__", "EOX licence (MIT style)"); 75 | set_dict_item_str_str(dict, "__version__", VERSION); 76 | 77 | exit: 78 | return module; 79 | } 80 | 81 | /*---------------------------------------------------------------------------*/ 82 | 83 | PyObject* PyInit__pysunpos(void) 84 | { 85 | import_array(); 86 | return init_module(); 87 | } 88 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pytimeconv.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Time conversion utilities. 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2018-2022 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #include "common.h" /* common definitions - to be included before Python.h */ 31 | #include 32 | #include 33 | 34 | /* module version */ 35 | #include "version.h" 36 | 37 | /* common python utilities */ 38 | #include "py_common.h" 39 | 40 | /* time conversion subroutines */ 41 | #include "pytimeconv_mjd2000_to_decimal_year.h" 42 | #include "pytimeconv_mjd2000_to_year_fraction.h" 43 | #include "pytimeconv_decimal_year_to_mjd2000.h" 44 | 45 | /*---------------------------------------------------------------------------*/ 46 | /* module's doc string */ 47 | 48 | #define DOC_PYTIMECONV \ 49 | "Time conversion utilities." 50 | 51 | /*---------------------------------------------------------------------------*/ 52 | /*define module's methods */ 53 | static PyMethodDef pytimeconv_methods[] = 54 | { 55 | {"mjd2000_to_decimal_year", (PyCFunction)pytimeconv_mjd2000_to_decimal_year, METH_VARARGS|METH_KEYWORDS, DOC_MJD2000_TO_DECIMAL_YEAR}, 56 | {"mjd2000_to_year_fraction", (PyCFunction)pytimeconv_mjd2000_to_year_fraction, METH_VARARGS|METH_KEYWORDS, DOC_MJD2000_TO_YEAR_FRACTION}, 57 | {"decimal_year_to_mjd2000", (PyCFunction)pytimeconv_decimal_year_to_mjd2000, METH_VARARGS|METH_KEYWORDS, DOC_DECIMAL_YEAR_TO_MJD2000}, 58 | {NULL, NULL, 0, NULL} /* Sentinel - DO NOT REMOVE! */ 59 | } ; 60 | 61 | /*---------------------------------------------------------------------------*/ 62 | 63 | /* module initialization */ 64 | static PyObject* init_module(void) 65 | { 66 | PyObject *module = init_python_module("_pytimeconv", DOC_PYTIMECONV, pytimeconv_methods); 67 | if (NULL == module) 68 | goto exit; 69 | 70 | PyObject *dict = PyModule_GetDict(module); 71 | if (NULL == dict) 72 | goto exit; 73 | 74 | /* metadata */ 75 | set_dict_item_str_str(dict, "__author__", "Martin Paces (martin.paces@eox.at)"); 76 | set_dict_item_str_str(dict, "__copyright__", "Copyright (C) 2018-2022 EOX IT Services GmbH"); 77 | set_dict_item_str_str(dict, "__licence__", "EOX licence (MIT style)"); 78 | set_dict_item_str_str(dict, "__version__", VERSION); 79 | 80 | exit: 81 | return module; 82 | } 83 | 84 | /*---------------------------------------------------------------------------*/ 85 | 86 | PyObject* PyInit__pytimeconv(void) 87 | { 88 | import_array(); 89 | return init_module(); 90 | } 91 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pytimeconv_decimal_year_to_mjd2000.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Decimal year to MJD2000 conversion 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2018-2022 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #ifndef PYTIMECONV_DECIMAL_YEAR_TO_MJD2000_H 31 | #define PYTIMECONV_DECIMAL_YEAR_TO_MJD2000_H 32 | 33 | #include "pymm_aux.h" 34 | #include "time_conversion.h" 35 | #include 36 | 37 | /* 38 | * nD-array recursive evaluation 39 | */ 40 | 41 | static void _year_to_mjd2k(ARRAY_DATA arrd_in, ARRAY_DATA arrd_out) 42 | { 43 | if (arrd_in.ndim > 0) 44 | { 45 | npy_intp i; 46 | for(i = 0; i < arrd_in.dim[0]; ++i) 47 | _year_to_mjd2k( 48 | _get_arrd_item(&arrd_in, i), 49 | _get_arrd_item(&arrd_out, i) 50 | ); 51 | } 52 | else 53 | { 54 | #define S(a) (*((double*)(a).data)) 55 | S(arrd_out) = decimal_year_to_mjd2k(S(arrd_in)); 56 | #undef S 57 | } 58 | } 59 | 60 | 61 | /* 62 | * Python function definition 63 | */ 64 | 65 | #define DOC_DECIMAL_YEAR_TO_MJD2000 "\n"\ 66 | " time_mjd2k = decimal_year_to_mjd2000(decimal_year)\n\n"\ 67 | " Output:\n"\ 68 | " time_mjd2k - array of the calculated MJD2000 times.\n"\ 69 | "\n"\ 70 | " Parameters:\n"\ 71 | " decimal_year - array of decimal years (up to 15 dimensions).\n"\ 72 | "\n" 73 | 74 | static PyObject* pytimeconv_decimal_year_to_mjd2000( 75 | PyObject *self, PyObject *args, PyObject *kwdict 76 | ) 77 | { 78 | static char *keywords[] = {"time_mjd2k", NULL}; 79 | PyObject *obj_in = NULL; // input object 80 | PyArrayObject *arr_in = NULL; // input array 81 | PyArrayObject *arr_out = NULL; // return array 82 | PyObject *retval = NULL; // return object 83 | 84 | // parse input arguments 85 | if (!PyArg_ParseTupleAndKeywords( 86 | args, kwdict, "O|:decimal_year_to_mjd2000", keywords, &obj_in 87 | )) 88 | goto exit; 89 | 90 | // cast input objects to arrays 91 | if (NULL == (arr_in = _get_as_double_array(obj_in, 0, 0, NPY_ARRAY_ALIGNED, keywords[0]))) 92 | goto exit; 93 | 94 | // check maximum allowed input array dimension 95 | if (PyArray_NDIM(arr_in) > (MAX_OUT_ARRAY_NDIM-1)) 96 | { 97 | PyErr_Format(PyExc_ValueError, "Array dimension of '%s'"\ 98 | " %d exceeds the allowed maximum value %d!", keywords[0], 99 | PyArray_NDIM(arr_in), (MAX_OUT_ARRAY_NDIM-1)); 100 | goto exit; 101 | } 102 | 103 | // create a new output array 104 | if (NULL == (arr_out = (PyArrayObject*) PyArray_EMPTY(PyArray_NDIM(arr_in), PyArray_DIMS(arr_in), NPY_DOUBLE, 0))) 105 | goto exit; 106 | 107 | // convert decimal years to MJD2000 times 108 | _year_to_mjd2k( 109 | _array_to_arrd(arr_in), 110 | _array_to_arrd(arr_out) 111 | ); 112 | 113 | // assign return value 114 | retval = (PyObject*) arr_out; 115 | 116 | exit: 117 | 118 | // decrease reference counters to the arrays 119 | if (arr_in) Py_DECREF(arr_in); 120 | if (!retval && arr_out) Py_DECREF(arr_out); 121 | 122 | return retval; 123 | } 124 | 125 | #endif /* PYTIMECONV_DECIMAL_YEAR_TO_MJD2000_H */ 126 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pytimeconv_mjd2000_to_decimal_year.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * MJD2000 to decimal year conversion 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2018-2022 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #ifndef PYTIMECONV_MJD2000_TO_DECIMAL_YEAR_H 31 | #define PYTIMECONV_MJD2000_TO_DECIMAL_YEAR_H 32 | 33 | #include "pymm_aux.h" 34 | #include "time_conversion.h" 35 | #include 36 | 37 | /* 38 | * nD-array recursive evaluation 39 | */ 40 | 41 | static void _mjd2k_to_year(ARRAY_DATA arrd_in, ARRAY_DATA arrd_out) 42 | { 43 | if (arrd_in.ndim > 0) 44 | { 45 | npy_intp i; 46 | for(i = 0; i < arrd_in.dim[0]; ++i) 47 | _mjd2k_to_year( 48 | _get_arrd_item(&arrd_in, i), 49 | _get_arrd_item(&arrd_out, i) 50 | ); 51 | } 52 | else 53 | { 54 | #define S(a) (*((double*)(a).data)) 55 | S(arrd_out) = mjd2k_to_decimal_year(S(arrd_in)); 56 | #undef S 57 | } 58 | } 59 | 60 | 61 | /* 62 | * Python function definition 63 | */ 64 | 65 | #define DOC_MJD2000_TO_DECIMAL_YEAR "\n"\ 66 | " decimal_year = mjd2000_to_decimal_year(time_mjd2k)\n\n"\ 67 | " Output:\n"\ 68 | " decimal_year - array of the calculated decimal years.\n"\ 69 | "\n"\ 70 | " Parameters:\n"\ 71 | " time_mjd2k - array of MJD2000 times (up to 15 dimensions).\n"\ 72 | "\n" 73 | 74 | static PyObject* pytimeconv_mjd2000_to_decimal_year( 75 | PyObject *self, PyObject *args, PyObject *kwdict 76 | ) 77 | { 78 | static char *keywords[] = {"time_mjd2k", NULL}; 79 | PyObject *obj_in = NULL; // input object 80 | PyArrayObject *arr_in = NULL; // input array 81 | PyArrayObject *arr_out = NULL; // return array 82 | PyObject *retval = NULL; // return object 83 | 84 | // parse input arguments 85 | if (!PyArg_ParseTupleAndKeywords( 86 | args, kwdict, "O|:mjd2000_to_decimal_year", keywords, &obj_in 87 | )) 88 | goto exit; 89 | 90 | // cast the objects to arrays 91 | if (NULL == (arr_in = _get_as_double_array(obj_in, 0, 0, NPY_ARRAY_ALIGNED, keywords[0]))) 92 | goto exit; 93 | 94 | // check maximum allowed input array dimension 95 | if (PyArray_NDIM(arr_in) > (MAX_OUT_ARRAY_NDIM-1)) 96 | { 97 | PyErr_Format(PyExc_ValueError, "Array dimension of '%s'"\ 98 | " %d exceeds the allowed maximum value %d!", keywords[0], 99 | PyArray_NDIM(arr_in), (MAX_OUT_ARRAY_NDIM-1)); 100 | goto exit; 101 | } 102 | 103 | // create a new output array 104 | if (NULL == (arr_out = (PyArrayObject*) PyArray_EMPTY(PyArray_NDIM(arr_in), PyArray_DIMS(arr_in), NPY_DOUBLE, 0))) 105 | goto exit; 106 | 107 | // convert MJD2000 times to decimal years 108 | _mjd2k_to_year( 109 | _array_to_arrd(arr_in), 110 | _array_to_arrd(arr_out) 111 | ); 112 | 113 | // assign return value 114 | retval = (PyObject*) arr_out; 115 | 116 | exit: 117 | 118 | // decrease reference counters to the arrays 119 | if (arr_in) Py_DECREF(arr_in); 120 | if (!retval && arr_out) Py_DECREF(arr_out); 121 | 122 | return retval; 123 | } 124 | 125 | #endif /* PYTIMECONV_MJD2000_TO_DECIMAL_YEAR_H */ 126 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/pytimeconv_mjd2000_to_year_fraction.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * MJD2000 to year fraction conversion 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2018-2022 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #ifndef PYTIMECONV_MJD2000_TO_YEAR_FRACTION_H 31 | #define PYTIMECONV_MJD2000_TO_YEAR_FRACTION_H 32 | 33 | #include "pymm_aux.h" 34 | #include "time_conversion.h" 35 | #include 36 | 37 | /* 38 | * nD-array recursive evaluation 39 | */ 40 | 41 | static void _mjd2k_to_year_fract(ARRAY_DATA arrd_in, ARRAY_DATA arrd_out) 42 | { 43 | if (arrd_in.ndim > 0) 44 | { 45 | npy_intp i; 46 | for(i = 0; i < arrd_in.dim[0]; ++i) 47 | _mjd2k_to_year_fract( 48 | _get_arrd_item(&arrd_in, i), 49 | _get_arrd_item(&arrd_out, i) 50 | ); 51 | } 52 | else 53 | { 54 | #define S(a) (*((double*)(a).data)) 55 | S(arrd_out) = mjd2k_to_year_fraction(S(arrd_in)); 56 | #undef S 57 | } 58 | } 59 | 60 | 61 | /* 62 | * Python function definition 63 | */ 64 | 65 | #define DOC_MJD2000_TO_YEAR_FRACTION "\n"\ 66 | " year_fraction = mjd2000_to_year_fraction(time_mjd2k)\n\n"\ 67 | " Output:\n"\ 68 | " year_fraction - fraction of the year since the latest *-01-01T00:00Z\n"\ 69 | "\n"\ 70 | " Parameters:\n"\ 71 | " time_mjd2k - array of MJD2000 times (up to 15 dimensions).\n"\ 72 | "\n" 73 | 74 | static PyObject* pytimeconv_mjd2000_to_year_fraction( 75 | PyObject *self, PyObject *args, PyObject *kwdict 76 | ) 77 | { 78 | static char *keywords[] = {"time_mjd2k", NULL}; 79 | PyObject *obj_in = NULL; // input object 80 | PyArrayObject *arr_in = NULL; // input array 81 | PyArrayObject *arr_out = NULL; // return array 82 | PyObject *retval = NULL; // return object 83 | 84 | // parse input arguments 85 | if (!PyArg_ParseTupleAndKeywords( 86 | args, kwdict, "O|:mjd2000_to_year_fraction", keywords, &obj_in 87 | )) 88 | goto exit; 89 | 90 | // cast the objects to arrays 91 | if (NULL == (arr_in = _get_as_double_array(obj_in, 0, 0, NPY_ARRAY_ALIGNED, keywords[0]))) 92 | goto exit; 93 | 94 | // check maximum allowed input array dimension 95 | if (PyArray_NDIM(arr_in) > (MAX_OUT_ARRAY_NDIM-1)) 96 | { 97 | PyErr_Format(PyExc_ValueError, "Array dimension of '%s'"\ 98 | " %d exceeds the allowed maximum value %d!", keywords[0], 99 | PyArray_NDIM(arr_in), (MAX_OUT_ARRAY_NDIM-1)); 100 | goto exit; 101 | } 102 | 103 | // create a new output array 104 | if (NULL == (arr_out = (PyArrayObject*) PyArray_EMPTY(PyArray_NDIM(arr_in), PyArray_DIMS(arr_in), NPY_DOUBLE, 0))) 105 | goto exit; 106 | 107 | // convert MJD2000 times to year fractions 108 | _mjd2k_to_year_fract( 109 | _array_to_arrd(arr_in), 110 | _array_to_arrd(arr_out) 111 | ); 112 | 113 | // assign return value 114 | retval = (PyObject*) arr_out; 115 | 116 | exit: 117 | 118 | // decrease reference counters to the arrays 119 | if (arr_in) Py_DECREF(arr_in); 120 | if (!retval && arr_out) Py_DECREF(arr_out); 121 | 122 | return retval; 123 | } 124 | 125 | #endif /* PYTIMECONV_MJD2000_TO_YEAR_FRACTION_H */ 126 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/quasi_dipole_coordinates.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Quasi-Dipole Apex Coordinates / Geomagnetism Library 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2015-2024 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from os.path import isfile 30 | from . import _pyqd 31 | from .data import APEX_LATEST 32 | 33 | __all__ = [ 34 | "eval_qdlatlon", 35 | "eval_qdlatlon_with_base_vectors", 36 | "eval_mlt", 37 | "eval_subsol", 38 | "QDIPOLE_VERSION", 39 | ] 40 | 41 | QDIPOLE_VERSION = _pyqd.QDIPOLE_VERSION 42 | 43 | def eval_qdlatlon(gclat, gclon, gcrad, time, fname=APEX_LATEST): 44 | """ 45 | Evaluate magnetic quasi-dipole coordinates a single or multiple input 46 | points. 47 | 48 | Inputs: 49 | gclat - geocentric latitude(s). 50 | gclon - geocentric longitude(s). 51 | gcrad - geocentric radial coordinate(s) in km. 52 | time - decimal year time(s) 53 | fname - file-name of the model text file. 54 | 55 | Outputs: 56 | qdlat - quasi-dipole latitude(s). 57 | qdlon - quasi-dipole longitude(s). 58 | """ 59 | if not isfile(fname): 60 | raise IOError(f"File not found! fname={fname!r}") 61 | return _pyqd.eval_qdlatlon(gclat, gclon, gcrad, time, fname) 62 | 63 | 64 | def eval_qdlatlon_with_base_vectors(gclat, gclon, gcrad, time, fname=APEX_LATEST): 65 | """ 66 | Evaluate magnetic quasi-dipole coordinates a single or multiple input 67 | coordinates. 68 | 69 | Inputs: 70 | gclat - geocentric latitude(s). 71 | gclon - geocentric longitude(s). 72 | gcrad - geocentric radial coordinate(s) in km. 73 | time - decimal year time(s) 74 | fname - file-name of the model text file. 75 | 76 | Outputs: 77 | qdlat - quasi-dipole latitude(s). 78 | qdlon - quasi-dipole longitude(s). 79 | f11 - base vector F1 component 1 80 | f12 - base vector F1 component 2 81 | f21 - base vector F2 component 1 82 | f22 - base vector F2 component 2 83 | f - | F1 x F2 | value 84 | """ 85 | if not isfile(fname): 86 | raise IOError(f"File not found! fname={fname!r}") 87 | return _pyqd.eval_qdlatlon(gclat, gclon, gcrad, time, fname, True) 88 | 89 | 90 | def eval_mlt(qdlon, time): 91 | """ 92 | Evaluate magnetic local time for given quasi dipole longitudes. 93 | 94 | Inputs: 95 | qdlon - quasi-dipole longitudes(s). 96 | time - MJD2000 time(s) 97 | 98 | Outputs: 99 | mlt - magnetic local time(s). 100 | """ 101 | return _pyqd.eval_mlt(qdlon, time) 102 | 103 | 104 | def eval_subsol(time): 105 | """ 106 | Evaluate sub-solar point coordinates. 107 | 108 | Inputs: 109 | time - MJD2000 time(s) 110 | 111 | Outputs: 112 | gdlat - sub-solar point latitude(s). 113 | gdlon - sub-solar point longitude(s). 114 | """ 115 | return _pyqd.eval_subsol(time) 116 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/solar_position.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Solar position calculation 4 | # 5 | # Deprecated old interface. 6 | # 7 | # Project: VirES 8 | # Author: Martin Paces 9 | # 10 | #------------------------------------------------------------------------------- 11 | # Copyright (C) 2017 EOX IT Services GmbH 12 | # 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | # 20 | # The above copyright notice and this permission notice shall be included in 21 | # all 22 | # copies of this Software or works derived from this Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | # THE SOFTWARE. 31 | #------------------------------------------------------------------------------- 32 | 33 | from . import _pysunpos 34 | 35 | __all__ = ["sunpos"] 36 | 37 | 38 | def sunpos(time_mjd2k, lat, lon, rad=6371.2, dtt=0): 39 | """ Calculate solar equatorial and horizontal coordinates 40 | for given MJD2000 times and geocentric coordinates (lat, lon, rad). 41 | 42 | Outputs: arrays of the Sun equatorial and horizontal coordinates: 43 | declination 44 | right ascension 45 | local hour angle (global hour angle for 0, 0 latitude and longitude) 46 | local azimuth 47 | local zenith 48 | 49 | Parameters: 50 | time_mjd2k - array of MJD2000 times (up to 15 dimensions). 51 | lat - array of latitudes [deg] 52 | lon - array of longitudes [deg] 53 | rad - array of radii [km] (set to 0 to disable the parallax correction) 54 | dtt - array of offsets to TT [sec] 55 | """ 56 | result = _pysunpos.sunpos(time_mjd2k, lat, lon, rad, dtt) 57 | return tuple(result[..., idx] for idx in range(result.shape[-1])) 58 | 59 | 60 | def sunpos_original(time_mjd2k, lat, lon, rad=6371.2, dtt=0, pres=1.0, temp=20.0): 61 | """ Calculate solar equatorial and horizontal coordinates 62 | for given MJD2000 times and geocentric coordinates (lat, lon, rad). 63 | This is the original implementation. 64 | 65 | Outputs: arrays of the Sun equatorial and horizontal coordinates: 66 | declination 67 | right ascension 68 | local hour angle (global hour angle for 0, 0 latitude and longitude) 69 | local azimuth 70 | local zenith 71 | 72 | Parameters: 73 | time_mjd2k - array of MJD2000 times (up to 15 dimensions). 74 | lat - array of latitudes [deg] 75 | lon - array of longitudes [deg] 76 | rad - array of radii [km] (set to 0 to disable the parallax correction) 77 | dtt - array of offsets to TT [sec] 78 | pres - array of offsets of pressures [atm] (refraction correction) 79 | temp - array of offsets to temperatures [dgC] (refraction coorection) 80 | """ 81 | result = _pysunpos.sunpos_original( 82 | time_mjd2k, lat, lon, rad, dtt, pres, temp 83 | ) 84 | return tuple(result[..., idx] for idx in range(result.shape[-1])) 85 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ESA-VirES/MagneticModel/1d4e14f66a4d01aebd39b77d9506cf650c8218cf/eoxmagmod/eoxmagmod/tests/__init__.py -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/data/__init__.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Test data 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from os.path import dirname, join 30 | 31 | QUASI_DIPOLE_TEST_DATA = join(dirname(__file__), "QuasiDipoleTestData.tsv") 32 | SUN_POSITION_TEST_DATA = join(dirname(__file__), "SunPositionTestData.tsv") 33 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/data/generate_quasi_dipole_test_data.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Quasi-Dipole coordinates - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | #pylint: disable=missing-docstring 29 | 30 | import sys 31 | from itertools import product 32 | from numpy import array, vectorize 33 | from numpy.random import uniform 34 | from eoxmagmod import mjd2000_to_decimal_year 35 | from eoxmagmod.quasi_dipole_coordinates import ( 36 | eval_mlt, eval_subsol, eval_qdlatlon_with_base_vectors, 37 | ) 38 | 39 | EARTH_RADIUS = 6371.2 # km 40 | START_TIME = 0.0 # MJD2000 / 2000-01-01T00:00:00Z 41 | END_TIME = 10958. # MJD2000 / 2030-01-01T00:00:00Z 42 | 43 | 44 | def generate_test_data(file_out): 45 | """ Generate test dataset. """ 46 | tmp = array(list(product(range(-90, 91, 5), range(-180, 181, 10)))) 47 | 48 | lat, lon = tmp[..., 0], tmp[..., 1] 49 | rad = uniform(EARTH_RADIUS, 2*EARTH_RADIUS, lat.shape) 50 | time_mjd2000 = uniform(START_TIME, END_TIME, lat.shape) 51 | time_decimal_year = vectorize(mjd2000_to_decimal_year)(time_mjd2000) 52 | 53 | qdlat, qdlon, f11, f12, f21, f22, f__ = eval_qdlatlon_with_base_vectors( 54 | lat, lon, rad, time_decimal_year 55 | ) 56 | mlt = eval_mlt(qdlon, time_mjd2000) 57 | sol_lat, sol_lon = eval_subsol(time_mjd2000) 58 | 59 | header = [ 60 | "MJD2000", "DecimalYear", "Latitude", "Longitude", "Radius", 61 | "QDLatitude", "QDLongitude", "F11", "F12", "F21", "F22", "F", 62 | "MagneticLocalTime", "SubsolarLatitude", "SubsolarLongitude", 63 | ] 64 | records = zip( 65 | time_mjd2000, time_decimal_year, lat, lon, rad, 66 | qdlat, qdlon, f11, f12, f21, f22, f__, 67 | mlt, sol_lat, sol_lon 68 | ) 69 | 70 | file_out.write("\t".join(header) + "\r\n") 71 | for record in records: 72 | file_out.write("\t".join(f"{value:.14e}" for value in record) + "\r\n") 73 | 74 | 75 | if __name__ == "__main__": 76 | generate_test_data(sys.stdout) 77 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/data/generate_solar_position_test_data.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Solar position - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | #pylint: disable=missing-docstring 29 | 30 | import sys 31 | from itertools import product 32 | from numpy import array 33 | from numpy.random import uniform 34 | from eoxmagmod.solar_position import sunpos 35 | 36 | EARTH_RADIUS = 6371.2 # km 37 | START_TIME = -10957.0 # MJD2000 / 1970-01-01T00:00:00Z 38 | END_TIME = 10958.0 # MJD2000 / 2030-01-01T00:00:00Z 39 | 40 | 41 | def generate_test_data(file_out): 42 | """ Generate test dataset. """ 43 | tmp = array(list(product(range(-90, 91, 5), range(-180, 181, 10)))) 44 | lat, lon = tmp[..., 0], tmp[..., 1] 45 | rad = uniform(0*EARTH_RADIUS, 2*EARTH_RADIUS, lat.shape) 46 | time_mjd2000 = uniform(START_TIME, END_TIME, lat.shape) 47 | decl, rasc, lha, azim, znth = sunpos(time_mjd2000, lat, lon, rad) 48 | 49 | header = [ 50 | "MJD2000", "Latitude", "Longitude", "Radius", 51 | "Declination", "RightAscension", "HourAngle", "Azimuth", "Zenith", 52 | ] 53 | records = zip(time_mjd2000, lat, lon, rad, decl, rasc, lha, azim, znth) 54 | 55 | file_out.write("\t".join(header) + "\r\n") 56 | for record in records: 57 | file_out.write("\t".join(f"{value:.14e}" for value in record) + "\r\n") 58 | 59 | 60 | if __name__ == "__main__": 61 | generate_test_data(sys.stdout) 62 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/data/mma_external.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Spherical Harmonic Expansion - Geomagnetic Model - test dataset 4 | # 5 | # External Spherical Harmonic Coefficients - SWARM MMA 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from numpy import zeros 30 | 31 | DEGREE = 2 32 | TIME = 2016.99965847 33 | 34 | DATA = [ 35 | (1, 0, 30.7117594582906754), 36 | (1, 1, 0.741232780798051327), 37 | (1, -1, -8.95006273357913607), 38 | (2, 0, 0.968979330373007097), 39 | (2, 1, 1.42731572806433937), 40 | (2, -1, 7.0650510135243545), 41 | ] 42 | 43 | COEF_Q = zeros(((DEGREE + 1)*(DEGREE + 2))//2) 44 | COEF_S = zeros(((DEGREE + 1)*(DEGREE + 2))//2) 45 | 46 | for n, m, coeff in DATA: 47 | if m >= 0: 48 | COEF_Q[(n*(n + 1))//2 + m] = coeff 49 | else: 50 | COEF_S[(n*(n + 1))//2 - m] = coeff 51 | 52 | del DATA, n, m, coeff, zeros 53 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/data/mma_internal.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Spherical Harmonic Expansion - Geomagnetic Model - test dataset 4 | # 5 | # External Spherical Harmonic Coefficients - SWARM MMA 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | 29 | from numpy import zeros 30 | 31 | DEGREE = 3 32 | TIME = 2016.99965847 33 | 34 | DATA = [ 35 | (1, 0, 6.37661120235457446), 36 | (1, 1, -0.295167002837779691), 37 | (1, -1, -3.5069934604844577), 38 | (2, 0, -0.666513682337298352), 39 | (2, 1, 1.11417106500435548), 40 | (2, -1, 2.65809245405974481), 41 | (2, 2, -0.413685898767694404), 42 | (2, -2, 0.638934950391676026), 43 | (3, 0, 2.26438999698106125), 44 | (3, 1, 0.931464083813372312), 45 | (3, -1, 0.473701177575287014), 46 | (3, 2, 0.382569838536052365), 47 | (3, -2, 0.822668017809991881), 48 | (3, 3, -0.233587111584525969), 49 | (3, -3, -0.0149047101935679896) 50 | ] 51 | 52 | COEF_G = zeros(((DEGREE + 1)*(DEGREE + 2))//2) 53 | COEF_H = zeros(((DEGREE + 1)*(DEGREE + 2))//2) 54 | 55 | for n, m, coeff in DATA: 56 | if m >= 0: 57 | COEF_G[(n*(n + 1))//2 + m] = coeff 58 | else: 59 | COEF_H[(n*(n + 1))//2 - m] = coeff 60 | 61 | del DATA, n, m, coeff, zeros 62 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/data/update_quasi_dipole_test_data.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Quasi-Dipole coordinates - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | #pylint: disable=missing-docstring 29 | 30 | import sys 31 | from numpy import array 32 | from eoxmagmod import mjd2000_to_decimal_year 33 | from eoxmagmod.quasi_dipole_coordinates import ( 34 | eval_mlt, eval_subsol, eval_qdlatlon_with_base_vectors, 35 | ) 36 | 37 | 38 | def load_coordinates(file_in): 39 | 40 | _ = next(file_in) 41 | 42 | times, lats, lons, rads = [], [], [], [] 43 | 44 | for line in file_in: 45 | time, _, lat, lon, rad = [float(v) for v in line.split()[:5]] 46 | times.append(time) 47 | lats.append(lat) 48 | lons.append(lon) 49 | rads.append(rad) 50 | 51 | return array(times), array(lats), array(lons), array(rads) 52 | 53 | 54 | def update_test_data(file_out, time_mjd2000, lat, lon, rad): 55 | """ Generate test dataset. """ 56 | time_decimal_year = mjd2000_to_decimal_year(time_mjd2000) 57 | 58 | qdlat, qdlon, f11, f12, f21, f22, f__ = eval_qdlatlon_with_base_vectors( 59 | lat, lon, rad, time_decimal_year 60 | ) 61 | mlt = eval_mlt(qdlon, time_mjd2000) 62 | sol_lat, sol_lon = eval_subsol(time_mjd2000) 63 | 64 | header = [ 65 | "MJD2000", "DecimalYear", "Latitude", "Longitude", "Radius", 66 | "QDLatitude", "QDLongitude", "F11", "F12", "F21", "F22", "F", 67 | "MagneticLocalTime", "SubsolarLatitude", "SubsolarLongitude", 68 | ] 69 | records = zip( 70 | time_mjd2000, time_decimal_year, lat, lon, rad, 71 | qdlat, qdlon, f11, f12, f21, f22, f__, 72 | mlt, sol_lat, sol_lon 73 | ) 74 | 75 | file_out.write("\t".join(header) + "\n") 76 | for record in records: 77 | file_out.write("\t".join(f"{value:.14e}" for value in record) + "\n") 78 | 79 | 80 | def main(filename): 81 | with open(filename, encoding="ascii") as file_in: 82 | time, lat, lon, rad = load_coordinates(file_in) 83 | update_test_data(sys.stdout, time, lat, lon, rad) 84 | 85 | 86 | if __name__ == "__main__": 87 | main(sys.argv[1]) 88 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/pymm_bisect.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Bi-section interval search - tests 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2022 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | # pylint: disable=missing-docstring, invalid-name, too-few-public-methods 29 | 30 | from unittest import TestCase, main 31 | from numpy import searchsorted, nan, inf, linspace, isnan, asarray 32 | from numpy.random import random 33 | from numpy.testing import assert_equal 34 | from eoxmagmod._pymm import ( 35 | BISECT_SIDE_LEFT, BISECT_SIDE_RIGHT, bisect, 36 | ) 37 | 38 | 39 | class BisectTestMixIn: 40 | 41 | def call_bisect(self, intervals, points): 42 | raise NotImplementedError 43 | 44 | def call_bisect_ref(self, intervals, points): 45 | raise NotImplementedError 46 | 47 | def _test_bisect(self, intervals, points): 48 | assert_equal( 49 | self.call_bisect(intervals, points), 50 | self.call_bisect_ref(intervals, points) 51 | ) 52 | 53 | def test_special_values(self): 54 | self._test_bisect([0, 1], [nan, -inf, inf]) 55 | 56 | def test_dimension_too_low(self): 57 | with self.assertRaises(ValueError): 58 | self.call_bisect(1, []) 59 | 60 | def test_dimension_too_high(self): 61 | with self.assertRaises(ValueError): 62 | self.call_bisect([[1, 2], [3, 4]], []) 63 | 64 | def test_border_values(self): 65 | self._test_bisect([1, 3, 5], [0, 1, 2, 3, 4, 5, 6]) 66 | 67 | def test_zero_interval_values(self): 68 | self._test_bisect([1, 3, 3, 5], [0, 1, 2, 3, 4, 5, 6]) 69 | 70 | def test_array_0d(self): 71 | self._test_bisect(linspace(0.1, 0.9, 5), random(())) 72 | 73 | def test_array_1d(self): 74 | self._test_bisect(linspace(0.1, 0.9, 5), random((5,))) 75 | 76 | def test_array_2d(self): 77 | self._test_bisect(linspace(0.1, 0.9, 5), random((2,3))) 78 | 79 | #------------------------------------------------------------------------------- 80 | 81 | class TestBisectLeft(TestCase, BisectTestMixIn): 82 | def call_bisect(self, intervals, points): 83 | return bisect(intervals, points, side=BISECT_SIDE_LEFT) 84 | 85 | def call_bisect_ref(self, intervals, points): 86 | points = asarray(points) 87 | idx = asarray(searchsorted(intervals, points, side="left") - 1) 88 | idx[isnan(points)] = -1 89 | return idx 90 | 91 | 92 | class TestBisectRight(TestCase, BisectTestMixIn): 93 | def call_bisect(self, intervals, points): 94 | return bisect(intervals, points, side=BISECT_SIDE_RIGHT) 95 | 96 | def call_bisect_ref(self, intervals, points): 97 | points = asarray(points) 98 | idx = asarray(searchsorted(intervals, points, side="right") - 1) 99 | idx[isnan(points)] = -1 100 | return idx 101 | 102 | 103 | class TestBisectDefault(TestBisectLeft): 104 | def call_bisect(self, intervals, points): 105 | return bisect(intervals, points) 106 | 107 | #------------------------------------------------------------------------------- 108 | 109 | if __name__ == "__main__": 110 | main() 111 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/pymm_loncossin.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Spherical Harmonic Expansion - Geomagnetic Model - tests 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | # pylint: disable=missing-docstring 29 | 30 | from unittest import TestCase, main 31 | from numpy import empty, asarray, linspace, pi, sin, cos 32 | from numpy.testing import assert_allclose 33 | from eoxmagmod._pymm import loncossin 34 | 35 | 36 | class TestLongitudialSinCosSeries(TestCase): 37 | 38 | @classmethod 39 | def reference(cls, lons, degree): 40 | """ Evaluate sin/cos series. """ 41 | lons = asarray(lons) 42 | size = lons.size 43 | shape = lons.shape 44 | 45 | nterms = degree + 1 46 | 47 | lons = lons.ravel() 48 | cossins = empty((size, nterms, 2)) 49 | 50 | for i in range(size): 51 | cossins[i, :, :] = cls.reference_scalar(lons[i], degree) 52 | 53 | return cossins.reshape((*shape, nterms, 2)) 54 | 55 | @staticmethod 56 | def reference_scalar(value, degree): 57 | """ Reference implementation. """ 58 | value *= pi / 180.0 59 | return asarray([[cos(i*value), sin(i*value)] for i in range(degree + 1)]) 60 | 61 | @staticmethod 62 | def _assert_allclose(result0, result1): 63 | assert_allclose(result0, result1, atol=1e-14) 64 | 65 | def _test_loncossin_scalar(self, *args, **kwargs): 66 | degree = 6 67 | longitudes = [float(v) for v in range(-180, 180, 30)] 68 | 69 | for longitude in longitudes: 70 | self._assert_allclose( 71 | loncossin(longitude, degree, *args, **kwargs), 72 | self.reference(longitude, degree) 73 | ) 74 | 75 | def _test_loncossin_array(self, *args, **kwargs): 76 | degree = 6 77 | longitudes = linspace(-180, 180, 91).reshape((13, 7)) 78 | self._assert_allclose( 79 | loncossin(longitudes, degree, *args, **kwargs), 80 | self.reference(longitudes, degree) 81 | ) 82 | 83 | def test_invalid_degree(self): 84 | self.assertRaises(ValueError, loncossin, 0.0, -1) 85 | 86 | def test_loncossin_zero_degree(self): 87 | self._assert_allclose(loncossin(0, 0), [[1.0, 0.0]]) 88 | self._assert_allclose(loncossin(90, 0, False), [[1.0, 0.0]]) 89 | self._assert_allclose(loncossin(-90, 0, True), [[1.0, 0.0]]) 90 | 91 | def test_loncossin_default(self): 92 | self._test_loncossin_scalar() 93 | 94 | def test_loncossin_fast(self): 95 | self._test_loncossin_scalar(True) 96 | 97 | def test_loncossin_slow(self): 98 | self._test_loncossin_scalar(False) 99 | 100 | def test_loncossin_array_default(self): 101 | self._test_loncossin_array() 102 | 103 | def test_loncossin_array_fast(self): 104 | self._test_loncossin_array(True) 105 | 106 | def test_loncossin_array_slow(self): 107 | self._test_loncossin_array(False) 108 | 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/quasi_dipole_coordinates.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Quasi-Dipole coordinates - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | #pylint: disable=missing-docstring 29 | 30 | from unittest import TestCase, main 31 | from numpy import array 32 | from numpy.testing import assert_allclose 33 | from eoxmagmod.quasi_dipole_coordinates import ( 34 | eval_qdlatlon, eval_mlt, eval_subsol, 35 | eval_qdlatlon_with_base_vectors, 36 | ) 37 | from eoxmagmod.tests.data import QUASI_DIPOLE_TEST_DATA 38 | 39 | 40 | def load_test_data(filename): 41 | """ Load test data from a tab-separated values file. """ 42 | def _load_test_data(file_in): 43 | header = next(file_in).strip().split("\t") 44 | records = array([ 45 | [float(v) for v in line.strip().split("\t")] for line in file_in 46 | ]) 47 | return { 48 | variable: records[..., idx] for idx, variable in enumerate(header) 49 | } 50 | 51 | with open(filename, encoding="ascii") as file_in: 52 | return _load_test_data(file_in) 53 | 54 | 55 | class TestQuasiDipoleCoordinates(TestCase): 56 | test_data = load_test_data(QUASI_DIPOLE_TEST_DATA) 57 | 58 | def test_eval_qdlatlon(self): 59 | qdlat, qdlon = eval_qdlatlon( 60 | self.test_data["Latitude"], 61 | self.test_data["Longitude"], 62 | self.test_data["Radius"], 63 | self.test_data["DecimalYear"], 64 | ) 65 | assert_allclose(qdlat, self.test_data["QDLatitude"], atol=1e-8) 66 | assert_allclose(qdlon, self.test_data["QDLongitude"], atol=1e-8) 67 | 68 | def test_eval_qdlatlon_with_base_vectors(self): 69 | qdlat, qdlon, f11, f12, f21, f22, f__ = eval_qdlatlon_with_base_vectors( 70 | self.test_data["Latitude"], 71 | self.test_data["Longitude"], 72 | self.test_data["Radius"], 73 | self.test_data["DecimalYear"], 74 | ) 75 | assert_allclose(qdlat, self.test_data["QDLatitude"], atol=1e-8) 76 | assert_allclose(qdlon, self.test_data["QDLongitude"], atol=1e-8) 77 | assert_allclose(f11, self.test_data["F11"], atol=1e-8) 78 | assert_allclose(f12, self.test_data["F12"], atol=1e-8) 79 | assert_allclose(f21, self.test_data["F21"], atol=1e-8) 80 | assert_allclose(f22, self.test_data["F22"], atol=1e-8) 81 | assert_allclose(f__, self.test_data["F"], atol=1e-8) 82 | 83 | def test_eval_mlt(self): 84 | mlt = eval_mlt(self.test_data["QDLongitude"], self.test_data["MJD2000"]) 85 | assert_allclose(mlt, self.test_data["MagneticLocalTime"], atol=1e-8) 86 | 87 | def test_eval_subsol(self): 88 | sollat, sollon = eval_subsol(self.test_data["MJD2000"]) 89 | assert_allclose(sollat, self.test_data["SubsolarLatitude"], atol=1e-8) 90 | assert_allclose(sollon, self.test_data["SubsolarLongitude"], atol=1e-8) 91 | 92 | 93 | if __name__ == "__main__": 94 | main() 95 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/tests/solar_position.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Solar position - test 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2018 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | #pylint: disable=missing-docstring 29 | 30 | from unittest import TestCase, main 31 | from numpy import array 32 | from numpy.testing import assert_allclose 33 | from eoxmagmod.quasi_dipole_coordinates import eval_subsol 34 | from eoxmagmod.solar_position import sunpos, sunpos_original 35 | from eoxmagmod.tests.data import SUN_POSITION_TEST_DATA 36 | 37 | 38 | def load_test_data(filename): 39 | """ Load test data from a tab-separated values file. """ 40 | def _load_test_data(file_in): 41 | header = next(file_in).strip().split("\t") 42 | records = array([ 43 | [float(v) for v in line.strip().split("\t")] for line in file_in 44 | ]) 45 | return { 46 | variable: records[..., idx] for idx, variable in enumerate(header) 47 | } 48 | 49 | with open(filename, encoding="ascii") as file_in: 50 | return _load_test_data(file_in) 51 | 52 | 53 | class TestSunPosition(TestCase): 54 | test_data = load_test_data(SUN_POSITION_TEST_DATA) 55 | 56 | def test_sunpos(self): 57 | decl, rasc, lha, azimuth, zenith = sunpos( 58 | self.test_data["MJD2000"], 59 | self.test_data["Latitude"], 60 | self.test_data["Longitude"], 61 | self.test_data["Radius"], 62 | ) 63 | assert_allclose(decl, self.test_data["Declination"], atol=1e-8) 64 | assert_allclose(rasc, self.test_data["RightAscension"], atol=1e-8) 65 | assert_allclose(lha, self.test_data["HourAngle"], atol=1e-8) 66 | assert_allclose(azimuth, self.test_data["Azimuth"], atol=1e-8) 67 | assert_allclose(zenith, self.test_data["Zenith"], atol=1e-8) 68 | 69 | def test_sunpos_original(self): 70 | # Note: the original code does handle correctly only days 71 | # between 2000-03-01 and 2400-02-29. 72 | mask = ( 73 | (self.test_data["MJD2000"] >= 60.0) & 74 | (self.test_data["MJD2000"] < 146157.0) 75 | ) 76 | decl, rasc, lha, azimuth, zenith = sunpos_original( 77 | self.test_data["MJD2000"][mask], 78 | self.test_data["Latitude"][mask], 79 | self.test_data["Longitude"][mask], 80 | self.test_data["Radius"][mask], 81 | pres=0.0, 82 | ) 83 | assert_allclose(decl, self.test_data["Declination"][mask], atol=1e-8) 84 | assert_allclose(rasc, self.test_data["RightAscension"][mask], atol=1e-8) 85 | assert_allclose(lha, self.test_data["HourAngle"][mask], atol=1e-8) 86 | assert_allclose(azimuth, self.test_data["Azimuth"][mask], atol=1e-8) 87 | assert_allclose(zenith, self.test_data["Zenith"][mask], atol=1e-8) 88 | 89 | 90 | def test_sunpos_subsol_comparison(self): 91 | # The two different Solar models are expected to be aligned. 92 | decl, _, gha, _, _, = sunpos(self.test_data["MJD2000"], 0, 0, 0) 93 | sollat, sollon = eval_subsol(self.test_data["MJD2000"]) 94 | assert_allclose(decl, sollat, atol=4e-1) 95 | assert_allclose(-gha, sollon, atol=2e-1) 96 | 97 | 98 | if __name__ == "__main__": 99 | main() 100 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/time_util.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Time utilities 4 | # 5 | # Project: VirES 6 | # Author: Martin Paces 7 | # 8 | #------------------------------------------------------------------------------- 9 | # Copyright (C) 2018 EOX IT Services GmbH 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy 12 | # of this software and associated documentation files (the "Software"), to deal 13 | # in the Software without restriction, including without limitation the rights 14 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | # copies of the Software, and to permit persons to whom the Software is 16 | # furnished to do so, subject to the following conditions: 17 | # 18 | # The above copyright notice and this permission notice shall be included in 19 | # all 20 | # copies of this Software or works derived from this Software. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | # THE SOFTWARE. 29 | #------------------------------------------------------------------------------- 30 | 31 | from datetime import datetime 32 | from numpy import asarray, floor 33 | from ._pytimeconv import ( 34 | decimal_year_to_mjd2000, 35 | mjd2000_to_decimal_year, 36 | mjd2000_to_year_fraction, 37 | ) 38 | 39 | __all__ = [ 40 | "decimal_year_to_mjd2000", 41 | "mjd2000_to_decimal_year", 42 | "mjd2000_to_year_fraction", 43 | "mjd2000_to_decimal_year_simple", 44 | "mjd2000_to_year_fraction_simple", 45 | "decimal_year_to_mjd2000_simple", 46 | "datetime_to_decimal_year", 47 | ] 48 | 49 | 50 | def mjd2000_to_decimal_year_simple(mjd2000): 51 | """ Convert Modified Julian Date since 2000 to (Julian) decimal year 52 | using the simplified conversion method: 53 | decimal_year = 2000.0 + mjd2000/365.25 54 | """ 55 | return 2000.0 + asarray(mjd2000) / 365.25 56 | 57 | 58 | def mjd2000_to_year_fraction_simple(mjd2000): 59 | """ Convert Modified Julian Date since 2000 to (Julian) year fraction 60 | using the simplified conversion method: 61 | year_fraction = mjd2000/365.25 - floor(mjd2000/365.25) 62 | """ 63 | mjy2000 = asarray(mjd2000) / 365.25 64 | return mjy2000 - floor(mjy2000) 65 | 66 | 67 | def decimal_year_to_mjd2000_simple(decimal_year): 68 | """ Convert (Julian) decimal year to Modified Julian Date since 2000 69 | using the simplified conversion method: 70 | mjd2000 = (decimal_year - 2000.0) * 365.25 71 | """ 72 | return (asarray(decimal_year) - 2000.0) * 365.25 73 | 74 | 75 | def datetime_to_decimal_year(time): 76 | """ Convert time given by a `datetime.datetime` object to a decimal year 77 | value. 78 | """ 79 | if not isinstance(time, datetime): 80 | raise TypeError("The input must be a datetime object.") 81 | 82 | year_start = datetime(year=time.year, month=1, day=1) 83 | next_year_start = datetime(year=time.year+1, month=1, day=1) 84 | 85 | year_elapsed = (time - year_start).total_seconds() 86 | year_total = (next_year_start - year_start).total_seconds() 87 | 88 | return time.year + year_elapsed / year_total 89 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/util.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # utilities utilities 4 | # 5 | # Author: Martin Paces 6 | # 7 | #------------------------------------------------------------------------------- 8 | # Copyright (C) 2017 EOX IT Services GmbH 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies of this Software or works derived from this Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | #------------------------------------------------------------------------------- 28 | # pylint: disable=no-name-in-module 29 | 30 | from numpy import sqrt, asarray 31 | from ._pymm import ( 32 | GEODETIC_ABOVE_WGS84, GEOCENTRIC_SPHERICAL, GEOCENTRIC_CARTESIAN, 33 | convert, vrot_sph2geod, vrot_sph2cart, vrot_cart2sph, 34 | ) 35 | 36 | SPHERICAL_COORD_TYPES = (GEODETIC_ABOVE_WGS84, GEOCENTRIC_SPHERICAL) 37 | 38 | __all__ = [ 39 | "vrotate", 40 | "vnorm", 41 | "vincdecnorm", 42 | ] 43 | 44 | 45 | def vrotate(arr, coord_in, coord_out, coord_type_in, coord_type_out): 46 | """ Rotate vectors from one coordinate system to another. 47 | Input: 48 | arr - array of the source vectors 49 | coord_in - source coordinates 50 | coord_out - destination coordinates 51 | coord_type_in - source coordinate system type 52 | coord_type_out - destination coordinate system type 53 | Output: 54 | arr_out - array of the rotated vectors 55 | """ 56 | # pylint: disable=too-many-return-statements 57 | if coord_type_in == coord_type_out: 58 | return arr 59 | 60 | coord_out = None if coord_out is None else asarray(coord_out) 61 | coord_in = None if coord_in is None else asarray(coord_in) 62 | 63 | if coord_type_in == GEODETIC_ABOVE_WGS84: 64 | if coord_type_out == GEODETIC_ABOVE_WGS84: 65 | return arr 66 | if coord_type_out == GEOCENTRIC_SPHERICAL: 67 | return vrot_sph2geod(arr, coord_out[..., 0] - coord_in[..., 0]) 68 | if coord_type_out == GEOCENTRIC_CARTESIAN: 69 | return vrot_sph2cart(arr, coord_in[..., 0], coord_in[..., 1]) 70 | 71 | elif coord_type_in == GEOCENTRIC_SPHERICAL: 72 | if coord_type_out == GEODETIC_ABOVE_WGS84: 73 | return vrot_sph2geod(arr, coord_out[..., 0] - coord_in[..., 0]) 74 | if coord_type_out == GEOCENTRIC_CARTESIAN: 75 | return vrot_sph2cart(arr, coord_in[..., 0], coord_in[..., 1]) 76 | 77 | elif coord_type_in == GEOCENTRIC_CARTESIAN: 78 | if coord_type_out in SPHERICAL_COORD_TYPES: 79 | return vrot_cart2sph(arr, coord_out[..., 0], coord_out[..., 1]) 80 | 81 | raise ValueError("Unsupported coordinate system type!") 82 | 83 | 84 | def vnorm(arr): 85 | """ Calculate norm for each vector form an input array of vectors: 86 | |v| = sqrt(dot(v, v)) 87 | """ 88 | arr = asarray(arr) 89 | return sqrt((arr*arr).sum(axis=arr.ndim-1)) 90 | 91 | 92 | def vincdecnorm(arr): 93 | """ Convenience function converting magnetic field vector in the local 94 | northing, easting, elevation (up-pointing) geodetic frame to inclination 95 | in degrees (from -90 to 90, positive values point down), declination 96 | in degrees (from -180 to 180, 0 points north, clockwise), and vector 97 | intensity (vector norm). 98 | This conversion is equivalent to convert of a Cartesian vector to 99 | spherical coordinates. 100 | The function accepts either one vector or an array of vectors and returns 101 | a tuple of the inclination, declination and norm. 102 | """ 103 | tmp = convert(arr, GEOCENTRIC_CARTESIAN, GEOCENTRIC_SPHERICAL) 104 | return -tmp[..., 0], tmp[..., 1], tmp[..., 2] 105 | -------------------------------------------------------------------------------- /eoxmagmod/eoxmagmod/version.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * version definition 4 | * 5 | * Author: Martin Paces 6 | * 7 | *----------------------------------------------------------------------------- 8 | * Copyright (C) 2018-2024 EOX IT Services GmbH 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies of this Software or works derived from this Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | *----------------------------------------------------------------------------- 28 | */ 29 | 30 | #ifndef VERSION 31 | #define VERSION "0.14.0" 32 | #endif 33 | -------------------------------------------------------------------------------- /eoxmagmod/setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_rpm] 2 | requires=python spacepy numpy qdipole >= 0.2.0 3 | provides=EOxMagMod eoxmagmod python-eoxmagmod 4 | release=1 5 | -------------------------------------------------------------------------------- /eoxmagmod/setup.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # Distutils Setup Script 4 | # 5 | # Project: Earth magnetic field in Python. 6 | # Author: Martin Paces 7 | # 8 | #------------------------------------------------------------------------------- 9 | # Copyright (C) 2014 EOX IT Services GmbH 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy 12 | # of this software and associated documentation files (the "Software"), to deal 13 | # in the Software without restriction, including without limitation the rights 14 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | # copies of the Software, and to permit persons to whom the Software is 16 | # furnished to do so, subject to the following conditions: 17 | # 18 | # The above copyright notice and this permission notice shall be included in 19 | # all 20 | # copies of this Software or works derived from this Software. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | # THE SOFTWARE. 29 | #------------------------------------------------------------------------------- 30 | 31 | import sys 32 | from os.path import join 33 | from distutils.core import setup 34 | from distutils.extension import Extension 35 | import eoxmagmod 36 | 37 | 38 | COMMON_INCLUDE_DIRS = [ 39 | './eoxmagmod', 40 | './eoxmagmod/include', 41 | join(sys.prefix, 'include'), 42 | ] 43 | 44 | try: 45 | import numpy 46 | COMMON_INCLUDE_DIRS.append(numpy.get_include()) 47 | except ImportError: 48 | pass 49 | 50 | setup( 51 | name="eoxmagmod", 52 | description="Earth magnetic field utilities.", 53 | author="Martin Paces", 54 | author_email="martin.paces@eox.at", 55 | classifiers=[ 56 | 'Development Status :: 5 - Production/Stable', 57 | 'Intended Audience :: Science/Research', 58 | 'License :: OSI Approved :: MIT License', 59 | 'Operating System :: POSIX', 60 | 'Operating System :: POSIX :: Linux', 61 | 'Programming Language :: Python', 62 | 'Programming Language :: Python :: 3.6', 63 | 'Programming Language :: Python :: 3.7', 64 | 'Programming Language :: Python :: 3.8', 65 | 'Programming Language :: Python :: 3.9', 66 | 'Programming Language :: Python :: 3.10', 67 | 'Programming Language :: Python :: Implementation :: CPython', 68 | 'Topic :: Scientific/Engineering :: Physics', 69 | 'Topic :: Utilities', 70 | ], 71 | install_requires=[ 72 | 'numpy>=1.13.0', 73 | 'spacepy', 74 | ], 75 | packages=[ 76 | 'eoxmagmod', 77 | 'eoxmagmod.data', 78 | 'eoxmagmod.tests', 79 | 'eoxmagmod.tests.data', 80 | 'eoxmagmod.magnetic_model', 81 | 'eoxmagmod.magnetic_model.tests', 82 | 'eoxmagmod.magnetic_model.tests.data', 83 | ], 84 | license='EOX licence (MIT style)', 85 | version=eoxmagmod.__version__, 86 | package_data={ 87 | 'eoxmagmod': [ 88 | 'data/*', 89 | 'tests/data/*.tsv', 90 | 'magnetic_model/tests/data/*.txt', 91 | 'magnetic_model/tests/data/*.cdf', 92 | ], 93 | }, 94 | ext_modules=[ 95 | Extension( 96 | 'eoxmagmod._pymm', 97 | sources=[ 98 | 'eoxmagmod/pymm.c', 99 | ], 100 | libraries=[], 101 | library_dirs=[], 102 | include_dirs=COMMON_INCLUDE_DIRS, 103 | ), 104 | Extension( 105 | 'eoxmagmod._pyqd', 106 | sources=[ 107 | 'eoxmagmod/pyqd.c', 108 | ], 109 | libraries=['qdipole'], 110 | library_dirs=[], 111 | include_dirs=COMMON_INCLUDE_DIRS, 112 | ), 113 | Extension( 114 | 'eoxmagmod._pysunpos', 115 | sources=[ 116 | 'eoxmagmod/pysunpos.c', 117 | ], 118 | libraries=[], 119 | library_dirs=[], 120 | include_dirs=COMMON_INCLUDE_DIRS, 121 | ), 122 | Extension( 123 | 'eoxmagmod._pytimeconv', 124 | sources=[ 125 | 'eoxmagmod/pytimeconv.c', 126 | ], 127 | libraries=[], 128 | library_dirs=[], 129 | include_dirs=COMMON_INCLUDE_DIRS, 130 | ), 131 | ] 132 | ) 133 | -------------------------------------------------------------------------------- /libcdf/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # install CDF library from sources 3 | # 4 | 5 | VERSION = 39_1 6 | DIRNAME = ./cdf$(VERSION)-dist/ 7 | FILENAME = ./cdf$(VERSION)-dist-cdf.tar.gz 8 | SOURCE_URL = https://spdf.gsfc.nasa.gov/pub/software/cdf/dist/cdf$(VERSION)/linux/cdf$(VERSION)-dist-cdf.tar.gz 9 | MD5CHECKSUM = 88fbb3f612ba802e5846a00bcae0632a 10 | INSTALLDIR ?= /usr/local/cdf 11 | 12 | all: build 13 | 14 | clean: 15 | -rm -fR $(DIRNAME) 16 | 17 | install: build test 18 | make -C $(DIRNAME) INSTALLDIR=$(INSTALLDIR) install 19 | 20 | test: build 21 | make -C $(DIRNAME) test 22 | 23 | build: $(DIRNAME) 24 | make -C $(DIRNAME) OS=linux ENV=gnu CURSES=no all 25 | 26 | $(DIRNAME): $(FILENAME) md5check 27 | tar -xzf $(FILENAME) 28 | 29 | md5check: $(FILENAME) 30 | echo $(MD5CHECKSUM) $(FILENAME) | md5sum -c 31 | 32 | $(FILENAME): 33 | curl $(SOURCE_URL) > $(FILENAME) 34 | -------------------------------------------------------------------------------- /libcdf/bld.bat: -------------------------------------------------------------------------------- 1 | :: 2 | :: CDF library conda build script 3 | :: 4 | 5 | :: TODO: windows build 6 | -------------------------------------------------------------------------------- /libcdf/build.sh: -------------------------------------------------------------------------------- 1 | # 2 | # CDF library conda build script 3 | # 4 | 5 | # TODO: add support for additional platforms 6 | 7 | error() { 8 | echo "ERROR: $*" 1>&2 9 | exit 1 10 | } 11 | 12 | if [ "$OSTYPE" = 'linux-gnu' ] 13 | then 14 | OS="linux" 15 | if [ "$ARCH" == '32' ] 16 | then 17 | ENV="gnu32" 18 | elif [ "$ARCH" == '64' ] 19 | then 20 | ENV="gnu" 21 | else 22 | error "Unsupported architecture $ARCH!" 23 | fi 24 | else 25 | error "Unsupported platform $OSTYPE!" 26 | fi 27 | 28 | BUILD_OPTIONS="SHARED=yes FORTRAN=no CURSES=no" 29 | make OS=$OS ENV=$ENV AR=$AR RANLIBcmd=$RANLIB LD_${OS}_${ENV}=$CC CC_${OS}_${ENV}=$CC FC_${OS}=$FC $BUILD_OPTIONS all 30 | make test 31 | make INSTALLDIR="$PREFIX" install 32 | -------------------------------------------------------------------------------- /libcdf/meta.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # CDF library conda build metadata 3 | # 4 | 5 | # NOTE: scipy is installed during the build to assure a compatible version 6 | # of the Fortran compiler and its run-time libraries. 7 | # conda is not able to resolve version conflicts if the scipy and libcdf 8 | # are compiled with different versions of the GFortran compiler. 9 | 10 | 11 | {% set version = "3.9.1" %} 12 | {% set file_version = "cdf39_1" %} 13 | 14 | package: 15 | name: "cdf" 16 | version: {{ version }} 17 | 18 | source: 19 | - url: https://spdf.gsfc.nasa.gov/pub/software/cdf/dist/{{ file_version }}/unix/{{ file_version }}-dist-cdf.tar.gz 20 | sha256: d548789117c52fcd4d08be5f432c86ae927e182d3876e800cd4ca98e5f7fa5e7 21 | 22 | requirements: 23 | build: 24 | - make 25 | - {{ compiler('c') }} 26 | - {{ compiler('fortran') }} 27 | - ncurses 28 | - scipy 29 | 30 | about: 31 | home: https://github.com/ESA-VirES/MagneticModel/ 32 | -------------------------------------------------------------------------------- /libcdf/post-link.sh: -------------------------------------------------------------------------------- 1 | conda env config vars set "CDF_LIB=$CONDA_PREFIX/lib" 2 | -------------------------------------------------------------------------------- /libcdf/pre-unlink.sh: -------------------------------------------------------------------------------- 1 | conda env config vars unset CDF_LIB 2 | -------------------------------------------------------------------------------- /qdipole/.gitignore: -------------------------------------------------------------------------------- 1 | config.status 2 | config.log 3 | Makefile 4 | *.cache 5 | *.o 6 | *.so 7 | *.lo 8 | *.mod 9 | *_test 10 | qdipole_info 11 | qdipole_conf.h 12 | -------------------------------------------------------------------------------- /qdipole/Makefile.in: -------------------------------------------------------------------------------- 1 | # paths set by autoconf 2 | prefix=@prefix@ 3 | exec_prefix=@exec_prefix@ 4 | PACKAGE_TARNAME=@PACKAGE_TARNAME@ 5 | CC=@CC@ 6 | FC=@FC@ 7 | LD=$(FC) 8 | RM_FILE=rm -fv 9 | RM_DIR=rm -rfv 10 | CFLAGS=@CFLAGS@ 11 | LDFLAGS=@LDFLAGS@ 12 | LIB_DIR=@libdir@ 13 | BIN_DIR=@bindir@ 14 | INC_DIR=@includedir@/${PACKAGE_TARNAME} 15 | DOC_DIR=@docdir@ 16 | DATA_DIR=@datadir@/${PACKAGE_TARNAME} 17 | INSTALL_LIB_DIR=${INSTALL_PREFIX}${LIB_DIR} 18 | INSTALL_BIN_DIR=${INSTALL_PREFIX}${BIN_DIR} 19 | INSTALL_INC_DIR=${INSTALL_PREFIX}${INC_DIR} 20 | INSTALL_DOC_DIR=${INSTALL_PREFIX}${DOC_DIR} 21 | INSTALL_DATA_DIR=${INSTALL_PREFIX}${DATA_DIR} 22 | LIB_TARGET=libqdipole.so 23 | BIN_TARGET=subsol_test qdlatlon_test mlt_test qdipole_info 24 | INC= 25 | HDR=cqdipole.h qdipole_conf.h 26 | OBJ=cqdipole.o apex.o apexsh.o makeapexsh.o eval_subsol.o eval_qdlatlon.o eval_mlt.o 27 | DOC=README 28 | DATA=apexsh_1980-2030.txt \ 29 | apexsh_1980-2025.txt \ 30 | apexsh_1980-2020.txt \ 31 | apexsh_1995-2015.txt \ 32 | test_result.qdlatlon.apexsh_1980-2030.txt \ 33 | test_result.qdlatlon.apexsh_1980-2025.txt \ 34 | test_result.qdlatlon.apexsh_1980-2020.txt \ 35 | test_result.qdlatlon.apexsh_1995-2015.txt \ 36 | test_result.mlt.txt \ 37 | test_result.subsol.txt 38 | MOD=alfbasismodule.mod apxshmodule.mod 39 | TEST=run_dipole_test 40 | 41 | %.o: %.c $(HDR) 42 | $(CC) -c -o $@ $< $(CFLAGS) 43 | 44 | %.o: %.f90 $(INC) 45 | $(FC) -c -o $@ $< $(CFLAGS) 46 | 47 | all: build 48 | 49 | build: $(LIB_TARGET) $(BIN_TARGET) 50 | 51 | install: $(LIB_TARGET) $(BIN_TARGET) $(HDR) $(DOC) $(DATA) $(TEST) 52 | mkdir -p $(INSTALL_LIB_DIR) 53 | install -c -m 0755 $(LIB_TARGET) $(INSTALL_LIB_DIR) 54 | [ "$(USER)" != "root" ] || ldconfig 55 | mkdir -p $(INSTALL_BIN_DIR) 56 | install -c -m 0755 $(BIN_TARGET) $(INSTALL_BIN_DIR) 57 | install -c -m 0755 $(TEST) $(INSTALL_BIN_DIR) 58 | mkdir -p $(INSTALL_INC_DIR) 59 | install -c -m 0664 $(HDR) $(INSTALL_INC_DIR) 60 | mkdir -p $(INSTALL_DOC_DIR) 61 | install -c -m 0664 $(DOC) $(INSTALL_DOC_DIR) 62 | mkdir -p $(INSTALL_DATA_DIR) 63 | install -c -m 0644 $(DATA) $(INSTALL_DATA_DIR) 64 | 65 | test: $(TEST) 66 | $(INSTALL_BIN_DIR)/$(TEST) $(INSTALL_BIN_DIR) $(INSTALL_DATA_DIR) 67 | 68 | clean: 69 | $(RM_FILE) $(OBJ) $(LIB_TARGET) $(BIN_TARGET) $(MOD) 70 | 71 | $(LIB_TARGET): $(OBJ) 72 | $(LD) -o $(LIB_TARGET) $(OBJ) -shared -lm $(LDFLAGS) 73 | 74 | qdipole_info: qdipole_info.c 75 | $(CC) -o qdipole_info qdipole_info.c -L./ $(LDFLAGS) 76 | 77 | qdlatlon_test: $(LIB_TARGET) qdlatlon_test.c 78 | $(CC) -o qdlatlon_test qdlatlon_test.c -lqdipole -L./ $(LDFLAGS) 79 | 80 | mlt_test: $(LIB_TARGET) mlt_test.c 81 | $(CC) -o mlt_test mlt_test.c -lqdipole -lm -L./ $(LDFLAGS) 82 | 83 | subsol_test: $(LIB_TARGET) subsol_test.c 84 | $(CC) -o subsol_test subsol_test.c -lqdipole -lm -L./ $(LDFLAGS) 85 | -------------------------------------------------------------------------------- /qdipole/README: -------------------------------------------------------------------------------- 1 | 2 | Quasi-Dipole apex coordinates evaluation. 3 | ========================================= 4 | 5 | This is the original Fortran code (kindly provided by Nils Olsen) compiled 6 | as a shared library. More details to be found in the commented Fortran code 7 | itself. 8 | 9 | The implementation of the C wrapper and preparation of the makefile was done by 10 | EOX IT Services GmbH, Vienna, Austria 11 | 12 | CHANGE LOG 13 | ---------- 14 | 15 | 2024-12-14 - 0.5.0 Update 16 | - increasing the file path size limit to 256 characters 17 | - removing the old, no longer used, make_apex subroutine 18 | - removing need for QD-coefficient file for MLT calculation 19 | - minor corrections 20 | 21 | 2024-12-09 - 0.4.0 Update 22 | - update of the IGRF coefficients 23 | 24 | 2020-01-17 - 0.3.0 Update 25 | - update of the IGRF coefficients 26 | 27 | 2016-06-09 - 0.2.0 Update 28 | - review and re-factoring of the C interface 29 | - separating MLT calculation from the DQ latitude/longitude evaluation 30 | - new interface to the sub-solar point calculation 31 | 32 | 2016-02-27 - 0.1.1 Update 33 | - employing GNU/Autoconf for build configuration 34 | - improving the build process 35 | 36 | 2015-01-09 - 0.1.0 Initial version 37 | - implementation of the C the wrapper of the Fortran subroutines 38 | 39 | 40 | INSTALLATION 41 | ------------ 42 | 43 | Step 1 - configure: 44 | 45 | $ ./configure --prefix=/usr/ 46 | 47 | For more detail see 48 | 49 | $ ./configure --help 50 | 51 | 52 | Step 2 - build the binaries: 53 | 54 | $ make build 55 | 56 | 57 | Step 3 - install the software: 58 | 59 | $ sudo make install 60 | 61 | 62 | TESTING 63 | ------- 64 | 65 | To test the build package run either 66 | 67 | $ sudo make test 68 | 69 | or 70 | 71 | $ ./qdlatlon_test apexsh_1995-2015.txt | diff - test_result.qdlatlon.apexsh_1995-2015.txt 72 | $ ./qdlatlon_test apexsh_1980-2020.txt | diff - test_result.qdlatlon.apexsh_1980-2020.txt 73 | $ ./qdlatlon_test apexsh_1980-2025.txt | diff - test_result.qdlatlon.apexsh_1980-2025.txt 74 | $ ./qdlatlon_test apexsh_1980-2030.txt | diff - test_result.qdlatlon.apexsh_1980-2030.txt 75 | $ ./mlt_test | diff - test_result.mlt.txt 76 | $ ./subsol_test | diff - test_result.subsol.txt 77 | 78 | 79 | RELEASE VERSION UPDATE 80 | ---------------------- 81 | 82 | Change version in the configure.ac file. 83 | 84 | Make sure the autoconf and automake are installed and run 85 | 86 | $ autoreconf -i 87 | 88 | and commit the new generated configuration script. 89 | -------------------------------------------------------------------------------- /qdipole/bld.bat: -------------------------------------------------------------------------------- 1 | :: 2 | :: qdipole conda build script 3 | :: 4 | 5 | :: TODO: windows build 6 | -------------------------------------------------------------------------------- /qdipole/build.sh: -------------------------------------------------------------------------------- 1 | # 2 | # qdipole conda build script 3 | # 4 | cd "$SRC_DIR/qdipole" 5 | ./configure --prefix="$PREFIX" "LDFLAGS=$LDFLAGS_USED" 6 | make build 7 | make install 8 | -------------------------------------------------------------------------------- /qdipole/configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([qdipole],[0.6.0]) 2 | 3 | # get the C compiler 4 | AC_PROG_CC 5 | 6 | # get the Fortran compiler 7 | AC_PROG_FC 8 | 9 | # check if the compiler supports -fPIC 10 | echo 'void f(){}' > conftest.c 11 | if test -z "`${CC-cc} $CFLAGS -fPIC -c conftest.c 2>&1`"; then 12 | CFLAGS="$CFLAGS -fPIC" 13 | fi 14 | 15 | AC_CONFIG_FILES([Makefile]) 16 | AC_CONFIG_HEADERS([qdipole_conf.h]) 17 | AC_OUTPUT 18 | -------------------------------------------------------------------------------- /qdipole/cqdipole.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Quasi-Dipole Magnetic Coordinates - C wrapper around the Fortran code 4 | * 5 | * Project: EOX Magnetic Model 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2015-2024 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | 31 | #ifndef CQDIPOLE_H 32 | #define CQDIPOLE_H 33 | 34 | /** 35 | * @brief Get filename size limit. 36 | * @returns maximum length of the filename path 37 | */ 38 | 39 | size_t get_qdipole_max_fname_lenght(); 40 | 41 | 42 | /** 43 | * @brief Get library version. 44 | * @returns String containing version of the library 45 | */ 46 | 47 | const char* get_qdipole_version(); 48 | 49 | 50 | /** 51 | * @brief Batch evaluation of the Quasi-Dipole apex coordinates with the vector base 52 | * @returns 0 on success, non-zero on failure 53 | */ 54 | 55 | int c_eval_qdlatlonvb( 56 | double* qdlat, double* qdlon, double* f11, double* f12, double* f21, 57 | double* f22, double* f, const double* time_dy, const double* gcrad, 58 | const double* gclat, const double* gclon, const int n_data, 59 | const char* coeff_file 60 | ); 61 | 62 | 63 | /** 64 | * @brief Batch evaluation of the Quasi-Dipole apex coordinates 65 | * @returns 0 on success, non-zero on failure 66 | */ 67 | 68 | int c_eval_qdlatlon( 69 | double* qdlat, double* qdlon, const double* time_dy, const double* gcrad, 70 | const double* gclat, const double* gclon, const int n_data, 71 | const char* coeff_file 72 | ); 73 | 74 | 75 | /** 76 | * @brief Batch evaluation of the Magnetic Local Time 77 | * @returns 0 on success, non-zero on failure 78 | */ 79 | 80 | int c_eval_mlt( 81 | double *t_mlt, const double *qdlon, const double *t_mjd2k, const int n_data 82 | ); 83 | 84 | 85 | /** 86 | * @brief Batch evaluation of the sub-solar point latitude and longitude 87 | * @returns 0 on success, non-zero on failure 88 | */ 89 | 90 | int c_eval_subsol( 91 | double* sbsllat, double* sbsllon, const double* time_mjd2k, const int n_data 92 | ); 93 | 94 | #endif /*CQDIPOLE_H*/ 95 | -------------------------------------------------------------------------------- /qdipole/eval_mlt.f90: -------------------------------------------------------------------------------- 1 | !*********************************************************************** 2 | ! 3 | ! File Name: eval_mlt.f90 4 | ! Authors: Martin Paces 5 | ! 6 | ! Date: 14-12-2024 7 | ! Version: 2.0 8 | ! Description: 9 | ! Evaluate magnetic local time. 10 | ! Wrapper around the low-level subroutines. 11 | ! For more details see MAGLOCTM in apex.f90 12 | ! 13 | ! Latest changes: 14 | ! 09-06-2016: First version of the subroutine. 15 | ! 14-12-2024: Review + removal loading of QD coefficients. 16 | ! 17 | !*********************************************************************** 18 | ! 19 | ! eval_mlt 20 | ! 21 | ! Evaluate Magnetic Local Time for given QD-longitude and MJD2000 time 22 | ! 23 | ! call eval_mlt(t_mlt, qdlon, t_mjd2k, n_data) 24 | ! 25 | ! INPUT ARGUMENTS 26 | ! qdlon array of Quasi-Dipole longitudes 27 | ! t_mjd2k array of MJD2000 times 28 | ! n_data array size 29 | ! 30 | ! OUTPUT ARGUMENTS 31 | ! t_mlt array of Magnetic Local Time values. 32 | ! 33 | !*********************************************************************** 34 | 35 | subroutine eval_mlt(t_mlt, qdlon, t_mjd2k, n_data) 36 | 37 | implicit none 38 | 39 | real*8 qdlon(*), t_mjd2k(*), t_mlt(*) 40 | real*8 t_0, sec 41 | real*4 t_mlt4, qdlon4 42 | real*4 dp_colat, dp_elon, dp_vp 43 | real*4 sbsllat, sbsllon, ssec 44 | real*4 epoch, epoch_limit, epoch_old 45 | integer n_data, i 46 | integer iyr, imon, iday, idoy, ihour, imin 47 | parameter(epoch_limit = 1./365.25) 48 | 49 | epoch_old = -9999.9 50 | 51 | do i = 1, n_data 52 | ! The epoch is approximated by neglecting the leap years 53 | epoch = t_mjd2k(i) / 365.25 + 2000 54 | ! Reload coefficients and evaluate the dipole pole location. 55 | ! If time has changed by more than epoch_limit 56 | if (abs(epoch - epoch_old) .gt. epoch_limit) then 57 | call cofrm (epoch) 58 | call dypol(dp_colat, dp_elon, dp_vp) 59 | epoch_old = epoch 60 | endif 61 | 62 | ! 1. Ccalculate civil date 63 | call tmjd(iyr, imon, iday, ihour, imin, sec, t_mjd2k(i), -1) 64 | ! 2. Calculate mjd = nint(t_0) of first day of year 'iyr' 65 | call tmjd(iyr, 1, 1, 0, 0, 0.d0, t_0, 1) 66 | ! 3. Get day of year (doy) 67 | idoy = int(t_mjd2k(i)) - nint(t_0) + 1 68 | ! 4. Evaluate the sub-solar lat/lon 69 | ssec = sec 70 | call subsol(iyr, idoy, ihour, imin, ssec, sbsllat, sbsllon) 71 | ! 5. Evaluate magnetic local time 72 | qdlon4 = qdlon(i) 73 | call magloctm(qdlon4, sbsllat, sbsllon, dp_colat, dp_elon, t_mlt4) 74 | t_mlt(i) = t_mlt4 75 | enddo 76 | 77 | return 78 | end subroutine eval_mlt 79 | -------------------------------------------------------------------------------- /qdipole/eval_subsol.f90: -------------------------------------------------------------------------------- 1 | !*********************************************************************** 2 | ! 3 | ! File Name: eval_subsol.f90 4 | ! Authors: Martin Paces 5 | ! 6 | ! Date: 14-12-2024 7 | ! Version: 1.0 8 | ! Description: 9 | ! Evaluate sub-solar geographic latitude and longitude. 10 | ! Wrapper around the low-level subroutines. 11 | ! For more details see SUBSOL in apex.f90 12 | ! Note: 13 | ! The SUBSOL subroutine expects the time in the Universal Time (UT) 14 | ! while the MJD2000 is aligned to UTC. The offset caused by 15 | ! the accumulated leap seconds is not compensated which leads to 16 | ! an approx. 0.15 dg longitude offset for the currently accumulated 17 | ! 36 leap seconds. 18 | ! 19 | ! References: n/a 20 | ! 21 | ! Latest changes: 22 | ! 13-05-2016: First version of the subroutine. 23 | ! 14-12-2024: Correcting documentation. 24 | ! 25 | !*********************************************************************** 26 | ! 27 | ! eval_subsol 28 | ! 29 | ! Evaluate sub-solar geographic latitude and longitude for the given 30 | ! MJD2000 values. 31 | ! 32 | ! call eval_subsol(sbslat, sbslon, t_mjd2k, n_data) 33 | ! 34 | ! INPUT ARGUMENTS 35 | ! t_mjd2k vector of times (decimal year) 36 | ! n_data number of points, i.e., vector size 37 | ! 38 | ! OUTPUT ARGUMENTS 39 | ! sbsllat array of sub-solar latitudes 40 | ! sbsllon array of sub-solar longitudes 41 | ! 42 | !*********************************************************************** 43 | 44 | subroutine eval_subsol(sbsllat, sbsllon, t_mjd2k, n_data) 45 | implicit none 46 | 47 | real*8 sbsllat(*), sbsllon(*), t_mjd2k(*) 48 | real*8 t_0, sec 49 | real*4 lat, lon, ssec 50 | integer n_data, i 51 | integer iyr, imon, iday, idoy, ihour, imin 52 | 53 | do i = 1, n_data 54 | ! 1. Calculate civil date 55 | call tmjd(iyr, imon, iday, ihour, imin, sec, t_mjd2k(i), -1) 56 | ! 2. Calculate mjd = nint(t_0) of first day of year 'iyr' 57 | call tmjd(iyr, 1, 1, 0, 0, 0.d0, t_0, 1) 58 | ! 3. Get day of year (doy) 59 | idoy = int(t_mjd2k(i)) - nint(t_0) + 1 60 | ! 4. Evaluate the sub-solar lat/lon 61 | ssec = sec 62 | call subsol(iyr, idoy, ihour, imin, ssec, lat, lon) 63 | sbsllat(i) = lat 64 | sbsllon(i) = lon 65 | enddo 66 | 67 | return 68 | end subroutine eval_subsol 69 | -------------------------------------------------------------------------------- /qdipole/meta.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # qdipole conda build metadata 3 | # 4 | 5 | # NOTE: scipy is installed during the build to assure a compatible version 6 | # of the Fortran compiler and its run-time libraries. 7 | # conda is not able to resolve version conflicts if the scipy and qdipole 8 | # are compiled with different versions of the GFortran compiler. 9 | 10 | {% set version = "0.6.0" %} 11 | 12 | package: 13 | name: "qdipole" 14 | version: {{ version }} 15 | 16 | source: 17 | git_rev: {{ "qdipole-" + version }} 18 | git_url: "https://github.com/ESA-VirES/MagneticModel" 19 | 20 | requirements: 21 | build: 22 | - make 23 | - {{ compiler('c') }} 24 | - {{ compiler('fortran') }} 25 | - scipy 26 | 27 | test: 28 | commands: 29 | - ls -l $PREFIX/share/qdipole/ 30 | - run_dipole_test "" "$PREFIX/share/qdipole" 31 | 32 | about: 33 | home: https://github.com/ESA-VirES/MagneticModel/ 34 | -------------------------------------------------------------------------------- /qdipole/mlt_test.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Magnetic Local Time - Fortran wrapper test 4 | * 5 | * Project: EOX Magnetic Model 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2016-2024 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | 31 | #include 32 | #include "cqdipole.h" 33 | 34 | #define N 12 35 | 36 | int main(int argc, char* argv[]) 37 | { 38 | int status; 39 | int i; 40 | int n_data = N; 41 | double time[N] = { 42 | 0.0, 0.0, 0.0, 0.0, // MJD200 origin 2000-01-01T00:00:00 43 | 5923.00, 5923.25, 5923.50, 5923.75, 5924.00, // spring equinox 2016 44 | 7122.50, 8949.00 , 10775.50 // decimal years 2019.5, 2024.5, 2029.5 45 | }; 46 | double qdlon[N] = { 47 | 0.0, 90.0, 180.0, -90.0, 48 | 0.0, 0.0, 0.0, 0.0, 0.0, 49 | 0.0, 0.0, 0.0 50 | }; 51 | double mlt[N]; 52 | 53 | for (i = 0; i < N; ++i) 54 | { 55 | mlt[i] = 0.0; 56 | } 57 | 58 | status = c_eval_mlt(mlt, qdlon, time, n_data); 59 | 60 | if (status) { 61 | fprintf(stderr, "ERROR: Call to c_eval_mlt() failed with an error! error_code = %d\n", status); 62 | return 1; 63 | } 64 | 65 | for (i = 0; i < N; ++i) 66 | { 67 | printf("#%i\n", i+1); 68 | printf(" time: %g\n", time[i]); 69 | printf(" qdlon: %g\n", qdlon[i]); 70 | printf(" mlt: %g\n", mlt[i]); 71 | } 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /qdipole/qdipole_conf.h.in: -------------------------------------------------------------------------------- 1 | /* qdipole_conf.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* Define to the address where bug reports for this package should be sent. */ 4 | #undef PACKAGE_BUGREPORT 5 | 6 | /* Define to the full name of this package. */ 7 | #undef PACKAGE_NAME 8 | 9 | /* Define to the full name and version of this package. */ 10 | #undef PACKAGE_STRING 11 | 12 | /* Define to the one symbol short name of this package. */ 13 | #undef PACKAGE_TARNAME 14 | 15 | /* Define to the home page for this package. */ 16 | #undef PACKAGE_URL 17 | 18 | /* Define to the version of this package. */ 19 | #undef PACKAGE_VERSION 20 | -------------------------------------------------------------------------------- /qdipole/qdipole_info.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Command printing info about the qdipole library. 4 | * 5 | * Project: EOX Magnetic Model 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2024 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | #include 31 | #include "qdipole_conf.h" 32 | 33 | int main(int argc, char* argv[]) 34 | { 35 | printf("%s-%s\n", PACKAGE_NAME, PACKAGE_VERSION); 36 | 37 | return 0; 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /qdipole/qdlatlon_test.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Quasi-Dipole Magnetic Coordinates - Fortran wrapper test 4 | * 5 | * Project: EOX Magnetic Model 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2016-2024 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | #include 31 | #include 32 | #include "cqdipole.h" 33 | 34 | #define N 6 35 | 36 | const char *basename(char const *path) 37 | { 38 | const char *position = strrchr(path, '/'); 39 | if (position != NULL) 40 | { 41 | return position + 1; 42 | } 43 | return path; 44 | } 45 | 46 | 47 | int main(int argc, char* argv[]) 48 | { 49 | int status; 50 | int i; 51 | int n_data = N; 52 | double time[N] = { 2012.5, 2013.5, 2014.5, 2019.5, 2024.5, 2029.5 }; 53 | double gclat[N] = { 0.0, 30.0, 75.0, -70.0, -30.0, 80.0 }; 54 | double gclon[N] = { 0.0, 45.0, -90.0, 90.0, -45.0, 120.0 }; 55 | double gcrad[N] = { 7000.0, 6371.0, 6371.0, 6371.0, 6371.0, 7371.0 }; 56 | double qdlon1[N], qdlat1[N]; 57 | double qdlon2[N], qdlat2[N]; 58 | double f11[N], f12[N], f21[N], f22[N], f[N]; 59 | const char *fname = "apexsh_1980-2030.txt"; 60 | 61 | if (argc > 1) 62 | { 63 | fname = argv[1]; 64 | } 65 | printf("Using: %s\n", basename(fname)); 66 | 67 | if (strlen(fname) > get_qdipole_max_fname_lenght()) 68 | { 69 | fprintf(stderr, "ERROR: Filename is too long and exceeds the maximum allowed %d bytes! filename = %s\n", get_qdipole_max_fname_lenght(), fname); 70 | return 1; 71 | } 72 | 73 | for (i = 0; i < N; ++i) 74 | { 75 | qdlon1[i] = 0.0; 76 | qdlat1[i] = 0.0; 77 | qdlon2[i] = 0.0; 78 | qdlat2[i] = 0.0; 79 | f11[i] = 0.0; 80 | f12[i] = 0.0; 81 | f21[i] = 0.0; 82 | f22[i] = 0.0; 83 | f[i] = 0.0; 84 | } 85 | 86 | status = c_eval_qdlatlon(qdlat1, qdlon1, time, gcrad, gclat, gclon, n_data, fname); 87 | 88 | if (status) { 89 | fprintf(stderr, "ERROR: Call to c_eval_qdlatlon() failed with an error! error_code = %d\n", status); 90 | return 1; 91 | } 92 | 93 | status = c_eval_qdlatlonvb(qdlat2, qdlon2, f11, f12, f21, f22, f, 94 | time, gcrad, gclat, gclon, n_data, fname); 95 | 96 | if (status) { 97 | fprintf(stderr, "ERROR: Call to c_eval_qdlatlonvb() failed with an error! error_code = %d\n", status); 98 | return 1; 99 | } 100 | 101 | for (i = 0; i < N; ++i) 102 | { 103 | printf("#%i\n", i+1); 104 | printf(" time: %g\n", time[i]); 105 | printf(" gclat: %g\n", gclat[i]); 106 | printf(" gclon: %g\n", gclon[i]); 107 | printf(" gcrad: %g\n", gcrad[i]); 108 | printf(" qdlon: %g %g\n", qdlon1[i], qdlon2[i]); 109 | printf(" qdlat: %g %g\n", qdlat1[i], qdlat2[i]); 110 | printf(" f11: %g", f11[i]); 111 | printf(" f12: %g\n", f12[i]); 112 | printf(" f21: %g", f21[i]); 113 | printf(" f22: %g\n", f22[i]); 114 | printf(" f: %g\n", f[i]); 115 | } 116 | 117 | return 0; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /qdipole/run_dipole_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Execute qdipole tests. 4 | 5 | BIN_PATH="$1${1:+/}" 6 | DATA_PATH="$2${2:+/}" 7 | 8 | run_test() { 9 | EXPECTED_RESULT="$1" 10 | shift 11 | echo -n "$@" " " 12 | TEST_OUTPUT="$( "$@" 2>&1 )" 13 | if [ "$?" != "0" ] 14 | then 15 | echo "FAILED" 16 | echo $TEST_OUTPUT 17 | return 1 18 | fi 19 | 20 | TEST_OUTPUT_DIFF="$( echo "$TEST_OUTPUT" | diff - "$EXPECTED_RESULT" )" 21 | if [ "$?" != "0" -o -n "$TEST_OUTPUT_DIFF" ] 22 | then 23 | echo "FAILED" 24 | echo $TEST_OUTPUT_DIFF 25 | return 1 26 | fi 27 | echo "OK" 28 | return 0 29 | } 30 | 31 | STATUS="0" 32 | qdipole_info ; STATUS=$((STATUS + $?)) 33 | run_test "${DATA_PATH}test_result.qdlatlon.apexsh_1995-2015.txt" "${BIN_PATH}qdlatlon_test" "${DATA_PATH}apexsh_1995-2015.txt" ; STATUS=$((STATUS + $?)) 34 | run_test "${DATA_PATH}test_result.qdlatlon.apexsh_1980-2020.txt" "${BIN_PATH}qdlatlon_test" "${DATA_PATH}apexsh_1980-2020.txt" ; STATUS=$((STATUS + $?)) 35 | run_test "${DATA_PATH}test_result.qdlatlon.apexsh_1980-2025.txt" "${BIN_PATH}qdlatlon_test" "${DATA_PATH}apexsh_1980-2025.txt" ; STATUS=$((STATUS + $?)) 36 | run_test "${DATA_PATH}test_result.qdlatlon.apexsh_1980-2030.txt" "${BIN_PATH}qdlatlon_test" "${DATA_PATH}apexsh_1980-2030.txt" ; STATUS=$((STATUS + $?)) 37 | run_test "${DATA_PATH}test_result.mlt.txt" "${BIN_PATH}mlt_test" ; STATUS=$((STATUS + $?)) 38 | run_test "${DATA_PATH}test_result.subsol.txt" "${BIN_PATH}subsol_test" ; STATUS=$((STATUS + $?)) 39 | 40 | exit $STATUS 41 | -------------------------------------------------------------------------------- /qdipole/subsol_test.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * 3 | * Sub-solar point - Fortran wrapper test 4 | * 5 | * Project: EOX Magnetic Model 6 | * Author: Martin Paces 7 | * 8 | *----------------------------------------------------------------------------- 9 | * Copyright (C) 2016-2024 EOX IT Services GmbH 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies of this Software or works derived from this Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | *----------------------------------------------------------------------------- 29 | */ 30 | #include 31 | #include 32 | #include "cqdipole.h" 33 | 34 | #define N 5 35 | 36 | int main(int argc, char* argv[]) 37 | { 38 | int status; 39 | int i; 40 | int n_data = N; 41 | double time[N] = { 42 | 0.0, // MJD200 origin 2000-01-01T00:00:00 43 | 902.558333333, // summer solstice 2002 44 | 5013.86388889, // autumn equinox 2013 45 | 5923.1875, // spring equinox 2016 46 | 6929.93263889, // winter solstice 2018 47 | }; 48 | double sbsllat[N], sbsllon[N]; 49 | 50 | for (i = 0; i < N; ++i) 51 | { 52 | sbsllon[i] = 0.0; 53 | sbsllat[i] = 0.0; 54 | } 55 | 56 | status = c_eval_subsol(sbsllat, sbsllon, time, n_data); 57 | 58 | if (status) { 59 | fprintf(stderr, "ERROR: Call to c_eval_subsol() failed with an error! error_code = %d\n", status); 60 | return 1; 61 | } 62 | 63 | for (i = 0; i < N; ++i) 64 | { 65 | printf("#%i\n", i+1); 66 | printf(" time: %.12g\n", time[i]); 67 | printf(" sbsllat: %g\n", sbsllat[i]); 68 | printf(" sbsllon: %g (%g)\n", sbsllon[i], 360.0 * (0.5 - fmod(time[i], 1.0))); 69 | } 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /qdipole/test_result.mlt.txt: -------------------------------------------------------------------------------- 1 | #1 2 | time: 0 3 | qdlon: 0 4 | mlt: 18.8709 5 | #2 6 | time: 0 7 | qdlon: 90 8 | mlt: 0.870874 9 | #3 10 | time: 0 11 | qdlon: 180 12 | mlt: 6.87087 13 | #4 14 | time: 0 15 | qdlon: -90 16 | mlt: 12.8709 17 | #5 18 | time: 5923 19 | qdlon: 0 20 | mlt: 19.0185 21 | #6 22 | time: 5923.25 23 | qdlon: 0 24 | mlt: 1.04839 25 | #7 26 | time: 5923.5 27 | qdlon: 0 28 | mlt: 7.02049 29 | #8 30 | time: 5923.75 31 | qdlon: 0 32 | mlt: 13.0516 33 | #9 34 | time: 5924 35 | qdlon: 0 36 | mlt: 19.0276 37 | #10 38 | time: 7122.5 39 | qdlon: 0 40 | mlt: 6.81346 41 | #11 42 | time: 8949 43 | qdlon: 0 44 | mlt: 19.3137 45 | #12 46 | time: 10775.5 47 | qdlon: 0 48 | mlt: 6.80662 49 | -------------------------------------------------------------------------------- /qdipole/test_result.qdlatlon.apexsh_1980-2020.txt: -------------------------------------------------------------------------------- 1 | Using: apexsh_1980-2020.txt 2 | #1 3 | time: 2012.5 4 | gclat: 0 5 | gclon: 0 6 | gcrad: 7000 7 | qdlon: 73.317 73.317 8 | qdlat: -11.3833 -11.3833 9 | f11: 1.01455 f12: 0.0168991 10 | f21: -0.128075 f22: 1.04557 11 | f: 1.06295 12 | #2 13 | time: 2013.5 14 | gclat: 30 15 | gclon: 45 16 | gcrad: 6371 17 | qdlon: 117.694 117.694 18 | qdlat: 24.9158 24.9158 19 | f11: 1.08449 f12: -0.0756728 20 | f21: 0.0234143 f22: 1.05048 21 | f: 1.141 22 | #3 23 | time: 2014.5 24 | gclat: 75 25 | gclon: -90 26 | gcrad: 6371 27 | qdlon: -23.1453 -23.1453 28 | qdlat: 82.9296 82.9296 29 | f11: 0.88294 f12: -0.120099 30 | f21: 0.0951455 f22: 1.09265 31 | f: 0.976173 32 | #4 33 | time: 2019.5 34 | gclat: -70 35 | gclon: 90 36 | gcrad: 6371 37 | qdlon: 110.052 110.052 38 | qdlat: -78.8155 -78.8155 39 | f11: 0.549429 f12: 0.772584 40 | f21: -0.803698 f22: 0.631868 41 | f: 0.96809 42 | #5 43 | time: 2024.5 44 | gclat: -30 45 | gclon: -45 46 | gcrad: 6371 47 | qdlon: 20.2038 20.2038 48 | qdlat: -26.2983 -26.2983 49 | f11: 0.736223 f12: 0.493971 50 | f21: -0.123455 f22: 0.837654 51 | f: 0.677684 52 | #6 53 | time: 2029.5 54 | gclat: 80 55 | gclon: 120 56 | gcrad: 7371 57 | qdlon: -172.31 -172.31 58 | qdlat: 74.6035 74.6035 59 | f11: 0.959916 f12: -0.0288698 60 | f21: 0.176277 f22: 1.05196 61 | f: 1.01488 62 | -------------------------------------------------------------------------------- /qdipole/test_result.qdlatlon.apexsh_1980-2025.txt: -------------------------------------------------------------------------------- 1 | Using: apexsh_1980-2025.txt 2 | #1 3 | time: 2012.5 4 | gclat: 0 5 | gclon: 0 6 | gcrad: 7000 7 | qdlon: 73.3068 73.3068 8 | qdlat: -11.3908 -11.3908 9 | f11: 1.01491 f12: 0.0167322 10 | f21: -0.128022 f22: 1.04556 11 | f: 1.06329 12 | #2 13 | time: 2013.5 14 | gclat: 30 15 | gclon: 45 16 | gcrad: 6371 17 | qdlon: 117.684 117.684 18 | qdlat: 24.9176 24.9176 19 | f11: 1.08445 f12: -0.0753763 20 | f21: 0.0234453 f22: 1.05047 21 | f: 1.14095 22 | #3 23 | time: 2014.5 24 | gclat: 75 25 | gclon: -90 26 | gcrad: 6371 27 | qdlon: -23.1734 -23.1734 28 | qdlat: 82.927 82.927 29 | f11: 0.882938 f12: -0.120419 30 | f21: 0.0954025 f22: 1.09268 31 | f: 0.976256 32 | #4 33 | time: 2019.5 34 | gclat: -70 35 | gclon: 90 36 | gcrad: 6371 37 | qdlon: 109.493 109.493 38 | qdlat: -78.7569 -78.7569 39 | f11: 0.547374 f12: 0.774579 40 | f21: -0.803315 f22: 0.62962 41 | f: 0.966869 42 | #5 43 | time: 2024.5 44 | gclat: -30 45 | gclon: -45 46 | gcrad: 6371 47 | qdlon: 19.5469 19.5469 48 | qdlat: -27.2051 -27.2051 49 | f11: 0.721856 f12: 0.493437 50 | f21: -0.116659 f22: 0.852217 51 | f: 0.672742 52 | #6 53 | time: 2029.5 54 | gclat: 80 55 | gclon: 120 56 | gcrad: 7371 57 | qdlon: -171.876 -171.876 58 | qdlat: 74.8739 74.8739 59 | f11: 0.966962 f12: -0.0101352 60 | f21: 0.160785 f22: 1.05139 61 | f: 1.01828 62 | -------------------------------------------------------------------------------- /qdipole/test_result.qdlatlon.apexsh_1980-2030.txt: -------------------------------------------------------------------------------- 1 | Using: apexsh_1980-2030.txt 2 | #1 3 | time: 2012.5 4 | gclat: 0 5 | gclon: 0 6 | gcrad: 7000 7 | qdlon: 73.3068 73.3068 8 | qdlat: -11.3908 -11.3908 9 | f11: 1.01491 f12: 0.0167322 10 | f21: -0.128022 f22: 1.04556 11 | f: 1.06329 12 | #2 13 | time: 2013.5 14 | gclat: 30 15 | gclon: 45 16 | gcrad: 6371 17 | qdlon: 117.684 117.684 18 | qdlat: 24.9176 24.9176 19 | f11: 1.08445 f12: -0.0753763 20 | f21: 0.0234453 f22: 1.05047 21 | f: 1.14095 22 | #3 23 | time: 2014.5 24 | gclat: 75 25 | gclon: -90 26 | gcrad: 6371 27 | qdlon: -23.1734 -23.1734 28 | qdlat: 82.927 82.927 29 | f11: 0.882938 f12: -0.120419 30 | f21: 0.0954025 f22: 1.09268 31 | f: 0.976256 32 | #4 33 | time: 2019.5 34 | gclat: -70 35 | gclon: 90 36 | gcrad: 6371 37 | qdlon: 109.48 109.48 38 | qdlat: -78.7575 -78.7575 39 | f11: 0.547209 f12: 0.774709 40 | f21: -0.803423 f22: 0.62946 41 | f: 0.966865 42 | #5 43 | time: 2024.5 44 | gclat: -30 45 | gclon: -45 46 | gcrad: 6371 47 | qdlon: 19.6803 19.6803 48 | qdlat: -27.1452 -27.1452 49 | f11: 0.720545 f12: 0.492262 50 | f21: -0.116558 f22: 0.853462 51 | f: 0.672335 52 | #6 53 | time: 2029.5 54 | gclat: 80 55 | gclon: 120 56 | gcrad: 7371 57 | qdlon: -171.468 -171.468 58 | qdlat: 75.0718 75.0718 59 | f11: 0.970351 f12: -0.00990997 60 | f21: 0.15733 f22: 1.05213 61 | f: 1.02249 62 | -------------------------------------------------------------------------------- /qdipole/test_result.qdlatlon.apexsh_1995-2015.txt: -------------------------------------------------------------------------------- 1 | Using: apexsh_1995-2015.txt 2 | #1 3 | time: 2012.5 4 | gclat: 0 5 | gclon: 0 6 | gcrad: 7000 7 | qdlon: 73.3154 73.3154 8 | qdlat: -11.3924 -11.3924 9 | f11: 1.01429 f12: 0.0171216 10 | f21: -0.128365 f22: 1.04588 11 | f: 1.06303 12 | #2 13 | time: 2013.5 14 | gclat: 30 15 | gclon: 45 16 | gcrad: 6371 17 | qdlon: 117.682 117.682 18 | qdlat: 24.9283 24.9283 19 | f11: 1.08376 f12: -0.0776905 20 | f21: 0.022971 f22: 1.05076 21 | f: 1.14056 22 | #3 23 | time: 2014.5 24 | gclat: 75 25 | gclon: -90 26 | gcrad: 6371 27 | qdlon: -23.1177 -23.1177 28 | qdlat: 82.9501 82.9501 29 | f11: 0.883835 f12: -0.119204 30 | f21: 0.0951553 f22: 1.09321 31 | f: 0.977559 32 | #4 33 | time: 2019.5 34 | gclat: -70 35 | gclon: 90 36 | gcrad: 6371 37 | qdlon: 109.156 109.156 38 | qdlat: -78.715 -78.715 39 | f11: 0.542328 f12: 0.777111 40 | f21: -0.803689 f22: 0.623699 41 | f: 0.962805 42 | #5 43 | time: 2024.5 44 | gclat: -30 45 | gclon: -45 46 | gcrad: 6371 47 | qdlon: 19.8958 19.8958 48 | qdlat: -25.4518 -25.4518 49 | f11: 0.74967 f12: 0.49321 50 | f21: -0.125847 f22: 0.829886 51 | f: 0.684209 52 | #6 53 | time: 2029.5 54 | gclat: 80 55 | gclon: 120 56 | gcrad: 7371 57 | qdlon: -172.944 -172.944 58 | qdlat: 74.2387 74.2387 59 | f11: 0.957321 f12: -0.0302862 60 | f21: 0.177791 f22: 1.05327 61 | f: 1.0137 62 | -------------------------------------------------------------------------------- /qdipole/test_result.subsol.txt: -------------------------------------------------------------------------------- 1 | #1 2 | time: 0 3 | sbsllat: -23.0728 4 | sbsllon: -179.234 (180) 5 | #2 6 | time: 902.558333333 7 | sbsllat: 23.4386 8 | sbsllon: -20.5654 (-21) 9 | #3 10 | time: 5013.86388889 11 | sbsllat: 0.00402407 12 | sbsllon: -132.869 (-131) 13 | #4 14 | time: 5923.1875 15 | sbsllat: -0.00329402 16 | sbsllon: 114.36 (112.5) 17 | #5 18 | time: 6929.93263889 19 | sbsllat: -23.4362 20 | sbsllon: -156.184 (-155.75) 21 | --------------------------------------------------------------------------------