I am trying to solve an economic model. The algorithm involves an iterative two-step procedure, like a back-and-forth optimization. There are lots of inputs and moving parts. I created a
struct to keep track of it all. What follows involves psuedocode rather than a MWE.
struct MyModel A B C D end
The algorithm works like this: (1) holding
B fixed, solve for new
A, (2) holding new
A fixed, solve for new
B. Repeat. Each step takes as input potentially all fields of
MyModel (and there many and of various types).
I created a functor that when called should initiate the algorithm and produce the solution (which is just
B after convergence). However, inside that function I call functions that do each step of the algorithm – also in place.
function updateA!(m::MyModel) # do stuff to get newA # updates A in place (using Setfield.jl) @set m.A = newA return nothing end function updateB!(m::MyModel) # do stuff to get newB # updates B in place (using Setfield.jl) @set m.B = newB return nothing end
So that the functor method actually is like this
function (m::MyModel)(;kw...) chg = Inf while chg > tolerance updateA!(m) updateB!(m) chg = ... end # A and B updated in place so returns nothing return nothing end
I am wondering what happens in this situation. Is it really using the same
m object everywhere? Do I need to worry about unexpected behaviors here, which could be very very difficult to debug? I think of functors as operating on or within themselves (perhaps this is completely incorrect), so it feels weird to “pass”
m to another mutating function as part of this process.
My initial goal of this approach was to keep the code clean, to eliminate unnecessary creation of temporary variables (eg. elements
D, etc. are shared between the two steps), and ultimately to have this be performant. I am open to suggestions if this is a poor design decision.