folx 0.2.3__tar.gz → 0.2.4__tar.gz
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.
- {folx-0.2.3 → folx-0.2.4}/PKG-INFO +2 -1
- folx-0.2.4/folx/ad.py +104 -0
- {folx-0.2.3 → folx-0.2.4}/folx/api.py +29 -7
- {folx-0.2.3 → folx-0.2.4}/folx/custom_hessian.py +8 -1
- {folx-0.2.3 → folx-0.2.4}/folx/hessian.py +8 -5
- {folx-0.2.3 → folx-0.2.4}/folx/interpreter.py +9 -3
- {folx-0.2.3 → folx-0.2.4}/folx/jvp.py +3 -2
- {folx-0.2.3 → folx-0.2.4}/folx/wrapped_functions.py +61 -14
- {folx-0.2.3 → folx-0.2.4}/folx/wrapper.py +29 -1
- {folx-0.2.3 → folx-0.2.4}/pyproject.toml +2 -1
- {folx-0.2.3 → folx-0.2.4}/LICENSE +0 -0
- {folx-0.2.3 → folx-0.2.4}/README.md +0 -0
- {folx-0.2.3 → folx-0.2.4}/folx/__init__.py +0 -0
- {folx-0.2.3 → folx-0.2.4}/folx/operators.py +0 -0
- {folx-0.2.3 → folx-0.2.4}/folx/tree_utils.py +0 -0
- {folx-0.2.3 → folx-0.2.4}/folx/utils.py +0 -0
- {folx-0.2.3 → folx-0.2.4}/folx/vmap.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: folx
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Forward Laplacian for JAX
|
|
5
5
|
Home-page: https://github.com/microsoft/folx
|
|
6
6
|
License: MIT
|
|
@@ -28,6 +28,7 @@ Requires-Dist: jax
|
|
|
28
28
|
Requires-Dist: jaxlib
|
|
29
29
|
Requires-Dist: jaxtyping
|
|
30
30
|
Requires-Dist: numpy
|
|
31
|
+
Requires-Dist: parameterized
|
|
31
32
|
Requires-Dist: pytest
|
|
32
33
|
Project-URL: Repository, https://github.com/microsoft/folx
|
|
33
34
|
Description-Content-Type: text/markdown
|
folx-0.2.4/folx/ad.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import jax
|
|
2
|
+
import jax.numpy as jnp
|
|
3
|
+
import jax.flatten_util as jfu
|
|
4
|
+
import jax.tree_util as jtu
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_tree_complex(tree):
|
|
8
|
+
leaves = jtu.tree_leaves(tree)
|
|
9
|
+
return any(jnp.iscomplexobj(leaf) for leaf in leaves)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def vjp_rc(fun, *primals: jax.Array):
|
|
13
|
+
def real_fun(*primals):
|
|
14
|
+
return jnp.real(fun(*primals))
|
|
15
|
+
|
|
16
|
+
def imag_fun(*primals):
|
|
17
|
+
return jnp.imag(fun(*primals))
|
|
18
|
+
|
|
19
|
+
_, vjp_r = jax.vjp(real_fun, *primals)
|
|
20
|
+
_, vjp_i = jax.vjp(imag_fun, *primals)
|
|
21
|
+
|
|
22
|
+
def vjp(*tangents: jax.Array):
|
|
23
|
+
real_tangents = jtu.tree_map(jnp.real, tangents)
|
|
24
|
+
imag_tangents = jtu.tree_map(jnp.imag, tangents)
|
|
25
|
+
|
|
26
|
+
# letters: v=vector, j=jacobian, r=real, i=imag
|
|
27
|
+
vr_jr = vjp_r(*real_tangents)
|
|
28
|
+
vi_jr = vjp_r(*imag_tangents)
|
|
29
|
+
vr_ji = vjp_i(*real_tangents)
|
|
30
|
+
vi_ji = vjp_i(*imag_tangents)
|
|
31
|
+
|
|
32
|
+
result = jtu.tree_map(
|
|
33
|
+
lambda vr_jr, vi_jr, vr_ji, vi_ji: vr_jr - vi_ji + 1j * (vr_ji + vi_jr),
|
|
34
|
+
vr_jr,
|
|
35
|
+
vi_jr,
|
|
36
|
+
vr_ji,
|
|
37
|
+
vi_ji,
|
|
38
|
+
)
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
return vjp
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def vjp(fun, *primals: jax.Array):
|
|
45
|
+
out, vjp = jax.vjp(fun, *primals)
|
|
46
|
+
if is_tree_complex(primals):
|
|
47
|
+
if is_tree_complex(out):
|
|
48
|
+
# C -> C
|
|
49
|
+
return vjp
|
|
50
|
+
else:
|
|
51
|
+
# C -> R
|
|
52
|
+
return vjp
|
|
53
|
+
else:
|
|
54
|
+
if is_tree_complex(out):
|
|
55
|
+
# R -> C
|
|
56
|
+
return vjp_rc(fun, *primals)
|
|
57
|
+
else:
|
|
58
|
+
# R -> R
|
|
59
|
+
return vjp
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def jacrev(f):
|
|
63
|
+
# Similar to jax.jacrev but works with complex inputs and outputs.
|
|
64
|
+
# A crucial difference is that we do not preserve the structure of the output but
|
|
65
|
+
# always flatten it to a 1D array.
|
|
66
|
+
def jacfun(*primals):
|
|
67
|
+
flat_primals, unravel = jfu.ravel_pytree(primals)
|
|
68
|
+
|
|
69
|
+
def flat_f(x):
|
|
70
|
+
return jfu.ravel_pytree(f(*unravel(x)))[0]
|
|
71
|
+
|
|
72
|
+
out = flat_f(flat_primals)
|
|
73
|
+
|
|
74
|
+
result = jax.vmap(vjp(flat_f, flat_primals))(
|
|
75
|
+
jnp.eye(out.size, dtype=out.dtype)
|
|
76
|
+
)[0]
|
|
77
|
+
result = jax.vmap(unravel, out_axes=0)(result)
|
|
78
|
+
if len(primals) == 1:
|
|
79
|
+
return result[0]
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
return jacfun
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def jacfwd(f):
|
|
86
|
+
# Similar to jax.jacfwd but works with complex inputs and outputs.
|
|
87
|
+
# A crucial difference is that we do not preserve the structure of the input but
|
|
88
|
+
# always flatten it to a 1D array.
|
|
89
|
+
def jacfun(*primals):
|
|
90
|
+
flat_primals, unravel = jfu.ravel_pytree(primals)
|
|
91
|
+
|
|
92
|
+
def jvp_fun(s):
|
|
93
|
+
return jax.jvp(f, primals, unravel(s))[1]
|
|
94
|
+
|
|
95
|
+
eye = jnp.eye(flat_primals.size, dtype=flat_primals.dtype)
|
|
96
|
+
J = jax.vmap(jvp_fun, out_axes=-1)(eye)
|
|
97
|
+
return J
|
|
98
|
+
|
|
99
|
+
return jacfun
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def hessian(f):
|
|
103
|
+
# Similar to jax.hessian but works with complex inputs and outputs.
|
|
104
|
+
return jacfwd(jacrev(f))
|
|
@@ -151,7 +151,7 @@ class FwdJacobian(NamedTuple):
|
|
|
151
151
|
if idx is None:
|
|
152
152
|
return self.dense_array
|
|
153
153
|
if len(idx) == 0:
|
|
154
|
-
return jnp.zeros((*self.data_shape, 0))
|
|
154
|
+
return jnp.zeros((*self.data_shape, 0), dtype=self.data.dtype)
|
|
155
155
|
if self.x0_idx is None:
|
|
156
156
|
return self.data[idx]
|
|
157
157
|
return self.materialize_for_idx(self.get_index_mask(idx), len(idx))
|
|
@@ -225,6 +225,17 @@ class FwdJacobian(NamedTuple):
|
|
|
225
225
|
def astype(self, dtype):
|
|
226
226
|
return FwdJacobian(self.data.astype(dtype), self.x0_idx)
|
|
227
227
|
|
|
228
|
+
@property
|
|
229
|
+
def real(self):
|
|
230
|
+
return FwdJacobian(self.data.real, self.x0_idx)
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def imag(self):
|
|
234
|
+
return FwdJacobian(self.data.imag, self.x0_idx)
|
|
235
|
+
|
|
236
|
+
def conj(self):
|
|
237
|
+
return FwdJacobian(self.data.conj(), self.x0_idx)
|
|
238
|
+
|
|
228
239
|
|
|
229
240
|
class FwdLaplArray(NamedTuple):
|
|
230
241
|
"""
|
|
@@ -265,12 +276,8 @@ class FwdLaplArray(NamedTuple):
|
|
|
265
276
|
return FwdLaplArray(self.x, self.jacobian.as_dense, self.laplacian)
|
|
266
277
|
|
|
267
278
|
def astype(self, dtype):
|
|
268
|
-
if dtype
|
|
269
|
-
jnp.
|
|
270
|
-
jnp.float32,
|
|
271
|
-
jnp.float64,
|
|
272
|
-
jnp.complex64,
|
|
273
|
-
jnp.complex128,
|
|
279
|
+
if jax.dtypes.issubdtype(dtype, jnp.floating) or jax.dtypes.issubdtype(
|
|
280
|
+
dtype, jnp.complexfloating
|
|
274
281
|
):
|
|
275
282
|
return FwdLaplArray(
|
|
276
283
|
self.x.astype(dtype),
|
|
@@ -280,6 +287,21 @@ class FwdLaplArray(NamedTuple):
|
|
|
280
287
|
# If we convert to integer or boolean we drop the derivatives
|
|
281
288
|
return self.x.astype(dtype)
|
|
282
289
|
|
|
290
|
+
@property
|
|
291
|
+
def dtype(self):
|
|
292
|
+
return self.x.dtype
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def real(self):
|
|
296
|
+
return FwdLaplArray(self.x.real, self.jacobian.real, self.laplacian.real)
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def imag(self):
|
|
300
|
+
return FwdLaplArray(self.x.imag, self.jacobian.imag, self.laplacian.imag)
|
|
301
|
+
|
|
302
|
+
def conj(self):
|
|
303
|
+
return FwdLaplArray(self.x.conj(), self.jacobian.conj(), self.laplacian.conj())
|
|
304
|
+
|
|
283
305
|
|
|
284
306
|
def IS_LPL_ARR(x):
|
|
285
307
|
return isinstance(x, FwdLaplArray)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import jax
|
|
2
2
|
import jax.numpy as jnp
|
|
3
3
|
|
|
4
|
+
from folx.ad import is_tree_complex
|
|
5
|
+
|
|
4
6
|
from .api import Array, ExtraArgs, FwdLaplArgs, MergeFn, JAC_DIM
|
|
5
7
|
from .utils import trace_of_product
|
|
6
8
|
|
|
@@ -56,4 +58,9 @@ def slogdet_jac_hessian_jac(
|
|
|
56
58
|
return None, elementwise(*x)
|
|
57
59
|
|
|
58
60
|
signs, flat_out = jax.lax.scan(scan_wrapper, None, (A_inv, J))[1]
|
|
59
|
-
|
|
61
|
+
sign_out, log_abs_out = signs.reshape(leading_dims), flat_out.reshape(leading_dims)
|
|
62
|
+
|
|
63
|
+
if is_tree_complex(A):
|
|
64
|
+
# this is not the real Tr(JHJ^T) but a cached value we use later to compute the Tr(JHJ^T)
|
|
65
|
+
return log_abs_out, log_abs_out.real
|
|
66
|
+
return sign_out, log_abs_out.real
|
|
@@ -34,6 +34,7 @@ from .utils import (
|
|
|
34
34
|
trace_of_product,
|
|
35
35
|
vmap_sequences_and_squeeze,
|
|
36
36
|
)
|
|
37
|
+
from .ad import hessian, jacrev
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def general_jac_hessian_jac(
|
|
@@ -56,8 +57,8 @@ def general_jac_hessian_jac(
|
|
|
56
57
|
grad_2d = jnp.concatenate([x.T for x in grads_2d], axis=0)
|
|
57
58
|
D, K = grad_2d.shape
|
|
58
59
|
if K > D:
|
|
59
|
-
#
|
|
60
|
-
flat_hessian =
|
|
60
|
+
# Fwd on Reverse AD
|
|
61
|
+
flat_hessian = hessian(flat_fn)(flat_x)
|
|
61
62
|
flat_out = trace_of_product(flat_hessian, grad_2d @ grad_2d.T)
|
|
62
63
|
elif D > K:
|
|
63
64
|
# Directly copmute the trace of tr(HJJ^T)=tr(J^THJ)
|
|
@@ -79,7 +80,7 @@ def general_jac_hessian_jac(
|
|
|
79
80
|
@functools.partial(jax.vmap, in_axes=-1, out_axes=-1)
|
|
80
81
|
def hvp(tangent):
|
|
81
82
|
def jacobian(x):
|
|
82
|
-
return
|
|
83
|
+
return jacrev(flat_fn)(x)
|
|
83
84
|
|
|
84
85
|
return jax.jvp(jacobian, (flat_x,), (tangent,))[1]
|
|
85
86
|
|
|
@@ -115,7 +116,9 @@ def off_diag_jac_hessian_jac(
|
|
|
115
116
|
return unravel(flat_out)
|
|
116
117
|
|
|
117
118
|
|
|
118
|
-
def
|
|
119
|
+
def dot_product_jac_hessian_jac(
|
|
120
|
+
fn: ForwardFn, args: FwdLaplArgs, shared_idx: Array | None
|
|
121
|
+
):
|
|
119
122
|
# For a dot product we know that the hessian looks like this:
|
|
120
123
|
# [0, I]
|
|
121
124
|
# [I, 0]
|
|
@@ -321,7 +324,7 @@ def vmapped_jac_hessian_jac(
|
|
|
321
324
|
if custom_jac_hessian_jac is not None:
|
|
322
325
|
result = custom_jac_hessian_jac(args, extra_args, merge, materialize_idx)
|
|
323
326
|
elif FunctionFlags.MULTIPLICATION in flags:
|
|
324
|
-
result =
|
|
327
|
+
result = dot_product_jac_hessian_jac(merged_fn, args, materialize_idx)
|
|
325
328
|
elif FunctionFlags.LINEAR_IN_ONE in flags:
|
|
326
329
|
result = off_diag_jac_hessian_jac(merged_fn, args, materialize_idx)
|
|
327
330
|
else:
|
|
@@ -161,8 +161,8 @@ def eval_jaxpr_with_forward_laplacian(
|
|
|
161
161
|
|
|
162
162
|
def eval_laplacian(eqn: core.JaxprEqn, invals):
|
|
163
163
|
subfuns, params = eqn.primitive.get_bind_params(eqn.params)
|
|
164
|
-
fn = get_laplacian(eqn.primitive, True)
|
|
165
164
|
with LoggingPrefix(f'({summarize(eqn.source_info)})'):
|
|
165
|
+
fn = get_laplacian(eqn.primitive, True)
|
|
166
166
|
return fn(
|
|
167
167
|
(*subfuns, *invals), params, sparsity_threshold=sparsity_threshold
|
|
168
168
|
)
|
|
@@ -219,12 +219,18 @@ def init_forward_laplacian_state(
|
|
|
219
219
|
x_flat, unravel = ravel(x)
|
|
220
220
|
jac = jtu.tree_map(jnp.ones_like, x)
|
|
221
221
|
jac_idx = unravel(np.arange(x_flat.shape[0]))
|
|
222
|
+
if jnp.iscomplexobj(x_flat):
|
|
223
|
+
logging.info(
|
|
224
|
+
'[folx] Found complex input. This is not well supported, results might be wrong.'
|
|
225
|
+
)
|
|
222
226
|
if sparsity:
|
|
223
227
|
jac = jtu.tree_map(
|
|
224
|
-
lambda j, i: FwdJacobian(j[None], np.array(i)[None]),
|
|
228
|
+
lambda j, i: FwdJacobian(j.astype(x_flat.dtype)[None], np.array(i)[None]),
|
|
229
|
+
jac,
|
|
230
|
+
jac_idx,
|
|
225
231
|
)
|
|
226
232
|
else:
|
|
227
|
-
jac = jax.vmap(unravel)(jnp.eye(len(x_flat)))
|
|
233
|
+
jac = jax.vmap(unravel)(jnp.eye(len(x_flat), dtype=x_flat.dtype))
|
|
228
234
|
jac = jtu.tree_map(FwdJacobian.from_dense, jac)
|
|
229
235
|
lapl_x = jtu.tree_map(jnp.zeros_like, x)
|
|
230
236
|
return jtu.tree_map(FwdLaplArray, x, jac, lapl_x)
|
|
@@ -29,6 +29,7 @@ from .utils import (
|
|
|
29
29
|
get_jacobian_for_reduction,
|
|
30
30
|
np_concatenate_brdcast,
|
|
31
31
|
)
|
|
32
|
+
from .ad import vjp
|
|
32
33
|
|
|
33
34
|
R = TypeVar('R', bound=PyTree[Array])
|
|
34
35
|
|
|
@@ -115,7 +116,7 @@ def sparse_diag_jvp(
|
|
|
115
116
|
):
|
|
116
117
|
# If we have elementwise functions, we can just compute the full jacobian and
|
|
117
118
|
# do the operations a bit faster.
|
|
118
|
-
jac =
|
|
119
|
+
jac = vjp(fwd, laplace_args.x[0])(jnp.ones_like(y))[0]
|
|
119
120
|
grad_y = jac * laplace_args.jacobian[0].data
|
|
120
121
|
lapl_y = jac * laplace_args.laplacian[0]
|
|
121
122
|
else:
|
|
@@ -373,7 +374,7 @@ def dense_elementwise_jvp(
|
|
|
373
374
|
if y.shape != laplace_args.x[0].shape:
|
|
374
375
|
return dense_split_jvp(fwd, laplace_args)
|
|
375
376
|
|
|
376
|
-
jac =
|
|
377
|
+
jac = vjp(fwd, laplace_args.x[0])(jnp.ones_like(y))[0]
|
|
377
378
|
grad_y = jac * laplace_args.dense_jacobian[0]
|
|
378
379
|
lapl_y = jac * laplace_args.laplacian[0]
|
|
379
380
|
return y, grad_y, lapl_y
|
|
@@ -7,16 +7,23 @@ import jax.numpy as jnp
|
|
|
7
7
|
import jax.tree_util as jtu
|
|
8
8
|
from jax.core import Primitive
|
|
9
9
|
|
|
10
|
+
from folx.ad import is_tree_complex
|
|
11
|
+
|
|
10
12
|
from .api import (
|
|
11
13
|
Array,
|
|
12
14
|
ArrayOrFwdLaplArray,
|
|
13
15
|
ForwardLaplacian,
|
|
14
16
|
FunctionFlags,
|
|
17
|
+
FwdJacobian,
|
|
15
18
|
FwdLaplArray,
|
|
16
19
|
PyTree,
|
|
17
20
|
)
|
|
18
21
|
from .custom_hessian import slogdet_jac_hessian_jac
|
|
19
|
-
from .wrapper import
|
|
22
|
+
from .wrapper import (
|
|
23
|
+
wrap_elementwise,
|
|
24
|
+
wrap_forward_laplacian,
|
|
25
|
+
warp_without_fwd_laplacian,
|
|
26
|
+
)
|
|
20
27
|
|
|
21
28
|
R = TypeVar('R', bound=PyTree[Array])
|
|
22
29
|
P = ParamSpec('P')
|
|
@@ -119,7 +126,10 @@ def dtype_conversion(
|
|
|
119
126
|
def slogdet(x):
|
|
120
127
|
# We only need this custom slog det to avoid a jax bug
|
|
121
128
|
# https://github.com/google/jax/issues/17379
|
|
122
|
-
return
|
|
129
|
+
# We explictily decompose this here as newer version will return
|
|
130
|
+
# SlogDetResult which is a NamedTuple and does not combine nicely with regular tuples.
|
|
131
|
+
sign, logdet = jnp.linalg.slogdet(x)
|
|
132
|
+
return sign, logdet
|
|
123
133
|
|
|
124
134
|
|
|
125
135
|
def slogdet_jvp(primals, tangents):
|
|
@@ -136,10 +146,18 @@ def slogdet_jvp(primals, tangents):
|
|
|
136
146
|
|
|
137
147
|
jacobians = jnp.linalg.inv(primals)
|
|
138
148
|
|
|
139
|
-
def custom_jvp(jacobian, tangent):
|
|
140
|
-
|
|
149
|
+
def custom_jvp(jacobian, tangent, sign):
|
|
150
|
+
jac_dot_tangent = jnp.vdot(jacobian.T.conj(), tangent)
|
|
151
|
+
if jac_dot_tangent.dtype in (jnp.complex64, jnp.complex128):
|
|
152
|
+
# this is not the real jvp but a cached value to ease the Tr(JHJ^T) computation
|
|
153
|
+
sign_jvp = jac_dot_tangent
|
|
154
|
+
log_det_jvp = jac_dot_tangent.real
|
|
155
|
+
else:
|
|
156
|
+
sign_jvp = jnp.zeros(())
|
|
157
|
+
log_det_jvp = jac_dot_tangent
|
|
158
|
+
return (sign_jvp, log_det_jvp)
|
|
141
159
|
|
|
142
|
-
y_tangent = jax.vmap(custom_jvp)(jacobians, tangents)
|
|
160
|
+
y_tangent = jax.vmap(custom_jvp)(jacobians, tangents, sign)
|
|
143
161
|
|
|
144
162
|
y, y_tangent = jtu.tree_map(lambda x: x.reshape(*batch_shape), (y, y_tangent))
|
|
145
163
|
return y, y_tangent
|
|
@@ -158,15 +176,44 @@ def slogdet_wrapper(
|
|
|
158
176
|
)
|
|
159
177
|
sign, logdet = fwd_lapl_fn(x, {}, sparsity_threshold=0)
|
|
160
178
|
# Remove the jacobian of the sign
|
|
161
|
-
|
|
179
|
+
if jax.dtypes.issubdtype(sign.dtype, jnp.complexfloating):
|
|
180
|
+
sign_jac = sign.jacobian.data
|
|
181
|
+
sign_jac_flat = sign_jac.reshape(-1, *sign.shape).imag
|
|
182
|
+
sign_jac_dot = jnp.einsum('i...,i...->...', sign_jac_flat, sign_jac_flat)
|
|
183
|
+
sign = FwdLaplArray(
|
|
184
|
+
sign.x,
|
|
185
|
+
FwdJacobian(1.0j * sign.x * sign_jac.imag, x0_idx=sign.jacobian.x0_idx),
|
|
186
|
+
sign.x * (1.0j * sign.laplacian.imag - sign_jac_dot),
|
|
187
|
+
)
|
|
188
|
+
if jax.dtypes.issubdtype(sign.dtype, jnp.floating):
|
|
189
|
+
sign = sign.x
|
|
162
190
|
return sign, logdet
|
|
163
191
|
|
|
164
192
|
|
|
193
|
+
def abs_wrapper(
|
|
194
|
+
x: tuple[ArrayOrFwdLaplArray],
|
|
195
|
+
kwargs: dict[str, Any],
|
|
196
|
+
sparsity_threshold: int,
|
|
197
|
+
):
|
|
198
|
+
if not is_tree_complex(x):
|
|
199
|
+
return wrap_forward_laplacian(
|
|
200
|
+
jax.lax.abs, flags=FunctionFlags.LINEAR, in_axes=()
|
|
201
|
+
)(x, kwargs, sparsity_threshold)
|
|
202
|
+
import folx # we must import folx here to avoid circular imports
|
|
203
|
+
|
|
204
|
+
return folx.forward_laplacian(
|
|
205
|
+
lambda x: jnp.sqrt((x * x.conj()).real),
|
|
206
|
+
sparsity_threshold=sparsity_threshold,
|
|
207
|
+
disable_jit=True,
|
|
208
|
+
)(x[0])
|
|
209
|
+
|
|
210
|
+
|
|
165
211
|
_LAPLACE_FN_REGISTRY: dict[Primitive | str, ForwardLaplacian] = {
|
|
212
|
+
jax.lax.conj_p: wrap_elementwise(jnp.conj),
|
|
213
|
+
jax.lax.imag_p: wrap_elementwise(jnp.imag),
|
|
214
|
+
jax.lax.real_p: wrap_elementwise(jnp.real),
|
|
166
215
|
jax.lax.dot_general_p: dot_general,
|
|
167
|
-
jax.lax.abs_p:
|
|
168
|
-
jax.lax.abs, flags=FunctionFlags.LINEAR, in_axes=()
|
|
169
|
-
),
|
|
216
|
+
jax.lax.abs_p: abs_wrapper,
|
|
170
217
|
jax.lax.neg_p: wrap_forward_laplacian(
|
|
171
218
|
jax.lax.neg, flags=FunctionFlags.LINEAR, in_axes=()
|
|
172
219
|
),
|
|
@@ -266,11 +313,11 @@ _LAPLACE_FN_REGISTRY: dict[Primitive | str, ForwardLaplacian] = {
|
|
|
266
313
|
flags=FunctionFlags.INDEXING | FunctionFlags.SCATTER,
|
|
267
314
|
name='scatter',
|
|
268
315
|
),
|
|
269
|
-
jax.lax.scatter_add_p: wrap_forward_laplacian(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
),
|
|
316
|
+
# jax.lax.scatter_add_p: wrap_forward_laplacian(
|
|
317
|
+
# jax.lax.scatter_add_p.bind,
|
|
318
|
+
# flags=FunctionFlags.LINEAR | FunctionFlags.SCATTER,
|
|
319
|
+
# name='scatter_add',
|
|
320
|
+
# ),
|
|
274
321
|
jax.lax.stop_gradient_p: warp_without_fwd_laplacian(jax.lax.stop_gradient),
|
|
275
322
|
jax.lax.eq_p: warp_without_fwd_laplacian(jax.lax.eq),
|
|
276
323
|
jax.lax.lt_p: warp_without_fwd_laplacian(jax.lax.lt),
|
|
@@ -14,6 +14,7 @@ from .api import (
|
|
|
14
14
|
ForwardLaplacian,
|
|
15
15
|
ForwardLaplacianFns,
|
|
16
16
|
FunctionFlags,
|
|
17
|
+
FwdJacobian,
|
|
17
18
|
FwdLaplArgs,
|
|
18
19
|
FwdLaplArray,
|
|
19
20
|
MergeFn,
|
|
@@ -120,7 +121,12 @@ def wrap_forward_laplacian(
|
|
|
120
121
|
lapl_y = tree_add(
|
|
121
122
|
lapl_y, lapl_fns.jac_hessian_jac_trace(laplace_args, sparsity_threshold)
|
|
122
123
|
)
|
|
123
|
-
return jax.tree_util.tree_map(
|
|
124
|
+
return jax.tree_util.tree_map(
|
|
125
|
+
lambda x, jac, lapl: FwdLaplArray(x, jac, lapl).astype(x.dtype),
|
|
126
|
+
y,
|
|
127
|
+
grad_y,
|
|
128
|
+
lapl_y,
|
|
129
|
+
)
|
|
124
130
|
|
|
125
131
|
return new_fn
|
|
126
132
|
|
|
@@ -139,3 +145,25 @@ def warp_without_fwd_laplacian(fn) -> ForwardLaplacian:
|
|
|
139
145
|
return fn(*args, **kwargs)
|
|
140
146
|
|
|
141
147
|
return wrapped
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def wrap_elementwise(fn) -> ForwardLaplacian:
|
|
151
|
+
"""
|
|
152
|
+
Decorator that applies the function directly to the data, jacobian and laplacian.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def wrapped(args, kwargs, sparsity_threshold: int):
|
|
156
|
+
assert len(args) == 1
|
|
157
|
+
|
|
158
|
+
def inner(x: ArrayOrFwdLaplArray):
|
|
159
|
+
if isinstance(x, FwdLaplArray):
|
|
160
|
+
return FwdLaplArray(
|
|
161
|
+
fn(x.x),
|
|
162
|
+
FwdJacobian(fn(x.jacobian.data), x.jacobian.x0_idx),
|
|
163
|
+
fn(x.laplacian),
|
|
164
|
+
)
|
|
165
|
+
return fn(x)
|
|
166
|
+
|
|
167
|
+
return inner(args[0])
|
|
168
|
+
|
|
169
|
+
return wrapped
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = 'folx'
|
|
3
|
-
version = '0.2.
|
|
3
|
+
version = '0.2.4'
|
|
4
4
|
description = 'Forward Laplacian for JAX'
|
|
5
5
|
authors = [
|
|
6
6
|
"Nicholas Gao <n.gao@tum.de>",
|
|
@@ -37,6 +37,7 @@ jaxlib = "*"
|
|
|
37
37
|
jaxtyping = "*"
|
|
38
38
|
numpy = "*"
|
|
39
39
|
pytest = "*"
|
|
40
|
+
parameterized = "*"
|
|
40
41
|
|
|
41
42
|
[build-system]
|
|
42
43
|
requires = ["poetry-core>=1.0.0"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|