pyharm 0.4.11__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyharm/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ """
2
+ PyHarm is a Python wrapper for CHarm.
3
+ """
4
+
5
+
6
+ # start delvewheel patch
7
+ def _delvewheel_patch_1_13_0():
8
+ import os
9
+ if os.path.isdir(libs_dir := os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'pyharm.libs'))):
10
+ os.add_dll_directory(libs_dir)
11
+
12
+
13
+ _delvewheel_patch_1_13_0()
14
+ del _delvewheel_patch_1_13_0
15
+ # end delvewheel patch
16
+
17
+ import os as _os
18
+ from ._lib import _load_lib
19
+
20
+ # Name of the shared CHarm library to load
21
+ _libcharmname = 'libcharm'
22
+
23
+ # Directory of "_libcharmname"
24
+ _libcharmdir = _os.path.join(_os.path.dirname(__file__), '')
25
+
26
+ # Load the shared CHarm library
27
+ _libcharm = _load_lib(_libcharmdir, _libcharmname)
28
+
29
+ # Prefix to be added to the CHarm function names. Depends on the format of
30
+ # floating point numbers used to compile CHarm (single or double precision).
31
+ _CHARM = 'charm_'
32
+
33
+ # Prefix to be added to the PyHarm functions when calling "__repr__" methods
34
+ _pyharm = 'pyharm'
35
+
36
+ # The "err" module is intentionally not imported, as users do not interact with
37
+ # it in PyHarm.
38
+ from . import crd, glob, gfm, integ, leg, misc, sha, shc, shs
39
+ __all__ = ['crd', 'glob', 'gfm', 'integ', 'leg', 'misc', 'sha', 'shc', 'shs']
pyharm/_check_types.py ADDED
@@ -0,0 +1,177 @@
1
+ """
2
+ Internal checks of data types passed by users to PyHarm.
3
+ """
4
+
5
+
6
+ import numpy as _np
7
+ from ._data_types import _pyharm_ints, _pyharm_flt_all, _pyharm_flt
8
+
9
+
10
+ def _check_int_scalar(x, s):
11
+ """
12
+ Checks whether "x" is an instance of one of the classes in `_pyharm_ints`
13
+ and if so, whether it is a scalar in order to represent the quantity named
14
+ `s`.
15
+
16
+ Parameters
17
+ ----------
18
+ x : any data type
19
+ Data to check
20
+ s : str
21
+ String with the name of the data that is being checked with the first
22
+ letter being uppercase (e.g. 'The \'crd_type\' variable').
23
+ """
24
+
25
+ if not isinstance(x, _pyharm_ints):
26
+ msg = f'{s} must be an instance of one of the following classes: '
27
+ msg += f'{_pyharm_ints}.'
28
+ raise TypeError(msg)
29
+
30
+ if not _np.isscalar(x):
31
+ raise ValueError(f'{s} must be a scalar.')
32
+
33
+ return
34
+
35
+
36
+ def _check_nonneg_int_scalar(x, s):
37
+ """
38
+ The same as `_check_int_scalar` but additionally checks whether `x` is
39
+ non-negative.
40
+
41
+ Parameters
42
+ ----------
43
+ x : any data type
44
+ Data to check
45
+ s : str
46
+ String with the name of the data that is being checked with the first
47
+ letter being uppercase (e.g. 'The \'crd_type\' variable').
48
+ """
49
+
50
+ _check_int_scalar(x, s)
51
+ if x < 0:
52
+ raise ValueError(f'{s} must not be negative.')
53
+
54
+ return
55
+
56
+
57
+ def _check_flt_scalar(x, s):
58
+ """
59
+ Checks whether `x` is an instance of one of the classes in
60
+ `_pyharm_flt_all` and if so, whether it is a scalar to represent
61
+ the quantity named `s`.
62
+
63
+ Parameters
64
+ ----------
65
+ x : any data type
66
+ Data to check
67
+ s : str
68
+ String with the name of the data that is being checked with the first
69
+ letter being uppercase (e.g. 'Scaling parameter').
70
+ """
71
+
72
+ if not isinstance(x, _pyharm_flt_all):
73
+ msg = f'{s} must be an instance of one of the following classes: '
74
+ msg += f'{_pyharm_flt_all}.'
75
+ raise TypeError(msg)
76
+
77
+ if not _np.isscalar(x):
78
+ raise ValueError(f'{s} must be a scalar.')
79
+
80
+ return
81
+
82
+
83
+ def _check_deg_ord(x, do):
84
+ """
85
+ Checks whether `x` satisfies all conditions to represent harmonic degree
86
+ (`do == 'degree'`) or harmonic order (`do == 'order'`).
87
+
88
+ Parameters
89
+ ----------
90
+ x : any data type
91
+ do : str
92
+ Either 'degree' or 'order'
93
+ """
94
+
95
+ # Check whether "x" is of a correct integer class and whether it is
96
+ # a scalar
97
+ _check_int_scalar(x, f'Spherical harmonic {do}')
98
+ if do != 'degree' and do != 'order':
99
+ msg = f'The \'do\' variable must be set either to \'degree\' '
100
+ msg += f'or to \'order\'.'
101
+ raise ValueError(msg)
102
+
103
+ if x < 0:
104
+ msg = f'Spherical harmonic {do} cannot be smaller than zero.'
105
+ raise ValueError(msg)
106
+
107
+ return
108
+
109
+
110
+ def _check_radius(x):
111
+ """
112
+ Checks whether `x` satisfies all conditions to represent spherical radius.
113
+
114
+ Parameters
115
+ ----------
116
+ x : any data type
117
+ """
118
+
119
+ _check_flt_scalar(x, 'Spherical radius')
120
+
121
+ if x <= 0.0:
122
+ raise ValueError('Spherical radius must be larger than zero.')
123
+
124
+ return
125
+
126
+
127
+ def _check_flt_ndarray(x, n, s):
128
+ """
129
+ Checks whether `x` satisfies all conditions to represent an nD floating
130
+ point numpy array that can safely enter CHarm in order to represent
131
+ quantity named `s`.
132
+
133
+ Parameters
134
+ ----------
135
+ x : any data type
136
+ Object to be checked
137
+ n : integer
138
+ Required number of dimensions of the nD floating point array
139
+ s : str
140
+ String with the name of the object that is being checked with the first
141
+ letter being uppercase (e.g., 'Latitudes')
142
+ """
143
+
144
+ # Must be a "_np.ndarray"
145
+ if not isinstance(x, _np.ndarray):
146
+ msg = f'{s} must be an instance of one of the following classes: '
147
+ msg += f'{_np.ndarray}.'
148
+ raise TypeError(msg)
149
+
150
+ # Must be of the correct "dtype"
151
+ if x.dtype != _pyharm_flt:
152
+ msg = f'{s} must be of the following \'dtype\': {_pyharm_flt}.'
153
+ raise ValueError(msg)
154
+
155
+ # Must have the correct number of dimensions
156
+ if x.ndim != n:
157
+ msg = f'{s} must be of the following dimension: \'ndim={n}\'.'
158
+ raise ValueError(msg)
159
+
160
+ # Must be C-contiguous
161
+ if not x.flags.c_contiguous:
162
+ msg = f'{s} must be C-contiguous in memory (see the \'order\' flag '
163
+ msg += f'of numpy arrays).'
164
+ raise ValueError(msg)
165
+
166
+ return
167
+
168
+
169
+ def _check_pointer(pointer, func_name, lib_name):
170
+
171
+ if not pointer:
172
+ msg = f'Function call to \'{func_name}\' from the CHarm library '
173
+ msg += f'\'{lib_name}\' returned a NULL pointer.'
174
+ raise ValueError(msg)
175
+
176
+ return
177
+
pyharm/_constants.py ADDED
@@ -0,0 +1,62 @@
1
+ """
2
+ Generated automatically. Do not modify!
3
+ """
4
+
5
+ _globals = {
6
+ 'CHARM_SHC__NMAX_MODEL': -18446744073709551615,
7
+ 'CHARM_SHC__NMAX_ERROR': -18446744073709551614,
8
+ 'CHARM_SHC_WRITE_N': 0,
9
+ 'CHARM_SHC_WRITE_M': 1,
10
+ 'CHARM_CRD_CELL_GRID': -2,
11
+ 'CHARM_CRD_CELL_SCATTERED': -1,
12
+ 'CHARM_CRD_POINT_SCATTERED': 1,
13
+ 'CHARM_CRD_POINT_GRID': 2,
14
+ 'CHARM_CRD_POINT_GRID_GL': 3,
15
+ 'CHARM_CRD_POINT_GRID_DH1': 4,
16
+ 'CHARM_CRD_POINT_GRID_DH2': 5,
17
+ 'CHARM_VERSION_MAJOR': 0,
18
+ 'CHARM_VERSION_MINOR': 4,
19
+ 'CHARM_VERSION_PATCH': 11,
20
+ 'CHARM_VERSION_STRING': '0.4.11',
21
+ 'CHARM_WITH_MPFR': 1,
22
+ 'CHARM_WITH_MPI': 1,
23
+ 'CHARM_ERR_MAX_FILE': 4096,
24
+ 'CHARM_ERR_MAX_FUNC': 256,
25
+ 'CHARM_ERR_MAX_MSG': 4096,
26
+ 'CHARM_ERR_MAX_LEVEL': 10,
27
+ 'CHARM_ERR_MALLOC_FAILURE': 'Memory allocation failure.',
28
+ 'CHARM_ERR_FFTW_INIT_FAILURE': 'FFTW failed to initialize OpenMP threads.',
29
+ 'CHARM_FAILURE': -1,
30
+ 'CHARM_SUCCESS': 0,
31
+ 'CHARM_EMEM': 1,
32
+ 'CHARM_EFUNCARG': 2,
33
+ 'CHARM_EFILEIO': 3,
34
+ 'CHARM_EFFTWINIT': 4,
35
+ 'CHARM_EMPIPROCESS': 5,
36
+ 'CHARM_SHA_CELL_AQ': 0,
37
+ 'CHARM_LEG_PMNJ': 0,
38
+ 'CHARM_LEG_PMJN': 1,
39
+ 'CHARM_GFM_Q00': 0,
40
+ 'CHARM_GFM_Q10': 10,
41
+ 'CHARM_GFM_Q11': 11,
42
+ 'CHARM_GFM_Q20': 20,
43
+ 'CHARM_GFM_Q21': 21,
44
+ 'CHARM_GFM_Q22': 22,
45
+ 'CHARM_GFM_NEAR_ZONE': 1,
46
+ 'CHARM_GFM_FAR_ZONE': -1,
47
+ 'CHARM_EXTERN': None,
48
+ 'CHARM_CDECL': None,
49
+ 'GRAD_0': 0,
50
+ 'GRAD_1': -1,
51
+ 'GRAD_2': -2,
52
+ 'GRAD_LL': 0,
53
+ 'GRAD_LR': 1,
54
+ 'GRAD_LP': 2,
55
+ 'GRAD_RR': 3,
56
+ 'GRAD_RP': 4,
57
+ 'GRAD_PP': 5,
58
+ 'GRAD_P': 2,
59
+ 'GRAD_L': 0,
60
+ 'GRAD_R': 1,
61
+ }
62
+
pyharm/_data_types.py ADDED
@@ -0,0 +1,46 @@
1
+ """
2
+ Defines data types that:
3
+
4
+ * users can pass to PyHarm,
5
+ * PyHarm passes to CHarm, and
6
+ * CHarm returns to PyHarm.
7
+ """
8
+
9
+ import numpy as _np
10
+ import ctypes as _ct
11
+
12
+ # Data types that user can pass to PyHarm to represent integers
13
+ _pyharm_ints = (int, _np.integer)
14
+
15
+ # Data types that user can pass to PyHarm to represent floating point
16
+ # numbers. Note that "PY_FLOAT" already contains the comma, so no comma
17
+ # between "float," and "_np.float64".
18
+ _pyharm_flt_all = (float, _np.float64,) + _pyharm_ints
19
+
20
+ # Floating point data types for numpy ndarrays. A *single* data type must be
21
+ # defined here! The reason is that it avoids making any internal copies of
22
+ # data entered by the user, as this could be a problem for large arrays.
23
+ _pyharm_flt = _np.float64
24
+
25
+ # This is the data type, to which PyHarm converts the C's "double" (or "float"
26
+ # if in single precision) returned by CHarm
27
+ _charm_flt = _np.float64
28
+
29
+ # C's "int" entering CHarm
30
+ _ct_int = _ct.c_int
31
+
32
+ # C's "unsigned int" entering CHarm
33
+ _ct_uint = _ct.c_uint
34
+
35
+ # C's "size_t" entering CHarm
36
+ _ct_size_t = _ct.c_size_t
37
+
38
+ # C's "long int" entering CHarm
39
+ _ct_long = _ct.c_long
40
+
41
+ # C's "unsigned long int" entering CHarm
42
+ _ct_ulong = _ct.c_ulong
43
+
44
+ # C's "double" (or "float" if in single precision) entering CHarm
45
+ _ct_flt = _ct.c_double
46
+
pyharm/_encoding.py ADDED
@@ -0,0 +1,45 @@
1
+ """
2
+ Defines default encoding for strings passed to CHarm and a routine returing
3
+ ctype pointer to a Python string.
4
+ """
5
+
6
+ # Default encoding
7
+ _default_encoding = 'utf-8'
8
+
9
+ # Default treatment of characters outside "_default_encoding" when calling the
10
+ # "decode" method on strings of the "bytes" class
11
+ _default_encoding_errors = 'replace'
12
+
13
+
14
+ import ctypes as _ct
15
+
16
+
17
+ def _str_ptr(string, encoding=_default_encoding):
18
+ """
19
+ Returns a ctypes pointer to `string` encoded with `encoding`.
20
+ """
21
+
22
+ if isinstance(string, str):
23
+ return _ct.create_string_buffer(string.encode(encoding))
24
+ elif string is None:
25
+ return None
26
+ else:
27
+ raise ValueError('\'string\' must be of \'str\' type or \'None\'.')
28
+
29
+
30
+ def _bytes_decode(string,
31
+ encoding=_default_encoding,
32
+ errors=_default_encoding_errors):
33
+ """
34
+ Returns `str` by encoding `string` of class `bytes` using
35
+ `_default_encoding` and treating encoding errors by
36
+ `_default_encoding_errors`.
37
+ """
38
+
39
+ if isinstance(string, bytes):
40
+ return string.decode(encoding=_default_encoding,
41
+ errors=_default_encoding_errors)
42
+ elif string is None:
43
+ return None
44
+ else:
45
+ raise ValueError('\'string\' must be of \'bytes\' type.')
pyharm/_err.py ADDED
@@ -0,0 +1,194 @@
1
+ # Implements the "err" module of PyHarm.
2
+ #
3
+ # NOTE: Unlike in CHarm, users do *not* interact with the "err" module in
4
+ # PyHarm!
5
+
6
+
7
+ import ctypes as _ct
8
+ from . import _libcharm, _libcharmname, _CHARM
9
+ from ._get_module_constants import _get_module_constants
10
+ from ._constants import _globals
11
+ from ._data_types import _ct_int, _ct_size_t
12
+ from ._check_types import _check_pointer
13
+ from ._encoding import _bytes_decode
14
+
15
+
16
+ # Get the module constants from "_constants.py" and add them to the module's
17
+ # namespace
18
+ _get_module_constants('CHARM_ERR_')
19
+
20
+
21
+ # Character to represent the C's null character
22
+ NULL_CHAR = '\0'
23
+
24
+
25
+ class _Err(_ct.Structure):
26
+ """
27
+ Private class to represent the `charm_err` structure of CHarm.
28
+ """
29
+
30
+ _fields_ = [('level', _ct_size_t),
31
+ ('file', _ct.POINTER(_ct.POINTER(_ct.c_char))),
32
+ ('line', _ct.POINTER(_ct_size_t)),
33
+ ('func', _ct.POINTER(_ct.POINTER(_ct.c_char))),
34
+ ('code', _ct_int),
35
+ ('msg', _ct.POINTER(_ct.c_char)),
36
+ ('saturated', _ct.c_bool)]
37
+
38
+
39
+ class CHarmError(Exception):
40
+ pass
41
+
42
+
43
+ def init():
44
+ """
45
+ Creates an empty instance of the `_Err` class.
46
+
47
+ Returns
48
+ -------
49
+ out : _Err
50
+ An instance of the `_Err` class.
51
+ """
52
+
53
+ func = _libcharm[_CHARM + 'err_init']
54
+ func.restype = _ct.POINTER(_Err)
55
+ func.argtypes = None
56
+
57
+ err = func()
58
+ _check_pointer(err, _CHARM + 'err_init', _libcharmname)
59
+
60
+ return err
61
+
62
+
63
+ def free(a):
64
+ """
65
+ Frees the memory associated with an instance `a` of the `_Err` class.
66
+
67
+ Parameters
68
+ ----------
69
+
70
+ a : _Err
71
+ An instance of the `_Err` class to be freed.
72
+ """
73
+
74
+ if a is not None:
75
+ func = _libcharm[_CHARM + 'err_free']
76
+ func.restype = None
77
+ func.argtypes = [_ct.POINTER(_Err)]
78
+ func(a)
79
+
80
+ return
81
+
82
+
83
+ def isempty(a):
84
+ """
85
+ Returns `True` if an instance of the `_Err` class `a` is empty or `False`
86
+ otherwise.
87
+
88
+ Parameters
89
+ ----------
90
+ a : _Err
91
+ An instance of the `_Err` class to be checked
92
+
93
+ Returns
94
+ -------
95
+ out : bool
96
+ `True` if `a` is empty, `False` otherwise.
97
+ """
98
+
99
+ func = _libcharm[_CHARM + 'err_isempty']
100
+ func.restype = _ct.c_bool
101
+ func.argtypes = [_ct.POINTER(_Err)]
102
+
103
+ return func(a)
104
+
105
+
106
+ def reset(a):
107
+ """
108
+ Resets an instance `a` of the `_Err` class.
109
+
110
+ Parameters
111
+ ----------
112
+ a : _Err
113
+ An instance of the `_Err` class to be reset.
114
+ """
115
+
116
+ func = _libcharm[_CHARM + 'err_reset']
117
+ func.restype = None
118
+ func.argtypes = [_ct.POINTER(_Err)]
119
+
120
+ func(a)
121
+
122
+ return
123
+
124
+
125
+ def handler(a, terminate):
126
+ """
127
+ This is a Python implementation of the CHarm's `charm_err_handler`
128
+ function. This Python implementation allows better error handling in
129
+ Python.
130
+
131
+ Parameters
132
+ ----------
133
+ a : _Err
134
+ An instance of the `_Err` class to be handled.
135
+ terminate : bool
136
+ If `True` and `a` is not empty, the execution of the program
137
+ terminates. If `False` and `a` is not empty, the error is printed and
138
+ the program continues in code execution. If `False` and `a` is empty,
139
+ nothing happens.
140
+ """
141
+
142
+ if isempty(a):
143
+ return
144
+
145
+ e = a.contents
146
+
147
+ err_msg = f'\n'
148
+ err_msg += f'Error code: {e.code} ' \
149
+ 'Traceback (most recent call last)\n'
150
+ err_msg += f'\n'
151
+
152
+ for l in range(e.level - 1, -1, -1):
153
+
154
+ f = _bytes_decode(e.file[l][:MAX_FILE])
155
+ idx1 = f.find(NULL_CHAR)
156
+ if idx1 == -1:
157
+ raise ValueError(f'The \'file[{l}]\' string in the '
158
+ f'\'{_CHARM}err\' structure that was passed by '
159
+ f'CHarm to PyHarm does not contain the '
160
+ f'null character. Cannot read the CHarm error.')
161
+
162
+ func = _bytes_decode(e.func[l][:MAX_FUNC])
163
+ idx2 = func.find(NULL_CHAR)
164
+ if idx2 == -1:
165
+ raise ValueError(f'The \'func[{l}]\' string in the '
166
+ f'\'{_CHARM}err\' structure that was passed by '
167
+ f'CHarm to PyHarm does not contain the '
168
+ f'null character. Cannot read the CHarm error.')
169
+
170
+ err_msg += f' File \'{f[:idx1]}\', line: {e.line[l]}, '
171
+ err_msg += f'function: \'{func[:idx2]}\'\n\n'
172
+
173
+ if e.saturated:
174
+ err_msg += ' Warning: The error structure is '
175
+ err_msg += 'saturated. Most recent function calls may therefore '
176
+ err_msg += 'not be reported.\n'
177
+ err_msg += f'\n'
178
+
179
+ msg = _bytes_decode(e.msg[:MAX_MSG])
180
+ idx = msg.find(NULL_CHAR)
181
+ if idx == -1:
182
+ raise ValueError(f'The \'msg\' string in the '
183
+ f'\'{_CHARM}err\' structure that was passed by '
184
+ f'CHarm to PyHarm does not contain the '
185
+ f'null character. Cannot read the CHarm error.')
186
+ err_msg += f'Error message: {msg[:idx]}\n'
187
+
188
+ if terminate:
189
+ raise CHarmError(err_msg)
190
+ else:
191
+ print(err_msg)
192
+
193
+ return
194
+
pyharm/_formatting.py ADDED
@@ -0,0 +1,6 @@
1
+ """
2
+ Defines default formatting when writting floating point numbers to text files.
3
+ """
4
+
5
+ # Default formatting
6
+ _default_formatting = '%0.18e'
@@ -0,0 +1,17 @@
1
+ import numpy as _np
2
+ from ._data_types import _pyharm_flt
3
+
4
+
5
+ def _get_empty_array():
6
+ """
7
+ Private function to return something that PyHarm considers to be an empty
8
+ numpy array.
9
+
10
+ Returns
11
+ -------
12
+ out : numpy floating point array
13
+ Empty numpy ndarray
14
+ """
15
+
16
+ return _np.array([], dtype=_pyharm_flt, order='C')
17
+
@@ -0,0 +1,29 @@
1
+ import sys
2
+ from ._constants import _globals
3
+
4
+
5
+ def _get_module_constants(prefix):
6
+ """
7
+ Private function to dynamically add global constants from the `_globals`
8
+ dictionary inside `_constants.py` to the module's namespace. The variables
9
+ are added under a modified name following the pattern:
10
+
11
+ CHARM_CRD_CELLS_GRID --> CELLS_GRID
12
+
13
+ where `prefix = 'CHARM_CRD_'` is the input variable.
14
+
15
+ Parameters
16
+ ----------
17
+ prefix : str
18
+ Prefix to be removed from the keys in `_globals` before adding to the
19
+ module's namespace. Should be, e.g., 'CHARM_CRD_', 'CHARM_SHA_', etc.
20
+ """
21
+
22
+ namespace = sys._getframe(1).f_globals
23
+
24
+ for name, value in _globals.items():
25
+ if name.startswith(prefix):
26
+ namespace[name[len(prefix):]] = value
27
+
28
+ return
29
+
pyharm/_lib.py ADDED
@@ -0,0 +1,29 @@
1
+ import os
2
+ import numpy as _np
3
+
4
+
5
+ def _load_lib(libdir, libname):
6
+ """
7
+ Private function to load a shared library 'libname' from directory
8
+ 'libdir'.
9
+
10
+ Parameters
11
+ ----------
12
+ libdir : str
13
+ Path to the shared library
14
+ libname : str
15
+ Name of the shared library
16
+
17
+ Returns
18
+ -------
19
+ out : ctypes.CDLL
20
+ The CHarm shared library
21
+ """
22
+
23
+ try:
24
+ return _np.ctypeslib.load_library(libname, libdir)
25
+ except OSError:
26
+ fullpath = os.path.join(libdir, libname)
27
+ msg = f'Failed to load the shared library \'{fullpath}\'.'
28
+ raise OSError(msg)
29
+