-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathTravellingSalesmanSolver.cs
93 lines (76 loc) · 3.21 KB
/
TravellingSalesmanSolver.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using BlazorAI.Shared.Types;
using BlazorAI.Shared.Utility;
using GeneticSharp;
using System;
using System.Linq;
using static MoreLinq.Extensions.PairwiseExtension;
namespace BlazorAI.Shared.Solvers
{
public record TravellingSalesmanSolution(Point[] Points, double Distance);
/// <summary>
/// Solver for https://en.wikipedia.org/wiki/Travelling_salesman_problem
/// We take a series of (X, Y) points as input and try to evolve the shortest
/// route which visits all points and returns to the starting point.
/// Our chromosome is an array of ints representing the index of points to visit.
/// </summary>
public class TravellingSalesmanSolver : Solver<TravellingSalesmanSolution>
{
public TravellingSalesmanSolver(Point[] inputPoints)
{
NumPoints = inputPoints.Length;
FitnessProvider = new TravellingSalesmanFitness(inputPoints);
}
int NumPoints { get; }
TravellingSalesmanFitness FitnessProvider { get; }
protected override GeneticAlgorithm GetGA(SolverParameters parameters)
{
// First node is fixed
var adamChromosome = new IntChromosome(1.To(NumPoints - 1).ToArray());
var population =
new Population(parameters.Population, parameters.Population, adamChromosome);
return new GeneticAlgorithm(
population,
FitnessProvider,
new TournamentSelection(),
new OrderedCrossover(),
new ReverseSequenceMutation());
}
protected override TravellingSalesmanSolution GetSolution(IChromosome best) =>
new TravellingSalesmanSolution
(
Points: FitnessProvider.GetPoints(best),
Distance: Math.Round(FitnessProvider.GetTotalDistance(best), 2)
);
}
/// <summary>
/// Our fitness function is the total Euclidian distance between points
/// along our route (starting and ending at the origin).
/// </summary>
public class TravellingSalesmanFitness : IFitness
{
public TravellingSalesmanFitness(Point[] inputPoints)
{
InputPoints = inputPoints;
var cycle = inputPoints.Append(inputPoints[0]).ToArray();
InitialDistance = GetTotalDistance(cycle);
}
Point[] InputPoints { get; }
double InitialDistance { get; }
// Add fixed start point to beginning and end and apply permutation
public Point[] GetPoints(IChromosome chromosome) =>
chromosome.GetGenes()
.Select(x => (int)x.Value)
.Prepend(0)
.Append(0)
.Select(x => InputPoints[x])
.ToArray();
public double GetTotalDistance(IChromosome chromosome) =>
GetTotalDistance(GetPoints(chromosome));
double GetTotalDistance(Point[] points) =>
points.Pairwise((x, y) => x.DistanceTo(y)).Sum();
// Use the original distance (a random route between all points)
// as the benchmark for our evolving solution
public double Evaluate(IChromosome chromosome) =>
Math.Max(0, 1.0 - (GetTotalDistance(chromosome) / InitialDistance));
}
}