Skip to content

Commit

Permalink
Merge pull request #3461 from obspy/np2
Browse files Browse the repository at this point in the history
numpy 2.0 compatibility
  • Loading branch information
megies committed Jun 6, 2024
2 parents 16c8fe2 + bc0def3 commit 236b507
Show file tree
Hide file tree
Showing 41 changed files with 254 additions and 176 deletions.
2 changes: 1 addition & 1 deletion .github/test_conda_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dependencies:
- decorator
- lxml
- matplotlib
- numpy
- numpy < 2
- scipy
- requests
- setuptools
Expand Down
2 changes: 1 addition & 1 deletion .github/test_mindep_conda_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dependencies:
- decorator
- lxml
- matplotlib
- numpy
- numpy < 2
- scipy
- requests
- setuptools
Expand Down
27 changes: 27 additions & 0 deletions .github/test_np2_conda_env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: test
channels:
- conda-forge
- defaults
dependencies:
# obspy
- decorator
- lxml
- matplotlib
- numpy >= 2.0.0rc2
# dummy package needed to install the np2 rc via conda
- conda-forge/label/numpy_rc::_numpy_rc
- scipy
- requests
- setuptools
# see #3258
- sqlalchemy < 2.0
# soft dependencies
- cartopy
- geographiclib
- pyshp
# tests
- packaging
- pyproj
- pytest
- pytest-cov
- pytest-json-report >= 1.4
12 changes: 11 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ jobs:
- os: ubuntu-latest
python-version: '3.12'
options: default warnings
# numpy 2 specific builds
- os: ubuntu-latest
python-version: '3.12'
options: default warnings np2
- os: macos-latest
python-version: '3.12'
options: default warnings np2
- os: windows-latest
python-version: '3.12'
options: default warnings np2
- os: ubuntu-latest
python-version: '3.12'
options: ${{ (github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'test_network')) && 'network warnings' || 'default' }}
Expand All @@ -136,7 +146,7 @@ jobs:
# set individual cache key
cache_key: ${{ matrix.os }}-py${{ matrix.python-version }}-${{ contains(matrix.options, 'mindep') }}-${{ contains(matrix.options, 'mindepversion') }}
# set conda environment file with dependencies
env_file: .github/test${{ contains(matrix.options, 'mindepversion') && '_mindepversion' || contains(matrix.options, 'mindep') && '_mindep' || '' }}_conda_env.yml
env_file: .github/test${{ contains(matrix.options, 'np2') && '_np2' || contains(matrix.options, 'mindepversion') && '_mindepversion' || contains(matrix.options, 'mindep') && '_mindep' || '' }}_conda_env.yml
# set additional runtest options (--keep-images, --network, -W error)
runtest_options: ${{ github.event_name == 'pull_request' && '--keep-images' || '' }} ${{ contains(matrix.options, 'network') && '--network' || '' }} ${{ contains(matrix.options, 'warnings') && '-W error' || '' }}
steps:
Expand Down
13 changes: 12 additions & 1 deletion obspy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,20 @@
(https://www.gnu.org/copyleft/lesser.html)
"""
import sys
import warnings

# don't change order
from obspy.core.utcdatetime import UTCDateTime # NOQA
# ignore pkg resources deprecation warning for now
# we really only need this here for one single reason: this file here gets
# imported during the pytest startup phase before ignore rules in pytest.ini
# take effect. otherwise we could just add this warning to the ignore list in
# pytest.ini (see #3333)
# The warning capture can be removed after #3333 gets finalized and merged
with warnings.catch_warnings(record=True) as w:
msg = ('pkg_resources is deprecated as an API')
warnings.filterwarnings(
'ignore', message=msg, category=DeprecationWarning, module='obspy')
from obspy.core.utcdatetime import UTCDateTime # NOQA
from obspy.core.util import _get_version_string
__version__ = _get_version_string(abbrev=10)
from obspy.core.trace import Trace # NOQA
Expand Down
3 changes: 2 additions & 1 deletion obspy/core/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from obspy.core.stream import Stream
from obspy.core.trace import Trace
from obspy.core.utcdatetime import UTCDateTime
from obspy.core.util.misc import ptp


def create_preview(trace, delta=60):
Expand Down Expand Up @@ -66,7 +67,7 @@ def create_preview(trace, delta=60):
# reshape matrix
data = trace.data[start:end].reshape([number_of_slices, samples_per_slice])
# get minimum and maximum for each row
diff = data.ptp(axis=1)
diff = ptp(data, axis=1)
# fill masked values with -1 -> means missing data
if isinstance(diff, np.ma.masked_array):
diff = np.ma.filled(diff, -1)
Expand Down
9 changes: 5 additions & 4 deletions obspy/core/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
_read_from_plugin, _generic_reader)
from obspy.core.util.decorator import (map_example_filename,
raise_if_masked, uncompress_file)
from obspy.core.util.misc import get_window_times, buffered_load_entry_point
from obspy.core.util.misc import (
get_window_times, buffered_load_entry_point, ptp)
from obspy.core.util.obspy_types import ObsPyException


Expand Down Expand Up @@ -3353,13 +3354,13 @@ def stack(self, group_by='all', stack_type='linear', npts_tol=0,
raise ValueError(msg)
if 'starttime' not in header and time_tol > 0:
times = [tr.stats.starttime for tr in traces]
if np.ptp(times) <= time_tol:
if ptp(times) <= time_tol:
# use high median as starttime
header['starttime'] = sorted(times)[len(times) // 2]
header['stack'] = AttribDict(group=groupid, count=len(traces),
type=stack_type)
npts_all = [len(tr) for tr in traces]
npts_dif = np.ptp(npts_all)
npts_dif = ptp(npts_all)
npts = min(npts_all)
if npts_dif > npts_tol:
msg = ('Difference of number of points of the traces is higher'
Expand Down Expand Up @@ -3407,7 +3408,7 @@ def _dummy_stream_from_string(s):
sampling_rate = float(items[6])
npts = int(items[8])
tr = Trace()
tr.data = np.ones(npts, dtype=np.float_)
tr.data = np.ones(npts, dtype=np.float64)
tr.stats.station = sta
tr.stats.network = net
tr.stats.location = loc
Expand Down
2 changes: 1 addition & 1 deletion obspy/core/tests/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1427,7 +1427,7 @@ def test_times(self):
tr.data = np.ma.ones(100)
tr.data[30:40] = np.ma.masked
tm = tr.times()
assert np.alltrue(tr.data.mask == tm.mask)
assert np.all(tr.data.mask == tm.mask)
# test relative with reftime
tr.data = np.ones(100)
shift = 9.5
Expand Down
12 changes: 6 additions & 6 deletions obspy/core/tests/test_utcdatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ def test_from_numpy_string(self):
Tests importing from NumPy strings.
"""
# some strange patterns
dt = UTC(np.string_("1970-01-01 12:23:34"))
dt = UTC(np.bytes_("1970-01-01 12:23:34"))
assert dt == UTC(1970, 1, 1, 12, 23, 34)
dt = UTC(np.string_("1970,01,01,12:23:34"))
dt = UTC(np.bytes_("1970,01,01,12:23:34"))
assert dt == UTC(1970, 1, 1, 12, 23, 34)
dt = UTC(np.string_("1970,001,12:23:34"))
dt = UTC(np.bytes_("1970,001,12:23:34"))
assert dt == UTC(1970, 1, 1, 12, 23, 34)
dt = UTC(np.string_("20090701121212"))
dt = UTC(np.bytes_("20090701121212"))
assert dt == UTC(2009, 7, 1, 12, 12, 12)
dt = UTC(np.string_("19700101"))
dt = UTC(np.bytes_("19700101"))
assert dt == UTC(1970, 1, 1, 0, 0)
# non ISO8601 strings should raise an exception
with pytest.raises(Exception):
UTC(np.string_("1970,001,12:23:34"), iso8601=True)
UTC(np.bytes_("1970,001,12:23:34"), iso8601=True)

def test_from_python_date_time(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion obspy/core/tests/test_util_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def test_nan(self):
Ensure NaNs eval equal if equal_nan is used, else they do not.
"""
tr1, tr2 = read()[0], read()[0]
tr1.data[0], tr2.data[0] = np.NaN, np.NaN
tr1.data[0], tr2.data[0] = np.nan, np.nan
assert traces_almost_equal(tr1, tr2, equal_nan=True)
assert not traces_almost_equal(tr1, tr2, equal_nan=False)

Expand Down
2 changes: 1 addition & 1 deletion obspy/core/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1735,7 +1735,7 @@ def resample(self, sampling_rate, window='hann', no_filter=True,
self.filter('lowpass_cheby_2', freq=freq, maxorder=12)

# resample in the frequency domain. Make sure the byteorder is native.
x = rfft(self.data.newbyteorder("="))
x = rfft(self.data.view(self.data.dtype.newbyteorder("=")))
# Cast the value to be inserted to the same dtype as the array to avoid
# issues with numpy rule 'safe'.
x = np.insert(x, 1, x.dtype.type(0))
Expand Down
6 changes: 6 additions & 0 deletions obspy/core/utcdatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,12 @@ def __add__(self, value):
msg = ("unsupported operand type(s) for +: 'UTCDateTime' and "
"'UTCDateTime'")
raise TypeError(msg)
# need to make sure we don't get e.g. np.float32 singl precision input
# or worse, because then numpy is in charge of the calculations and
# numpy 2.0 is not automatically upcasting to avoid precision loss
# which means we can't keep full precision when converting input
# seconds to nanoseconds
value = float(value)
return UTCDateTime(ns=self._ns + int(round(value * 1e9)))

def __sub__(self, value):
Expand Down
14 changes: 14 additions & 0 deletions obspy/core/util/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,20 @@ def func(obj, parent=None, attr=None):
return func(obj)


def ptp(a, *args, **kwargs):
"""
Replacement for :meth:`numpy.ndarray.ptp()` and the corresponding method on
`MaskedArray` objects which are being removed in numpy 2.0
Basically just makes sure we call the correct replacement function numpy
put in place for regular and masked arrays.
:type a: :class:`numpy.ndarray` or :class:`numpy.ma.MaskedArray`
"""
if isinstance(a, np.ma.MaskedArray):
return np.ma.ptp(a, *args, **kwargs)
return np.ptp(a, *args, **kwargs)


if __name__ == '__main__':
import doctest
doctest.testmod(exclude_empty=True)
7 changes: 5 additions & 2 deletions obspy/io/alsep/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ def _read_header(file):
# np.fromfile accepts file path on-disk file-like
# objects. In all other cases, use np.frombuffer:
try:
return np.fromfile(file, dtype='u1', count=16)
header = np.fromfile(file, dtype='u1', count=16)
except UnsupportedOperation:
# stream does not support fileno
buffer = io_stream.read(16)
return np.frombuffer(buffer, dtype='u1')
header = np.frombuffer(buffer, dtype='u1')
# manual upcast for later bitshifting etc
header = np.require(header, dtype=np.int64)
return header


def _is_pse(file):
Expand Down
2 changes: 1 addition & 1 deletion obspy/io/alsep/pse/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class PseFrame(object):
def __init__(self, frame, _is_old_format):
self.data = frame
self.data = np.require(frame, dtype=np.int64)
self._is_old_format = _is_old_format
# Frame header parameters
self.software_time_flag = None
Expand Down
2 changes: 1 addition & 1 deletion obspy/io/alsep/wt/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class _WtFrame(object):

def __init__(self, frame):
self.data = frame
self.data = np.require(frame, dtype=np.int64)
# Frame header parameters
self.flag_bit = None
self.msec_of_year = None
Expand Down
3 changes: 1 addition & 2 deletions obspy/io/ascii/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,7 @@ def _write_slist(stream, filename, custom_fmt=None,
else:
data = trace.data
data = data.reshape((-1, 6))
np.savetxt(fh, data, delimiter=b'\t',
fmt=fmt.encode('ascii', 'strict'))
np.savetxt(fh, data, delimiter='\t', fmt=fmt)
if rest:
fh.write(('\t'.join([fmt % d for d in trace.data[-rest:]]) +
'\n').encode('ascii', 'strict'))
Expand Down
2 changes: 1 addition & 1 deletion obspy/io/mseed/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1609,7 +1609,7 @@ def shift_time_of_file(input_file, output_file, timeshift):
# This should rarely be the case.
if current_time_shift == 0 and is_time_correction_applied:
# This sets bit 2 of the activity flags to 0.
current_record[36] = current_record[36] & (~2)
current_record[36] = np.int64(current_record[36]) & (~2)
is_time_correction_applied = False
# This is the case if the time correction has been applied. This
# requires some more work by changing both, the actual time and the
Expand Down
4 changes: 3 additions & 1 deletion obspy/io/nordic/ellipse.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,9 @@ def subtended_angle(self, pt=(0, 0)):
temp.y -= pt[1]
t0, t1 = temp._get_tangents((0, 0))
cosang = np.dot(t0, t1)
sinang = np.linalg.norm(np.cross(t0, t1))
# avoid np.cross() complaining about 2-d vectors
# see https://github.com/obspy/obspy/pull/3461#issuecomment-2149488336
sinang = abs(t0[0] * t1[1] - t0[1] * t1[0])
return np.degrees(np.arctan2(sinang, cosang))

def plot(self, linewidth=2, color='k',
Expand Down
2 changes: 1 addition & 1 deletion obspy/io/reftek/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def drop_not_implemented_packet_types(self):
Checks if there are packets of a type that is currently not implemented
and drop them showing a warning message.
"""
is_implemented = np.in1d(
is_implemented = np.isin(
self._data['packet_type'],
[x.encode() for x in PACKET_TYPES_IMPLEMENTED])
# if all packets are of a type that is implemented, the nothing to do..
Expand Down
Loading

0 comments on commit 236b507

Please sign in to comment.