Tag Archives: Julia

Julia, custom serialization with JSON.jl

By: Picaud Vincent

Re-posted from: https://pixorblog.wordpress.com/2026/05/05/julia-custom-serialization-with-json-jl/

Introduction

The GitHub:JSON3.jl package has been deprecated. That bothered me a little because I had to migrate a lot of my code to use GitHub:JSON.jl. Luckily, the migration turned out to be easier than I expected.

My use case is a bit special: I have to serialize my structures with type information so that I can retrieve the exact types after deserialization.

I know about GitHub:BSON.jl (see also Wiki:BSON) and Julia:Serialization, but I didn’t want to use them because they produce binary files. I wanted to keep a human‑readable format.

In this note I give a minimal working example that might save you some time.

Code

We’ll need the JSON.jl package. We also use StaticArrays.jl to show how to preserve the right vector type when deserializing an AbstractVector.

using JSON
using StaticArrays 

Let’s imagine we have an abstract type Abstract_Foo and two concrete types: Foo_A and Foo_B.

abstract type Abstract_Foo end

@nonstruct struct Foo_A{V <: AbstractVector}  <: Abstract_Foo
    v::V
    x::Float64
end

@nonstruct struct Foo_B <: Abstract_Foo
    v::AbstractVector
    n::Int
end 

Nothing special here, except the @nonstruct macro. That macro comes from GitHub:StructUtils.jl, a package used by JSON.jl to automate common struct operations (construction, etc.).

Using Doc:@nonstruct in front of a struct definition marks it as “special”. You tell JSON.jl to treat it as a primitive type that should be converted directly using lift() and lower() methods, rather than constructing it from field values. In short, you have to do all the work by hand, but you also get all the freedom to serialize and deserialize the structure however you want.

Serialization

During serialization the lower() method is called. We save the field values but also any type information needed for deserialization. Personally, I store this information in a field called type that holds the type of the structure. The name type isn’t special, you could call it internal_type, but I think it’s good practice to adopt a convention and stick to it.

function StructUtils.lower(to_serialize::Foo_A)

    return (type = string(typeof(to_serialize)),
            v = to_serialize.v,
            x = to_serialize.x)
end

For Foo_B, it’s a bit more complicated because the v field is an AbstractVector type, so we need an extra field to save the type information:

function StructUtils.lower(to_serialize::Foo_B)

    return (type = string(typeof(to_serialize)),
            v_type = string(typeof(to_serialize.v)),
            v = to_serialize.v,
            n = to_serialize.n)
end

Demonstration

Here’s a demonstration of serialization:

a = Foo_A(@SVector(Int[1,2]),1.2)

a_json_str = JSON.json(a, pretty=true)
{
  "type": "Foo_A{SVector{2, Int64}}",
  "v": [
    1,
    2
  ],
  "x": 1.2
}

Now for Foo_B

b = Foo_B(Float16[3,4],34)

b_json_str = JSON.json(b, pretty=true)
{
  "type": "Foo_B",
  "v_type": "Vector{Float16}",
  "v": [
    3.0,
    4.0
  ],
  "n": 34
}

Deserialization

To deserialize you have to define the lift() methods.

First, we intercept all Abstract_Foo occurrences and extract the concrete type. Right now the type is a String, to turn it into a Julia DataType we use Base.eval() and Meta.parse(). Once we have that instantiated type, we continue deserialization with it.

function StructUtils.lift(type::Type{<:Abstract_Foo},
                          to_deserialize)

    actual_type = Base.eval(Main,Meta.parse(to_deserialize.type))
    StructUtils.lift(actual_type,to_deserialize)
end

Now we redefine lift() for the specific concrete types. You have to be careful to define these new methods for all possible specializations, otherwise you’ll get an infinite recursion with the previous function. It would be nice to detect this situation, but how? (feel free to add a comment 🙂 )

For Foo_A:

function StructUtils.lift(type::Type{<:Foo_A{V}},
                          to_deserialize) where {V<:AbstractVector}

    v = StructUtils.lift(V,to_deserialize.v) # deserialize vect.
    x = to_deserialize.x

    type(v,x)
end 

For Foo_B:

function StructUtils.lift(type::Type{<:Foo_B},
                          to_deserialize)

    v_type = Base.eval(Main,Meta.parse(to_deserialize.v_type))
    v = StructUtils.lift(v_type,to_deserialize.v) # deserialize vect.
    n = to_deserialize.n

    type(v,n)
end 

Demonstration

Notice that we don’t need to give the exact type, just Abstract_Foo is enough.

