patrick-kidger / patrick-kidger/diffrax

Gradients of state variables in post-event simulations

Open
#729 15 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

question
Dominant language
Python
Stars
2.1k
Forks
189
Avg merge
3d 18h
Merged PRs (30d)
1

Description

Sorry, took me a while to carve out some time to have a look at this again. Looks like state sensitivity in post-event simulations is now always wrong, independent of usage of ClipStepSizeController (but computed gradient values still appear to depend on it).

this is with latest jax (0.9.0.1) and diffrax (0.7.1)

I understand that for this particular problem, we could simply use the ClipStepSizeController and not add the event to diffeqsolve, but this does not work for other settings we are looking at.

import jax

jax.config.update("jax_enable_x64", True)

import jax.numpy as jnp
import optimistix as optx
import diffrax

jump_time = 0.98
controller = diffrax.PIDController(rtol=1e-6, atol=1e-6)
controller = diffrax.ClipStepSizeController(controller, jump_ts=[jump_time])

def solve(event_time):
    term = diffrax.ODETerm(
        lambda t, y, args: jnp.array([jnp.select(
            [jnp.less(t,event_time),True],
            [1.0, 0.0]
        )])
    )
    solver = diffrax.Heun()

    sol_event = diffrax.diffeqsolve(
        term,
        solver,
        t0=0,
        t1=2,
        dt0=None,
        y0=jnp.array([0.0]),
        stepsize_controller=controller,
        event=diffrax.Event(
            cond_fn=lambda t, y, args, **kw: event_time - t,
            root_finder=optx.Newton(atol=1e-4, rtol=1e-4),
        ),
        max_steps=100,
    )
    sol = diffrax.diffeqsolve(
        term,
        solver,
        t0=sol_event.ts[-1],
        t1=2,
        dt0=None,
        y0=sol_event.ys[-1],
        stepsize_controller=controller,
        max_steps=100,
    )
    return sol_event, sol

def compute_end_state(event_time):
    sol_event, sol = solve(event_time)
    return sol.ys[-1, 0]

def compute_event_time(event_time):
    sol_event, sol = solve(event_time)
    return sol_event.ts[-1]

assert jnp.isclose(compute_end_state(jump_time),jump_time) # pass
assert jnp.isclose(compute_event_time(jump_time),jump_time) # pass

assert jnp.isclose(jax.grad(compute_event_time)(jump_time), 1.0) # pass
print(jax.grad(compute_end_state)(jump_time))
# 0.62716916609091 without `ClipStepSizeController`
# 2.220446049250313e-16 with `ClipStepSizeController`
assert jnp.isclose(jax.grad(compute_end_state)(jump_time),1.0) # fail

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the reproducer against diffrax.diffeqsolve with Event and the two solve paths shown in the issue. Trace how event times and state sensitivities are handled during autodifferentiation, comparing results with and without ClipStepSizeController. Done means the end-state gradient matches the expected value of 1.0 in the supplied assertion.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
machine-learning
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.