Skip to content

Commit

Permalink
Python: remove buffer protocol from docs, tests and examples
Browse files Browse the repository at this point in the history
because nanobind doesn't support it
  • Loading branch information
wojdyr committed Jul 30, 2024
1 parent 6749aea commit 0b21552
Show file tree
Hide file tree
Showing 8 changed files with 26 additions and 56 deletions.
20 changes: 3 additions & 17 deletions docs/grid.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,35 +121,21 @@ Values are accessed with functions get_value() and set_value():
>>> grid.get_value(-11, 13, 25)
7.0

.. _buffer_protocol:

The data can be also accessed through the
`buffer protocol <https://docs.python.org/3/c-api/buffer.html>`_.
It means that you can use it as a NumPy array (Fortran-style contiguous)
without copying the data:
The data can be also accessed as a NumPy array (Fortran-style contiguous),
without copying the data, through the `array` property:

.. doctest::
:skipif: numpy is None or sys.platform == 'win32'

>>> import numpy
>>> array = numpy.array(grid, copy=False)
>>> array = grid.array
>>> array.dtype
dtype('float32')
>>> array.shape
(12, 12, 12)
>>> numpy.argwhere(array == 7.0)
array([[1, 1, 1]])

The buffer protocol is not specific to NumPy -- any other Python library
that supports this protocol can directly access the grid data.
Alternatively, the grid can be viewed as a NumPy array through
the `array` property:

.. doctest::
:skipif: numpy is None or sys.platform == 'win32'

>>> grid.array.shape
(12, 12, 12)

Symmetry
--------
Expand Down
29 changes: 8 additions & 21 deletions docs/hkl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -392,23 +392,14 @@ It provides access to all the MTZ data as 2D NumPy array
>>> mtz.array.shape
(441, 8)

Another way to access MTZ data in Python is through the
`buffer protocol <https://docs.python.org/3/c-api/buffer.html>`_.
For example, here is equivalent of `mtz.array`:

.. doctest::
:skipif: numpy is None

>>> all_data = numpy.array(mtz, copy=False)

It helps to have labels on the columns. A good data structure for this
is Pandas DataFrame:

.. doctest::
:skipif: pandas is None

>>> import pandas
>>> df = pandas.DataFrame(data=all_data, columns=mtz.column_labels())
>>> df = pandas.DataFrame(data=mtz.array, columns=mtz.column_labels())
>>> # now we can handle columns using their labels:
>>> I_over_sigma = df['I'] / df['SIGI']

Expand Down Expand Up @@ -965,7 +956,7 @@ into a single DataFrame:

.. literalinclude:: ../examples/merge_mtz_mmcif.py
:language: python
:lines: 4-28
:lines: 4-27

We want to compare the FP column from the MTZ file and the F_meas_au column
from mmCIF. We start with plotting one against the other:
Expand All @@ -976,7 +967,7 @@ from mmCIF. We start with plotting one against the other:

.. literalinclude:: ../examples/merge_mtz_mmcif.py
:language: python
:lines: 31-38
:lines: 30-37

The numbers are similar, but not exactly equal.
Let us check how the difference between the two values depends on the
Expand All @@ -990,7 +981,7 @@ so we can use it for coloring.

.. literalinclude:: ../examples/merge_mtz_mmcif.py
:language: python
:lines: 41-
:lines: 40-

Apparently, some scaling has been applied. The scaling is anisotropic
and is the strongest along the *k* axis.
Expand Down Expand Up @@ -1457,8 +1448,7 @@ To check if the size is big enough you can call:
>>> rblock.data_fits_into([52,6,18])
False

To access the data you can use either the buffer protocol
(:ref:`in the same way <buffer_protocol>` as in the Grid class),
To access the data you can use either the NumPy interface (`grid.array`),
or getter and setter:

.. doctest::
Expand Down Expand Up @@ -1572,8 +1562,7 @@ tool.

>>> size = rblock.get_size_for_hkl(sample_rate=2.6)
>>> full = rblock.get_f_phi_on_grid('pdbx_FWT', 'pdbx_PHWT', size)
>>> array = numpy.array(full, copy=False)
>>> complex_map = numpy.fft.ifftn(array.conj())
>>> complex_map = numpy.fft.ifftn(full.array.conj())
>>> scale_factor = complex_map.size / full.unit_cell.volume
>>> real_map = numpy.real(complex_map) * scale_factor
>>> round(real_map[1][2][3], 5)
Expand All @@ -1591,8 +1580,7 @@ using complex-to-real FFT on a half of the data:
:skipif: numpy is None

>>> half = rblock.get_f_phi_on_grid('pdbx_FWT', 'pdbx_PHWT', size, half_l=True)
>>> array = numpy.array(half, copy=False)
>>> real_map = numpy.fft.irfftn(array.conj()) * scale_factor
>>> real_map = numpy.fft.irfftn(half.array.conj()) * scale_factor
>>> round(real_map[1][2][3], 5)
-0.40554
>>> round(real_map.std(), 5)
Expand Down Expand Up @@ -1646,8 +1634,7 @@ The grid can be accessed as NumPy 3D array:
.. doctest::
:skipif: numpy is None

>>> array = numpy.array(_, copy=False)
>>> round(array.std(), 5)
>>> round(_.array.std(), 5)
0.66338

.. _map_from_rblock:
Expand Down
4 changes: 2 additions & 2 deletions examples/maskcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def maskcheck(mask_path, coor_path, output_diff_map=None, verbose=False):


