Brush C++ API
A flexible interpretable machine learning framework
Loading...
Searching...
No Matches
variation.cpp
Go to the documentation of this file.
1#include "variation.h"
2
3namespace Brush {
4namespace Var {
5
6
7using namespace Brush;
8using namespace Pop;
9using namespace MAB;
10
18{
19public:
20 template<Brush::ProgramType T>
21 static auto mutate(Program<T>& program, Iter spot, Variation<T>& variator,
22 const Parameters& params)
23 {
24 // get_node_like will sample a similar node based on node_map_weights or
25 // terminal_weights, and maybe will return a Node.
26
27 optional<Node> newNode = variator.bandit_get_node_like(spot.node->data);
28
29 if (!newNode) // overload to check if newNode == nullopt
30 return false;
31
32 // if optional contains a Node, we access its contained value
33 program.Tree.replace(spot, *newNode);
34
35 return true;
36 }
37};
38
46{
47public:
48 template<Brush::ProgramType T>
49 static auto find_spots(Program<T>& program, Variation<T>& variator,
50 const Parameters& params)
51 {
52 vector<float> weights;
53
54 if (program.Tree.size() < params.get_max_size()) {
55 Iter iter = program.Tree.begin();
56 std::transform(program.Tree.begin(), program.Tree.end(), std::back_inserter(weights),
57 [&](const auto& n){
58 size_t d = 1+program.Tree.depth(iter);
59 std::advance(iter, 1);
60
61 // check if SS holds an operator to avoid failing `check` in sample_op_with_arg
62 if ((d >= params.get_max_depth())
63 || (variator.search_space.node_map.find(n.ret_type) == variator.search_space.node_map.end())) {
64 return 0.0f;
65 }
66 else {
67 return n.get_prob_change();
68 }
69 });
70 }
71 else {
72 // fill the vector with zeros, since we're already at max_size
73 weights.resize(program.Tree.size());
74 std::fill(weights.begin(), weights.end(), 0.0f);
75 }
76
77 return weights;
78 }
79
80 template<Brush::ProgramType T>
81 static auto mutate(Program<T>& program, Iter spot, Variation<T>& variator,
82 const Parameters& params)
83 {
84 auto spot_type = spot.node->data.ret_type;
85
86 // pick a random compatible node to insert (with probabilities given by
87 // node_map_weights). The `-1` represents the node being inserted.
88 // Ideally, it should always find at least one match (the same node
89 // used as a reference when calling the function). However, we have a
90 // size restriction, which will be relaxed here (just as it is in the PTC2
91 // algorithm). This mutation can create a new expression that exceeds the
92 // maximum size by the highest arity among the operators.
93
94 std::optional<Node> n = variator.bandit_sample_op_with_arg(
95 spot_type, spot_type, params.max_size-program.Tree.size()-1);
96
97 if (!n) // there is no operator with compatible arguments
98 return false;
99
100 // make node n wrap the subtree at the chosen spot
101 auto parent_node = program.Tree.wrap(spot, *n);
102
103 // now fill the arguments of n appropriately
104 bool spot_filled = false;
105 for (auto a: (*n).arg_types)
106 {
107 if (spot_filled)
108 {
109 // if spot is in its child position, append children.
110 auto opt = variator.bandit_sample_terminal(a);
111
112 if (!opt)
113 return false;
114
115 program.Tree.append_child(parent_node, opt.value());
116 }
117 // if types match, treat this spot as filled by the spot node
118 else if (a == spot_type)
119 spot_filled = true;
120 // otherwise, add siblings before spot node
121 else {
122 auto opt = variator.bandit_sample_terminal(a);
123
124 if (!opt)
125 return false;
126
127 program.Tree.insert(spot, opt.value());
128 }
129 }
130
131 return true;
132 }
133};
134
142{
143public:
144 template<Brush::ProgramType T>
145 static auto mutate(Program<T>& program, Iter spot, Variation<T>& variator,
146 const Parameters& params)
147 {
148 // sample_terminal will sample based on terminal_weights. If it succeeds,
149 // then the new terminal will be in `opt.value()`
150
151 auto opt = variator.bandit_sample_terminal(spot.node->data.ret_type);
152
153 if (!opt) // there is no terminal with compatible arguments
154 return false;
155
156 program.Tree.erase_children(spot);
157
158 program.Tree.replace(spot, opt.value());
159
160 return true;
161 }
162};
163
171{
172public:
173 template<Brush::ProgramType T>
174 static auto find_spots(Program<T>& program, Variation<T>& variator,
175 const Parameters& params)
176 {
177 vector<float> weights(program.Tree.size());
178
179 if (program.Tree.size() < params.max_size) {
180 std::transform(program.Tree.begin(), program.Tree.end(), weights.begin(),
181 [&](const auto& n){
182 // some nodetypes must always have a weight
183 if (Is<NodeType::OffsetSum>(n.node_type) || Is<NodeType::Constant>(n.node_type))
184 return 0.0f;
185
186 // only weighted nodes can be toggled off
187 if (!n.get_is_weighted()
188 && IsWeighable(n.node_type))
189 {
190 return n.get_prob_change();
191 }
192 else
193 return 0.0f;
194 });
195 }
196 else {
197 // fill the vector with zeros, since we're already at max_size
198 std::fill(weights.begin(), weights.end(), 0.0f);
199 }
200
201 return weights;
202 }
203
204 template<Brush::ProgramType T>
205 static auto mutate(Program<T>& program, Iter spot, Variation<T>& variator,
206 const Parameters& params)
207 {
208 if (spot.node->data.get_is_weighted()==true // cant turn on whats already on
209 || !IsWeighable(spot.node->data.node_type)) // does not accept weights (e.g. boolean)
210 return false; // false indicates that mutation failed and should return std::nullopt
211
212 spot.node->data.set_is_weighted(true);
213 return true;
214 }
215};
216
224{
225public:
226 template<Brush::ProgramType T>
227 static auto find_spots(Program<T>& program, Variation<T>& variator,
228 const Parameters& params)
229 {
230 vector<float> weights(program.Tree.size());
231
232 std::transform(program.Tree.begin(), program.Tree.end(), weights.begin(),
233 [&](const auto& n){
234 // some nodetypes must always have a weight
235 if (Is<NodeType::OffsetSum>(n.node_type) || Is<NodeType::Constant>(n.node_type))
236 return 0.0f;
237
238 if (n.get_is_weighted()
239 && IsWeighable(n.node_type))
240 return n.get_prob_change();
241 else
242 return 0.0f;
243 });
244
245 return weights;
246 }
247
248 template<Brush::ProgramType T>
249 static auto mutate(Program<T>& program, Iter spot, Variation<T>& variator,
250 const Parameters& params)
251 {
252 if (spot.node->data.get_is_weighted()==false) // TODO: This condition should never happen. Make sure it dont, then remove it. (this is also true for toggleweighton, also fix that)
253 return false;
254
255 spot.node->data.set_is_weighted(false);
256 return true;
257 }
258};
259
267{
268public:
269 template<Brush::ProgramType T>
270 static auto find_spots(Program<T>& program, Variation<T>& variator,
271 const Parameters& params)
272 {
273 vector<float> weights;
274
275 auto node_map = variator.search_space.node_map;
276
277 // The minimal size increment would be 2 - replacing a constant with a weighted terminal.
278 // we dont check for size constraints because the replacement can shrink the tree.
279 Iter iter = program.Tree.begin();
280 std::transform(program.Tree.begin(), program.Tree.end(), std::back_inserter(weights),
281 [&](const auto& n){
282 size_t d = program.Tree.depth(iter);
283 size_t s = program.Tree.size(iter);
284 std::advance(iter, 1);
285
286 // we need to make sure there's some node to start the subtree
287 if ((d >= params.max_depth)
288 || (node_map.find(n.ret_type) == node_map.end()) )
289 return 0.0f;
290 else
291 return n.get_prob_change();
292 });
293
294 return weights;
295 }
296
297 template<Brush::ProgramType T>
298 static auto mutate(Program<T>& program, Iter spot, Variation<T>& variator,
299 const Parameters& params)
300 {
301 // check if we exceeded the size/depth constrains (without subtracting,
302 // to avoid overflow cases if the user sets max_size smaller than arity
303 // of smallest operator. The overflow would happen when calculating d and
304 // s in the following lines, to choose the PTC2 limits)
305 if ( params.max_size <= (program.Tree.size() - program.Tree.size(spot))
306 || params.max_depth <= program.Tree.depth(spot) )
307 return false;
308
309 auto spot_type = spot.node->data.ret_type;
310
311 // d and s must be compatible with PTC2 --- they should be based on
312 // tree structure, not program structure
313 size_t d = params.max_depth - program.Tree.depth(spot);
314 size_t s;
315
316 // since `s` is size_t, we need to ensure the operation below will not overflow
317 if (program.Tree.size() < params.max_size)
318 s = params.max_size - (program.Tree.size() - program.Tree.size(spot));
319 else
320 s = 1;
321
322 s = r.rnd_int(1, s+1);
323
324 // sample subtree uses PTC2, which operates on depth and size of the tree<Node>
325 // (and not on the program!). we shoudn't care for weights here
326
327 auto subtree = variator.search_space.sample_subtree(spot.node->data, d, s);
328
329 if (!subtree) // there is no terminal with compatible arguments
330 return false;
331
332 // if optional contains a Node, we access its contained value
333 program.Tree.erase_children(spot);
334
335 program.Tree.move_ontop(spot, subtree.value().begin());
336
337 return true;
338 }
339};
340
348{
349public:
350 template<Brush::ProgramType T>
351 static auto find_spots(Program<T>& program, Variation<T>& variator,
352 const Parameters& params)
353 {
354 vector<float> weights;
355
356 if (program.Tree.size() < params.get_max_size()) {
357 Iter iter = program.Tree.begin();
358 std::transform(program.Tree.begin(), program.Tree.end(), std::back_inserter(weights),
359 [&](const auto& n){
360 size_t d = 1+program.Tree.depth(iter);
361 std::advance(iter, 1);
362
363 // check if SS holds an operator to avoid failing `check` in sample_op_with_arg
364 if (d >= params.get_max_depth()
365 || variator.search_space.node_map.find(n.ret_type) == variator.search_space.node_map.end()
366 // || check if n.ret_type can be splitted (e.g. DataType::ArrayF)
367 ) {
368 return 0.0f;
369 }
370 else {
371 return n.get_prob_change();
372 }
373 });
374 }
375 else {
376 // fill the vector with zeros, since we're already at max_size
377 weights.resize(program.Tree.size());
378 std::fill(weights.begin(), weights.end(), 0.0f);
379 }
380
381 return weights;
382 }
383
384 template<Brush::ProgramType T>
385 static auto mutate(Program<T>& program, Iter spot, Variation<T>& variator,
386 const Parameters& params)
387 {
388 return false;
389 }
390};
391
415template<Brush::ProgramType T>
416std::optional<Individual<T>> Variation<T>::cross(
417 const Individual<T>& mom, const Individual<T>& dad)
418{
419 /* subtree crossover between this and other, producing new Program */
420 // choose location by weighted sampling of program
421 // TODO: why doesn't this copy the search space reference to child?
422 Program<T> child(mom.program);
423
424 // pick a subtree to replace
425 vector<float> child_weights(child.Tree.size());
426
427 auto child_iter = child.Tree.begin();
428 std::transform(child.Tree.begin(), child.Tree.end(), child_weights.begin(),
429 [&](const auto& n){
430 auto s_at = child.size_at(child_iter);
431 auto d_at = child.depth_to_reach(child_iter);
432
433 std::advance(child_iter, 1);
434
435 if (
436 // We don't have to check size here, because it will be replaced
437 // by something with a valid new size.
438 // s_at<parameters.max_size &&
439 d_at<parameters.max_depth
440 )
441 return n.get_prob_change();
442 else
443 return 0.0f;
444 }
445 );
446
447 if (std::all_of(child_weights.begin(), child_weights.end(), [](const auto& w) {
448 return w<=0.0;
449 }))
450 { // There is no spot that has a probability to be selected
451 return std::nullopt;
452 }
453
454 // pick a subtree to insert. Selection is based on other_weights
455 Program<T> other(dad.program);
456
457 int attempts = 0;
458 while (++attempts <= 3)
459 {
460 auto child_spot = r.select_randomly(child.Tree.begin(),
461 child.Tree.end(),
462 child_weights.begin(),
463 child_weights.end()
464 );
465
466 auto child_ret_type = child_spot.node->data.ret_type;
467
468 auto allowed_size = parameters.max_size -
469 ( child.size() - child.size_at(child_spot) );
470 auto allowed_depth = parameters.max_depth -
471 ( child.depth_to_reach(child_spot) );
472
473 vector<float> other_weights(other.Tree.size());
474
475 // Iterator to traverse the tree during transformation
476 auto other_iter = other.Tree.begin();
477 std::transform(other.Tree.begin(), other.Tree.end(), other_weights.begin(),
478 [&other, &other_iter, allowed_size, allowed_depth, child_ret_type](const auto& n) mutable {
479 int s = other.size_at(other_iter);
480 int d = other.depth_at(other_iter);
481
482 std::advance(other_iter, 1);
483
484 // Check feasibility and matching return type
485 if (s <= allowed_size && d <= allowed_depth && n.ret_type == child_ret_type) {
486 return n.get_prob_change();
487 }
488
489 return 0.0f; // Non-feasible crossover point
490 }
491 );
492
493 bool matching_spots_found = std::any_of(other_weights.begin(), other_weights.end(),
494 [](float w) { return w > 0.0f; });
495
496 if (matching_spots_found) {
497
498 auto other_spot = r.select_randomly(
499 other.Tree.begin(),
500 other.Tree.end(),
501 other_weights.begin(),
502 other_weights.end()
503 );
504
505 // fmt::print("other_spot : {}\n",other_spot.node->data);
506 // swap subtrees at child_spot and other_spot
507 child.Tree.move_ontop(child_spot, other_spot);
508
509 Individual<T> ind(child);
510 ind.set_variation("cx"); // TODO: use enum here to make it faster
511
512 return ind;
513 }
514 }
515
516 return std::nullopt;
517};
518
559template<Brush::ProgramType T>
560std::optional<Individual<T>> Variation<T>::mutate(
561 const Individual<T>& parent, string choice)
562{
563 if (choice.empty())
564 {
565 auto options = parameters.mutation_probs;
566
567 bool all_zero = true;
568 for (auto &it : parameters.mutation_probs) {
569 if (it.second > 0.0) {
570 all_zero = false;
571 break;
572 }
573 }
574
575 if (all_zero) { // No mutation can be successfully applied to this solution
576 return std::nullopt;
577 }
578
579 // picking a valid mutation option
580 choice = r.random_choice(parameters.mutation_probs);
581 }
582
583 Program<T> copy(parent.program);
584
585 vector<float> weights; // choose location by weighted sampling of program
586 if (choice.compare("point") == 0) // TODO: use enum here to optimize
587 weights = PointMutation::find_spots(copy, (*this), parameters);
588 else if (choice.compare("insert") == 0)
589 weights = InsertMutation::find_spots(copy, (*this), parameters);
590 else if (choice.compare("delete") == 0)
591 weights = DeleteMutation::find_spots(copy, (*this), parameters);
592 else if (choice.compare("subtree") == 0)
593 weights = SubtreeMutation::find_spots(copy, (*this), parameters);
594 else if (choice.compare("toggle_weight_on") == 0)
595 weights = ToggleWeightOnMutation::find_spots(copy, (*this), parameters);
596 else if (choice.compare("toggle_weight_off") == 0)
597 weights = ToggleWeightOffMutation::find_spots(copy, (*this), parameters);
598 else {
599 std::string msg = fmt::format("{} not a valid mutation choice", choice);
601 }
602
603 if (std::all_of(weights.begin(), weights.end(), [](const auto& w) {
604 return w<=0.0;
605 }))
606 { // There is no spot that has a probability to be selected
607 return std::nullopt;
608 }
609
610 int attempts = 0;
611 while(attempts++ < 3)
612 {
613 Program<T> child(parent.program);
614
615 // apply the mutation and check if it succeeded
616 auto spot = r.select_randomly(child.Tree.begin(), child.Tree.end(),
617 weights.begin(), weights.end());
618
619 // Every mutation here works inplace, so they return bool instead of
620 // std::optional to indicare the result of their manipulation over the
621 // program tree. Here we call the mutation function and return the result
622
623 bool success;
624 if (choice.compare("point") == 0)
625 success = PointMutation::mutate(child, spot, (*this), parameters);
626 else if (choice.compare("insert") == 0)
627 success = InsertMutation::mutate(child, spot, (*this), parameters);
628 else if (choice.compare("delete") == 0)
629 success = DeleteMutation::mutate(child, spot, (*this), parameters);
630 else if (choice.compare("subtree") == 0)
631 success = SubtreeMutation::mutate(child, spot, (*this), parameters);
632 else if (choice.compare("toggle_weight_on") == 0)
633 success = ToggleWeightOnMutation::mutate(child, spot, (*this), parameters);
634 else // it must be"toggle_weight_off"
635 success = ToggleWeightOffMutation::mutate(child, spot, (*this), parameters);
636
637 if (// strict mutation --- returns only valid solutions.
638 ( success
639 && (child.size() <= parameters.max_size)
640 && (child.depth() <= parameters.max_depth) )
641
642 // loose mutation --- it will try its best, but may return something slightly larger.
643 || attempts==3 // this is the final attempt, return whatever we got.
644 ){
645 Individual<T> ind(child);
646
647 ind.set_variation(choice);
648
649 // subtree performs several samplings, and it will leverate
650 // what point/insert/delete mutations learned about each node utility.
651
652 // TODO: handle subtree - it will sample too many nodes and it may
653 // be hard to track which ones actually improved the expression to
654 // update the bandits/ maybe we should skip it?
655 // mutations that sampled from search space
656 if (choice.compare("point") == 0
657 || choice.compare("insert") == 0
658 || choice.compare("delete") == 0
659 // || choice.compare("subtree") == 0 // TODO: disable this one
660 ) {
661 ind.set_sampled_nodes({spot.node->data});
662 }
663
664 return ind;
665 }
666 else { // reseting
667 }
668 }
669
670 return std::nullopt;
671};
672
673template<Brush::ProgramType T>
674void Variation<T>::vary(Population<T>& pop, int island,
675 const vector<size_t>& parents)
676{
677 auto indices = pop.get_island_indexes(island);
678
679 for (unsigned i = 0; i<indices.size(); ++i)
680 {
681 if (pop.individuals.at(indices.at(i)) != nullptr)
682 {
683 continue; // skipping if it is an individual
684 }
685
686 // pass check for children undergoing variation
687 std::optional<Individual<T>> opt=std::nullopt; // new individual
688
689 const Individual<T>& mom = pop[
690 *r.select_randomly(parents.begin(), parents.end())];
691
692 vector<Individual<T>> ind_parents;
693
694 bool crossover = ( r() < parameters.cx_prob );
695 if (crossover)
696 {
697 const Individual<T>& dad = pop[
698 *r.select_randomly(parents.begin(), parents.end())];
699
700 auto variation_result = cross(mom, dad);
701 ind_parents = {mom, dad};
702 opt = variation_result;
703 }
704 else
705 {
706 auto variation_result = mutate(mom);
707
708 ind_parents = {mom};
709 opt = variation_result;
710 }
711
712 // this assumes that islands do not share indexes before doing variation
713 unsigned id = parameters.current_gen*parameters.pop_size+indices.at(i);
714
715 // mutation and crossover already perform 3 attempts. If it fails, we just fill with a random individual
716
717 Individual<T> ind;
718 if (opt) // variation worked, lets keep this
719 {
720 ind = opt.value();
721 ind.set_parents(ind_parents);
722 }
723 else { // no optional value was returned. creating a new random individual
724 // It seems that the line below will not fix the root in clf programs
725 ind.init(search_space, parameters); // ind.variation is born by default
726
727 // Program<T> p = search_space.make_program<Program<T>>(parameters, 0, 0);
728 // ind = Individual<T>(p);
729 }
730
731 ind.set_objectives(mom.get_objectives()); // it will have an invalid fitness
732
733 ind.is_fitted_ = false;
734 ind.set_id(id);
735
736 // TODO: smarter way of copying the entire fitness
737 // copying mom fitness to the new individual (without making the fitnes valid)
738 // so the bandits can access this information. Fitness will be valid
739 // only when we do set_values(). We are setting these parameters below
740 // because we want to keep the previous values for the bandits, and
741 // we are not updating the fitness values here.
742 ind.fitness.set_loss(mom.fitness.get_loss());
744 ind.fitness.set_size(mom.fitness.get_size());
748
749 // dont set stuff that is not used to calculate the rewards, like crowding_dist
750 // ind.fitness.set_crowding_dist(0.0);
751
752 assert(ind.program.size()>0);
753 assert(ind.fitness.valid()==false);
754
755 pop.individuals.at(indices.at(i)) = std::make_shared<Individual<T>>(ind);
756 }
757};
758
759template <Brush::ProgramType T>
761{
762 // propagate bandits learnt information to the search space.
763 // TODO: not all arms are initialized, if the user set something to zero then we must
764 // disable it. So, during update, we need to properly handle these skipped arms. --> remove this for nodes, allow it just for variations. If the user doesnt want to use a feature or op, he should not set it at the first place. We need to do this with variations because the user
765 // can choose it directly instead of letting brush to figure out.
766
767 // variation: getting new probabilities for variation operators
768 auto variation_probs = variation_bandit.sample_probs(true);
769
770 if (variation_probs.find("cx") != variation_probs.end())
771 parameters.set_cx_prob(variation_probs.at("cx"));
772
773 for (const auto& variation : variation_probs)
774 if (variation.first != "cx")
775 parameters.mutation_probs[variation.first] = variation.second;
776
777 // terminal: getting new probabilities for terminal nodes in search space
778 for (auto& bandit : terminal_bandits) {
779 auto datatype = bandit.first;
780
781 auto terminal_probs = bandit.second.sample_probs(true);
782
783 for (auto& [terminal_name, terminal_prob] : terminal_probs) {
784 // Search for the index that matches the terminal name
785 auto it = std::find_if(
786 search_space.terminal_map.at(datatype).begin(),
787 search_space.terminal_map.at(datatype).end(),
788 [&](auto& node) { return node.get_feature() == terminal_name; });
789
790 // if (it != search_space.terminal_map.at(datatype).end()) {
791 auto index = std::distance(search_space.terminal_map.at(datatype).begin(), it);
792
793 // Update the terminal weights with the second value
794 search_space.terminal_weights.at(datatype)[index] = terminal_prob;
795 // }
796 }
797 }
798
799 // operators: getting new probabilities for op nodes
800 for (auto& [ret_type, bandit_map] : op_bandits) {
801 for (auto& [args_type, bandit] : bandit_map) {
802 auto op_probs = bandit.sample_probs(true);
803
804 for (auto& [op_name, op_prob] : op_probs) {
805
806 for (const auto& [node_type, node_value]: search_space.node_map.at(ret_type).at(args_type))
807 {
808 if (node_value.name == op_name) {
809
810 search_space.node_map_weights.at(ret_type).at(args_type).at(node_type) = op_prob;
811 }
812 }
813 }
814 }
815 }
816};
817
818} //namespace Var
819} //namespace Brush
void set_variation(string v)
Definition individual.h:112
void set_sampled_nodes(const vector< Node > &nodes)
Definition individual.h:118
void set_id(unsigned i)
Definition individual.h:122
Fitness fitness
aggregate fitness score
Definition individual.h:37
vector< string > get_objectives() const
Definition individual.h:152
void set_objectives(vector< string > objs)
Definition individual.h:153
void set_parents(const vector< Individual< T > > &parents)
Definition individual.h:123
void init(SearchSpace &ss, const Parameters &params)
Definition individual.h:52
Program< T > program
executable data structure
Definition individual.h:17
vector< size_t > get_island_indexes(int island)
Definition population.h:39
vector< std::shared_ptr< Individual< T > > > individuals
Definition population.h:19
delete subtree and replace it with a terminal of the same return type
static auto mutate(Program< T > &program, Iter spot, Variation< T > &variator, const Parameters &params)
insert a node with spot as a child
Definition variation.cpp:46
static auto mutate(Program< T > &program, Iter spot, Variation< T > &variator, const Parameters &params)
Definition variation.cpp:81
static auto find_spots(Program< T > &program, Variation< T > &variator, const Parameters &params)
Definition variation.cpp:49
static auto find_spots(Program< T > &program, Variation< T > &variator, const Parameters &params)
Definition variation.h:618
tree< Node >::pre_order_iterator Iter
Definition variation.h:615
replace node with same typed node
Definition variation.cpp:18
static auto mutate(Program< T > &program, Iter spot, Variation< T > &variator, const Parameters &params)
Definition variation.cpp:21
Inserts an split node in the spot
static auto find_spots(Program< T > &program, Variation< T > &variator, const Parameters &params)
static auto mutate(Program< T > &program, Iter spot, Variation< T > &variator, const Parameters &params)
replaces the subtree rooted in spot
static auto find_spots(Program< T > &program, Variation< T > &variator, const Parameters &params)
static auto mutate(Program< T > &program, Iter spot, Variation< T > &variator, const Parameters &params)
toggle the node's weight OFF
static auto find_spots(Program< T > &program, Variation< T > &variator, const Parameters &params)
static auto mutate(Program< T > &program, Iter spot, Variation< T > &variator, const Parameters &params)
toggle the node's weight ON
static auto mutate(Program< T > &program, Iter spot, Variation< T > &variator, const Parameters &params)
static auto find_spots(Program< T > &program, Variation< T > &variator, const Parameters &params)
Class representing the variation operators in Brush.
Definition variation.h:44
map< DataType, map< size_t, Bandit > > op_bandits
Definition variation.h:600
void vary(Population< T > &pop, int island, const vector< size_t > &parents)
Handles variation of a population.
SearchSpace search_space
Definition variation.h:591
std::optional< Node > bandit_sample_op_with_arg(DataType ret, DataType arg, int max_args=0)
Definition variation.h:516
std::optional< Individual< T > > cross(const Individual< T > &mom, const Individual< T > &dad)
Performs croearch_spaceover operation on two individuals.
std::optional< Node > bandit_sample_terminal(DataType R)
Definition variation.h:458
Parameters parameters
Definition variation.h:593
std::optional< Node > bandit_get_node_like(Node node)
Definition variation.h:483
map< DataType, Bandit > terminal_bandits
Definition variation.h:599
std::optional< Individual< T > > mutate(const Individual< T > &parent, string choice="")
Performs mutation operation on an individual.
#define HANDLE_ERROR_THROW(err)
Definition error.h:27
static Rnd & r
Definition rnd.h:176
< nsga2 selection operator for getting the front
Definition bandit.cpp:4
auto IsWeighable() noexcept -> bool
Definition node.h:46
float get_loss() const
Definition fitness.h:64
bool valid() const
Definition fitness.h:155
void set_linear_complexity(unsigned int new_lc)
Definition fitness.h:80
void set_complexity(unsigned int new_c)
Definition fitness.h:75
float get_loss_v() const
Definition fitness.h:68
void set_loss_v(float f_v)
Definition fitness.h:67
void set_depth(unsigned int new_d)
Definition fitness.h:85
unsigned int get_complexity() const
Definition fitness.h:77
void set_size(unsigned int new_s)
Definition fitness.h:71
unsigned int get_depth() const
Definition fitness.h:86
unsigned int get_linear_complexity() const
Definition fitness.h:82
unsigned int get_size() const
Definition fitness.h:72
void set_loss(float f)
Definition fitness.h:63
unsigned get_max_size() const
Definition params.h:142
unsigned int max_depth
Definition params.h:37
unsigned int max_size
Definition params.h:38
An individual program, a.k.a. model.
Definition program.h:50
tree< Node > Tree
fitness
Definition program.h:73
int size_at(Iter &top, bool include_weight=true) const
count the size of a given subtree, optionally including the weights in weighted nodes....
Definition program.h:121
int depth() const
count the tree depth of the program. The depth is not influenced by weighted nodes.
Definition program.h:128
int depth_to_reach(Iter &top) const
count the depth until reaching the given subtree. The depth is not influenced by weighted nodes....
Definition program.h:146
int size(bool include_weight=true) const
count the tree size of the program, including the weights in weighted nodes.
Definition program.h:110
Map< Node > node_map
Maps return types to argument types to node types.
std::optional< tree< Node > > sample_subtree(Node root, int max_d, int max_size) const
create a subtree with maximum size and depth restrictions and root of type root_type