Skip to content

Commit 754a52f

Browse files
Add sequence_to_ctypes_array to convert a sequence to a ctypes array (#3136)
Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com>
1 parent a166ae1 commit 754a52f

File tree

2 files changed

+57
-34
lines changed

2 files changed

+57
-34
lines changed

pygmt/clib/conversion.py

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
Functions to convert data types into ctypes friendly formats.
33
"""
44

5+
import ctypes as ctp
56
import warnings
7+
from collections.abc import Sequence
68

79
import numpy as np
810
from pygmt.exceptions import GMTInvalidInput
@@ -243,41 +245,55 @@ def as_c_contiguous(array):
243245
return array
244246

245247

246-
def kwargs_to_ctypes_array(argument, kwargs, dtype):
248+
def sequence_to_ctypes_array(sequence: Sequence, ctype, size: int) -> ctp.Array | None:
247249
"""
248-
Convert an iterable argument from kwargs into a ctypes array variable.
250+
Convert a sequence of numbers into a ctypes array variable.
249251
250-
If the argument is not present in kwargs, returns ``None``.
252+
If the sequence is ``None``, returns ``None``. Otherwise, returns a ctypes array.
253+
The function only works for sequences of numbers. For converting a sequence of
254+
strings, use ``strings_to_ctypes_array`` instead.
251255
252256
Parameters
253257
----------
254-
argument : str
255-
The name of the argument.
256-
kwargs : dict
257-
Dictionary of keyword arguments.
258-
dtype : ctypes type
259-
The ctypes array type (e.g., ``ctypes.c_double*4``)
258+
sequence
259+
The sequence to convert. If ``None``, returns ``None``. Otherwise, it must be a
260+
sequence (e.g., list, tuple, numpy array).
261+
ctype
262+
The ctypes type of the array (e.g., ``ctypes.c_int``).
263+
size
264+
The size of the array. If the sequence is smaller than the size, the remaining
265+
elements will be filled with zeros. If the sequence is larger than the size, an
266+
exception will be raised.
260267
261268
Returns
262269
-------
263-
ctypes_value : ctypes array or None
270+
ctypes_array
271+
The ctypes array variable.
264272
265273
Examples
266274
--------
267-
268-
>>> import ctypes as ct
269-
>>> value = kwargs_to_ctypes_array("bla", {"bla": [10, 10]}, ct.c_long * 2)
270-
>>> type(value)
271-
<class 'pygmt.clib.conversion.c_long_Array_2'>
272-
>>> should_be_none = kwargs_to_ctypes_array(
273-
... "swallow", {"bla": 1, "foo": [20, 30]}, ct.c_int * 2
274-
... )
275-
>>> print(should_be_none)
275+
>>> import ctypes as ctp
276+
>>> ctypes_array = sequence_to_ctypes_array([1, 2, 3], ctp.c_long, 3)
277+
>>> type(ctypes_array)
278+
<class 'pygmt.clib.conversion.c_long_Array_3'>
279+
>>> ctypes_array[:]
280+
[1, 2, 3]
281+
>>> ctypes_array = sequence_to_ctypes_array([1, 2], ctp.c_long, 5)
282+
>>> type(ctypes_array)
283+
<class 'pygmt.clib.conversion.c_long_Array_5'>
284+
>>> ctypes_array[:]
285+
[1, 2, 0, 0, 0]
286+
>>> ctypes_array = sequence_to_ctypes_array(None, ctp.c_long, 5)
287+
>>> print(ctypes_array)
276288
None
289+
>>> ctypes_array = sequence_to_ctypes_array([1, 2, 3, 4, 5, 6], ctp.c_long, 5)
290+
Traceback (most recent call last):
291+
...
292+
IndexError: invalid index
277293
"""
278-
if argument in kwargs:
279-
return dtype(*kwargs[argument])
280-
return None
294+
if sequence is None:
295+
return None
296+
return (ctype * size)(*sequence)
281297

282298

283299
def array_to_datetime(array):

pygmt/clib/session.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
array_to_datetime,
2020
as_c_contiguous,
2121
dataarray_to_matrix,
22-
kwargs_to_ctypes_array,
22+
sequence_to_ctypes_array,
2323
vectors_to_arrays,
2424
)
2525
from pygmt.clib.loading import load_libgmt
@@ -628,7 +628,17 @@ def call_module(self, module, args):
628628
f"Module '{module}' failed with status code {status}:\n{self._error_message}"
629629
)
630630

631-
def create_data(self, family, geometry, mode, **kwargs):
631+
def create_data(
632+
self,
633+
family,
634+
geometry,
635+
mode,
636+
dim=None,
637+
ranges=None,
638+
inc=None,
639+
registration="GMT_GRID_NODE_REG",
640+
pad=None,
641+
):
632642
"""
633643
Create an empty GMT data container.
634644
@@ -692,15 +702,13 @@ def create_data(self, family, geometry, mode, **kwargs):
692702
valid_modifiers=["GMT_GRID_IS_CARTESIAN", "GMT_GRID_IS_GEO"],
693703
)
694704
geometry_int = self._parse_constant(geometry, valid=GEOMETRIES)
695-
registration_int = self._parse_constant(
696-
kwargs.get("registration", "GMT_GRID_NODE_REG"), valid=REGISTRATIONS
697-
)
705+
registration_int = self._parse_constant(registration, valid=REGISTRATIONS)
698706

699707
# Convert dim, ranges, and inc to ctypes arrays if given (will be None
700708
# if not given to represent NULL pointers)
701-
dim = kwargs_to_ctypes_array("dim", kwargs, ctp.c_uint64 * 4)
702-
ranges = kwargs_to_ctypes_array("ranges", kwargs, ctp.c_double * 4)
703-
inc = kwargs_to_ctypes_array("inc", kwargs, ctp.c_double * 2)
709+
dim = sequence_to_ctypes_array(dim, ctp.c_uint64, 4)
710+
ranges = sequence_to_ctypes_array(ranges, ctp.c_double, 4)
711+
inc = sequence_to_ctypes_array(inc, ctp.c_double, 2)
704712

705713
# Use a NULL pointer (None) for existing data to indicate that the
706714
# container should be created empty. Fill it in later using put_vector
@@ -714,7 +722,7 @@ def create_data(self, family, geometry, mode, **kwargs):
714722
ranges,
715723
inc,
716724
registration_int,
717-
self._parse_pad(family, kwargs),
725+
self._parse_pad(family, pad),
718726
None,
719727
)
720728

@@ -723,15 +731,14 @@ def create_data(self, family, geometry, mode, **kwargs):
723731

724732
return data_ptr
725733

726-
def _parse_pad(self, family, kwargs):
734+
def _parse_pad(self, family, pad):
727735
"""
728736
Parse and return an appropriate value for pad if none is given.
729737
730738
Pad is a bit tricky because, for matrix types, pad control the matrix ordering
731739
(row or column major). Using the default pad will set it to column major and
732740
mess things up with the numpy arrays.
733741
"""
734-
pad = kwargs.get("pad", None)
735742
if pad is None:
736743
pad = 0 if "MATRIX" in family else self["GMT_PAD_DEFAULT"]
737744
return pad
@@ -1080,7 +1087,7 @@ def write_data(self, family, geometry, mode, wesn, output, data):
10801087
self["GMT_IS_FILE"],
10811088
geometry_int,
10821089
self[mode],
1083-
(ctp.c_double * 6)(*wesn),
1090+
sequence_to_ctypes_array(wesn, ctp.c_double, 6),
10841091
output.encode(),
10851092
data,
10861093
)

0 commit comments

Comments
 (0)