bio_ik
MoveIt kinematics_base plugin based on particle optimization & GA
ik_evolution_2.cpp
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2016-2017, Philipp Sebastian Ruppel
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 #include "ik_base.h"
36 
37 #ifdef ENABLE_CPP_OPTLIB
38 #include "cppoptlib/solver/lbfgssolver.h"
39 #endif
40 
41 namespace bio_ik
42 {
43 
44 // fast evolutionary inverse kinematics
45 template <int memetic> struct IKEvolution2 : IKBase
46 {
47  struct Individual
48  {
50  aligned_vector<double> gradients;
51  double fitness;
52  };
53 
54  struct Species
55  {
56  std::vector<Individual> individuals;
57  double fitness;
58  bool improved;
59  };
60 
61  std::vector<double> initial_guess;
62  std::vector<double> solution;
63  std::vector<double> temp_joint_variables;
64  double solution_fitness;
65  std::vector<Species> species;
66  std::vector<Individual> children;
67  std::vector<aligned_vector<Frame>> phenotypes, phenotypes2, phenotypes3;
68  std::vector<size_t> child_indices;
69  std::vector<double*> genotypes;
70  std::vector<Frame> phenotype;
71  std::vector<size_t> quaternion_genes;
72  aligned_vector<double> genes_min, genes_max, genes_span;
73  aligned_vector<double> gradient, temp;
74 
75  IKEvolution2(const IKParams& p)
76  : IKBase(p)
77  {
78  }
79 
80 #ifdef ENABLE_CPP_OPTLIB
81  struct OptlibProblem : cppoptlib::Problem<double>
82  {
83  IKEvolution2* ik;
84  OptlibProblem(IKEvolution2* ik)
85  : ik(ik)
86  {
87  }
88  double value(const TVector& x)
89  {
90  const double* genes = x.data();
91  ik->model.computeApproximateMutations(1, &genes, ik->phenotypes);
92  return ik->computeCombinedFitnessActiveVariables(ik->phenotypes[0], genes);
93  }
94  };
95  typedef cppoptlib::LbfgsSolver<OptlibProblem> OptlibSolver;
96  std::shared_ptr<OptlibSolver> optlib_solver;
97  std::shared_ptr<OptlibProblem> optlib_problem;
98  typename OptlibSolver::TVector optlib_vector;
99 #endif
100 
101  void genesToJointVariables(const Individual& individual, std::vector<double>& variables)
102  {
103  auto& genes = individual.genes;
104  variables.resize(params.robot_model->getVariableCount());
105  for(size_t i = 0; i < problem.active_variables.size(); i++)
106  variables[problem.active_variables[i]] = genes[i];
107  }
108 
109  const std::vector<double>& getSolution() const { return solution; }
110 
111  void initialize(const Problem& problem)
112  {
113  BLOCKPROFILER("initialization");
114 
115  IKBase::initialize(problem);
116 
117  // init list of quaternion joint genes to be normalized during each mutation
118  quaternion_genes.clear();
119  for(size_t igene = 0; igene < problem.active_variables.size(); igene++)
120  {
121  size_t ivar = problem.active_variables[igene];
122  auto* joint_model = params.robot_model->getJointOfVariable(ivar);
123  if(joint_model->getFirstVariableIndex() + 3 != ivar) continue;
124  if(joint_model->getType() != moveit::core::JointModel::FLOATING) continue;
125  quaternion_genes.push_back(igene);
126  }
127 
128  // set solution to initial guess
129  initial_guess = problem.initial_guess;
130  solution = initial_guess;
131  solution_fitness = computeFitness(solution);
132 
133  // init temporary buffer with positions of inactive joints
134  temp_joint_variables = initial_guess;
135 
136  // params
137  size_t population_size = 2;
138  size_t child_count = 16;
139 
140  // initialize population on current island
141  species.resize(2);
142  for(auto& s : species)
143  {
144  // create individuals
145  s.individuals.resize(population_size);
146 
147  // initialize first individual
148  {
149  auto& v = s.individuals[0];
150 
151  // init genes
152  v.genes.resize(problem.active_variables.size());
153  // if(thread_index == 0) // on first island?
154  // if(thread_index % 2 == 0) // on every second island...
155  if(1)
156  {
157  // set to initial_guess
158  for(size_t i = 0; i < v.genes.size(); i++)
159  v.genes[i] = initial_guess[problem.active_variables[i]];
160  }
161  else
162  {
163  // initialize populations on other islands randomly
164  for(size_t i = 0; i < v.genes.size(); i++)
165  v.genes[i] = random(modelInfo.getMin(problem.active_variables[i]), modelInfo.getMax(problem.active_variables[i]));
166  }
167 
168  // set gradients to zero
169  v.gradients.clear();
170  v.gradients.resize(problem.active_variables.size(), 0);
171  }
172 
173  // clone first individual
174  for(size_t i = 1; i < s.individuals.size(); i++)
175  {
176  s.individuals[i].genes = s.individuals[0].genes;
177  s.individuals[i].gradients = s.individuals[0].gradients;
178  }
179  }
180 
181  // space for child population
182  children.resize(population_size + child_count);
183  for(auto& child : children)
184  {
185  child.genes.resize(problem.active_variables.size());
186  child.gradients.resize(problem.active_variables.size());
187  }
188 
189  // init gene infos
190  // if(genes_min.empty())
191  {
192  genes_min.resize(problem.active_variables.size());
193  genes_max.resize(problem.active_variables.size());
194  genes_span.resize(problem.active_variables.size());
195  for(size_t i = 0; i < problem.active_variables.size(); i++)
196  {
197  genes_min[i] = modelInfo.getClipMin(problem.active_variables[i]);
198  genes_max[i] = modelInfo.getClipMax(problem.active_variables[i]);
199  genes_span[i] = modelInfo.getSpan(problem.active_variables[i]);
200  }
201  }
202 
203  /*
204  // init chain mutation masks
205  chain_mutation_masks.resize(chain_mutation_mask_count);
206  for(auto& chain_mutation_mask : chain_mutation_masks)
207  {
208  temp_mutation_chain.clear();
209  if(problem.tip_link_indices.size() > 1)
210  {
211  for(auto* chain_link = params.robot_model->getLinkModel(random_element(problem.tip_link_indices)); chain_link; chain_link = chain_link->getParentLinkModel())
212  temp_mutation_chain.push_back(chain_link);
213  temp_mutation_chain.resize(random_index(temp_mutation_chain.size()) + 1);
214  }
215 
216  temp_chain_mutation_var_mask.resize(params.robot_model->getVariableCount());
217  for(auto& m : temp_chain_mutation_var_mask) m = 0;
218  for(auto* chain_link : temp_mutation_chain)
219  {
220  auto* chain_joint = chain_link->getParentJointModel();
221  for(size_t ivar = chain_joint->getFirstVariableIndex(); ivar < chain_joint->getFirstVariableIndex() + chain_joint->getVariableCount(); ivar++)
222  temp_chain_mutation_var_mask[ivar] = 1;
223  }
224 
225  chain_mutation_mask.resize(problem.active_variables.size());
226  for(size_t igene = 0; igene < problem.active_variables.size(); igene++)
227  chain_mutation_mask[igene] = temp_chain_mutation_var_mask[problem.active_variables[igene]];
228  }
229  */
230  }
231 
232  /*
233  const size_t chain_mutation_mask_count = 256;
234  std::vector<std::vector<int>> chain_mutation_masks;
235  std::vector<const moveit::core::LinkModel*> temp_mutation_chain;
236  std::vector<int> temp_chain_mutation_var_mask;
237  */
238 
239  // aligned_vector<double> rmask;
240 
241  // create offspring and mutate
242  __attribute__((hot)) __attribute__((noinline))
243  //__attribute__((target_clones("avx2", "avx", "sse2", "default")))
244  //__attribute__((target("avx")))
245  void
246  reproduce(const std::vector<Individual>& population)
247  {
248  const auto __attribute__((aligned(32)))* __restrict__ genes_span = this->genes_span.data();
249  const auto __attribute__((aligned(32)))* __restrict__ genes_min = this->genes_min.data();
250  const auto __attribute__((aligned(32)))* __restrict__ genes_max = this->genes_max.data();
251 
252  auto gene_count = children[0].genes.size();
253 
254  size_t s = (children.size() - population.size()) * gene_count + children.size() * 4 + 4;
255 
256  auto* __restrict__ rr = fast_random_gauss_n(s);
257  rr = (const double*)(((size_t)rr + 3) / 4 * 4);
258 
259  /*rmask.resize(s);
260  for(auto& m : rmask) m = fast_random() < 0.1 ? 1.0 : 0.0;
261  double* dm = rmask.data();*/
262 
263  for(size_t child_index = population.size(); child_index < children.size(); child_index++)
264  {
265  double mutation_rate = (1 << fast_random_index(16)) * (1.0 / (1 << 23));
266  auto& parent = population[0];
267  auto& parent2 = population[1];
268  double fmix = (child_index % 2 == 0) * 0.2;
269  double gradient_factor = child_index % 3;
270 
271  auto __attribute__((aligned(32)))* __restrict__ parent_genes = parent.genes.data();
272  auto __attribute__((aligned(32)))* __restrict__ parent_gradients = parent.gradients.data();
273 
274  auto __attribute__((aligned(32)))* __restrict__ parent2_genes = parent2.genes.data();
275  auto __attribute__((aligned(32)))* __restrict__ parent2_gradients = parent2.gradients.data();
276 
277  auto& child = children[child_index];
278 
279  auto __attribute__((aligned(32)))* __restrict__ child_genes = child.genes.data();
280  auto __attribute__((aligned(32)))* __restrict__ child_gradients = child.gradients.data();
281 
282 #pragma omp simd aligned(genes_span : 32), aligned(genes_min : 32), aligned(genes_max : 32), aligned(parent_genes : 32), aligned(parent_gradients : 32), aligned(parent2_genes : 32), aligned(parent2_gradients : 32), aligned(child_genes : 32), aligned(child_gradients : 32) aligned(rr : 32)
283 #pragma unroll
284  for(size_t gene_index = 0; gene_index < gene_count; gene_index++)
285  {
286  // double mutation_rate = (1 << fast_random_index(16)) * (1.0 / (1 << 23));
287 
288  double r = rr[gene_index];
289  // r *= dm[gene_index];
290  double f = mutation_rate * genes_span[gene_index];
291  double gene = parent_genes[gene_index];
292  double parent_gene = gene;
293  gene += r * f;
294  double parent_gradient = mix(parent_gradients[gene_index], parent2_gradients[gene_index], fmix);
295  double gradient = parent_gradient * gradient_factor;
296  gene += gradient;
297  gene = clamp(gene, genes_min[gene_index], genes_max[gene_index]);
298  child_genes[gene_index] = gene;
299  child_gradients[gene_index] = mix(parent_gradient, gene - parent_gene, 0.3);
300  }
301  rr += (gene_count + 3) / 4 * 4;
302  // dm += (gene_count + 3) / 4 * 4;
303 
304  /*if(problem.tip_link_indices.size() > 1)
305  {
306  if(fast_random() < 0.5)
307  {
308  auto& mask = chain_mutation_masks[fast_random_index(chain_mutation_mask_count)];
309  for(size_t gene_index = 0; gene_index < gene_count; gene_index++)
310  {
311  if(!mask[gene_index])
312  {
313  child_genes[gene_index] = parent_genes[gene_index];
314  child_gradients[gene_index] = parent_gradients[gene_index];
315  }
316  }
317  }
318  }*/
319 
320  for(auto quaternion_gene_index : quaternion_genes)
321  {
322  auto& qpos = (*(Quaternion*)&(children[child_index].genes[quaternion_gene_index]));
323  normalizeFast(qpos);
324  }
325  }
326  }
327 
328  void step()
329  {
330  FNPROFILER();
331 
332  for(size_t ispecies = 0; ispecies < species.size(); ispecies++)
333  {
334  auto& species = this->species[ispecies];
335  auto& population = species.individuals;
336 
337  {
338  BLOCKPROFILER("evolution");
339 
340  // initialize forward kinematics approximator
341  genesToJointVariables(species.individuals[0], temp_joint_variables);
342  {
343  BLOCKPROFILER("fk");
344  model.applyConfiguration(temp_joint_variables);
345  model.initializeMutationApproximator(problem.active_variables);
346  }
347 
348  // run evolution for a few generations
349  size_t generation_count = 16;
350  if(memetic) generation_count = 8;
351  for(size_t generation = 0; generation < generation_count; generation++)
352  {
353  // BLOCKPROFILER("evolution");
354 
355  if(canceled) break;
356 
357  // reproduction
358  {
359  BLOCKPROFILER("reproduction");
360  reproduce(population);
361  }
362 
363  size_t child_count = children.size();
364 
365  // pre-selection by secondary objectives
366  if(problem.secondary_goals.size())
367  {
368  BLOCKPROFILER("pre-selection");
369  child_count = random_index(children.size() - population.size() - 1) + 1 + population.size();
370  for(size_t child_index = population.size(); child_index < children.size(); child_index++)
371  {
372  children[child_index].fitness = computeSecondaryFitnessActiveVariables(children[child_index].genes.data());
373  }
374  {
375  BLOCKPROFILER("pre-selection sort");
376  std::sort(children.begin() + population.size(), children.end(), [](const Individual& a, const Individual& b) { return a.fitness < b.fitness; });
377  }
378  }
379 
380  // keep parents
381  {
382  BLOCKPROFILER("keep alive");
383  for(size_t i = 0; i < population.size(); i++)
384  {
385  children[i].genes = population[i].genes;
386  children[i].gradients = population[i].gradients;
387  }
388  }
389 
390  // genotype-phenotype mapping
391  {
392  BLOCKPROFILER("phenotype");
393  size_t gene_count = children[0].genes.size();
394  genotypes.resize(child_count);
395  for(size_t i = 0; i < child_count; i++)
396  genotypes[i] = children[i].genes.data();
397  model.computeApproximateMutations(child_count, genotypes.data(), phenotypes);
398  }
399 
400  // fitness
401  {
402  BLOCKPROFILER("fitness");
403  for(size_t child_index = 0; child_index < child_count; child_index++)
404  {
405  children[child_index].fitness = computeFitnessActiveVariables(phenotypes[child_index], genotypes[child_index]);
406  }
407  }
408 
409  // selection
410  {
411  BLOCKPROFILER("selection");
412  child_indices.resize(child_count);
413  for(size_t i = 0; i < child_count; i++)
414  child_indices[i] = i;
415  for(size_t i = 0; i < population.size(); i++)
416  {
417  size_t jmin = i;
418  double fmin = children[child_indices[i]].fitness;
419  for(size_t j = i + 1; j < child_count; j++)
420  {
421  double f = children[child_indices[j]].fitness;
422  if(f < fmin) jmin = j, fmin = f;
423  }
424  std::swap(child_indices[i], child_indices[jmin]);
425  }
426  for(size_t i = 0; i < population.size(); i++)
427  {
428  std::swap(population[i].genes, children[child_indices[i]].genes);
429  std::swap(population[i].gradients, children[child_indices[i]].gradients);
430  }
431  }
432  }
433  }
434 
435  // memetic optimization
436  {
437  BLOCKPROFILER("memetics");
438 
439  if(memetic == 'q' || memetic == 'l')
440  {
441 
442  // init
443  auto& individual = population[0];
444  gradient.resize(problem.active_variables.size());
445  if(genotypes.empty()) genotypes.emplace_back();
446  phenotypes2.resize(1);
447  phenotypes3.resize(1);
448 
449  // differentiation step size
450  double dp = 0.0000001;
451  if(fast_random() < 0.5) dp = -dp;
452 
453  for(size_t generation = 0; generation < 8; generation++)
454  // for(size_t generation = 0; generation < 32; generation++)
455  {
456 
457  if(canceled) break;
458 
459  // compute gradient
460  temp = individual.genes;
461  genotypes[0] = temp.data();
462  model.computeApproximateMutations(1, genotypes.data(), phenotypes2);
463  double f2p = computeFitnessActiveVariables(phenotypes2[0], genotypes[0]);
464  double fa = f2p + computeSecondaryFitnessActiveVariables(genotypes[0]);
465  for(size_t i = 0; i < problem.active_variables.size(); i++)
466  {
467  double* pp = &(genotypes[0][i]);
468  genotypes[0][i] = individual.genes[i] + dp;
469  model.computeApproximateMutation1(problem.active_variables[i], +dp, phenotypes2[0], phenotypes3[0]);
470  double fb = computeCombinedFitnessActiveVariables(phenotypes3[0], genotypes[0]);
471  genotypes[0][i] = individual.genes[i];
472  double d = fb - fa;
473  gradient[i] = d;
474  }
475 
476  // normalize gradient
477  double sum = dp * dp;
478  for(size_t i = 0; i < problem.active_variables.size(); i++)
479  sum += fabs(gradient[i]);
480  double f = 1.0 / sum * dp;
481  for(size_t i = 0; i < problem.active_variables.size(); i++)
482  gradient[i] *= f;
483 
484  // sample support points for line search
485  for(size_t i = 0; i < problem.active_variables.size(); i++)
486  genotypes[0][i] = individual.genes[i] - gradient[i];
487  model.computeApproximateMutations(1, genotypes.data(), phenotypes3);
488  double f1 = computeCombinedFitnessActiveVariables(phenotypes3[0], genotypes[0]);
489 
490  double f2 = fa;
491 
492  for(size_t i = 0; i < problem.active_variables.size(); i++)
493  genotypes[0][i] = individual.genes[i] + gradient[i];
494  model.computeApproximateMutations(1, genotypes.data(), phenotypes3);
495  double f3 = computeCombinedFitnessActiveVariables(phenotypes3[0], genotypes[0]);
496 
497  // quadratic step size
498  if(memetic == 'q')
499  {
500 
501  // compute step size
502  double v1 = (f2 - f1); // f / j
503  double v2 = (f3 - f2); // f / j
504  double v = (v1 + v2) * 0.5; // f / j
505  double a = (v1 - v2); // f / j^2
506  double step_size = v / a; // (f / j) / (f / j^2) = f / j / f * j * j = j
507 
508  // double v1 = (f2 - f1) / dp;
509  // double v2 = (f3 - f2) / dp;
510  // double v = (v1 + v2) * 0.5;
511  // double a = (v2 - v1) / dp;
512  // // v * x + a * x * x = 0;
513  // // v = - a * x
514  // // - v / a = x
515  // // x = -v / a;
516  // double step_size = -v / a / dp;
517 
518  // for(double f : { 1.0, 0.5, 0.25 })
519  {
520 
521  double f = 1.0;
522 
523  // move by step size along gradient and compute fitness
524  for(size_t i = 0; i < problem.active_variables.size(); i++)
525  genotypes[0][i] = modelInfo.clip(individual.genes[i] + gradient[i] * step_size * f, problem.active_variables[i]);
526  model.computeApproximateMutations(1, genotypes.data(), phenotypes2);
527  double f4p = computeFitnessActiveVariables(phenotypes2[0], genotypes[0]);
528 
529  // accept new position if better
530  if(f4p < f2p)
531  {
532  individual.genes = temp;
533  continue;
534  }
535  else
536  {
537  break;
538  }
539  }
540 
541  // break;
542  }
543 
544  // linear step size
545  if(memetic == 'l')
546  {
547 
548  // compute step size
549  double cost_diff = (f3 - f1) * 0.5; // f / j
550  double step_size = f2 / cost_diff; // f / (f / j) = j
551 
552  // move by step size along gradient and compute fitness
553  for(size_t i = 0; i < problem.active_variables.size(); i++)
554  temp[i] = modelInfo.clip(individual.genes[i] - gradient[i] * step_size, problem.active_variables[i]);
555  model.computeApproximateMutations(1, genotypes.data(), phenotypes2);
556  double f4p = computeFitnessActiveVariables(phenotypes2[0], genotypes[0]);
557 
558  // accept new position if better
559  if(f4p < f2p)
560  {
561  individual.genes = temp;
562  continue;
563  }
564  else
565  {
566  break;
567  }
568  }
569  }
570  }
571 
572 #ifdef ENABLE_CPP_OPTLIB
573  // cppoptlib::LbfgsSolver memetic test
574  if(memetic == 'o')
575  {
576  auto& individual = population[0];
577 
578  // create cppoptlib solver and cppoptlib problem, if not yet existing
579  if(!optlib_solver)
580  {
581  optlib_solver = std::make_shared<OptlibSolver>();
582  cppoptlib::Criteria<double> crit;
583  crit.iterations = 4;
584  optlib_solver->setStopCriteria(crit);
585  optlib_problem = std::make_shared<OptlibProblem>(this);
586  }
587 
588  // init starting point
589  optlib_vector.resize(problem.active_variables.size());
590  for(size_t i = 0; i < problem.active_variables.size(); i++)
591  optlib_vector[i] = individual.genes[i];
592 
593  // solve
594  optlib_solver->minimize(*optlib_problem, optlib_vector);
595 
596  // get result
597  for(size_t i = 0; i < problem.active_variables.size(); i++)
598  individual.genes[i] = modelInfo.clip(optlib_vector[i], problem.active_variables[i]);
599  }
600 #endif
601  }
602  }
603 
604  {
605  BLOCKPROFILER("species");
606 
607  // compute species fitness
608  for(auto& species : this->species)
609  {
610  genesToJointVariables(species.individuals[0], temp_joint_variables);
611  double fitness = computeFitness(temp_joint_variables);
612  species.improved = (fitness != species.fitness);
613  species.fitness = fitness;
614  }
615 
616  // sort species by fitness
617  std::sort(species.begin(), species.end(), [](const Species& a, const Species& b) { return a.fitness < b.fitness; });
618 
619  // wipeouts
620  for(size_t species_index = 1; species_index < species.size(); species_index++)
621  {
622  if(fast_random() < 0.1 || !species[species_index].improved)
623  // if(fast_random() < 0.05 || !species[species_index].improved)
624  {
625  {
626  auto& individual = species[species_index].individuals[0];
627 
628  for(size_t i = 0; i < individual.genes.size(); i++)
629  individual.genes[i] = random(modelInfo.getMin(problem.active_variables[i]), modelInfo.getMax(problem.active_variables[i]));
630 
631  for(auto& v : individual.gradients)
632  v = 0;
633  }
634  for(size_t i = 0; i < species[species_index].individuals.size(); i++)
635  species[species_index].individuals[i] = species[species_index].individuals[0];
636  }
637  }
638 
639  // update solution
640  if(species[0].fitness < solution_fitness)
641  {
642  genesToJointVariables(species[0].individuals[0], solution);
643  solution_fitness = species[0].fitness;
644  }
645  }
646  }
647 
648  // number of islands
649  virtual size_t concurrency() const { return 4; }
650 };
651 
652 static IKFactory::Class<IKEvolution2<0>> bio2("bio2");
653 static IKFactory::Class<IKEvolution2<'q'>> bio2_memetic("bio2_memetic");
654 static IKFactory::Class<IKEvolution2<'l'>> bio2_memetic_l("bio2_memetic_l");
655 
656 #ifdef ENABLE_CPP_OPTLIB
657 static IKFactory::Class<IKEvolution2<'o'>> bio2_memetic_0("bio2_memetic_lbfgs");
658 #endif
659 }