Skip to content

Commit 569c545

Browse files
committed
BUG: avoid soon-to-be-deprecated direct mutations of ndarray.shape attributes
1 parent e962538 commit 569c545

File tree

13 files changed

+124
-63
lines changed

13 files changed

+124
-63
lines changed

CONTRIBUTING.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -720,9 +720,7 @@ Source code style guide
720720
* Do not use nested classes unless you have a very good reason to, such as
721721
requiring a namespace or class-definition modification. Classes should live
722722
at the top level. ``__metaclass__`` is exempt from this.
723-
* Avoid copying memory when possible. For example, don't do
724-
``a = a.reshape(3, 4)`` when ``a.shape = (3, 4)`` will do, and ``a = a * 3``
725-
should be ``np.multiply(a, 3, a)``.
723+
* Avoid copying memory when possible.
726724
* In general, avoid all double-underscore method names: ``__something`` is
727725
usually unnecessary.
728726
* When writing a subclass, use the super built-in to access the super class,

yt/_maintenance/numpy2_compat.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
# avoid deprecation warnings in numpy >= 2.0
2+
from dataclasses import dataclass
3+
from importlib.metadata import version
4+
from typing import Literal, TypeAlias
25

36
import numpy as np
7+
from packaging.version import Version
48

59
if hasattr(np, "trapezoid"):
610
# np.trapz is deprecated in numpy 2.0 in favor of np.trapezoid
711
trapezoid = np.trapezoid
812
else:
913
trapezoid = np.trapz # type: ignore # noqa: NPY201
14+
15+
16+
NUMPY_VERSION = Version(version("numpy"))
17+
18+
CopyFunc: TypeAlias = Literal["reshape"]
19+
MIN_NUMPY_VERSION: dict[CopyFunc, Version] = {
20+
"reshape": Version("2.1.0"),
21+
}
22+
23+
24+
@dataclass(frozen=True, slots=True)
25+
class CopyKwarg:
26+
value: bool | None
27+
28+
def get(self, f: CopyFunc, /) -> dict[Literal["copy"], bool | None]:
29+
if NUMPY_VERSION >= MIN_NUMPY_VERSION[f]:
30+
return {"copy": self.value}
31+
else:
32+
return {}
33+
34+
35+
COPY_NONE = CopyKwarg(None)
36+
COPY_TRUE = CopyKwarg(True)
37+
COPY_FALSE = CopyKwarg(False)

yt/fields/field_detector.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -276,18 +276,19 @@ def fcoords(self):
276276
np.mgrid[0 : 1 : self.nd * 1j, 0 : 1 : self.nd * 1j, 0 : 1 : self.nd * 1j]
277277
)
278278
if self.flat:
279-
fc.shape = (self.nd * self.nd * self.nd, 3)
279+
fc = fc.reshape(self.nd * self.nd * self.nd, 3)
280280
else:
281281
fc = fc.transpose()
282282
return self.ds.arr(fc, units="code_length")
283283

284284
@property
285285
def fcoords_vertex(self):
286-
rng = np.random.default_rng()
287-
fc = rng.random((self.nd, self.nd, self.nd, 8, 3))
288286
if self.flat:
289-
fc.shape = (self.nd * self.nd * self.nd, 8, 3)
290-
return self.ds.arr(fc, units="code_length")
287+
shape = (self.nd * self.nd * self.nd, 8, 3)
288+
else:
289+
shape = (self.nd, self.nd, self.nd, 8, 3)
290+
rng = np.random.default_rng()
291+
return self.ds.arr(rng.random(shape), units="code_length")
291292

292293
@property
293294
def icoords(self):
@@ -297,21 +298,23 @@ def icoords(self):
297298
0 : self.nd - 1 : self.nd * 1j,
298299
]
299300
if self.flat:
300-
ic.shape = (self.nd * self.nd * self.nd, 3)
301+
return ic.reshape(self.nd * self.nd * self.nd, 3)
301302
else:
302-
ic = ic.transpose()
303-
return ic
303+
return ic.transpose()
304304

