【问题标题】:Understanding strange behavior in Rcpp了解 Rcpp 中的奇怪行为
【发布时间】:2023-04-04 12:27:01
【问题描述】:

我遇到了一些我无法解决的问题。这是更大的编码工作的一部分,但这里有一个最小的例子:

#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
Rcpp::List foo(arma::vec & tau2, const arma::vec & nu) {

  arma::vec bet = Rcpp::rnorm(3);
  tau2 = R::rgamma(1, arma::as_scalar(sum(pow(bet, 2)/nu)));

  return Rcpp::List::create(Rcpp::Named("nu") = nu,
                            Rcpp::Named("tau2") = tau2);
}

(tau2虽然是标量,但这里是向量,因为我想通过引用传递:function pass by reference in RcppArmadillo)

让我不解的是,如果我现在运行以下 R 代码:

n <- 3
m <- matrix(0, n, 1)
for (r in 1:1000) {
  tau2 <- 1.0
  nu <- matrix(1, n, 1)
  upd <- foo(tau2, nu)
}

我明白了:

error: element-wise division: incompatible matrix dimensions: 3x1 and 18x1
Error in foo(tau2, nu) : 
  element-wise division: incompatible matrix dimensions: 3x1 and 18x1

18x1 变化的地方;大多数情况下它是0x1,但它始终是3 的倍数。

查看输出:

> nu
         [,1]     [,2]     [,3]     [,4]
[1,] 4.165242 4.165242 4.165242 4.165242
[2,] 4.165242 4.165242 4.165242 4.165242
[3,] 4.165242 4.165242 4.165242 4.165242
> upd
$nu
     [,1]
[1,]    1
[2,]    1
[3,]    1

$tau2
         [,1]
[1,] 4.165242

也就是说,尽管将 nu 声明为常量引用(我这样做是因为我确实希望它改变),但它被改变了。它填充的值是upd$tau2(但为什么?)。

