Skip to content

Design Doc: support optim() and optimize()

perrydv edited this page Apr 24, 2017 · 33 revisions

Owner: @fritzo

Objective

Compile R code that uses optim() and optimize(), such that the compiled function directly uses the underlying C interface.

Outline of processing flow ideas

We start with a call like (R syntax): ans <- optim(par, fn, gr, ..., method = 'Nelder-Mead', lower, upper, control, hessian)

Ideas will follow sequence of processing steps.

  1. Do we need keyword processing? In general, keyword processing is necessary when we need to insert new setup code. It is sometimes convenient for simple code transformations even if we don't need new setup code, but in general I prefer to have such transformations at later steps (like size processing, which is misnamed because we do more than size processing there).

In the current prototype, keyword processing creates something like the following

{
  vPtrFor_innerNF <- voidPtr(innerNF,nimbleFunction)
  vPtrFor_yProvided <- voidPtr(ARG2_yProvided_,double(1))
  vPtrFor_2p5 <- voidPtr(Interm_6,double(=0,default=2.5))
  bareBonesOptim(initPar=ARG1_xInit_,optFun=OPTIMREADY_nfObjective,voidNimFunPtr=vPtrFor_innerNF,numAdditionalArgs=3,y=vPtrFor_yProvided,z=vPtrFor_2p5,w=vPtrFor_2p5)
}

It also creates a line of newSetupCode (I think to create an object named OPTIMREADY_nfObjective whose role is really to trigger some other steps later).

Some observations about this:

* We originally thought the "method" would have to be baked in at compile time and become a different keyword (e.g. `bareBonesOptim`.  We can evaluate if that is necessary.  We can probably have run-time flexibility for method.

* Lifting of arguments to temporary variables (voidPtrs) is typically done in size processing, not keyword processing.

* voidPtr is an example of an internal part of the DSL.  It's valid, but we don't document it because there's nothing to do with it as a programmer.

* The prototype only works if the objective function is the run function of a nimbleFunction class (i.e. with setup code).  We probably, ideally, want optim to work with 3-4 cases: an RCfunction (aka simple nimbleFunction, without setup code), a member function of the same class in which optim is being used, or a method of another class (potentially nested: method of a nimbleFunction contained in another nimbleFunction, `optim(par, fn = A$B$foo)`).  The prototype works only because (using our example) keyword processing for nf2Gen$run can expect that innerNF is already populated in the symbolTable and we can look up its `run` symbol.  But for some of the desired cases, we won't be able to find another nimbleFunction's symbolTable until size processing. E.g. if we call `optim(par, fn = foo)` and foo is an RCfunction, we would normally not know anything about foo during keyword processing, but we would know (or be able to find out) about it during size processing.  Summary: I bet we'll want to move much (all?) of what keyword processing does in the prototype to size processing.

Clone this wiki locally