Free cookie consent management tool by TermsFeed Policy Generator

Changeset 5598


Ignore:
Timestamp:
03/02/11 22:33:30 (14 years ago)
Author:
abeham
Message:

#1330

  • Updated solution view
  • Made distance matrix a mandatory parameter (solution instances are small anyway)
  • Added and set best known solution if available for a QAPLIB instance
Location:
branches/QAP
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • branches/QAP/HeuristicLab.Problems.QuadraticAssignment.Views/3.3/QAPAssignmentView.Designer.cs

    r5583 r5598  
    3333      this.assignmentGroupBox = new System.Windows.Forms.GroupBox();
    3434      this.assignmentViewHost = new HeuristicLab.MainForm.WindowsForms.ViewHost();
     35      this.weightsRadioButton = new System.Windows.Forms.RadioButton();
     36      this.distancesRadioButton = new System.Windows.Forms.RadioButton();
    3537      ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
    3638      this.splitContainer.Panel1.SuspendLayout();
     
    108110      // visualizationTabPage
    109111      //
     112      this.visualizationTabPage.Controls.Add(this.distancesRadioButton);
     113      this.visualizationTabPage.Controls.Add(this.weightsRadioButton);
    110114      this.visualizationTabPage.Controls.Add(this.pictureBox);
    111115      this.visualizationTabPage.Location = new System.Drawing.Point(4, 22);
     
    124128      this.pictureBox.BackColor = System.Drawing.Color.White;
    125129      this.pictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
    126       this.pictureBox.Location = new System.Drawing.Point(6, 6);
     130      this.pictureBox.Location = new System.Drawing.Point(84, 6);
    127131      this.pictureBox.Name = "pictureBox";
    128       this.pictureBox.Size = new System.Drawing.Size(516, 269);
     132      this.pictureBox.Size = new System.Drawing.Size(438, 269);
    129133      this.pictureBox.TabIndex = 0;
    130134      this.pictureBox.TabStop = false;
     
    170174      this.assignmentViewHost.ViewsLabelVisible = true;
    171175      this.assignmentViewHost.ViewType = null;
     176      //
     177      // weightsRadioButton
     178      //
     179      this.weightsRadioButton.AutoSize = true;
     180      this.weightsRadioButton.Location = new System.Drawing.Point(6, 29);
     181      this.weightsRadioButton.Name = "weightsRadioButton";
     182      this.weightsRadioButton.Size = new System.Drawing.Size(64, 17);
     183      this.weightsRadioButton.TabIndex = 1;
     184      this.weightsRadioButton.Text = "Weights";
     185      this.weightsRadioButton.UseVisualStyleBackColor = true;
     186      this.weightsRadioButton.CheckedChanged += new System.EventHandler(this.radioButton_CheckedChanged);
     187      //
     188      // distancesRadioButton
     189      //
     190      this.distancesRadioButton.AutoSize = true;
     191      this.distancesRadioButton.Checked = true;
     192      this.distancesRadioButton.Location = new System.Drawing.Point(6, 6);
     193      this.distancesRadioButton.Name = "distancesRadioButton";
     194      this.distancesRadioButton.Size = new System.Drawing.Size(72, 17);
     195      this.distancesRadioButton.TabIndex = 2;
     196      this.distancesRadioButton.TabStop = true;
     197      this.distancesRadioButton.Text = "Distances";
     198      this.distancesRadioButton.UseVisualStyleBackColor = true;
     199      this.distancesRadioButton.CheckedChanged += new System.EventHandler(this.radioButton_CheckedChanged);
    172200      //
    173201      // QAPAssignmentView
     
    185213      this.tabControl.ResumeLayout(false);
    186214      this.visualizationTabPage.ResumeLayout(false);
     215      this.visualizationTabPage.PerformLayout();
    187216      ((System.ComponentModel.ISupportInitialize)(this.pictureBox)).EndInit();
    188217      this.valueTabPage.ResumeLayout(false);
     
    203232    private System.Windows.Forms.GroupBox assignmentGroupBox;
    204233    private MainForm.WindowsForms.ViewHost assignmentViewHost;
     234    private System.Windows.Forms.RadioButton distancesRadioButton;
     235    private System.Windows.Forms.RadioButton weightsRadioButton;
    205236  }
    206237}
  • branches/QAP/HeuristicLab.Problems.QuadraticAssignment.Views/3.3/QAPAssignmentView.cs

    r5583 r5598  
    3737  [Content(typeof(QAPAssignment), true)]
    3838  public sealed partial class QAPAssignmentView : ItemView {
     39    private Bitmap bitmap;
     40
    3941    public new QAPAssignment Content {
    4042      get { return (QAPAssignment)base.Content; }
     
    8284        if (Content == null) {
    8385          pictureBox.Image = null;
     86          if (bitmap != null) bitmap.Dispose();
     87          bitmap = null;
    8488        } else {
    85           bool drawDistances = false;
     89          Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
     90          bool drawDistances = distancesRadioButton.Checked;
    8691          DoubleMatrix coordinates = Content.Coordinates;
    8792          DoubleMatrix distances = Content.Distances;
     93
    8894          if ((coordinates == null || coordinates.Rows == 0)
    89             && (distances == null || distances.Rows == 0)) return;
    90           else if ((coordinates == null || coordinates.Rows == 0)
    91             && Content.ViewCoordinates == null) {
    92             coordinates = new DoubleMatrix(distances.Rows, 2);
    93             int columns = (int)Math.Ceiling(Math.Sqrt(coordinates.Rows));
    94             int x = 0, y = 0;
    95             for (int i = 0; i < coordinates.Rows; i++) {
    96               coordinates[i, 0] = 10 * x;
    97               coordinates[i, 1] = 10 * y;
    98               if (++x > columns) {
    99                 y++;
    100                 x = 0;
    101               }
    102             }
    103             Content.ViewCoordinates = coordinates;
    104             drawDistances = true;
    105           } else if ((coordinates == null || coordinates.Rows == 0)
    106             && Content.ViewCoordinates != null) {
    107             coordinates = Content.ViewCoordinates;
    108             drawDistances = true;
    109           }
    110 
    111           DoubleMatrix weights = Content.Weights;
    112           Permutation permutation = Content.Assignment;
    113           Bitmap bitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
    114 
    115           if ((coordinates != null) && (coordinates.Rows > 0) && (coordinates.Columns == 2)) {
     95            && (distances == null || distances.Rows == 0)) {
     96            using (Graphics g = Graphics.FromImage(newBitmap)) {
     97              string str = "No coordinates and no distance matrix specified.";
     98              SizeF strSize = g.MeasureString(str, Font);
     99              g.DrawString(str, Font, Brushes.Black, (float)(newBitmap.Width - strSize.Width) / 2.0f, (float)(newBitmap.Height - strSize.Height) / 2.0f);
     100            }
     101          } else {
     102            if ((coordinates == null || coordinates.Rows == 0)
     103              && Content.ViewCoordinates == null) {
     104              coordinates = new DoubleMatrix(distances.Rows, 2);
     105              double rad = (2 * Math.PI) / coordinates.Rows;
     106              for (int i = 0; i < coordinates.Rows; i++) {
     107                coordinates[i, 0] = 10 * Math.Cos(rad * i);
     108                coordinates[i, 1] = 10 * Math.Sin(rad * i);
     109              }
     110              Content.ViewCoordinates = coordinates;
     111            } else if ((coordinates == null || coordinates.Rows == 0)
     112              && Content.ViewCoordinates != null) {
     113              coordinates = Content.ViewCoordinates;
     114            }
     115
     116            DoubleMatrix weights = Content.Weights;
     117            Permutation assignment = Content.Assignment;
     118
    116119            double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;
     120            double maxWeight = double.MinValue, maxDistance = double.MinValue;
    117121            for (int i = 0; i < coordinates.Rows; i++) {
    118122              if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];
     
    120124              if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];
    121125              if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];
     126
     127              for (int j = i + 1; j < coordinates.Rows; j++) {
     128                if (weights[i, j] + weights[j, i] > maxWeight)
     129                  maxWeight = weights[i, j] + weights[j, i];
     130
     131                if (distances[i, j] > maxDistance)
     132                  maxDistance = distances[i, j];
     133                else if (distances[j, i] > maxDistance)
     134                  maxDistance = distances[j, i];
     135              }
    122136            }
    123137
     
    129143            for (int i = 0; i < coordinates.Rows; i++)
    130144              points[i] = new Point(border + ((int)((coordinates[i, 0] - xMin) * xStep)),
    131                                     bitmap.Height - (border + ((int)((coordinates[i, 1] - yMin) * yStep))));
    132 
    133             using (Graphics graphics = Graphics.FromImage(bitmap)) {
    134               if ((permutation != null) && (permutation.Length == coordinates.Rows) && (permutation.Validate())) {
    135                 for (int i = 0; i < permutation.Length; i++) {
    136                   for (int j = 0; j < permutation.Length; j++) {
    137                     if (i != j && weights[i, j] > 0) {
    138                       graphics.DrawLine(Pens.Black, points[permutation[i]], points[permutation[j]]);
    139                       float midX = ((points[permutation[i]].X + points[permutation[j]].X) / 2.0f) + 5;
    140                       float midY = ((points[permutation[i]].Y + points[permutation[j]].Y) / 2.0f) - 5;
    141                       graphics.DrawString(weights[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat), Font, Brushes.Black, midX, midY);
     145                                    newBitmap.Height - (border + ((int)((coordinates[i, 1] - yMin) * yStep))));
     146
     147            Random rand = new Random();
     148            using (Graphics graphics = Graphics.FromImage(newBitmap)) {
     149              graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
     150              graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
     151              if ((assignment != null) && (assignment.Length == coordinates.Rows) && (assignment.Validate())) {
     152                for (int i = 0; i < assignment.Length - 1; i++) {
     153                  for (int j = i + 1; j < assignment.Length; j++) {
     154                    Point start = points[assignment[i]], end = points[assignment[j]];
     155                    string caption = String.Empty;
     156                    double d = Math.Max(distances[i, j], distances[j, i]);
     157                    if (drawDistances) {
     158                      float width = (float)Math.Ceiling(5.0 * d / maxDistance);
     159                      graphics.DrawLine(new Pen(Color.IndianRed, width), start, end);
     160                      if (distances[i, j] != distances[j, i])
     161                        caption = distances[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat)
     162                          + " / " + distances[j, i].ToString(CultureInfo.InvariantCulture.NumberFormat);
     163                      else
     164                        caption = distances[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat);
     165                    } else {
     166                      double w = weights[i, j] + weights[j, i];
     167                      if (w > 0) {
     168                        float width = (float)Math.Ceiling(5.0 * w / maxWeight);
     169                        graphics.DrawLine(new Pen(Color.MediumBlue, width), start, end);
     170                        caption = w.ToString(CultureInfo.InvariantCulture.NumberFormat);
     171                      }
     172                      if (!String.IsNullOrEmpty(caption)) {
     173                        double r = rand.NextDouble();
     174                        while (r < 0.2 || r > 0.8) r = rand.NextDouble();
     175                        float x = (float)(start.X + (end.X - start.X) * r + 5);
     176                        float y = (float)(start.Y + (end.Y - start.Y) * r + 5);
     177                        graphics.DrawString(caption, Font, Brushes.Black, x, y);
     178                      }
    142179                    }
    143180                  }
    144181                }
    145182              }
    146               for (int i = 0; i < points.Length; i++)
    147                 graphics.FillRectangle(Brushes.Red, points[i].X - 2, points[i].Y - 2, 6, 6);
     183              for (int i = 0; i < points.Length; i++) {
     184                Point p = new Point(points[assignment[i]].X - 3, points[assignment[i]].Y - 3);
     185                graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8);
     186                graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10);
     187              }
    148188            }
    149189          }
    150           pictureBox.Image = bitmap;
     190          pictureBox.Image = newBitmap;
     191          if (bitmap != null) bitmap.Dispose();
     192          bitmap = newBitmap;
    151193        }
    152194      }
     
    163205            GenerateImage();
    164206            break;
    165           case "Permutation":
     207          case "Assignment":
    166208            GenerateImage();
    167209            assignmentViewHost.Content = Content.Assignment;
     
    178220      GenerateImage();
    179221    }
     222
     223    private void radioButton_CheckedChanged(object sender, EventArgs e) {
     224      GenerateImage();
     225    }
    180226  }
    181227}
  • branches/QAP/HeuristicLab.Problems.QuadraticAssignment/3.3/Evaluators/QAPEvaluator.cs

    r5562 r5598  
    2020#endregion
    2121
    22 using System;
    2322using HeuristicLab.Common;
    2423using HeuristicLab.Core;
     
    3534    public ILookupParameter<Permutation> PermutationParameter {
    3635      get { return (ILookupParameter<Permutation>)Parameters["Permutation"]; }
    37     }
    38     public ILookupParameter<BoolValue> UseDistanceMatrixParameter {
    39       get { return (ILookupParameter<BoolValue>)Parameters["UseDistanceMatrix"]; }
    4036    }
    4137    public ILookupParameter<DoubleMatrix> DistanceMatrixParameter {
     
    5753    public QAPEvaluator() {
    5854      Parameters.Add(new LookupParameter<Permutation>("Permutation", "The permutation that represents the current solution."));
    59       Parameters.Add(new ValueLookupParameter<BoolValue>("UseDistanceMatrix", "True if the distance matrix should be used, false if the distances should be caluclated by taking the euclidean distance between the coordinates."));
    6055      Parameters.Add(new LookupParameter<DoubleMatrix>("DistanceMatrix", "The distance matrix that contains the distances between the locations."));
    6156      Parameters.Add(new LookupParameter<DoubleMatrix>("Coordinates", "The coordinates in case the distance matrix should not be used."));
     
    6863    }
    6964
    70     public static double Apply(Permutation assignment, DoubleMatrix weights, Func<int, int, double> distance) {
     65    public static double Apply(Permutation assignment, DoubleMatrix weights, DoubleMatrix distances) {
    7166      double quality = 0;
    7267      for (int i = 0; i < assignment.Length; i++) {
    7368        for (int j = 0; j < assignment.Length; j++) {
    74           quality += weights[i, j] * distance(assignment[i], assignment[j]);
     69          quality += weights[i, j] * distances[assignment[i], assignment[j]];
    7570        }
    7671      }
     
    8075    public override IOperation Apply() {
    8176      Permutation assignment = PermutationParameter.ActualValue;
    82       bool useDistanceMatrix = UseDistanceMatrixParameter.ActualValue.Value;
    8377      DoubleMatrix weights = WeightsParameter.ActualValue;
    84       double quality;
     78      DoubleMatrix distanceMatrix = DistanceMatrixParameter.ActualValue;
    8579
    86       if (useDistanceMatrix) {
    87         DoubleMatrix distanceMatrix = DistanceMatrixParameter.ActualValue;
    88         quality = Apply(assignment, weights, (x, y) => distanceMatrix[x, y]);
    89       } else {
    90         DoubleMatrix coordinates = CoordinatesParameter.ActualValue;
    91         quality = Apply(assignment, weights, (x, y) => Distance(coordinates, x, y));
    92       }
    93 
     80      double quality = Apply(assignment, weights, distanceMatrix);
    9481      QualityParameter.ActualValue = new DoubleValue(quality);
    9582
    9683      return base.Apply();
    9784    }
    98 
    99     private double Distance(DoubleMatrix coordinates, int row1, int row2) {
    100       double dx = coordinates[row1, 0] - coordinates[row2, 0];
    101       double dy = coordinates[row1, 1] - coordinates[row2, 1];
    102       return Math.Sqrt(dx * dx + dy * dy);
    103     }
    10485  }
    10586}
  • branches/QAP/HeuristicLab.Problems.QuadraticAssignment/3.3/HeuristicLab.Problems.QuadraticAssignment-3.3.csproj

    r5583 r5598  
    144144    <Compile Include="Evaluators\QAPEvaluator.cs" />
    145145    <Compile Include="Interfaces\IQAPEvaluator.cs" />
     146    <Compile Include="Parsers\QAPLIBSolutionParser.cs" />
    146147    <Compile Include="Parsers\QAPLIBParser.cs" />
    147148    <Compile Include="QAPAssignment.cs" />
  • branches/QAP/HeuristicLab.Problems.QuadraticAssignment/3.3/Interfaces/IQAPEvaluator.cs

    r5562 r5598  
    2828  public interface IQAPEvaluator : ISingleObjectiveEvaluator {
    2929    ILookupParameter<Permutation> PermutationParameter { get; }
    30     ILookupParameter<BoolValue> UseDistanceMatrixParameter { get; }
    3130    ILookupParameter<DoubleMatrix> DistanceMatrixParameter { get; }
    3231    ILookupParameter<DoubleMatrix> CoordinatesParameter { get; }
  • branches/QAP/HeuristicLab.Problems.QuadraticAssignment/3.3/QuadraticAssignmentProblem.cs

    r5583 r5598  
    5959      get { return (ValueParameter<DoubleMatrix>)Parameters["DistanceMatrix"]; }
    6060    }
    61     public ValueParameter<BoolValue> UseDistanceMatrixParameter {
    62       get { return (ValueParameter<BoolValue>)Parameters["UseDistanceMatrix"]; }
    63     }
    64 
    6561    #endregion
    6662
     
    8177      get { return DistanceMatrixParameter.Value; }
    8278      set { DistanceMatrixParameter.Value = value; }
    83     }
    84     public BoolValue UseDistanceMatrix {
    85       get { return UseDistanceMatrixParameter.Value; }
    86       set { UseDistanceMatrixParameter.Value = value; }
    8779    }
    8880
     
    112104      : base() {
    113105      Parameters.Add(new ValueParameter<Permutation>("BestKnownSolution", "The best known solution which is updated whenever a new better solution is found or may be the optimal solution if it is known beforehand.", null));
    114       Parameters.Add(new ValueParameter<DoubleMatrix>("Coordinates", "The coordinates of the locations."));
     106      Parameters.Add(new ValueParameter<DoubleMatrix>("Coordinates", "The coordinates of the locations. If this is changed the distance matrix is calculated automatically using the euclidean distance."));
    115107      Parameters.Add(new ValueParameter<DoubleMatrix>("Weights", "The strength of the connection between the facilities.", new DoubleMatrix(5, 5)));
    116108      Parameters.Add(new ValueParameter<DoubleMatrix>("DistanceMatrix", "The distance matrix which can either be specified directly without the coordinates, or can be calculated automatically from the coordinates.", new DoubleMatrix(5, 5)));
    117       Parameters.Add(new ValueParameter<BoolValue>("UseDistanceMatrix", "Defaults to true, set to false only when the number of facilities is very high and a distance matrix would become too large (>1000 facilities).", new BoolValue(true)));
    118109
    119110      Maximization = new BoolValue(false);
     
    196187      ParameterizeOperators();
    197188    }
     189    private void CoordinatesParameter_ValueChanged(object sender, EventArgs e) {
     190      Coordinates.Reset += new EventHandler(Coordinates_Reset);
     191      Coordinates.ItemChanged += new EventHandler<EventArgs<int, int>>(Coordinates_ItemChanged);
     192      UpdateDistanceMatrix();
     193    }
     194    private void Coordinates_ItemChanged(object sender, EventArgs<int, int> e) {
     195      UpdateDistanceMatrix();
     196    }
     197    private void Coordinates_Reset(object sender, EventArgs e) {
     198      UpdateDistanceMatrix();
     199    }
    198200    #endregion
    199201
     
    205207
    206208    private void AttachEventHandlers() {
     209      SolutionCreator.PermutationParameter.ActualNameChanged += new EventHandler(SolutionCreator_PermutationParameter_ActualNameChanged);
     210      Evaluator.QualityParameter.ActualNameChanged += new EventHandler(Evaluator_QualityParameter_ActualNameChanged);
    207211      WeightsParameter.ValueChanged += new EventHandler(WeightsParameter_ValueChanged);
    208212      Weights.RowsChanged += new EventHandler(Weights_RowsChanged);
    209       SolutionCreator.PermutationParameter.ActualNameChanged += new EventHandler(SolutionCreator_PermutationParameter_ActualNameChanged);
    210       Evaluator.QualityParameter.ActualNameChanged += new EventHandler(Evaluator_QualityParameter_ActualNameChanged);
     213      CoordinatesParameter.ValueChanged += new EventHandler(CoordinatesParameter_ValueChanged);
     214      Coordinates.Reset += new EventHandler(Coordinates_Reset);
     215      Coordinates.ItemChanged += new EventHandler<EventArgs<int, int>>(Coordinates_ItemChanged);
    211216    }
    212217
     
    226231      if (Evaluator != null) {
    227232        Evaluator.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
    228         Evaluator.UseDistanceMatrixParameter.ActualName = UseDistanceMatrixParameter.Name;
    229233        Evaluator.DistanceMatrixParameter.ActualName = DistanceMatrixParameter.Name;
    230234        Evaluator.CoordinatesParameter.ActualName = CoordinatesParameter.Name;
     
    265269      }
    266270    }
     271
     272    private void UpdateDistanceMatrix() {
     273      if (Coordinates != null && Coordinates.Columns == 2 && Coordinates.Rows > 1) {
     274        DistanceMatrix = new DoubleMatrix(Coordinates.Rows, Coordinates.Rows);
     275        for (int i = 0; i < Coordinates.Rows - 1; i++) {
     276          for (int j = i + 1; j < Coordinates.Rows; j++) {
     277            double dx = Coordinates[i, 0] - Coordinates[j, 0];
     278            double dy = Coordinates[i, 1] - Coordinates[j, 1];
     279            DistanceMatrix[i, j] = Math.Sqrt(dx * dx + dy * dy);
     280            DistanceMatrix[j, i] = DistanceMatrix[i, j];
     281          }
     282        }
     283      }
     284    }
    267285    #endregion
    268286
     
    273291      DistanceMatrix = new DoubleMatrix(parser.Distances);
    274292      Weights = new DoubleMatrix(parser.Weights);
    275       UseDistanceMatrix.Value = true;
    276293      Name = "Quadratic Assignment Problem (imported from " + Path.GetFileNameWithoutExtension(filename) + ")";
    277294      Description = "Imported problem data using QAPLIBParser " + Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true).Cast<AssemblyFileVersionAttribute>().FirstOrDefault().Version + ".";
     295      BestKnownQuality = null;
     296      BestKnownSolution = null;
    278297      OnReset();
    279298    }
     
    287306        DistanceMatrix = new DoubleMatrix(parser.Distances);
    288307        Weights = new DoubleMatrix(parser.Weights);
    289         UseDistanceMatrix.Value = true;
    290308        Name = "Quadratic Assignment Problem (loaded instance " + instance + ")";
    291309        Description = "Loaded embedded problem data of instance " + instance + ".";
    292310        OnReset();
    293311      }
     312      bool solutionExists = Assembly.GetExecutingAssembly()
     313          .GetManifestResourceNames()
     314          .Where(x => x.EndsWith(instance + ".sln"))
     315          .Any();
     316      if (solutionExists) {
     317        using (Stream solStream = Assembly.GetExecutingAssembly()
     318          .GetManifestResourceStream(InstancePrefix + instance + ".sln")) {
     319          QAPLIBSolutionParser solParser = new QAPLIBSolutionParser();
     320          solParser.Parse(solStream);
     321          BestKnownQuality = new DoubleValue(solParser.Qualiy);
     322          BestKnownSolution = new Permutation(PermutationTypes.Absolute, solParser.Assignment);
     323        }
     324      } else {
     325        BestKnownQuality = null;
     326        BestKnownSolution = null;
     327      }
    294328    }
    295329  }
Note: See TracChangeset for help on using the changeset viewer.