|
13 | 13 | # limitations under the License. |
14 | 14 |
|
15 | 15 | """JAX root finding functions.""" |
| 16 | + |
16 | 17 | import dataclasses |
17 | 18 | import functools |
18 | 19 | from typing import Callable, Final |
|
21 | 22 | import jax.numpy as jnp |
22 | 23 | import numpy as np |
23 | 24 | from torax._src import jax_utils |
| 25 | +from torax._src.solver import linesearch |
24 | 26 |
|
25 | 27 | # Delta is a vector. If no entry of delta is above this magnitude, we terminate |
26 | 28 | # the delta loop. This is to avoid getting stuck in an infinite loop in edge |
@@ -123,9 +125,7 @@ def back(g, y): |
123 | 125 |
|
124 | 126 | if use_jax_custom_root: |
125 | 127 | if custom_jac is not None: |
126 | | - raise ValueError( |
127 | | - 'custom_jac is not compatible with use_jax_custom_root.' |
128 | | - ) |
| 128 | + raise ValueError('custom_jac is not compatible with use_jax_custom_root.') |
129 | 129 | x_out, metadata = jax.lax.custom_root( |
130 | 130 | f=fun, |
131 | 131 | initial_guess=x0, |
@@ -199,109 +199,44 @@ def _body( |
199 | 199 | dtype = input_state['x'].dtype |
200 | 200 | a_mat = jacobian_fun(input_state['x']) |
201 | 201 | rhs = -input_state['residual'] |
202 | | - # delta = x_new - x_old |
203 | | - # tau = delta/delta0, where delta0 is the delta that sets the linearized |
204 | | - # residual to zero. tau < 1 when needed such that x_new meets |
205 | | - # conditions of reduced residual and valid state quantities. |
206 | | - # If tau < taumin while residual > tol, then the routine exits with an |
207 | | - # error flag, leading to either a warning or recalculation at lower dt |
208 | | - initial_delta_state = { |
209 | | - 'x': input_state['x'], |
210 | | - 'delta': jnp.linalg.solve(a_mat, rhs), |
211 | | - 'residual_old': input_state['residual'], |
212 | | - 'residual_new': input_state['residual'], |
213 | | - 'tau': jnp.array(1.0, dtype=dtype), |
214 | | - } |
215 | | - output_delta_state = _compute_output_delta_state( |
216 | | - initial_delta_state, residual_fun, delta_reduction_factor |
| 202 | + |
| 203 | + direction = jnp.linalg.solve(a_mat, rhs) |
| 204 | + |
| 205 | + def norm_fn(res): |
| 206 | + return jnp.mean(jnp.abs(res)) |
| 207 | + |
| 208 | + init_norm = norm_fn(input_state['residual']) |
| 209 | + |
| 210 | + def accept_fn(step_size, trial_norm): |
| 211 | + del step_size # Unused |
| 212 | + return (trial_norm <= init_norm) & (~jnp.isnan(trial_norm)) |
| 213 | + |
| 214 | + ls_state = linesearch.backtracking_linesearch( |
| 215 | + residual_fn=residual_fun, |
| 216 | + x_init=input_state['x'], |
| 217 | + direction=direction, |
| 218 | + accept_fn=accept_fn, |
| 219 | + norm_fn=norm_fn, |
| 220 | + initial_residual=input_state['residual'], |
| 221 | + initial_residual_norm=init_norm, |
| 222 | + delta_reduction_factor=delta_reduction_factor, |
| 223 | + max_steps=100, |
| 224 | + min_step_norm=MIN_DELTA, |
217 | 225 | ) |
218 | 226 |
|
219 | 227 | output_state = { |
220 | | - 'x': input_state['x'] + output_delta_state['delta'], |
221 | | - 'residual': output_delta_state['residual_new'], |
| 228 | + 'x': ls_state.x, |
| 229 | + 'residual': ls_state.residual, |
222 | 230 | 'iterations': jnp.array(input_state['iterations'][...], dtype=dtype) + 1, |
223 | | - 'last_tau': output_delta_state['tau'], |
| 231 | + 'last_tau': ls_state.step_size, |
224 | 232 | } |
| 233 | + |
225 | 234 | if log_iterations: |
226 | 235 | jax.debug.print( |
227 | 236 | 'Iteration: {iteration:d}. Residual: {residual:.16f}. tau = {tau:.6f}', |
228 | 237 | iteration=output_state['iterations'].astype(jax_utils.get_int_dtype()), |
229 | 238 | residual=_residual_scalar(output_state['residual']), |
230 | | - tau=output_delta_state['tau'], |
| 239 | + tau=ls_state.step_size, |
231 | 240 | ) |
232 | 241 |
|
233 | 242 | return output_state |
234 | | - |
235 | | - |
236 | | -def _compute_output_delta_state( |
237 | | - initial_state: dict[str, jax.Array], |
238 | | - residual_fun: Callable[[jax.Array], jax.Array], |
239 | | - delta_reduction_factor: float, |
240 | | -): |
241 | | - """Updates output delta state.""" |
242 | | - delta_body_fun = functools.partial( |
243 | | - _delta_body, |
244 | | - delta_reduction_factor=delta_reduction_factor, |
245 | | - ) |
246 | | - delta_cond_fun = functools.partial( |
247 | | - _delta_cond, |
248 | | - residual_fun=residual_fun, |
249 | | - ) |
250 | | - output_delta_state = jax.lax.while_loop( |
251 | | - delta_cond_fun, delta_body_fun, initial_state |
252 | | - ) |
253 | | - |
254 | | - x_new = output_delta_state['x'] + output_delta_state['delta'] |
255 | | - residual_vec_x_new = residual_fun(x_new) |
256 | | - output_delta_state |= dict( |
257 | | - residual_new=residual_vec_x_new, |
258 | | - ) |
259 | | - return output_delta_state |
260 | | - |
261 | | - |
262 | | -def _delta_cond( |
263 | | - delta_state: dict[str, jax.Array], |
264 | | - residual_fun: Callable[[jax.Array], jax.Array], |
265 | | -) -> bool: |
266 | | - """Check if delta obtained from Newton step is valid. |
267 | | -
|
268 | | - Args: |
269 | | - delta_state: see `delta_body`. |
270 | | - residual_fun: Residual function. |
271 | | -
|
272 | | - Returns: |
273 | | - True if the new value of `x` causes any NaNs or has increased the residual |
274 | | - relative to the old value of `x`. |
275 | | - """ |
276 | | - x_old = delta_state['x'] |
277 | | - x_new = x_old + delta_state['delta'] |
278 | | - residual_vec_x_old = delta_state['residual_old'] |
279 | | - residual_scalar_x_old = _residual_scalar(residual_vec_x_old) |
280 | | - # Avoid sanity checking inside residual, since we directly |
281 | | - # afterwards check sanity on the output (NaN checking) |
282 | | - # TODO(b/312453092) consider instead sanity-checking x_new |
283 | | - with jax_utils.enable_errors(False): |
284 | | - residual_vec_x_new = residual_fun(x_new) |
285 | | - residual_scalar_x_new = _residual_scalar(residual_vec_x_new) |
286 | | - delta_state['residual_new'] = residual_vec_x_new |
287 | | - return jnp.bool_( |
288 | | - jnp.logical_and( |
289 | | - jnp.max(jnp.abs(delta_state['delta'])) > MIN_DELTA, |
290 | | - jnp.logical_or( |
291 | | - residual_scalar_x_old < residual_scalar_x_new, |
292 | | - jnp.isnan(residual_scalar_x_new), |
293 | | - ), |
294 | | - ), |
295 | | - ) |
296 | | - |
297 | | - |
298 | | -def _delta_body( |
299 | | - input_delta_state: dict[str, jax.Array], |
300 | | - delta_reduction_factor: float, |
301 | | -) -> dict[str, jax.Array]: |
302 | | - """Reduces step size for this Newton iteration.""" |
303 | | - return input_delta_state | dict( |
304 | | - delta=input_delta_state['delta'] * delta_reduction_factor, |
305 | | - tau=jnp.array(input_delta_state['tau'][...], dtype=jax_utils.get_dtype()) |
306 | | - * delta_reduction_factor, |
307 | | - ) |
0 commit comments