So if you can’t tell by my username, I’m a horrendous programmer. That said, I’m hoping to get opinions on the following:
1. What would actually be a decent way to achieve this
2. Is there any hope for user-dependent meta-programming in compiled code?
Suppose I would like to develop a package to numerically integrate through some hefty dynamics models, dependent on a user’s desires: [:modelA, :alternative_to_model_C]
, where each entry turned on a simulated physical effect: gravity, wind model 1, wind model 2, etc. One could then define the function and proceed, assuming the contents of the latter array were appropriately mapped to the Boolean sub-array, opts[1]
:
function somethingLessEgregious(state0, opts)
function dynamics!(dS, state, options, Δt)
options[1][1] && A_dynamics!(dS, state, options[2])
options[1][2] && B_dynamics!(dS, state, options[3])
options[1][3] ? C_dynamics!(dS, state) : alternative_C_dynamics!(dS, state)
end
commonSense = ODEProblem(dynamics!, state0, (0.0, 3.14159), opts)
return solve(commonSense)
end
The numerical integration itself could take on the order of several minutes (high fidelity models are…lovely), but would only need to be performed on occasion, after which the data could be saved for a rainy day. Even if there were 10+ effects the user could specify within the array of symbols, I realize that the logic implemented in dynamics!
is computationally very light, whereas defining multiple methods could get rather unwieldy.
The following solution is akin to chopping off ones leg in order to prevent a singular mosquito bite, but the “what if?” question got the best of me:
models = [:modelA, :alternativeC]
function absolutelyAwful(state0, opts, models)
lunacy = "function metaDynamics!(dS, state, options, Δt)\n"
:modelA in models && (lunacy *= "A_dynamics!(dS, state, options[1])\n")
:modelB in models && (lunacy *= "B_dynamics!(dS, state, options[2])\n")
:modelC in models && (lunacy *= "C_dynamics!(dS, state)\n")
:alternativeC in models && (lunacy *= "alternative_C_dynamics!(dS, state)\n")
lunacy *= "return dS\n end"
eval(Meta.parse(lunacy))
updated(arg1, arg2, arg3, arg4) = Base.invokelatest(metaDynamics!, arg1, arg2, arg3, arg4)
someonesNuts = ODEproblem(updated, state0, (0.0, 3.14159), opts)
return solve(someonesNuts)
end
Not surprisingly, the performance is many thousand times worse. But hey, if I chop off one leg, I have a backup, right?
The meta-programming question ties in very closely with the “automatic recompilation of dependent functions” discussion. Is there any hope for being able to re-compile a section of code whose structure depends on user inputs without slowing things down with Base.invokelatest
(although I’m appreciative that such a function exists!)? Are there any suggestions for developing a package where such a dynamics!
function does not have to unnecessarily check so much logic at every step of the numerical integration scheme while still being able to account for potentially many tens of user-dependent options? A much better solution may very readily be apparent, but it evades me.
Thanks for the input and forgive me for the excessively long post. I wasn’t sure how best to capture certain complexities.