305305
@property
306306
def ires(self):
307-
ir = np.ones(self.nd**3, dtype="int64")
308-
if not self.flat:
309-
ir.shape = (self.nd, self.nd, self.nd)
310-
return ir
307+
if self.flat:
308+
shape = (self.nd**3,)
309+
else:
310+
shape = (self.nd, self.nd, self.nd)
311+
return np.ones(shape, dtype="int64")
311312

312313
@property
313314
def fwidth(self):
314-
fw = np.ones((self.nd**3, 3), dtype="float64") / self.nd
315-
if not self.flat:
316-
fw.shape = (self.nd, self.nd, self.nd, 3)
315+
if self.flat:
316+
shape = (self.nd**3, 3)
317+
else:
318+
shape = (self.nd, self.nd, self.nd, 3)
319+
fw = np.full(shape, 1 / self.nd, dtype="float64")
317320
return self.ds.arr(fw, units="code_length")

yt/fields/geometric_fields.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import numpy as np
22

3+
from yt._maintenance.numpy2_compat import COPY_FALSE
34
from yt.utilities.lib.geometry_utils import compute_morton
45
from yt.utilities.math_utils import (
56
get_cyl_r,
@@ -128,14 +129,17 @@ def _morton_index(data):
128129
LE = data.ds.domain_left_edge - eps * uq
129130
RE = data.ds.domain_right_edge + eps * uq
130131
# .ravel() only copies if it needs to
131-
morton = compute_morton(
132-
data["index", "x"].ravel(),
133-
data["index", "y"].ravel(),
134-
data["index", "z"].ravel(),
135-
LE,
136-
RE,
132+
morton = np.reshape(
133+
compute_morton(
134+
data["index", "x"].ravel(),
135+
data["index", "y"].ravel(),
136+
data["index", "z"].ravel(),
137+
LE,
138+
RE,
139+
),
140+
data["index", "x"].shape,
141+
**COPY_FALSE.get("reshape"),
137142
)
138-
morton.shape = data["index", "x"].shape
139143
return morton.view("f8")
140144

141145
registry.add_field(

yt/frontends/amrvac/datfile_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def get_single_block_field_data(istream, byte_offset, block_shape, field_idx):
157157
byte_size_field = struct.calcsize(fmt)
158158

159159
istream.seek(byte_offset + byte_size_field * field_idx)
160-
data = np.fromfile(istream, "=f8", count=np.prod(field_shape))
161-
data.shape = field_shape[::-1]
160+
data = np.fromfile(
161+
istream, dtype=("=f8", field_shape[::-1]), count=np.prod(field_shape)
162+
)
162163
return data.T

yt/frontends/amrvac/io.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ def _read_data(self, fid, grid, field):
108108
byte_size_field = count * 8 # size of a double
109109

110110
fid.seek(offset + byte_size_field * field_idx)
111-
data = np.fromfile(fid, "=f8", count=count)
112-
data.shape = field_shape[::-1]
111+
data = np.fromfile(fid, dtype=("=f8", field_shape[::-1]), count=count)
113112
data = data.T
114113
# Always convert data to 3D, as grid.ActiveDimensions is always 3D
115114
while len(data.shape) < 3:

yt/frontends/chimera/data_structures.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import numpy as np
1212

13+
from yt._maintenance.numpy2_compat import COPY_FALSE
1314
from yt.data_objects.index_subobjects.unstructured_mesh import SemiStructuredMesh
1415
from yt.data_objects.static_output import Dataset
1516
from yt.geometry.api import Geometry
@@ -103,7 +104,9 @@ def _initialize_mesh(self):
103104
mylog.warning(
104105
"Yin-Yang File Detected; This data is not currently supported."
105106
)
106-
coords.shape = (nxd * nyd * nzd, 3)
107+
coords = np.reshape(
108+
coords, (nxd * nyd * nzd, 3), **COPY_FALSE.get("reshape")
109+
)
107110
# Connectivity is an array of rows, each of which corresponds to a grid cell.
108111
# The 8 elements of each row are integers representing the cell vertices.
109112
# These integers reference the numerical index of the element of the

yt/frontends/gadget/io.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,10 +462,9 @@ def _yield_coordinates(self, data_file, needed_ptype=None):
462462
if needed_ptype is not None and ptype != needed_ptype:
463463
continue
464464
# The first total_particles * 3 values are positions
465-
pp = np.fromfile(f, dtype=dt, count=count * 3).astype(
465+
pp = np.fromfile(f, dtype=(dt, (count, 3)), count=count * 3).astype(
466466
dt_native, copy=False
467467
)
468-
pp.shape = (count, 3)
469468
yield ptype, pp
470469

471470
def _get_smoothing_length(self, data_file, position_dtype, position_shape):

yt/frontends/http_stream/io.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import numpy as np
22

3+
from yt._maintenance.numpy2_compat import COPY_FALSE
34
from yt.funcs import mylog
45
from yt.utilities.io_handler import BaseParticleIOHandler
56
from yt.utilities.on_demand_imports import _requests as requests
@@ -36,26 +37,35 @@ def _read_particle_coords(self, chunks, ptf):
3637
for data_file in self._sorted_chunk_iterator(chunks):
3738
for ptype in ptf:
3839
s = self._open_stream(data_file, (ptype, "Coordinates"))
39-
c = np.frombuffer(s, dtype="float64")
40-
c.shape = (c.shape[0] / 3.0, 3)
40+
c_raw = np.frombuffer(s, dtype="float64")
41+
c = np.reshape(
42+
c_raw, (c_raw.shape[0] // 3, 3), **COPY_FALSE.get("reshape")
43+
)
4144
yield ptype, (c[:, 0], c[:, 1], c[:, 2]), 0.0
4245

4346
def _read_particle_fields(self, chunks, ptf, selector):
4447
# Now we have all the sizes, and we can allocate
4548
for data_file in self._sorted_chunk_iterator(chunks):
4649
for ptype, field_list in sorted(ptf.items()):
4750
s = self._open_stream(data_file, (ptype, "Coordinates"))
48-
c = np.frombuffer(s, dtype="float64")
49-
c.shape = (c.shape[0] / 3.0, 3)
51+
c_raw = np.frombuffer(s, dtype="float64")
52+
c = np.reshape(
53+
c_raw, c_raw.shape[0] // 3, 3, **COPY_FALSE.get("reshape")
54+
)
5055
mask = selector.select_points(c[:, 0], c[:, 1], c[:, 2], 0.0)
5156
del c
57+
del c_raw
5258
if mask is None:
5359
continue
5460
for field in field_list:
5561
s = self._open_stream(data_file, (ptype, field))
56-
c = np.frombuffer(s, dtype="float64")
62+
c_raw = np.frombuffer(s, dtype="float64")
5763
if field in self._vector_fields:
58-
c.shape = (c.shape[0] / 3.0, 3)
64+
c = np.reshape(
65+
c_raw,
66+
(c_raw.shape[0] // 3, 3),
67+
**COPY_FALSE.get("reshape"),
68+
)
5969
data = c[mask, ...]
6070
yield (ptype, field), data
6171

yt/frontends/open_pmd/io.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import numpy as np
44

5+
from yt._maintenance.numpy2_compat import COPY_FALSE
56
from yt.frontends.open_pmd.misc import get_component, is_const_component
67
from yt.geometry.selection_routines import GridSelector
78
from yt.utilities.io_handler import BaseIOHandler
@@ -189,13 +190,15 @@ def _read_fluid_selection(self, chunks, selector, fields, size):
189190
continue
190191
component = fname.replace("_", "/").replace("-", "_")
191192
if component.split("/")[0] not in grid.ftypes:
192-
data = np.full(grid.ActiveDimensions, 0, dtype=np.float64)
193+
data = np.full_like(mask, 0)
193194
else:
194-
data = get_component(ds, component, grid.findex, grid.foffset)
195+
data = np.reshape( # Workaround - casts a 2D (x,y) array to 3D (x,y,1)
196+
get_component(ds, component, grid.findex, grid.foffset),
197+
mask.shape,
198+
**COPY_FALSE.get("reshape"),
199+
)
200+
195201
# The following is a modified AMRGridPatch.select(...)
196-
data.shape = (
197-
mask.shape
198-
) # Workaround - casts a 2D (x,y) array to 3D (x,y,1)
199202
count = grid.count(selector)
200203
rv[field][ind[field] : ind[field] + count] = data[mask]
201204
ind[field] += count

0 commit comments

Comments
 (0)