Hello everyone, I am working on an aerospace system and as I am developing more complex use cases, I need to change fields on structs to a new type 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. I don’t need to do this often (relatively speaking to say an integration step size), but it does need to happen at certain points in a simulation. Reconstructing the whole spacecraft struct causes me problems because it breaks references to the struct used in the sim elsewhere.
I know using abstract types here causes major performance problems so I have avoided that. The solution I am considering is to use concrete “wrapper structs” and parameterize the state and time type 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 and since the wrapper internals are parameterized, it’s fields are also 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 behind the API.
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
# EXAMPLE USAGE HERE
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, this would happen in the internals not at user level, just showing what needs to happen
sc.time = TDB(4.5) # Time was UTC type, now it is TDB type
# Propagate to Moon happens here