Compiled and non-compiled functions return different values

78 views
Skip to first unread message

dirkdouw...@gmail.com

unread,
Mar 12, 2025, 3:14:59 PM3/12/25
to nimble-users
Hello,

One thing I have noticed recently that has made me a bit nervous is that compiled and non-compiled code can return different values. For instance,

library(nimble)

testf <- nimbleFunction(
  run = function(y = double(1)) { # type declarations
    for(i in 1:4){
      y <- y+2
    }
    return(i)
    returnType(double(0)) # return type declaration
  } )

testf(1)

ctestf <- compileNimble(testf)

ctestf(1)

Here, testf(1) returns 4 and ctestf(1) returns 5. It appears when compiling that the for loop index gets increased by 1 after the loop.

This has made me nervous because I write my own samplers. I always test these samplers in R so I can go line by line. I am not sure how to test the compiled samplers to make sure they return the same sampled, and other, values. Anyone know how to do this?

Also, does anyone know other important examples where there would be differences in the compiled and non-compiled code?

Thanks!

Perry de Valpine

unread,
Mar 13, 2025, 5:37:21 PM3/13/25
to dirkdouw...@gmail.com, nimble-users
Hi Dirk,
That's an interesting catch. The exit behavior of the for-loop index differs (but result y shouldn't differ). Often for-loop indices are later ignored, but sometimes one relies on their exit value, so this could matter. There are some other differences in compiled vs uncompiled behavior, notably that in compiled code non-scalar numeric arguments will be passed by reference. There shouldn't be glaring differences in math.
One way to test your own samplers would be to call them manually (outside of the MCMC), after setting the seed for reproducibility. Off the top of my head I can suggest two ways to do this. The first would be to build the sampler on its own (not within an MCMC). For that purpose it might be easier to remove uses of the mvSamples, since those are set up from the MCMC (i.e. buildMCMC). Then you could compile the sampler object directly and, if you set a seed, set values in the model, and call the sampler, all with the uncompiled objects (model + sampler) and then (after re-setting to the same seed) with the compiled objects (model + sampler),  you should get identical behavior. The second would be to do
nimbleOptions(buildInterfacesForCompiledNestedNimbleFunctions = TRUE)
(always worth a chuckle at that long but accurate name ;-) )
Then you can use the samplers as they were built in the MCMC (including mvSamples etc). You should be able to access samplers (compiled or uncompiled) by
MCMC$samplerFunctions[[i]]
where MCMC was returned from buildMCMC and i is a choice of which sampler you want to look at. E.g. you can set the state of the model, then the seed, and then manually call one iteration of sampler i:
MCMC$samplerFunctions[[i]]$run()
or inspect its internals
MCMC$samplerFunctions[[i]]$scale
In the uncompiled MCMC, you can check the target node of a sampler to be sure it's the sampler of interest
MCMC$samplerFunctions[[i]]$target
A final approach to checking on what your own sampler is doing is to define a nimbleRcall to some R function and call it from the run code of your sampler. Then put a browser() in the R function (or debug() it). Then when your compiled code is running, it will call your R function and you'll get into a browser, from which you can inspect the state of the compiled model etc (e.g. to see what proposal value was used, or what calculations were done). You can also access sampler setup (member) variables as described above. If you need to look at local variables, you'd have to pass them as arguments to the nimbleRcall.
HTH
Perry


--
You received this message because you are subscribed to the Google Groups "nimble-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nimble-users...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/nimble-users/d198d9db-c8fd-499f-9971-18471ded8ec6n%40googlegroups.com.

Devin Johnson - NOAA Federal

unread,
Mar 20, 2025, 11:06:21 AM3/20/25
to nimble-users
All,
I ran into something similar (maybe?) to this issue. When I use a reverse index in a for loop, e.g. for(i in K:1){..} everything works in the R version of the code. It compiles with no issue, but the result from the compiled version is not correct at all. If I rework the code to function with a "regular" loop, e.g., fot(i in 1:K){..} everything works on both sides. I'm not sure if this is a known issue. I rather new to the nimble way, so maybe this is common knowledge. I can code up a quick example if there's interest. 
-Devin

Perry de Valpine

unread,
Mar 20, 2025, 12:03:02 PM3/20/25
to Devin Johnson - NOAA Federal, nimble-users
Thanks Devin. It is correct (and known) that reverse-order for loops are not supported for compilation.

I checked our User Manual to see how well this is documented. I see in a couple of places we state that for loops only support sequential integer indexing, but evidently we don't also say that it must be increasing sequential integer indexing.

Perry

Devin Johnson - NOAA Federal

unread,
Mar 20, 2025, 12:16:26 PM3/20/25
to nimble-users
ok, thanks Perry
-Devin

Reply all
Reply to author
Forward
0 new messages