parallel-sparse-tools 0.2.4__cp313-cp313-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.
- parallel_sparse_tools/__init__.py +1 -0
- parallel_sparse_tools/_version.py +1 -0
- parallel_sparse_tools/expm_multiply_parallel_core/__init__.py +1 -0
- parallel_sparse_tools/expm_multiply_parallel_core/expm_multiply_parallel_core.py +353 -0
- parallel_sparse_tools/expm_multiply_parallel_core/expm_multiply_parallel_wrapper.cp313-win_amd64.pyd +0 -0
- parallel_sparse_tools/expm_multiply_parallel_core/expm_multiply_parallel_wrapper.cpp +41962 -0
- parallel_sparse_tools/expm_multiply_parallel_core/generate_source.py +222 -0
- parallel_sparse_tools/expm_multiply_parallel_core/source/csr_utils.h +164 -0
- parallel_sparse_tools/expm_multiply_parallel_core/source/expm_multiply_parallel_impl.h +314 -0
- parallel_sparse_tools/matvec/__init__.py +9 -0
- parallel_sparse_tools/matvec/_oputils/oputils_impl.h +4359 -0
- parallel_sparse_tools/matvec/_oputils.cp313-win_amd64.pyd +0 -0
- parallel_sparse_tools/matvec/_oputils.cpp +18269 -0
- parallel_sparse_tools/matvec/generate_oputils.py +502 -0
- parallel_sparse_tools/matvec/matvec_core.py +96 -0
- parallel_sparse_tools-0.2.4.dist-info/METADATA +24 -0
- parallel_sparse_tools-0.2.4.dist-info/RECORD +20 -0
- parallel_sparse_tools-0.2.4.dist-info/WHEEL +5 -0
- parallel_sparse_tools-0.2.4.dist-info/licenses/LICENSE +28 -0
- parallel_sparse_tools-0.2.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._version import __version__ as __version__
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.4"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .expm_multiply_parallel_core import ExpmMultiplyParallel
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
from functools import cached_property
|
|
2
|
+
from scipy.sparse.linalg import LinearOperator, onenormest, aslinearoperator
|
|
3
|
+
from .expm_multiply_parallel_wrapper import (
|
|
4
|
+
_wrapper_expm_multiply,
|
|
5
|
+
_wrapper_expm_multiply_batch,
|
|
6
|
+
_wrapper_csr_trace,
|
|
7
|
+
_wrapper_csr_1_norm,
|
|
8
|
+
)
|
|
9
|
+
from scipy.sparse import eye
|
|
10
|
+
from scipy.sparse.linalg._expm_multiply import _fragment_3_1, _exact_1_norm
|
|
11
|
+
import scipy.sparse as _sp
|
|
12
|
+
import numpy as _np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExpmMultiplyParallel(object):
|
|
16
|
+
"""Implements `scipy.sparse.linalg.expm_multiply()` for *openmp*.
|
|
17
|
+
|
|
18
|
+
Notes
|
|
19
|
+
-----
|
|
20
|
+
* this is a wrapper over custom c++ code.
|
|
21
|
+
* the `dtype` input need not be the same dtype as `A` or `a`; however, it must be possible to cast the result of `a*A` to this `dtype`.
|
|
22
|
+
* consider the special case of real-time evolution with a purely-imaginary Hamiltonian, in which case `a=-1j*time` and `A` are both complex-valued, while the resulting matrix exponential is real-valued: in such cases, one can use either one of
|
|
23
|
+
|
|
24
|
+
>>> expm_multiply_parallel( (1j*H.tocsr()).astype(np.float64), a=-1.0, dtype=np.float64)`
|
|
25
|
+
|
|
26
|
+
and
|
|
27
|
+
|
|
28
|
+
>>> expm_multiply_parallel( H.tocsr(), a=-1.0j, dtype=np.complex128)
|
|
29
|
+
|
|
30
|
+
The more efficient way to compute the matrix exponential in this case is to use a real-valued `dtype`.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
|
|
36
|
+
This example shows how to construct the `expm_multiply_parallel` object.
|
|
37
|
+
|
|
38
|
+
Further code snippets can be found in the examples for the function methods of the class.
|
|
39
|
+
The code snippet below initiates the class, and is required to run the example codes for the function methods.
|
|
40
|
+
|
|
41
|
+
.. literalinclude:: ../../doc_examples/expm_multiply_parallel-example.py
|
|
42
|
+
:linenos:
|
|
43
|
+
:language: python
|
|
44
|
+
:lines: 7-30
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, A, a=1.0, dtype=None, copy=False):
|
|
49
|
+
"""Initializes `expm_multiply_parallel`.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
A : {array_like, scipy.sparse matrix}
|
|
54
|
+
The operator (matrix) whose exponential is to be calculated.
|
|
55
|
+
a : scalar, optional
|
|
56
|
+
scalar value multiplying generator matrix :math:`A` in matrix exponential: :math:`\\mathrm{e}^{aA}`.
|
|
57
|
+
dtype : numpy.dtype, optional
|
|
58
|
+
data type specified for the total operator :math:`\\mathrm{e}^{aA}`. Default is: `numpy.result_type(A.dtype,min_scalar_type(a),float64)`.
|
|
59
|
+
copy : bool, optional
|
|
60
|
+
if `True` the matrix is copied otherwise the matrix is stored by reference.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
if _np.array(a).ndim == 0:
|
|
64
|
+
self._a = a
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError("a must be scalar value.")
|
|
67
|
+
|
|
68
|
+
self._A = _sp.csr_matrix(A, copy=copy)
|
|
69
|
+
|
|
70
|
+
if A.shape[0] != A.shape[1]:
|
|
71
|
+
raise ValueError("A must be a square matrix.")
|
|
72
|
+
|
|
73
|
+
a_dtype_min = _np.min_scalar_type(self._a)
|
|
74
|
+
|
|
75
|
+
# use double precision by default.
|
|
76
|
+
if dtype is None:
|
|
77
|
+
self._dtype = _np.result_type(A.dtype, a_dtype_min, _np.float64)
|
|
78
|
+
else:
|
|
79
|
+
min_dtype = _np.result_type(A.dtype, a_dtype_min, _np.float32)
|
|
80
|
+
if not _np.can_cast(min_dtype, dtype):
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"dtype not sufficient to represent a*A to at least float32 precision."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self._dtype = dtype
|
|
86
|
+
|
|
87
|
+
tol = _np.finfo(self._dtype).eps / 2
|
|
88
|
+
tol_dtype = _np.finfo(self._dtype).eps.dtype
|
|
89
|
+
self._tol = _np.array(tol, dtype=tol_dtype)
|
|
90
|
+
|
|
91
|
+
mu = (
|
|
92
|
+
_wrapper_csr_trace(self._A.indptr, self._A.indices, self._A.data)
|
|
93
|
+
/ self._A.shape[0]
|
|
94
|
+
)
|
|
95
|
+
self._mu = _np.array(mu, dtype=self._dtype)
|
|
96
|
+
self._A_1_norm = _wrapper_csr_1_norm(
|
|
97
|
+
self._A.indptr, self._A.indices, self._A.data, -self._mu
|
|
98
|
+
)
|
|
99
|
+
self._calculate_partition()
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def a(self):
|
|
103
|
+
"""scalar: value multiplying generator matrix :math:`A` in matrix exponential: :math:`\\mathrm{e}^{aA}`"""
|
|
104
|
+
return self._a
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def A(self):
|
|
108
|
+
"""scipy.sparse.csr_matrix: csr_matrix to be exponentiated."""
|
|
109
|
+
return self._A
|
|
110
|
+
|
|
111
|
+
def set_a(self, a, dtype=None):
|
|
112
|
+
"""Sets the value of the property `a`.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
a : scalar
|
|
117
|
+
new value of `a`.
|
|
118
|
+
dtype : numpy.dtype, optional
|
|
119
|
+
dtype specified for this operator. Default is: result_type(A.dtype,min_scalar_type(a),float64)
|
|
120
|
+
|
|
121
|
+
Examples
|
|
122
|
+
--------
|
|
123
|
+
|
|
124
|
+
.. literalinclude:: ../../doc_examples/expm_multiply_parallel-example.py
|
|
125
|
+
:linenos:
|
|
126
|
+
:language: python
|
|
127
|
+
:lines: 32-35
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
if _np.array(a).ndim == 0:
|
|
132
|
+
self._a = a
|
|
133
|
+
|
|
134
|
+
a_dtype_min = _np.min_scalar_type(self._a)
|
|
135
|
+
|
|
136
|
+
# use double precision by default.
|
|
137
|
+
if dtype is None:
|
|
138
|
+
self._dtype = _np.result_type(self._A.dtype, a_dtype_min, _np.float64)
|
|
139
|
+
else:
|
|
140
|
+
min_dtype = _np.result_type(self.A.dtype, a_dtype_min, _np.float32)
|
|
141
|
+
if not _np.can_cast(min_dtype, dtype):
|
|
142
|
+
raise ValueError(
|
|
143
|
+
"dtype not sufficient to represent a*A to at least float32 precision."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self._dtype = dtype
|
|
147
|
+
|
|
148
|
+
tol = _np.finfo(self._dtype).eps / 2
|
|
149
|
+
tol_dtype = _np.finfo(self._dtype).eps.dtype
|
|
150
|
+
self._tol = _np.array(tol, dtype=tol_dtype)
|
|
151
|
+
self._mu = _np.array(self._mu, dtype=self._dtype)
|
|
152
|
+
|
|
153
|
+
self._calculate_partition()
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError("expecting 'a' to be scalar.")
|
|
156
|
+
|
|
157
|
+
def dot(self, v, work_array=None, overwrite_v=False, tol=None):
|
|
158
|
+
"""Calculates the action of :math:`\\mathrm{e}^{aA}` on a vector :math:`v`.
|
|
159
|
+
|
|
160
|
+
Examples
|
|
161
|
+
--------
|
|
162
|
+
|
|
163
|
+
.. literalinclude:: ../../doc_examples/expm_multiply_parallel-example.py
|
|
164
|
+
:linenos:
|
|
165
|
+
:language: python
|
|
166
|
+
:lines: 37-
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
v : contiguous numpy.ndarray, 1d or 2d array
|
|
171
|
+
array to apply :math:`\\mathrm{e}^{aA}` on.
|
|
172
|
+
work_array : contiguous numpy.ndarray, optional
|
|
173
|
+
array can be any shape but must contain 2*v.size contiguous elements.
|
|
174
|
+
This array is used as temporary memory space for the underlying c-code. This saves extra memory allocation for function operations.
|
|
175
|
+
overwrite_v : bool, optoinal
|
|
176
|
+
if set to `True`, the data in `v` is overwritten by the function. This saves extra memory allocation for the results.
|
|
177
|
+
tol: float, optoinal
|
|
178
|
+
tolerance value used to truncate Taylor expansion of matrix exponential.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
numpy.ndarray
|
|
183
|
+
result of :math:`\\mathrm{e}^{aA}v`.
|
|
184
|
+
|
|
185
|
+
If `overwrite_v = True` the dunction returns `v` with the data overwritten, otherwise the result is stored in a new array.
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
v = _np.asarray(v)
|
|
189
|
+
|
|
190
|
+
if v.ndim > 2:
|
|
191
|
+
raise ValueError("array must have ndim <= 2.")
|
|
192
|
+
|
|
193
|
+
if v.shape[0] != self._A.shape[1]:
|
|
194
|
+
raise ValueError("dimension mismatch {}, {}".format(self._A.shape, v.shape))
|
|
195
|
+
|
|
196
|
+
v_dtype = _np.result_type(self._dtype, v.dtype)
|
|
197
|
+
|
|
198
|
+
if overwrite_v:
|
|
199
|
+
if v_dtype != v.dtype:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
"if overwrite_v is True, the input array must match correct output dtype for matrix multiplication."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if not v.flags["CARRAY"]:
|
|
205
|
+
raise TypeError("input array must a contiguous and writable.")
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
v = v.astype(v_dtype, order="C", copy=True)
|
|
209
|
+
|
|
210
|
+
if work_array is None:
|
|
211
|
+
if v.ndim == 1:
|
|
212
|
+
work_array = _np.zeros((2 * self._A.shape[0],), dtype=v.dtype)
|
|
213
|
+
else:
|
|
214
|
+
work_array = _np.zeros(
|
|
215
|
+
(2 * self._A.shape[0], v.shape[1]), dtype=v.dtype
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
work_array = work_array.ravel(order="A")
|
|
219
|
+
|
|
220
|
+
if work_array.size != 2 * v.size:
|
|
221
|
+
raise ValueError(
|
|
222
|
+
"work_array must have twice the number of elements as in v."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if work_array.dtype != v_dtype:
|
|
226
|
+
raise ValueError(
|
|
227
|
+
"work_array must be array of dtype which matches the result of the matrix-vector multiplication."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
a = _np.array(self._a, dtype=v_dtype)
|
|
231
|
+
mu = _np.array(self._mu, dtype=v_dtype)
|
|
232
|
+
if tol is not None:
|
|
233
|
+
tol = _np.array(tol, dtype=mu.real.dtype)
|
|
234
|
+
else:
|
|
235
|
+
tol = _np.array(self._tol, dtype=mu.real.dtype)
|
|
236
|
+
if v.ndim == 1:
|
|
237
|
+
_wrapper_expm_multiply(
|
|
238
|
+
self._A.indptr,
|
|
239
|
+
self._A.indices,
|
|
240
|
+
self._A.data,
|
|
241
|
+
self._s,
|
|
242
|
+
self._m_star,
|
|
243
|
+
tol,
|
|
244
|
+
mu,
|
|
245
|
+
a,
|
|
246
|
+
v,
|
|
247
|
+
work_array.ravel(),
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
work_array = work_array.reshape((-1, v.shape[1]))
|
|
251
|
+
_wrapper_expm_multiply_batch(
|
|
252
|
+
self._A.indptr,
|
|
253
|
+
self._A.indices,
|
|
254
|
+
self._A.data,
|
|
255
|
+
self._s,
|
|
256
|
+
self._m_star,
|
|
257
|
+
tol,
|
|
258
|
+
mu,
|
|
259
|
+
a,
|
|
260
|
+
v,
|
|
261
|
+
work_array,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return v
|
|
265
|
+
|
|
266
|
+
def _calculate_partition(self):
|
|
267
|
+
if _np.abs(self._a) * self._A_1_norm == 0:
|
|
268
|
+
self._m_star, self._s = 0, 1
|
|
269
|
+
else:
|
|
270
|
+
ell = 2
|
|
271
|
+
norm_info = LazyOperatorNormInfo(
|
|
272
|
+
self._A, self._A_1_norm, self._a, self._mu, self._dtype, ell=ell
|
|
273
|
+
)
|
|
274
|
+
self._m_star, self._s = _fragment_3_1(norm_info, 1, self._tol, ell=ell)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
##### code below is copied from scipy.sparse.linalg._expm_multiply_core and modified slightly.
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def matvec_p(v, A, a, mu, p):
|
|
281
|
+
for i in range(p):
|
|
282
|
+
v = a * (A.dot(v) - mu * v)
|
|
283
|
+
|
|
284
|
+
return v
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class LazyOperatorNormInfo:
|
|
288
|
+
"""
|
|
289
|
+
Information about an operator is lazily computed.
|
|
290
|
+
|
|
291
|
+
The information includes the exact 1-norm of the operator,
|
|
292
|
+
in addition to estimates of 1-norms of powers of the operator.
|
|
293
|
+
This uses the notation of Computing the Action (2011).
|
|
294
|
+
This class is specialized enough to probably not be of general interest
|
|
295
|
+
outside of this module.
|
|
296
|
+
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(self, A, A_1_norm, a, mu, dtype, ell=2):
|
|
300
|
+
"""
|
|
301
|
+
Provide the operator and some norm-related information.
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
A : linear operator
|
|
306
|
+
The operator of interest.
|
|
307
|
+
A_1_norm : float
|
|
308
|
+
The exact 1-norm of A.
|
|
309
|
+
ell : int, optional
|
|
310
|
+
A technical parameter controlling norm estimation quality.
|
|
311
|
+
|
|
312
|
+
"""
|
|
313
|
+
self._A = A
|
|
314
|
+
self._a = a
|
|
315
|
+
self._mu = mu
|
|
316
|
+
self._dtype = dtype
|
|
317
|
+
self._A_1_norm = A_1_norm
|
|
318
|
+
self._ell = ell
|
|
319
|
+
self._d = {}
|
|
320
|
+
|
|
321
|
+
def onenorm(self):
|
|
322
|
+
"""
|
|
323
|
+
Compute the exact 1-norm.
|
|
324
|
+
"""
|
|
325
|
+
return _np.abs(self._a) * self._A_1_norm
|
|
326
|
+
|
|
327
|
+
@cached_property
|
|
328
|
+
def linear_operator(self):
|
|
329
|
+
def matvec(v):
|
|
330
|
+
return self._a * (self._A.dot(v) - self._mu * v)
|
|
331
|
+
|
|
332
|
+
def rmatvec(v):
|
|
333
|
+
return _np.conj(self._a) * (self._A.transpose().conj().dot(v) - _np.conj(self._mu) * v)
|
|
334
|
+
|
|
335
|
+
return LinearOperator(
|
|
336
|
+
self._A.shape, dtype=self._dtype, matvec=matvec, rmatvec=rmatvec
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def d(self, p):
|
|
340
|
+
"""
|
|
341
|
+
Lazily estimate d_p(A) ~= || A^p ||^(1/p) where ||.|| is the 1-norm.
|
|
342
|
+
"""
|
|
343
|
+
if p not in self._d:
|
|
344
|
+
est = onenormest(self.linear_operator**p)
|
|
345
|
+
self._d[p] = est ** (1.0 / p)
|
|
346
|
+
|
|
347
|
+
return self._d[p]
|
|
348
|
+
|
|
349
|
+
def alpha(self, p):
|
|
350
|
+
"""
|
|
351
|
+
Lazily compute max(d(p), d(p+1)).
|
|
352
|
+
"""
|
|
353
|
+
return max(self.d(p), self.d(p + 1))
|