From 12a8772cb66aded55b568457226bb5c540486bd9 Mon Sep 17 00:00:00 2001 From: gAldeia Date: Fri, 3 May 2024 18:53:00 -0300 Subject: [PATCH] Archive implementation. Individual ids. New TODOs to solve --- pybrush/BrushEstimator.py | 58 +++++++++++++++- src/bindings/bind_engines.h | 14 ++++ src/bindings/bind_params.cpp | 1 + src/bindings/bind_variation.h | 2 +- src/engine.cpp | 119 +++++++++++++++++++++++++++++++-- src/engine.h | 33 +++++---- src/ind/individual.h | 18 +++++ src/params.h | 11 +++ src/pop/archive.cpp | 45 +++++++------ src/pop/archive.h | 14 ++-- src/vary/variation.cpp | 24 +++++-- src/vary/variation.h | 5 +- tests/cpp/test_brush.cpp | 8 +++ tests/cpp/test_individuals.cpp | 4 +- tests/cpp/test_population.cpp | 2 +- 15 files changed, 301 insertions(+), 57 deletions(-) diff --git a/pybrush/BrushEstimator.py b/pybrush/BrushEstimator.py index 82af3e9e..21ad723c 100644 --- a/pybrush/BrushEstimator.py +++ b/pybrush/BrushEstimator.py @@ -89,6 +89,9 @@ class BrushEstimator(BaseEstimator): val_from_arch: boolean, optional (default: True) Validates the final model using the archive rather than the whole population. + use_arch: boolean, optional (default: False) + Determines if we should save pareto front of the entire evolution + (when set to True) or just the final population (False). batch_size : float, default 1.0 Percentage of training data to sample every generation. If `1.0`, then all data is used. Very small values can improve execution time, but @@ -146,6 +149,7 @@ def __init__( logfile="", weights_init=True, val_from_arch=True, + use_arch=False, validation_size: float = 0.0, batch_size: float = 1.0 ): @@ -165,7 +169,8 @@ def __init__( self.cx_prob=cx_prob self.logfile=logfile self.mutation_probs=mutation_probs - self.val_from_arch=val_from_arch # TODO: val from arch + self.val_from_arch=val_from_arch # TODO: val from arch implementation (in cpp side) + self.use_arch=use_arch self.functions=functions self.objectives=objectives self.initialization=initialization @@ -227,6 +232,8 @@ def fit(self, X, y): self.parameters_.max_size = self.max_size self.parameters_.objectives = self.objectives self.parameters_.cx_prob = self.cx_prob + self.parameters_.use_arch = self.use_arch + self.parameters_.val_from_arch = self.val_from_arch self.parameters_.mig_prob = self.mig_prob self.parameters_.functions = self.functions self.parameters_.mutation_probs = self.mutation_probs @@ -312,6 +319,30 @@ def get_params(self, deep=True): out[key] = value return out + def predict_archive(self, X): + """Returns a list of dictionary predictions for all models.""" + check_is_fitted(self) + + if isinstance(X, pd.DataFrame): + X = X.values + + assert isinstance(X, np.ndarray) + + data = Dataset(X=X, ref_dataset=self.data_, c=self.mode == "classification", + feature_names=self.feature_names_) + + archive = self.engine_.get_archive() + + preds = [] + for ind in archive: + tmp = { + 'id' : ind['id'], + 'y_pred' : self.engine_.predict_archive(ind['id'], data) + } + preds.append(tmp) + + return preds + class BrushClassifier(BrushEstimator,ClassifierMixin): """Deap-based Brush for classification. @@ -368,6 +399,31 @@ def predict_proba(self, X): prob[:, 0] -= prob[:, 1] return prob + + + def predict_archive(self, X): + """Returns a list of dictionary predictions for all models.""" + check_is_fitted(self) + + if isinstance(X, pd.DataFrame): + X = X.values + + assert isinstance(X, np.ndarray) + + data = Dataset(X=X, ref_dataset=self.data_, c=True, + feature_names=self.feature_names_) + + archive = self.engine_.get_archive() + + preds = [] + for ind in archive: + tmp = { + 'id' : ind['id'], + 'y_pred' : self.engine_.predict_proba_archive(ind['id'], data) + } + preds.append(tmp) + + return preds class BrushRegressor(BrushEstimator, RegressorMixin): diff --git a/src/bindings/bind_engines.h b/src/bindings/bind_engines.h index 1ac661ca..24acd2c1 100644 --- a/src/bindings/bind_engines.h +++ b/src/bindings/bind_engines.h @@ -59,6 +59,13 @@ void bind_engine(py::module& m, string name) .def("predict", static_cast &X)>(&T::predict), "predict from X data") + .def("predict_archive", + static_cast(&T::predict_archive), + "predict from individual in archive") + .def("predict_archive", + static_cast &X)>(&T::predict_archive), + "predict from individual in archive") + .def("get_archive", &T::get_archive, py::arg("front") = false) .def(py::pickle( [](const T &p) { // __getstate__ /* Return a tuple that fully encodes the state of the object */ @@ -82,6 +89,13 @@ void bind_engine(py::module& m, string name) .def("predict_proba", static_cast &X)>(&T::predict_proba), "predict from X data") + .def("predict_proba_archive", + static_cast(&T::predict_proba_archive), + "predict from individual in archive") + .def("predict_proba_archive", + static_cast &X)>(&T::predict_proba_archive), + "predict from individual in archive") + ; } } \ No newline at end of file diff --git a/src/bindings/bind_params.cpp b/src/bindings/bind_params.cpp index 6c740a23..dbf3c316 100644 --- a/src/bindings/bind_params.cpp +++ b/src/bindings/bind_params.cpp @@ -25,6 +25,7 @@ void bind_params(py::module& m) .def_property("save_population", &Brush::Parameters::get_save_population, &Brush::Parameters::set_save_population) .def_property("logfile", &Brush::Parameters::get_logfile, &Brush::Parameters::set_logfile) .def_property("num_islands", &Brush::Parameters::get_num_islands, &Brush::Parameters::set_num_islands) + .def_property("use_arch", &Brush::Parameters::get_use_arch, &Brush::Parameters::set_use_arch) .def_property("n_classes", &Brush::Parameters::get_n_classes, &Brush::Parameters::set_n_classes) .def_property("n_jobs", &Brush::Parameters::get_n_jobs, &Brush::Parameters::set_n_classes) .def_property("classification", &Brush::Parameters::get_classification, &Brush::Parameters::set_classification) diff --git a/src/bindings/bind_variation.h b/src/bindings/bind_variation.h index 9647b61d..65613aab 100644 --- a/src/bindings/bind_variation.h +++ b/src/bindings/bind_variation.h @@ -43,7 +43,7 @@ void bind_variation(py::module& m, string name) // including offspring indexes (the vary method will store the offspring in the second half of the index vector) pop.add_offspring_indexes(island); - self.vary(pop, island, parents); + self.vary(pop, island, parents, params); // making copies of the second half of the island individuals vector idxs = pop.get_island_indexes(island); diff --git a/src/engine.cpp b/src/engine.cpp index 33825127..e872b7d6 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -211,6 +211,97 @@ void Engine::print_stats(std::ofstream& log, float fraction) <<"\n\n"; } +template +vector Engine::get_archive(bool front) +{ + json j; // TODO: use this front argument (or remove it). I think I can remove + for (const auto& ind : archive.individuals) { + to_json(j, ind); // Serialize each individual + } + return j; +} + +// TODO: private function called find_individual that searches for it based on id. Then, +// use this function in predict_archive and predict_proba_archive. +template +auto Engine::predict_archive(int id, const Dataset& data) +{ + if (id == best_ind.id) + return best_ind.predict(data); + + for (int i = 0; i < this->archive.individuals.size(); ++i) + { + Individual& ind = this->archive.individuals.at(i); + + if (id == ind.id) + return ind.predict(data); + } + for (int island=0; islandid) + return ind->predict(data); + } + } + + std::runtime_error("Could not find id = " + + to_string(id) + "in archive or population."); + + return best_ind.predict(data); +} + +template +auto Engine::predict_archive(int id, const Ref& X) +{ + Dataset d(X); + return predict_archive(id, d); +} + +template +template + requires((P == PT::BinaryClassifier) || (P == PT::MulticlassClassifier)) +auto Engine::predict_proba_archive(int id, const Dataset& data) +{ + if (id == best_ind.id) + return best_ind.predict_proba(data); + + for (int i = 0; i < this->archive.individuals.size(); ++i) + { + Individual& ind = this->archive.individuals.at(i); + + if (id == ind.id) + return ind.predict_proba(data); + } + for (int island=0; islandid) + return ind->predict_proba(data); + } + } + + std::runtime_error("Could not find id = " + + to_string(id) + "in archive or population."); + + return best_ind.predict_proba(data); +} + +template +template + requires((P == PT::BinaryClassifier) || (P == PT::MulticlassClassifier)) +auto Engine::predict_proba_archive(int id, const Ref& X) +{ + Dataset d(X); + return predict_proba_archive(id, d); +} template // TODO: use the dataset, or ignore it bool Engine::update_best(const Dataset& data, bool val) @@ -313,6 +404,7 @@ void Engine::run(Dataset &data) unsigned generation = 0; unsigned stall_count = 0; float fraction = 0; + bool use_arch; auto stop = [&]() { return ( (generation == params.gens) @@ -402,7 +494,7 @@ void Engine::run(Dataset &data) //std::cout << "before vary" << std::endl; // // variation to produce offspring - variator.vary(this->pop, island, island_parents.at(island)); + variator.vary(this->pop, island, island_parents.at(island), params); //std::cout << "before update fitness" << std::endl; evaluator.update_fitness(this->pop, island, data, params, true); @@ -442,16 +534,16 @@ void Engine::run(Dataset &data) auto finish_gen = subflow.emplace([&]() { bool updated_best = this->update_best(data); - // TODO: use_arch - if ( params.verbosity>1 || !params.logfile.empty()) { + if ( (params.verbosity>1 || !params.logfile.empty() ) + || params.use_arch ) { calculate_stats(); } // TODO: logger working // logger.log("calculate stats...",2); - // if (use_arch) // TODO: archive - // archive.update(pop,params); + if (params.use_arch) + archive.update(pop, params); fraction = params.max_time == -1 ? ((generation+1)*1.0)/params.gens : timer.Elapsed().count()/params.max_time; @@ -498,6 +590,23 @@ void Engine::run(Dataset &data) // TODO: open, write, close? (to avoid breaking the file and allow some debugging if things dont work well) if (log.is_open()) log.close(); + + // if we're not using an archive, let's store the final population in the + // archive + if (!params.use_arch) + { + archive.individuals.resize(0); + for (int island =0; island< pop.num_islands; ++island) { + // cout << "island" << island << endl; + vector idxs = pop.get_island_indexes(island); + + for (unsigned i = 0; iarchive.individuals.size(); }; ///return population as string vector get_archive(bool front); - // /// predict on unseen data from the whole archive - // VectorXf predict_archive(int id, MatrixXf& X); - // VectorXf predict_archive(int id, MatrixXf& X, LongData& Z); - // ArrayXXf predict_proba_archive(int id, MatrixXf& X, LongData& Z); - // ArrayXXf predict_proba_archive(int id, MatrixXf& X); + /// predict on unseen data from the archive + auto predict_archive(int id, const Dataset& data); + auto predict_archive(int id, const Ref& X); + + template + requires((P == PT::BinaryClassifier) || (P == PT::MulticlassClassifier)) + auto predict_proba_archive(int id, const Dataset& data); + template + requires((P == PT::BinaryClassifier) || (P == PT::MulticlassClassifier)) + auto predict_proba_archive(int id, const Ref& X); + + // TODO: make these work + // VectorXf predict_archive(int id, const Ref& X, LongData& Z); + // ArrayXXf predict_proba_archive(int id, const Ref& X, LongData& Z); /// train the model void run(Dataset &d); Parameters params; ///< hyperparameters of brush, which the user can interact Individual best_ind; + + Archive archive; ///< pareto front archive private: SearchSpace ss; @@ -135,7 +145,6 @@ class Engine{ Log_Stats stats; ///< runtime stats Timer timer; ///< start time of training - Archive archive; ///< pareto front archive bool is_fitted; ///< keeps track of whether fit was called. @@ -146,10 +155,10 @@ class Engine{ }; // Only stuff to make new predictions or call fit again -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine, params, best_ind); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine,params, best_ind); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine,params, best_ind); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine,params, best_ind); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine, params, best_ind, archive); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine,params, best_ind, archive); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine,params, best_ind, archive); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Engine,params, best_ind, archive); } // Brush diff --git a/src/ind/individual.h b/src/ind/individual.h index ac6692ca..fb5de712 100644 --- a/src/ind/individual.h +++ b/src/ind/individual.h @@ -23,6 +23,11 @@ class Individual{ // this flag is used to avoid re-fitting an individual. the program is_fitted_ flag is used to perform checks (like in predict with weights). They are two different things and I think I;ll keep this way (individual is just a container to keep program and fitness together) bool is_fitted_ = false; + // archive utility (and also keep track of evolution) (this is meaningful only + // if variation is done using the vary() function) + unsigned id; ///< tracking id + vector parent_id; ///< ids of parents + VectorXf error; ///< training error (used in lexicase selectors) Fitness fitness; ///< aggregate fitness score @@ -32,6 +37,7 @@ class Individual{ Individual() { objectives = {"error", "complexity"}; + id = 0; // unsigned }; Individual(Program& prg) : Individual() { program = prg; }; @@ -85,6 +91,14 @@ class Individual{ void set_fitness(Fitness &f) { fitness=f; }; Fitness& get_fitness() { return fitness; }; + + void set_id(unsigned i){id = i;}; + void set_parents(const vector>& parents){ + parent_id.clear(); + for (const auto& p : parents) + parent_id.push_back(p.id); + }; /// set parent ids using parents + void set_parents(const vector& parents){ parent_id = parents; }; /// set parent ids using id values // TODO: USE setters and getters intead of accessing it directly // template @@ -142,6 +156,8 @@ void to_json(json &j, const Individual &p) j = json{ {"program", p.program}, {"fitness", p.fitness}, + {"id", p.id}, + {"parent_id", p.parent_id}, // {"loss", p.loss}, // {"loss_v", p.loss_v}, // {"complexity", p.complexity}, @@ -158,6 +174,8 @@ void from_json(const json &j, Individual& p) {// TODO: figure out if this works with private attributes and try to actually make them private (and use getters and setters) j.at("program").get_to( p.program ); j.at("fitness").get_to( p.fitness ); + j.at("id").get_to( p.id ); + j.at("parent_id").get_to( p.parent_id ); // j.at("loss").get_to( p.loss ); // j.at("loss_v").get_to( p.loss_v ); // j.at("complexity").get_to( p.complexity ); diff --git a/src/params.h b/src/params.h index da61ae15..20913e31 100644 --- a/src/params.h +++ b/src/params.h @@ -43,6 +43,11 @@ struct Parameters std::unordered_map functions; int num_islands=5; + // if we should save pareto front of the entire evolution (use_arch=true) + // or just the final population (use_arch=false) + bool use_arch=false; + bool val_from_arch=true; + // variation std::map mutation_probs = { {"point", 0.167}, @@ -143,6 +148,12 @@ struct Parameters void set_mig_prob(float new_mig_prob){ mig_prob = new_mig_prob; }; float get_mig_prob(){ return mig_prob; }; + void set_use_arch(bool new_use_arch){ use_arch = new_use_arch; }; + bool get_use_arch(){ return use_arch; }; + + void set_val_from_arch(bool new_val_from_arch){ val_from_arch = new_val_from_arch; }; + bool get_val_from_arch(){ return val_from_arch; }; + void set_classification(bool c){ classification = c; }; bool get_classification(){ return classification; }; diff --git a/src/pop/archive.cpp b/src/pop/archive.cpp index 7ae8df01..4ffe975f 100644 --- a/src/pop/archive.cpp +++ b/src/pop/archive.cpp @@ -27,8 +27,8 @@ bool Archive::sortComplexity(const Individual& lhs, } template -bool Archive::sortObj(const Individual& lhs, - const Individual& rhs, const int index) +bool Archive::sortObj1(const Individual& lhs, + const Individual& rhs) { // sort based on index (we can have more than 2 obj in brush implementation) // obs: because of the weights, every objective is a maximization problem @@ -36,7 +36,10 @@ bool Archive::sortObj(const Individual& lhs, // the bigger the better. the weights allow us to use different min/max metrics // without having to deal with this particular details - return lhs.fitness.wvalues.at(index) > rhs.fitness.wvalues.at(index); + float lhs_obj1 = lhs.fitness.wvalues.at(0); + float rhs_obj1 = rhs.fitness.wvalues.at(0); + + return lhs_obj1 > rhs_obj1; } template @@ -54,15 +57,8 @@ template bool Archive::sameObjectives(const Individual& lhs, const Individual& rhs) { - for (const auto& o_lhs : lhs.fitness) - { - for (const auto& o_rhs : rhs.fitness) - { - if (o_lhs != o_rhs) - return false; - } - } - return true; + return (lhs.fitness == rhs.fitness); + } template @@ -75,7 +71,9 @@ void Archive::init(Population& pop) // dealing with islands --> fast nds for each island for (int island =0; island< pop.num_islands; ++island) { - selector.fast_nds(pop, island); + vector idxs = pop.get_island_indexes(island); + + selector.fast_nds(pop, idxs); } // OBS: fast_nds will change all individual fitness inplace. @@ -89,7 +87,7 @@ void Archive::init(Population& pop) for (unsigned i = 0; i::init(Population& pop) if (this->sort_complexity) std::sort(individuals.begin(),individuals.end(), &sortComplexity); else - std::sort(individuals.begin(),individuals.end(), &sortObj); + std::sort(individuals.begin(),individuals.end(), &sortObj1); } template -void Archive::update(const Population& pop, const Parameters& params) +void Archive::update(Population& pop, const Parameters& params) { individuals.resize(0); // clear archive // refill archive with new pareto fronts (one pareto front for each island!) for (int island =0; island< pop.num_islands; ++island) { - auto front = selector.fast_nds(pop, island); - for (const auto& i : front) + cout << "island" << island << endl; + vector idxs = pop.get_island_indexes(island); + + // TODO: can i just call fast nds with all indexes in idxs? + vector> front = selector.fast_nds(pop, idxs); + for (const auto& i : front[0]) { - individuals.push_back( pop.individuals.at(i) ); + individuals.push_back( *pop.individuals.at(i) ); + cout << "index" << i << endl; } } if (this->sort_complexity) - std::sort(individuals.begin(),individuals.end(),&sortComplexity); + std::sort(individuals.begin(), individuals.end(), &sortComplexity); else - std::sort(individuals.begin(),individuals.end(), &sortObj); + std::sort(individuals.begin(), individuals.end(), &sortObj1); /* auto it = std::unique(individuals.begin(),individuals.end(), &sameFitComplexity); */ auto it = std::unique(individuals.begin(),individuals.end(), diff --git a/src/pop/archive.h b/src/pop/archive.h index 8abbac9d..aef9165e 100644 --- a/src/pop/archive.h +++ b/src/pop/archive.h @@ -27,7 +27,7 @@ struct Archive void init(Population& pop); - void update(const Population& pop, const Parameters& params); + void update(Population& pop, const Parameters& params); void set_objectives(vector objectives); @@ -36,8 +36,8 @@ struct Archive const Individual& rhs); /// Sort population by first objective. - static bool sortObj(const Individual& lhs, - const Individual& rhs, const int index=0); + static bool sortObj1(const Individual& lhs, + const Individual& rhs); /// check for repeats static bool sameFitComplexity(const Individual& lhs, @@ -47,10 +47,10 @@ struct Archive }; //serialization -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals, sort_complexity); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals, sort_complexity); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals, sort_complexity); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Archive, individuals, sort_complexity); } // Pop } // Brush diff --git a/src/vary/variation.cpp b/src/vary/variation.cpp index 213bbd7a..762a3dbc 100644 --- a/src/vary/variation.cpp +++ b/src/vary/variation.cpp @@ -614,7 +614,7 @@ std::optional> Variation::mutate(const Individual& parent) template void Variation::vary(Population& pop, int island, - const vector& parents) + const vector& parents, const Parameters& p) { auto idxs = pop.get_island_indexes(island); @@ -633,33 +633,45 @@ void Variation::vary(Population& pop, int island, const Individual& mom = pop[ *r.select_randomly(parents.begin(), parents.end())]; + vector> ind_parents; if ( r() < parameters.cx_prob) // crossover { const Individual& dad = pop[ *r.select_randomly(parents.begin(), parents.end())]; - opt = cross(mom, dad); + opt = cross(mom, dad); + ind_parents = {mom, dad}; } else // mutation { - opt = mutate(mom); + opt = mutate(mom); + ind_parents = {mom}; } + // this assumes that islands do not share indexes before doing variation + unsigned id = p.current_gen*p.pop_size+idxs.at(i); + // mutation and crossover will already perform 3 attempts. If it fails, we just fill with a random individual - if (opt) // no optional value was returned + if (opt) // variation worked, lets keep this { Individual ind = opt.value(); + ind.is_fitted_ = false; + ind.set_id(id); + ind.set_parents(ind_parents); assert(ind.program.size()>0); pop.individuals.at(idxs.at(i)) = std::make_shared>(ind); } - else { + else { // no optional value was returned Individual new_ind; - new_ind.is_fitted_ = false; + // creating a new random individual from nothing new_ind.init(search_space, parameters); + new_ind.set_objectives(mom.get_objectives()); // it will have an invalid fitness + new_ind.set_id(id); + new_ind.is_fitted_ = false; pop.individuals.at(idxs.at(i)) = std::make_shared>(new_ind); } diff --git a/src/vary/variation.h b/src/vary/variation.h index 6c9994aa..8868a53b 100644 --- a/src/vary/variation.h +++ b/src/vary/variation.h @@ -108,7 +108,7 @@ class Variation { private: SearchSpace search_space; - Parameters parameters; + Parameters parameters; // stop using this thing here and get parameter as argument public: Variation() = default; @@ -129,7 +129,8 @@ class Variation std::optional> mutate(const Individual& parent); /// method to handle variation of population - void vary(Population& pop, int island, const vector& parents); + void vary(Population& pop, int island, const vector& parents, + const Parameters& p); }; } //namespace Var diff --git a/tests/cpp/test_brush.cpp b/tests/cpp/test_brush.cpp index 2211c4aa..5e46b324 100644 --- a/tests/cpp/test_brush.cpp +++ b/tests/cpp/test_brush.cpp @@ -23,6 +23,11 @@ #include "../../src/pop/archive.cpp" #include "../../src/pop/population.cpp" +// TODO: test predict from archive +// TODO: rename it to test_engine + +// TODO: test serialization of archive (get archive and save to json) + // TODO: test logger, verbose, print stats, etc. TEST(Engine, EngineWorks) { @@ -84,6 +89,9 @@ TEST(Engine, EngineWorks) params.set_gens(10); params.set_mig_prob(0.5); + // just to see if nothing breaks + params.set_use_arch(true); + std::cout << "n jobs = 1" << std::endl; params.set_n_jobs(1); Brush::RegressorEngine est6(params); diff --git a/tests/cpp/test_individuals.cpp b/tests/cpp/test_individuals.cpp index 86cfb2c3..5b3e5df6 100644 --- a/tests/cpp/test_individuals.cpp +++ b/tests/cpp/test_individuals.cpp @@ -1 +1,3 @@ -// TODO: test predict, predict proba, fit. \ No newline at end of file +// TODO: test predict, predict proba, fit. + +// TODO: test parent_id and id \ No newline at end of file diff --git a/tests/cpp/test_population.cpp b/tests/cpp/test_population.cpp index 8bd2937f..3a3e2b27 100644 --- a/tests/cpp/test_population.cpp +++ b/tests/cpp/test_population.cpp @@ -107,7 +107,7 @@ TEST(Population, PopulationTests) // variation applied to population fmt::print("Variations for island {}\n", j); - variator.vary(pop, j, parents); + variator.vary(pop, j, parents, params); fmt::print("fitting {}\n", j); // at this step, we know that theres only one pointer to each individual being fitted, so we can perform it in parallel evaluator.update_fitness(pop, j, data, params, true, true);