Mutating values in immutable struct (specifically of type ODESolution)

I’m solving a differential equation that has been nondimensionalized, so I’d like to change the scaling of the dependent variable for plotting. Here is my example.

using DifferentialEquations
using Plots
using Accessors

f(u, p, t) = 1.01 * u
u0 = 1 / 2
tspan = (0.0, 1.0)
prob = ODEProblem(f, u0, tspan)
sol = solve(prob, Tsit5(), reltol = 1e-8, abstol = 1e-8)

myscalingfactor = 1e2
@reset sol.t = sol.t .* myscalingfactor
plot(sol)

At the @reset statement, I get this error:

MethodError: no method matching ODESolution(::Vector{Float64}, ::Nothing, ::Nothing, ::Vector{Float64}, ::Vector{Vector{Float64}}, ::ODEProblem{Float64, Tuple{Float64, Float64}, false, SciMLBase.NullParameters, ODEFunction{false, SciMLBase.AutoSpecialize, typeof(f), LinearAlgebra.UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, SciMLBase.StandardODEProblem}, ::Tsit5{typeof(OrdinaryDiffEq.trivial_limiter!), typeof(OrdinaryDiffEq.trivial_limiter!), Static.False}, ::OrdinaryDiffEq.InterpolationData{ODEFunction{false, SciMLBase.AutoSpecialize, typeof(f), LinearAlgebra.UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Vector{Float64}, Vector{Float64}, Vector{Vector{Float64}}, OrdinaryDiffEq.Tsit5ConstantCache}, ::Bool, ::Int64, ::DiffEqBase.DEStats, ::Nothing, ::SciMLBase.ReturnCode.T)

I see this solution, but I’m not sure how to define a constructor for the complicated structure above and why it doesn’t exist in the first place if it was created by solve?

The ODESolution itself appears to be a very big and complicated structure. It probably has a nontrivial custom constructor and Accessors.jl has not been told how to use it.

Even if ODESolution itself is immutable, a Vector (almost?) anywhere is still mutable. You can simply do sol.t .*= myscalingfactor in this case. Recall, this is identical to sol.t .= sol.t .* myscalingfactor. The .= is why this works to update sol.t in-place – no need to make a new Vector and build a new ODESolution around it.

Hopefully there isn’t some dependence on this field baked into other fields. If there is, you could end up with a ODESolution that does not behave as expected.

1 Like

Interesting idea… so you update the elements of the vector and not the property with a new vector.

Hopefully there isn’t some dependence on this field baked into other fields.

…unfortunately it looks to be the case.

Plotting

plot(sol.t .* myscalingfactor, sol.u)

gives me

plot1

while

sol.t .*= myscalingfactor
plot(sol)

gives me

plot2

However this appears to due to the internals for the plot method for ODESolution. The command below gives the expected curve (like the first plot above) after the vector modification.

plot(sol.t, sol.u)

For what it’s worth, the @reset thing might not update the vector properly either. But it looks like you’ll have to do something more clever here if you want to fix the value in post-processing like this.

Thanks - I guess that’s a separate issue to be resolved for that specific method.