Best practices for modeling systems that evolve over time — mutability, patterns, and performance?

What you can´t (for performance reasons) is to use abstract types for the fields like this. To keep generality you have to parameterize them, i. e.

mutable struct Spacecraft{S<:AbstractSpace} <: AbstractPoint
    state::S
    ...

With that an instance of a space craft will be concrete, but the definition of the type is generic enough to support different types of spaces.

2 Likes

Ah, I see! This saved me a lot of time tinkering. Thank you!

I need to handle defaults flexibly, and not require setting defaults on an struct that will encapsulate a lot of complexity. I think a Parameterized struct, with keyword arg out constructor solves these problems. Am I missing anything subtle here to be warned of:

struct Spacecraft{S<:AbstractState, T<:Time}
state::S
time::T
end

Defaults

default_state() = CartesianState([7000.0, 0.0, 0.0, 0.0, 7.5, 0.0])
default_time() = Time(“2015-09-21T12:23:12”, :tai, :isot)

Keyword-based outer constructor

function Spacecraft(;
state::AbstractState = default_state(),
time::Time = default_time()
)
Spacecraft(state, time)
end

all defaults

sc1 = Spacecraft()

no defaults

sc4 = Spacecraft(
time = Time(“2025-09-21T12:23:12”, :tt, :isot),
state = CartesianState([7100.0, 0.0, 500.0, 0.0, 7.6, 0.0]),
)

Hello Orestis, I was watching a few videos from last summer’s session at JuliaCon 2024 a few days ago. Do you know if a similar session is planned for 2025?

On the topic of units, for some purposes DynamicQuantities.jl may also be worth considering. The short version is that where Unitful puts all of the units in the type domain, so they are handled at compile time, DynamicQuantities (unless otherwise specified) puts everything into base SI marks and does any unit handling at runtime. The upside of this is that you can have a concretely-typed array of quantities with disparate units, which isn’t really possible for Unitful.
Both still have a little friction with SciML solvers, last I checked, but DynamicQuantities is a more natural fit I believe because of the array typing.

1 Like

Another aspect of this topic I’m not seeing mentioned are packages like Accessors.jl which “make the immutable mutable”, or in other words provide easy ways to create a copy of an arbitrary (nested) struct with a particular value (or values) changed. I use this quite heavily, and if you need to take it a step further and programmatically change some subset of fields then setproperties from ConstructionsBase.jl will help.

2 Likes

That seems ok. Just to add that in Julia you have @kwdef to help you with that:

julia> @kwdef struct A{T1,T2}
           x::T1 = 1
           y::T2 = 1.0
       end

julia> A()
A{Int64, Float64}(1, 1.0)

julia> A(;x=1, y=1)
A{Int64, Int64}(1, 1)

julia> A(;x=1.0, y=1.0)
A{Float64, Float64}(1.0, 1.0)


1 Like