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