Skip to content
Snippets Groups Projects
Commit 5a36759a authored by Stefan Mauerberger's avatar Stefan Mauerberger
Browse files

Merge branch 'master' into docs

parents f73bde43 de9d962b
No related branches found
No related tags found
1 merge request!4Docs
......@@ -7,4 +7,4 @@ pages:
paths:
- public
only:
- docs
- master
......@@ -12,9 +12,9 @@ $ pymagglobal --list-models
```
to get a list of these default models or go to [pymagglobal/dat](https://gitext.gfz-potsdam.de/arthus/pymagglobal/-/tree/master/pymagglobal/dat) for further information. Using
```console
$ pymagglobal --input <path/to/your_model> ... your.model
$ pymagglobal ... <path/to/your_model>
```
you can use pymagglobal to evaluate your own models, if they come in a similar format. `<path/to/your_model>` specifies the path to your model file and `your.model` the name of your model. You can download additional models [here](TODO: PROVIDE MODELS VIA FTP) and use them as above.
you can use pymagglobal to evaluate your own models, if they come in a similar format. `<path/to/your_model>` specifies the path to your model and is given instead of the name an included model. You can download additional models [here](TODO: PROVIDE MODELS VIA FTP) and use them as above.
Once installed, pymagglobal can be imported and its routines used to access the models from inside your own python code.
......@@ -40,7 +40,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
TODO
## Documentation
From the command line, you can use pymagglobal to get various results from the models. For example,
Check out the extended documention [here](http://arthus.gitext.gfz-potsdam.de/pymagglobal). From the command line, you can use pymagglobal to get various results from the models. For example,
```console
$ pymagglobal dipole gufm1
```
......@@ -50,7 +50,7 @@ $ pymagglobal command --options model
```
where `command` specifies the quantity you want to get from pymagglobal and `model` is the respective model. You can use
```console
$ pymagglobal -i <path/to/your_model> command --options your.model
$ pymagglobal command --options <path/to/your_model>
```
to parse your own model, if it is in a format similar to gufm1. Use
```console
......
......@@ -6,9 +6,9 @@ Package Documentation
.. todo:: Fix URL for the package i.e. `pymagglobal/__init__.py`
Pymagglobal
***********
.. automodule:: pymagglobal.pymagglobal
Core
****
.. automodule:: pymagglobal.core
:members:
......@@ -26,6 +26,6 @@ Argument Parser & Main Program
Commands
********
.. automodule:: pymagglobal.commands
.. automodule:: pymagglobal._commands
:members:
......@@ -27,8 +27,8 @@
__all__ = ['master_curve', 'dipole_series', 'file2splines', 'field',
'models', 'coefficients', 'utils']
from .pymagglobal import master_curve, dipole_series, file2splines, \
from pymagglobal.core import master_curve, dipole_series, file2splines, \
field, coefficients, models
from . import utils
from pymagglobal import utils
__version__ = '0.0.2'
......@@ -29,8 +29,8 @@
import sys
import argparse
from . import commands
from .pymagglobal import models
from pymagglobal import _commands
from pymagglobal.core import models
class ListModelsAction(argparse.Action):
......@@ -59,12 +59,12 @@ def argument_parser():
parser.add_argument('-h', '--help', action='help',
default=argparse.SUPPRESS,
help=f'show this message and exit. each of the '
f'commands above has its own --help, so use e.g. '
f'_commands above has its own --help, so use e.g. '
f'master --help to get information on how to create a '
f'master curve')
parser.add_argument('--list-models', help='list available models and exit',
action=ListModelsAction)
# parser for arguments that are shared between all commands
# parser for arguments that are shared between all _commands
base_parser = argparse.ArgumentParser(add_help=False)
base_parser.add_argument('--no-show', action='store_true',
help='do not show the results')
......@@ -77,7 +77,7 @@ def argument_parser():
f'ages before the year 1950 in ka (kiloyears), '
f'i.e. the epoch 10 refers to the year -8050 '
f'without the flag')
# parser for arguments that are shared between time series commands
# parser for arguments that are shared between time series _commands
series_base_parser = argparse.ArgumentParser(add_help=False)
res_default = 401
......@@ -115,7 +115,7 @@ def argument_parser():
series_base_parser],
epilog=mst_epilog)
mst_parser.set_defaults(func=commands.master_curve)
mst_parser.set_defaults(func=_commands.master_curve)
mst_parser.add_argument('lat', type=float,
help=f'the latitude at which to create the master '
f'curve')
......@@ -140,7 +140,7 @@ def argument_parser():
parents=[base_parser,
series_base_parser],
epilog=dip_epilog)
dip_parser.set_defaults(func=commands.dipole_series)
dip_parser.set_defaults(func=_commands.dipole_series)
# parser for coefficients
cff_ex_ofn = '../dat_cff.txt'
......@@ -165,7 +165,7 @@ def argument_parser():
f'coefficients for a specific epoch',
parents=[base_parser],
epilog=cff_epilog)
cff_parser.set_defaults(func=commands.coefficients)
cff_parser.set_defaults(func=_commands.coefficients)
cff_parser.add_argument('epoch', type=float, help='the epoch for which '
'the coeffients are returned')
......@@ -183,7 +183,7 @@ def argument_parser():
f'unconventional',
parents=[base_parser],
epilog=map_epilog)
map_parser.set_defaults(func=commands.maps)
map_parser.set_defaults(func=_commands.maps)
map_parser.add_argument('--type', choices=['dif', 'nez'], type=str,
help=f'the type of the resulting field. '
f'may be either dif for declination, inclination '
......@@ -199,29 +199,34 @@ def argument_parser():
f'{map_res_default:d}',
default=res_default)
# the model to be used
parser.add_argument('model', type=str, help='a model')
parser.add_argument('-i', '--input', metavar='<path/to/input>', type=str,
help=f'input path to a model file. may be used to '
f'evaluate your own model. if given, this path will '
f'be used instead over the included files')
parser.add_argument('model', type=str, help=f'one of the included models.'
f'use pymagglobal --list-models to get a list of '
f'the included models or pass a <path/to/your_model> '
f'to parse your own model')
return parser
def main():
'''the main command of pymagglobal. it handles command line arguments and
calls the respective functions from the commands module.
calls the respective functions from the _commands module.
'''
parser = argument_parser()
# parse the command line arguments
args = parser.parse_args()
# check whether model and input are consistent
if args.input is None and args.model in models:
if args.model in models:
args.input = models[args.model]
else:
args.input = args.model
args.model = 'custom model'
"""
if args.input is None and args.model not in models:
raise parser.error(f'Model {args.model} is not include in pymagglobal.'
f' To process your own model, specify an input '
f'file using \n'
f' $ pymagglobal --input <path/to/{args.model}> '
f'... {args.model}')
"""
# get the right time units
if args.longterm:
args.t_unit = 'ka'
......
# This module contains the commands that are used by the main module of
# pymagglobal.
# core.
#
# Copyright (C) 2020 Helmholtz Centre Potsdam GFZ,
# German Research Centre for Geosciences, Potsdam, Germany
......@@ -7,7 +7,7 @@
# Cite as:
# TODO
#
# This file is part of pymagglobal.
# This file is part of core.
#
# pymagglobal is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -20,7 +20,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pymagglobal. If not, see <https://www.gnu.org/licenses/>.
# along with core. If not, see <https://www.gnu.org/licenses/>.
'''Just a bunch of functions ...
......@@ -37,9 +37,8 @@ from matplotlib import pyplot as plt, colors, cm
from pyfield import REARTH
# XXX PEP 8 used to recommend absolute imports
from . import pymagglobal
from . import utils
from pymagglobal import core
from pymagglobal import utils
class OutOfRangeWarning(Warning):
......@@ -177,10 +176,10 @@ def master_curve(args):
# get an array of times
times = args2times(args)
# get the splines from the input file
splines = pymagglobal.file2splines(args.input)
splines = core.file2splines(args.input)
# create a master curve using the core function
curves = pymagglobal.master_curve(times, (args.lat, args.lon), splines,
ser_type=args.type)
curves = core.master_curve(times, (args.lat, args.lon), splines,
ser_type=args.type)
# output formats for dif and nez components
fmts = {'dif': ('%.2f', '%2.6f', '%2.6f', '%1.7e'),
'nez': ('%.2f', '%1.7e', '%1.7e', '%1.7e')}
......@@ -199,7 +198,7 @@ def master_curve(args):
fmt=fmts[args.type],
delimiter=',',
header=f'Master curves at ({args.lat}°, {args.lon}°) '
f'for the model {args.model}\n'
f'for {args.model}\n'
f'{args.t_label}'
f'{utils.labels[args.type][0]},'
f'{utils.labels[args.type][1]},'
......@@ -209,7 +208,7 @@ def master_curve(args):
if not args.no_show:
fig = plt.figure(figsize=(10, 7))
fig.suptitle(f'Master curves at ({args.lat}°, {args.lon}°) '
f'for the model {args.model}')
f'for {args.model}')
axs = np.empty(3, dtype=object)
axs[0] = fig.add_subplot(221)
axs[1] = fig.add_subplot(222)
......@@ -240,9 +239,9 @@ def dipole_series(args):
# get an array of times
times = args2times(args)
# get the splines from the input file
splines = pymagglobal.file2splines(args.input)
splines = core.file2splines(args.input)
# create the dipole-moment time series for the given model
dip_ser = pymagglobal.dipole_series(times, splines)
dip_ser = core.dipole_series(times, splines)
# if the --longterm flag is set, translate the years accordingly
if args.longterm:
times = yr2lt(times)
......@@ -254,7 +253,7 @@ def dipole_series(args):
dip_ser]).T,
fmt=('%.2f', '%1.7e'),
delimiter=',',
header=f'Dipole moment series for for the model '
header=f'Dipole moment series for '
f'{args.model}\n'
f'{args.t_label},m [m^2 A]')
......@@ -263,7 +262,7 @@ def dipole_series(args):
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111)
ax.plot(times, dip_ser)
ax.set_title(f'Dipole moment series for for the model {args.model}')
ax.set_title(f'Dipole moment series for {args.model}')
ax.set_ylabel(r'$m$ [m$^2$ A]')
if args.longterm:
ax.invert_xaxis()
......@@ -286,22 +285,22 @@ def coefficients(args):
# check whether the epoch is in the range of the model
epoch = valid_epoch(args)
# get the splines from the input file
splines = pymagglobal.file2splines(args.input)
splines = core.file2splines(args.input)
# evaluate the splines at the epoch
ls, ms, coeffs = pymagglobal.coefficients(epoch, splines)
ls, ms, coeffs = core.coefficients(epoch, splines)
# if an output is specified, save the result to text
if args.output is not None:
np.savetxt(args.output,
np.array([ls, ms, coeffs]).T,
fmt=('%.d', '%d', '%1.7e'),
delimiter=',',
header=f'Coefficients for the model {args.model} at epoch '
header=f'Coefficients for {args.model} at epoch '
f'{args.epoch} {args.t_unit}\n'
f'l,m,g_l^m')
# if no output is given and the --no-show flag is not set, print the
# coefficients to the command line
elif not args.no_show:
print(f'Coefficients for the model {args.model} at epoch '
print(f'Coefficients for {args.model} at epoch '
f'{args.epoch} {args.t_unit}')
right = len(str(ms[-1]))
print(f'l\t' + ' '*(right-1) + 'm\t g_l^m')
......@@ -330,9 +329,9 @@ def maps(args):
z_at[2] = REARTH
z_at[3] = epoch
# get the splines from the input file
splines = pymagglobal.file2splines(args.input)
splines = core.file2splines(args.input)
# use the core function to get the field
field = pymagglobal.field(z_at, splines)
field = core.field(z_at, splines)
# convert the field if necessary
if args.type == 'dif':
field = np.array(utils.nez2dif(*field))
......@@ -350,7 +349,7 @@ def maps(args):
field[2]]).T,
fmt=fmts[args.type],
delimiter=',',
header=f'Field map for the model {args.model} at epoch '
header=f'Field map for {args.model} at epoch '
f'{args.epoch} {args.t_unit}\n'
f'lat,lon,'
f'{utils.labels[args.type][0]},'
......@@ -365,8 +364,10 @@ def maps(args):
units = [r'$\mu$T', r'$\mu$T', r'$\mu$T']
if args.type == 'dif':
field[2] /= 1000
vmaxs[0] = 40
vmins[0] = -vmaxs[0]
vmaxs[2] /= 1000
vmins[2] = 0
vmins[2] = np.min(field[2]) / 1000
cmaps[2] = 'Blues'
units[0] = r'deg.'
units[1] = r'deg.'
......@@ -383,7 +384,7 @@ def maps(args):
fig, axs = plt.subplots(1, 3, figsize=(13, 3),
subplot_kw={'projection': proj})
fig.suptitle(f'Field maps for the model {args.model} at epoch '
fig.suptitle(f'Field maps for {args.model} at epoch '
f'{args.epoch} {args.t_unit}')
for it in range(3):
......
......@@ -35,7 +35,7 @@ from scipy.interpolate import BSpline
from pyfield import CRUSpline as Kernel, REARTH, i2lm_l, i2lm_m, lmax2N
from .utils import nez2dif
from pymagglobal.utils import nez2dif
# generate a dictionary of models from models the models folder
here = os.path.abspath(os.path.dirname(__file__))
......
import unittest
import doctest
from pathlib import PurePath
from pymagglobal import utils
from pymagglobal import _commands
def test_suite():
'''To be called in setup.py
XXX Deprecated since 3.8'''
tests_path = PurePath(__file__).parent
# By convention discovers TestCases in test_*.py
suite = unittest.TestLoader().discover(tests_path)
# Add doc-test manually
suite.addTest(doctest.DocTestSuite(utils))
suite.addTest(doctest.DocTestSuite(_commands))
return suite
import unittest
import io
import sys
from pymagglobal.__main__ import argument_parser
class _SetUpCls(unittest.TestCase):
'''Dummy just to instantiate the parser'''
@classmethod
def setUpClass(cls):
cls.parser = argument_parser()
class ParserTestGlobal(_SetUpCls):
'''Test-cases from global options'''
def test_help(self):
'''Check if the help flag terminates successfully with exit code 0'''
with self.assertRaises(SystemExit) as cm:
# Redirect stdout not to pollute output the terminal
sys.stdout = io.StringIO()
# Pass list since sys.argv splits at white space (filename stripped)
nsp = self.parser.parse_args(['-h'])
# Assert if exit-code differs from 0
self.assertEqual(cm.exception.code, 0)
# Again with --help
with self.assertRaises(SystemExit) as cm:
nsp = self.parser.parse_args(['--help'])
self.assertEqual(cm.exception.code, 0)
def test_list_models(self):
'''Check if `--list-models` terminates successfully'''
with self.assertRaises(SystemExit) as cm:
# Redirect stdout not to pollute output the terminal
sys.stdout = io.StringIO()
# Pass list since sys.argv splits at white space (filename stripped)
nsp = self.parser.parse_args(['--list-models'])
# Assert if exit-code is different from 0
self.assertEqual(cm.exception.code, 0)
class ParserTestMaster(_SetUpCls):
'''Test-cases from the `master` sub-command'''
def test_master_valid(self):
'''Check if a syntactically proper string is parsed without error'''
# TODO: Grab that command from 'example of use'
cmd = 'pymagglobal master --no-show 12 12 gufm1'
nsp = self.parser.parse_args(cmd.split()[1:])
def test_master_exclusive_res_every(self):
'''Raise if both, --res and --every are specified'''
cmd = 'pymagglobal master --no-show --res=20 --every=12 12 12 gufm1'
with self.assertRaises(SystemExit) as cm:
# Redirect stderr not to pollute output the terminal
sys.stderr = io.StringIO()
nsp = self.parser.parse_args(cmd.split()[1:])
self.assertNotEqual(cm.exception.code, 0,
msg='A non-zero exit code is expected.')
class ParserTestMap(_SetUpCls):
'''Test-cases from the `map` sub-command'''
def test_master_valid(self):
'''Check if a syntactically proper string is parsed without error'''
# TODO: Grab that command from 'example of use'
cmd = 'pymagglobal map 1700 gufm1'
nsp = self.parser.parse_args(cmd.split()[1:])
import unittest
from pathlib import Path
from packaging.version import Version
import pymagglobal
# Implicitly check the package index __all__
# Not a test-case since `import *` is only allowed at module level
from pymagglobal import *
class ParserTestPackage(unittest.TestCase):
'''Unit-tests for the package i.e. __init__.py'''
def test_version(self):
'''Check if pymagglobal.__version__ conforms PEP 440'''
version = Version(pymagglobal.__version__)
def test_models(self):
'''Check if built-in model-files exist'''
for model_fn in models.values():
with self.subTest(msg=model_fn):
self.assertTrue(Path(model_fn).is_file())
......@@ -67,7 +67,8 @@ def nez2dif(n, e, z):
Example
-------
>>> nez2dif(1, 1, 1)
>>> nez2dif(1, 0, 0)
(0.0, 0.0, 1.0)
'''
return np.rad2deg(np.arctan2(e, n)), \
np.rad2deg(np.arctan2(z, np.sqrt(n**2 + e**2))), \
......
'''
This is a basic test file for pymagglobal.
Copyright (C) 2020 Helmholtz Centre Potsdam GFZ,
German Research Centre for Geosciences, Potsdam, Germany
Cite as:
TODO
This file is part of pymagglobal.
pymagglobal is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pymagglobal is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pymagglobal. If not, see <https://www.gnu.org/licenses/>.
'''
import subprocess
import numpy as np
import pymagglobal
lat = 52.24
lon = 13.4
# test the command line tool
subprocess.run(f"pymagglobal master {lat} {lon} --every 5 -o potsdam.test"
f" --no-show CALS10k.2", shell=True)
# import the results
new = np.genfromtxt('potsdam.test', delimiter=',').T
# generate the same results using the package
splines = pymagglobal.file2splines(pymagglobal.models['CALS10k.2'])
times = new[0]
d, i, f = pymagglobal.master_curve(times, (lat, lon), splines)
assert(np.allclose(d, new[1], atol=1e-6))
assert(np.allclose(i, new[2], atol=1e-6))
assert(np.allclose(f, new[3], atol=1e-6))
......@@ -74,4 +74,5 @@ setup(
include_package_data=True,
cmdclass=cmdclass,
command_options=command_options,
test_suite='pymagglobal.tests.test_suite',
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment