I have a question related to this, so I am posting to this topic again in the hopes I can get some guidance before making some design changes that would permeate what I am working on.
As I have continued to develop, I am addressing more complex use cases, and, I need to be able to change the type on a struct’s field during a simulation. For example, when a spacecraft leaves the Earth system, we use a different time system to model time. We also use different coordinate systems and state representations as a mission evolves. So, immutable concrete types in fields are problematic. And, reconstructing the struct causes me problems because it breaks references to the struct used in the sim elsewhere.
The solution I am considering is to use concrete “wrapper structs” and parameterize the state and time inside the wrappers for performance and type flexibility. If I need to change the state or time type, I reconstruct the wrapper which is concrete.
To hide this from users (who are aerospace engineers, not Julia experts), I would overload get/set property so users only work with the concrete time and state types and the nested structure pattern is hidden.
Here is working code. Is there a better way to do this? Feedback, thoughts?
Thanks in advance for any help here!!
# Abstract interfaces
abstract type AbstractState end
abstract type AbstractTime end
# Concrete types (there would be many more, these are examples)
struct CartesianState <: AbstractState
rv::Vector
end
struct UTC <: AbstractTime
seconds::Float64
end
struct TDB <: AbstractTime
seconds::Float64
end
# Parametric wrappers for time and state
struct OrbitState{T<:AbstractState}
state::T
end
struct SpacecraftTime{T<:AbstractTime}
time::T
end
# Spacecraft stuct
mutable struct Spacecraft
state::OrbitState
time::SpacecraftTime
end
# Outer constructor
function Spacecraft(state::AbstractState, time::AbstractTime)
Spacecraft(OrbitState(state), SpacecraftTime(time))
end
# Override get/set property for Spacecraft so wrappers and type management are hidden
function Base.getproperty(s::Spacecraft, sym::Symbol)
if sym === :state
return getfield(s, :state).state
elseif sym === :time
return getfield(s, :time).time
end
end
function Base.setproperty!(s::Spacecraft, sym::Symbol, val)
if sym === :time && val isa AbstractTime
return setfield!(s, :time, SpacecraftTime(val))
elseif sym === :state && val isa AbstractState
return setfield!(s, :state, OrbitState(val))
else
return setfield!(s, sym, val)
end
end
sc = Spacecraft(CartesianState([10.0e6,0.0,0,0,0,0,7.8,15.0,0]), UTC(123.456))
# Propagate to Earth Sphere of influence happens here
# Change time to TDB, would changer other types like state/coords too, keeping it simple for example
sc.time = TDB(4.5)
# Propagate to Moon happens here