The full background is the following: I have implemented a type Particles <: Real
which wraps a vector for Float64
. I can define lots of functions for this type, but some functions are hard to define in a meaningful way. In those situations, I want to fall back to propagate each float of the wrapped vector through the function one by one and collect the result in a new Particles
at the output. If the particles
appear as an argument to the top-level function, this is really straightforward. The problem appears when my type Particles
appears as a field nested somewhere in a struct I have no control over. As an example, consider this structure
julia> P
TransferFunction{ControlSystems.SisoRational{StaticParticles{Float64,100}}}
0.998 ± 0.1
-----------------
1.0*s + 1.0 ± 0.1
Continuous-time transfer function model
julia> dump(P)
TransferFunction{ControlSystems.SisoRational{StaticParticles{Float64,100}}}
matrix: Array{ControlSystems.SisoRational{StaticParticles{Float64,100}}}((1, 1))
1: ControlSystems.SisoRational{StaticParticles{Float64,100}}
num: Polynomials.Poly{StaticParticles{Float64,100}}
a: Array{StaticParticles{Float64,100}}((1,)) StaticParticles{Float64,100}[0.998 ± 0.1]
var: Symbol x
den: Polynomials.Poly{StaticParticles{Float64,100}}
a: Array{StaticParticles{Float64,100}}((2,)) StaticParticles{Float64,100}[1.0 ± 0.1, 1.0]
var: Symbol x
Ts: Float64 0.0
nu: Int64 1
ny: Int64 1
The particles appear deeply nested in the object P
which is the input to my function, and can be accessed by P.matrix[1].num.a[:]
and P.matrix[1].den.a[:]
. which are the expressions I am generating.
I want to create an object identical to P
, let’s call it P2
, but in all places where Particles
appear, I want to have Float64
instead. I then want to populate P2
with particle index i
for eachindex(particles)
. I then call my function with P2
which is completely free of weird particle types and put the result of the computation into an output object with the desired Particles
fields.
The real code to generate the expressions is here
I turn these expressions into functions that get and set fields in the generated intermediate buffers P2
and Pres
to store the result. The buffer is autogenerated here and it mostly works okay at the moment. The only problem is with the world age, causing me to not be able to use the getter and setter functions until the world age is updated. My current strategy is to make the user create a Workspace
object which creates the internal buffer P2
and the eval
ed functions, and then call this workspace object with the desired function in a later stage:
P = tf(1 ∓ 0.1, [1, 1∓0.1]) # The P shown above
w = Workspace(P) # Creates work buffer `P2` and some getter/setter functions
f = x->c2d(x,0.1) # This is the computation I want to perform on P but which fails if P contains Particles
Pd = w(f) # This works
However, if created and called in the same function:
tt = function (P)
w = Workspace(P)
f = x->c2d(x,0.1)
@time Pd = w(f)
end
@test_throws MethodError tt(P) # This causes a world-age problem. If this tests suddenly break, it would be nice and we can get rid of the intermediate workspace object.