Brush C++ API
A flexible interpretable machine learning framework
Loading...
Searching...
No Matches
engine.cpp
Go to the documentation of this file.
1#include "engine.h"
2
3#include <iostream>
4#include <fstream>
5#include <cmath>
6
7namespace Brush{
8
9using namespace Pop;
10using namespace Sel;
11using namespace Eval;
12using namespace Var;
13using namespace MAB;
14
16template <ProgramType T>
18{
19 r.set_seed(params.get_random_state());
20
21 set_is_fitted(false);
22
23 this->pop = Population<T>();
24 this->evaluator = Evaluation<T>();
25 this->selector = Selection<T>(params.sel, false);
26 this->survivor = Selection<T>(params.surv, true);
27
28 this->archive.set_objectives(params.get_objectives());
29
30 timer.Reset();
31
32 // reset statistics
33 this->stats = Log_Stats();
34}
35
36template <ProgramType T>
37void Engine<T>::print_progress(float percentage)
38{
39 int val = (int) (percentage * 100);
40 int lpad = (int) (percentage * PBWIDTH);
41 int rpad = PBWIDTH - lpad;
42
43 printf ("\rCompleted %3d%% [%.*s%*s]", val, lpad, PBSTR.c_str(), rpad, "");
44
45 fflush (stdout);
46
47 if(val == 100)
48 cout << "\n";
49}
50
51
52template <ProgramType T>
54{
55 int pop_size = 0;
56 if (params.num_islands < 0) {
57 HANDLE_ERROR_THROW("Invalid params.num_islands: cannot be negative");
58 }
59
60 if (static_cast<size_t>(params.num_islands) > pop.island_indexes.size()) {
62 "params.num_islands is greater than pop.island_indexes size in calculate_stats");
63 }
65 for (int island=0; island<params.num_islands; ++island)
66 {
67 auto indices = pop.island_indexes.at(island);
68 pop_size += indices.size();
69 }
70
71 ArrayXf scores(pop_size);
72 ArrayXf scores_v(pop_size);
73
74 // TODO: change all size_t to unsigned?
75 ArrayXi sizes(pop_size);
76 ArrayXi complexities(pop_size);
78 float error_weight = Individual<T>::weightsMap[params.scorer];
79
80 int index = 0;
81 for (int island=0; island<params.num_islands; ++island)
82 {
83 auto indices = pop.island_indexes.at(island);
84 for (unsigned int i=0; i<indices.size(); ++i)
85 {
86 const size_t population_index = indices[i];
87 if (population_index >= this->pop.individuals.size()) {
89 "Invalid population index in island " + to_string(island)
90 + ": " + to_string(population_index)
91 + " (individuals size=" + to_string(this->pop.individuals.size()) + ")");
92 }
93
94 const auto& p = this->pop.individuals.at(population_index);
95
96 // Skip nullptr individuals (offspring slots not yet filled)
97 if (!p)
98 continue;
99
100 float fitness_loss = 0.0f;
101 float fitness_loss_v = 0.0f;
102 unsigned individual_size = 0;
103 unsigned individual_complexity = 0;
104 try {
105 fitness_loss = p->fitness.get_loss();
106 fitness_loss_v = p->fitness.get_loss_v();
107 individual_size = p->get_size();
108 individual_complexity = p->get_complexity();
109 } catch (const std::exception& e) {
111 "Failed to get fitness/size/complexity from individual at population index "
112 + to_string(population_index) + ": " + e.what());
113 } catch (...) {
115 "Failed to get fitness/size/complexity from individual at population index "
116 + to_string(population_index) + ": unknown error");
119 if (!std::isfinite(fitness_loss) || !std::isfinite(fitness_loss_v)) {
121 "Invalid non-finite fitness values at population index "
122 + to_string(population_index));
124
125 // Fitness class will store every information that can be used as
126 // fitness. you just need to access them. Multiplying by weight
127 // so we can find best score. From Fitness::dominates:
128 // the proper way of comparing weighted values is considering
129 // everything as a maximization problem
130 scores(index) = fitness_loss;
131 scores_v(index) = fitness_loss_v;
132 sizes(index) = individual_size;
133 complexities(index) = individual_complexity;
134 ++index;
135 }
136 }
137
138 // index now contains the actual count of non-null individuals
139 // Resize arrays to only include valid individuals
140 scores.conservativeResize(index);
141 scores_v.conservativeResize(index);
142 sizes.conservativeResize(index);
143 complexities.conservativeResize(index);
144
145 // Multiply by weight to make it a maximization problem.
146 // Then, multiply again to get rid of signal
147 float best_score = index > 0 ? (scores*error_weight).maxCoeff()*error_weight : 0.0f;
148 float best_score_v = this->best_ind.fitness.get_loss_v();
149
150 if (!std::isfinite(best_score_v)) {
151 HANDLE_ERROR_THROW("Invalid non-finite validation loss in best_ind while calculating stats");
152 }
153
154 float med_score = index > 0 ? median(scores) : 0.0f;
155 float med_score_v = index > 0 ? median(scores_v) : 0.0f;
156 unsigned med_size = index > 0 ? median(sizes) : 0;
157 unsigned med_complexity = index > 0 ? median(complexities) : 0;
158 unsigned max_size = index > 0 ? sizes.maxCoeff() : 0;
159 unsigned max_complexity = index > 0 ? complexities.maxCoeff() : 0;
160
161 // update stats
162 stats.update(params.current_gen,
163 timer.Elapsed().count(),
164 best_score,
165 best_score_v,
166 med_score,
167 med_score_v,
168 med_size,
169 med_complexity,
170 max_size,
171 max_complexity);
172}
173
174
175template <ProgramType T>
176void Engine<T>::log_stats(std::ofstream& log)
177{
178 // print stats in tabular format
179 string sep = ",";
180 if (params.current_gen == 0) // print header
181 {
182 log << "generation" << sep
183 << "time" << sep
184 << "best_score" << sep
185 << "best_score_val" << sep
186 << "med_score" << sep
187 << "med_score_val" << sep
188 << "med_size" << sep
189 << "med_complexity" << sep
190 << "max_size" << sep
191 << "max_complexity" << "\n";
192 }
193 log << params.current_gen << sep
194 << timer.Elapsed().count() << sep
195 << stats.best_score.back() << sep
196 << stats.best_score_v.back() << sep
197 << stats.med_score.back() << sep
198 << stats.med_score_v.back() << sep
199 << stats.med_size.back() << sep
200 << stats.med_complexity.back() << sep
201 << stats.max_size.back() << sep
202 << stats.max_complexity.back() << "\n";
203}
204
205template <ProgramType T>
206void Engine<T>::print_stats(std::ofstream& log, float fraction)
207{
208 // progress bar
209 string bar, space = "";
210 for (unsigned int i = 0; i<50; ++i)
211 {
212 if (i <= 50*fraction) bar += "/";
213 else space += " ";
214 }
215
216 std::cout.precision(5);
217 std::cout << std::scientific;
218
219 if(params.max_time == -1)
220 std::cout << "Generation " << params.current_gen+1 << "/"
221 << params.max_gens << " [" + bar + space + "]\n";
222 else
223 std::cout << std::fixed << "Time elapsed "<< timer
224 << "/" << params.max_time
225 << " seconds (Generation "<< params.current_gen+1
226 << ") [" + bar + space + "]\n";
227
228 std::cout << std::fixed
229 << "Best model on Val:" << best_ind.program.get_model() << "\n"
230 << "Train Loss (Med): " << stats.best_score.back() << " (" << stats.med_score.back() << ")\n"
231 << "Val Loss (Med): " << stats.best_score_v.back() << " (" << stats.med_score_v.back() << ")\n"
232 << "Median Size (Max): " << stats.med_size.back() << " (" << stats.max_size.back() << ")\n"
233 << "Median complexity (Max): " << stats.med_complexity.back() << " (" << stats.max_complexity.back() << ")\n"
234 << "Time (s): " << timer
235 <<"\n\n";
236}
237
238template <ProgramType T>
240{
241 vector<json> archive_vector;
242 for (const auto& ind : archive.individuals) {
243 json j;
244 to_json(j, ind);
245 archive_vector.push_back(j);
246 }
247
248 return archive_vector;
249}
250
251// TODO: dont have a get_pop and get_serialized_pop --> make them the same name but overloaded.
252// Also fix this in the pybind wrapper
253template <ProgramType T>
255{
256 vector<json> pop_vector;
257 for (const auto& ind : pop.individuals) {
258 if (ind == nullptr) {
259 // HANDLE_ERROR_THROW("get_population found a nullptr individual");
260 continue;
261 }
262
263 json j;
264 to_json(j, *ind);
265 pop_vector.push_back(j);
266 }
267
268 // Note: pop_vector.size() may be less than pop_size during evolution
269 // when offspring slots (nullptrs) haven't been filled by variation yet.
270 // This is a valid transient state and should not throw an error.
271
272 return pop_vector;
273}
274
275template <ProgramType T>
276vector<Individual<T>> Engine<T>::get_archive()
277{
278 vector<Individual<T>> archive_vector;
279 for (const auto& ind : archive.individuals) {
280 archive_vector.push_back(ind);
281 }
282
283 return archive_vector;
284}
285
286template <ProgramType T>
287vector<Individual<T>> Engine<T>::get_population()
288{
289 vector<Individual<T>> pop_vector;
290 for (const auto& ind : pop.individuals) {
291 if (ind == nullptr) {
292 continue;
293 }
294 pop_vector.push_back(*ind);
295 }
296
297 // Note: pop_vector.size() may be less than pop_size during evolution
298 // when offspring slots (nullptrs) haven't been filled by variation yet.
299 // This is a valid transient state and should not throw an error.
300
301 return pop_vector;
302}
303
304template <ProgramType T>
305void Engine<T>::set_population_from_json(vector<json> pop_vector)
306{
307 vector<Individual<T>> new_pop;
308
309 // load serialized individuals
310 for (const auto& ind_j : pop_vector) {
311 Individual<T> ind;
312
313 // deserialize individual
314 from_json(ind_j, ind);
315
316 // set reference to search space
318
319 new_pop.push_back(ind);
320 }
321
322 // check if size matches
323 if(new_pop.size() != params.pop_size)
324 HANDLE_ERROR_THROW("set_population size is different from params.pop_size");
325
326 // re-initialize population
327 this->pop.init(new_pop, params);
328}
329
330template <ProgramType T>
332{
333 vector<Individual<T>> new_pop;
334 for (const auto& ind_j : pop_vector) {
335 Individual<T> ind;
336 ind.program = ind_j.program;
337 ind.set_objectives(ind_j.get_objectives());
338
339 new_pop.push_back(ind);
340 }
341
342 if(new_pop.size() != params.pop_size)
343 HANDLE_ERROR_THROW("set_population size is different from params.pop_size");
344
345 this->pop.init(new_pop, params);
346}
347
348
349template <ProgramType T>
350void Engine<T>::lock_nodes(int end_depth, bool keep_leaves_unlocked, bool keep_current_weights)
351{
352 // iterate over the population, locking the program's tree nodes
353 for (int island=0; island<pop.num_islands; ++island) {
354 auto indices = pop.get_island_indexes(island);
355
356 for (unsigned i = 0; i<indices.size(); ++i)
357 {
358 const auto& ind = pop.individuals.at(indices.at(i));
359
360 // Skip nullptr individuals (offspring slots not yet filled)
361 if (!ind)
362 continue;
363
364 ind->program.lock_nodes(end_depth, keep_leaves_unlocked, keep_current_weights);
365 }
366 }
367}
368
369template <ProgramType T>
371{
372 bool updated = false;
373 bool passed;
374
375 vector<size_t> merged_islands(0);
376 for (int j=0;j<pop.num_islands; ++j)
377 {
378 auto indices = pop.island_indexes.at(j);
379 for (int i=0; i<indices.size(); ++i)
380 {
381 merged_islands.push_back(indices.at(i));
382 }
383 }
384
385 for (int i=0; i < merged_islands.size(); ++i)
386 {
387 const auto& ind_ptr = pop.individuals.at(merged_islands[i]);
388
389 // Skip nullptr individuals (offspring slots not yet filled)
390 if (!ind_ptr)
391 continue;
392
393 const auto& ind = *ind_ptr;
394
395 // TODO: use intermediary variables for wvalues
396 // Iterate over the weighted values to compare (everything is a maximization problem here)
397 passed = false;
398 for (size_t j = 0; j < ind.fitness.get_wvalues().size(); ++j) {
399 if (ind.fitness.get_wvalues()[j] > this->best_ind.fitness.get_wvalues()[j]) {
400 passed = true;
401 break;
402 }
403 if (ind.fitness.get_wvalues()[j] < this->best_ind.fitness.get_wvalues()[j]) {
404 // it is not better, and it is also not equal. So, it is worse. Stop here.
405 break;
406 }
407 // if no break, then its equal, so we keep going
408 }
409
410 if (passed)
411 {
412 this->best_ind = ind;
413 updated = true;
414 }
415 }
416
417 return updated;
418}
419
420
421template <ProgramType T>
423{
424 // avoid re-initializing stuff so we can perform partial fits
425 if (!this->is_fitted){
426 //TODO: i need to make sure i initialize everything (pybind needs to have constructors
427 // without arguments to work, and i need to handle correcting these values before running)
428
429 // initializing classes that need data references
430 this->ss.init(data, params.functions, params.weights_init);
431
432 // TODO: make init to take necessary arguments and perform all initializations inside that function
433 this->init();
434
435 if (params.load_population != "") {
436 this->pop.load(params.load_population);
437 }
438 else if (this->pop.individuals.size() == 0)
439 {
440 // This only works because the Population constructor resizes individuals to zero.
441 this->pop.init(this->ss, this->params);
442 }
443 }
444
445 // TODO: Should I refit them? or use the values at it is? (the fitness WILL BE recalculated regardless)
446 // invalidating all individuals (so they are fitted with current data)
447 // for (auto& individual : this->pop.individuals) {
448 // if (individual != nullptr) {
449 // // will force re-fit and calc all fitness information
450 // individual->set_is_fitted(false);
451 // }
452 // }
453
454 // This is data dependent so we initialize it everytime, regardless of partial fit
455 // TODO: make variator have a default constructor and make it an attribute of engine
456 Variation<T> variator = Variation<T>(this->params, this->ss, data);
457
458 // log file stream
459 std::ofstream log;
460 if (!params.logfile.empty()) {
461 log.open(params.logfile, std::ofstream::app);
462 if (!log.is_open()) {
463 HANDLE_ERROR_THROW("Failed to open logfile: " + params.logfile);
464 }
465 }
466
467 evaluator.set_scorer(params.scorer);
468
469 Dataset &batch = data;
470
471 int threads;
472 if (params.n_jobs == -1)
473 threads = std::thread::hardware_concurrency();
474 else if (params.n_jobs == 0)
475 threads = params.num_islands;
476 else
477 threads = params.n_jobs;
478
479 tf::Executor executor(threads);
480
481 assert( (executor.num_workers() > 0) && "Invalid number of workers");
482
483 tf::Taskflow taskflow;
484
485 // stop criteria
486 unsigned generation = 0;
487 unsigned stall_count = 0;
488 float fraction = 0;
489
490 auto stop = [&]() {
491 bool condition = ( (generation == params.max_gens)
492 || (params.max_stall != 0 && stall_count > params.max_stall)
493 || (params.max_time != -1 && timer.Elapsed().count() > params.max_time)
494 );
495
496 return condition;
497 };
498
499 // TODO: check that I dont use pop.size() (or I use correctly, because it will return the size with the slots for the offspring)
500 // vectors to store each island separatedly
501 vector<vector<size_t>> island_parents;
502
503 island_parents.clear();
504 island_parents.resize(pop.num_islands);
505
506 for (int i=0; i< params.num_islands; i++){
507 size_t idx_start = std::floor(i*params.pop_size/params.num_islands);
508 size_t idx_end = std::floor((i+1)*params.pop_size/params.num_islands);
509
510 auto delta = idx_end - idx_start;
511
512 island_parents.at(i).clear();
513 island_parents.at(i).resize(delta);
514 }
515
516 // heavily inspired in https://github.com/heal-research/operon/blob/main/source/algorithms/nsga2.cpp
517 auto [init, cond, body, back, done] = taskflow.emplace(
518 [&](tf::Subflow& subflow) {
519 auto fit_init_pop = subflow.for_each_index(0, this->params.num_islands, 1, [&](int island) {
520 // Evaluate the individuals at least once
521 // Set validation loss before calling update best
522
523 evaluator.update_fitness(this->pop, island, data, params, true, true);
524 });
525 auto find_init_best = subflow.emplace([&]() {
526 // Make sure we initialize it. We do this update here because we need to
527 // have the individuals fitted before we can compare them. When update_best
528 // is called, we are garanteed that the individuals are fitted and have valid
529 // fitnesses.
530 this->best_ind = *pop.individuals.at(0);
531 this->update_best(); // at this moment we dont care about update_best return value
532 });
533 fit_init_pop.precede(find_init_best);
534 }, // init (entry point for taskflow)
535
536 stop, // loop condition
537
538 [&](tf::Subflow& subflow) { // loop body (evolutionary main loop)
539 auto prepare_gen = subflow.emplace([&]() {
540 params.set_current_gen(generation);
541 batch = data.get_batch(); // will return the original dataset if it is set to dont use batch
542 }).name("prepare generation");// set generation in params, get batch
543
544 auto run_generation = subflow.for_each_index(0, this->params.num_islands, 1, [&](int island) {
545
546 evaluator.update_fitness(this->pop, island, data, params, false, false); // fit the weights with all training data
547
548 // TODO: have some way to set which fitness to use (for example in params, or it can infer based on split size idk)
549 // TODO: if using batch, fitness should be called before selection to set the batch
550 if (data.use_batch) // assign the batch error as fitness (but fit was done with training data)
551 evaluator.update_fitness(this->pop, island, batch, params, false, false);
552
553 vector<size_t> parents = selector.select(this->pop, island, params);
554 for (int i=0; i< parents.size(); i++){
555 island_parents.at(island).at(i) = parents.at(i);
556 }
557
558 this->pop.add_offspring_indexes(island);
559
560 }).name("runs one generation at each island in parallel");
561
562 auto update_pop = subflow.emplace([&]() { // sync point
563 // Variation is not thread safe.
564 // TODO: optimize this and make it work with multiple islands in parallel.
565 for (int island = 0; island < this->params.num_islands; ++island) {
566
567 // TODO: do I have to pass data as an argument here? or can I use the instance reference.
568 // OBS: this function already calls fit internally!
569 variator.vary_and_update(this->pop, island, island_parents.at(island),
570 data, evaluator,
571
572 // conditions to apply simplification.
573 // It starts only on the second half of generations.
574 // Also, we garantee that the final generation
575 // will be simplified.
576 (generation>=params.max_gens/2) || (stall_count == params.max_stall) || (generation==params.max_gens-1)
577 );
578 }
579
580 // select survivors from combined pool of parents and offspring.
581 // if the same individual exists in different islands, then it will be selected several times and the pareto front will have less diversity.
582 // to avoid this, survive should be unified
583 // TODO: survivor should still take params?
584 // TODO: RETURN SINGLE VECTOR and stop wrapping it into a single-element vector
585
586 auto survivor_indices = survivor.survive(this->pop, 0, params);
587
588 // TODO: do i need these next this-> pointers?
589 variator.update_ss();
590 this->pop.update({survivor_indices});
591 this->pop.migrate();
592 }).name("update, migrate and disentangle indexes between islands");
593
594 auto finish_gen = subflow.emplace([&]() {
595 // Set validation loss before calling update best
596 for (int island = 0; island < this->params.num_islands; ++island) {
597 // we can set fit to true, because already fitted individuals will be skipped,
598 // and we ensure we fit everyone
599 evaluator.update_fitness(this->pop, island, data, params, true, true);
600 }
601
602 archive.update(pop, params);
603
604 bool updated_best = this->update_best();
605
606 fraction = params.max_time == -1 ? ((generation+1)*1.0)/params.max_gens :
607 timer.Elapsed().count()/params.max_time;
608
609 if ( params.verbosity>1 || !params.logfile.empty()) {
611 }
612
613 if(params.verbosity>1)
614 print_stats(log, fraction);
615 else if(params.verbosity == 1)
616 print_progress(fraction);
617
618 if (!params.logfile.empty() && log.is_open())
619 log_stats(log);
620
621 if (generation == 0 || updated_best )
622 stall_count = 0;
623 else
624 ++stall_count;
625
626 ++generation;
627
628 }).name("update best, update ss, log, archive, stall");
629
630 // set-up subflow graph
631 prepare_gen.precede(run_generation);
632 run_generation.precede(update_pop);
633 update_pop.precede(finish_gen);
634 },
635
636 [&]() { return 0; }, // jump back to the next iteration
637
638 [&](tf::Subflow& subflow) {
639 // set validation loss, refit (will work only on individuals not fitted)
640 for (int island = 0; island < this->params.num_islands; ++island) {
641 evaluator.update_fitness(this->pop, island, data, params, true, true);
642 }
643
644 archive.update(pop, params);
645 // calculate_stats();
646
647 if (params.save_population != "")
648 this->pop.save(params.save_population);
649
650 set_is_fitted(true);
651
652 // logging the simplifications performed
653 if (!params.logfile.empty() && params.inexact_simplification) {
654 std::ofstream log_simplification;
655 log_simplification.open(params.logfile+"_simplification_table", std::ofstream::app);
656 variator.log_simplification_table(log_simplification);
657
658 log_simplification.close();
659 }
660
661 // TODO: open, write, close? (to avoid breaking the file and allow some debugging if things dont work well)
662 if (log.is_open())
663 log.close();
664
665 // getting the updated versions
666 this->ss = variator.search_space;
667 this->params = variator.parameters;
668
669 } // work done, report last gen and stop
670 ); // evolutionary loop
671
672 init.name("init");
673 cond.name("termination");
674 body.name("main loop");
675 back.name("back");
676 done.name("done");
677 taskflow.name("island_gp");
678
679 init.precede(cond);
680 cond.precede(body, done);
681 body.precede(back);
682 back.precede(cond);
683
684 executor.run(taskflow);
685 executor.wait_for_all();
686
687 //When you have tasks that are created at runtime (e.g., subflow,
688 // cudaFlow), you need to execute the graph first to spawn these tasks and dump the entire graph.
689}
690}
holds variable type data.
Definition data.h:51
Dataset get_batch() const
select random subset of data for training weights.
Definition data.cpp:187
Selection< T > selector
selection algorithm
Definition engine.h:143
Timer timer
start time of training
Definition engine.h:149
bool is_fitted
keeps track of whether fit was called
Definition engine.h:141
void calculate_stats()
Definition engine.cpp:53
Individual< T > best_ind
best individual found during training
Definition engine.h:137
void print_progress(float percentage)
Definition engine.cpp:37
void run(Dataset &d)
train the model
Definition engine.cpp:422
Log_Stats stats
runtime stats
Definition engine.h:147
Archive< T > archive
pareto front archive
Definition engine.h:138
Population< T > pop
population of programs
Definition engine.h:139
void set_population(vector< Individual< T > > pop_vector)
Definition engine.cpp:331
Selection< T > survivor
survival algorithm
Definition engine.h:145
void set_is_fitted(bool f)
set flag indicating whether fit has been called
Definition engine.h:154
vector< json > get_archive_as_json()
return archive/population as strings
Definition engine.cpp:239
void lock_nodes(int end_depth=0, bool keep_leaves_unlocked=true, bool keep_current_weights=false)
Definition engine.cpp:350
Evaluation< T > evaluator
evaluation code
Definition engine.h:144
Parameters params
hyperparameters of brush, which the user can interact
Definition engine.h:134
vector< Individual< T > > get_population()
Definition engine.cpp:287
void init()
initialize Feat object for fitting.
Definition engine.cpp:17
vector< json > get_population_as_json()
Definition engine.cpp:254
vector< Individual< T > > get_archive()
return archive/popularion as individuals ready to use
Definition engine.cpp:276
bool update_best()
updates best score by searching in the population for the individual that best fits the given data
Definition engine.cpp:370
void log_stats(std::ofstream &log)
Definition engine.cpp:176
void print_stats(std::ofstream &log, float fraction)
Definition engine.cpp:206
SearchSpace ss
Definition engine.h:135
void set_population_from_json(vector< json > pop_vector)
Definition engine.cpp:305
Class for evaluating the fitness of individuals in a population.
Definition evaluation.h:27
static std::map< std::string, float > weightsMap
set parent ids using id values
Definition individual.h:165
void set_objectives(vector< string > objs)
Definition individual.h:179
Program< T > program
executable data structure
Definition individual.h:17
Class representing the variation operators in Brush.
Definition variation.h:44
void vary_and_update(Population< T > &pop, int island, const vector< size_t > &parents, const Dataset &data, Evaluation< T > &evaluator, bool do_simplification)
Varies a population and updates the selection strategy based on rewards.
Definition variation.h:223
#define HANDLE_ERROR_THROW(err)
Definition error.h:27
Scalar median(const T &v)
calculate median
Definition utils.h:202
string to_string(const T &value)
template function to convert objects to string for logging
Definition utils.h:369
static Rnd & r
Definition rnd.h:176
string PBSTR
Definition utils.cpp:13
int PBWIDTH
Definition utils.cpp:14
< nsga2 selection operator for getting the front
Definition bandit.cpp:4
void from_json(const json &j, Fitness &f)
Definition fitness.cpp:31
void to_json(json &j, const Fitness &f)
Definition fitness.cpp:6
Eigen::Array< int, Eigen::Dynamic, 1 > ArrayXi
Definition types.h:40
void set_search_space(const std::reference_wrapper< SearchSpace > s)
Definition program.h:87
interfaces with selection operators.
Definition selection.h:25