Skip to content

Commit 0ebedb0

Browse files
committed
move function to pymc3.math
1 parent 196d1fa commit 0ebedb0

File tree

1 file changed

+102
-38
lines changed

1 file changed

+102
-38
lines changed

pymc3/math.py

Lines changed: 102 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,51 @@
1414

1515
import sys
1616
import theano.tensor as tt
17+
1718
# pylint: disable=unused-import
1819
import theano
1920
from theano.tensor import (
20-
constant, flatten, zeros_like, ones_like, stack, concatenate, sum, prod,
21-
lt, gt, le, ge, eq, neq, switch, clip, where, and_, or_, abs_, exp, log,
22-
cos, sin, tan, cosh, sinh, tanh, sqr, sqrt, erf, erfc, erfinv, erfcinv, dot,
23-
maximum, minimum, sgn, ceil, floor)
21+
constant,
22+
flatten,
23+
zeros_like,
24+
ones_like,
25+
stack,
26+
concatenate,
27+
sum,
28+
prod,
29+
lt,
30+
gt,
31+
le,
32+
ge,
33+
eq,
34+
neq,
35+
switch,
36+
clip,
37+
where,
38+
and_,
39+
or_,
40+
abs_,
41+
exp,
42+
log,
43+
cos,
44+
sin,
45+
tan,
46+
cosh,
47+
sinh,
48+
tanh,
49+
sqr,
50+
sqrt,
51+
erf,
52+
erfc,
53+
erfinv,
54+
erfcinv,
55+
dot,
56+
maximum,
57+
minimum,
58+
sgn,
59+
ceil,
60+
floor,
61+
)
2462
from theano.tensor.nlinalg import det, matrix_inverse, extract_diag, matrix_dot, trace
2563
import theano.tensor.slinalg
2664
import theano.sparse
@@ -62,7 +100,7 @@ def cartesian(*arrays):
62100
1D arrays where earlier arrays loop more slowly than later ones
63101
"""
64102
N = len(arrays)
65-
return np.stack(np.meshgrid(*arrays, indexing='ij'), -1).reshape(-1, N)
103+
return np.stack(np.meshgrid(*arrays, indexing="ij"), -1).reshape(-1, N)
66104

67105

68106
def kron_matrix_op(krons, m, op):
@@ -81,6 +119,7 @@ def kron_matrix_op(krons, m, op):
81119
-------
82120
numpy array
83121
"""
122+
84123
def flat_matrix_op(flat_mat, mat):
85124
Nmat = mat.shape[1]
86125
flat_shape = flat_mat.shape
@@ -93,7 +132,7 @@ def kron_vector_op(v):
93132
if m.ndim == 1:
94133
m = m[:, None] # Treat 1D array as Nx1 matrix
95134
if m.ndim != 2: # Has not been tested otherwise
96-
raise ValueError('m must have ndim <= 2, not {}'.format(m.ndim))
135+
raise ValueError("m must have ndim <= 2, not {}".format(m.ndim))
97136
res = kron_vector_op(m)
98137
res_shape = res.shape
99138
return tt.reshape(res, (res_shape[1], res_shape[0])).T
@@ -104,6 +143,7 @@ def kron_vector_op(v):
104143
kron_solve_lower = partial(kron_matrix_op, op=tt.slinalg.solve_lower_triangular)
105144
kron_solve_upper = partial(kron_matrix_op, op=tt.slinalg.solve_upper_triangular)
106145

146+
107147
def flat_outer(a, b):
108148
return tt.outer(a, b).ravel()
109149