def compare_mask_arrays(grid1, grid2, verbose):
arr1 = numpy.array(grid1, copy=False)
arr2 = numpy.array(grid2, copy=False)
arr1 = grid1.array
arr2 = grid2.array
if arr1.shape != arr2.shape:
sys.exit('Different grid sizes %s and %s. Exiting.' %
(arr1.shape, arr2.shape))
Expand Down
4 changes: 2 additions & 2 deletions examples/maskdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

def maskdiff(path1, path2):
mask1 = gemmi.read_ccp4_mask(path1, setup=True)
arr1 = numpy.array(mask1.grid, copy=False)
arr1 = mask1.grid.array
mask2 = gemmi.read_ccp4_mask(path2, setup=True)
arr2 = numpy.array(mask2.grid, copy=False)
arr2 = mask2.grid.array
print("Size: %d x %d x %d and %d x %d x %d" % (arr1.shape + arr2.shape))
if arr1.shape != arr2.shape:
sys.exit("Different sizes. Exiting.")
Expand Down
3 changes: 1 addition & 2 deletions examples/merge_mtz_mmcif.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@

# make DataFrame from MTZ file
mtz = gemmi.read_mtz_file(MTZ_PATH)
mtz_data = numpy.array(mtz, copy=False)
mtz_df = pandas.DataFrame(data=mtz_data, columns=mtz.column_labels())
mtz_df = pandas.DataFrame(data=mtz.array, columns=mtz.column_labels())
# (optional) store Miller indices as integers
mtz_df = mtz_df.astype({label: 'int32' for label in 'HKL'})

Expand Down
2 changes: 1 addition & 1 deletion examples/patterson_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# toxd_aupatt.map is generated by $CCP4/examples/unix/runnable/patterson
ccp4 = gemmi.read_ccp4_map('/tmp/wojdyr/toxd_aupatt.map', setup=True)
arr = numpy.array(ccp4.grid, copy=False)
arr = ccp4.grid.array
x = numpy.linspace(0, ccp4.grid.unit_cell.a, num=arr.shape[0], endpoint=False)
y = numpy.linspace(0, ccp4.grid.unit_cell.b, num=arr.shape[1], endpoint=False)
X, Y = numpy.meshgrid(x, y, indexing='ij')
Expand Down
2 changes: 1 addition & 1 deletion tests/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def test_reading(self):
m.grid.symmetrize_max()
self.assertEqual(m.grid.get_value(60-3, 24//2+4, 60-5), 90)
if numpy:
arr = numpy.array(m.grid, copy=False)
arr = m.grid.array
self.assertEqual(arr.shape, (60, 24, 60))
self.assertEqual(arr[3][4][5], 90)
grid2 = gemmi.FloatGrid(arr)
Expand Down
18 changes: 8 additions & 10 deletions tests/test_hkl.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def fft_test(self, data, f, phi, size, order=gemmi.AxisOrder.XYZ):
self.assertTrue(data.data_fits_into(size))
grid_full = data.get_f_phi_on_grid(f, phi, size, half_l=False, order=order)
self.assertEqual(grid_full.axis_order, order)
array_full = numpy.array(grid_full, copy=False)
array_full = grid_full.array
map1 = gemmi.transform_f_phi_grid_to_map(grid_full)
self.assertEqual(map1.axis_order, order)
map2 = numpy.fft.ifftn(array_full.conj())
Expand Down Expand Up @@ -84,7 +84,6 @@ def test_read_write(self):
os.remove(out_name)
self.assertEqual(mtz2.spacegroup.hm, 'P 1 21 1')
if numpy is not None:
assert_numpy_equal(self, numpy.array(mtz, copy=False), mtz.array)
assert_numpy_equal(self, mtz.array, mtz2.array)

def test_remove_and_add_column(self):
Expand All @@ -96,17 +95,16 @@ def test_remove_and_add_column(self):
ncol = len(mtz.columns)
if numpy is None:
return
assert_numpy_equal(self, col.array, numpy.array(col, copy=False))
arr = col.array.copy()
mtz_data = numpy.array(mtz, copy=True)
mtz_data = mtz.array.copy()
self.assertEqual(mtz_data.shape, (mtz.nreflections, ncol))
mtz.remove_column(col_idx)
self.assertEqual(len(mtz.columns), ncol-1)
self.assertEqual(numpy.array(mtz, copy=False).shape,
self.assertEqual(mtz.array.shape,
(mtz.nreflections, ncol-1))
col = mtz.add_column(col_name, 'I', dataset_id=0, pos=col_idx)
numpy.array(col, copy=False)[:] = arr
assert_numpy_equal(self, mtz_data, numpy.array(mtz, copy=False))
col.array[:] = arr
assert_numpy_equal(self, mtz_data, mtz.array)

def asu_data_test(self, grid):
asu = grid.prepare_asu_data()
Expand All @@ -130,8 +128,8 @@ def test_f_phi_grid(self):
order=gemmi.AxisOrder.ZYX)
if numpy is None:
continue
array1 = numpy.array(grid1, copy=False)
array2 = numpy.array(grid2, copy=False)
array1 = grid1.array
array2 = grid2.array
self.assertTrue((array2 == array1.transpose(2,1,0)).all())
self.asu_data_test(grid1)

Expand All @@ -146,7 +144,7 @@ def test_value_grid(self):
if numpy is None:
return
asu = gemmi.ReciprocalAsu(mtz.spacegroup)
mtz_data = numpy.array(mtz, copy=False)
mtz_data = mtz.array
fp_idx = mtz.column_labels().index('FP')
fp_map = {}
for row in mtz_data:
Expand Down

0 comments on commit 0b21552

Please sign in to comment.