奇怪的是,我可以通过以下看似毫无意义的更改来消除这种行为:

  • tau2 &lt;- 1.0nu &lt;- matrix(1, n, 1)(或两者)置于循环之外
  • 删除第一个参数中的引用(即使用arma::vec tau2
  • 不将pow(bet, 2) 除以nu
  • 更改为nu &lt;- rep(1, n)

也许最令人困惑的部分是,如果我选择循环内的代码块并重复运行它,它就可以工作(!)。但是,如果我使用循环运行 R 代码,它会在第二次迭代时崩溃。

因为我似乎能够解决问题,所以我最感兴趣的是了解这里发生了什么。我怀疑这只是我缺乏 C++ 专业知识以及对各种变量类型鲁莽的结果,因此了解导致所有这些的原因将非常有价值。

【问题讨论】:

  • "也许最令人困惑的部分是,如果我选择循环内的代码块并重复运行它,它可以工作(!)。但是,如果我使用循环运行 R 代码,它会崩溃在第二次迭代中。”如果关闭 JIT 编译,是否会在循环中发生?
  • 你的tau2(在R中)被你的foo()覆盖——这些(最终)都是SEXP,它们都是指针。重命名,或在里面复制tau2
  • @Roland 如果我用012 运行compile::enableJIT,它就可以工作。
  • 这根本不是问题......底层的cpp代码很糟糕。特别是,使用Rcpp:: 生成器将NumericVector 强制 转换为arma::vec
  • @DirkEddelbuettel 这基本上就是我所追求的。这是一个 MCMC 采样器,其中tau2 是正在更新的变量之一。它应该全部在 C++ 中(我想它更有意义),但现在当我遇到这个时,我只是在 R 中测试这个更新函数以简化调试过程。

标签: c++ r rcpp


【解决方案1】:

两个修复:

  1. tau2 是双精度(此处模仿 @dirkeddelbuettel)
  2. 在保存到 bet 之前生成长度为 nNumericVector 的临时变量

代码:

#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]

// [[Rcpp::export]]
Rcpp::List foo(double tau2, const arma::vec & nu) {

  int n = nu.n_elem;

  Rcpp::NumericVector x = Rcpp::rnorm(n);
  arma::vec bet = arma::vec(x.begin(), n, true, false);

  tau2 = R::rgamma(1, arma::as_scalar(sum(pow(bet, 2) / nu)));

  return Rcpp::List::create(Rcpp::Named("nu") = nu,
                            Rcpp::Named("tau2") = tau2);
}

测试用例:

n <- 3
m <- matrix(0, n, 1)
for (r in 1:1000) {
  tau2 <- 1.0
  nu <- matrix(1, n, 1)
  upd <- foo(tau2, nu)
}
upd
#> $nu
#>      [,1]
#> [1,]    1
#> [2,]    1
#> [3,]    1
#> 
#> $tau2
#> [1] 3.292889

【讨论】:

  • 这无疑是一个更好的解决方案。您能否就nu 的修改提供任何见解,尽管没有在C++ 代码中被覆盖并且尽管是一个常量引用?我知道代码非常低,但了解为什么会发生这种情况(不仅仅是如何避免它)会很有用。
  • nu 被作为&amp; 的“引用”传递。因此,它使用 same 内存作为 R 中的对象。当我们使用高级构造函数移动随机样本抽取时,我们正在这样做。要使其保持静止:移除&amp;。但是,在此示例中,没有任何变化。
  • 我明白了。在我的应用程序中,nu 实际上相当大,所以我的意图是避免复制它(因此是 &)。但我想这一切都搞砸了。
  • 正如我刚刚对 Dirk 的回答发表评论时,我只记得我首先避开了 double,因为他在这里的回答:stackoverflow.com/questions/44047145/… 我基本上只是想给函数一些变量应相应更新。
  • 如果需要引用,可以使用arma::vec&amp; tau,通过tau2(0) = R::gamma(1, ...)将值存入tau2。将来,考虑制作更完整的场景。
【解决方案2】:

如果我将界面更改为使用double,则一切正常:

代码

#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
Rcpp::List foo(double & tau2, const arma::vec & nu) {

  arma::vec bet = Rcpp::rnorm(3);
  tau2 = R::rgamma(1, arma::as_scalar(sum(pow(bet, 2)/nu)));

  return Rcpp::List::create(Rcpp::Named("nu") = nu,
                            Rcpp::Named("tau2") = tau2);
}

/*** R
n <- 3
m <- matrix(0, n, 1)
for (r in 1:1000) {
  tau2 <- 1.0
  nu <- matrix(1, n, 1)
  upd <- foo(tau2, nu)
}
*/

演示

R> sourceCpp("/tmp/hejseb.cpp")

R> n <- 3

R> m <- matrix(0, n, 1)

R> for (r in 1:1000) {
+   tau2 <- 1.0
+   nu <- matrix(1, n, 1)
+   upd <- foo(tau2, nu)
+ }
R> upd
$nu
     [,1]
[1,]    1
[2,]    1
[3,]    1

$tau2
[1] 1.77314

R> 

我不确定这些是否是您预期的数字。我真的没有时间完成你想​​要做的事情。

【讨论】:

  • 我对数字的期望并不高(这只是一个代表),但我没想到nu 会在 1 时更改)它没有被 C++ 代码覆盖,并且2)它是一个恒定的参考。所以我的问题主要是关于了解发生了什么。
  • 现在我才想起为什么我一开始就避免使用 double --- 你的答案在这里:stackoverflow.com/questions/44047145/… 我正在研究 Gibbs 采样器,所以 tau2 就像现在的采样器中 tau^2 的值和我的示例中的函数更新它(尽管在示例中显然代码是没有意义的)。我太专注于这个问题,以至于忘记将其添加到我的帖子中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
  • 2013-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多