Services config via multiple inheritance?


#1

I think we can construct a simple, modular configuration object for each of our services using multiple inheritance. Here’s a bare-bones example for some of nuts(_diag_e_adapt). The point would be that other services would be able to share these objects. Everything gets validated modularly, then more validation can also happen at higher levels.

#include <string>
#include <iostream>

class stepsize_adapt_config {
 public:
  bool adapt_stepsize_;
  double stepsize_adapt_gamma_;
  double stepsize_adapt_delta_;
  double stepsize_adapt_kappa_;
  stepsize_adapt_config()
      : adapt_stepsize_(true), stepsize_adapt_gamma_(0.05),
        stepsize_adapt_delta_(0.8), stepsize_adapt_kappa_(0.75) { }
  void set_adapt_stepsize(bool v) {
    adapt_stepsize_ = v;
  }
  void set_stepsize_adapt_kappa(double v) {
    stepsize_adapt_kappa_ = v;
  }
  bool validate() {
    return stepsize_adapt_gamma_ < 0;
  }
};

class model_data_init_config {
 public:
  std::string model_;
  std::string data_;
  std::string init_;
  model_data_init_config() : model_(""), data_(""), init_("") { }
  void set_model(const std::string& v) {
    model_ = v;
  }
  bool validate() {
  }
};


class nuts_config
    : public stepsize_adapt_config,
      public model_data_init_config {

 public:
  nuts_config() : stepsize_adapt_config(), model_data_init_config() { }

  bool validate() {
    return this->stepsize_adapt_config::validate();
    return this->model_data_init_config::validate();
  }
};


int main() {
  nuts_config cfg;
  cfg.set_model("~/temp2/foo.stan");
  std::cout << cfg.model_ << std::endl;
  return 0;
}

I’m only using these two base config classes as an example—we’d want to make them as reusable as possible. They’re easy to compose into bigger chunks just by defining a class that inherits from them all and calls their constructors and validators.


#2

Looks good to me. Often I try to use composition instead of inheritance but I think inheritance seems probably better in this case because:

  1. there’s just one level of hierarchy
  2. we’re explicitly calling each class’s constructor and validate methods by name
  3. you want the ease of accessing the data at cfg.<field> instead of cfg.stepsize_adapt_config_.<field>

#3

I think CmdStan already has code to do this here:

I realize that’s a little more oriented towards the command line and there’s some pointer-chasing in there but it seems to work fine. Have you thought about just using that?

I think I made a more templated version of it a while back that skipped the pointer-chasing but I’d have to look if I did what I think I did.


#4

Rather “using that approach and most of that code”.


#5

Me, too, which is why this approach didn’t occur to me initially.

The CmdStan config used to be in the services, too, until the big refactor, which was undertaken to eliminate that style of config. So yes, we (mainly @syclik and me) thought about it and disliked it so much that we (all @syclik on the coding side) rebuilt the current services layer to eliminate it.

I don’t like the hierarchical organization—I’m never ever able to figure out CmdStan inputs without pulling out the manual and looking at the call skeletons. I want to flatten everything out like a normal command.

Also, the dynamic casting is intrinsically dangerous (in that it can throw runtime type errors), but that’s not my main concern.

I’m pretty sure we’ll be able to knock what we’re doing down to a few service functions:

nuts, nuts_dense     // all Euclidean cases with and without adaptation
simulate             // now Fixed_param
bfgs, l_bfgs         // @bgoodri thinks there's utility in maintaining bfgs as well as l-bfgs
advi, advi_dense     // only two of these

I would very much like to see these reflected in the CmdStan and other interface calls. Right now, the rstan::stan()call in R or the sampling option in CmdStan cover all of the NUTS and static HMC cases, so there are lots of argument interactions.

I don’t think anyone will miss the static HMC calls or the Newton optimizer. My goal originally was to have functions that didn’t have any nasty argument interactions (of the form, parameter a is only valid if parameter b is defined and vice-versa, which you get by putting all 7 of these together) and could thus easily support defaults and modular error checking.


#6

Until we find other ones that are better! I’m not trying to imply that Stan is always and forever just implementing nuts and nuts_dense. People have been asking about other algorithms, and I always say the same thing: we take pull requests, but if you want to get it merged, it will have to have some comprehensive comparisons to our current approaches.


#7

@Bob_Carpenter, think it’s worth me putting together a prototype CmdStan version that implements this subset and has flat arguments?


#8

would be happy to work on this.
cheers,
Mitzi


#9

I hope so! You’e in charge of CmdStan now.

If you want to add it to the current CmdStan, then we should leave the old way of doing things intact until we upgrade to CmdStan3 and remove everything that’s deprecated.

The first thing to do is lay down a functional spec—just a description of what the new commands will look like from a user point of view. We then want to follow the same basic flat structure with simple out-of-process wrappers for Python and R.