Skip to content

Commit

Permalink
Make empymod a hard dependency (#324)
Browse files Browse the repository at this point in the history
Make empymod a hard dependency. This removes duplication of code (`EMArray`, and in the future potentially more, e.g., with regards to noise generation) and streamlines things. It also brings in `scooby`, which is good for user support / helping with issues.
  • Loading branch information
prisae authored Mar 4, 2024
1 parent 072ba17 commit 8750f85
Show file tree
Hide file tree
Showing 11 changed files with 41 additions and 184 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- python-version: "3.10"
name: full
os: ubuntu
conda: "numba scipy xarray empymod h5py scooby discretize matplotlib" # tqdm
conda: "numba scipy xarray h5py discretize matplotlib" # tqdm
- python-version: "3.10"
name: plain
os: ubuntu
Expand All @@ -55,15 +55,15 @@ jobs:
- python-version: "3.11"
name: full
os: ubuntu
conda: "numba scipy xarray tqdm empymod h5py scooby discretize matplotlib"
conda: "numba scipy xarray tqdm h5py discretize matplotlib"
- python-version: "3.12"
name: plain
os: ubuntu
conda: "numba scipy"
- python-version: "3.12"
name: full
os: ubuntu
conda: "numba scipy xarray tqdm empymod h5py scooby discretize matplotlib"
conda: "numba scipy xarray tqdm h5py discretize matplotlib"

env:
# Used for coveralls flag
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ Changelog
latest
------

TODO: some text about empymod, scooby.

- Maintenance:

- Bumped the minimum requirements to:

- Python 3.9
- SciPy 1.9
- Numba 0.53
- empymod 2.3.0 (NEW requirement)

- Testing: added Python 3.12, dropped Python 3.8.

Expand Down
9 changes: 4 additions & 5 deletions docs/manual/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ or via ``pip``:
pip install emg3d
Requirements are the modules ``scipy`` and ``numba``. Various other packages
are recommended or required for some advanced functionalities, namely:
Requirements are the modules ``scipy``, ``numba``, and ``empymod``. Various
other packages are recommended or required for some advanced functionalities,
namely:

- ``xarray``: For the :class:`emg3d.surveys.Survey` and
:class:`emg3d.simulations.Simulation` classes (model many sources and
Expand All @@ -23,16 +24,14 @@ are recommended or required for some advanced functionalities, namely:
plotting utilities).
- ``matplotlib``: To use the plotting utilities within ``discretize``.
- ``h5py``: Save and load data in the HDF5 format.
- ``empymod``: Time-domain modelling (:class:`emg3d.time.Fourier`).
- ``scooby``: For the version and system report (:class:`emg3d.utils.Report`).
- ``tqdm``: For nice progress bars when computing many sources and frequencies.

All soft dependencies are also available both on ``conda-forge`` and ``pip``.
To get therefore the complete experience use one of the following options:

.. code-block:: console
conda install -c conda-forge emg3d empymod discretize xarray matplotlib h5py tqdm scooby
conda install -c conda-forge emg3d discretize xarray matplotlib h5py tqdm
or via ``pip``:

Expand Down
3 changes: 1 addition & 2 deletions docs/manual/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -342,5 +342,4 @@ high-level class ``Simulation``. It has to be carried out by using
above. Have a look at the repo https://github.com/emsig/article-TDEM.


*Note:* In addition to ``emg3d`` this requires the soft dependency ``empymod``
(``discretize`` is recommended).
*Note:* In addition to ``emg3d``, the soft dependency ``discretize`` is recommended for this.
17 changes: 2 additions & 15 deletions emg3d/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,17 @@

import warnings

import empymod
import numpy as np
import scipy as sp

try:
import empymod
except ImportError:
empymod = None

from emg3d import utils

__all__ = ['Fourier', ]


def __dir__():
return __all__


@utils._requires('empymod')
class Fourier:
r"""Time-domain CSEM computation.
Expand Down Expand Up @@ -68,12 +61,6 @@ class Fourier:
with cubic spline interpolation (on a log-scale) using
:class:`scipy.interpolate.InterpolatedUnivariateSpline`.
.. note::
The package ``empymod`` has to be installed in order to use
``Fourier``:
``pip install empymod`` or ``conda install -c conda-forge empymod``.
Parameters
----------
Expand Down Expand Up @@ -106,7 +93,7 @@ class Fourier:
- If ``ft='dlf'``:
- ``dlf``: string of filter name in :mod:`empymod.filters` or the
filter method itself; default: ``'key_201_CosSin_2012'``.
filter method itself; default: ``'key_201_2012'``.
- ``pts_per_dec``: points per decade; default: -1.
- If 0: Standard DLF;
Expand Down
96 changes: 4 additions & 92 deletions emg3d/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,8 @@
from datetime import datetime, timedelta

import numpy as np

try:
from scooby import Report as ScoobyReport
except ImportError:
class ScoobyReport:
"""Dummy placeholder."""
from empymod import EMArray
from scooby import Report as ScoobyReport

# Version: We take care of it here instead of in __init__, so we can use it
# within the package itself (logs).
Expand Down Expand Up @@ -121,7 +117,6 @@ def passer(*args, **kwargs):


# PUBLIC UTILS
@_requires('scooby')
class Report(ScoobyReport):
r"""Print date, time, and version information.
Expand All @@ -138,11 +133,6 @@ class Report(ScoobyReport):
All modules provided in ``add_pckg`` are also shown.
.. note::
The package ``scooby`` has to be installed in order to use ``Report``:
``pip install scooby`` or ``conda install -c conda-forge scooby``.
Parameters
----------
Expand All @@ -165,94 +155,16 @@ def __init__(self, add_pckg=None, ncol=3, text_width=80, sort=False):
"""Initiate a scooby.Report instance."""

# Mandatory packages.
core = ['numpy', 'scipy', 'numba', 'emg3d']
core = ['numpy', 'scipy', 'numba', 'emg3d', 'empymod']

# Optional packages.
optional = ['empymod', 'xarray', 'discretize', 'h5py', 'matplotlib',
optional = ['xarray', 'discretize', 'h5py', 'matplotlib',
'tqdm', 'IPython']

super().__init__(additional=add_pckg, core=core, optional=optional,
ncol=ncol, text_width=text_width, sort=sort)


class EMArray(np.ndarray):
r"""An EM-ndarray adds the methods `amp` (amplitude) and `pha` (phase).
Parameters
----------
data : ndarray
Data to which to add ``.amp`` and ``.pha`` attributes.
Examples
--------
.. ipython::
In [1]: import numpy as np
...: from empymod.utils import EMArray
...: emvalues = EMArray(np.array([1+1j, 1-4j, -1+2j]))
# Amplitudes
In [2]: emvalues.amp()
Out[2]: EMArray([1.41421356, 4.12310563, 2.23606798])
# Phase in radians
In [3]: emvalues.pha()
Out[3]: EMArray([ 0.78539816, -1.32581766, -4.24874137])
# Phase in degrees
In [4]: emvalues.pha(deg=True)
Out[4]: EMArray([ 45. , -75.96375653, -243.43494882])
# Phase in degrees, lead defined
In [5]: emvalues.pha(deg=True, lag=False)
Out[5]: EMArray([-45. , 75.96375653, 243.43494882])
"""

def __new__(cls, data):
r"""Create a new EMArray."""
return np.asarray(data).view(cls)

def amp(self):
"""Amplitude of the electromagnetic field."""
return np.abs(self.view())

def pha(self, deg=False, unwrap=True, lag=True):
"""Phase of the electromagnetic field.
Parameters
----------
deg : bool, default: False
The returned phase is in degrees if True, else in radians.
unwrap : bool, default: True
The returned phase is unwrapped if True.
lag : bool, default: True
The returned phase is lag defined if True, else lead defined.
"""
# Get phase, lead or lag defined.
if lag:
pha = np.angle(self.view())
else:
pha = np.angle(np.conj(self.view()))

# Unwrap if `unwrap`.
# np.unwrap removes the EMArray class;
# for consistency, we wrap it in EMArray again.
if unwrap and self.size > 1:
pha = EMArray(np.unwrap(pha))

# Convert to degrees if `deg`.
if deg:
pha *= 180/np.pi

return pha


class Timer:
"""Class for timing (now; runtime)."""

Expand Down
3 changes: 0 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
# SOFT DEPENDENCIES
tqdm
h5py
scooby
xarray
empymod
cython
discretize
matplotlib

Expand Down
15 changes: 7 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@
install_requires=[
"scipy>=1.9",
"numba>=0.53",
"empymod>=2.3.0",
],
extras_require={
'full': [
'tqdm',
'h5py',
'scooby',
'xarray',
'empymod',
'discretize>=0.7.3',
'matplotlib',
"full": [
"tqdm",
"h5py",
"xarray",
"discretize",
"matplotlib",
],
},
use_scm_version={
Expand Down
10 changes: 5 additions & 5 deletions tests/test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from numpy.testing import assert_allclose

import emg3d
import empymod
from emg3d import _multiprocessing as _mp

try:
Expand All @@ -13,9 +14,9 @@
tqdm = None

try:
import empymod
import xarray
except ImportError:
empymod = None
xarray = None

# Data generated with tests/create_data/regression.py
REGRES = emg3d.load(join(dirname(__file__), 'data', 'regression.npz'))
Expand Down Expand Up @@ -74,7 +75,7 @@ def test__solve():
assert_allclose(dat['Fresult'].field, efield.field)


@pytest.mark.skipif(empymod is None, reason="empymod not installed.")
@pytest.mark.skipif(xarray is None, reason="xarray not installed.")
def test_layered():

src = emg3d.TxElectricDipole((-2000, 0, 200, 20, 5))
Expand Down Expand Up @@ -157,7 +158,6 @@ def test_layered():
assert np.all(out[::2, 1:, 1, :] != 0.0)


@pytest.mark.skipif(empymod is None, reason="empymod not installed.")
def test_empymod_fwd():
# Simple check for status quo.
empymod_inp = {
Expand Down Expand Up @@ -203,7 +203,7 @@ class DummySrcRec:
assert out['p1'] == rec.center


@pytest.mark.skipif(empymod is None, reason="empymod not installed.")
@pytest.mark.skipif(xarray is None, reason="xarray not installed.")
def test_fd_gradient():

res = np.array([0.9876, ])
Expand Down
9 changes: 2 additions & 7 deletions tests/test_time.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import pytest
import empymod
import numpy as np
from numpy.testing import assert_allclose

from emg3d import time

try:
import empymod
except ImportError:
empymod = None


@pytest.mark.skipif(empymod is None, reason="empymod not installed.")
class TestFourier:
def test_defaults(self, capsys):
times = np.logspace(-2, 2)
Expand All @@ -33,7 +28,7 @@ def test_defaults(self, capsys):
assert Fourier.signal == 0 # Impulse respons
assert_allclose(times, Fourier.time, 0, 0)
assert Fourier.verb == 3 # Verbose by default
assert 'key' in out.lower()
assert 'key' in out
assert 'Req. freq' in out
assert 'Calc. freq' in out
assert Fourier.freq_compute.min() >= fmin
Expand Down
Loading

0 comments on commit 8750f85

Please sign in to comment.