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.
@@ -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))