Callbacks in Stan?

54 views
Skip to first unread message

Daniel Lee

unread,
Mar 12, 2014, 1:19:55 PM3/12/14
to stan...@googlegroups.com
I'll start with the use-case. In RStan, there's a function called 'run_markov_chain()' that mirrors Stan's function. I'd post the code, but it's not really useful as is. The substantive difference between the two functions is that RStan has one additional line:

    R_CheckUserInterrupt();

RStan calls that function to check whether or not the user has interrupted the process within a for loop. (For the time being, I'm going to ignore the danger in leaving dangling resources. This is how RStan currently works now and I'm just trying to refactor the code so we share more common code.)

My question is, should I just add a function pointer to the method to deal with this interrupt handler? Is there a different pattern we should adopt to make it cleaner?

Is this going to introduce too much overhead? What's the cleanest way to deal with an empty function? CmdStan doesn't need this call, so what should we do about it? Do I create two functions in Stan to maintain efficiency at the cost of duplicating code (one with and without this argument)?


Daniel






Michael Betancourt

unread,
Mar 12, 2014, 1:23:07 PM3/12/14
to stan...@googlegroups.com
It should be easy enough to provide API functions for

initialize
generate_warmup_transition()
generate_sample_transition()

as well as

warmup {
  for(n in n_samples)
   generate_warmup_transition();
}

and then just have the interfaces ignore the warmup function
if it has to get inside?  At least then the code duplication is
minimal.




--
You received this message because you are subscribed to the Google Groups "stan development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to stan-dev+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Daniel Lee

unread,
Mar 12, 2014, 1:30:49 PM3/12/14
to stan...@googlegroups.com
I think that's going the other way from what I envisioned. I'm thinking about pushing as much as I can to Stan's common, with the ability for RStan and whatever other interface to insert hooks where they need it.

I think what you're suggesting puts the code duplication at the interface level. The motivation for moving the logic into Stan is that will propagate changes much faster to the interfaces if we can pull it off that way.


Bob Carpenter

unread,
Mar 12, 2014, 1:31:09 PM3/12/14
to stan...@googlegroups.com
While I don't think a per-iteration null callback function
is going to be noticeable speed-wise, I prefer Michael's suggestion
to set up the interface so that clients pull iterations rather
than inject callbacks.

That is, assuming I'm interpreting it right. Rather than
generate_warmup_transition(), I'd call it something like
get_next_warmup() or get_warmup().

- Bob

Michael Betancourt

unread,
Mar 12, 2014, 1:32:21 PM3/12/14
to stan...@googlegroups.com
I just don't know how you're going to add a hook to interrupt sampling without breaking out each iteration.

Daniel Lee

unread,
Mar 12, 2014, 1:38:07 PM3/12/14
to stan...@googlegroups.com
I see. I still think that logic can be pushed into Stan.

Let's get down to the implementation level, then. Here's RStan's function:
    template <class Model, class RNG_t>
    void run_markov_chain(stan::mcmc::base_mcmc* sampler_ptr,
                          const stan_args& args,
                          bool is_warmup,
                          mcmc_output<Model>& outputter,
                          stan::mcmc::sample& init_s,
                          Model& model,
                          std::vector<Rcpp::NumericVector>& chains,
                          int& iter_save_i,
                          const std::vector<size_t>& qoi_idx,
                          std::vector<double>& sum_pars,
                          double& sum_lp,
                          std::vector<Rcpp::NumericVector>& sampler_params,
                          std::vector<Rcpp::NumericVector>& iter_params,
                          std::string& adaptation_info,
                          RNG_t& base_rng) {
      int start = 0;
      int end = args.get_ctrl_sampling_warmup();
      if (!is_warmup) {
        start = end;
        end = args.get_iter();
      }
      for (int m = start; m < end; ++m) {
        stan::common::print_progress(m, 0, args.get_iter(),
                                     args.get_ctrl_sampling_refresh(), is_warmup,
                                     "\r", "", rstan::io::rcout);
        R_CheckUserInterrupt();
        init_s = sampler_ptr -> transition(init_s);
        if (args.get_ctrl_sampling_save_warmup() && (((m - start) % args.get_ctrl_sampling_thin()) == 0)) {
          outputter.output_sample_params(base_rng, init_s, sampler_ptr, model,
                                        chains, is_warmup,
                                        sampler_params, iter_params,
                                        sum_pars, sum_lp, qoi_idx,
                                        iter_save_i, &rstan::io::rcout);
          iter_save_i++;
          outputter.output_diagnostic_params(init_s, sampler_ptr);
        }
      }
    }


