Expose UDF from external file

I bet that this is easy but I’m having a bit of trouble. I have a function that is defined in a .stan file but it is not wrapped in functions { } because I may have a bunch of these I want to reference in my stan program like

functions {
#include my_fun.stan
}
data {
...

and in cmdstanr I run the program as

fp <- file.path("./full_program.stan")
mod <- cmdstan_model(fp, include_paths = "./function_file_path/")

Can I expose stan functions using rstan on the my_fun.stan function?

Hey there!

Are you looking for something like this?

Cheers,
Max

If the contents of my_fun.stan is not enclosed in functions {} it will not compile.

I think the easiest solutions here are:

  • concat new Stan code by enclosing my_fun.stan in a functions {} block
  • auto-format the full_program.stan to generate a model with #includes replaced with the code from the files and use the experimental rstan 2.26 (it will work with that).
1 Like

Can you give some more detail on solution 2?

Sure.

Say we have my_fun.stan:

real foo(real a, real b) {
  return a + b;
}

real goo(real a, real b) {
  return a - b;
}

and full.stan:

functions {
#include "my_fun.stan"
}
parameters {
  real y;
}
model {
  y ~ std_normal();
}
library(cmdstanr)
library(rstan)

auto_format <- function(stan_file, include_paths = NULL)  {
  if(isTRUE(.Platform$OS.type == "windows")) {
    stanc <- file.path(cmdstan_path(), "bin", "stanc.exe")
  } else {
    stanc <- file.path(cmdstan_path(), "bin", "stanc")
  }
  stancflags_val <- NULL
  if (!is.null(include_paths)) {
    include_paths <- paste0(include_paths, collapse = ",")
    include_paths_flag <- " --include-paths="
    include_paths_flag <- " --include_paths="
    stancflags_val <- trimws(paste0(include_paths_flag, include_paths, " "))
  }
  args = c(
    "--auto-format",
    stan_file,
    stancflags_val
  )
  
  processx::run(
    stanc,
    args = args,
    echo_cmd = TRUE
  )$stdout
}

setwd("~/Desktop/testing/standalone/")
fp <- file.path("full.stan")

p <- auto_format(fp, include_paths = ".")
full_temp_file <- write_stan_file(p)
expose_stan_functions(full_temp_file)
> foo(2,3)
[1] 5
> goo(2,3)
[1] -1

The auto-format parts could be done with rstan I think, but I had the auto format thing ready for cmdstanr for other purposes. Will update if I have time to explore rstan a bit.

The rstan I used is the development version from our repo:

install.packages("StanHeaders", repos = c("https://mc-stan.org/r-packages/", getOption("repos")))
install.packages("rstan", repos = c("https://mc-stan.org/r-packages/", getOption("repos")))

Actually, ignore the above. You can just do:

fp <- file.path("full.stan")
expose_stan_functions(fp, includes = ".")

and you get all the functions exposed.

install.packages("StanHeaders", repos = c("https://mc-stan.org/r-packages/", getOption("repos")))
install.packages("rstan", repos = c("https://mc-stan.org/r-packages/", getOption("repos")))

You might get a redefined something warning, but ignore it.

1 Like