Order of ran cells changes output, using DifferentialEquations

Hello,

I’m running into a strange issue while using Pluto and DifferentialEquations.

In a nutshell: I get a systematically different output depending on the order I run the cells in my notebook.

I’ve managed to boil it down to this MWE:

# [Cell 1]
using DifferentialEquations, Plots

# [Cell 2]
times = 0:0.01:500

# [Cell 3]
begin # Add callback event
	condition(u, t, integrator) = (t == 250.);
	affect!(integrator) = (integrator.p[1] = 2);
	cb = DiscreteCallback(condition, affect!);
end

# [Cell 4]
# Define parameters
testP = [1.];

# [Cell 5]
# Define initial conditions
u0 = [1.];

# [Cell 6]
# Define problem
prob = ODEProblem(f, u0, (times[1], times[end]), testP, callback = cb);

# [Cell 7]
# Integrate system
sol = solve(prob, Euler(), dt = 0.001, tstops = [250.]);

# [Cell 8]
plot(sol)

If I launch this notebook, I get this plot, which evaluates everything properly:

But if I evaluate [Cell 7] after this, then I get:

Which seems to ignore the callback that changes a parameter in the integration. Not sure what is going on… Is it a problem on my end? Or is this something else?

I’m running Julia Version 1.11.1 (2024-10-16), Pluto v0.20.3 and DifferentialEquations v7.14.0 (and Plots v1.40.8, although the same is produced with CairoMakie)

Thanks a lot!

Not sure if this will work, but here is the notebook:

glitch_pluto.jl (106.4 KB)

Your affect! modifies the parameter, so when you run it again it’s still modified since you directly modified the reference in p.

Right ok, I see, that makes sense.

However, on Pluto’s side, shouldn’t it produce the same results every time?

Pluto has no internal state of the notebook unless you work with mutable objects :sweat_smile: That’s pretty subtle, but I guess it makes sense (?)
And allows one to “hack” the notebook a bit by creating constructs that do depend on cell order.

So modifying a Vector for example will persist if the cell defining (and thus “resetting”) the Vector is not re-run explicitly.

In Pluto’s internal dependency graph, running [Cell 7] does not trigger [Cell 4] because that code does not depend on anything from [Cell 7], but the object that was created in [Cell 4] (the parameter vector) can be modified and re-used without problem.

One solution to your problem might be to either use an immutable type for your parameters (not sure if that’s possible with the callback) or making sure that a new parameter vector is generated when you run the solve cell. Something like this perhaps:

make_problem() = ODEProblem(...)
...

sol = solve(make_problem(), ...)

Then every time, the solve cell is re-run, it re-generates a new problem. But make sure to not re-use the same object in the problem again, so this probably still won’t work (there is only one testP object which will be re-used all the time)

make_problem() = ODEProblem(..., testP, ...)

and might need to be this (I’m sure there are nicer ways to write it though, depends on what you need):

make_problem() = ODEProblem(..., [1.], )

Pluto.jl cannot track arbitrary dependencies between cells (and if you think about it this also doesn’t make because you ran into ambiguous situations very quickly - find an example below). Pluto generally tracks assignments to variables and nothing else. Since you affect! modifies only the value inside something a variable refers to, Pluto.jl has no chance of seeing this dependency.

In general you should keep things, that mutate inside the cell that defines what is being mutated to keep things consistent. I agree that it is a bit difficult to see in your scenario.

Example: Ambigous cell order
# Cell 1
var = [1,2]

# Cell 2
var[1] += 3

# Cell 3
var[1] *= 2

# Cell 4
var # what should this print?!

As you can see the order of Cells 2 and 3 is completely ambiguous.

1 Like

That all makes sense to me, thanks!

I ended up wrapping some parts of my code inside of functions to make it more predictable and benefit from the updating of Pluto when changing stuff around.

1 Like