@@ -198,6 +198,12 @@ def densify(Fc, cols, n_x, scale=1.0):
198198MAX_BLOCKS = 2 ** 20
199199
200200
201+ @jax .jit
202+ def axmy (x , y ):
203+ """x - y."""
204+ return x - y
205+
206+
201207def apply (v , Fc , base , weights , scale , grid_n , cols , order ):
202208 """L.v for v (n_m, N, n_x)."""
203209 if len (cols ) == 0 :
@@ -207,24 +213,91 @@ def apply(v, Fc, base, weights, scale, grid_n, cols, order):
207213 return contract (Fc , X , scale )
208214
209215
210- @partial (
211- jax .jit , static_argnames = ("grid_n" , "cols" , "order" , "restart" , "maxiter" )
212- )
213- def gmres_I_minus_L (b , Fc , base , weights , scale , tol , grid_n , cols , order , restart , maxiter ):
214- """Solve (I - L) x = b with GMRES on the device, starting from x0 = b. Stops when
215- |b - (I - L) x|_2 <= tol (absolute, as Dolo.jl's gmres!)."""
216-
217- def matvec (x ):
218- return x - apply (x , Fc , base , weights , scale , grid_n , cols , order )
219-
220- x , _ = jax .scipy .sparse .linalg .gmres (
221- matvec ,
222- b ,
223- x0 = b ,
224- tol = 0.0 ,
225- atol = tol ,
226- restart = restart ,
227- maxiter = maxiter ,
228- solve_method = "incremental" ,
229- )
230- return x
216+ # --------------------------------------------------------------------------------------------
217+ # GMRES as Dolo.jl's `gmres!` (src/algos/linsolve.jl): one cycle of at most m Krylov vectors,
218+ # modified Gram-Schmidt, Givens rotations, absolute tolerance on the 2-norm of the residual.
219+ # The Krylov basis and the matrix-vector products stay on the device; the small (m+1) x m
220+ # Hessenberg problem is solved on the host (one transfer of a column per iteration). As fast
221+ # as `jax.scipy.sparse.linalg.gmres` here (whose tolerance is relative, with
222+ # reorthogonalization), with the iterations and the stopping rule of Dolo.jl.
223+
224+
225+ @partial (jax .jit , donate_argnums = (0 ,))
226+ def _arnoldi_step (V , w , j ):
227+ """Orthogonalize w against V[0..j] (modified Gram-Schmidt), store w / |w| in V[j+1] (when
228+ j + 1 < len(V)). Returns V and the column h[0..j+1] (zeros beyond)."""
229+
230+ def body (i , carry ):
231+ w , h = carry
232+ v_i = jax .lax .dynamic_index_in_dim (V , i , keepdims = False )
233+ h_i = jnp .vdot (v_i , w )
234+ return w - h_i * v_i , h .at [i ].set (h_i )
235+
236+ h = jnp .zeros (V .shape [0 ], dtype = w .dtype )
237+ w , h = jax .lax .fori_loop (0 , j + 1 , body , (w , h ))
238+ h_norm = jnp .linalg .norm (w )
239+ h = h .at [j + 1 ].set (h_norm )
240+ V = jax .lax .dynamic_update_index_in_dim (V , w / h_norm , jnp .minimum (j + 1 , V .shape [0 ] - 1 ), 0 )
241+ return V , h
242+
243+
244+ @partial (jax .jit , donate_argnums = (0 ,))
245+ def _krylov_init (V , r , beta ):
246+ return V .at [0 ].set (r / beta )
247+
248+
249+ @jax .jit
250+ def _krylov_vector (V , j ):
251+ return jax .lax .dynamic_index_in_dim (V , j , keepdims = False )
252+
253+
254+ @jax .jit
255+ def _krylov_update (x , V , y ):
256+ """x + sum_i y_i V[i] (y padded with zeros)."""
257+ return x + jnp .tensordot (y , V , axes = 1 )
258+
259+
260+ def gmres (matvec , b , x0 , tol , m ):
261+ """Solve A x = b, A given by `matvec` on flat device vectors, starting from x0: one cycle of
262+ at most m Krylov vectors, until |b - A x|_2 < tol. Returns x and the number of iterations."""
263+ n = b .shape [0 ]
264+ x = x0
265+ r = b - matvec (x )
266+ beta = float (jnp .linalg .norm (r ))
267+ if beta < tol :
268+ return x , 0
269+ V = _krylov_init (jnp .zeros ((m + 1 , n ), dtype = b .dtype ), r , beta )
270+ H = np .zeros ((m + 1 , m ))
271+ cs = np .zeros (m )
272+ sn = np .zeros (m )
273+ g = np .zeros (m + 1 )
274+ g [0 ] = beta
275+ eps = np .finfo (np .float64 ).eps
276+ k = 0
277+ for j in range (m ):
278+ w = matvec (_krylov_vector (V , j ))
279+ V , h_col = _arnoldi_step (V , w , j )
280+ h_col = np .asarray (h_col )
281+ H [: j + 2 , j ] = h_col [: j + 2 ]
282+ h = H [j + 1 , j ]
283+ # previous rotations on the new column
284+ for i in range (j ):
285+ t = cs [i ] * H [i , j ] + sn [i ] * H [i + 1 , j ]
286+ H [i + 1 , j ] = - sn [i ] * H [i , j ] + cs [i ] * H [i + 1 , j ]
287+ H [i , j ] = t
288+ # new rotation eliminating H[j+1, j]
289+ rho = np .hypot (H [j , j ], H [j + 1 , j ])
290+ cs [j ] = H [j , j ] / rho
291+ sn [j ] = H [j + 1 , j ] / rho
292+ H [j , j ] = rho
293+ H [j + 1 , j ] = 0.0
294+ g [j + 1 ] = - sn [j ] * g [j ]
295+ g [j ] = cs [j ] * g [j ]
296+ k = j + 1
297+ # converged, or lucky breakdown (the Krylov space is invariant)
298+ if abs (g [j + 1 ]) < tol or h <= eps * beta :
299+ break
300+ y = np .zeros (m + 1 )
301+ for i in range (k - 1 , - 1 , - 1 ):
302+ y [i ] = (g [i ] - H [i , i + 1 : k ] @ y [i + 1 : k ]) / H [i , i ]
303+ return _krylov_update (x , V , jnp .asarray (y , dtype = b .dtype )), k
0 commit comments