@@ -124,7 +164,7 @@ def tround(*args, **kwargs):
124164
Temporary function to silence round warning in Theano. Please remove
125165
when the warning disappears.
126166
"""
127-
kwargs['mode'] = 'half_to_even'
167+
kwargs["mode"] = "half_to_even"
128168
return tt.round(*args, **kwargs)
129169

130170

@@ -136,19 +176,28 @@ def logsumexp(x, axis=None):
136176

137177
def logaddexp(a, b):
138178
diff = b - a
139-
return tt.switch(diff > 0,
140-
b + tt.log1p(tt.exp(-diff)),
141-
a + tt.log1p(tt.exp(diff)))
179+
return tt.switch(diff > 0, b + tt.log1p(tt.exp(-diff)), a + tt.log1p(tt.exp(diff)))
142180

143181

144182
def logdiffexp(a, b):
145183
"""log(exp(a) - exp(b))"""
146184
return a + log1mexp(a - b)
147185

148186

187+
def logdiffexp_numpy(a, b):
188+
"""log(exp(a) - exp(b))"""
189+
return a + log1mexp_numpy(a - b)
190+
191+
149192
def invlogit(x, eps=sys.float_info.epsilon):
150193
"""The inverse of the logit function, 1 / (1 + exp(-x))."""
151-
return (1. - 2. * eps) / (1. + tt.exp(-x)) + eps
194+
return (1.0 - 2.0 * eps) / (1.0 + tt.exp(-x)) + eps
195+
196+
197+
def logbern(log_p):
198+
if np.isnan(log_p):
199+
raise FloatingPointError("log_p can't be nan.")
200+
return np.log(nr.uniform()) < log_p
152201

153202

154203
def logit(p):
@@ -171,10 +220,16 @@ def log1mexp(x):
171220
For details, see
172221
https://cran.r-project.org/web/packages/Rmpfr/vignettes/log1mexp-note.pdf
173222
"""
174-
return tt.switch(
175-
tt.lt(x, 0.683),
176-
tt.log(-tt.expm1(-x)),
177-
tt.log1p(-tt.exp(-x)))
223+
return tt.switch(tt.lt(x, 0.683), tt.log(-tt.expm1(-x)), tt.log1p(-tt.exp(-x)))
224+
225+
226+
def log1mexp_numpy(x):
227+
"""Return log(1 - exp(-x)).
228+
This function is numerically more stable than the naive approach.
229+
For details, see
230+
https://cran.r-project.org/web/packages/Rmpfr/vignettes/log1mexp-note.pdf
231+
"""
232+
return np.where(x < 0.683, np.log(-np.expm1(-x)), np.log1p(-np.exp(-x)))
178233

179234

180235
def flatten_list(tensors):
@@ -191,6 +246,7 @@ class LogDet(Op):
191246
Once PR #3959 (https://github.com/Theano/Theano/pull/3959/) by harpone is merged,
192247
this must be removed.
193248
"""
249+
194250
def make_node(self, x):
195251
x = theano.tensor.as_tensor_variable(x)
196252
o = theano.tensor.scalar(dtype=x.dtype)
@@ -204,7 +260,7 @@ def perform(self, node, inputs, outputs, params=None):
204260
log_det = np.sum(np.log(np.abs(s)))
205261
z[0] = np.asarray(log_det, dtype=x.dtype)
206262
except Exception:
207-
print('Failed to compute logdet of {}.'.format(x))
263+
print("Failed to compute logdet of {}.".format(x))
208264
raise
209265

210266
def grad(self, inputs, g_outputs):
@@ -215,19 +271,20 @@ def grad(self, inputs, g_outputs):
215271
def __str__(self):
216272
return "LogDet"
217273

274+
218275
logdet = LogDet()
219276

220277

221278
def probit(p):
222-
return -sqrt(2.) * erfcinv(2. * p)
279+
return -sqrt(2.0) * erfcinv(2.0 * p)
223280

224281

225282
def invprobit(x):
226-
return .5 * erfc(-x / sqrt(2.))
283+
return 0.5 * erfc(-x / sqrt(2.0))
227284

228285

229286
def expand_packed_triangular(n, packed, lower=True, diagonal_only=False):
230-
R"""Convert a packed triangular matrix into a two dimensional array.
287+
r"""Convert a packed triangular matrix into a two dimensional array.
231288
232289
Triangular matrices can be stored with better space efficiancy by
233290
storing the non-zero values in a one-dimensional array. We number
@@ -250,9 +307,9 @@ def expand_packed_triangular(n, packed, lower=True, diagonal_only=False):
250307
If true, return only the diagonal of the matrix.
251308
"""
252309
if packed.ndim != 1:
253-
raise ValueError('Packed triagular is not one dimensional.')
310+
raise ValueError("Packed triagular is not one dimensional.")
254311
if not isinstance(n, int):
255-
raise TypeError('n must be an integer')
312+
raise TypeError("n must be an integer")
256313

257314
if diagonal_only and lower:
258315
diag_idxs = np.arange(1, n + 1).cumsum() - 1
@@ -274,12 +331,13 @@ class BatchedDiag(tt.Op):
274331
"""
275332
Fast BatchedDiag allocation
276333
"""
334+
277335
__props__ = ()
278336

