What to expect if coercing a parameter with as.numeric?
williamaeberhard opened this issue · 2 comments
Dear Kasper,
Not really an issue, but something I experienced while playing around with RTMB. I didn't find answers in the doc, I thought posting here might be helpful for others too.
In an R function I compile with RTMB::MakeADFun
I have a line where I coerce a parameter to an ordinary numeric type. This parameter is an element of the parameter list which is the only argument to the function. Upon running MakeADFun I get the warning about the imaginary part of an object being discarded. I understand this relates to how you used the complex number class to store both a parameter's value and... something else (its numerical derivative? the chain of nested operations required by an AD pass? something like that, no?). Now, the interesting thing is this is only a warning, I can actually use the compiled obj
. With it I can REPORT
this coerced numeric parameter and recover the correct value, but I don't recover the correct value when this parameter is the main output of my R function (in return()
). I suppose the cause of this difference is the numeric coercion, but I cannot find any particular pattern in the difference.
It's clear to me we should not mess with your usage of the complex number class, but if we have to coerce some parameter, is there a way to predict what happens to its value if it contributes to the main output of the R function? Thanks!
Best regards,
William
Just to confirm what you are saying here's an example:
library(RTMB)
f <- function(x) as.numeric(x)
F <- MakeTape(f, 1:3)
And it gives
Warning message:
In f(x) : imaginary parts discarded in coercion
This is definitely not the intention... Given that f
is a perfectly differentiable function, the above should just work.
Fortunately, looking at ?as.numeric
, one can write methods for it via as.double
. So, the fix is to add something like
as.double.advector <- function (x, ...) x
It may look weird that advector
to double
conversion should return an advector
. The rationale for this is that, from the point of the user, the advector
is equivalent to numeric
, regardless of the internal storage. Conversion from advector
to an actual double
should be made difficult on purpose because such conversion drops derivative tracing.
Thanks for your reply. Ok, we indeed lose the derivative tracing when discarding the imaginary part. For the reference, here is a MWE which illustrates the unexpected differences between return
and REPORT
:
library(RTMB)
x <- 1:3
func1 <- function(par){
sumpar <- sum(par) #
REPORT(sumpar)
return(sumpar)
}
obj <- MakeADFun(func=func1,parameters=x)
c(obj$report(x)$sumpar, obj$fn(x))
# ^ identical, as expected
func2 <- function(par){
sumpar <- sum(as.numeric(par)) # added as.numeric() around par
REPORT(sumpar)
return(sumpar)
}
obj <- MakeADFun(func=func2,parameters=x)
c(obj$report(x)$sumpar, obj$fn(x))
# ^ REPORTed sumpar still correct, but $fn incorrect now
Again, this is not a real issue since I doubt many people would ever need to call as.numeric
within the function. I'll experiment with an as.double.advector
method if this need arises again.