JSON.parse(a_json_str,Abstract_Foo)
Foo_A{SVector{2, Int64}}([1, 2], 1.2)
JSON.parse(b_json_str,Abstract_Foo)
Foo_B(Float16[3.0, 4.0], 34)

Remarks

@kwdef and @nonstruct together

You cannot use @kwdef and @nonstruct together. The following code generates an error:

@nonstruct @kwdef struct Foo_C <: Abstract_Foo
end

The solution is to do the work of @nonstruct by hand. First, look at what this macro does:

@macroexpand @nonstruct  struct Foo_C <: Abstract_Foo
end
quote
    begin
        $(Expr(:meta, :doc))
        struct Foo_C <: Abstract_Foo
        end
    end
    StructUtils.structlike(::StructUtils.StructStyle, ::Type{<:Foo_C}) = false
end

So the fix is simply to replace

@nonstruct @kwdef struct Foo_C <: Abstract_Foo
end

by

@kwdef struct Foo_C <: Abstract_Foo
end

StructUtils.structlike(::StructUtils.StructStyle,
                       ::Type{<:Foo_C}) = false

Writing / reading file

Please follow the JSON.jl official doc, nothing special here:

JSON.json(file, a, pretty=true)      # write file
JSON.parsefile(file, Abstract_Foo)   # read file

Complete code

To make your life easier, here’s the complete code:

using JSON
using StaticArrays

abstract type Abstract_Foo end

@nonstruct struct Foo_A{V <: AbstractVector}  <: Abstract_Foo
    v::V
    x::Float64
end

@nonstruct struct Foo_B <: Abstract_Foo
    v::AbstractVector
    n::Int
end

function StructUtils.lower(to_serialize::Foo_A)

    return (type = string(typeof(to_serialize)),
            v = to_serialize.v,
            x = to_serialize.x)
end

function StructUtils.lower(to_serialize::Foo_B)

    return (type = string(typeof(to_serialize)),
            v_type = string(typeof(to_serialize.v)),
            v = to_serialize.v,
            n = to_serialize.n)
end

a = Foo_A(@SVector(Int[1,2]),1.2)

a_json_str = JSON.json(a, pretty=true)

println(a_json_str)

b = Foo_B(Float16[3,4],34)

b_json_str = JSON.json(b, pretty=true)

println(b_json_str)

function StructUtils.lift(type::Type{<:Abstract_Foo},
                          to_deserialize)

    actual_type = Base.eval(Main,Meta.parse(to_deserialize.type))
    StructUtils.lift(actual_type,to_deserialize)
end

function StructUtils.lift(type::Type{<:Foo_A{V}},
                          to_deserialize) where {V<:AbstractVector}

    v = StructUtils.lift(V,to_deserialize.v) # deserialize vect.
    x = to_deserialize.x

    type(v,x)
end

function StructUtils.lift(type::Type{<:Foo_B},
                          to_deserialize)

    v_type = Base.eval(Main,Meta.parse(to_deserialize.v_type))
    v = StructUtils.lift(v_type,to_deserialize.v) # deserialize vect.
    n = to_deserialize.n

    type(v,n)
end

JSON.parse(a_json_str,Abstract_Foo)

JSON.parse(b_json_str,Abstract_Foo)

Conclusion

There’s nothing more ridiculous than a conclusion, because nothing is ever finished. But I admit it’s still handy to say goodbye 🙂

diff all the things! Part 2

By: Domenic Di Francesco

Re-posted from: https://allyourbayes.com/posts/gradients_pt2/


TLDR

After reading Part 1, we know how great autodiff is, and how Julia lets us use it freely. We introduced the Enzyme library and showed some example applications.

Here in Part 2, we look at an emerging competing library, Mooncake, and why it’s worth keeping an eye on ?


recap: Enzyme is great ?

As we saw in Part 1, Enzyme solves the reliability problems that Zygote can (occasionally) exhibit. It differentiates our code at the LLVM level and is mega performant.

We also discussed in Part 1, how nice it is that Julia projects use only Julia code and the benefits of this for interoperability. This isn’t strictly true for Enzyme. Since it operates at the LLVM level, it can differentiate code in any language that compiles to the LLVM IR (including C++, and Rust!). We call a Julia API, but the differentiation is actually happening in the Enzyme software, outside of Julia.

We’ve mentioned it a few times as if it’s basic knowledge – wasn’t for me! LLVM is a compiler framework used by many languages (including Julia) as a shared backend for generating machine code.

