Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2019 Jakub Rydzewski (jr@fizyka.umk.pl). All rights reserved.
3 :
4 : See http://www.maze-code.github.io for more information.
5 :
6 : This file is part of maze.
7 :
8 : maze is free software: you can redistribute it and/or modify it under the
9 : terms of the GNU Lesser General Public License as published by the Free
10 : Software Foundation, either version 3 of the License, or (at your option)
11 : any later version.
12 :
13 : maze is distributed in the hope that it will be useful, but WITHOUT ANY
14 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 : FOR A PARTICULAR PURPOSE.
16 :
17 : See the GNU Lesser General Public License for more details.
18 :
19 : You should have received a copy of the GNU Lesser General Public License
20 : along with maze. If not, see <https://www.gnu.org/licenses/>.
21 : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
22 : #ifndef __PLUMED_maze_Memetic_h
23 : #define __PLUMED_maze_Memetic_h
24 :
25 : /**
26 : * @file Memetic.h
27 : *
28 : * @author J. Rydzewski (jr@fizyka.umk.pl)
29 : */
30 :
31 : #include "core/ActionRegister.h"
32 :
33 : #include "Core.h"
34 : #include "Member.h"
35 : #include "Optimizer.h"
36 :
37 : namespace PLMD {
38 : namespace maze {
39 :
40 : /**
41 : * @class Memetic Memetic.h "maze/Memetic.h"
42 : *
43 : * @brief Memetic algorithms for the optimization of the loss function.
44 : */
45 : class Memetic: public Optimizer {
46 : public:
47 : /**
48 : * PLMD constructor.
49 : *
50 : * @param[in] ao PLMD::ActionOptions&.
51 : */
52 : explicit Memetic(const ActionOptions& ao);
53 :
54 : /**
55 : * Destructor required for deriving classes.
56 : */
57 : ~Memetic();
58 :
59 : /**
60 : * Registers PLMD keywords.
61 : *
62 : * @param[in] keys Keywords.
63 : */
64 : static void registerKeywords(Keywords& keys);
65 :
66 : /**
67 : * Each class deriving from Optimizer needs to override this function.
68 : */
69 : void optimize() override;
70 :
71 : private:
72 : /**
73 : * Create a set of translations relative to the ligand, each translation
74 : * encodes a probable biasing direction.
75 : */
76 : void initialize_members();
77 :
78 : /**
79 : * Score each translation by the loss function.
80 : */
81 : void score_members();
82 :
83 : /**
84 : * Calculate the mean score.
85 : */
86 : double score_mean();
87 :
88 : /**
89 : * Sort the population using heaps, required for finding a minimum of the loss
90 : * function.
91 : */
92 : void sort_members();
93 :
94 : /**
95 : * Encode a ligand conformation.
96 : */
97 : Vector create_coding();
98 :
99 : /**
100 : * Check if the vector length is out of bounds.
101 : *
102 : * @param[in] v Vector length.
103 : */
104 : bool out_of_bounds(double v);
105 :
106 : /**
107 : * Score a single member.
108 : *
109 : * @param[in] v Member's translation.
110 : */
111 : double score_member(const Vector& v);
112 :
113 : /**
114 : * Print a status.
115 : */
116 : void print_status() const;
117 :
118 : /**
119 : * Select a new population using the roulette selection.
120 : */
121 : void selection_roulette();
122 :
123 : /**
124 : * Perform mating in the population.
125 : *
126 : * @param[in,out] members Population.
127 : */
128 : void mating(std::vector<Member>& members);
129 :
130 : /**
131 : * Mate two members.
132 : *
133 : * @param[in,out] m1 1st member.
134 : * @param[in,out] m2 2nd member.
135 : */
136 : void crossover(Member& m1, Member& m2);
137 :
138 : /**
139 : * Mutate a member.
140 : *
141 : * @param[in,out] m Member.
142 : */
143 : void mutation(Member& m);
144 :
145 : /**
146 : * Mutate the population.
147 : *
148 : * @param[in,out] members Population.
149 : */
150 : void mutation(std::vector<Member>& members);
151 :
152 : protected:
153 : /**
154 : * Local searches to improve global solutions.
155 : */
156 :
157 : /**
158 : * Stochastic hill climbing.
159 : *
160 : * Neighbors with better or equal cost should be accepted, allowing the
161 : * technique to navigate across plateaus in the response surface.
162 : *
163 : * @param[in,out] m Member.
164 : * @param[in] params None.
165 : */
166 : void stochastic_hill_climbing(
167 : Member& m,
168 : /* none */ const std::vector<double>& params = {}
169 : );
170 :
171 : /**
172 : * Random-restart hill climbing.
173 : *
174 : * The algorithm can be restarted and repeated a number of times after it
175 : * converges to provide an improved result.
176 : *
177 : * @param[in,out] m Member.
178 : * @param[in] params Number of restarts.
179 : */
180 : void random_restart_hill_climbing(
181 : Member& m,
182 : /* n_restarts */ const std::vector<double>& params = {10}
183 : );
184 :
185 : /**
186 : * Solis-Wets random walk.
187 : *
188 : * Adaptive random search algorithm was designed to address the limitations of
189 : * the fixed step size. The strategy for adaptive random search is to
190 : * continually approximate the optimal step size required to reach the global
191 : * optimum in the search space. This is achieved by trialling and adopting
192 : * smaller or larger step sizes only if they result in an improvement in the
193 : * search performance.
194 : *
195 : * Very large step sizes are trialled with a much lower frequency. This
196 : * strategy of preferring large moves is intended to allow the technique to
197 : * escape local optima. Smaller step sizes are adopted if no improvement is
198 : * made for an extended period.
199 : *
200 : * @param[in,out] m Member.
201 : * @param[in] params
202 : */
203 : void adaptive_random_search(
204 : Member& m,
205 : /* */ const std::vector<double>& params = {1.0, 1.e-5, 2.0, 2.0, 3.0, 3.0}
206 : );
207 :
208 : /**
209 : * Luus–Jaakola heuristics.
210 : *
211 : * @param[in,out] m Member.
212 : * @param[in] params Bounds.
213 : */
214 : void luus_jaakola(
215 : Member& m,
216 : /* bounds */ const std::vector<double>& params
217 : );
218 :
219 : /**
220 : * Local annealing.
221 : *
222 : * @param[in,out] m Member.
223 : * @param[in] params T, alpha.
224 : */
225 : void annealing(
226 : Member& m,
227 : /* T, alpha */const std::vector<double>& params = {300.0, 0.95}
228 : );
229 :
230 : /**
231 : * Apply local search to members.
232 : *
233 : * @param[in,out] members Population.
234 : */
235 : void local_search(std::vector<Member>& members);
236 :
237 : protected:
238 : /**
239 : * Return an optimal biasing direction.
240 : */
241 : Vector solve();
242 :
243 : public:
244 : /**
245 : * Setters and getters.
246 : */
247 :
248 : unsigned int get_capacity() const;
249 : void set_capacity(unsigned int);
250 :
251 : unsigned int get_coding_len() const;
252 : void set_coding_len(unsigned int);
253 :
254 : unsigned int get_n_local_iterations() const;
255 : void set_n_local_iterations(unsigned int);
256 :
257 : unsigned int get_n_global_iterations() const;
258 : void set_n_global_iterations(unsigned int);
259 :
260 : double get_mutation_rate() const;
261 : void set_mutation_rate(double);
262 :
263 : double get_mating_rate() const;
264 : void set_mating_rate(double);
265 :
266 : double get_cauchy_mean() const;
267 : void set_cauchy_mean(double);
268 :
269 : double get_cauchy_spread() const;
270 : void set_cauchy_spread(double);
271 :
272 : bool is_local_search_on() const;
273 : void local_search_on();
274 : void local_search_off();
275 :
276 : double get_local_search_rate() const;
277 : void set_local_search_rate(double);
278 :
279 : std::string get_local_search_type() const;
280 : void set_local_search_type(const std::string&);
281 :
282 : protected:
283 : //! Population
284 : std::vector<Member> members_;
285 :
286 : //! Bound
287 : double bound_;
288 :
289 : //! Scores
290 : double score_worst_;
291 : double score_best_;
292 : Member member_best_;
293 :
294 : //! If a local search is performed
295 : bool adaptation_;
296 :
297 : protected:
298 : //! Size of population
299 : unsigned int capacity_;
300 : //! Length of coding
301 : unsigned int coding_len_;
302 :
303 : //! Number of local search iterations
304 : unsigned int n_local_iterations_;
305 : //! Number of global search iterations, doomsday
306 : unsigned int n_global_iterations_;
307 :
308 : //! Probability of mutation
309 : double mutation_rate_;
310 : //! Probability of mating
311 : double mating_rate_;
312 :
313 : //! Mean and spread of cauchy sampler
314 : double cauchy_mean_alpha_;
315 : double cauchy_mean_beta_;
316 :
317 : //! If local search is employed in sampling
318 : bool local_search_on_;
319 : //! Rate of local mutation
320 : double local_search_rate_;
321 : //! Type of local search, stochastic_hill_climbing or adaptive_random_search
322 : std::string local_search_type_;
323 : };
324 :
325 6 : void Memetic::initialize_members() {
326 6 : members_.clear();
327 6 : members_.resize(capacity_);
328 :
329 42 : for (size_t i = 0; i < capacity_; ++i) {
330 36 : Member m{};
331 :
332 36 : m.score=0;
333 36 : m.translation=create_coding();
334 :
335 36 : members_.at(i) = m;
336 : }
337 6 : }
338 :
339 30 : void Memetic::score_members() {
340 210 : for (size_t i = 0; i < members_.size(); ++i) {
341 180 : double s = score_member(members_[i].translation);
342 180 : members_[i].score=s;
343 : }
344 30 : }
345 :
346 30 : void Memetic::sort_members() {
347 30 : std::make_heap(
348 : members_.begin(),
349 : members_.end(),
350 : compare
351 : );
352 :
353 30 : std::sort_heap(
354 : members_.begin(),
355 : members_.end(),
356 : compare
357 : );
358 :
359 30 : member_best_ = members_[capacity_ - 1];
360 30 : score_best_ = members_[capacity_ - 1].score;
361 30 : score_worst_ = members_[0].score;
362 30 : }
363 :
364 0 : double Memetic::score_mean() {
365 0 : auto acc = [](double s, const Member& m) { return s + m.score; };
366 :
367 : return std::accumulate(
368 : members_.begin(),
369 : members_.end(),
370 : 0.0,
371 0 : acc) / capacity_;
372 : }
373 :
374 30 : void Memetic::selection_roulette() {
375 30 : std::vector<Member> sel(members_);
376 30 : std::vector<double> rel_scores(capacity_, 0.0);
377 :
378 210 : for (std::size_t i = 0; i < capacity_; ++i) {
379 180 : double r = 1.0 / (members_[i].score + 0.01);
380 180 : rel_scores.at(i) = r;
381 : }
382 :
383 30 : std::vector<double> cum_sum(capacity_, 0.0);
384 30 : std::partial_sum(
385 : rel_scores.begin(),
386 : rel_scores.end(),
387 : cum_sum.begin(),
388 : std::plus<double>()
389 : );
390 :
391 30 : double sum = cum_sum.back();
392 : members_.clear();
393 30 : members_.resize(capacity_);
394 : int chosen = -1;
395 :
396 210 : for (size_t j = 0; j < capacity_; ++j) {
397 : double probability=rnd::next_double(sum);
398 642 : for (size_t i = 0; i < cum_sum.size(); ++i) {
399 642 : if (cum_sum[i] > probability) {
400 180 : chosen = i;
401 :
402 360 : members_.at(j).score = sel.at(chosen).score;
403 180 : members_.at(j).translation = sel.at(chosen).translation;
404 :
405 180 : break;
406 : }
407 : }
408 : }
409 30 : }
410 :
411 4 : void Memetic::crossover(Member& s1, Member& s2) {
412 4 : size_t i = rnd::next_int(1, coding_len_ - 1);
413 :
414 4 : Member z1(s1);
415 4 : Member z2(s2);
416 :
417 12 : for (size_t j = i; j < coding_len_; ++j) {
418 8 : z1.translation[j] = s2.translation[j];
419 8 : z2.translation[j] = s1.translation[j];
420 : }
421 :
422 4 : if (!out_of_bounds(z1.translation.modulo()) && !out_of_bounds(z2.translation.modulo())) {
423 4 : s1 = z1;
424 4 : s2 = z2;
425 : }
426 4 : }
427 :
428 94 : void Memetic::mutation(Member& m) {
429 94 : int which = rnd::next_int(coding_len_);
430 94 : double v = rnd::next_cauchy(cauchy_mean_alpha_, cauchy_mean_beta_);
431 94 : m.translation[which] += v;
432 94 : if (out_of_bounds(m.translation.modulo())) {
433 27 : m.translation[which] -= v;
434 : }
435 94 : }
436 :
437 30 : void Memetic::mutation(std::vector<Member>& m) {
438 210 : for (std::vector<Member>::iterator it = m.begin(), end = m.end(); it != end; ++it) {
439 180 : double r = rnd::next_double();
440 180 : if (r < mutation_rate_) {
441 4 : mutation(*it);
442 : }
443 : }
444 30 : }
445 :
446 9 : void Memetic::stochastic_hill_climbing(
447 : Member& m,
448 : const std::vector<double>& params)
449 : {
450 99 : for (std::size_t i = 0; i < n_local_iterations_; ++i) {
451 90 : Member n;
452 90 : n.translation = m.translation;
453 90 : mutation(n);
454 90 : double score_n = score_member(n.translation);
455 :
456 90 : if (m.score > score_n) {
457 48 : m.translation = n.translation;
458 : }
459 : }
460 9 : }
461 :
462 0 : void Memetic::random_restart_hill_climbing(
463 : Member& m,
464 : const std::vector<double>& params)
465 : {
466 0 : unsigned int n_restarts = static_cast<int>(params[0]);
467 0 : std::vector<Member> s(n_restarts);
468 : unsigned int ndx = 0;
469 0 : double min = m.score;
470 :
471 0 : for (std::size_t r = 0; r < n_restarts; ++r) {
472 0 : Member n = m;
473 0 : stochastic_hill_climbing(n);
474 0 : s[r] = n;
475 :
476 0 : if (min > n.score) {
477 : min = n.score;
478 0 : ndx = r;
479 : }
480 : }
481 :
482 0 : m = s[ndx];
483 0 : }
484 :
485 0 : void Memetic::annealing(
486 : Member& m,
487 : const std::vector<double>& params)
488 : {
489 0 : double T = params[0];
490 0 : double alpha = params[1];
491 :
492 0 : for (std::size_t i = 0; i < n_local_iterations_; ++i) {
493 0 : Member n = m;
494 0 : mutation(n);
495 0 : double score_n = score_member(n.translation);
496 :
497 : double probability = std::min(
498 0 : 1.0,
499 0 : std::exp(-(score_n - m.score) / T)
500 0 : );
501 :
502 0 : double r = rnd::next_double();
503 :
504 0 : if (r < probability) {
505 0 : m = n;
506 : }
507 :
508 0 : T *= alpha;
509 : }
510 0 : }
511 :
512 0 : void Memetic::luus_jaakola(
513 : Member& m,
514 : const std::vector<double>& params)
515 : {
516 : /* TODO */
517 0 : }
518 :
519 0 : void Memetic::adaptive_random_search(
520 : Member& m,
521 : const std::vector<double>& params)
522 : {
523 0 : double rho_start = params[0];
524 0 : double rho_lower_bound = params[1];
525 0 : double ex = params[2];
526 0 : double ct = params[3];
527 0 : int s_ex = static_cast<int>(params[4]);
528 0 : int f_ct = static_cast<int>(params[5]);
529 :
530 : unsigned int k = 0;
531 0 : Vector xk = m.translation;
532 : int ns = 0;
533 : int nf = 0;
534 0 : Vector bk;
535 0 : bk.zero();
536 : double rho_k = rho_start;
537 :
538 0 : while (rho_k > rho_lower_bound && k < n_local_iterations_) {
539 0 : if (ns >= s_ex) {
540 0 : rho_k *= ex;
541 : }
542 0 : else if (nf >= f_ct) {
543 0 : rho_k *= ct;
544 : }
545 :
546 0 : std::vector<double> chiv = rnd::next_double(-rho_k, rho_k, 3);
547 0 : Vector chi = tls::vector2Vector(chiv);
548 0 : Vector tmp;
549 :
550 0 : for (unsigned int i = 0; i < 3; ++i) {
551 0 : tmp[i] = 2.0 * (xk[i] - chi[i]);
552 : }
553 :
554 0 : double score_chi = score_member(chi);
555 0 : double score_xk = score_member(xk);
556 0 : double score_tmp = score_member(tmp);
557 :
558 0 : if (score_chi < score_xk) {
559 0 : ns++;
560 : nf = 0;
561 :
562 0 : for (unsigned int i = 0; i < 3; i++) {
563 0 : bk[i] = 0.2 * bk[i] + 0.4 * (chi[i] - xk[i]);
564 0 : xk[i] = chi[i];
565 : }
566 : }
567 0 : else if (score_tmp < score_xk && score_xk <= score_chi) {
568 0 : ns++;
569 : nf = 0;
570 :
571 0 : for (unsigned int i = 0; i < 3; i++) {
572 0 : bk[i] = bk[i] - 0.4 * (chi[i] - xk[i]);
573 0 : xk[i] = 2.0 * xk[i] - chi[i];
574 : }
575 : }
576 : else {
577 : ns = 0;
578 0 : nf++;
579 :
580 0 : for (unsigned int i = 0; i < 3; i++) {
581 0 : bk[i] = 0.5 * bk[i];
582 : }
583 : }
584 :
585 0 : k++;
586 : }
587 :
588 0 : m.translation = xk;
589 0 : }
590 :
591 30 : void Memetic::local_search(std::vector<Member>& m) {
592 30 : adaptation_ = true;
593 :
594 30 : if (local_search_on_) {
595 105 : for (std::size_t i = 0; i < capacity_; ++i) {
596 90 : double probability = rnd::next_double();
597 :
598 90 : if (probability < local_search_rate_) {
599 9 : if (local_search_type_ == "stochastic_hill_climbing")
600 18 : stochastic_hill_climbing(m[i]);
601 0 : else if (local_search_type_ == "adaptive_random_search")
602 0 : adaptive_random_search(m[i]);
603 0 : else if (local_search_type_ == "random_restart_hill_climbing")
604 0 : random_restart_hill_climbing(m[i]);
605 : }
606 : }
607 : }
608 :
609 30 : adaptation_ = false;
610 30 : }
611 :
612 30 : void Memetic::mating(std::vector<Member>& m) {
613 : std::vector<Member> offspring;
614 :
615 120 : while (m.size() != 0) {
616 90 : int j = rnd::next_int(m.size());
617 90 : int i = rnd::next_int(m.size());
618 :
619 140 : while (i == j) {
620 50 : j=rnd::next_int(m.size());
621 : }
622 :
623 90 : Member m1 = m[i];
624 90 : Member m2 = m[j];
625 :
626 90 : if (i > j) {
627 : m.erase(m.begin() + i);
628 : m.erase(m.begin() + j);
629 : }
630 41 : else if (j > i) {
631 : m.erase(m.begin() + j);
632 : m.erase(m.begin() + i);
633 : }
634 :
635 90 : double probability = rnd::next_double();
636 90 : if (probability < mating_rate_) {
637 4 : crossover(m1, m2);
638 : }
639 :
640 90 : offspring.push_back(m1);
641 90 : offspring.push_back(m2);
642 : }
643 :
644 30 : m = offspring;
645 : offspring.clear();
646 30 : }
647 :
648 36 : Vector Memetic::create_coding() {
649 36 : double s = sampling_radius();
650 : double r = rnd::next_double(s);
651 :
652 36 : return rnd::next_plmd_vector(r);
653 : }
654 :
655 102 : bool Memetic::out_of_bounds(double v) {
656 102 : double s = sampling_radius();
657 :
658 102 : return v > s;
659 : }
660 :
661 270 : double Memetic::score_member(const Vector& coding) {
662 : double action = 0;
663 270 : Vector distance;
664 270 : const unsigned nl_size = neighbor_list_->size();
665 270 : Vector dev = coding;
666 :
667 270 : #pragma omp parallel num_threads(get_n_threads_openmp())
668 : {
669 : #pragma omp for reduction(+:action)
670 : for (unsigned int i = 0; i < nl_size; i++) {
671 : unsigned i0 = neighbor_list_->getClosePair(i).first;
672 : unsigned i1 = neighbor_list_->getClosePair(i).second;
673 :
674 : if (getAbsoluteIndex(i0) == getAbsoluteIndex(i1)) {
675 : continue;
676 : }
677 :
678 : if (pbc_) {
679 : distance = pbcDistance(getPosition(i0) + dev, getPosition(i1));
680 : }
681 : else {
682 : distance = delta(getPosition(i0) + dev, getPosition(i1));
683 : }
684 :
685 : action += pairing(distance.modulo());
686 : }
687 : }
688 :
689 270 : return action;
690 : }
691 :
692 0 : void Memetic::print_status() const {
693 0 : log.printf("Lowest score: %f \n", score_best_);
694 0 : }
695 :
696 6 : Vector Memetic::solve() {
697 6 : initialize_members();
698 :
699 : size_t epoch = 0;
700 36 : while (epoch < n_global_iterations_) {
701 30 : score_members();
702 :
703 30 : selection_roulette();
704 30 : mating(members_);
705 30 : mutation(members_);
706 30 : local_search(members_);
707 :
708 30 : sort_members();
709 :
710 30 : epoch++;
711 : }
712 :
713 6 : return member_best_.translation / member_best_.translation.modulo();
714 : }
715 :
716 : inline unsigned int Memetic::get_capacity() const {
717 : return capacity_;
718 : }
719 :
720 : inline void Memetic::set_capacity(unsigned int capacity) {
721 : capacity_ = capacity;
722 : }
723 :
724 : inline unsigned int Memetic::get_coding_len() const {
725 : return coding_len_;
726 : }
727 :
728 : inline void Memetic::set_coding_len(unsigned int coding_len) {
729 : coding_len_ = coding_len;
730 : }
731 :
732 : inline unsigned int Memetic::get_n_global_iterations() const {
733 : return n_global_iterations_;
734 : }
735 :
736 : inline void Memetic::set_n_global_iterations(unsigned int n_global_iterations) {
737 2 : n_global_iterations_ = n_global_iterations;
738 : }
739 :
740 : inline unsigned int Memetic::get_n_local_iterations() const {
741 : return n_local_iterations_;
742 : }
743 :
744 : inline void Memetic::set_n_local_iterations(unsigned int n_local_iterations) {
745 : n_local_iterations_ = n_local_iterations;
746 : }
747 :
748 : inline double Memetic::get_mating_rate() const {
749 : return mating_rate_;
750 : }
751 :
752 : inline void Memetic::set_mating_rate(double mating_rate) {
753 : mating_rate_ = mating_rate;
754 : }
755 :
756 : inline double Memetic::get_mutation_rate() const {
757 : return mutation_rate_;
758 : }
759 :
760 : inline void Memetic::set_mutation_rate(double mutation_rate) {
761 : mutation_rate_ = mutation_rate;
762 : }
763 :
764 : inline double Memetic::get_cauchy_mean() const {
765 : return cauchy_mean_alpha_;
766 : }
767 :
768 : inline void Memetic::set_cauchy_mean(double cauchy_mean_alpha) {
769 : cauchy_mean_alpha_ = cauchy_mean_alpha;
770 : }
771 :
772 : inline double Memetic::get_cauchy_spread() const {
773 : return cauchy_mean_beta_;
774 : }
775 :
776 : inline void Memetic::set_cauchy_spread(double cauchy_mean_beta) {
777 : cauchy_mean_beta_ = cauchy_mean_beta;
778 : }
779 :
780 : inline bool Memetic::is_local_search_on() const {
781 : return local_search_on_;
782 : }
783 :
784 : inline void Memetic::local_search_on() {
785 : local_search_on_ = true;
786 : }
787 :
788 : inline void Memetic::local_search_off() {
789 : local_search_on_ = false;
790 : }
791 :
792 : inline double Memetic::get_local_search_rate() const {
793 : return local_search_rate_;
794 : }
795 :
796 : inline void Memetic::set_local_search_rate(double local_search_rate) {
797 : local_search_rate_ = local_search_rate;
798 : }
799 :
800 : inline std::string Memetic::get_local_search_type() const {
801 : return local_search_type_;
802 : }
803 :
804 : inline void Memetic::set_local_search_type(const std::string& local_search_type) {
805 : local_search_type_ = local_search_type;
806 : }
807 :
808 : } // namespace maze
809 : } // namespace PLMD
810 :
811 : #endif // __PLUMED_maze_Memetic_h
|