Free cookie consent management tool by TermsFeed Policy Generator

Ignore:
Timestamp:
03/19/10 15:59:37 (14 years ago)
Author:
abeham
Message:

Updated RealVector encoding to use a double matrix as bounds #929

Location:
trunk/sources/HeuristicLab.Encodings.RealVectorEncoding/3.3/Manipulators
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/sources/HeuristicLab.Encodings.RealVectorEncoding/3.3/Manipulators/BreederGeneticAlgorithmManipulator.cs

    r3060 r3123  
    3838  public class BreederGeneticAlgorithmManipulator : RealVectorManipulator {
    3939    private static readonly double[] powerOfTwo = new double[] { 1, 0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625, 0.0078125, 0.00390625, 0.001953125, 0.0009765625, 0.00048828125, 0.000244140625, 0.0001220703125, 0.00006103515625, 0.000030517578125 };
    40     public ValueLookupParameter<DoubleValue> MinimumParameter {
    41       get { return (ValueLookupParameter<DoubleValue>)Parameters["Minimum"]; }
    42     }
    43     public ValueLookupParameter<DoubleValue> MaximumParameter {
    44       get { return (ValueLookupParameter<DoubleValue>)Parameters["Maximum"]; }
     40    public ValueLookupParameter<DoubleMatrix> BoundsParameter {
     41      get { return (ValueLookupParameter<DoubleMatrix>)Parameters["Bounds"]; }
    4542    }
    4643    public ValueLookupParameter<DoubleValue> SearchIntervalFactorParameter {
     
    4845    }
    4946    /// <summary>
    50     /// Initializes a new instance of <see cref="BreederGeneticAlgorithmManipulator"/> with three variable
    51     /// infos (<c>Minimum</c>, <c>Maximum</c> and <c>SearchIntervalFactor</c>).
     47    /// Initializes a new instance of <see cref="BreederGeneticAlgorithmManipulator"/> with two
     48    /// parameters (<c>Bounds</c> and <c>SearchIntervalFactor</c>).
    5249    /// </summary>
    5350    public BreederGeneticAlgorithmManipulator()
    5451      : base() {
    55       Parameters.Add(new ValueLookupParameter<DoubleValue>("Minimum", "The lower bound for each element in the vector."));
    56       Parameters.Add(new ValueLookupParameter<DoubleValue>("Maximum", "The upper bound for each element in the vector."));
     52      Parameters.Add(new ValueLookupParameter<DoubleMatrix>("Bounds", "The lower and upper bounds for each element in the vector."));
    5753      Parameters.Add(new ValueLookupParameter<DoubleValue>("SearchIntervalFactor", "The factor determining the size of the search interval, that will be added/removed to/from the allele selected for manipulation.", new DoubleValue(0.1)));
    5854    }
     
    6359    /// <param name="random">A random number generator.</param>
    6460    /// <param name="vector">The real vector to manipulate.</param>
    65     /// <param name="min">The minimum number of the sampling range for the vector element (inclusive).</param>
    66     /// <param name="max">The maximum number of the sampling range for the vector element (exclusive).</param>
     61    /// <param name="bounds">The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled.</param>
    6762    /// <param name="searchIntervalFactor">The factor determining the size of the search interval.</param>
    68     public static void Apply(IRandom random, RealVector vector, DoubleValue min, DoubleValue max, DoubleValue searchIntervalFactor) {
     63    public static void Apply(IRandom random, RealVector vector, DoubleMatrix bounds, DoubleValue searchIntervalFactor) {
    6964      int length = vector.Length;
    7065      double prob, value;
     
    7267        value = Sigma(random);
    7368      } while (value == 0);
    74       value *= searchIntervalFactor.Value * (max.Value - min.Value);
    7569
    7670      prob = 1.0 / (double)length;
     
    7973      for (int i = 0; i < length; i++) {
    8074        if (random.NextDouble() < prob) {
     75          double range = bounds[i % bounds.Rows, 1] - bounds[i % bounds.Rows, 0];
    8176          if (random.NextDouble() < 0.5) {
    82             vector[i] = vector[i] + value;
     77            vector[i] = vector[i] + value * searchIntervalFactor.Value * range;
    8378          } else {
    84             vector[i] = vector[i] - value;
     79            vector[i] = vector[i] - value * searchIntervalFactor.Value * range;
    8580          }
    8681          wasMutated = true;
     
    9186      if (!wasMutated) {
    9287        int pos = random.Next(length);
     88        double range = bounds[pos % bounds.Rows, 1] - bounds[pos % bounds.Rows, 0];
    9389        if (random.NextDouble() < 0.5) {
    94           vector[pos] = vector[pos] + value;
     90          vector[pos] = vector[pos] + value * searchIntervalFactor.Value * range;
    9591        } else {
    96           vector[pos] = vector[pos] - value;
     92          vector[pos] = vector[pos] - value * searchIntervalFactor.Value * range;
    9793        }
    9894      }
     
    114110
    115111    /// <summary>
    116     /// Checks the parameters Minimum, Maximum, and SearchIntervalFactor and forwards the call to <see cref="Apply(IRandom, RealVector, DoubleValue, DoubleValue, DoubleValue)"/>.
     112    /// Checks the parameters Bounds, and SearchIntervalFactor and forwards the call to <see cref="Apply(IRandom, RealVector, DoubleValue, DoubleValue, DoubleValue)"/>.
    117113    /// </summary>
    118114    /// <param name="random">A random number generator.</param>
    119115    /// <param name="realVector">The real vector to manipulate.</param>
    120116    protected override void Manipulate(IRandom random, RealVector realVector) {
    121       if (MinimumParameter.ActualValue == null) throw new InvalidOperationException("BreederGeneticAlgorithmManipulator: Parameter " + MinimumParameter.ActualName + " could not be found.");
    122       if (MaximumParameter.ActualValue == null) throw new InvalidOperationException("BreederGeneticAlgorithmManipulator: Paraemter " + MaximumParameter.ActualName + " could not be found.");
     117      if (BoundsParameter.ActualValue == null) throw new InvalidOperationException("BreederGeneticAlgorithmManipulator: Parameter " + BoundsParameter.ActualName + " could not be found.");
    123118      if (SearchIntervalFactorParameter.ActualValue == null) throw new InvalidOperationException("BreederGeneticAlgorithmManipulator: Paraemter " + SearchIntervalFactorParameter.ActualName + " could not be found.");
    124       Apply(random, realVector, MinimumParameter.ActualValue, MaximumParameter.ActualValue, SearchIntervalFactorParameter.ActualValue);
     119      Apply(random, realVector, BoundsParameter.ActualValue, SearchIntervalFactorParameter.ActualValue);
    125120    }
    126121  }
  • trunk/sources/HeuristicLab.Encodings.RealVectorEncoding/3.3/Manipulators/MichalewiczNonUniformAllPositionsManipulator.cs

    r3060 r3123  
    3838  public class MichalewiczNonUniformAllPositionsManipulator : RealVectorManipulator {
    3939    /// <summary>
    40     /// The lower bound of the values in the real vector.
     40    /// The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled.
    4141    /// </summary>
    42     public ValueLookupParameter<DoubleValue> MinimumParameter {
    43       get { return (ValueLookupParameter<DoubleValue>)Parameters["Minimum"]; }
    44     }
    45     /// <summary>
    46     /// The upper bound of the values in the real vector.
    47     /// </summary>
    48     public ValueLookupParameter<DoubleValue> MaximumParameter {
    49       get { return (ValueLookupParameter<DoubleValue>)Parameters["Maximum"]; }
     42    public ValueLookupParameter<DoubleMatrix> BoundsParameter {
     43      get { return (ValueLookupParameter<DoubleMatrix>)Parameters["Bounds"]; }
    5044    }
    5145    /// <summary>
     
    7064    /// <summary>
    7165    /// Initializes a new instance of <see cref="MichalewiczNonUniformAllPositionsManipulator"/> with
    72     /// five parameters (<c>Minimum</c>, <c>Maximum</c>, <c>CurrentGeneration</c>,
     66    /// four parameters (<c>Bounds</c>, <c>CurrentGeneration</c>,
    7367    /// <c>MaximumGenerations</c> and <c>GenerationDependency</c>).
    7468    /// </summary>
    7569    public MichalewiczNonUniformAllPositionsManipulator()
    7670      : base() {
    77       Parameters.Add(new ValueLookupParameter<DoubleValue>("Minimum", "Minimum of the sampling range for the vector element (included)"));
    78       Parameters.Add(new ValueLookupParameter<DoubleValue>("Maximum", "Maximum of the sampling range for the vector element (excluded)"));
     71      Parameters.Add(new ValueLookupParameter<DoubleMatrix>("Bounds", "The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled."));
    7972      Parameters.Add(new LookupParameter<IntValue>("Generation", "Current generation of the algorithm"));
    8073      Parameters.Add(new LookupParameter<IntValue>("MaximumGenerations", "Maximum number of generations"));
     
    8982    /// <param name="random">The random number generator.</param>
    9083    /// <param name="vector">The real vector to manipulate.</param>
    91     /// <param name="min">The minimum value of the sampling range for the vector element (inclusive).</param>
    92     /// <param name="max">The maximum value of the sampling range for the vector element (exclusive).</param>
     84    /// <param name="bounds">The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled.</param>
    9385    /// <param name="currentGeneration">The current generation of the algorithm.</param>
    9486    /// <param name="maximumGenerations">Maximum number of generations.</param>
    9587    /// <param name="generationsDependency">Specifies the degree of dependency on the number of generations.</param>
    9688    /// <returns>The manipulated real vector.</returns>
    97     public static void Apply(IRandom random, RealVector vector, DoubleValue min, DoubleValue max, IntValue currentGeneration, IntValue maximumGenerations, DoubleValue generationsDependency) {
     89    public static void Apply(IRandom random, RealVector vector, DoubleMatrix bounds, IntValue currentGeneration, IntValue maximumGenerations, DoubleValue generationsDependency) {
    9890      if (currentGeneration.Value > maximumGenerations.Value) throw new ArgumentException("MichalewiczNonUniformAllPositionManipulator: CurrentGeneration must be smaller or equal than MaximumGeneration", "currentGeneration");
    9991      int length = vector.Length;
     
    10294
    10395      for (int i = 0; i < length; i++) {
     96        double min = bounds[i % bounds.Rows, 0];
     97        double max = bounds[i % bounds.Rows, 1];
    10498        if (random.NextDouble() < 0.5) {
    105           vector[i] = vector[i] + (max.Value - vector[i]) * (1 - Math.Pow(random.NextDouble(), prob));
     99          vector[i] = vector[i] + (max - vector[i]) * (1 - Math.Pow(random.NextDouble(), prob));
    106100        } else {
    107           vector[i] = vector[i] - (vector[i] - min.Value) * (1 - Math.Pow(random.NextDouble(), prob));
     101          vector[i] = vector[i] - (vector[i] - min) * (1 - Math.Pow(random.NextDouble(), prob));
    108102        }
    109103      }
     
    116110    /// <param name="realVector">The real vector that should be manipulated.</param>
    117111    protected override void Manipulate(IRandom random, RealVector realVector) {
    118       if (MinimumParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformAllPositionManipulator: Parameter " + MinimumParameter.ActualName + " could not be found.");
    119       if (MaximumParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformAllPositionManipulator: Parameter " + MaximumParameter.ActualName + " could not be found.");
     112      if (BoundsParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformAllPositionManipulator: Parameter " + BoundsParameter.ActualName + " could not be found.");
    120113      if (GenerationParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformAllPositionManipulator: Parameter " + GenerationParameter.ActualName + " could not be found.");
    121114      if (MaximumGenerationsParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformAllPositionManipulator: Parameter " + MaximumGenerationsParameter.ActualName + " could not be found.");
    122115      if (GenerationDependencyParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformAllPositionManipulator: Parameter " + GenerationDependencyParameter.ActualName + " could not be found.");
    123       Apply(random, realVector, MinimumParameter.ActualValue, MaximumParameter.ActualValue, GenerationParameter.ActualValue, MaximumGenerationsParameter.ActualValue, GenerationDependencyParameter.ActualValue);
     116      Apply(random, realVector, BoundsParameter.ActualValue, GenerationParameter.ActualValue, MaximumGenerationsParameter.ActualValue, GenerationDependencyParameter.ActualValue);
    124117    }
    125118  }
  • trunk/sources/HeuristicLab.Encodings.RealVectorEncoding/3.3/Manipulators/MichalewiczNonUniformOnePositionManipulator.cs

    r3060 r3123  
    3838  public class MichalewiczNonUniformOnePositionManipulator : RealVectorManipulator {
    3939    /// <summary>
    40     /// The lower bound of the values in the real vector.
     40    /// The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled.
    4141    /// </summary>
    42     public ValueLookupParameter<DoubleValue> MinimumParameter {
    43       get { return (ValueLookupParameter<DoubleValue>)Parameters["Minimum"]; }
    44     }
    45     /// <summary>
    46     /// The upper bound of the values in the real vector.
    47     /// </summary>
    48     public ValueLookupParameter<DoubleValue> MaximumParameter {
    49       get { return (ValueLookupParameter<DoubleValue>)Parameters["Maximum"]; }
     42    public ValueLookupParameter<DoubleMatrix> BoundsParameter {
     43      get { return (ValueLookupParameter<DoubleMatrix>)Parameters["Bounds"]; }
    5044    }
    5145    /// <summary>
     
    6963
    7064    /// <summary>
    71     /// Initializes a new instance of <see cref="MichalewiczNonUniformOnePositionManipulator"/> with five
    72     /// parameters (<c>Minimum</c>, <c>Maximum</c>, <c>CurrentGeneration</c>, <c>MaximumGenerations</c>
     65    /// Initializes a new instance of <see cref="MichalewiczNonUniformOnePositionManipulator"/> with four
     66    /// parameters (<c>Bounds</c>, <c>CurrentGeneration</c>, <c>MaximumGenerations</c>
    7367    /// and <c>GenerationDependency</c>).
    7468    /// </summary>
    7569    public MichalewiczNonUniformOnePositionManipulator()
    7670      : base() {
    77       Parameters.Add(new ValueLookupParameter<DoubleValue>("Minimum", "Minimum of the sampling range for the vector element (included)"));
    78       Parameters.Add(new ValueLookupParameter<DoubleValue>("Maximum", "Maximum of the sampling range for the vector element (excluded)"));
     71      Parameters.Add(new ValueLookupParameter<DoubleMatrix>("Bounds", "The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled."));
    7972      Parameters.Add(new LookupParameter<IntValue>("Generation", "Current generation of the algorithm"));
    8073      Parameters.Add(new LookupParameter<IntValue>("MaximumGenerations", "Maximum number of generations"));
     
    8982    /// <param name="random">The random number generator.</param>
    9083    /// <param name="vector">The real vector to manipulate.</param>
    91     /// <param name="min">The minimum value of the sampling range for the vector element (inclusive).</param>
    92     /// <param name="max">The maximum value of the sampling range for the vector element (exclusive).</param>
     84    /// <param name="bounds">The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled.</param>
    9385    /// <param name="currentGeneration">The current generation of the algorithm.</param>
    9486    /// <param name="maximumGenerations">Maximum number of generations.</param>
    9587    /// <param name="generationsDependency">Specifies the degree of dependency on the number of generations.</param>
    9688    /// <returns>The manipulated real vector.</returns>
    97     public static void Apply(IRandom random, RealVector vector, DoubleValue min, DoubleValue max, IntValue currentGeneration, IntValue maximumGenerations, DoubleValue generationsDependency) {
     89    public static void Apply(IRandom random, RealVector vector, DoubleMatrix bounds, IntValue currentGeneration, IntValue maximumGenerations, DoubleValue generationsDependency) {
    9890      if (currentGeneration.Value > maximumGenerations.Value) throw new ArgumentException("MichalewiczNonUniformOnePositionManipulator: CurrentGeneration must be smaller or equal than MaximumGeneration", "currentGeneration");
    9991      int length = vector.Length;
     
    10294      double prob = (1 - Math.Pow(random.NextDouble(), Math.Pow(1 - currentGeneration.Value / maximumGenerations.Value, generationsDependency.Value)));
    10395
     96      double min = bounds[index % bounds.Rows, 0];
     97      double max = bounds[index % bounds.Rows, 1];
     98
    10499      if (random.NextDouble() < 0.5) {
    105         vector[index] = vector[index] + (max.Value - vector[index]) * prob;
     100        vector[index] = vector[index] + (max - vector[index]) * prob;
    106101      } else {
    107         vector[index] = vector[index] - (vector[index] - min.Value) * prob;
     102        vector[index] = vector[index] - (vector[index] - min) * prob;
    108103      }
    109104    }
     
    115110    /// <param name="realVector">The real vector that should be manipulated.</param>
    116111    protected override void Manipulate(IRandom random, RealVector realVector) {
    117       if (MinimumParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformOnePositionManipulator: Parameter " + MinimumParameter.ActualName + " could not be found.");
    118       if (MaximumParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformOnePositionManipulator: Parameter " + MaximumParameter.ActualName + " could not be found.");
     112      if (BoundsParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformOnePositionManipulator: Parameter " + BoundsParameter.ActualName + " could not be found.");
    119113      if (GenerationParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformOnePositionManipulator: Parameter " + GenerationParameter.ActualName + " could not be found.");
    120114      if (MaximumGenerationsParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformOnePositionManipulator: Parameter " + MaximumGenerationsParameter.ActualName + " could not be found.");
    121115      if (GenerationDependencyParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformOnePositionManipulator: Parameter " + GenerationDependencyParameter.ActualName + " could not be found.");
    122       Apply(random, realVector, MinimumParameter.ActualValue, MaximumParameter.ActualValue, GenerationParameter.ActualValue, MaximumGenerationsParameter.ActualValue, GenerationDependencyParameter.ActualValue);
     116      Apply(random, realVector, BoundsParameter.ActualValue, GenerationParameter.ActualValue, MaximumGenerationsParameter.ActualValue, GenerationDependencyParameter.ActualValue);
    123117    }
    124118  }
  • trunk/sources/HeuristicLab.Encodings.RealVectorEncoding/3.3/Manipulators/UniformOnePositionManipulator.cs

    r3060 r3123  
    3333  /// It is implemented as described in Michalewicz, Z. 1999. Genetic Algorithms + Data Structures = Evolution Programs. Third, Revised and Extended Edition, Spring-Verlag Berlin Heidelberg.
    3434  /// </remarks>
    35   [Item("UniformOnePositionManipulator", "Changes a single position in the vector by sampling uniformly from the interval [Minimum, Maximum). It is implemented as described in Michalewicz, Z. 1999. Genetic Algorithms + Data Structures = Evolution Programs. Third, Revised and Extended Edition, Spring-Verlag Berlin Heidelberg.")]
     35  [Item("UniformOnePositionManipulator", "Changes a single position in the vector by sampling uniformly from the interval [Minimum_i, Maximum_i) in dimension i. It is implemented as described in Michalewicz, Z. 1999. Genetic Algorithms + Data Structures = Evolution Programs. Third, Revised and Extended Edition, Spring-Verlag Berlin Heidelberg.")]
    3636  [StorableClass]
    3737  public class UniformOnePositionManipulator : RealVectorManipulator {
    3838    /// <summary>
    39     /// The lower bound of the values in the real vector.
     39    /// The bounds of the values in the real vector.
    4040    /// </summary>
    41     public ValueLookupParameter<DoubleValue> MinimumParameter {
    42       get { return (ValueLookupParameter<DoubleValue>)Parameters["Minimum"]; }
    43     }
    44     /// <summary>
    45     /// The upper bound of the values in the real vector.
    46     /// </summary>
    47     public ValueLookupParameter<DoubleValue> MaximumParameter {
    48       get { return (ValueLookupParameter<DoubleValue>)Parameters["Maximum"]; }
     41    public ValueLookupParameter<DoubleMatrix> BoundsParameter {
     42      get { return (ValueLookupParameter<DoubleMatrix>)Parameters["Bounds"]; }
    4943    }
    5044
    5145    /// <summary>
    52     /// Initializes a new instance of <see cref="UniformOnePositionManipulator"/> with two parameters
    53     /// (<c>Minimum</c> and <c>Maximum</c>).
     46    /// Initializes a new instance of <see cref="UniformOnePositionManipulator"/> with one parameter
     47    /// (<c>Bounds</c>).
    5448    /// </summary>
    5549    public UniformOnePositionManipulator() {
    56       Parameters.Add(new ValueLookupParameter<DoubleValue>("Minimum", "Minimum of the sampling range for the vector element (included)"));
    57       Parameters.Add(new ValueLookupParameter<DoubleValue>("Maximum", "Maximum of the sampling range for the vector element (excluded)"));
     50      Parameters.Add(new ValueLookupParameter<DoubleMatrix>("Bounds", "Lower and upper bound of the positions in the vector."));
    5851    }
    5952
     
    6356    /// <param name="random">A random number generator.</param>
    6457    /// <param name="vector">The real vector to manipulate.</param>
    65     /// <param name="min">The minimum value of the sampling range for
    66     /// the vector element to change (inclusive).</param>
    67     /// <param name="max">The maximum value of the sampling range for
    68     /// the vector element to change (exclusive).</param>
    69     public static void Apply(IRandom random, RealVector vector, DoubleValue min, DoubleValue max) {
     58    /// <param name="bounds">The lower and upper bound (1st and 2nd column) of the positions in the vector. If there are less rows than dimensions, the rows are cycled.</param>
     59    public static void Apply(IRandom random, RealVector vector, DoubleMatrix bounds) {
    7060      int index = random.Next(vector.Length);
    71       vector[index] = min.Value + random.NextDouble() * (max.Value - min.Value);
     61      double min = bounds[index % bounds.Rows, 0];
     62      double max = bounds[index % bounds.Rows, 1];
     63      vector[index] = min + random.NextDouble() * (max - min);
    7264    }
    7365
    7466    /// <summary>
    75     /// Checks if the minimum and maximum parameters are available and forwards the call to <see cref="Apply(IRandom, RealVector, DoubleValue, DoubleValue)"/>.
     67    /// Checks if the bounds parameters is available and forwards the call to <see cref="Apply(IRandom, RealVector, DoubleMatrix)"/>.
    7668    /// </summary>
    7769    /// <param name="random">The random number generator to use.</param>
    7870    /// <param name="realVector">The real vector to manipulate.</param>
    7971    protected override void Manipulate(IRandom random, RealVector realVector) {
    80       if (MinimumParameter.ActualValue == null) throw new InvalidOperationException("UniformOnePositionManipulator: Parameter " + MinimumParameter.ActualName + " could not be found.");
    81       if (MaximumParameter.ActualValue == null) throw new InvalidOperationException("UniformOnePositionManipulator: Parameter " + MaximumParameter.ActualName + " could not be found.");
    82       Apply(random, realVector, MinimumParameter.ActualValue, MaximumParameter.ActualValue);
     72      if (BoundsParameter.ActualValue == null) throw new InvalidOperationException("MichalewiczNonUniformOnePositionManipulator: Parameter " + BoundsParameter.ActualName + " could not be found.");
     73      Apply(random, realVector, BoundsParameter.ActualValue);
    8374    }
    8475  }
Note: See TracChangeset for help on using the changeset viewer.