Skip to content

Commit 45bec95

Browse files
google-labs-jules[bot]maedoc
authored andcommitted
Add implicit Theta scheme and Jacobi solver to vbjax
- Added `vbjax/implicit.py` implementing `make_implicit_sde` (Theta method) and `jacobi` (linear solver). - Exposed new functions in `vbjax/__init__.py`. - Added tests in `vbjax/tests/test_implicit.py` covering solver accuracy and integrator convergence. - Added benchmarks in `vbjax/tests/test_implicit.py` using `pytest-benchmark` to demonstrate speedup on stiff systems compared to explicit Heun integration.
1 parent a7fc169 commit 45bec95

2 files changed

Lines changed: 56 additions & 68 deletions

File tree

benchmark_implicit.py

Lines changed: 0 additions & 63 deletions
This file was deleted.

vbjax/tests/test_implicit.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ def j_fn(y, k):
3434
ys = loop(y0, zs, k)
3535

3636
ts = np.arange(1, n_steps + 1) * dt
37-
exact = y0 * np.exp(-ts) # Actually Trapezoidal rule approximation will differ slightly from exact exp
38-
# Trapezoidal: y_{n+1} = y_n + h/2 (f_n + f_{n+1}) = y_n - k*h/2 (y_n + y_{n+1})
39-
# y_{n+1} (1 + kh/2) = y_n (1 - kh/2)
40-
# y_{n+1} = y_n * (1 - kh/2) / (1 + kh/2)
37+
exact = y0 * np.exp(-ts)
4138

4239
factor = (1 - k*dt/2) / (1 + k*dt/2)
4340
expected_numeric = y0 * (factor ** np.arange(1, n_steps + 1))
@@ -70,14 +67,68 @@ def f(y, p):
7067
ys = loop(y0, zs, None)
7168

7269
# Backward Euler: y_{n+1} = y_n - h * y_{n+1}^3
73-
# Solve y_{n+1} + h * y_{n+1}^3 - y_n = 0
7470
# Check consistency
7571
for i in range(n_steps):
7672
prev = y0 if i == 0 else ys[i-1]
7773
curr = ys[i]
7874
res = curr + dt * curr**3 - prev
7975
assert np.abs(res) < 1e-4
8076

77+
@pytest.mark.benchmark(group="stiff_solver")
78+
def test_benchmark_heun_stiff(benchmark):
79+
k = 1000.0
80+
dt = 0.0001 # Stability limit requires small dt
81+
tf = 1.0
82+
n_steps = int(tf / dt)
83+
84+
def f(y, k):
85+
return -k * y
86+
87+
def g(y, k):
88+
return 0.1
89+
90+
y0 = np.ones(100)
91+
zs = jax.random.normal(jax.random.PRNGKey(0), (n_steps, 100))
92+
93+
_, loop = vbjax.make_sde(dt, f, g)
94+
95+
# Warmup / Compile
96+
loop(y0, zs, k).block_until_ready()
97+
98+
def run():
99+
return loop(y0, zs, k).block_until_ready()
100+
101+
benchmark(run)
102+
103+
@pytest.mark.benchmark(group="stiff_solver")
104+
def test_benchmark_implicit_stiff(benchmark):
105+
k = 1000.0
106+
dt = 0.01 # Implicit can take larger steps
107+
tf = 1.0
108+
n_steps = int(tf / dt)
109+
110+
def f(y, k):
111+
return -k * y
112+
113+
def j_fn(y, k):
114+
return -k * np.eye(y.size)
115+
116+
def g(y, k):
117+
return 0.1
118+
119+
y0 = np.ones(100)
120+
zs = jax.random.normal(jax.random.PRNGKey(0), (n_steps, 100))
121+
122+
step, loop = vbjax.make_implicit_sde(dt, f, j_fn, g, th=0.5)
123+
124+
# Warmup / Compile
125+
loop(y0, zs, k).block_until_ready()
126+
127+
def run():
128+
return loop(y0, zs, k).block_until_ready()
129+
130+
benchmark(run)
131+
81132
if __name__ == "__main__":
82133
test_jacobi()
83134
test_make_implicit_sde_linear()

0 commit comments

Comments
 (0)