Fleet  0.0.9
Inference in the LOT
MyHypothesis.h
Go to the documentation of this file.
1 #pragma once
2 
6 #include "ConstantContainer.h"
8 #include "Random.h"
9 
10 
11 // we also consider our scale variables (proposals to constants) as powers of 10
12 const int MIN_SCALE = -6;
13 const int MAX_SCALE = 12;
14 
15 // most nodes we'll consider in a hypothesis
16 const size_t MY_MAX_NODES = 35;
17 
18 // we use at most this many constants; note that if we have higher, we are given zero prior.
19 // this value kinda matters because we assume we always have this many, in order to avoid problems
20 // with changing dimensionality.
21 const size_t N_CONSTANTS = 4;
22 
23 // scale for the prior on constants
24 //const double PRIOR_SCALE = 1.0;
25 
26 // a normal distribution with SD exp(s) with s uniform on MIN_SCALE, MAX_SCALE
27 double compoundNormalLogUniform_lpdf(const double _x) {
28  auto x = std::abs(_x);
29  return log(std::erfc(x*exp(-MAX_SCALE)/ROOT2)-std::erfc(x*exp(-MIN_SCALE)/ROOT2)) -
30  LOG2 - log(x) - log(MAX_SCALE-MIN_SCALE);
31 }
32 
33 class MyHypothesis final : public ConstantContainer,
34  public DeterministicLOTHypothesis<MyHypothesis,X_t,D,MyGrammar,&grammar> {
35 
36 public:
37 
39  using Super::Super;
40 
41  virtual D call(const X_t x, const D err=NaN) {
42  // We need to override this because DeterministicLOTHypothesis::call asserts that the program is non-empty
43  // but actually ours can be if we are only a constant.
44  // my own wrapper that zeros the constant_i counter
45  assert(constants.size() == N_CONSTANTS);
46 
47  reset_constant_index();
48  try {
49  const auto out = Super::call(x,err);
50  assert(constant_idx == count_constants()); // just check we used all constants
51  return out;
52  }
53  catch(TooManyConstantsException& e) {
54  return err;
55  }
56  }
57 
58  virtual double constant_prior() const {
59  // we're going to override and do a LSE over scales
60 
61  if(count_constants() > N_CONSTANTS) {
62  return -infinity;
63  }
64 
65  double lp = 0.0;
66  for(auto& c : constants) {
67  lp += compoundNormalLogUniform_lpdf(std::abs(c)); // NOTE: NOT normalized
68  //lp += t_lpdf(c, PRIOR_SCALE);
69  }
70  return lp;
71  }
72 
73  virtual void randomize_constants() override {
74  // NOTE: this MUST match how the prior is computed
75  constants.resize(N_CONSTANTS);
76  for(size_t i=0;i<N_CONSTANTS;i++) {
77  constants[i] = random_normal(0.0, exp(uniform(MIN_SCALE,MAX_SCALE))); // propose with random and random scale
78  //constants[i] = random_t(PRIOR_SCALE);
79  }
80  }
81 
82  virtual size_t count_constants() const override {
83  size_t cnt = 0;
84  for(const auto& x : value) {
85  cnt += (x.nt() == grammar.nt<Constant>());
86  }
87 
88  return cnt;
89  }
90 
91  // Propose to a constant c, returning a new value and fb
92  // NOTE: When we use a symmetric drift kernel, fb=0
93  std::pair<double,double> constant_proposal(Constant c) const override {
94 
95  if(flip(0.95)) {
96  // we use fb=0 here because we can consider an auxilliary variable
97  // to be the scale variable
98  auto sc = exp(uniform(MIN_SCALE, MAX_SCALE));
99  return std::make_pair(random_normal(c,sc), 0.0);
100  }
101  else if(flip(0.5)) {
102  // one third probability for each of the other choices
104  double fb = normal_lpdf( v, data_X_mean, data_X_sd) -
105  normal_lpdf<D>(c, data_X_mean, data_X_sd);
106  return std::make_pair(v,fb);
107  }
108  else {
110  double fb = normal_lpdf( v, data_Y_mean, data_Y_sd) -
111  normal_lpdf<D>(c, data_Y_mean, data_Y_sd);
112  return std::make_pair(v,fb);
113  }
114  }
115 
116  double compute_single_likelihood(const datum_t& datum) override {
117  double fx = this->call(datum.input, NaN);
118 
119  if(std::isnan(fx) or std::isinf(fx))
120  return -infinity;
121 
122  //PRINTN(string(), datum.output, fx, datum.reliability, normal_lpdf( fx, datum.output, datum.reliability ));
123 
124  return normal_lpdf(fx, datum.output, datum.reliability );
125  }
126 
127  virtual double compute_prior() override {
128 
129  if(this->value.count() > MY_MAX_NODES) {
130  return this->prior = -infinity;
131  }
132 
133  // check to see if it uses all variables.
134  #ifdef REQUIRE_USE_ALL_VARIABLES
135  std::vector<bool> uses_variable(NUM_VARS, false);
136  for(const auto& n : this->value) {
137  if(n.rule->format[0] == 'x') {
138  std::string s = n.rule->format; s.erase(0,1); // remove x
139  uses_variable[string_to<int>(s)] = true;
140  }
141  }
142  for(const auto& v : uses_variable) { // check that we used everything
143  if(not v) return this->prior = -infinity;
144  }
145  #endif
146 
147  this->prior = Super::compute_prior() + this->constant_prior();
148  return this->prior;
149  }
150 
151  virtual std::string __my_string_recurse(const Node* n, size_t& idx) const {
152  // we need this to print strings -- its in a similar format to evaluation
153  if(n->rule->nt == grammar.nt<Constant>()) {
154  return "("+to_string_with_precision(constants[idx++], 8)+")";
155  }
156  else if(n->rule->N == 0) {
157  return n->rule->format;
158  }
159  else {
160 
161  // strings are evaluated in right->left order so we have to
162  // use that here (since we use them to index idx)
163  std::vector<std::string> childStrings(n->nchildren());
164 
166  // which means that they are popped
167  for(size_t i=0;i<n->rule->N;i++) {
168  childStrings[i] = __my_string_recurse(&n->child(i),idx);
169  }
170 
171  std::string s = n->rule->format;
172  for(size_t i=0;i<n->rule->N;i++) { // can't be size_t for counting down
173  auto pos = s.find(Rule::ChildStr);
174  assert(pos != std::string::npos); // must contain the ChildStr for all children all children
175  s.replace(pos, Rule::ChildStr.length(), childStrings[i]);
176  }
177 
178  return s;
179  }
180  }
181 
182  virtual std::string string(std::string prefix="") const override {
183  // we can get here where our constants have not been defined it seems...
184  if(not this->is_evaluable())
185  return structure_string(); // don't fill in constants if we aren't complete
186 
187  size_t idx = 0;
188  return prefix + LAMBDAXDOT_STRING + __my_string_recurse(&value, idx);
189  }
190 
191  virtual std::string structure_string(bool usedot=true) const {
192  return Super::string("", usedot);
193  }
194 
198 
199  virtual bool operator==(const MyHypothesis& h) const override {
200  // equality requires our constants to be equal
201  return this->Super::operator==(h) and ConstantContainer::operator==(h);
202  }
203 
204  virtual size_t hash() const override {
205  // hash includes constants so they are only ever equal if constants are equal
206  size_t h = Super::hash();
207  hash_combine(h, ConstantContainer::hash());
208  return h;
209  }
210 
214 
215 #if FEYNMAN
216 
217  virtual ProposalType propose() const override {
218  // Our proposals will either be to constants, or entirely from the prior
219  // Note that if we have no constants, we will always do prior proposals
220  assert(constants.size() == N_CONSTANTS);
221 
222  ProposalType p;
223 
224  if(flip(0.75)) p = Proposals::regenerate(&grammar, value);
225  else if(flip(0.1)) p = Proposals::sample_function_leaving_args(&grammar, value);
226  else if(flip(0.1)) p = Proposals::swap_args(&grammar, value);
227  else if(flip()) p = Proposals::insert_tree(&grammar, value);
228  else p = Proposals::delete_tree(&grammar, value);
229 
230  if(not p) return {};
231  auto x = p.value();
232 
233  MyHypothesis ret{std::move(x.first)};
234  ret.constants = constants;
235 
236  double fb = x.second;
237 
238 
239  return std::make_pair(ret, fb);
240  }
241 
242 #else
243 
244  virtual ProposalType propose() const override {
245  // Our proposals will either be to constants, or entirely from the prior
246  // Note that if we have no constants, we will always do prior proposals
247  assert(constants.size() == N_CONSTANTS);
248 
249  // most of the time we are going to propose to a constant
250  // UNLESS we are using FEYNMAN (in which case we never should)
251  if(flip(0.85)){
252  MyHypothesis ret = *this;
253 
254  double fb = 0.0;
255 
256  // now add to all that I have
257  auto should_propose = random_nonempty_subset(N_CONSTANTS, 0.1);
258  for(size_t i=0;i<N_CONSTANTS;i++) { // note N_CONSTANTS here, so we propose to the whole vector
259  if(should_propose[i]) {
260  auto [v, __fb] = this->constant_proposal(constants[i]);
261  ret.constants[i] = v;
262  fb += __fb;
263  }
264  }
265 
266  return std::make_pair(ret, fb);
267  }
268  else {
269 
270  ProposalType p;
271 
272  if(flip(0.5)) p = Proposals::regenerate(&grammar, value);
273  else if(flip(0.1)) p = Proposals::sample_function_leaving_args(&grammar, value);
274  else if(flip(0.1)) p = Proposals::swap_args(&grammar, value);
275  else if(flip()) p = Proposals::insert_tree(&grammar, value);
276  else p = Proposals::delete_tree(&grammar, value);
277 
278  if(not p) return {};
279  auto x = p.value();
280 
281  MyHypothesis ret{std::move(x.first)};
282 
283  double fb = x.second;
284 
285  #if FEYNMAN
286  ret.constants = constants;
287  #else
288  assert( constants.size() == N_CONSTANTS);
289  if(flip(0.5)) {
290  ret.randomize_constants(); // with random constants
291  fb += ret.constant_prior()-this->constant_prior();
292  }
293  else { // else just copy our constants
294  ret.constants = constants;
295  }
296  #endif
297 
298  return std::make_pair(ret, fb);
299  }
300 
301  }
302 
303 #endif
304 
305  virtual MyHypothesis restart() const override {
306  MyHypothesis ret = Super::restart(); // reset my structure
307  ret.randomize_constants();
308  return ret;
309  }
310 
311  virtual void complete() override {
312  Super::complete();
313  randomize_constants();
314  }
315 
316  virtual MyHypothesis make_neighbor(int k) const override {
317  auto ret = Super::make_neighbor(k);
318  ret.randomize_constants();
319  return ret;
320  }
321  virtual void expand_to_neighbor(int k) override {
323  randomize_constants();
324  }
325 
326  [[nodiscard]] static MyHypothesis sample() {
327  auto ret = Super::sample();
328  ret.randomize_constants();
329  return ret;
330  }
331 };
332 
std::vector< bool > random_nonempty_subset(const size_t n, const double p)
Returns a random nonempty subset of n elements, as a vector<bool> of length n with trues for elements...
Definition: Random.h:220
ProposalType
Definition: Main.cpp:124
virtual size_t hash() const override
Definition: Lexicon.h:117
MyGrammar grammar
virtual bool operator==(const ConstantContainer &h) const
Definition: ConstantContainer.h:77
virtual size_t hash() const
Definition: ConstantContainer.h:91
std::optional< std::pair< Node, double > > sample_function_leaving_args(GrammarType *grammar, const Node &from)
This samples functions f(a,b) -> g(a,b) (e.g. without destroying what&#39;s below). This uses a little tr...
Definition: Proposers.h:331
This is a struct to basically hold a double (or float) for use in SymbolicRegression etc This allows ...
Definition: Node.h:22
static MyHypothesis sample()
Static function for making a hypothesis. Be careful using this with references because they may not f...
Definition: MCMCable.h:29
virtual double compute_prior() override
Definition: MyHypothesis.h:40
Definition: ConstantContainer.h:48
double compute_single_likelihood(const datum_t &d) override
Compute the likelihood of a single data point.
Definition: Main.cpp:229
size_t NUM_VARS
Definition: Main.cpp:43
double uniform()
Definition: Random.h:11
const double ROOT2
Definition: Numerics.h:19
std::string to_string_with_precision(const T a_value, const int n=14)
Definition: Strings.h:23
double prior
Definition: Bayesable.h:42
double data_X_mean
Definition: Main.cpp:32
virtual DiscreteDistribution< output_t > call(const key_t k, const input_t x, const output_t &err=output_t{})
Definition: Lexicon.h:361
static const std::string ChildStr
Definition: Rule.h:24
DiscreteDistribution< word > call(const utterance &input)
Definition: Main.cpp:225
std::array< D, MAX_VARS > X_t
Definition: Main.cpp:47
bool flip(float p=0.5)
Definition: Random.h:25
Definition: ConstantContainer.h:12
size_t N
Definition: Rule.h:29
we don&#39;t need inputs/outputs for out MyHypothesis
Definition: MyHypothesis.h:6
std::optional< std::pair< Node, double > > delete_tree(GrammarType *grammar, const Node &from)
Definition: Proposers.h:275
double random_normal(double mu=0, double sd=1.0)
Definition: Random.h:39
constexpr double infinity
Definition: Numerics.h:20
static constexpr nonterminal_t nt()
Definition: Grammar.h:84
Definition: DeterministicLOTHypothesis.h:14
Bayesable< defaultdatum_t< S, S >, std::vector< defaultdatum_t< S, S > > >::datum_t datum_t
Definition: LOTHypothesis.h:47
const double LOG2
Definition: Numerics.h:18
std::string format
Definition: Rule.h:28
this_t & child(const size_t i)
Definition: BaseNode.h:175
const Rule * rule
Definition: Node.h:32
std::optional< std::pair< Node, double > > regenerate(GrammarType *grammar, const Node &from)
A little helper function that resamples everything below when we can. If we can&#39;t, then we&#39;ll recurse.
Definition: Proposers.h:107
virtual std::string string(std::string prefix="") const override
Definition: MyHypothesis.h:159
const std::string LAMBDAXDOT_STRING
Definition: Strings.h:20
virtual std::string string(std::string prefix="") const override
Definition: Lexicon.h:102
std::optional< std::pair< Node, double > > insert_tree(GrammarType *grammar, const Node &from)
Definition: Proposers.h:192
constexpr double NaN
Definition: Numerics.h:21
This is a thread_local rng whose first object is used to see others (in other threads). This way, we can have thread_local rngs that all are seeded deterministcally in Fleet via –seed=X.
double data_Y_sd
Definition: Main.cpp:35
virtual MyHypothesis make_neighbor(int k) const
Return a new hypothesis which is the k&#39;th neighbor (just calls expand_to_neighbor) NOTE This does not...
Definition: Searchable.h:35
nonterminal_t nt
Definition: Rule.h:27
Definition: ConstantContainer.h:24
std::optional< std::pair< Node, double > > swap_args(GrammarType *grammar, const Node &from)
This propose swaps around arguments of the same type.
Definition: Proposers.h:389
virtual MyHypothesis restart() const override
Definition: Lexicon.h:301
virtual bool operator==(const MyHypothesis &l) const override
Equality checks equality on each part.
Definition: Lexicon.h:138
double data_Y_mean
Definition: Main.cpp:33
double data_X_sd
Definition: Main.cpp:34
size_t nchildren() const
Definition: BaseNode.h:208
double D
Definition: Main.cpp:15
T normal_lpdf(T x, T mu=0.0, T sd=1.0)
Definition: Random.h:45
virtual double compute_prior() override
Definition: Lexicon.h:252
virtual std::optional< std::pair< MyHypothesis, double > > propose() const override
This proposal guarantees that there will be at least one factor that is proposed to. Each individual factor is proposed to with p_factor_propose.
Definition: Main.cpp:163