Hello,
I have a struct that I would like to include a function in. How should I declare its type?
Base.@kwdef mutable struct Tmp
Δt :: Float64 = 0.3
t :: Float64 = 0
timestep :: Int = 0
function = 0.0
end
By default it returns 0 Float64, but it can also return other Float64 values. However, I believe that’s no its type. What’s the type of a function? Running typeof on a function doesn’t return anything.
Thanks!
Ah, thanks. That’s weird, since I’ve been using that line for a long time. I guess the {} stuff changed some things.
Now the issue is the function is not modifiable.
Base.@kwdef mutable struct Tmp21{T<:Function}
Δt :: Float64 = 0.3
t :: Float64 = 0.0
timestep :: Int = 0
a :: T = (_...) -> 0.0
end
tmp=Tmp21()
tmp.a=(x->x*2)
ERROR: MethodError: Cannot `convert` an object of type var"#196#197" to an object of type var"#187#192"
Closest candidates are:
convert(::Type{T}, ::T) where T at essentials.jl:171
Stacktrace:
[1] setproperty!(::Tmp21{var"#187#192"}, ::Symbol, ::Function) at .\Base.jl:34
[2] top-level scope at REPL[52]:1
To be modifiable you need to remove the parameter (because each function has its own type) and declare that Function. But that will make the field abstract, and that will be bad for performance (if that matters).
One odd alternative would be (I don’t know if this would peform well, though):
julia> Base.@kwdef mutable struct A
f::Ref = Ref(() -> 0)
end
A
julia> a = A()
A(Base.RefValue{var"#2#4"}(var"#2#4"()))
julia> a.f[]()
0
julia> a.f = Ref(sin)
Base.RefValue{typeof(sin)}(sin)
julia> a.f[](π/2)
1.0
If you use Base.@kwdef, a parametric type T, and give a default argument for the function a, T will be the type of the default argument. If you use an anonymous function, that type will be some random var"#xyz" thing (try typeof(tmp.a) in your last example). The point is that each function has its own type. So sin is of type "typeof sin", same for cos and so on. In the mutable struct you have created it is no problem to change e.g. t to 1.0, because only the value is changed. But if you want to swap out the function and you have a parametric type, where each type is set in stone, you just cannot change a to another function, because this also changes the type not the value. There are two ways out here: 1) fall back to a::Function, which might come with performance drawbacks, as the compiler won’t be able to make certain optimizations, or 2) just leave the default argument blank a::T. Then you can create new instances of your struct with Tmp21(;a=sin), Tmp21(;a=cos), so you need a new struct for each new function, but you can always change t, timestep etc.
Looking at the struct you want to define I assume you want to containerize the parameters for some time-dependent simulation. You can also take a step back and reconsider your code, e.g. fixed time series can nicely be expressed just with ranges 0:0.1:100. Or you can also take a look at named tuples for initializing your code, e.g. params = (timespan=0:0.1:10, f=sin).
@lmiq and @maxfreu
Yes, performance is critical, so I need something that works very well. Redefining the entire struct if I need to update the function is not unreasonable. Maybe that’s what I’ll do.
Regarding taking a step back: what this function is supposed to be is a user defined body motion for my simulation. It actually would return a StaticArray with the velocity of each point in my geometry. That’s why it can’t be a simple range or something like that. I want to be able to have an input parameter which is a generic time-dependent motion (e.g. a rotation matrix or what have you). A function within the solver struct was the best I could come up with, but I’m open to opinions =].
Thanks a lot!
I made it mutable because there’s a lot of stuff in there that gets updated (e.g., current timestep). The function being mutable is nice in case I want a body to do a certain motion for n timesteps and then change that function entirely for another n timesteps. But redeclaring the struct in that case is not a bad solution. Not the most convenient, but very doable.
Thanks!