apsg 1.3.0__py3-none-any.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.
- AUTHORS.md +9 -0
- CHANGELOG.md +304 -0
- CONTRIBUTING.md +91 -0
- apsg/__init__.py +104 -0
- apsg/config.py +214 -0
- apsg/database/__init__.py +23 -0
- apsg/database/_alchemy.py +609 -0
- apsg/database/_sdbread.py +284 -0
- apsg/decorator/__init__.py +5 -0
- apsg/decorator/_decorator.py +43 -0
- apsg/feature/__init__.py +79 -0
- apsg/feature/_container.py +1808 -0
- apsg/feature/_geodata.py +702 -0
- apsg/feature/_paleomag.py +425 -0
- apsg/feature/_statistics.py +430 -0
- apsg/feature/_tensor2.py +550 -0
- apsg/feature/_tensor3.py +1108 -0
- apsg/helpers/__init__.py +28 -0
- apsg/helpers/_helper.py +7 -0
- apsg/helpers/_math.py +46 -0
- apsg/helpers/_notation.py +119 -0
- apsg/math/__init__.py +6 -0
- apsg/math/_matrix.py +406 -0
- apsg/math/_vector.py +590 -0
- apsg/pandas/__init__.py +27 -0
- apsg/pandas/_pandas_api.py +507 -0
- apsg/plotting/__init__.py +25 -0
- apsg/plotting/_fabricplot.py +563 -0
- apsg/plotting/_paleomagplots.py +71 -0
- apsg/plotting/_plot_artists.py +551 -0
- apsg/plotting/_projection.py +326 -0
- apsg/plotting/_roseplot.py +360 -0
- apsg/plotting/_stereogrid.py +332 -0
- apsg/plotting/_stereonet.py +992 -0
- apsg/shell.py +35 -0
- apsg-1.3.0.dist-info/AUTHORS.md +9 -0
- apsg-1.3.0.dist-info/METADATA +141 -0
- apsg-1.3.0.dist-info/RECORD +40 -0
- apsg-1.3.0.dist-info/WHEEL +4 -0
- apsg-1.3.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from scipy.special import gamma as gamma_fun
|
|
5
|
+
from scipy.special import iv as modified_bessel_2ndkind
|
|
6
|
+
from scipy.special import ivp as modified_bessel_2ndkind_derivative
|
|
7
|
+
from scipy.stats import uniform
|
|
8
|
+
from scipy.stats import norm as gauss
|
|
9
|
+
from scipy.optimize import minimize_scalar
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def vonMisesFisher(mu, kappa, num_samples):
|
|
13
|
+
"""Generate N samples from von Mises Fisher
|
|
14
|
+
distribution around center mu in R^N with concentration kappa.
|
|
15
|
+
|
|
16
|
+
Adopted from https://github.com/jasonlaska/spherecluster
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def _sample_weight(kappa):
|
|
20
|
+
"""Rejection sampling scheme for sampling distance from center on
|
|
21
|
+
surface of the sphere.
|
|
22
|
+
"""
|
|
23
|
+
b = 2 / (np.sqrt(4.0 * kappa**2 + 4) + 2 * kappa)
|
|
24
|
+
x = (1.0 - b) / (1.0 + b)
|
|
25
|
+
c = kappa * x + 2 * np.log(1 - x**2)
|
|
26
|
+
|
|
27
|
+
while True:
|
|
28
|
+
z = np.random.beta(1, 1)
|
|
29
|
+
w = (1.0 - (1.0 + b) * z) / (1.0 - (1.0 - b) * z)
|
|
30
|
+
u = np.random.uniform(low=0, high=1)
|
|
31
|
+
if kappa * w + 2 * np.log(1.0 - x * w) - c >= np.log(u):
|
|
32
|
+
return w
|
|
33
|
+
|
|
34
|
+
def _sample_orthonormal_to(mu):
|
|
35
|
+
"""Sample point on sphere orthogonal to mu."""
|
|
36
|
+
v = np.random.randn(mu.shape[0])
|
|
37
|
+
proj_mu_v = mu * np.dot(mu, v) / np.linalg.norm(mu)
|
|
38
|
+
orthto = v - proj_mu_v
|
|
39
|
+
return orthto / np.linalg.norm(orthto)
|
|
40
|
+
|
|
41
|
+
result = np.zeros((num_samples, 3))
|
|
42
|
+
for nn in range(num_samples):
|
|
43
|
+
# sample offset from center (on sphere) with spread kappa
|
|
44
|
+
w = _sample_weight(kappa)
|
|
45
|
+
|
|
46
|
+
# sample a point v on the unit sphere that's orthogonal to mu
|
|
47
|
+
v = _sample_orthonormal_to(mu)
|
|
48
|
+
|
|
49
|
+
# compute new point
|
|
50
|
+
result[nn, :] = v * np.sqrt(1.0 - w**2) + w * mu
|
|
51
|
+
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def estimate_k(features):
|
|
56
|
+
# objective function to be minimized
|
|
57
|
+
def obj_fun(k):
|
|
58
|
+
W = np.exp(
|
|
59
|
+
k * (np.abs(np.dot(np.asarray(features), np.asarray(features).T)))
|
|
60
|
+
) * (k / (4 * math.pi * math.sinh(k + 1e-9)))
|
|
61
|
+
np.fill_diagonal(W, 0.0)
|
|
62
|
+
return -np.log(W.sum(axis=0)).sum()
|
|
63
|
+
|
|
64
|
+
if len(features) > 1:
|
|
65
|
+
return minimize_scalar(obj_fun, bounds=(0.1, len(features)), method="bounded").x
|
|
66
|
+
else:
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class KentDistribution(object):
|
|
71
|
+
"""
|
|
72
|
+
The algorithms here are partially based on methods described in:
|
|
73
|
+
[The Fisher-Bingham Distribution on the Sphere, John T. Kent
|
|
74
|
+
Journal of the Royal Statistical Society. Series B (Methodological)
|
|
75
|
+
Vol. 44, No. 1 (1982), pp. 71-80 Published by: Wiley
|
|
76
|
+
Article Stable URL: http://www.jstor.org/stable/2984712]
|
|
77
|
+
|
|
78
|
+
Implementation by Daniel Fraenkel
|
|
79
|
+
https://github.com/edfraenkel/kent_distribution
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
minimum_value_for_kappa = 1e-6
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def create_matrix_H(theta, phi):
|
|
86
|
+
return np.array(
|
|
87
|
+
[
|
|
88
|
+
[np.cos(theta), -np.sin(theta), 0.0],
|
|
89
|
+
[
|
|
90
|
+
np.sin(theta) * np.cos(phi),
|
|
91
|
+
np.cos(theta) * np.cos(phi),
|
|
92
|
+
-np.sin(phi),
|
|
93
|
+
],
|
|
94
|
+
[np.sin(theta) * np.sin(phi), np.cos(theta) * np.sin(phi), np.cos(phi)],
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def create_matrix_Ht(theta, phi):
|
|
100
|
+
return np.transpose(KentDistribution.create_matrix_H(theta, phi))
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def create_matrix_K(psi):
|
|
104
|
+
return np.array(
|
|
105
|
+
[
|
|
106
|
+
[1.0, 0.0, 0.0],
|
|
107
|
+
[0.0, np.cos(psi), -np.sin(psi)],
|
|
108
|
+
[0.0, np.sin(psi), np.cos(psi)],
|
|
109
|
+
]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def create_matrix_Kt(psi):
|
|
114
|
+
return np.transpose(KentDistribution.create_matrix_K(psi))
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def create_matrix_Gamma(theta, phi, psi):
|
|
118
|
+
H = KentDistribution.create_matrix_H(theta, phi)
|
|
119
|
+
K = KentDistribution.create_matrix_K(psi)
|
|
120
|
+
return np.inner(H, np.transpose(K))
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def create_matrix_Gammat(theta, phi, psi):
|
|
124
|
+
return np.transpose(KentDistribution.create_matrix_Gamma(theta, phi, psi))
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def spherical_coordinates_to_gammas(theta, phi, psi):
|
|
128
|
+
Gamma = KentDistribution.create_matrix_Gamma(theta, phi, psi)
|
|
129
|
+
gamma1 = Gamma[:, 0]
|
|
130
|
+
gamma2 = Gamma[:, 1]
|
|
131
|
+
gamma3 = Gamma[:, 2]
|
|
132
|
+
return (gamma1, gamma2, gamma3)
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def gamma1_to_spherical_coordinates(gamma1):
|
|
136
|
+
theta = np.arccos(gamma1[0])
|
|
137
|
+
phi = np.arctan2(gamma1[2], gamma1[1])
|
|
138
|
+
return (theta, phi)
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def gammas_to_spherical_coordinates(gamma1, gamma2):
|
|
142
|
+
(theta, phi) = KentDistribution.gamma1_to_spherical_coordinates(gamma1)
|
|
143
|
+
Ht = KentDistribution.create_matrix_Ht(theta, phi)
|
|
144
|
+
u = np.inner(Ht, np.reshape(gamma2, (1, 3)))
|
|
145
|
+
psi = np.arctan2(u[2][0], u[1][0])
|
|
146
|
+
return (theta, phi, psi)
|
|
147
|
+
|
|
148
|
+
def __init__(self, gamma1, gamma2, gamma3, kappa, beta):
|
|
149
|
+
self.gamma1 = np.array(gamma1, dtype=np.float64)
|
|
150
|
+
self.gamma2 = np.array(gamma2, dtype=np.float64)
|
|
151
|
+
self.gamma3 = np.array(gamma3, dtype=np.float64)
|
|
152
|
+
self.kappa = float(kappa)
|
|
153
|
+
self.beta = float(beta)
|
|
154
|
+
|
|
155
|
+
(
|
|
156
|
+
self.theta,
|
|
157
|
+
self.phi,
|
|
158
|
+
self.psi,
|
|
159
|
+
) = KentDistribution.gammas_to_spherical_coordinates(self.gamma1, self.gamma2)
|
|
160
|
+
|
|
161
|
+
for gamma in (gamma1, gamma2, gamma3):
|
|
162
|
+
assert len(gamma) == 3
|
|
163
|
+
|
|
164
|
+
self._cached_rvs = np.array([], dtype=np.float64)
|
|
165
|
+
self._cached_rvs.shape = (0, 3)
|
|
166
|
+
|
|
167
|
+
def __repr__(self):
|
|
168
|
+
return "kent(%s, %s, %s, %s, %s)" % (
|
|
169
|
+
self.theta,
|
|
170
|
+
self.phi,
|
|
171
|
+
self.psi,
|
|
172
|
+
self.kappa,
|
|
173
|
+
self.beta,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def Gamma(self):
|
|
178
|
+
return self.create_matrix_Gamma(self.theta, self.phi, self.psi)
|
|
179
|
+
|
|
180
|
+
def normalize(self, cache=dict(), return_num_iterations=False):
|
|
181
|
+
"""
|
|
182
|
+
Returns the normalization constant of the Kent distribution.
|
|
183
|
+
The proportional error may be expected not to be greater than
|
|
184
|
+
1E-11.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
(k, b) = (self.kappa, self.beta)
|
|
188
|
+
if (k, b) not in cache:
|
|
189
|
+
G = gamma_fun
|
|
190
|
+
Imb2 = modified_bessel_2ndkind
|
|
191
|
+
result = 0.0
|
|
192
|
+
j = 0
|
|
193
|
+
if b == 0.0:
|
|
194
|
+
result = (0.5 * k) ** (-2 * j - 0.5) * Imb2(2 * j + 0.5, k)
|
|
195
|
+
result /= G(j + 1)
|
|
196
|
+
result *= G(j + 0.5)
|
|
197
|
+
else:
|
|
198
|
+
|
|
199
|
+
while True:
|
|
200
|
+
a = np.exp(
|
|
201
|
+
np.log(b) * 2 * j + np.log(0.5 * k) * (-2 * j - 0.5)
|
|
202
|
+
) * Imb2(2 * j + 0.5, k)
|
|
203
|
+
a /= G(j + 1)
|
|
204
|
+
a *= G(j + 0.5)
|
|
205
|
+
result += a
|
|
206
|
+
|
|
207
|
+
j += 1
|
|
208
|
+
if abs(a) < abs(result) * 1e-12 and j > 5:
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
cache[k, b] = 2 * np.pi * result
|
|
212
|
+
if return_num_iterations:
|
|
213
|
+
return (cache[k, b], j)
|
|
214
|
+
else:
|
|
215
|
+
return cache[k, b]
|
|
216
|
+
|
|
217
|
+
def log_normalize(self, return_num_iterations=False):
|
|
218
|
+
"""
|
|
219
|
+
Returns the logarithm of the normalization constant.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
if return_num_iterations:
|
|
223
|
+
(normalize, num_iter) = self.normalize(return_num_iterations=True)
|
|
224
|
+
return (np.log(normalize), num_iter)
|
|
225
|
+
else:
|
|
226
|
+
return np.log(self.normalize())
|
|
227
|
+
|
|
228
|
+
def pdf_max(self, normalize=True):
|
|
229
|
+
return np.exp(self.log_pdf_max(normalize))
|
|
230
|
+
|
|
231
|
+
def log_pdf_max(self, normalize=True):
|
|
232
|
+
"""
|
|
233
|
+
Returns the maximum value of the log(pdf)
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
if self.beta == 0.0:
|
|
237
|
+
x = 1
|
|
238
|
+
else:
|
|
239
|
+
x = self.kappa * 1.0 / (2 * self.beta)
|
|
240
|
+
if x > 1.0:
|
|
241
|
+
x = 1
|
|
242
|
+
fmax = self.kappa * x + self.beta * (1 - x**2)
|
|
243
|
+
if normalize:
|
|
244
|
+
return fmax - self.log_normalize()
|
|
245
|
+
else:
|
|
246
|
+
return fmax
|
|
247
|
+
|
|
248
|
+
def pdf(self, xs, normalize=True):
|
|
249
|
+
"""
|
|
250
|
+
Returns the pdf of the kent distribution for 3D vectors that
|
|
251
|
+
are stored in xs which must be an array of N x 3 or N x M x 3
|
|
252
|
+
N x M x P x 3 etc.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
return np.exp(self.log_pdf(xs, normalize))
|
|
256
|
+
|
|
257
|
+
def log_pdf(self, xs, normalize=True):
|
|
258
|
+
"""
|
|
259
|
+
Returns the log(pdf) of the kent distribution.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
axis = len(np.shape(xs)) - 1
|
|
263
|
+
g1x = np.sum(self.gamma1 * xs, axis)
|
|
264
|
+
g2x = np.sum(self.gamma2 * xs, axis)
|
|
265
|
+
g3x = np.sum(self.gamma3 * xs, axis)
|
|
266
|
+
(k, b) = (self.kappa, self.beta)
|
|
267
|
+
|
|
268
|
+
f = k * g1x + b * (g2x**2 - g3x**2)
|
|
269
|
+
if normalize:
|
|
270
|
+
return f - self.log_normalize()
|
|
271
|
+
else:
|
|
272
|
+
return f
|
|
273
|
+
|
|
274
|
+
def pdf_prime(self, xs, normalize=True):
|
|
275
|
+
"""
|
|
276
|
+
Returns the derivative of the pdf with respect to kappa and beta.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
return self.pdf(xs, normalize) * self.log_pdf_prime(xs, normalize)
|
|
280
|
+
|
|
281
|
+
def log_pdf_prime(self, xs, normalize=True):
|
|
282
|
+
"""
|
|
283
|
+
Returns the derivative of the log(pdf) with respect to kappa and beta.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
axis = len(np.shape(xs)) - 1
|
|
287
|
+
g1x = np.sum(self.gamma1 * xs, axis)
|
|
288
|
+
g2x = np.sum(self.gamma2 * xs, axis)
|
|
289
|
+
g3x = np.sum(self.gamma3 * xs, axis)
|
|
290
|
+
|
|
291
|
+
dfdk = g1x
|
|
292
|
+
dfdb = g2x**2 - g3x**2
|
|
293
|
+
df = np.array([dfdk, dfdb])
|
|
294
|
+
if normalize:
|
|
295
|
+
return np.transpose(np.transpose(df) - self.log_normalize_prime())
|
|
296
|
+
else:
|
|
297
|
+
return df
|
|
298
|
+
|
|
299
|
+
def normalize_prime(self, cache=dict(), return_num_iterations=False):
|
|
300
|
+
"""
|
|
301
|
+
Returns the derivative of the normalization factor with respect
|
|
302
|
+
to kappa and beta.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
(k, b) = (self.kappa, self.beta)
|
|
306
|
+
if (k, b) not in cache:
|
|
307
|
+
G = gamma_fun
|
|
308
|
+
Imb2 = modified_bessel_2ndkind
|
|
309
|
+
(dcdk, dcdb) = (0.0, 0.0)
|
|
310
|
+
j = 0
|
|
311
|
+
if b == 0:
|
|
312
|
+
dcdk = (
|
|
313
|
+
G(j + 0.5)
|
|
314
|
+
/ G(j + 1)
|
|
315
|
+
* ((-0.5 * j - 0.125) * k ** (-2 * j - 1.5))
|
|
316
|
+
* Imb2(2 * j + 0.5, k)
|
|
317
|
+
)
|
|
318
|
+
dcdk += (
|
|
319
|
+
G(j + 0.5)
|
|
320
|
+
/ G(j + 1)
|
|
321
|
+
* (0.5 * k) ** (-2 * j - 0.5)
|
|
322
|
+
* modified_bessel_2ndkind_derivative(2 * j + 0.5, k, 1)
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
dcdb = 0.0
|
|
326
|
+
else:
|
|
327
|
+
while True:
|
|
328
|
+
dk = (
|
|
329
|
+
(-1 * j - 0.25)
|
|
330
|
+
* np.exp(np.log(b) * 2 * j + np.og(0.5 * k) * (-2 * j - 1.5))
|
|
331
|
+
* Imb2(2 * j + 0.5, k)
|
|
332
|
+
)
|
|
333
|
+
dk += np.exp(
|
|
334
|
+
np.log(b) * 2 * j + np.log(0.5 * k) * (-2 * j - 0.5)
|
|
335
|
+
) * modified_bessel_2ndkind_derivative(2 * j + 0.5, k, 1)
|
|
336
|
+
dk /= G(j + 1)
|
|
337
|
+
dk *= G(j + 0.5)
|
|
338
|
+
|
|
339
|
+
db = (
|
|
340
|
+
2
|
|
341
|
+
* j
|
|
342
|
+
* np.exp(
|
|
343
|
+
np.log(b) * (2 * j - 1) + np.log(0.5 * k) * (-2 * j - 0.5)
|
|
344
|
+
)
|
|
345
|
+
* Imb2(2 * j + 0.5, k)
|
|
346
|
+
)
|
|
347
|
+
db /= G(j + 1)
|
|
348
|
+
db *= G(j + 0.5)
|
|
349
|
+
dcdk += dk
|
|
350
|
+
dcdb += db
|
|
351
|
+
|
|
352
|
+
j += 1
|
|
353
|
+
if (
|
|
354
|
+
abs(dk) < abs(dcdk) * 1e-12
|
|
355
|
+
and abs(db) < abs(dcdb) * 1e-12
|
|
356
|
+
and j > 5
|
|
357
|
+
):
|
|
358
|
+
break
|
|
359
|
+
|
|
360
|
+
# print("dc", dcdk, dcdb, "(", k, b)
|
|
361
|
+
|
|
362
|
+
cache[k, b] = 2 * np.pi * np.array([dcdk, dcdb])
|
|
363
|
+
if return_num_iterations:
|
|
364
|
+
return (cache[k, b], j)
|
|
365
|
+
else:
|
|
366
|
+
return cache[k, b]
|
|
367
|
+
|
|
368
|
+
def log_normalize_prime(self, return_num_iterations=False):
|
|
369
|
+
"""
|
|
370
|
+
Returns the derivative of the logarithm of the normalization factor.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
if return_num_iterations:
|
|
374
|
+
(normalize_prime, num_iter) = self.normalize_prime(
|
|
375
|
+
return_num_iterations=True
|
|
376
|
+
)
|
|
377
|
+
return (normalize_prime / self.normalize(), num_iter)
|
|
378
|
+
else:
|
|
379
|
+
return self.normalize_prime() / self.normalize()
|
|
380
|
+
|
|
381
|
+
def log_likelihood(self, xs):
|
|
382
|
+
"""
|
|
383
|
+
Returns the log likelihood for xs.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
retval = self.log_pdf(xs)
|
|
387
|
+
return np.sum(retval, len(np.shape(retval)) - 1)
|
|
388
|
+
|
|
389
|
+
def log_likelihood_prime(self, xs):
|
|
390
|
+
"""
|
|
391
|
+
Returns the derivative with respect to kappa and beta of the log
|
|
392
|
+
likelihood for xs.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
retval = self.log_pdf_prime(xs)
|
|
396
|
+
if len(np.shape(retval)) == 1:
|
|
397
|
+
return retval
|
|
398
|
+
else:
|
|
399
|
+
return np.sum(retval, len(np.shape(retval)) - 1)
|
|
400
|
+
|
|
401
|
+
def _rvs_helper(self):
|
|
402
|
+
num_samples = 10000
|
|
403
|
+
xs = gauss(0, 1).rvs((num_samples, 3))
|
|
404
|
+
xs = np.divide(xs, np.reshape(np.linalg.norm(xs, axis=1), (num_samples, 1)))
|
|
405
|
+
pvalues = self.pdf(xs, normalize=False)
|
|
406
|
+
fmax = self.pdf_max(normalize=False)
|
|
407
|
+
return xs[uniform(0, fmax).rvs(num_samples) < pvalues]
|
|
408
|
+
|
|
409
|
+
def rvs(self, n_samples=None):
|
|
410
|
+
"""
|
|
411
|
+
Returns random samples from the Kent distribution by rejection sampling.
|
|
412
|
+
May become inefficient for large kappas.
|
|
413
|
+
|
|
414
|
+
The returned random samples are 3D unit vectors.
|
|
415
|
+
If n_samples == None then a single sample x is returned with shape (3,)
|
|
416
|
+
If n_samples is an integer value N then N samples are returned in an array with shape (N, 3)
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
num_samples = 1 if n_samples is None else n_samples
|
|
420
|
+
rvs = self._cached_rvs
|
|
421
|
+
while len(rvs) < num_samples:
|
|
422
|
+
new_rvs = self._rvs_helper()
|
|
423
|
+
rvs = np.concatenate([rvs, new_rvs])
|
|
424
|
+
if n_samples is None:
|
|
425
|
+
self._cached_rvs = rvs[1:]
|
|
426
|
+
return rvs[0]
|
|
427
|
+
else:
|
|
428
|
+
self._cached_rvs = rvs[num_samples:]
|
|
429
|
+
retval = rvs[:num_samples]
|
|
430
|
+
return retval
|