Free cookie consent management tool by TermsFeed Policy Generator

source: branches/MemPRAlgorithm/HeuristicLab.Algorithms.MemPR/3.3/Permutation/PermutationMemPR.cs @ 14667

Last change on this file since 14667 was 14563, checked in by abeham, 8 years ago

#2701:

  • Tagged unbiased models with property
  • Changed default configuration
  • Added solution distance to breeding, relinking and delinking performance models
  • Changed sampling model to base prediction on average distance in genotype space
  • Changed target for hillclimber and relinking to relative (quality improvement)
  • changed breeding to count cache hits per crossover
File size: 43.2 KB
RevLine 
[14420]1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
[14556]25using System.Runtime.CompilerServices;
[14420]26using System.Threading;
[14450]27using HeuristicLab.Algorithms.MemPR.Interfaces;
28using HeuristicLab.Algorithms.MemPR.Util;
[14420]29using HeuristicLab.Common;
30using HeuristicLab.Core;
[14450]31using HeuristicLab.Encodings.PermutationEncoding;
[14420]32using HeuristicLab.Optimization;
33using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
34using HeuristicLab.PluginInfrastructure;
35using HeuristicLab.Random;
36
[14450]37namespace HeuristicLab.Algorithms.MemPR.Permutation {
38  [Item("MemPR (permutation)", "MemPR implementation for permutations.")]
[14420]39  [StorableClass]
40  [Creatable(CreatableAttribute.Categories.PopulationBasedAlgorithms, Priority = 999)]
[14552]41  public class PermutationMemPR : MemPRAlgorithm<ISingleObjectiveHeuristicOptimizationProblem, Encodings.PermutationEncoding.Permutation, PermutationMemPRPopulationContext, PermutationMemPRSolutionContext> {
[14450]42#if DEBUG
43    private const bool VALIDATE = true;
44#else
45    private const bool VALIDATE = false;
46#endif
47
[14420]48    [StorableConstructor]
[14450]49    protected PermutationMemPR(bool deserializing) : base(deserializing) { }
50    protected PermutationMemPR(PermutationMemPR original, Cloner cloner) : base(original, cloner) { }
51    public PermutationMemPR() {
52      foreach (var trainer in ApplicationManager.Manager.GetInstances<ISolutionModelTrainer<PermutationMemPRPopulationContext>>())
[14420]53        SolutionModelTrainerParameter.ValidValues.Add(trainer);
[14563]54
55      if (SolutionModelTrainerParameter.ValidValues.Count > 0) {
56        var unbiased = SolutionModelTrainerParameter.ValidValues.FirstOrDefault(x => !x.Bias);
57        if (unbiased != null) SolutionModelTrainerParameter.Value = unbiased;
58      }
59
[14450]60      foreach (var localSearch in ApplicationManager.Manager.GetInstances<ILocalSearch<PermutationMemPRSolutionContext>>()) {
61        LocalSearchParameter.ValidValues.Add(localSearch);
[14420]62      }
63    }
64
65    public override IDeepCloneable Clone(Cloner cloner) {
[14450]66      return new PermutationMemPR(this, cloner);
[14420]67    }
68
[14550]69    protected override bool Eq(Encodings.PermutationEncoding.Permutation a, Encodings.PermutationEncoding.Permutation b) {
70      return new PermutationEqualityComparer().Equals(a, b);
[14420]71    }
72
[14450]73    protected override double Dist(ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> a, ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> b) {
74      return Dist(a.Solution, b.Solution);
[14420]75    }
76
[14450]77    private static double Dist(Encodings.PermutationEncoding.Permutation a, Encodings.PermutationEncoding.Permutation b) {
[14466]78      return 1.0 - HammingSimilarityCalculator.CalculateSimilarity(a, b);
[14420]79    }
80
[14450]81    protected override ISolutionSubspace<Encodings.PermutationEncoding.Permutation> CalculateSubspace(IEnumerable<Encodings.PermutationEncoding.Permutation> solutions, bool inverse = false) {
[14552]82      var solutionsIter = solutions.GetEnumerator();
83      if (!solutionsIter.MoveNext()) throw new ArgumentException("Cannot calculate sub-space when no solutions are given.");
84      var first = solutionsIter.Current;
[14450]85
[14552]86      var N = solutionsIter.Current.Length;
87      var type = solutionsIter.Current.PermutationType;
88      var subspace = new bool[N, type == PermutationTypes.Absolute ? 1 : N];
89      switch (type) {
[14450]90        case PermutationTypes.Absolute: {
91            if (inverse) {
92              for (var i = 0; i < subspace.GetLength(0); i++)
93                subspace[i, 0] = true;
94            }
[14552]95            while (solutionsIter.MoveNext()) {
96              var s = solutionsIter.Current;
[14450]97              for (var i = 0; i < s.Length; i++) {
98                if (first[i] != s[i]) subspace[i, 0] = !inverse;
99              }
100            }
101          }
102          break;
103        case PermutationTypes.RelativeDirected: {
104            if (inverse) {
105              for (var i = 0; i < subspace.GetLength(0); i++)
106                for (var j = 0; j < subspace.GetLength(1); j++)
107                  subspace[i, j] = true;
108            }
109            var placedFirst = new int[first.Length];
110            for (var i = 0; i < first.Length; i++) {
111              placedFirst[first[i]] = i;
112            }
[14552]113            while (solutionsIter.MoveNext()) {
114              var s = solutionsIter.Current;
[14450]115              for (var i = 0; i < s.Length; i++) {
116                if (placedFirst[s[i]] - placedFirst[s.GetCircular(i + 1)] != -1)
117                  subspace[i, 0] = !inverse;
118              }
119            }
120          }
121          break;
122        case PermutationTypes.RelativeUndirected: {
123            if (inverse) {
124              for (var i = 0; i < subspace.GetLength(0); i++)
125                for (var j = 0; j < subspace.GetLength(1); j++)
126                  subspace[i, j] = true;
127            }
128            var placedFirst = new int[first.Length];
129            for (var i = 0; i < first.Length; i++) {
130              placedFirst[first[i]] = i;
131            }
[14552]132            while (solutionsIter.MoveNext()) {
133              var s = solutionsIter.Current;
[14450]134              for (var i = 0; i < s.Length; i++) {
135                if (Math.Abs(placedFirst[s[i]] - placedFirst[s.GetCircular(i + 1)]) != 1)
136                  subspace[i, 0] = !inverse;
137              }
138            }
139          }
140          break;
141        default:
[14552]142          throw new ArgumentException(string.Format("Unknown permutation type {0}", type));
[14450]143      }
144      return new PermutationSolutionSubspace(subspace);
145    }
146
[14544]147    protected override void AdaptiveWalk(ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> scope, int maxEvals, CancellationToken token, ISolutionSubspace<Encodings.PermutationEncoding.Permutation> subspace = null) {
[14450]148      var quality = scope.Fitness;
149      try {
[14552]150        TabuWalk(Context.Random, scope.Solution, Context.Evaluate, token, ref quality, maxEvals, subspace != null ? ((PermutationSolutionSubspace)subspace).Subspace : null);
[14450]151      } finally {
152        scope.Fitness = quality;
153      }
154    }
155
[14552]156    public void TabuWalk(IRandom random, Encodings.PermutationEncoding.Permutation perm, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, ref double quality, int maxEvals = int.MaxValue, bool[,] subspace = null) {
[14450]157      switch (perm.PermutationType) {
158        case PermutationTypes.Absolute:
[14552]159          TabuWalkSwap(random, perm, eval, token, ref quality, maxEvals, subspace);
[14450]160          break;
161        case PermutationTypes.RelativeDirected:
[14552]162          TabuWalkShift(random, perm, eval, token, ref quality, maxEvals, subspace);
[14450]163          break;
164        case PermutationTypes.RelativeUndirected:
[14552]165          TabuWalkOpt(random, perm, eval, token, ref quality, maxEvals, subspace);
[14450]166          break;
167        default: throw new ArgumentException(string.Format("Permutation type {0} is not known", perm.PermutationType));
168      }
169      if (VALIDATE && !perm.Validate()) throw new ArgumentException("TabuWalk produced invalid child");
170    }
171
[14552]172    public int TabuWalkSwap(IRandom random, Encodings.PermutationEncoding.Permutation perm, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, ref double quality, int maxEvals = int.MaxValue, bool[,] subspace = null) {
[14453]173      var evaluations = 0;
[14552]174      var maximization = Context.Maximization;
[14453]175      if (double.IsNaN(quality)) {
[14552]176        quality = eval(perm, token);
[14453]177        evaluations++;
178      }
[14450]179      Encodings.PermutationEncoding.Permutation bestOfTheWalk = null;
180      double bestOfTheWalkF = double.NaN;
181      var current = (Encodings.PermutationEncoding.Permutation)perm.Clone();
182      var currentF = quality;
183      var overallImprovement = false;
184      var tabu = new double[current.Length, current.Length];
185      for (var i = 0; i < current.Length; i++) {
186        for (var j = i; j < current.Length; j++) {
[14477]187          tabu[i, j] = tabu[j, i] = maximization ? double.MinValue : double.MaxValue;
[14420]188        }
[14450]189        tabu[i, current[i]] = currentF;
[14420]190      }
[14450]191
[14456]192      var steps = 0;
193      var stepsUntilBestOfWalk = 0;
194      for (var iter = 0; iter < int.MaxValue; iter++) {
[14450]195        var allTabu = true;
196        var bestOfTheRestF = double.NaN;
197        Swap2Move bestOfTheRest = null;
198        var improved = false;
199        foreach (var swap in ExhaustiveSwap2MoveGenerator.Generate(current).Shuffle(random)) {
[14456]200          if (subspace != null && !(subspace[swap.Index1, 0] && subspace[swap.Index2, 0]))
[14450]201            continue;
202
203          var h = current[swap.Index1];
204          current[swap.Index1] = current[swap.Index2];
205          current[swap.Index2] = h;
[14552]206          var q = eval(current, token);
[14453]207          evaluations++;
[14456]208          if (FitnessComparer.IsBetter(maximization, q, quality)) {
[14450]209            overallImprovement = true;
210            quality = q;
211            for (var i = 0; i < current.Length; i++) perm[i] = current[i];
212          }
213          // check if it would not be an improvement to swap these into their positions
[14456]214          var isTabu = !FitnessComparer.IsBetter(maximization, q, tabu[swap.Index1, current[swap.Index1]])
215                    && !FitnessComparer.IsBetter(maximization, q, tabu[swap.Index2, current[swap.Index2]]);
[14450]216          if (!isTabu) allTabu = false;
[14456]217          if (FitnessComparer.IsBetter(maximization, q, currentF) && !isTabu) {
218            if (FitnessComparer.IsBetter(maximization, q, bestOfTheWalkF)) {
[14450]219              bestOfTheWalk = (Encodings.PermutationEncoding.Permutation)current.Clone();
220              bestOfTheWalkF = q;
[14456]221              stepsUntilBestOfWalk = steps;
[14450]222            }
[14456]223            steps++;
[14450]224            improved = true;
225            // perform the move
226            currentF = q;
227            // mark that to move them to their previous position requires to make an improvement
[14456]228            tabu[swap.Index1, current[swap.Index2]] = maximization ? Math.Max(q, tabu[swap.Index1, current[swap.Index2]])
229                                                                   : Math.Min(q, tabu[swap.Index1, current[swap.Index2]]);
230            tabu[swap.Index2, current[swap.Index1]] = maximization ? Math.Max(q, tabu[swap.Index2, current[swap.Index1]])
231                                                                   : Math.Min(q, tabu[swap.Index2, current[swap.Index1]]);
[14450]232          } else { // undo the move
[14456]233            if (!isTabu && FitnessComparer.IsBetter(maximization, q, bestOfTheRestF)) {
[14450]234              bestOfTheRest = swap;
235              bestOfTheRestF = q;
236            }
237            current[swap.Index2] = current[swap.Index1];
238            current[swap.Index1] = h;
239          }
[14456]240          if (evaluations >= maxEvals) break;
[14450]241        }
[14456]242        if (!allTabu && !improved && bestOfTheRest != null) {
243          tabu[bestOfTheRest.Index1, current[bestOfTheRest.Index1]] = maximization ? Math.Max(currentF, tabu[bestOfTheRest.Index1, current[bestOfTheRest.Index1]])
244                                                                                   : Math.Min(currentF, tabu[bestOfTheRest.Index1, current[bestOfTheRest.Index1]]);
245          tabu[bestOfTheRest.Index2, current[bestOfTheRest.Index2]] = maximization ? Math.Max(currentF, tabu[bestOfTheRest.Index2, current[bestOfTheRest.Index2]])
246                                                                                   : Math.Min(currentF, tabu[bestOfTheRest.Index2, current[bestOfTheRest.Index2]]);
[14450]247
248          var h = current[bestOfTheRest.Index1];
249          current[bestOfTheRest.Index1] = current[bestOfTheRest.Index2];
250          current[bestOfTheRest.Index2] = h;
251
252          currentF = bestOfTheRestF;
[14456]253          steps++;
[14450]254        } else if (allTabu) break;
[14456]255        if (evaluations >= maxEvals) break;
[14450]256      }
[14453]257      Context.IncrementEvaluatedSolutions(evaluations);
[14450]258      if (!overallImprovement && bestOfTheWalk != null) {
259        quality = bestOfTheWalkF;
260        for (var i = 0; i < current.Length; i++) perm[i] = bestOfTheWalk[i];
261      }
[14456]262      return stepsUntilBestOfWalk;
[14420]263    }
264
[14552]265    public int TabuWalkShift(IRandom random, Encodings.PermutationEncoding.Permutation perm, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, ref double quality, int maxEvals = int.MaxValue, bool[,] subspace = null) {
[14456]266      return 0;
[14420]267    }
268
[14552]269    public int TabuWalkOpt(IRandom random, Encodings.PermutationEncoding.Permutation perm, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, ref double quality, int maxEvals = int.MaxValue, bool[,] subspace = null) {
270      var maximization = Context.Maximization;
[14453]271      var evaluations = 0;
272      if (double.IsNaN(quality)) {
[14552]273        quality = eval(perm, token);
[14453]274        evaluations++;
275      }
[14450]276      Encodings.PermutationEncoding.Permutation bestOfTheWalk = null;
[14456]277      var bestOfTheWalkF = double.NaN;
[14450]278      var current = (Encodings.PermutationEncoding.Permutation)perm.Clone();
279      var currentF = quality;
280      var overallImprovement = false;
281      var tabu = new double[current.Length, current.Length];
282      for (var i = 0; i < current.Length; i++) {
283        for (var j = i; j < current.Length; j++) {
284          tabu[i, j] = tabu[j, i] = double.MaxValue;
285        }
286        tabu[current[i], current.GetCircular(i + 1)] = currentF;
287        tabu[current.GetCircular(i + 1), current[i]] = currentF;
288      }
[14420]289
[14456]290      var steps = 0;
291      var stepsUntilBestOfWalk = 0;
292      for (var iter = 0; iter < int.MaxValue; iter++) {
[14420]293        var allTabu = true;
294        var bestOfTheRestF = double.NaN;
[14450]295        InversionMove bestOfTheRest = null;
[14420]296        var improved = false;
[14563]297       
[14450]298        foreach (var opt in ExhaustiveInversionMoveGenerator.Generate(current).Shuffle(random)) {
299          var prev = opt.Index1 - 1;
300          var next = (opt.Index2 + 1) % current.Length;
301          if (prev < 0) prev += current.Length;
[14456]302          if (subspace != null && !(subspace[current[prev], current[opt.Index1]] && subspace[current[opt.Index2], current[next]]))
[14450]303            continue;
[14420]304
[14556]305          current.Reverse(opt.Index1, opt.Index2 - opt.Index1 + 1);
[14450]306
[14552]307          var q = eval(current, token);
[14453]308          evaluations++;
[14456]309          if (FitnessComparer.IsBetter(maximization, q, quality)) {
[14450]310            overallImprovement = true;
311            quality = q;
312            for (var i = 0; i < current.Length; i++) perm[i] = current[i];
[14420]313          }
[14450]314          // check if it would not be an improvement to opt these into their positions
[14456]315          var isTabu = !FitnessComparer.IsBetter(maximization, q, tabu[current[prev], current[opt.Index1]])
316                    && !FitnessComparer.IsBetter(maximization, q, tabu[current[opt.Index2], current[next]]);
[14420]317          if (!isTabu) allTabu = false;
[14456]318          if (!isTabu && FitnessComparer.IsBetter(maximization, q, currentF)) {
319            if (FitnessComparer.IsBetter(maximization, q, bestOfTheWalkF)) {
[14450]320              bestOfTheWalk = (Encodings.PermutationEncoding.Permutation)current.Clone();
321              bestOfTheWalkF = q;
[14456]322              stepsUntilBestOfWalk = steps;
[14450]323            }
[14420]324
[14456]325            steps++;
[14420]326            improved = true;
[14450]327            // perform the move
328            currentF = q;
329            // mark that to move them to their previous position requires to make an improvement
[14456]330            if (maximization) {
331              tabu[current[prev], current[opt.Index2]] = Math.Max(q, tabu[current[prev], current[opt.Index2]]);
332              tabu[current[opt.Index2], current[prev]] = Math.Max(q, tabu[current[opt.Index2], current[prev]]);
333              tabu[current[opt.Index1], current[next]] = Math.Max(q, tabu[current[opt.Index1], current[next]]);
334              tabu[current[next], current[opt.Index1]] = Math.Max(q, tabu[current[next], current[opt.Index1]]);
335            } else {
336              tabu[current[prev], current[opt.Index2]] = Math.Min(q, tabu[current[prev], current[opt.Index2]]);
337              tabu[current[opt.Index2], current[prev]] = Math.Min(q, tabu[current[opt.Index2], current[prev]]);
338              tabu[current[opt.Index1], current[next]] = Math.Min(q, tabu[current[opt.Index1], current[next]]);
339              tabu[current[next], current[opt.Index1]] = Math.Min(q, tabu[current[next], current[opt.Index1]]);
340            }
[14420]341          } else { // undo the move
[14456]342            if (!isTabu && FitnessComparer.IsBetter(maximization, q, bestOfTheRestF)) {
[14450]343              bestOfTheRest = opt;
344              bestOfTheRestF = q;
[14420]345            }
[14556]346           
347            current.Reverse(opt.Index1, opt.Index2 - opt.Index1 + 1);
[14420]348          }
[14456]349          if (evaluations >= maxEvals) break;
[14420]350        }
[14456]351        if (!allTabu && !improved && bestOfTheRest != null) {
[14450]352          var prev = bestOfTheRest.Index1 - 1;
353          var next = (bestOfTheRest.Index2 + 1) % current.Length;
354          if (prev < 0) prev += current.Length;
355
[14456]356          if (maximization) {
357            tabu[current[prev], current[bestOfTheRest.Index1]] = Math.Max(currentF, tabu[current[prev], current[bestOfTheRest.Index1]]);
358            tabu[current[bestOfTheRest.Index1], current[prev]] = Math.Max(currentF, tabu[current[bestOfTheRest.Index1], current[prev]]);
359            tabu[current[bestOfTheRest.Index2], current[next]] = Math.Max(currentF, tabu[current[bestOfTheRest.Index2], current[next]]);
360            tabu[current[next], current[bestOfTheRest.Index2]] = Math.Max(currentF, tabu[current[next], current[bestOfTheRest.Index2]]);
361          } else {
362            tabu[current[prev], current[bestOfTheRest.Index1]] = Math.Min(currentF, tabu[current[prev], current[bestOfTheRest.Index1]]);
363            tabu[current[bestOfTheRest.Index1], current[prev]] = Math.Min(currentF, tabu[current[bestOfTheRest.Index1], current[prev]]);
364            tabu[current[bestOfTheRest.Index2], current[next]] = Math.Min(currentF, tabu[current[bestOfTheRest.Index2], current[next]]);
365            tabu[current[next], current[bestOfTheRest.Index2]] = Math.Min(currentF, tabu[current[next], current[bestOfTheRest.Index2]]);
366          }
[14556]367          current.Reverse(bestOfTheRest.Index1, bestOfTheRest.Index2 - bestOfTheRest.Index1 + 1);
[14450]368
369          currentF = bestOfTheRestF;
[14456]370          steps++;
[14420]371        } else if (allTabu) break;
[14456]372        if (evaluations >= maxEvals) break;
[14420]373      }
[14453]374      Context.IncrementEvaluatedSolutions(evaluations);
[14450]375      if (!overallImprovement && bestOfTheWalk != null) {
376        quality = bestOfTheWalkF;
377        for (var i = 0; i < current.Length; i++) perm[i] = bestOfTheWalk[i];
378      }
[14456]379      return stepsUntilBestOfWalk;
[14450]380    }
[14420]381
[14544]382    protected override ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> Breed(ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p1, ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p2, CancellationToken token) {
383      ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> child = null;
[14450]384
385      if (p1.Solution.PermutationType == PermutationTypes.Absolute) {
[14544]386        child = CrossAbsolute(p1, p2, token);
[14450]387      } else if (p1.Solution.PermutationType == PermutationTypes.RelativeDirected) {
[14544]388        child = CrossRelativeDirected(p1, p2, token);
[14450]389      } else if (p1.Solution.PermutationType == PermutationTypes.RelativeUndirected) {
[14544]390        child = CrossRelativeUndirected(p1, p2, token);
[14450]391      } else throw new ArgumentException(string.Format("Unknown permutation type {0}", p1.Solution.PermutationType));
392
[14544]393      if (VALIDATE && !child.Solution.Validate()) throw new ArgumentException("Cross produced invalid child");
394      return child;
[14420]395    }
396
[14544]397    private ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> CrossAbsolute(ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p1, ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p2, CancellationToken token) {
398      var cache = new HashSet<Encodings.PermutationEncoding.Permutation>(new PermutationEqualityComparer());
[14551]399      cache.Add(p1.Solution);
400      cache.Add(p2.Solution);
401
[14563]402      var cacheHits = new Dictionary<int, int>() { { 0, 0 }, { 1, 0 }, { 2, 0 } };
[14551]403      var evaluations = 0;
[14544]404      ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> offspring = null;
[14556]405      var probe = Context.ToScope((Encodings.PermutationEncoding.Permutation)p1.Solution.Clone());
[14551]406      while (evaluations < p1.Solution.Length) {
[14550]407        Encodings.PermutationEncoding.Permutation c = null;
[14563]408        var xochoice = cacheHits.SampleRandom(Context.Random).Key;
[14550]409        switch (xochoice) {
410          case 0: c = CyclicCrossover2.Apply(Context.Random, p1.Solution, p2.Solution); break;
411          case 1: c = PartiallyMatchedCrossover.Apply(Context.Random, p1.Solution, p2.Solution); break;
412          case 2: c = UniformLikeCrossover.Apply(Context.Random, p1.Solution, p2.Solution); break;
413        }
[14544]414        if (cache.Contains(c)) {
[14563]415          cacheHits[xochoice]++;
416          if (cacheHits[xochoice] > 10) {
417            cacheHits.Remove(xochoice);
418            if (cacheHits.Count == 0) break;
419          }
[14544]420          continue;
[14420]421        }
[14556]422        probe.Solution = c;
[14552]423        Context.Evaluate(probe, token);
[14551]424        evaluations++;
[14544]425        cache.Add(c);
426        if (offspring == null || Context.IsBetter(probe, offspring)) {
427          offspring = probe;
428          if (Context.IsBetter(offspring, p1) && Context.IsBetter(offspring, p2))
429            break;
430        }
[14450]431      }
[14551]432      Context.IncrementEvaluatedSolutions(evaluations);
[14552]433      return offspring ?? p1;
[14450]434    }
435
[14544]436    private ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> CrossRelativeDirected(ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p1, ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p2, CancellationToken token) {
437      var cache = new HashSet<Encodings.PermutationEncoding.Permutation>(new PermutationEqualityComparer());
[14551]438      cache.Add(p1.Solution);
439      cache.Add(p2.Solution);
440
[14563]441      var cacheHits = new Dictionary<int, int>() { { 0, 0 }, { 1, 0 }, { 2, 0 } };
[14551]442      var evaluations = 0;
[14544]443      ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> offspring = null;
[14556]444      var probe = Context.ToScope((Encodings.PermutationEncoding.Permutation)p1.Solution.Clone());
445      while (evaluations < p1.Solution.Length) {
[14550]446        Encodings.PermutationEncoding.Permutation c = null;
[14563]447        var xochoice = cacheHits.SampleRandom(Context.Random).Key;
[14550]448        switch (xochoice) {
449          case 0: c = OrderCrossover2.Apply(Context.Random, p1.Solution, p2.Solution); break;
450          case 1: c = PartiallyMatchedCrossover.Apply(Context.Random, p1.Solution, p2.Solution); break;
451          case 2: c = MaximalPreservativeCrossover.Apply(Context.Random, p1.Solution, p2.Solution); break;
452        }
[14544]453        if (cache.Contains(c)) {
[14563]454          cacheHits[xochoice]++;
455          if (cacheHits[xochoice] > 10) {
456            cacheHits.Remove(xochoice);
457            if (cacheHits.Count == 0) break;
458          }
[14544]459          continue;
[14420]460        }
[14556]461        probe.Solution = c;
[14552]462        Context.Evaluate(probe, token);
[14551]463        evaluations++;
[14544]464        cache.Add(c);
465        if (offspring == null || Context.IsBetter(probe, offspring)) {
466          offspring = probe;
467          if (Context.IsBetter(offspring, p1) && Context.IsBetter(offspring, p2))
468            break;
469        }
[14420]470      }
[14551]471      Context.IncrementEvaluatedSolutions(evaluations);
[14552]472      return offspring ?? p1;
[14420]473    }
474
[14544]475    private ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> CrossRelativeUndirected(ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p1, ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> p2, CancellationToken token) {
476      var cache = new HashSet<Encodings.PermutationEncoding.Permutation>(new PermutationEqualityComparer());
[14551]477      cache.Add(p1.Solution);
478      cache.Add(p2.Solution);
479
[14563]480      var cacheHits = new Dictionary<int, int>() { { 0, 0 }, { 1, 0 }, { 2, 0 } };
[14551]481      var evaluations = 0;
[14544]482      ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> offspring = null;
[14556]483      var probe = Context.ToScope((Encodings.PermutationEncoding.Permutation)p1.Solution.Clone());
484      while (evaluations <= p1.Solution.Length) {
[14550]485        Encodings.PermutationEncoding.Permutation c = null;
[14563]486        var xochoice = cacheHits.SampleRandom(Context.Random).Key;
[14550]487        switch (xochoice) {
488          case 0: c = OrderCrossover2.Apply(Context.Random, p1.Solution, p2.Solution); break;
489          case 1: c = EdgeRecombinationCrossover.Apply(Context.Random, p1.Solution, p2.Solution); break;
490          case 2: c = MaximalPreservativeCrossover.Apply(Context.Random, p1.Solution, p2.Solution); break;
491        }
[14544]492        if (cache.Contains(c)) {
[14563]493          cacheHits[xochoice]++;
494          if (cacheHits[xochoice] > 10) {
495            cacheHits.Remove(xochoice);
496            if (cacheHits.Count == 0) break;
497          }
[14544]498          continue;
[14420]499        }
[14556]500        probe.Solution = c;
[14552]501        Context.Evaluate(probe, token);
[14551]502        evaluations++;
[14544]503        cache.Add(c);
504        if (offspring == null || Context.IsBetter(probe, offspring)) {
505          offspring = probe;
506          if (Context.IsBetter(offspring, p1) && Context.IsBetter(offspring, p2))
507            break;
508        }
[14420]509      }
[14551]510      Context.IncrementEvaluatedSolutions(evaluations);
[14552]511      return offspring ?? p1;
[14420]512    }
513
[14544]514    protected override ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> Link(ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> a, ISingleObjectiveSolutionScope<Encodings.PermutationEncoding.Permutation> b, CancellationToken token, bool delink = false) {
[14450]515      double quality;
[14552]516      return Context.ToScope(Relink(Context.Random, a.Solution, b.Solution, Context.Evaluate, token, delink, out quality));
[14450]517    }
518
[14552]519    public Encodings.PermutationEncoding.Permutation Relink(IRandom random, Encodings.PermutationEncoding.Permutation p1, Encodings.PermutationEncoding.Permutation p2, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, bool delink, out double best) {
[14450]520      if (p1.PermutationType != p2.PermutationType) throw new ArgumentException(string.Format("Unequal permutation types {0} and {1}", p1.PermutationType, p2.PermutationType));
521      switch (p1.PermutationType) {
522        case PermutationTypes.Absolute:
[14552]523          return delink ? DelinkSwap(random, p1, p2, eval, token, out best) : RelinkSwap(random, p1, p2, eval, token, out best);
[14450]524        case PermutationTypes.RelativeDirected:
[14552]525          return RelinkShift(random, p1, p2, eval, token, delink, out best);
[14450]526        case PermutationTypes.RelativeUndirected:
[14556]527          return delink ? DelinkOpt(random, p1, p2, eval, token, out best) : RelinkOpt(random, p1, p2, eval, token, out best);
[14450]528        default: throw new ArgumentException(string.Format("Unknown permutation type {0}", p1.PermutationType));
529      }
530    }
531
[14552]532    public Encodings.PermutationEncoding.Permutation RelinkSwap(IRandom random, Encodings.PermutationEncoding.Permutation p1, Encodings.PermutationEncoding.Permutation p2, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, out double best) {
533      var maximization = Context.Maximization;
[14453]534      var evaluations = 0;
[14450]535      var child = (Encodings.PermutationEncoding.Permutation)p1.Clone();
536
537      best = double.NaN;
538      Encodings.PermutationEncoding.Permutation bestChild = null;
539
540      var options = Enumerable.Range(0, child.Length).Where(x => child[x] != p2[x]).ToList();
541      var invChild = new int[child.Length];
542      for (var i = 0; i < child.Length; i++) invChild[child[i]] = i;
[14550]543     
[14450]544      while (options.Count > 0) {
545        int bestOption = -1;
546        var bestChange = double.NaN;
547        for (var j = 0; j < options.Count; j++) {
548          var idx = options[j];
549          if (child[idx] == p2[idx]) {
550            options.RemoveAt(j);
551            j--;
552            continue;
[14420]553          }
[14450]554          Swap(child, invChild[p2[idx]], idx);
[14552]555          var moveF = eval(child, token);
[14453]556          evaluations++;
[14456]557          if (FitnessComparer.IsBetter(maximization, moveF, bestChange)) {
[14450]558            bestChange = moveF;
559            bestOption = j;
[14420]560          }
[14450]561          // undo
562          Swap(child, invChild[p2[idx]], idx);
[14420]563        }
[14450]564        if (!double.IsNaN(bestChange)) {
565          var idx1 = options[bestOption];
566          var idx2 = invChild[p2[idx1]];
567          Swap(child, idx1, idx2);
568          invChild[child[idx1]] = idx1;
569          invChild[child[idx2]] = idx2;
[14456]570          if (FitnessComparer.IsBetter(maximization, bestChange, best)) {
[14450]571            if (Dist(child, p2) > 0) {
572              best = bestChange;
573              bestChild = (Encodings.PermutationEncoding.Permutation)child.Clone();
574            }
575          }
576          options.RemoveAt(bestOption);
[14420]577        }
578      }
[14456]579      if (bestChild == null) {
[14552]580        best = eval(child, token);
[14456]581        evaluations++;
582      }
[14453]583      Context.IncrementEvaluatedSolutions(evaluations);
[14450]584
585      if (VALIDATE && bestChild != null && !bestChild.Validate()) throw new ArgumentException("Relinking produced invalid child");
586      if (VALIDATE && Dist(child, p2) > 0) throw new InvalidOperationException("Child is not equal to p2 after relinking");
[14456]587
[14450]588      return bestChild ?? child;
[14420]589    }
[14450]590
[14552]591    public Encodings.PermutationEncoding.Permutation DelinkSwap(IRandom random, Encodings.PermutationEncoding.Permutation p1, Encodings.PermutationEncoding.Permutation p2, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, out double best) {
592      var maximization = Context.Maximization;
[14453]593      var evaluations = 0;
[14450]594      var child = (Encodings.PermutationEncoding.Permutation)p1.Clone();
595
596      best = double.NaN;
597      Encodings.PermutationEncoding.Permutation bestChild = null;
598
[14550]599      var options = Enumerable.Range(0, child.Length).Where(x => child[x] == p2[x]).ToList();
600     
601      while (options.Count > 0) {
602        int bestOption = -1;
603        int bestCompanion = -1;
604        var bestChange = double.NaN;
605        for (var j = 0; j < options.Count; j++) {
606          var idx = options[j];
607          if (child[idx] != p2[idx]) {
608            options.RemoveAt(j);
609            j--;
610            continue;
611          }
612          for (var k = 0; k < child.Length; k++) {
613            if (k == idx) continue;
614            Swap(child, k, idx);
[14552]615            var moveF = eval(child, token);
[14550]616            evaluations++;
617            if (FitnessComparer.IsBetter(maximization, moveF, bestChange)) {
618              bestChange = moveF;
619              bestOption = j;
620              bestCompanion = k;
621            }
622            // undo
623            Swap(child, k, idx);
624          }
625        }
626        if (!double.IsNaN(bestChange)) {
627          var idx1 = options[bestOption];
628          Swap(child, idx1, bestCompanion);
629          if (FitnessComparer.IsBetter(maximization, bestChange, best)) {
630            if (!Eq(child, p2)) {
631              best = bestChange;
632              bestChild = (Encodings.PermutationEncoding.Permutation)child.Clone();
633            }
634          }
635          options.RemoveAt(bestOption);
636        }
637      }
638      if (bestChild == null) {
[14552]639        best = eval(child, token);
[14550]640        evaluations++;
641      }
642      Context.IncrementEvaluatedSolutions(evaluations);
643
[14551]644      if (VALIDATE && bestChild != null && !bestChild.Validate()) throw new ArgumentException("Delinking produced invalid child");
[14550]645
646      return bestChild ?? child;
647    }
648
[14552]649    public Encodings.PermutationEncoding.Permutation RelinkShift(IRandom random, Encodings.PermutationEncoding.Permutation p1, Encodings.PermutationEncoding.Permutation p2, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, bool delink, out double best) {
650      var maximization = Context.Maximization;
[14550]651      var evaluations = 0;
652      var child = (Encodings.PermutationEncoding.Permutation)p1.Clone();
653
654      best = double.NaN;
655      Encodings.PermutationEncoding.Permutation bestChild = null;
656
[14450]657      var invChild = new int[child.Length];
658      for (var i = 0; i < child.Length; i++) invChild[child[i]] = i;
659
660      var bestChange = double.NaN;
661      do {
662        int bestFrom = -1, bestTo = -1;
663        bestChange = double.NaN;
664        for (var j = 0; j < child.Length; j++) {
665          var c = invChild[p2[j]];
666          var n = invChild[p2.GetCircular(j + 1)];
667          if (n - c == 1 || c == child.Length - 1 && n == 0) continue;
668
669          if (c < n) Shift(child, from: n, to: c + 1);
670          else Shift(child, from: c, to: n);
[14552]671          var moveF = eval(child, token);
[14453]672          evaluations++;
[14456]673          if (FitnessComparer.IsBetter(maximization, moveF, bestChange)) {
[14450]674            bestChange = moveF;
675            bestFrom = c < n ? n : c;
676            bestTo = c < n ? c + 1 : n;
677          }
678          // undo
679          if (c < n) Shift(child, from: c + 1, to: n);
680          else Shift(child, from: n, to: c);
681        }
682        if (!double.IsNaN(bestChange)) {
683          Shift(child, bestFrom, bestTo);
684          for (var i = Math.Min(bestFrom, bestTo); i < Math.Max(bestFrom, bestTo); i++) invChild[child[i]] = i;
[14456]685          if (FitnessComparer.IsBetter(maximization, bestChange, best)) {
[14450]686            best = bestChange;
687            bestChild = (Encodings.PermutationEncoding.Permutation)child.Clone();
688          }
689        }
690      } while (!double.IsNaN(bestChange));
691
[14456]692      if (bestChild == null) {
[14552]693        best = eval(child, token);
[14456]694        evaluations++;
695      }
[14453]696      Context.IncrementEvaluatedSolutions(evaluations);
[14456]697
698      if (VALIDATE && bestChild != null && !bestChild.Validate()) throw new ArgumentException("Relinking produced invalid child");
[14450]699      if (VALIDATE && Dist(child, p2) > 0) throw new InvalidOperationException("Child is not equal to p2 after relinking");
[14456]700
701      return bestChild ?? child;
[14450]702    }
703
[14556]704    public Encodings.PermutationEncoding.Permutation RelinkOpt(IRandom random, Encodings.PermutationEncoding.Permutation p1, Encodings.PermutationEncoding.Permutation p2, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, out double best) {
[14552]705      var maximization = Context.Maximization;
[14453]706      var evaluations = 0;
[14450]707      var child = (Encodings.PermutationEncoding.Permutation)p1.Clone();
708
709      best = double.NaN;
710      Encodings.PermutationEncoding.Permutation bestChild = null;
711
712      var invChild = new int[child.Length];
713      var invP2 = new int[child.Length];
714      for (var i = 0; i < child.Length; i++) {
715        invChild[child[i]] = i;
716        invP2[p2[i]] = i;
717      }
718
719      var bestChange = double.NaN;
720      var moveQueue = new Queue<Tuple<int, int>>();
721      var undoStack = new Stack<Tuple<int, int>>();
722      do {
723        Queue<Tuple<int, int>> bestQueue = null;
724        bestChange = double.NaN;
725        for (var j = 0; j < p2.Length; j++) {
726          if (IsUndirectedEdge(invChild, p2[j], p2.GetCircular(j + 1))) continue;
727
728          var a = invChild[p2[j]];
729          var b = invChild[p2.GetCircular(j + 1)];
730          if (a > b) { var h = a; a = b; b = h; }
731          var aprev = a - 1;
732          var bprev = b - 1;
733          while (IsUndirectedEdge(invP2, child.GetCircular(aprev), child.GetCircular(aprev + 1))) {
734            aprev--;
735          }
736          while (IsUndirectedEdge(invP2, child.GetCircular(bprev), child.GetCircular(bprev + 1))) {
737            bprev--;
738          }
739          var bnext = b + 1;
740          var anext = a + 1;
741          while (IsUndirectedEdge(invP2, child.GetCircular(bnext - 1), child.GetCircular(bnext))) {
742            bnext++;
743          }
744          while (IsUndirectedEdge(invP2, child.GetCircular(anext - 1), child.GetCircular(anext))) {
745            anext++;
746          }
747          aprev++; bprev++; anext--; bnext--;
748
749          if (aprev < a && bnext > b) {
750            if (aprev < 0) {
751              moveQueue.Enqueue(Tuple.Create(a + 1, bnext));
752              moveQueue.Enqueue(Tuple.Create(a + 1, a + 1 + (bnext - b)));
753            } else {
754              moveQueue.Enqueue(Tuple.Create(aprev, b - 1));
755              moveQueue.Enqueue(Tuple.Create(b - 1 - (a - aprev), b - 1));
756            }
757          } else if (aprev < a && bnext == b && bprev == b) {
758            moveQueue.Enqueue(Tuple.Create(a + 1, b));
759          } else if (aprev < a && bprev < b) {
760            moveQueue.Enqueue(Tuple.Create(a + 1, b));
761          } else if (aprev == a && anext == a && bnext > b) {
762            moveQueue.Enqueue(Tuple.Create(a, b - 1));
763          } else if (aprev == a && anext == a && bnext == b && bprev == b) {
764            moveQueue.Enqueue(Tuple.Create(a, b - 1));
765          } else if (aprev == a && anext == a && bprev < b) {
766            moveQueue.Enqueue(Tuple.Create(a + 1, b));
767          } else if (anext > a && bnext > b) {
768            moveQueue.Enqueue(Tuple.Create(a, b - 1));
769          } else if (anext > a && bnext == b && bprev == b) {
770            moveQueue.Enqueue(Tuple.Create(a, b - 1));
771          } else /*if (anext > a && bprev < b)*/ {
772            moveQueue.Enqueue(Tuple.Create(a, bprev - 1));
773            moveQueue.Enqueue(Tuple.Create(bprev, b));
774          }
775
776          while (moveQueue.Count > 0) {
777            var m = moveQueue.Dequeue();
778            Opt(child, m.Item1, m.Item2);
779            undoStack.Push(m);
780          }
[14552]781          var moveF = eval(child, token);
[14453]782          evaluations++;
[14456]783          if (FitnessComparer.IsBetter(maximization, moveF, bestChange)) {
[14450]784            bestChange = moveF;
785            bestQueue = new Queue<Tuple<int, int>>(undoStack.Reverse());
786          }
787          // undo
788          while (undoStack.Count > 0) {
789            var m = undoStack.Pop();
790            Opt(child, m.Item1, m.Item2);
791          }
792        }
793        if (!double.IsNaN(bestChange)) {
794          while (bestQueue.Count > 0) {
795            var m = bestQueue.Dequeue();
796            Opt(child, m.Item1, m.Item2);
[14556]797            for (var i = m.Item1; i <= m.Item2; i++) invChild[child[i]] = i;
[14450]798          }
[14456]799          if (FitnessComparer.IsBetter(maximization, bestChange, best)) {
[14450]800            best = bestChange;
801            bestChild = (Encodings.PermutationEncoding.Permutation)child.Clone();
802          }
803        }
804      } while (!double.IsNaN(bestChange));
805
[14456]806      if (bestChild == null) {
[14552]807        best = eval(child, token);
[14456]808        evaluations++;
809      }
[14453]810      Context.IncrementEvaluatedSolutions(evaluations);
811     
[14456]812      if (VALIDATE && bestChild != null && !bestChild.Validate()) throw new ArgumentException("Relinking produced invalid child");
[14450]813      if (VALIDATE && Dist(child, p2) > 0) throw new InvalidOperationException("Child is not equal to p2 after relinking");
[14456]814      return bestChild ?? child;
[14450]815    }
816
[14556]817    public Encodings.PermutationEncoding.Permutation DelinkOpt(IRandom random, Encodings.PermutationEncoding.Permutation p1, Encodings.PermutationEncoding.Permutation p2, Func<Encodings.PermutationEncoding.Permutation, CancellationToken, double> eval, CancellationToken token, out double best) {
818      var evaluations = 0;
819      var child = (Encodings.PermutationEncoding.Permutation)p1.Clone();
820
821      best = double.NaN;
822      Encodings.PermutationEncoding.Permutation bestChild = null;
823
824      var invChild = new int[child.Length];
825      var invP2 = new int[child.Length];
826      for (var i = 0; i < child.Length; i++) {
827        invChild[child[i]] = i;
828        invP2[p2[i]] = i;
829      }
830
831      var order = Enumerable.Range(0, p2.Length).Where(x => IsUndirectedEdge(invP2, child[x], child.GetCircular(x + 1))).Shuffle(Context.Random).ToList();
832      while (order.Count > 0) {
833        var idx = order.First();
834        var bestChange = double.NaN;
835        var bestIdx = -1;
836        for (var m = 0; m < p2.Length; m++) {
837          if (Math.Abs(m - idx) <= 1 || Math.Abs(m - idx) >= p2.Length - 2) continue;
838          if (m < idx) {
839            if (IsUndirectedEdge(invP2, child.GetCircular(m - 1), child[idx])
840              || IsUndirectedEdge(invP2, child[m], child.GetCircular(idx + 1))) continue;
841            Opt(child, m, idx);
842            var moveF = eval(child, token);
843            evaluations++;
844            if (Context.IsBetter(moveF, bestChange)) {
845              bestChange = moveF;
846              bestIdx = m;
847            }
848            // undo
849            Opt(child, m, idx);
850          } else {
851            if (IsUndirectedEdge(invP2, child[idx], child[m])
852              || IsUndirectedEdge(invP2, child.GetCircular(idx + 1), child.GetCircular(m + 1))) continue;
853            Opt(child, idx + 1, m);
854            var moveF = eval(child, token);
855            evaluations++;
856            if (Context.IsBetter(moveF, bestChange)) {
857              bestChange = moveF;
858              bestIdx = m;
859            }
860            // undo
861            Opt(child, idx + 1, m);
862          }
863        }
864        if (bestIdx >= 0) {
865          if (bestIdx > idx)
866            Opt(child, idx + 1, bestIdx);
867          else Opt(child, bestIdx, idx);
868          for (var i = Math.Min(idx, bestIdx); i <= Math.Max(idx, bestIdx); i++)
869            invChild[child[i]] = i;
870
871          order = Enumerable.Range(0, p2.Length).Where(x => IsUndirectedEdge(invP2, child[x], child.GetCircular(x + 1))).Shuffle(Context.Random).ToList();
872          if (Context.IsBetter(bestChange, best)) {
873            best = bestChange;
874            bestChild = (Encodings.PermutationEncoding.Permutation)child.Clone();
875          }
876        }
877      }
878
879      if (bestChild == null) {
880        best = eval(child, token);
881        evaluations++;
882      }
883      Context.IncrementEvaluatedSolutions(evaluations);
884
885      if (VALIDATE && bestChild != null && !bestChild.Validate()) throw new ArgumentException("Delinking produced invalid child");
886      if (VALIDATE && Dist(child, p2) < 1) throw new InvalidOperationException("Child is not different from p2 after delinking");
887      return bestChild ?? child;
888    }
889
[14450]890    private static bool IsUndirectedEdge(int[] invP, int a, int b) {
891      var d = Math.Abs(invP[a] - invP[b]);
892      return d == 1 || d == invP.Length - 1;
893    }
894
895    private static void Swap(Encodings.PermutationEncoding.Permutation child, int from, int to) {
896      Swap2Manipulator.Apply(child, from, to);
897    }
898
899    private static void Shift(Encodings.PermutationEncoding.Permutation child, int from, int to) {
900      TranslocationManipulator.Apply(child, from, from, to);
901    }
902
[14556]903    [MethodImpl(MethodImplOptions.AggressiveInlining)]
[14450]904    private static void Opt(Encodings.PermutationEncoding.Permutation child, int from, int to) {
[14556]905      if (from > to) child.Reverse(to, from - to + 1);
906      else child.Reverse(from, to - from + 1);
[14450]907    }
[14420]908  }
909}
Note: See TracBrowser for help on using the repository browser.