Best practices for flattening and reconstructing nested structs for ODEs, Solve, Kalman.jl etc

Hello All,

I’m prototyping a space/astrodynamics simulation and optimization framework in Julia. For the user interface, the most intuitive way to encapsulate all the data is on structs. Below is a toy example which is far simpler than many use cases, but hopefully gets the point across.

struct CartesianState
    rv::SVector{3, Float64}
    vv::SVector{3, Float64}
end

struct Spacecraft
    state::CartesianState
    mass::Float64
end

struct ForceModel
    mu::Float64
    Cd::Float64
end

Now my question. I need a way to efficiently create “flat” state/optimization vectors for DifferentialEquations.jl., solve(), JuMP, Kalman.jl and other packages. If a user specifies Spacecraft.CartesianState and ForceModel.mu as problem variables (this is truly a toy, real problems are substantially more complicated), I need a fast, clean way to construct a vector from those fields for initializing ODEs, solvers, and a way to map vectors from those systems back to the structs in the sim system to evaluate ODE values, optimization functions etc. I need a way do this that is agnostic to package interface (solve(), JuMP, Kalman.jl, DifferentialEquations.jl) so unless I am missing an existing, elegant solution, I need to handle the “bookkeeping” on my end.

I’ve looked at ComponentArrays.jl, and Accessors.jl ad other packages, and they don’t seem to quite solve this problem. I’ve rolled my own and it works OK for a simple problem, but, doesn’t scale well for structs with nested composition . Unless I am missing something, there is not a canonical solution to this problem. Am I missing a package out there that solves this generally and efficiently? If there is no existing solution, any advise on how to tackle this problem?

1 Like

I don’t have an answer to your actual question, but I just want to point out that struct Spacecraft is an abstract container, so you may want to turn it into a parametrized struct (struct Spacecraft{T<:CartesianState} and then state::T) to avoid getting a performance hit.

Yep. Thanks. I learned that lesson recently and am converting my structs to use that approach for efficiency.

1 Like

NamedArrayPartition from RecursiveArrayTools?

Thanks. I tried a few approaches over the last month, and I can’t remember if I tried those tools. I’ll see if that works and post back here.

Try this:

obj = your arbitrary object

using AccessorsExtra

nums = getall(obj, RecursiveOfType(Number))
# nums is a flat collection of numbers
# ... do some manipulation with nums ...
newobj = setall(obj, RecursiveOfType(Number), nums)

Supports arbitrary structs, and more complex/fine-grained manipulations as well.