When you write Julia, your code eventually gets lowered to the LLVM level (we even saw how you can display this, using @code_llvm in Part 1).

Enzyme operates at this level. This means your high-level code has already been simplified and optimised a fair bit before any autodiff is attempted. This is a big reason why Enzyme is so performant.

The downside is that at the LLVM level, there is no concept of Julia types, dispatch, or packages. There are challenges (read on for details) associated with needing to cross this boundary.

So how good can autodiff be, if we learn lessons from Zygote and Enzyme, but stay entirely in Julia….

enter Mooncake ?

The pitch is as follows: an AD library, written entirely in Julia and competitive with Enzyme.

Oh, Mooncake

Like Enzyme, Mooncake handles mutation, control flow, and provides reliable correctness – stuff that Zygote can struggle with.

…but unlike Enzyme, it does all of this without leaving Julia. It is a self-described language-level autograd compiler.

Zygote (and ReverseDiff) are tracing AD libraries. They execute the function and record all operations on a tape. The tape can then be replayed in reverse to compute the gradients neatly – especially for simple functions.

The tape is data, not code, so Julia can’t optimise it.

A tape records a path (as your code runs), but Enzyme and Mooncake can apply the chain rule to the code before it runs, and then produce new code that: (a) preserves mutation and control flow, and (b) returns gradients with very little overhead, and (c) can be optimised by the compiler. This is only possible because (as we saw in Part 1), Julia’s compiler produces an IR that retains the loops, branches, types, and all, of your code. Python doesn’t have this and so it has to trace.

autodiff library reads generates consequence
Zygote Julia IR tape (via fragile IR transforms) slow on control flow, can silently mishandle edge cases
ReverseDiff runtime trace tape can’t be compiler-optimised
Enzyme LLVM IR new LLVM code fast, but outside Julia
Mooncake Julia IR new Julia functions fast, and stays in Julia

All of the major Python AD libraries (PyTorch, TensorFlow, JAX) implement some kind of tape or tracing. AFAIK, the only one that doesn’t re-trace every operation is JAX, which uses a method that imposes a fixed control flow – hence the self-described “sharp bits” ?.

Why does staying in Julia matter?

  • debugging: we get Julia errors – not messages from LLVM-land, which I certainly can’t follow.
  • new custom rules: adding new derivatives just requires a Julia function. Mooncake provides helpful macros for this too!
  • stability: we won’t get breaking changes on new Julia releases if something changes with the LLVM. I have read that Enzyme has previously had to make fixes for this.

If you still aren’t sure whether to take notice, then listen to the man himself, Chris Rackauckas, “Mooncake is Zygote, but done good, with mutation support” (see clip below)

lets be honest about current trade-offs

It’s tricky trying to find definitive benchmarks for the Julia AD ecosystem. The discourse pages will provide one-off examples of older libraries occasionally outperforming newer ones in terms of speed and reliability. However, the general consensus seems to be that Enzyme is currently the most performant, with Mooncake not far behind.

Let’s do our own….

# loading our autodiff libraries
using ReverseDiff, Zygote, Enzyme, Mooncake

# and to test
using BenchmarkTools, Random

# a simple function
test_function(x) = sum(sin.(x) .+ cos.(x.^2))

# and an example with some control flow (the *if*'s can be a problem for some AD libraries)
function control_flow_function(x)
    s = 0.0
    for i in eachindex(x)
        if x[i] > 0.0
            s += sin(x[i])
        else
            s += cos(x[i])
        end
    end
    return s
end

# simulate some reproducible inputs
x = rand(MersenneTwister(2311), 1000)

Loops, if/else branches, and recursion are how we naturally write scientific code. In this example, the control flow example has a for loop (with iterations) and a branching if/else statement. A tape-based AD (see callout above, “How does Mooncake work”) needs to trace every iteration and record whichever branch was taken, each with its own allocation!

The tape-free approach taken by Enzyme and Mooncake avoids this overhead entirely, producing derivative code where the loop is still a loop. They are especially powerful when differentiating through code with lots of control flow.

@btime ReverseDiff.gradient(test_function, $x)[begin]
@btime ReverseDiff.gradient(control_flow_function, $x)[begin]

@btime Zygote.gradient(test_function, $x)
@btime Zygote.gradient(control_flow_function, $x)

@btime Enzyme.gradient(Reverse, test_function, $x)
@btime Enzyme.gradient(Reverse, control_flow_function, $x)

