Sketch of the implementation in the preprocessor
- Currently, the preprocessor already contains implementation of external functions outside the model block, but these functions are currently called "unknown functions", which is not a very good name. Therefore, the following elements should be renamed:
in CodeInterpreter.hh, in the enum SymbolType, eUnknownFunction should be renamed to eExternalFunction
in ExprNode.{cc,hh}, the class UnknownFunctionNode should be renamed ExternalFunctionNode
some methods of class ParsingDriver and DataTree should also be renamed
A new class ExternalFunctionsTable should be created for storing the information about external functions. This class would store the list of external functions (explictly or implictly declared), with all the informations conveyed through the external_function keyword. An instance of this class would be stored inside the ModFile class. Since each external function is also given a symbol ID (in the SymbolTable) of type eExternalFunction, this class should provide methods to access information through that symbol ID
The class ExternalFunctionNode (formerly UnknownFunctionNode) is used to store a call to an external function. Currently many methods of this class are not implemented. At least, the methods for derivation and substitutions should be implemented. The eval() method will never be implemented, for obvious reasons. The methods for computing temporary terms, the compile() method, and the normalizeEquation() method will be left aside for the moment
- We also need to create node classes for storing derivatives of external functions:
A class FirstDerivExternalFunctionNode, which would be a subclass of ExternalFunctionNode. The only extra information needed is the integer index (between 1 and nargs) of the argument with respect to which the derivative is computed
Similarly, a class SecondDerivExternalFunctionNode, which would be also be a subclass of ExternalFunctionNode. This time two integer indices are needed.
- For these two classes, the derivation method needs to be overloaded (it produces a 2nd deriv node in case of a 1st deriv node, and it fails for a 2nd deriv node)
The writeOutput() method also needs to be overloaded. For the moment, we will adopt a very simple way which is clearly not efficient since it does not take into account expression sharing. Suppose we have the first derivative of funcname (function of 3 args) with respect to its 2nd arg, taken at the point (expr1, expr2, expr3), and that the user has provided the derivative in a separate M-file called funcname_deriv, then the M-file should contain:
subscript_get(funcname_deriv(expr1, expr2, expr3), 2)
where the subscript_get function is only a helper to select the n-th element of a vector (i.e. subscript_get = @(x,i) x(i)) (Note: is there a simpler way to do that??)
For 2nd derivatives, we need a similar construct with a subscript_get2 = @(x,i,j) x(i,j) function
The case where a single M-file gives both the value of the function and its derivative is dealt with in a similar way, using one more call to subscript_get
For numerical derivation, we use a similar construct, using a numerical derivator. A candidate is jacob.m located in the distribution of Dynare 3.
A check should be added in ModFile::checkPass(): the preprocessor should exit with an error if the model block contains an external function and if either use_dll or bytecode option is used
- When a first prototype is working as described above, we will add the following optimizations:
in DataTree class, add "maps" for external function nodes (plain ones and 1st and 2nd derivatives), in order to implement maximum expression sharing like we do for unary ops and binary ops
- use of temporary terms for avoiding recomputing this many times. For example, if we use the derivatives of a given function at a given point with respect to several variables, then only one call to the derivative M-file is necessary, instead of many, and its result should be stored in a temporary (vector) variable, which will be used at different places