Free cookie consent management tool by TermsFeed Policy Generator

source: branches/ScatterSearch (trunk integration)/HeuristicLab.Problems.TravelingSalesman/3.3/TravelingSalesmanProblem.cs @ 7887

Last change on this file since 7887 was 7789, checked in by jkarder, 13 years ago

#1331:

  • added Scatter Search algorithm
  • added problem specific operators for improvement, path relinking and similarity calculation
  • adjusted event handling
File size: 21.7 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 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.IO;
25using System.Linq;
26using HeuristicLab.Common;
27using HeuristicLab.Core;
28using HeuristicLab.Data;
29using HeuristicLab.Encodings.PermutationEncoding;
30using HeuristicLab.Optimization;
31using HeuristicLab.Optimization.Operators;
32using HeuristicLab.Parameters;
33using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
34using HeuristicLab.PluginInfrastructure;
35using HeuristicLab.Problems.Instances;
36
37namespace HeuristicLab.Problems.TravelingSalesman {
38  [Item("Traveling Salesman Problem", "Represents a symmetric Traveling Salesman Problem.")]
39  [Creatable("Problems")]
40  [StorableClass]
41  public sealed class TravelingSalesmanProblem : SingleObjectiveHeuristicOptimizationProblem<ITSPEvaluator, IPermutationCreator>, IStorableContent,
42    IProblemInstanceConsumer<TSPData> {
43    private static readonly int DistanceMatrixSizeLimit = 1000;
44    public string Filename { get; set; }
45
46    #region Parameter Properties
47    public OptionalValueParameter<DoubleMatrix> CoordinatesParameter {
48      get { return (OptionalValueParameter<DoubleMatrix>)Parameters["Coordinates"]; }
49    }
50    public OptionalValueParameter<DistanceMatrix> DistanceMatrixParameter {
51      get { return (OptionalValueParameter<DistanceMatrix>)Parameters["DistanceMatrix"]; }
52    }
53    public ValueParameter<BoolValue> UseDistanceMatrixParameter {
54      get { return (ValueParameter<BoolValue>)Parameters["UseDistanceMatrix"]; }
55    }
56    public OptionalValueParameter<Permutation> BestKnownSolutionParameter {
57      get { return (OptionalValueParameter<Permutation>)Parameters["BestKnownSolution"]; }
58    }
59    #endregion
60
61    #region Properties
62    public DoubleMatrix Coordinates {
63      get { return CoordinatesParameter.Value; }
64      set { CoordinatesParameter.Value = value; }
65    }
66    public DistanceMatrix DistanceMatrix {
67      get { return DistanceMatrixParameter.Value; }
68      set { DistanceMatrixParameter.Value = value; }
69    }
70    public BoolValue UseDistanceMatrix {
71      get { return UseDistanceMatrixParameter.Value; }
72      set { UseDistanceMatrixParameter.Value = value; }
73    }
74    public Permutation BestKnownSolution {
75      get { return BestKnownSolutionParameter.Value; }
76      set { BestKnownSolutionParameter.Value = value; }
77    }
78    private BestTSPSolutionAnalyzer BestTSPSolutionAnalyzer {
79      get { return Operators.OfType<BestTSPSolutionAnalyzer>().FirstOrDefault(); }
80    }
81    private TSPAlleleFrequencyAnalyzer TSPAlleleFrequencyAnalyzer {
82      get { return Operators.OfType<TSPAlleleFrequencyAnalyzer>().FirstOrDefault(); }
83    }
84    private TSPPopulationDiversityAnalyzer TSPPopulationDiversityAnalyzer {
85      get { return Operators.OfType<TSPPopulationDiversityAnalyzer>().FirstOrDefault(); }
86    }
87    #endregion
88
89    // BackwardsCompatibility3.3
90    #region Backwards compatible code, remove with 3.4
91    [Obsolete]
92    [Storable(Name = "operators")]
93    private IEnumerable<IOperator> oldOperators {
94      get { return null; }
95      set {
96        if (value != null && value.Any())
97          Operators.AddRange(value);
98      }
99    }
100    #endregion
101
102    [StorableConstructor]
103    private TravelingSalesmanProblem(bool deserializing) : base(deserializing) { }
104    private TravelingSalesmanProblem(TravelingSalesmanProblem original, Cloner cloner)
105      : base(original, cloner) {
106      RegisterEventHandlers();
107    }
108    public override IDeepCloneable Clone(Cloner cloner) {
109      return new TravelingSalesmanProblem(this, cloner);
110    }
111    public TravelingSalesmanProblem()
112      : base(new TSPRoundedEuclideanPathEvaluator(), new RandomPermutationCreator()) {
113      Parameters.Add(new OptionalValueParameter<DoubleMatrix>("Coordinates", "The x- and y-Coordinates of the cities."));
114      Parameters.Add(new OptionalValueParameter<DistanceMatrix>("DistanceMatrix", "The matrix which contains the distances between the cities."));
115      Parameters.Add(new ValueParameter<BoolValue>("UseDistanceMatrix", "True if a distance matrix should be calculated and used for evaluation, otherwise false.", new BoolValue(true)));
116      Parameters.Add(new OptionalValueParameter<Permutation>("BestKnownSolution", "The best known solution of this TSP instance."));
117
118      Maximization.Value = false;
119      MaximizationParameter.Hidden = true;
120      DistanceMatrixParameter.ReactOnValueToStringChangedAndValueItemImageChanged = false;
121
122      Coordinates = new DoubleMatrix(new double[,] {
123        { 100, 100 }, { 100, 200 }, { 100, 300 }, { 100, 400 },
124        { 200, 100 }, { 200, 200 }, { 200, 300 }, { 200, 400 },
125        { 300, 100 }, { 300, 200 }, { 300, 300 }, { 300, 400 },
126        { 400, 100 }, { 400, 200 }, { 400, 300 }, { 400, 400 }
127      });
128
129      SolutionCreator.PermutationParameter.ActualName = "TSPTour";
130      Evaluator.QualityParameter.ActualName = "TSPTourLength";
131      ParameterizeSolutionCreator();
132      ParameterizeEvaluator();
133
134      InitializeOperators();
135      RegisterEventHandlers();
136    }
137
138    #region Events
139    protected override void OnSolutionCreatorChanged() {
140      base.OnSolutionCreatorChanged();
141      SolutionCreator.PermutationParameter.ActualNameChanged += new EventHandler(SolutionCreator_PermutationParameter_ActualNameChanged);
142      ParameterizeSolutionCreator();
143      ParameterizeEvaluator();
144      ParameterizeAnalyzers();
145      ParameterizeOperators();
146    }
147    protected override void OnEvaluatorChanged() {
148      base.OnEvaluatorChanged();
149      Evaluator.QualityParameter.ActualNameChanged += new EventHandler(Evaluator_QualityParameter_ActualNameChanged);
150      ParameterizeEvaluator();
151      ParameterizeSolutionCreator();
152      UpdateMoveEvaluators();
153      ParameterizeAnalyzers();
154      ClearDistanceMatrix();
155    }
156    private void CoordinatesParameter_ValueChanged(object sender, EventArgs e) {
157      if (Coordinates != null) {
158        Coordinates.ItemChanged += new EventHandler<EventArgs<int, int>>(Coordinates_ItemChanged);
159        Coordinates.Reset += new EventHandler(Coordinates_Reset);
160      }
161      ParameterizeSolutionCreator();
162      ClearDistanceMatrix();
163    }
164    private void Coordinates_ItemChanged(object sender, EventArgs<int, int> e) {
165      ClearDistanceMatrix();
166    }
167    private void Coordinates_Reset(object sender, EventArgs e) {
168      ParameterizeSolutionCreator();
169      ClearDistanceMatrix();
170    }
171    private void SolutionCreator_PermutationParameter_ActualNameChanged(object sender, EventArgs e) {
172      ParameterizeEvaluator();
173      ParameterizeAnalyzers();
174      ParameterizeOperators();
175    }
176    private void Evaluator_QualityParameter_ActualNameChanged(object sender, EventArgs e) {
177      ParameterizeAnalyzers();
178    }
179    #endregion
180
181    #region Helpers
182    [StorableHook(HookType.AfterDeserialization)]
183    private void AfterDeserialization() {
184      // BackwardsCompatibility3.3
185      #region Backwards compatible code (remove with 3.4)
186      OptionalValueParameter<DoubleMatrix> oldDistanceMatrixParameter = Parameters["DistanceMatrix"] as OptionalValueParameter<DoubleMatrix>;
187      if (oldDistanceMatrixParameter != null) {
188        Parameters.Remove(oldDistanceMatrixParameter);
189        Parameters.Add(new OptionalValueParameter<DistanceMatrix>("DistanceMatrix", "The matrix which contains the distances between the cities."));
190        DistanceMatrixParameter.GetsCollected = oldDistanceMatrixParameter.GetsCollected;
191        DistanceMatrixParameter.ReactOnValueToStringChangedAndValueItemImageChanged = false;
192        if (oldDistanceMatrixParameter.Value != null) {
193          DoubleMatrix oldDM = oldDistanceMatrixParameter.Value;
194          DistanceMatrix newDM = new DistanceMatrix(oldDM.Rows, oldDM.Columns, oldDM.ColumnNames, oldDM.RowNames);
195          newDM.SortableView = oldDM.SortableView;
196          for (int i = 0; i < newDM.Rows; i++)
197            for (int j = 0; j < newDM.Columns; j++)
198              newDM[i, j] = oldDM[i, j];
199          DistanceMatrixParameter.Value = (DistanceMatrix)newDM.AsReadOnly();
200        }
201      }
202
203      ValueParameter<DoubleMatrix> oldCoordinates = (Parameters["Coordinates"] as ValueParameter<DoubleMatrix>);
204      if (oldCoordinates != null) {
205        Parameters.Remove(oldCoordinates);
206        Parameters.Add(new OptionalValueParameter<DoubleMatrix>("Coordinates", "The x- and y-Coordinates of the cities.", oldCoordinates.Value, oldCoordinates.GetsCollected));
207      }
208
209      if (Operators.Count == 0) InitializeOperators();
210      #endregion
211      RegisterEventHandlers();
212    }
213
214    private void RegisterEventHandlers() {
215      CoordinatesParameter.ValueChanged += new EventHandler(CoordinatesParameter_ValueChanged);
216      if (Coordinates != null) {
217        Coordinates.ItemChanged += new EventHandler<EventArgs<int, int>>(Coordinates_ItemChanged);
218        Coordinates.Reset += new EventHandler(Coordinates_Reset);
219      }
220      SolutionCreator.PermutationParameter.ActualNameChanged += new EventHandler(SolutionCreator_PermutationParameter_ActualNameChanged);
221      Evaluator.QualityParameter.ActualNameChanged += new EventHandler(Evaluator_QualityParameter_ActualNameChanged);
222    }
223
224    private void InitializeOperators() {
225      Operators.Add(new TSPImprovementOperator());
226      Operators.Add(new TSPMultipleGuidesPathRelinker());
227      Operators.Add(new TSPPathRelinker());
228      Operators.Add(new TSPSimultaneousPathRelinker());
229      Operators.Add(new TSPSimilarityCalculator());
230
231      Operators.Add(new BestTSPSolutionAnalyzer());
232      Operators.Add(new TSPAlleleFrequencyAnalyzer());
233      Operators.Add(new TSPPopulationDiversityAnalyzer());
234      ParameterizeAnalyzers();
235      var operators = new HashSet<IPermutationOperator>(new IPermutationOperator[] {
236        new OrderCrossover2(),
237        new InversionManipulator(),
238        new StochasticInversionMultiMoveGenerator()
239      }, new TypeEqualityComparer<IPermutationOperator>());
240      foreach (var op in ApplicationManager.Manager.GetInstances<IPermutationOperator>())
241        operators.Add(op);
242      Operators.AddRange(operators);
243      ParameterizeOperators();
244      UpdateMoveEvaluators();
245    }
246    private void UpdateMoveEvaluators() {
247      Operators.RemoveAll(x => x is ISingleObjectiveMoveEvaluator);
248      foreach (ITSPPathMoveEvaluator op in ApplicationManager.Manager.GetInstances<ITSPPathMoveEvaluator>())
249        if (op.EvaluatorType == Evaluator.GetType()) {
250          Operators.Add(op);
251        }
252      ParameterizeOperators();
253      OnOperatorsChanged();
254    }
255    private void ParameterizeSolutionCreator() {
256      if (Evaluator is ITSPDistanceMatrixEvaluator && DistanceMatrix != null)
257        SolutionCreator.LengthParameter.Value = new IntValue(DistanceMatrix.Rows);
258      else if (Evaluator is ITSPCoordinatesPathEvaluator && Coordinates != null)
259        SolutionCreator.LengthParameter.Value = new IntValue(Coordinates.Rows);
260      else SolutionCreator.LengthParameter.Value = null;
261      SolutionCreator.LengthParameter.Hidden = SolutionCreator.LengthParameter.Value != null;
262      SolutionCreator.PermutationTypeParameter.Value = new PermutationType(PermutationTypes.RelativeUndirected);
263      SolutionCreator.PermutationTypeParameter.Hidden = true;
264    }
265    private void ParameterizeEvaluator() {
266      if (Evaluator is ITSPPathEvaluator) {
267        ITSPPathEvaluator evaluator = (ITSPPathEvaluator)Evaluator;
268        evaluator.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
269        evaluator.PermutationParameter.Hidden = true;
270      }
271      if (Evaluator is ITSPCoordinatesPathEvaluator) {
272        ITSPCoordinatesPathEvaluator evaluator = (ITSPCoordinatesPathEvaluator)Evaluator;
273        evaluator.CoordinatesParameter.ActualName = CoordinatesParameter.Name;
274        evaluator.CoordinatesParameter.Hidden = true;
275        evaluator.DistanceMatrixParameter.ActualName = DistanceMatrixParameter.Name;
276        evaluator.DistanceMatrixParameter.Hidden = true;
277        evaluator.UseDistanceMatrixParameter.ActualName = UseDistanceMatrixParameter.Name;
278        evaluator.UseDistanceMatrixParameter.Hidden = true;
279      }
280      if (Evaluator is ITSPDistanceMatrixEvaluator) {
281        var evaluator = (ITSPDistanceMatrixEvaluator)Evaluator;
282        evaluator.DistanceMatrixParameter.ActualName = DistanceMatrixParameter.Name;
283        evaluator.DistanceMatrixParameter.Hidden = true;
284      }
285    }
286    private void ParameterizeAnalyzers() {
287      if (BestTSPSolutionAnalyzer != null) {
288        BestTSPSolutionAnalyzer.QualityParameter.ActualName = Evaluator.QualityParameter.ActualName;
289        BestTSPSolutionAnalyzer.CoordinatesParameter.ActualName = CoordinatesParameter.Name;
290        BestTSPSolutionAnalyzer.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
291        BestTSPSolutionAnalyzer.ResultsParameter.ActualName = "Results";
292        BestTSPSolutionAnalyzer.BestKnownQualityParameter.ActualName = BestKnownQualityParameter.Name;
293        BestTSPSolutionAnalyzer.BestKnownSolutionParameter.ActualName = BestKnownSolutionParameter.Name;
294        BestTSPSolutionAnalyzer.MaximizationParameter.ActualName = MaximizationParameter.Name;
295      }
296
297      if (TSPAlleleFrequencyAnalyzer != null) {
298        TSPAlleleFrequencyAnalyzer.MaximizationParameter.ActualName = MaximizationParameter.Name;
299        TSPAlleleFrequencyAnalyzer.CoordinatesParameter.ActualName = CoordinatesParameter.Name;
300        TSPAlleleFrequencyAnalyzer.DistanceMatrixParameter.ActualName = DistanceMatrixParameter.Name;
301        TSPAlleleFrequencyAnalyzer.SolutionParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
302        TSPAlleleFrequencyAnalyzer.QualityParameter.ActualName = Evaluator.QualityParameter.ActualName;
303        TSPAlleleFrequencyAnalyzer.BestKnownSolutionParameter.ActualName = BestKnownSolutionParameter.Name;
304        TSPAlleleFrequencyAnalyzer.ResultsParameter.ActualName = "Results";
305      }
306
307      if (TSPPopulationDiversityAnalyzer != null) {
308        TSPPopulationDiversityAnalyzer.MaximizationParameter.ActualName = MaximizationParameter.Name;
309        TSPPopulationDiversityAnalyzer.SolutionParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
310        TSPPopulationDiversityAnalyzer.QualityParameter.ActualName = Evaluator.QualityParameter.ActualName;
311        TSPPopulationDiversityAnalyzer.ResultsParameter.ActualName = "Results";
312      }
313    }
314    private void ParameterizeOperators() {
315      foreach (IPermutationCrossover op in Operators.OfType<IPermutationCrossover>()) {
316        op.ParentsParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
317        op.ParentsParameter.Hidden = true;
318        op.ChildParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
319        op.ChildParameter.Hidden = true;
320      }
321      foreach (IPermutationManipulator op in Operators.OfType<IPermutationManipulator>()) {
322        op.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
323        op.PermutationParameter.Hidden = true;
324      }
325      foreach (IPermutationMoveOperator op in Operators.OfType<IPermutationMoveOperator>()) {
326        op.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
327        op.PermutationParameter.Hidden = true;
328      }
329      foreach (ITSPPathMoveEvaluator op in Operators.OfType<ITSPPathMoveEvaluator>()) {
330        op.CoordinatesParameter.ActualName = CoordinatesParameter.Name;
331        op.CoordinatesParameter.Hidden = true;
332        op.DistanceMatrixParameter.ActualName = DistanceMatrixParameter.Name;
333        op.DistanceMatrixParameter.Hidden = true;
334        op.UseDistanceMatrixParameter.ActualName = UseDistanceMatrixParameter.Name;
335        op.UseDistanceMatrixParameter.Hidden = true;
336        op.QualityParameter.ActualName = Evaluator.QualityParameter.ActualName;
337        op.QualityParameter.Hidden = true;
338        op.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
339        op.PermutationParameter.Hidden = true;
340      }
341      foreach (IPermutationMultiNeighborhoodShakingOperator op in Operators.OfType<IPermutationMultiNeighborhoodShakingOperator>()) {
342        op.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
343        op.PermutationParameter.Hidden = true;
344      }
345      foreach (IImprovementOperator op in Operators.OfType<IImprovementOperator>()) {
346        op.TargetParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
347        op.TargetParameter.Hidden = true;
348      }
349      foreach (IPathRelinker op in Operators.OfType<IPathRelinker>()) {
350        op.ParentsParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
351        op.ParentsParameter.Hidden = true;
352      }
353      foreach (ISimilarityCalculator op in Operators.OfType<ISimilarityCalculator>()) {
354        op.TargetParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
355        op.TargetParameter.Hidden = true;
356      }
357    }
358
359    private void ClearDistanceMatrix() {
360      if (!(Evaluator is ITSPDistanceMatrixEvaluator))
361        DistanceMatrixParameter.Value = null;
362    }
363    #endregion
364
365    public void Load(TSPData data) {
366      if (data.Coordinates == null && data.Distances == null)
367        throw new System.IO.InvalidDataException("The given instance specifies neither coordinates nor distances!");
368      if (data.Dimension > DistanceMatrixSizeLimit && (data.DistanceMeasure == TSPDistanceMeasure.Att
369        || data.DistanceMeasure == TSPDistanceMeasure.Manhattan
370        || data.DistanceMeasure == TSPDistanceMeasure.Maximum
371        || data.DistanceMeasure == TSPDistanceMeasure.UpperEuclidean))
372        throw new System.IO.InvalidDataException("The given instance uses an unsupported distance measure and is too large for using a distance matrix.");
373      if (data.Coordinates != null && data.Coordinates.GetLength(1) != 2)
374        throw new System.IO.InvalidDataException("The coordinates of the given instance are not in the right format, there need to be one row for each customer and two columns for the x and y coordinates.");
375
376      Name = data.Name;
377      Description = data.Description;
378
379      if (data.Coordinates != null && data.Coordinates.GetLength(0) > 0)
380        Coordinates = new DoubleMatrix(data.Coordinates);
381      else Coordinates = null;
382
383      TSPEvaluator evaluator;
384      if (data.DistanceMeasure == TSPDistanceMeasure.Att
385        || data.DistanceMeasure == TSPDistanceMeasure.Manhattan
386        || data.DistanceMeasure == TSPDistanceMeasure.Maximum
387        || data.DistanceMeasure == TSPDistanceMeasure.UpperEuclidean) {
388        evaluator = new TSPDistanceMatrixEvaluator();
389        UseDistanceMatrix = new BoolValue(true);
390        DistanceMatrix = new DistanceMatrix(data.GetDistanceMatrix());
391      } else if (data.DistanceMeasure == TSPDistanceMeasure.Direct && data.Distances != null) {
392        evaluator = new TSPDistanceMatrixEvaluator();
393        UseDistanceMatrix = new BoolValue(true);
394        DistanceMatrix = new DistanceMatrix(data.Distances);
395      } else {
396        DistanceMatrix = null;
397        UseDistanceMatrix = new BoolValue(data.Dimension <= DistanceMatrixSizeLimit);
398        switch (data.DistanceMeasure) {
399          case TSPDistanceMeasure.Euclidean:
400            evaluator = new TSPEuclideanPathEvaluator();
401            break;
402          case TSPDistanceMeasure.RoundedEuclidean:
403            evaluator = new TSPRoundedEuclideanPathEvaluator();
404            break;
405          case TSPDistanceMeasure.Geo:
406            evaluator = new TSPGeoPathEvaluator();
407            break;
408          default:
409            throw new InvalidDataException("An unknown distance measure is given in the instance!");
410        }
411      }
412      evaluator.QualityParameter.ActualName = "TSPTourLength";
413      Evaluator = evaluator;
414
415      BestKnownSolution = null;
416      BestKnownQuality = null;
417
418      if (data.BestKnownTour != null) {
419        try {
420          EvaluateAndLoadTour(data.BestKnownTour);
421        }
422        catch (InvalidOperationException) {
423          if (data.BestKnownQuality.HasValue)
424            BestKnownQuality = new DoubleValue(data.BestKnownQuality.Value);
425        }
426      } else if (data.BestKnownQuality.HasValue) {
427        BestKnownQuality = new DoubleValue(data.BestKnownQuality.Value);
428      }
429      OnReset();
430    }
431
432    public void EvaluateAndLoadTour(int[] tour) {
433      var route = new Permutation(PermutationTypes.RelativeUndirected, tour);
434      BestKnownSolution = route;
435
436      double quality;
437      if (Evaluator is ITSPDistanceMatrixEvaluator) {
438        quality = TSPDistanceMatrixEvaluator.Apply(DistanceMatrix, route);
439      } else if (Evaluator is ITSPCoordinatesPathEvaluator) {
440        quality = TSPCoordinatesPathEvaluator.Apply((TSPCoordinatesPathEvaluator)Evaluator, Coordinates, route);
441      } else {
442        throw new InvalidOperationException("Cannot calculate solution quality, evaluator type is unknown.");
443      }
444      BestKnownQuality = new DoubleValue(quality);
445    }
446  }
447}
Note: See TracBrowser for help on using the repository browser.