1
1
// SPDX-License-Identifier: MIT
2
2
// SPDX-FileCopyrightText: Copyright 2019-2023 Heal Research
3
3
4
+ #include " reporter.hpp"
4
5
#include < chrono>
5
6
#include < cmath>
6
7
#include < cstdlib>
@@ -161,7 +162,9 @@ auto main(int argc, char** argv) -> int
161
162
}
162
163
}
163
164
}
164
- Operon::Problem problem (*dataset, trainingRange, testRange);
165
+ Operon::Problem problem (std::move (dataset));
166
+ problem.SetTrainingRange (trainingRange);
167
+ problem.SetTestRange (testRange);
165
168
problem.SetTarget (target.Hash );
166
169
problem.SetInputs (inputs);
167
170
problem.ConfigurePrimitiveSet (primitiveSetConfig);
@@ -170,7 +173,7 @@ auto main(int argc, char** argv) -> int
170
173
creator = ParseCreator (result[" creator" ].as <std::string>(), problem.GetPrimitiveSet (), problem.GetInputs ());
171
174
172
175
auto [amin, amax] = problem.GetPrimitiveSet ().FunctionArityLimits ();
173
- Operon::UniformTreeInitializer treeInitializer (* creator);
176
+ Operon::UniformTreeInitializer treeInitializer (creator. get () );
174
177
175
178
auto const initialMinDepth = result[" creator-mindepth" ].as <std::size_t >();
176
179
auto const initialMaxDepth = result[" creator-mindepth" ].as <std::size_t >();
@@ -200,30 +203,30 @@ auto main(int argc, char** argv) -> int
200
203
201
204
Operon::ChangeVariableMutation changeVar { problem.GetInputs () };
202
205
Operon::ChangeFunctionMutation changeFunc { problem.GetPrimitiveSet () };
203
- Operon::ReplaceSubtreeMutation replaceSubtree { * creator, * coeffInitializer, maxDepth, maxLength };
204
- Operon::InsertSubtreeMutation insertSubtree { * creator, * coeffInitializer, maxDepth, maxLength };
206
+ Operon::ReplaceSubtreeMutation replaceSubtree { creator. get (), coeffInitializer. get () , maxDepth, maxLength };
207
+ Operon::InsertSubtreeMutation insertSubtree { creator. get (), coeffInitializer. get () , maxDepth, maxLength };
205
208
Operon::RemoveSubtreeMutation removeSubtree { problem.GetPrimitiveSet () };
206
209
Operon::DiscretePointMutation discretePoint;
207
210
for (auto v : Operon::Math::Constants) {
208
211
discretePoint.Add (static_cast <Operon::Scalar>(v), 1 );
209
212
}
210
- mutator.Add (* onePoint, 1.0 );
211
- mutator.Add (changeVar, 1.0 );
212
- mutator.Add (changeFunc, 1.0 );
213
- mutator.Add (replaceSubtree, 1.0 );
214
- mutator.Add (insertSubtree, 1.0 );
215
- mutator.Add (removeSubtree, 1.0 );
216
- mutator.Add (discretePoint, 1.0 );
213
+ mutator.Add (onePoint. get () , 1.0 );
214
+ mutator.Add (& changeVar, 1.0 );
215
+ mutator.Add (& changeFunc, 1.0 );
216
+ mutator.Add (& replaceSubtree, 1.0 );
217
+ mutator.Add (& insertSubtree, 1.0 );
218
+ mutator.Add (& removeSubtree, 1.0 );
219
+ mutator.Add (& discretePoint, 1.0 );
217
220
218
221
Operon::DefaultDispatch dtable;
219
222
auto scale = result[" linear-scaling" ].as <bool >();
220
223
auto evaluator = Operon::ParseEvaluator (result[" objective" ].as <std::string>(), problem, dtable, scale);
221
224
evaluator->SetBudget (config.Evaluations );
222
225
223
- auto optimizer = std::make_unique<Operon::LevenbergMarquardtOptimizer<decltype (dtable), Operon::OptimizerType::Eigen>>(dtable, problem);
226
+ auto optimizer = std::make_unique<Operon::LevenbergMarquardtOptimizer<decltype (dtable), Operon::OptimizerType::Eigen>>(& dtable, & problem);
224
227
optimizer->SetIterations (config.Iterations );
225
228
226
- Operon::CoefficientOptimizer cOpt{* optimizer, config. LamarckianProbability };
229
+ Operon::CoefficientOptimizer cOpt{optimizer. get () };
227
230
228
231
EXPECT (problem.TrainingRange ().Size () > 0 );
229
232
@@ -237,7 +240,7 @@ auto main(int argc, char** argv) -> int
237
240
238
241
Operon::RandomGenerator random (config.Seed );
239
242
if (result[" shuffle" ].as <bool >()) {
240
- problem.GetDataset (). Shuffle (random );
243
+ problem.GetDataset ()-> Shuffle (random );
241
244
}
242
245
if (result[" standardize" ].as <bool >()) {
243
246
problem.StandardizeData (problem.TrainingRange ());
@@ -247,145 +250,13 @@ auto main(int argc, char** argv) -> int
247
250
248
251
auto t0 = std::chrono::steady_clock::now ();
249
252
250
- Operon::GeneticProgrammingAlgorithm gp { problem, config, treeInitializer, * coeffInitializer, * generator, * reinserter };
253
+ Operon::GeneticProgrammingAlgorithm gp { config, &problem, & treeInitializer, coeffInitializer. get (), generator. get (), reinserter. get () };
251
254
252
- Operon::Individual best{};
253
-
254
- auto report = [&]() {
255
- auto config = gp.GetConfig ();
256
- auto pop = gp.Parents ();
257
- auto off = gp.Offspring ();
258
-
259
- auto const & problem = gp.GetProblem ();
260
- auto trainingRange = problem.TrainingRange ();
261
- auto testRange = problem.TestRange ();
262
-
263
- auto targetValues = problem.TargetValues ();
264
- auto targetTrain = targetValues.subspan (trainingRange.Start (), trainingRange.Size ());
265
- auto targetTest = targetValues.subspan (testRange.Start (), testRange.Size ());
266
-
267
- auto const & evaluator = gp.GetGenerator ().Evaluator ();
268
-
269
- // some boilerplate for reporting results
270
- auto const idx{0UL };
271
- auto cmp = Operon::SingleObjectiveComparison (idx);
272
- best = *std::min_element (pop.begin (), pop.end (), cmp);
273
-
274
- Operon::Vector<Operon::Scalar> estimatedTrain;
275
- Operon::Vector<Operon::Scalar> estimatedTest;
276
-
277
- tf::Taskflow taskflow;
278
-
279
- using DT = Operon::DefaultDispatch;
280
-
281
- auto evalTrain = taskflow.emplace ([&]() {
282
- estimatedTrain = Operon::Interpreter<Operon::Scalar, DT>::Evaluate (best.Genotype , problem.GetDataset (), trainingRange);
283
- });
284
-
285
- auto evalTest = taskflow.emplace ([&]() {
286
- estimatedTest = Operon::Interpreter<Operon::Scalar, DT>::Evaluate (best.Genotype , problem.GetDataset (), testRange);
287
- });
288
-
289
- // scale values
290
- Operon::Scalar a{1.0 };
291
- Operon::Scalar b{0.0 };
292
- auto linearScaling = taskflow.emplace ([&]() {
293
- auto [a_, b_] = Operon::FitLeastSquares (estimatedTrain, targetTrain);
294
- a = static_cast <Operon::Scalar>(a_);
295
- b = static_cast <Operon::Scalar>(b_);
296
- // add scaling terms to the tree
297
- auto & nodes = best.Genotype .Nodes ();
298
- auto const sz = nodes.size ();
299
- if (std::abs (a - Operon::Scalar{1 }) > std::numeric_limits<Operon::Scalar>::epsilon ()) {
300
- nodes.emplace_back (Operon::Node::Constant (a));
301
- nodes.emplace_back (Operon::NodeType::Mul);
302
- }
303
- if (std::abs (b) > std::numeric_limits<Operon::Scalar>::epsilon ()) {
304
- nodes.emplace_back (Operon::Node::Constant (b));
305
- nodes.emplace_back (Operon::NodeType::Add);
306
- }
307
- if (nodes.size () > sz) {
308
- best.Genotype .UpdateNodes ();
309
- }
310
- });
311
-
312
- double r2Train{};
313
- double r2Test{};
314
- double nmseTrain{};
315
- double nmseTest{};
316
- double maeTrain{};
317
- double maeTest{};
318
-
319
- auto scaleTrain = taskflow.emplace ([&]() {
320
- Eigen::Map<Eigen::Array<Operon::Scalar, -1 , 1 >> estimated (estimatedTrain.data (), std::ssize (estimatedTrain));
321
- estimated = estimated * a + b;
322
- });
323
-
324
- auto scaleTest = taskflow.emplace ([&]() {
325
- Eigen::Map<Eigen::Array<Operon::Scalar, -1 , 1 >> estimated (estimatedTest.data (), std::ssize (estimatedTest));
326
- estimated = estimated * a + b;
327
- });
328
-
329
- auto calcStats = taskflow.emplace ([&]() {
330
- // negate the R2 because this is an internal fitness measure (minimization) which we here repurpose
331
- r2Train = -Operon::R2{}(estimatedTrain, targetTrain);
332
- r2Test = -Operon::R2{}(estimatedTest, targetTest);
333
-
334
- nmseTrain = Operon::NMSE{}(estimatedTrain, targetTrain);
335
- nmseTest = Operon::NMSE{}(estimatedTest, targetTest);
336
-
337
- maeTrain = Operon::MAE{}(estimatedTrain, targetTrain);
338
- maeTest = Operon::MAE{}(estimatedTest, targetTest);
339
- });
340
-
341
- double avgLength = 0 ;
342
- double avgQuality = 0 ;
343
- double totalMemory = 0 ;
344
-
345
- auto getSize = [](Operon::Individual const & ind) { return sizeof (ind) + sizeof (ind.Genotype ) + sizeof (Operon::Node) * ind.Genotype .Nodes ().capacity (); };
346
- auto calculateLength = taskflow.transform_reduce (pop.begin (), pop.end (), avgLength, std::plus{}, [](auto const & ind) { return ind.Genotype .Length (); });
347
- auto calculateQuality = taskflow.transform_reduce (pop.begin (), pop.end (), avgQuality, std::plus{}, [idx=idx](auto const & ind) { return ind[idx]; });
348
- auto calculatePopMemory = taskflow.transform_reduce (pop.begin (), pop.end (), totalMemory, std::plus{}, [&](auto const & ind) { return getSize (ind); });
349
- auto calculateOffMemory = taskflow.transform_reduce (off.begin (), off.end (), totalMemory, std::plus{}, [&](auto const & ind) { return getSize (ind); });
350
-
351
- // define task graph
352
- linearScaling.succeed (evalTrain, evalTest);
353
- linearScaling.precede (scaleTrain, scaleTest);
354
- calcStats.succeed (scaleTrain, scaleTest);
355
- calcStats.precede (calculateLength, calculateQuality, calculatePopMemory, calculateOffMemory);
356
-
357
- executor.corun (taskflow);
358
-
359
- avgLength /= static_cast <double >(pop.size ());
360
- avgQuality /= static_cast <double >(pop.size ());
361
-
362
- auto t1 = std::chrono::steady_clock::now ();
363
- auto elapsed = static_cast <double >(std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count ()) / 1e6 ;
364
-
365
- using T = std::tuple<std::string, double , std::string>;
366
- auto const * format = " :>#8.3g" ;
367
- std::array stats {
368
- T{ " iteration" , gp.Generation (), " :>" },
369
- T{ " r2_tr" , r2Train, format },
370
- T{ " r2_te" , r2Test, format },
371
- T{ " mae_tr" , maeTrain, format },
372
- T{ " mae_te" , maeTest, format },
373
- T{ " nmse_tr" , nmseTrain, format },
374
- T{ " nmse_te" , nmseTest, format },
375
- T{ " avg_fit" , avgQuality, format },
376
- T{ " avg_len" , avgLength, format },
377
- T{ " eval_cnt" , evaluator.CallCount , " :>" },
378
- T{ " res_eval" , evaluator.ResidualEvaluations , " :>" },
379
- T{ " jac_eval" , evaluator.JacobianEvaluations , " :>" },
380
- T{ " opt_time" , evaluator.CostFunctionTime , " :>" },
381
- T{ " seed" , config.Seed , " :>" },
382
- T{ " elapsed" , elapsed, " :>" },
383
- };
384
- Operon::PrintStats ({ stats.begin (), stats.end () }, gp.Generation () == 0 );
385
- };
386
-
387
- gp.Run (executor, random , report);
388
- fmt::print (" {}\n " , Operon::InfixFormatter::Format (best.Genotype , problem.GetDataset (), 6 ));
255
+ auto const * ptr = dynamic_cast <Operon::Evaluator<decltype (dtable)> const *>(evaluator.get ());
256
+ Operon::Reporter<Operon::Evaluator<decltype (dtable)>> reporter (ptr);
257
+ gp.Run (executor, random , [&](){ reporter (executor, gp); });
258
+ auto best = reporter.GetBest ();
259
+ fmt::print (" {}\n " , Operon::InfixFormatter::Format (best.Genotype , *problem.GetDataset (), 6 ));
389
260
} catch (std::exception & e) {
390
261
fmt::print (stderr, " error: {}\n " , e.what ());
391
262
return EXIT_FAILURE;
0 commit comments