External cpp and mac m1 issue

I’m trying to use the boost functions and I’m getting this error about not finding the symbols for arm64:

#include <stan/math/prim.hpp>
#include <stan/math/rev/core.hpp>
#include <boost/math/special_functions/sin_pi.hpp>
#include <boost/math/special_functions/cos_pi.hpp>

inline double tanpi(const double& x, std::ostream* pstream__) {
    return boost::math::sin_pi(x) / boost::math::cos_pi(x);
  }
functions {
  real tanpi(real x);
}

data {
  real y_mean;
}

parameters {
  real<lower=0> y;
}
transformed parameters {
 // real z = tanpi(y);
}
model {
  y ~ normal(y_mean, 1);
}
generated quantities {
  real z = tanpi(y_mean);
}

> mod <- cmdstan_model(fp, include_paths = getwd(),
+                      cpp_options = list(USER_HEADER = file.path(getwd(), hpp_file)),
+                      stanc_options = list("allow-undefined")
+ )
Compiling Stan program...
undef: __ZN20test_model_namespace5tanpiIdEEN5boost4math5tools12promote_argsIT_fffffE4typeERKS5_PNSt3__113basic_ostreamIcNSA_11char_traitsIcEEEE
Undefined symbols for architecture arm64:
  "boost::math::tools::promote_args<double, float, float, float, float, float>::type test_model_namespace::tanpi<double>(double const&, std::__1::basic_ostream<char, std::__1::char_traits<char> >*)", referenced from:
      stan::model::model_base_crtp<test_model_namespace::test_model>::write_array(boost::random::additive_combine_engine<boost::random::linear_congruential_engine<unsigned int, 40014u, 0u, 2147483563u>, boost::random::linear_congruential_engine<unsigned int, 40692u, 0u, 2147483399u> >&, Eigen::Matrix<double, -1, 1, 0, -1, 1>&, Eigen::Matrix<double, -1, 1, 0, -1, 1>&, bool, bool, std::__1::basic_ostream<char, std::__1::char_traits<char> >*) const in lto.o
      stan::model::model_base_crtp<test_model_namespace::test_model>::write_array(boost::random::additive_combine_engine<boost::random::linear_congruential_engine<unsigned int, 40014u, 0u, 2147483563u>, boost::random::linear_congruential_engine<unsigned int, 40692u, 0u, 2147483399u> >&, std::__1::vector<double, std::__1::allocator<double> >&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<double, std::__1::allocator<double> >&, bool, bool, std::__1::basic_ostream<char, std::__1::char_traits<char> >*) const in lto.o

ld: symbol(s) not found for architecture arm64

clang: error: linker command failed with exit code 1 (use -v to see invocation)

make: *** [/var/folders/22/bpr3xfb5343gnd0jqvhwk2380000gn/T/RtmpVCehZ1/model-c17b23c5daf4] Error 1

Error: An error occured during compilation! See the message above for more information.

tagging @rok_cesnovar and @andrjohns

1 Like

You should put the tanpi function in the test_model C++ namespace. Other than that this should work

Just wrap it in?

namespace test_namespace {

}

Updating the cpp now compiles but when I try to sample I get

Error in processx::run(command = stanc_cmd(), args = c(stan_file, "--info",  : 
  System command 'stanc' failed, exit status: 1, stderr:
E> Semantic error in '/Users/tanpi.stan', line 2, column 2 to column 21:
E>    -------------------------------------------------
E>      1:  functions {
E>      2:    real tanpi(real x);
E>            ^
E>      3:  }
E>      4:  
E>    -------------------------------------------------
E> 
E> Some function is declared without specifying a definition.
namespace tanpi_model_namespace {
#include <stan/math/prim.hpp>
#include <stan/math/rev/core.hpp>
#include <iostream>
#include <boost/math/special_functions/sin_pi.hpp>
#include <boost/math/special_functions/cos_pi.hpp>

inline double tanpi(const double& x, std::ostream* pstream__) {
    return boost::math::sin_pi(x) / boost::math::cos_pi(x);
  }
}  

and calling in R as

fp <- "tanpi.stan"
hpp_file <- "tanpi.cpp"
mod <- cmdstan_model(fp, include_paths = getwd(),
                     cpp_options = list(USER_HEADER = file.path(getwd(), hpp_file)),
                     stanc_options = list("allow-undefined")
)
mod$sample(
  data = list(y = 2)
)

This seems like a bug b/c I get the same error using your code in Custom c++ using cmdstanr? - #9 by rok_cesnovar

Hmm, I think we fixed that one, but maybe not. Can you check if you are using the latest Github version? Else I will open an issue.

Thanks @rok_cesnovar! I am on the latest github version -v 0.4.0.9001.

Edit: I made an issue for cmdstanr at included cpp functions are not recognized by stanc3 · Issue #612 · stan-dev/cmdstanr · GitHub

We recommend using the user_header directly:

mod <- cmdstan_model(stan_file,include_paths=getwd(),
              user_header=file.path(getwd(), hpp_file),
              stanc_options = list("allow_undefined")
)
fit <- mod$sample()

But your approach should work as well obviously, will fix shortly.

1 Like

Nice! Works

I’m also adding the derivative version here in case it helps anyone:

Stan-math has an updated way to add gradients. This works for 1d functions

namespace tanpi_model_namespace {
#include <stan/math/prim.hpp>
#include <stan/math/rev/core.hpp>
#include <iostream>
#include <boost/math/special_functions/sin_pi.hpp>
#include <boost/math/special_functions/cos_pi.hpp>

inline double tanpi(const double& x, std::ostream* pstream__) {
  return boost::math::sin_pi(x) / boost::math::cos_pi(x);
}

// Function to handle Stan parameters
inline stan::math::var tanpi(const stan::math::var& a, std::ostream* pstream__) {
  return stan::math::make_callback_var(
    boost::math::sin_pi(a.val()) / boost::math::cos_pi(a.val()),
    [a](auto& vi) mutable {
      a.adj() += vi.adj() * (stan::math::pi() / boost::math::cos_pi(a.val()));
    });
}

} 

Call the files in cmdstanr with

fp <- "tanpi.stan"
hpp_file <- "tanpi.cpp"
mod <- cmdstan_model(fp, include_paths = getwd(),
                     user_header = file.path(getwd(), hpp_file),
                     stanc_options = list("allow-undefined"),
                     force_recompile = T
)
mod$sample(
  data = list(y_mean = 0.4)
)

tanpi(0.4)

where the tanpi.stan file is

functions {
  real tanpi(real x);
}

data {
  real y_mean;
}

parameters {
  real<lower=0> y;
}
transformed parameters {
  real z = tanpi(y);
}
model {
  y ~ normal(y_mean, 1);
}
generated quantities {
  real z_no_d = tanpi(y_mean);
}

2 Likes