Skip to content

Support optim() in nimbleFunctions#318

Merged
fritzo merged 54 commits into
develfrom
optim-support
May 16, 2017
Merged

Support optim() in nimbleFunctions#318
fritzo merged 54 commits into
develfrom
optim-support

Conversation

@fritzo
Copy link
Copy Markdown
Contributor

@fritzo fritzo commented May 3, 2017

Status: Ready to merge

This PR removes Cliff's old partial implementation of optim() support and adds a simpler, more limited implementation that outputs a nimbleList. See the Design Document for details.

@fritzo fritzo requested a review from perrydv May 3, 2017 01:50
@perrydv
Copy link
Copy Markdown
Contributor

perrydv commented May 3, 2017

This looks good to me. Only comment is that some of the removed stale content of optimTools.R may turn out to be useful during next steps, but it seems ok to remove it now and wait to see if or how we adapt any pieces from it in the future.

@perrydv
Copy link
Copy Markdown
Contributor

perrydv commented May 3, 2017

Ok if I remove the old "Optim-Branch"?

@fritzo
Copy link
Copy Markdown
Contributor Author

fritzo commented May 3, 2017

It's fine with me if you remove the old Optim-Branch branch.

code$sizeExprs <- symTab$getSymbolObject('OptimResultNimbleList', inherits = TRUE)
code$toEigenize <- 'maybe'
code$nDim <- 0
asserts <- c(asserts, sizeInsertIntermediate(code$caller, code$callerArgID, symTab, typeEnv))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line lifts the expression out of whatever was calling it. Typically we do this only if(!(code$caller$name %in% assignmentOperators)).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, what is the correct behavior when code$caller$name %in% assignmentOperators?

BTW I just copied this from sizeNimbleListReturningFunction above, which implements nimSvd and nimEigen. I guess we should fix that code too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the situation is if we have a function like nimOptim that we can code-generate only for a direct assignment, e.g. result <- nimOptim(...). In that case code$caller$name %in% assignmentOperators and no modification is needed. But if a user has written result <- foo(nimOptim(...)), we need to transform that to two lines: Interm1 <- nimOptim(...) and result <- foo(Interm1). So when size processing nimOptim, we can see that its caller is not an assignment operator and perform the sizeInsertIntermediate step, which creates the two lines described. Summary: if the caller is already an assignment operator, no step is needed.

I saw that other case and figured you had just imitated it. I already ran into that and fixed it on branch nimbleList-RCfun, so you could either fix it on your branch or wait to pick up the change later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh perfect! Thanks for pointing me to your branch. I'll grab your changes and incorporate them into this PR.

// https://svn.r-project.org/R/trunk/src/library/stats/R/optim.R
// https://svn.r-project.org/R/trunk/src/library/stats/src/optim.c
// https://svn.r-project.org/R/trunk/src/include/R_ext/Applic.h
nimSmartPtr<OptimResultNimbleList> nimOptim(NimArr<1, double>& par,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably be a bit more general if we take the parameter vector as a NimArrBase, meaning we'll access high-dimensional objects in column-major order as vectors. One could argue it is preferable to always require it to be 1-dimensional for clarity. Not very important - just mentioning it.

// https://svn.r-project.org/R/trunk/src/library/stats/src/optim.c
// https://svn.r-project.org/R/trunk/src/include/R_ext/Applic.h
nimSmartPtr<OptimResultNimbleList> nimOptim(NimArr<1, double>& par,
NimObjectiveFn fn) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will the NimObjectiveFun type need to be different when fn includes additional parameters?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I plan to expand the NimObjectiveFunction type in subsequent PRs. I believe we'll only need one additional parameter (that packs all other arguments), but in any case this general shape of plumbing should work.

result->hessian.setSize(n, n);

// Use Nelder-Mead by default.
double* Bvec = par.getPtr();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not completely safe. If parMatrix is n-x-n and the user wants to provide the 3rd row as the initial parameters to optim, they can give par = parMatrix[3,]. NIMBLE would turn this into a NimArr that has map=TRUE. This treats its contents as a vector of length n but accesses the contents using a stride of n. When we need to ensure a NimArr argument coming into a C++ function uses contiguous memory (e.g. to pass it along to nmmin), we have used a nimArrCopyIfNeeded step. Example here:
https://github.com/nimble-dev/nimble/blob/devel/packages/nimble/inst/CppCode/nimDists.cpp#L42

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss intended usage. The NimArr code is quite difficult to read, so I've been guessing at how to use it. Later I'd like to either clean up that code or replace it with pure Eigen.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'd like to add an example like yours to the unit test suite. I didn't realiize R's optim() could operate on matrices or slices/maps.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's talk about the NimArr system. I think it is ripe for an overhaul, but we should be be cautious about priorities and time commitments. I think if we discuss in person we can decide how to handle the issue.

// https://svn.r-project.org/R/trunk/src/include/R_ext/Applic.h
static double optimfn_wrapper(int n, double* par, void* fn) {
NimArr<1, double> nim_par;
nim_par.setSize(n);
Copy link
Copy Markdown
Contributor

@perrydv perrydv May 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is slightly more efficient to use nim_par.setSize(n, false, false), which makes no initialization be done. I would have never thought it would be noticeable, but we saw in MCMCs that unnecessary initialization (at the time we used std::vector as the underlying memory allocation, which always initializes) took up a meaningful amount of computation time when repeats zillions of times.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, since this is called the inner loop of optimization! Done.

@perrydv
Copy link
Copy Markdown
Contributor

perrydv commented May 5, 2017 via email

@fritzo
Copy link
Copy Markdown
Contributor Author

fritzo commented May 11, 2017

@NLMichaud I just added a script to generate our static C++ code for OptimResultNimbleList. Could you please take a look and review for sanity?

I've automated this so that it's easier to generate additional static C++ code, e.g. an OptimControlNimbleList that is added in a later commit.

@fritzo
Copy link
Copy Markdown
Contributor Author

fritzo commented May 16, 2017

As we discussed at the team meeting, I'll merge this as soon as CI tests pass.

@fritzo fritzo merged commit 9a60d60 into devel May 16, 2017
@fritzo fritzo deleted the optim-support branch May 16, 2017 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants