Thanks to the curse of dimensionality, there are not many models that confront their agents with a wide range of choices. Most models present interesting choices, yes, but not lots and lots of choices.
Curses aside, there is a second problem giving agents lots of choices: the return functions are complicated and because of the limitations of MATLAB’s arrayfun implementation on GPUs, those complicates are often need to be replicated to compute other aspects of the overall economic system (the consumption function replicates most of the choice complexity to produce but one parametric value that goes into the larger economic equations; taxes are often contingent on decisions made; energy costs and rebates may represent their own complexities of choices informing consumption).
It is non-trivial/annoying to manage the amount of code replicated across these various considerations. I’m considering the idea of introducing a parameter to the return function that basically indicates what specific output we are calling it for: the Value Function itself, consumption, taxes, costs of various kinds, etc. In worst case we calculate everything and return the Value Function. This heavy calculation must be done across the entire range of possibilities, and is a big deal.
But we want to analyze the constituents of that main result, which is what EvalFnOnAgentDist does for us. If I modify the ReturnFn to respond with the appropriate constituent based on a parameter, then all my decision code can live in one place. In pseudocode, some thoughts:
% parameter_of_interest is a number 1..returnvals
% This is used locally within the ReturnFn; a parameter selects which field is returned
output_struct=struct('ValueFn','Consumption','CapitalGains', etc);
output_names=fieldnames(output_struct);
% Lots of complicated choices which compute values for interesting things
return output_struct.(output_names{parameter_of_interest});
This adds minor setup cost and a slightly more complex return value with the benefit that everything feeding the Value Function becomes easily and consistently accessed in other contexts. The toolkit does the bookkeeping as to whether we need to query the whole state and action space or just the optimal policy parts. Thoughts?
In many models, say a hypothetical model K2026, there will be K2026_ReturnFn() and K2026_ConsumptionFn(). But the K2026_ConsumptionFn() is actually just a copy-paste of part of K2026_ReturnFn().
Your point is, why not just have one K2026_EverythingFn(), with one of the input parameters being ‘returnorconsumption’ and when returnorconsumption=1 you get the ReturnFn output and when returnorconsumption=2 you get the ConsumptionFn output.
Further, you think about whether returnorconsumption can be automatically set internally by the toolkit.
Am I correct in this understanding?
(The part about whether to consider the whole state and action space, or just the optimal policy parts, is already being done where appropriate by the toolkit. When you do ValueFnIter it does the whole state and action space, when you do EvalFnOnAgentDist it does just the optimal policy part.)
Yes, and…a reason for the toolkit to understand this patters is because FnsToEvaluate would be clunky if I have to write a fully srpate dispatcher for each function->number mapping, but could be elegant if I could send the appropriate argument vector (of function numbers) to pass to the EverythingFn when I’m computing for EvalAgent.
I feel like it is tricky to know how to set the ‘appropriate argument vector’ automatically. If you can lay out the algorithmic logic for this then we can think about it, but not clear to me how to ‘know’ exactly which function outputs are or are not needed in any given situation.
Plus, “arrayfun()” on gpu can only return a scalar value at each point in the ‘space’, so you still have to loop over it to generate multiple function outputs [I guess you can easily enough bypass this by having the ‘function ff’ as an extra dimension]
As Jake says this should work fine. It never occured to me to do it for consumption, but I have done so before for taxes. Just make sure the function is based on assuming all inputs are scalar and the output is scalar (as arrayfun on gpu requires this).
Apologies for the noise. I just refactored by ReturnFn successfully, folding energy costs into carbon costs, carbon costs into consumption, and consumption in the return function. This will help me maintain my code going forward. Thanks!