-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexample_5.js
159 lines (140 loc) · 5.36 KB
/
example_5.js
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// @ts-check
'use strict'
const GA = require('./vovk-ga')
const trainer = new GA()
const complexity = 20 // Change this number to test a different number of cities
console.log(`Example 5: Traveling Salesman Problem - ${complexity} cities`)
const cityList = new Array(complexity).fill(1).map((x, i) => i)
/** @param {any[]} A * @param {number} [i] */// @ts-ignore
const rotateArray = (A, i) => { const B = A.map(x => x); i = i || 0; for (let x = 1; x <= i; x++) B.shift(B.push(B[0])); return B }
class TSPCity {
/** @param {number} [width] * @param {number} [height] */
constructor(width, height) {
this.width = width || 100
this.height = height || 100
this.x = 0
this.y = 0
this.randomizePosition()
}
/** @param {number} [width] * @param {number} [height] */
randomizePosition(width, height) {
this.width = width || this.width
this.height = height || this.height
const { width: w, height: h } = this
this.x = Math.round(Math.random() * w)
this.y = Math.round(Math.random() * h)
}
}
class TravelingSalesman {
/** @param {{ count: number; width?: number; height?: number; }} options */
constructor(options) {
const { count, width, height } = options
this.count = count || 6
this.combinations = []
this.width = width || 100
this.height = height || 100
this.cities = []
for (let i = 0; i < this.count; i++) {
this.combinations.push(i)
this.cities.push(new TSPCity(this.width, this.height))
}
}
/** @param {number} indexA * @param {number} indexB */
distanceBetweenPoints(indexA, indexB) {
const x1 = this.cities[indexA].x
const x2 = this.cities[indexB].x
const y1 = this.cities[indexA].y
const y2 = this.cities[indexB].y
const distance = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
return distance
}
combinationDistance(combination) {
let distance = 0
for (let i = 1; i < combination.length; i++) {
const p1 = combination[i - 1]
const p2 = combination[i]
const d = this.distanceBetweenPoints(p1, p2)
distance += d
}
const d = this.distanceBetweenPoints(combination[combination.length - 1], combination[0]) // Add distance back to beginning
distance += d
return distance
}
}
const dataSet = new TravelingSalesman({ count: complexity /* 8 */ })
console.log(`Creating cities ...`)
dataSet.cities.forEach((city, i) => {
console.log(`\t${i}: { x: ${city.x} y: ${city.y} }`)
})
/** @param {any[]} A */
const shuffleArray = A => { const B = A.map(x => x); for (let i = B.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * i); const temp = B[i]; B[i] = B[j]; B[j] = temp; } return B }
const parameters = { variable: 'path', type: 'combination', options: cityList /* [0, 1, 2, 3, 4, 5, 6, 7] */, start: 0, /* repeat: true */ }
const fitnessFunction = (sample, /* callback */) => { // optional async callback
const { path } = sample
let distance = dataSet.combinationDistance(path)
//callback(distance)
return distance
}
const conf = {
//debug: true,
maxPopulation: 10000,
survivorsPERCENT: 0.005,
crossoverChance: 0.15,
mutationChance: 0.05,
mutationPower: 1,
bestSurvive: true,
parameters: parameters,
initialValues: { path: cityList }, // Start out with a default path of [ 0, 1, 2, 3 ... n-1 ]
fitnessFunction: fitnessFunction,
fitnessTargetValue: 0,
fitnessTimeout: 10000,
//fitnessTargetTolerance: 1e-15,
}
const logProgress = progress => {
console.log(progress.message)
}
const logResult = result => {
console.log(result.message)
console.log(`Example 5: Traveling Salesman Problem - ${complexity} cities - Finished!`)
console.log(`\tRandom combinations:`)
for (let i = 0; i < 8; i++) {
let randomCombination = shuffleArray(dataSet.combinations)
if (parameters.start !== undefined && randomCombination.includes(parameters.start))
randomCombination = rotateArray(randomCombination, randomCombination.indexOf(parameters.start))
const distance = dataSet.combinationDistance(randomCombination)
console.log(`\t\tDistance for [${randomCombination}] = ${distance}`)
}
console.log(`\tTrained result:`)
const trainedCombination = result.parameters.path
const distance = dataSet.combinationDistance(trainedCombination)
console.log(`\t\tDistance for [${trainedCombination}] = ${distance}`)
}
const catchError = e => {
if (e === 'timeout') console.log('Training timeout!')
else throw e
}
setTimeout(() => {
console.log(`Starting training...`)
trainer.configure(conf).evolve(100, logProgress)
.then(result => {
logResult(result)
/*
Plot data:
1. Points:
0,0
10,10
5,5
2. Path:
1 2 3
*/
console.log(`Plot data:`)
console.log(`1. Points:`)
dataSet.cities.forEach(city => {
console.log(`\t${city.x},${city.y}`)
})
console.log(`2. Path:`)
const { path } = result.parameters
console.log(`\t${path.map(x => x + 1).join(' ')}`)
})
.catch(catchError)
}, 1000)