Here's Stan's function:
    template <class Model, class RNG>
    void run_markov_chain(stan::mcmc::base_mcmc* sampler,
                          int num_iterations,
                          int start,
                          int finish,
                          int num_thin,
                          int refresh,
                          bool save,
                          bool warmup,
                          stan::io::mcmc_writer<Model>& writer,
                          stan::mcmc::sample& init_s,
                          Model& model,
                          RNG& base_rng) {
     
      for (int m = 0; m < num_iterations; ++m) {
     
        print_progress(m, start, finish, refresh, warmup);
     
        init_s = sampler->transition(init_s);
         
        if ( save && ( (m % num_thin) == 0) ) {
          writer.print_sample_params(base_rng, init_s, *sampler, model);
          writer.print_diagnostic_params(init_s, sampler);
        }

      }
     
    }


I think the sampler->transition() call is the level you're talking about (unless I'm missing something). This is wrapping that call and putting it in a loop and printing where appropriate.


Daniel Lee

unread,
Mar 12, 2014, 2:14:23 PM3/12/14
to stan...@googlegroups.com
Of course, I might be missing the forest for the trees here. I'm trying to consolidate code in the least error prone manner possible. Here's what I'm thinking should happen with the run_markov_chain function within Stan:


    template <class Model, class RNG>
    void run_markov_chain(stan::mcmc::base_mcmc* sampler,
                          int num_iterations,
                          int start,
                          int finish,
                          int num_thin,
                          int refresh,
                          bool save,
                          bool warmup,
                          stan::io::mcmc_writer<Model>& writer,
                          stan::mcmc::sample& init_s,
                          Model& model,
                          RNG& base_rng,
                          std::string &prefix,  // rstan would use "\r", stan uses ""
                          std::ostream& ostream, // rstan uses rstan::io::rcout, stan uses std::cout
                          void (*interrupt_handler)(void)) {  // rstan uses R_CheckUserInterrupt(), stan uses nothing.
     
      for (int m = 0; m < num_iterations; ++m) {
     
        print_progress(m, start, finish, refresh, warmup,
                               prefix, ostream);

        interrupt_handler();   // <<<<----- here's the interrupt handler and the real change to behavior

        init_s = sampler->transition(init_s);
         
        if ( save && ( (m % num_thin) == 0) ) {
          writer.print_sample_params(base_rng, init_s, *sampler, model);
          writer.print_diagnostic_params(init_s, sampler);
        }

      }
     
    }


And the only thing I would really have to do is make sure the writer interface is abstract enough for RStan to implement and provide.

Michael Betancourt

unread,
Mar 12, 2014, 2:21:44 PM3/12/14
to stan...@googlegroups.com
> interrupt_handler(); // <<<<----- here's the interrupt handler and the real change to behavior

So the interfaces are able to implement interrupt_handler in a way that has
access to local environment conditions? If so that sounds like a good solution.

Daniel Lee

unread,
Mar 12, 2014, 2:24:09 PM3/12/14
to stan...@googlegroups.com
Yes. For now, RStan is the only use case that I'm trying to generalize to. (Sorry, Allen -- I have enough trouble keeping up with 2 things at once. Once I'm done here, I'll look at PyStan and start taking into account PyStan's requirements.)


That's the easiest way to cut down duplication between RStan and Stan. I'm taking down functions one at a time.


Bob Carpenter

unread,
Mar 12, 2014, 3:22:01 PM3/12/14
to stan...@googlegroups.com
I'd much rather see the callback passed in as a functor
rather than as a C-style function pointer.

So that'd add a template parameter

template <class Model, class RNG, class CB>
...
const CB& c) {
...
c();
}

and then just call the argument as a functor. We'd have
to supply a dummy functor for CmdStan. If you do go with the pointer,
you may just want to check if it's zero and maybe have it
default to 0.

In fact, we should be making the template functionals
for autodiff we're using now constant references instead
of copying them.

Back on topic, here's an example:

#include <iostream>

struct fun {
fun() { }
void operator()() const {
std::cout << "hello" << std::endl;
}
};

template <typename F>
void foo(const F& f) {
f();
}

int main() {
fun callback;
foo(callback);
}

- Bob

Daniel Lee

unread,
Mar 12, 2014, 3:31:39 PM3/12/14
to stan...@googlegroups.com
Thanks, Bob. I'll use that instead.

Bob Carpenter

unread,
Mar 12, 2014, 3:40:29 PM3/12/14
to stan...@googlegroups.com
I just realized that if you implement it the way I suggested,
you can also pass in functions!

#include <iostream>

struct fun {
fun() { }
void operator()() const {
std::cout << "hello" << std::endl;
}
};

template <typename F>
void foo(const F& f) {
f();
}

void bar() {
std::cout << "bar" << std::endl;
}

int main() {
fun callback;
foo(callback);
foo(bar);
}


This compiles and prints:

hello
bar

Here's a nice little tutorial:

http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html

- Bob

Allen Riddell

unread,
Mar 13, 2014, 10:38:27 AM3/13/14
to stan...@googlegroups.com
The PyStan wrapping mirrors RStan *very* closely. I'll follow along with the
changes.

Thanks, -a
Reply all
Reply to author
Forward
0 new messages