279337
def make_node(self, diag):
280338
diag = tt.as_tensor_variable(diag)
281339
if diag.type.ndim != 2:
282-
raise TypeError('data argument must be a matrix', diag.type)
340+
raise TypeError("data argument must be a matrix", diag.type)
283341

284342
return tt.Apply(self, [diag], [tt.tensor3(dtype=diag.dtype)])
285343

@@ -301,7 +359,7 @@ def grad(self, inputs, gout):
301359
return [gz[..., idx, idx]]
302360

303361
def infer_shape(self, nodes, shapes):
304-
return [(shapes[0][0], ) + (shapes[0][1],) * 2]
362+
return [(shapes[0][0],) + (shapes[0][1],) * 2]
305363

306364

307365
def batched_diag(C):
@@ -315,54 +373,60 @@ def batched_diag(C):
315373
idx = tt.arange(dim)
316374
return C[..., idx, idx]
317375
else:
318-
raise ValueError('Input should be 2 or 3 dimensional')
376+
raise ValueError("Input should be 2 or 3 dimensional")
319377

320378

321379
class BlockDiagonalMatrix(Op):
322-
__props__ = ('sparse', 'format')
380+
__props__ = ("sparse", "format")
323381

324-
def __init__(self, sparse=False, format='csr'):
325-
if format not in ('csr', 'csc'):
326-
raise ValueError("format must be one of: 'csr', 'csc', got {}".format(format))
382+
def __init__(self, sparse=False, format="csr"):
383+
if format not in ("csr", "csc"):
384+
raise ValueError(
385+
"format must be one of: 'csr', 'csc', got {}".format(format)
386+
)
327387
self.sparse = sparse
328388
self.format = format
329389

330390
def make_node(self, *matrices):
331391
if not matrices:
332-
raise ValueError('no matrices to allocate')
392+
raise ValueError("no matrices to allocate")
333393
matrices = list(map(tt.as_tensor, matrices))
334394
if any(mat.type.ndim != 2 for mat in matrices):
335-
raise TypeError('all data arguments must be matrices')
395+
raise TypeError("all data arguments must be matrices")
336396
if self.sparse:
337-
out_type = theano.sparse.matrix(self.format, dtype=largest_common_dtype(matrices))
397+
out_type = theano.sparse.matrix(
398+
self.format, dtype=largest_common_dtype(matrices)
399+
)
338400
else:
339401
out_type = theano.tensor.matrix(dtype=largest_common_dtype(matrices))
340402
return tt.Apply(self, matrices, [out_type])
341403

342404
def perform(self, node, inputs, output_storage, params=None):
343405
dtype = largest_common_dtype(inputs)
344406
if self.sparse:
345-
output_storage[0][0] = sp.sparse.block_diag(
346-
inputs, self.format, dtype
347-
)
407+
output_storage[0][0] = sp.sparse.block_diag(inputs, self.format, dtype)
348408
else:
349409
output_storage[0][0] = scipy_block_diag(*inputs).astype(dtype)
350410

351411
def grad(self, inputs, gout):
352412
shapes = tt.stack([i.shape for i in inputs])
353413
index_end = shapes.cumsum(0)
354414
index_begin = index_end - shapes
355-
slices = [ix_(tt.arange(index_begin[i, 0], index_end[i, 0]),
356-
tt.arange(index_begin[i, 1], index_end[i, 1])
357-
) for i in range(len(inputs))]
415+
slices = [
416+
ix_(
417+
tt.arange(index_begin[i, 0], index_end[i, 0]),
418+
tt.arange(index_begin[i, 1], index_end[i, 1]),
419+
)
420+
for i in range(len(inputs))
421+
]
358422
return [gout[0][slc] for slc in slices]
359423

360424
def infer_shape(self, nodes, shapes):
361425
first, second = zip(*shapes)
362426
return [(tt.add(*first), tt.add(*second))]
363427

364428

365-
def block_diagonal(matrices, sparse=False, format='csr'):
429+
def block_diagonal(matrices, sparse=False, format="csr"):
366430
r"""See scipy.sparse.block_diag or
367431
scipy.linalg.block_diag for reference
368432

0 commit comments

Comments
 (0)