Unlike Enzyme, which has both gradient() (convenience) and autodiff() (more explicit specification), Mooncake’s value_and_gradient!! is the main API. There isn’t a separate “advanced” form.

In Julia, the familiar single ! denotes a function that may mutate its arguments (but leaving them in a valid state), like how sort!(x) redefines x as a sorted array.

The double !! is new to me, and is apparently a convention from the AD ecosystem, and not base Julia. It signifies that arguments may be mutated, with no guarantees about how meaningful/useful they are afterwards. Presumably in the below example, it is the aggressive recycling of memory that is being signposted i.e. previous contents of the cache are being overwritten and shouldn’t be referenced? But maybe there is more to it.

# Mooncake needs a prepared cache
cache = Mooncake.prepare_gradient_cache(test_function, x);
@btime Mooncake.value_and_gradient!!(cache, test_function, $x)

cache = Mooncake.prepare_gradient_cache(control_flow_function, x);
@btime Mooncake.value_and_gradient!!(cache, control_flow_function, $x)

The results… ? ? ?

test_function()

library time (μs) allocations memory
Enzyme 15.4 8 24.16 KiB
Zygote 16.5 49 97.14 KiB
ReverseDiff 23.7 105 67.78 KiB
Mooncake 24.8 11 16.48 KiB

control_flow_function()

library time allocations memory
Enzyme 3.2 μs 3 8.06 KiB
Mooncake 14.5 μs 3 352 B
ReverseDiff 257.0 μs 8,023 375.70 KiB
Zygote 3,481 μs 42,118 9.51 MiB

As expected, Enzyme is fast. It sees this already-optimised LLVM code and differentiates that.

Although Mooncake was the slowest on the simple test function, it was only times slower than Enzyme – same order of magnitude, still competitive. The newer libraries really shone on the control flow function, hundreds (Mooncake) to thousands (Enzyme) times faster than Zygote.

And look at the memory! ?

Mooncake allocates so little! After the initial cache preparation, the gradients calculated in training or inference loops will be almost zero-allocation, which is fantastic for performance or memory bottlenecks.

reviewing the Bayesian model from Part 1

Using the same simulated data and priors, let’s run the linear regression using Turing (look how much neater it is) and swap AD backends with a single argument.

using Turing, Distributions

@model function linear_regression(x, y)
    # priors
    α ~ α_prior; β ~ β_prior; σ ~ σ_prior
    
    # there are ofc lots of ways to vector/optim-ise the likelihood, but...
    for i in eachindex(y)
        y[i] ~ Normal+ β * x[i], σ)
    end
end

linear_model = linear_regression(x, y); n_draws = 1_000

just running a single chain for post-warmup samples for the purposes of this example:

mooncake_draws =  sample(linear_model, NUTS(; adtype=AutoMooncake(; config=nothing)), n_draws)

enzyme_draws = sample(linear_model, NUTS(; adtype=AutoEnzyme(; mode=Enzyme.set_runtime_activity(Enzyme.Reverse))), n_draws)

not ideal syntax, but within each sample you can specify an AD backend. Actually in the above example, I wouldn’t expect any benefit from using Mooncake or Enzyme – it’s a tiny model ( parameters, data points) and, as discussed in Part 1, a Forward Mode AD library like ForwardDiff would likely be the best bet. As I mentioned in Part 1, there have been cases where I benefitted enormously from simply switching the AD backend to AutoMooncake().

my current thoughts

Please keep in mind, I don’t feel best placed to comment on direction of travel. I am a user (and fan) of the Julia scientific computing ecosystem, but I am not an open source developer.

Mooncake is still being developed by that community, with the stated goal to: “improve on ForwardDiff.jl, ReverseDiff.jl, and Zygote.jl in several ways.” It encourages us to use it, in a seemingly arduous way. Either by adding an extra step (prepare_gradient()), or by using DifferentiationInterface – a common interface for multiple Julia AD libraries.

As with most statistical software, I expect the future success of these libraries will be tied to how well they integrate with the rest of the ecosystem. If Mooncake can be subbed in for Enzyme in libraries like Turing and Flux, then it will be easy to switch and benefit from its features.

Or will the Julia AD ecosystem fail to converge and I’ll end up writing a Part 3 in this series? ?

Citation

