1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.math4.examples.sofm.tsp;
19
20 import java.util.List;
21 import java.util.ArrayList;
22 import java.util.Set;
23 import java.util.Collection;
24 import java.util.Iterator;
25 import java.util.NoSuchElementException;
26 import java.util.function.DoubleUnaryOperator;
27 import java.util.concurrent.Future;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.ExecutionException;
31
32 import org.apache.commons.rng.UniformRandomProvider;
33 import org.apache.commons.rng.sampling.CollectionSampler;
34 import org.apache.commons.rng.sampling.distribution.ContinuousUniformSampler;
35
36 import org.apache.commons.math4.neuralnet.DistanceMeasure;
37 import org.apache.commons.math4.neuralnet.EuclideanDistance;
38 import org.apache.commons.math4.neuralnet.FeatureInitializer;
39 import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
40 import org.apache.commons.math4.neuralnet.Network;
41 import org.apache.commons.math4.neuralnet.Neuron;
42 import org.apache.commons.math4.neuralnet.oned.NeuronString;
43 import org.apache.commons.math4.neuralnet.sofm.KohonenUpdateAction;
44 import org.apache.commons.math4.neuralnet.sofm.KohonenTrainingTask;
45 import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunctionFactory;
46 import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunction;
47 import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunctionFactory;
48 import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunction;
49
50
51
52
53
54
55 public final class TravellingSalesmanSolver {
56
57 private static final long FIRST_NEURON_ID = 0;
58
59 private final Network net;
60
61 private final DistanceMeasure distance = new EuclideanDistance();
62
63 private final int numberOfNeurons;
64
65
66
67
68
69 private TravellingSalesmanSolver(int numNeurons,
70 FeatureInitializer[] init) {
71
72 numberOfNeurons = numNeurons;
73
74
75 net = new NeuronString(numberOfNeurons, true, init).getNetwork();
76 }
77
78
79
80
81
82
83
84
85
86 public static City[] solve(City[] cities,
87 double neuronsPerCity,
88 long numUpdates,
89 int numTasks,
90 UniformRandomProvider random) {
91 if (cities.length <= 2) {
92 return cities;
93 }
94
95
96 final Set<City> uniqueCities = City.unique(cities);
97
98 final int numNeurons = (int) (neuronsPerCity * uniqueCities.size());
99 if (numNeurons < uniqueCities.size()) {
100 throw new IllegalArgumentException("Too few neurons");
101 }
102
103
104 final FeatureInitializer[] init = makeInitializers(numNeurons,
105 uniqueCities,
106 random);
107 final TravellingSalesmanSolver solver = new TravellingSalesmanSolver(numNeurons,
108 init);
109
110
111 final ExecutorService service = Executors.newCachedThreadPool();
112 final Runnable[] tasks = solver.createTasks(uniqueCities,
113 random,
114 numTasks,
115 numUpdates / numTasks);
116 final List<Future<?>> execOutput = new ArrayList<>();
117
118 for (final Runnable r : tasks) {
119 execOutput.add(service.submit(r));
120 }
121
122 try {
123 for (final Future<?> f : execOutput) {
124 f.get();
125 }
126 } catch (InterruptedException | ExecutionException e) {
127 if (e instanceof InterruptedException) {
128
129 Thread.currentThread().interrupt();
130 }
131 throw new RuntimeException(e);
132 }
133
134 service.shutdown();
135
136 return solver.getCityList(uniqueCities).toArray(new City[0]);
137 }
138
139
140
141
142
143
144
145
146
147
148 private Runnable[] createTasks(Set<City> cities,
149 UniformRandomProvider random,
150 int numTasks,
151 long numSamplesPerTask) {
152 final Runnable[] tasks = new Runnable[numTasks];
153 final LearningFactorFunction learning
154 = LearningFactorFunctionFactory.exponentialDecay(0.9,
155 0.05,
156 numSamplesPerTask / 2);
157 final NeighbourhoodSizeFunction neighbourhood
158 = NeighbourhoodSizeFunctionFactory.exponentialDecay(numberOfNeurons,
159 1,
160 numSamplesPerTask / 2);
161
162 for (int i = 0; i < numTasks; i++) {
163 final KohonenUpdateAction action = new KohonenUpdateAction(distance,
164 learning,
165 neighbourhood);
166 tasks[i] = new KohonenTrainingTask(net,
167 createIterator(numSamplesPerTask,
168 cities,
169 random),
170 action);
171 }
172
173 return tasks;
174 }
175
176
177
178
179
180
181
182
183
184
185 private static Iterator<double[]> createIterator(final long numSamples,
186 final Set<City> uniqueCities,
187 final UniformRandomProvider random) {
188 final CollectionSampler<City> sampler = new CollectionSampler<>(random, uniqueCities);
189
190 return new Iterator<double[]>() {
191
192 private long n;
193
194 @Override
195 public boolean hasNext() {
196 return n < numSamples;
197 }
198
199 @Override
200 public double[] next() {
201 if (!hasNext()) {
202 throw new NoSuchElementException();
203 }
204 ++n;
205 return sampler.sample().getCoordinates();
206 }
207
208 @Override
209 public void remove() {
210 throw new UnsupportedOperationException();
211 }
212 };
213 }
214
215
216
217
218 private List<Neuron> getNeuronList() {
219
220 final List<Neuron> list = new ArrayList<>();
221
222
223 Neuron current = net.getNeuron(FIRST_NEURON_ID);
224 while (true) {
225 list.add(current);
226 final Collection<Neuron> neighbours
227 = net.getNeighbours(current, list);
228
229 final Iterator<Neuron> iter = neighbours.iterator();
230 if (!iter.hasNext()) {
231
232 break;
233 }
234
235 current = iter.next();
236 }
237
238 return list;
239 }
240
241
242
243
244 public List<double[]> getCoordinatesList() {
245
246 final List<double[]> coordinatesList = new ArrayList<>();
247
248 for (final Neuron n : getNeuronList()) {
249 coordinatesList.add(n.getFeatures());
250 }
251
252 return coordinatesList;
253 }
254
255
256
257
258
259
260
261 private List<City> getCityList(Set<City> cities) {
262 final List<double[]> coord = getCoordinatesList();
263 final List<City> cityList = new ArrayList<>();
264 City previous = null;
265 final int max = coord.size();
266 for (int i = 0; i < max; i++) {
267 final double[] c = coord.get(i);
268 final City next = City.closest(c[0], c[1], cities);
269 if (!next.equals(previous)) {
270 cityList.add(next);
271 previous = next;
272 }
273 }
274 return cityList;
275 }
276
277
278
279
280
281
282
283
284
285
286 private static FeatureInitializer[] makeInitializers(final int numNeurons,
287 final Set<City> uniqueCities,
288 final UniformRandomProvider random) {
289
290 final double[] centre = City.barycentre(uniqueCities);
291
292 final double radius = 0.5 * City.largestDistance(centre[0], centre[1], uniqueCities);
293
294 final double omega = 2 * Math.PI / numNeurons;
295 final DoubleUnaryOperator h1 = new HarmonicOscillator(radius, omega, 0, centre[0]);
296 final DoubleUnaryOperator h2 = new HarmonicOscillator(radius, omega, 0.5 * Math.PI, centre[1]);
297
298 final double r = 0.05 * radius;
299 final ContinuousUniformSampler u = new ContinuousUniformSampler(random, -r, r);
300
301 return new FeatureInitializer[] {
302 FeatureInitializerFactory.randomize(u, FeatureInitializerFactory.function(h1, 0, 1)),
303 FeatureInitializerFactory.randomize(u, FeatureInitializerFactory.function(h2, 0, 1))
304 };
305 }
306 }
307
308
309
310
311 class HarmonicOscillator implements DoubleUnaryOperator {
312
313 private final double amplitude;
314
315 private final double omega;
316
317 private final double phase;
318
319 private final double offset;
320
321
322
323
324
325
326
327 HarmonicOscillator(double amplitude,
328 double omega,
329 double phase,
330 double offset) {
331 this.amplitude = amplitude;
332 this.omega = omega;
333 this.phase = phase;
334 this.offset = offset;
335 }
336
337 @Override
338 public double applyAsDouble(double x) {
339 return offset + amplitude * Math.cos(omega * x + phase);
340 }
341 }