BibTeX citation:
@online{di_francesco2026,
  author = {Di Francesco, Domenic},
  title = {Diff All the Things! {Part} 2},
  date = {2026-03-01},
  url = {https://allyourbayes.com/posts/gradients_pt2/},
  langid = {en}
}
For attribution, please cite this work as:
Di Francesco, Domenic. 2026. “Diff All the Things! Part 2.”
March 1, 2026. https://allyourbayes.com/posts/gradients_pt2/.

What Agentic AI “Vibe Coding” In The Hands Of Actual Programmers / Engineers

By: Christopher Rackauckas

Re-posted from: https://www.stochasticlifestyle.com/what-agentic-ai-vibe-coding-in-the-hands-of-actual-programmers-engineers/

I often have people ask how I’m using Claude code so much, given that I have a bot account storming the SciML Open Source Software repositories with tens to hundreds of PRs a day, with many of them successful. Then GSoC students come in with Claude/Codex and spit out things that are clearly just bot spam, and many people ask, what is different? The difference is actually knowing the codebase and the domains. It turns out that if you know how to actually program, you can use the LLM-based interfaces as just an accelerator for some of the tedious work that you have to do. I tend to think about it the same as working with a grad student: you need to give sufficient information for it to work, and if you don’t get good stuff back it’s because you didn’t explain it well enough.

Here’s two examples that I’d like to point out that recently showed up, and when you see the prompts you’ll instantly see how this differs from some random GSoC student’s vibe coded “solve this issue for me please and try hard!” prompt. The first issue was this numerical issue in DAE interpolation. I was able to look at this and identify that the reason for it is because it’s using a fallback Hermite interpolation when it should be using a specialized interpolation. The specialized interpolation is actually already implemented in a bit of the code for the initial conditions of the nonlinear solver, but it’s not setup throughout the rest of the code so that for plotting it knows how to do the better interpolation. So I created a prompt that gave it all of the context required in order to create the scaffolding for the interpolation to go into all of the right places:

OrdinaryDiffEq.jl's FBDF and QNDF currently uses the Hermite
interpolation fallback for its dense output / interpolation.
However, these have a well-defined interpolation on their k
values that should be used. For example, FBDF has the Legrange
interpolation already defined and used in its nonlinear solver
initial point
https://github.com/SciML/OrdinaryDiffEq.jl/blob/4004fc75dff0
9855bb96333f02d4ce0bb0f8c57c/lib/OrdinaryDiffEqBDF/src/
dae_perform_step.jl#L418.
This should be used for its dense output. While QNDF has it
defined here:
https://github.com/SciML/OrdinaryDiffEq.jl/blob/4004fc75dff0
9855bb96333f02d4ce0bb0f8c57c/lib/OrdinaryDiffEqBDF/src/
bdf_perform_step.jl#L935-L939 .
If you look at other stiff ODE solvers that have a specially
defined interpolation like the Rosenbrock methods, you see an
interpolations file
https://github.com/SciML/OrdinaryDiffEq.jl/blob/4004fc75dff0
9855bb96333f02d4ce0bb0f8c57c/lib/OrdinaryDiffEqRosenbrock/
src/rosenbrock_interpolants.jl
with a summary
https://github.com/SciML/OrdinaryDiffEq.jl/blob/4004fc75dff0
9855bb96333f02d4ce0bb0f8c57c/lib/OrdinaryDiffEqRosenbrock/
src/interp_func.jl
that overrides the interpolation. Importantly too though, the
post-solution interpolation saves the integrator.k which are
the values used for the interpolation
https://github.com/SciML/OrdinaryDiffEq.jl/blob/4004fc75dff0
9855bb96333f02d4ce0bb0f8c57c/lib/OrdinaryDiffEqRosenbrock/
src/rosenbrock_perform_step.jl#L1535.
If I understand correctly, this is already k in FBDF but in
QNDF this is currently the values named D. The tests for
custom interpolations are
https://github.com/SciML/OrdinaryDiffEq.jl/blob/4004fc75dff0
9855bb96333f02d4ce0bb0f8c57c/test/regression/
ode_dense_tests.jl
Search around for any more Rosenbrock interpolation tests as
well. This should make it so that savevalues! always uses the
interpolation
https://github.com/SciML/OrdinaryDiffEq.jl/blob/4004fc75dff0
9855bb96333f02d4ce0bb0f8c57c/lib/OrdinaryDiffEqCore/src/
integrators/integrator_utils.jl#L122
while if dense=true (i.e. normally when saveat is not
specified) the interpolation is then done on sol(t) by using
the saved (sol.u[i], sol.t[i], sol.k[i]).

Notice that some of the key features are that I am telling it exactly where in the code to look for the interpolation that exists, give an example of another stiff ODE solver that is using a high order interpolation, show exactly where these things are tested, and show other place sin the code where the interpolation is used. With this, it has a complete picture of exactly what it has to do in order to get things done.

Another example of this was with SciMLSensitivity.jl where a complex refactor needed to be done. I’ll let the prompt speak for itself:

The SciMLSensitivity.jl callback differentiation code has an
issue with the design. It uses the same vjp calls to
`_vecjacobian!` but its arguments are not the same. You can
see this here
https://github.com/SciML/SciMLSensitivity.jl/blob/master/
src/callback_tracking.jl#L384-L394
where the normal argument order is
(dλ, y, λ, p, t, S, isautojacvec, dgrad, dy, W)
but in the callback one it's putting p second. This is
breaking to some of the deeper changes to the code, since
for example Enzyme often wants to do something sophisticated
https://github.com/SciML/SciMLSensitivity.jl/blob/master/
src/derivative_wrappers.jl#L731-L756
but this fails for if y is now supposed to be a p-like
object. This is seen as the core issue in 4 open PRs
(https://github.com/SciML/SciMLSensitivity.jl/pull/1335,
https://github.com/SciML/SciMLSensitivity.jl/pull/1292,
https://github.com/SciML/SciMLSensitivity.jl/pull/1260,
https://github.com/SciML/SciMLSensitivity.jl/pull/1223)
where these all want to improve the ability for p to not be
a vector (i.e. using the SciMLStructures.jl interface
https://docs.sciml.ai/SciMLStructures/stable/interface/ and
https://docs.sciml.ai/SciMLStructures/stable/example/)
but this fails specifically on the callback tests because
the normal spot for p is changed, and so it needs to do this
interface on the other argument. This is simply not a good
way to make the code easy to maintain. Instead, the callback
code needs to be normalized in order to have the same
argument structure as the other codes.

But this was done for a reason. The reason why p and dy are
flipped in the callback code is because it is trying to
compute derivatives in terms of p, keeping y as a constant.
The objects being differentiated are
https://github.com/SciML/SciMLSensitivity.jl/blob/master/
src/callback_tracking.jl#L466-L496.
You can see `(ff::CallbackAffectPWrapper)(dp, p, u, t)`
flips the normal argument order, but it's also doing
something different, so it's not `u,p,t` instead its `p,u,t`
but it's because it's calculating `dp`, i.e. this is a
function of `p` (keeping u and t constant) and then computing
the `affect!`'s change given `p`, and this is what we want
to differentiate. So it's effectively hijacking the same
`vecjacobian!` call in order to differentiate this function
w.r.t. p by taking its code setup to do `(du,u,p,t)` and
then calling the same derivative now on `(dp,p,u,t)` and
taking the output of the derivative w.r.t. the second
argument.

But this is very difficult to maintain if `p` needs to be
treated differently since it can be some non-vector argument!
So we should normalize all of the functions here to use the
same ordering i.e. `(ff::CallbackAffectPWrapper)(dp, u, p, t)`
and then if we need to get a different derivative out of
`vecjacobian!`, it should have a boolean switch of the
behavior of what to differentiate by. But this would make it
so SciMLStructures code on the `p` argument always works.

Now this derivative does actually exist, the `dgrad` argument
is used for the derivative of the output w.r.t. the p
argument, but if you look at the callback call again:
  vecjacobian!(
      dgrad, integrator.p, grad, y, integrator.t, fakeSp;
      dgrad = nothing, dy = nothing
  )
it's making dgrad=nothing. The reason why it's doing this is
because we only want that derivative, so we effectively want
the first argument (the normal derivative accumulation ddu) to
be nothing, but `vecjacobian!` calls do not support that? It
seems like they do have dλ=nothing branches, so it should work
to flip the arguments back to the right ordering and then just
setup to use the dgrad arguments with a nothing on the dλ, but
this should get thoroughly tested. So do this refactor in
isolation in order to get all of the callback tests passing
with a less hacky structure, and then the SciMLStructures PR
should be put on top of that. All 4 of those PRs should be
able to be closed if the p just supports the SciMLStructures
(they are all almost the same).

So hopefully that helps people understand who are “vibe code curious” how they can use this. These are prompts that I slammed into Telegraph to text my OpenClaw during karaoke night to spin off the PRs, so it’s more that the interface is convenient (i.e. I don’t need a laptop open to program) rather than trying to get around the knowledge gap. The knowledge is still there, it’s just a different interface to programming.

The post What Agentic AI “Vibe Coding” In The Hands Of Actual Programmers / Engineers appeared first on Stochastic Lifestyle.