1 // -*- mode: C++; c-file-style: "cc-mode" -*-
2 //*************************************************************************
3 // DESCRIPTION: Verilator: Implementation of Christofides' algorithm to
4 // approximate the solution to the traveling salesman problem.
5 //
6 // ISSUES: This isn't exactly Christofides algorithm; see the TODO
7 // in perfectMatching(). True minimum-weight perfect matching
8 // would produce a better result. How much better is TBD.
9 //
10 // Code available from: https://verilator.org
11 //
12 //*************************************************************************
13 //
14 // Copyright 2003-2021 by Wilson Snyder. This program is free software; you
15 // can redistribute it and/or modify it under the terms of either the GNU
16 // Lesser General Public License Version 3 or the Perl Artistic License
17 // Version 2.0.
18 // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
19 //
20 //*************************************************************************
21
22 #include "config_build.h"
23 #include "verilatedos.h"
24
25 #include "V3Error.h"
26 #include "V3Global.h"
27 #include "V3File.h"
28 #include "V3Graph.h"
29 #include "V3TSP.h"
30
31 #include <cmath>
32 #include <list>
33 #include <memory>
34 #include <set>
35 #include <sstream>
36 #include <string>
37 #include <vector>
38 #include <unordered_set>
39 #include <unordered_map>
40
41 //######################################################################
42 // Support classes
43
44 namespace V3TSP {
45 static unsigned edgeIdNext = 0;
46
47 static void selfTestStates();
48 static void selfTestString();
49
50 VL_DEBUG_FUNC; // Declare debug()
51 } // namespace V3TSP
52
53 // Vertex that tracks a per-vertex key
54 template <typename T_Key> class TspVertexTmpl : public V3GraphVertex {
55 private:
56 const T_Key m_key;
57
58 public:
TspVertexTmpl(V3Graph * graphp,const T_Key & k)59 TspVertexTmpl(V3Graph* graphp, const T_Key& k)
60 : V3GraphVertex{graphp}
61 , m_key{k} {}
62 virtual ~TspVertexTmpl() override = default;
key() const63 const T_Key& key() const { return m_key; }
64
65 private:
66 VL_UNCOPYABLE(TspVertexTmpl);
67 };
68
69 // TspGraphTmpl represents a complete graph, templatized to work with
70 // different T_Key types.
71 template <typename T_Key> class TspGraphTmpl : public V3Graph {
72 public:
73 // TYPES
74 using Vertex = TspVertexTmpl<T_Key>;
75
76 // MEMBERS
77 std::unordered_map<T_Key, Vertex*> m_vertices; // T_Key to Vertex lookup map
78
79 // CONSTRUCTORS
TspGraphTmpl()80 TspGraphTmpl()
81 : V3Graph{} {}
82 virtual ~TspGraphTmpl() override = default;
83
84 // METHODS
addVertex(const T_Key & key)85 void addVertex(const T_Key& key) {
86 const auto itr = m_vertices.find(key);
87 UASSERT(itr == m_vertices.end(), "Vertex already exists with same key");
88 Vertex* v = new Vertex(this, key);
89 m_vertices[key] = v;
90 }
91
92 // For purposes of TSP, we are using non-directional graphs.
93 // Map that onto the normally-directional V3Graph by creating
94 // a matched pairs of opposite-directional edges to represent
95 // each non-directional edge:
addEdge(const T_Key & from,const T_Key & to,int cost)96 void addEdge(const T_Key& from, const T_Key& to, int cost) {
97 UASSERT(from != to, "Adding edge would form a loop");
98 Vertex* const fp = findVertex(from);
99 Vertex* const tp = findVertex(to);
100
101 // No need to dedup edges.
102 // The only time we may create duplicate edges is when
103 // combining the MST with the perfect-matched pairs,
104 // and in that case, we want to permit duplicate edges.
105 const unsigned edgeId = ++V3TSP::edgeIdNext;
106
107 // Record the 'id' which identifies a single bidir edge
108 // in the user field of each V3GraphEdge:
109 (new V3GraphEdge(this, fp, tp, cost))->user(edgeId);
110 (new V3GraphEdge(this, tp, fp, cost))->user(edgeId);
111 }
112
empty() const113 bool empty() const { return m_vertices.empty(); }
114
keysToVertexList(const std::vector<T_Key> & odds)115 const std::list<Vertex*> keysToVertexList(const std::vector<T_Key>& odds) {
116 std::list<Vertex*> vertices;
117 for (unsigned i = 0; i < odds.size(); ++i) { vertices.push_back(findVertex(odds.at(i))); }
118 return vertices;
119 }
120
121 class EdgeCmp final {
122 // Provides a deterministic compare for outgoing V3GraphEdge's
123 // to be used in Prim's algorithm below. Also used in the
124 // perfectMatching() routine.
125 public:
126 // CONSTRUCTORS
127 EdgeCmp() = default;
128 // METHODS
operator ()(const V3GraphEdge * ap,const V3GraphEdge * bp)129 bool operator()(const V3GraphEdge* ap, const V3GraphEdge* bp) {
130 const int aCost = ap->weight();
131 const int bCost = bp->weight();
132 // Sort first on cost, lowest cost edges first:
133 if (aCost < bCost) return true;
134 if (bCost < aCost) return false;
135 // Costs are equal. Compare edgeId's which should be unique.
136 return ap->user() < bp->user();
137 }
138
139 private:
140 VL_UNCOPYABLE(EdgeCmp);
141 };
142
castVertexp(V3GraphVertex * vxp)143 static Vertex* castVertexp(V3GraphVertex* vxp) { return dynamic_cast<Vertex*>(vxp); }
144
145 // From *this, populate *mstp with the minimum spanning tree.
146 // *mstp must be initially empty.
makeMinSpanningTree(TspGraphTmpl * mstp)147 void makeMinSpanningTree(TspGraphTmpl* mstp) {
148 UASSERT(mstp->empty(), "Output graph must start empty");
149
150 // Use Prim's algorithm to efficiently construct the MST.
151 std::unordered_set<Vertex*> visited_set;
152
153 EdgeCmp cmp;
154 using PendingEdgeSet = std::set<V3GraphEdge*, EdgeCmp&>;
155 // This is the set of pending edges from visited to unvisited
156 // nodes.
157 PendingEdgeSet pendingEdges(cmp);
158
159 vluint32_t vertCount = 0;
160 for (V3GraphVertex* vxp = verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
161 mstp->addVertex(castVertexp(vxp)->key());
162 vertCount++;
163 }
164
165 // Choose an arbitrary start vertex and visit it;
166 // all incident edges from this vertex go into a pending edge set.
167 Vertex* const start_vertexp = castVertexp(verticesBeginp());
168 visited_set.insert(start_vertexp);
169 for (V3GraphEdge* edgep = start_vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
170 pendingEdges.insert(edgep);
171 }
172
173 // Repeatedly find the least costly edge in the pending set.
174 // If it connects to an unvisited node, visit that node and update
175 // the pending edge set. If it connects to an already visited node,
176 // discard it and repeat again.
177 unsigned edges_made = 0;
178 while (!pendingEdges.empty()) {
179 const auto firstIt = pendingEdges.cbegin();
180 const V3GraphEdge* bestEdgep = *firstIt;
181 pendingEdges.erase(firstIt);
182
183 // bestEdgep->fromp() should be already seen
184 Vertex* const from_vertexp = castVertexp(bestEdgep->fromp());
185 UASSERT(visited_set.find(from_vertexp) != visited_set.end(), "Can't find vertex");
186
187 // If the neighbor is not yet visited, visit it and add its edges
188 // to the pending set.
189 Vertex* const neighborp = castVertexp(bestEdgep->top());
190 if (visited_set.find(neighborp) == visited_set.end()) {
191 const int bestCost = bestEdgep->weight();
192 UINFO(6, "bestCost = " << bestCost << " from " << from_vertexp->key() << " to "
193 << neighborp->key() << endl);
194
195 // Create the edge in our output MST graph
196 mstp->addEdge(from_vertexp->key(), neighborp->key(), bestCost);
197 edges_made++;
198
199 // Mark this vertex as visited
200 visited_set.insert(neighborp);
201
202 // Update the pending edges with new edges
203 for (V3GraphEdge* edgep = neighborp->outBeginp(); edgep;
204 edgep = edgep->outNextp()) {
205 pendingEdges.insert(edgep);
206 }
207 } else {
208 UINFO(6,
209 "Discarding edge to already-visited neighbor " << neighborp->key() << endl);
210 }
211 }
212
213 UASSERT(edges_made + 1 == vertCount, "Algorithm failed");
214 UASSERT(visited_set.size() == vertCount, "Algorithm failed");
215 }
216
217 // Populate *outp with a minimal perfect matching of *this.
218 // *outp must be initially empty.
perfectMatching(const std::vector<T_Key> & oddKeys,TspGraphTmpl * outp)219 void perfectMatching(const std::vector<T_Key>& oddKeys, TspGraphTmpl* outp) {
220 UASSERT(outp->empty(), "Output graph must start empty");
221
222 std::list<Vertex*> odds = keysToVertexList(oddKeys);
223 std::unordered_set<Vertex*> unmatchedOdds;
224 using VertexListIt = typename std::list<Vertex*>::iterator;
225 for (VertexListIt it = odds.begin(); it != odds.end(); ++it) {
226 outp->addVertex((*it)->key());
227 unmatchedOdds.insert(*it);
228 }
229
230 UASSERT(odds.size() % 2 == 0, "number of odd-order nodes should be even");
231
232 // TODO: The true Chrisofides algorithm calls for minimum-weight
233 // perfect matching. Instead, we have a simple greedy algorithm
234 // which might get close to the minimum, maybe, with luck?
235 //
236 // TODO: Revisit this. It's possible to compute the true minimum in
237 // N*N*log(N) time using variants of the Blossom algorithm.
238 // Implementing Blossom looks hard, maybe we can use an existing
239 // open source implementation -- for example the "LEMON" library
240 // which has a TSP solver.
241
242 // -----
243
244 // Reuse the comparator from Prim's routine. The logic is the same
245 // here. Note that the two V3GraphEdge's representing a single
246 // bidir edge will collide in the pendingEdges set here, but this
247 // is OK, we'll ignore the direction on the edge anyway.
248 EdgeCmp cmp;
249 using PendingEdgeSet = std::set<V3GraphEdge*, EdgeCmp&>;
250 PendingEdgeSet pendingEdges(cmp);
251
252 for (VertexListIt it = odds.begin(); it != odds.end(); ++it) {
253 for (V3GraphEdge* edgep = (*it)->outBeginp(); edgep; edgep = edgep->outNextp()) {
254 pendingEdges.insert(edgep);
255 }
256 }
257
258 // Iterate over all edges, in order from low to high cost.
259 // For any edge whose ends are both odd-order vertices which
260 // haven't been matched yet, match them.
261 for (typename PendingEdgeSet::iterator it = pendingEdges.begin(); it != pendingEdges.end();
262 ++it) {
263 Vertex* const fromp = castVertexp((*it)->fromp());
264 Vertex* const top = castVertexp((*it)->top());
265 if ((unmatchedOdds.find(fromp) != unmatchedOdds.end())
266 && (unmatchedOdds.find(top) != unmatchedOdds.end())) {
267 outp->addEdge(fromp->key(), top->key(), (*it)->weight());
268 unmatchedOdds.erase(fromp);
269 unmatchedOdds.erase(top);
270 }
271 }
272 UASSERT(unmatchedOdds.empty(), "Algorithm should have processed all vertices");
273 }
274
combineGraph(const TspGraphTmpl & g)275 void combineGraph(const TspGraphTmpl& g) {
276 std::unordered_set<vluint32_t> edges_done;
277 for (V3GraphVertex* vxp = g.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
278 const Vertex* const fromp = castVertexp(vxp);
279 for (V3GraphEdge* edgep = fromp->outBeginp(); edgep; edgep = edgep->outNextp()) {
280 const Vertex* const top = castVertexp(edgep->top());
281 if (edges_done.find(edgep->user()) == edges_done.end()) {
282 addEdge(fromp->key(), top->key(), edgep->weight());
283 edges_done.insert(edgep->user());
284 }
285 }
286 }
287 }
288
findEulerTourRecurse(std::unordered_set<unsigned> * markedEdgesp,Vertex * startp,std::vector<T_Key> * sortedOutp)289 void findEulerTourRecurse(std::unordered_set<unsigned>* markedEdgesp, Vertex* startp,
290 std::vector<T_Key>* sortedOutp) {
291 Vertex* cur_vertexp = startp;
292
293 // Go on a random tour. Fun!
294 std::vector<Vertex*> tour;
295 do {
296 UINFO(6, "Adding " << cur_vertexp->key() << " to tour.\n");
297 tour.push_back(cur_vertexp);
298
299 // Look for an arbitrary edge we've not yet marked
300 for (V3GraphEdge* edgep = cur_vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
301 const vluint32_t edgeId = edgep->user();
302 if (markedEdgesp->end() == markedEdgesp->find(edgeId)) {
303 // This edge is not yet marked, so follow it.
304 markedEdgesp->insert(edgeId);
305 Vertex* const neighborp = castVertexp(edgep->top());
306 UINFO(6, "following edge " << edgeId << " from " << cur_vertexp->key()
307 << " to " << neighborp->key() << endl);
308 cur_vertexp = neighborp;
309 goto found;
310 }
311 }
312 v3fatalSrc("No unmarked edges found in tour");
313 found:;
314 } while (cur_vertexp != startp);
315 UINFO(6, "stopped, got back to start of tour @ " << cur_vertexp->key() << endl);
316
317 // Look for nodes on the tour that still have
318 // un-marked edges. If we find one, recurse.
319 for (Vertex* vxp : tour) {
320 bool recursed;
321 do {
322 recursed = false;
323 // Look for an arbitrary edge at vxp we've not yet marked
324 for (V3GraphEdge* edgep = vxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
325 const vluint32_t edgeId = edgep->user();
326 if (markedEdgesp->end() == markedEdgesp->find(edgeId)) {
327 UINFO(6, "Recursing.\n");
328 findEulerTourRecurse(markedEdgesp, vxp, sortedOutp);
329 recursed = true;
330 goto recursed;
331 }
332 }
333 recursed:;
334 } while (recursed);
335 sortedOutp->push_back(vxp->key());
336 }
337
338 UINFO(6, "Tour was: ");
339 for (const Vertex* vxp : tour) UINFONL(6, " " << vxp->key());
340 UINFONL(6, "\n");
341 }
342
dumpGraph(std::ostream & os,const string & nameComment) const343 void dumpGraph(std::ostream& os, const string& nameComment) const {
344 // UINFO(0) as controlled by caller
345 os << "At " << nameComment << ", dumping graph. Keys:\n";
346 for (V3GraphVertex* vxp = verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
347 const Vertex* const tspvp = castVertexp(vxp);
348 os << " " << tspvp->key() << '\n';
349 for (V3GraphEdge* edgep = tspvp->outBeginp(); edgep; edgep = edgep->outNextp()) {
350 const Vertex* const neighborp = castVertexp(edgep->top());
351 os << " has edge " << edgep->user() << " to " << neighborp->key() << '\n';
352 }
353 }
354 }
dumpGraphFilePrefixed(const string & nameComment) const355 void dumpGraphFilePrefixed(const string& nameComment) const {
356 if (v3Global.opt.dumpTree()) {
357 const string filename = v3Global.debugFilename(nameComment) + ".txt";
358 const std::unique_ptr<std::ofstream> logp{V3File::new_ofstream(filename)};
359 if (logp->fail()) v3fatal("Can't write " << filename);
360 dumpGraph(*logp, nameComment);
361 }
362 }
363
findEulerTour(std::vector<T_Key> * sortedOutp)364 void findEulerTour(std::vector<T_Key>* sortedOutp) {
365 UASSERT(sortedOutp->empty(), "Output graph must start empty");
366 if (debug() >= 6) dumpDotFilePrefixed("findEulerTour");
367 std::unordered_set<unsigned /*edgeID*/> markedEdges;
368 // Pick a start node
369 Vertex* const start_vertexp = castVertexp(verticesBeginp());
370 findEulerTourRecurse(&markedEdges, start_vertexp, sortedOutp);
371 }
372
getOddDegreeKeys() const373 std::vector<T_Key> getOddDegreeKeys() const {
374 std::vector<T_Key> result;
375 for (V3GraphVertex* vxp = verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
376 const Vertex* const tspvp = castVertexp(vxp);
377 vluint32_t degree = 0;
378 for (V3GraphEdge* edgep = vxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
379 degree++;
380 }
381 if (degree & 1) result.push_back(tspvp->key());
382 }
383 return result;
384 }
385
386 private:
findVertex(const T_Key & key) const387 Vertex* findVertex(const T_Key& key) const {
388 const auto it = m_vertices.find(key);
389 UASSERT(it != m_vertices.end(), "Vertex not found");
390 return it->second;
391 }
392 VL_UNCOPYABLE(TspGraphTmpl);
393 };
394
395 //######################################################################
396 // Main algorithm
397
tspSort(const V3TSP::StateVec & states,V3TSP::StateVec * resultp)398 void V3TSP::tspSort(const V3TSP::StateVec& states, V3TSP::StateVec* resultp) {
399 UASSERT(resultp->empty(), "Output graph must start empty");
400
401 // Make this TSP implementation work for graphs of size 0 or 1
402 // which, unfortunately, is a special case as the following
403 // code assumes >= 2 nodes.
404 if (states.empty()) return;
405 if (states.size() == 1) {
406 resultp->push_back(*(states.begin()));
407 return;
408 }
409
410 // Build the initial graph from the starting state set.
411 using Graph = TspGraphTmpl<const TspStateBase*>;
412 Graph graph;
413 for (const auto& state : states) graph.addVertex(state);
414 for (V3TSP::StateVec::const_iterator it = states.begin(); it != states.end(); ++it) {
415 for (V3TSP::StateVec::const_iterator jt = it; jt != states.end(); ++jt) {
416 if (it == jt) continue;
417 graph.addEdge(*it, *jt, (*it)->cost(*jt));
418 }
419 }
420
421 // Create the minimum spanning tree
422 Graph minGraph;
423 graph.makeMinSpanningTree(&minGraph);
424 if (debug() >= 6) minGraph.dumpGraphFilePrefixed("minGraph");
425
426 const std::vector<const TspStateBase*> oddDegree = minGraph.getOddDegreeKeys();
427 Graph matching;
428 graph.perfectMatching(oddDegree, &matching);
429 if (debug() >= 6) matching.dumpGraphFilePrefixed("matching");
430
431 // Adds edges to minGraph, the resulting graph will have even number of
432 // edge counts at every vertex:
433 minGraph.combineGraph(matching);
434
435 V3TSP::StateVec prelim_result;
436 minGraph.findEulerTour(&prelim_result);
437
438 UASSERT(prelim_result.size() >= states.size(), "Algorithm size error");
439
440 // Discard duplicate nodes that the Euler tour might contain.
441 {
442 std::unordered_set<const TspStateBase*> seen;
443 for (V3TSP::StateVec::iterator it = prelim_result.begin(); it != prelim_result.end();
444 ++it) {
445 const TspStateBase* const elemp = *it;
446 if (seen.find(elemp) == seen.end()) {
447 seen.insert(elemp);
448 resultp->push_back(elemp);
449 }
450 }
451 }
452
453 UASSERT(resultp->size() == states.size(), "Algorithm size error");
454
455 // Find the most expensive arc and rotate the list so that the most
456 // expensive arc connects the last and first elements. (Since we're not
457 // modeling something that actually cycles back, we don't need to pay
458 // that cost at all.)
459 {
460 unsigned max_cost = 0;
461 unsigned max_cost_idx = 0;
462 for (unsigned i = 0; i < resultp->size(); ++i) {
463 const TspStateBase* const ap = (*resultp)[i];
464 const TspStateBase* const bp
465 = (i + 1 == resultp->size()) ? (*resultp)[0] : (*resultp)[i + 1];
466 const unsigned cost = ap->cost(bp);
467 if (cost > max_cost) {
468 max_cost = cost;
469 max_cost_idx = i;
470 }
471 }
472
473 if (max_cost_idx == resultp->size() - 1) {
474 // List is already rotated for minimum cost. stop.
475 return;
476 }
477
478 V3TSP::StateVec new_result;
479 unsigned i = max_cost_idx + 1;
480 UASSERT(i < resultp->size(), "Algorithm size error");
481 while (i != max_cost_idx) {
482 new_result.push_back((*resultp)[i]);
483 i++;
484 if (i >= resultp->size()) i = 0;
485 }
486 new_result.push_back((*resultp)[i]);
487
488 UASSERT(resultp->size() == new_result.size(), "Algorithm size error");
489 *resultp = new_result;
490 }
491 }
492
493 //######################################################################
494 // Self Tests
495
496 class TspTestState final : public V3TSP::TspStateBase {
497 public:
TspTestState(unsigned xpos,unsigned ypos)498 TspTestState(unsigned xpos, unsigned ypos)
499 : m_xpos{xpos}
500 , m_ypos{ypos}
501 , m_serial{++s_serialNext} {}
502 virtual ~TspTestState() override = default;
cost(const TspStateBase * otherp) const503 virtual int cost(const TspStateBase* otherp) const override {
504 return cost(dynamic_cast<const TspTestState*>(otherp));
505 }
diff(unsigned a,unsigned b)506 static unsigned diff(unsigned a, unsigned b) {
507 if (a > b) return a - b;
508 return b - a;
509 }
cost(const TspTestState * otherp) const510 virtual int cost(const TspTestState* otherp) const {
511 // For test purposes, each TspTestState is merely a point
512 // on the Cartesian plane; cost is the linear distance
513 // between two points.
514 unsigned xabs;
515 unsigned yabs;
516 xabs = diff(otherp->m_xpos, m_xpos);
517 yabs = diff(otherp->m_ypos, m_ypos);
518 return std::lround(std::sqrt(xabs * xabs + yabs * yabs));
519 }
xpos() const520 unsigned xpos() const { return m_xpos; }
ypos() const521 unsigned ypos() const { return m_ypos; }
522
operator <(const TspStateBase & other) const523 bool operator<(const TspStateBase& other) const override {
524 return operator<(dynamic_cast<const TspTestState&>(other));
525 }
operator <(const TspTestState & other) const526 bool operator<(const TspTestState& other) const { return m_serial < other.m_serial; }
527
528 private:
529 const unsigned m_xpos;
530 const unsigned m_ypos;
531 const unsigned m_serial;
532 static unsigned s_serialNext;
533 };
534
535 unsigned TspTestState::s_serialNext = 0;
536
selfTestStates()537 void V3TSP::selfTestStates() {
538 // Linear test -- coords all along the x-axis
539 {
540 V3TSP::StateVec states;
541 const TspTestState s10(10, 0);
542 const TspTestState s60(60, 0);
543 const TspTestState s20(20, 0);
544 const TspTestState s100(100, 0);
545 const TspTestState s5(5, 0);
546 states.push_back(&s10);
547 states.push_back(&s60);
548 states.push_back(&s20);
549 states.push_back(&s100);
550 states.push_back(&s5);
551
552 V3TSP::StateVec result;
553 tspSort(states, &result);
554
555 V3TSP::StateVec expect;
556 expect.push_back(&s100);
557 expect.push_back(&s60);
558 expect.push_back(&s20);
559 expect.push_back(&s10);
560 expect.push_back(&s5);
561 if (VL_UNCOVERABLE(expect != result)) {
562 for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) {
563 const TspTestState* const statep = dynamic_cast<const TspTestState*>(*it);
564 cout << statep->xpos() << " ";
565 }
566 cout << endl;
567 v3fatalSrc("TSP linear self-test fail. Result (above) did not match expectation.");
568 }
569 }
570
571 // Second test. Coords are distributed in 2D space.
572 // Test that tspSort() will rotate the list for minimum cost.
573 {
574 V3TSP::StateVec states;
575 const TspTestState a(0, 0);
576 const TspTestState b(100, 0);
577 const TspTestState c(200, 0);
578 const TspTestState d(200, 100);
579 const TspTestState e(150, 150);
580 const TspTestState f(0, 150);
581 const TspTestState g(0, 100);
582
583 states.push_back(&a);
584 states.push_back(&b);
585 states.push_back(&c);
586 states.push_back(&d);
587 states.push_back(&e);
588 states.push_back(&f);
589 states.push_back(&g);
590
591 V3TSP::StateVec result;
592 tspSort(states, &result);
593
594 V3TSP::StateVec expect;
595 expect.push_back(&f);
596 expect.push_back(&g);
597 expect.push_back(&a);
598 expect.push_back(&b);
599 expect.push_back(&c);
600 expect.push_back(&d);
601 expect.push_back(&e);
602
603 if (VL_UNCOVERABLE(expect != result)) {
604 for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) {
605 const TspTestState* const statep = dynamic_cast<const TspTestState*>(*it);
606 cout << statep->xpos() << "," << statep->ypos() << " ";
607 }
608 cout << endl;
609 v3fatalSrc(
610 "TSP 2d cycle=false self-test fail. Result (above) did not match expectation.");
611 }
612 }
613 }
614
selfTestString()615 void V3TSP::selfTestString() {
616 using Graph = TspGraphTmpl<std::string>;
617 Graph graph;
618 graph.addVertex("0");
619 graph.addVertex("1");
620 graph.addVertex("2");
621 graph.addVertex("3");
622
623 graph.addEdge("0", "1", 3943);
624 graph.addEdge("0", "2", 3456);
625 graph.addEdge("0", "3", 4920);
626 graph.addEdge("1", "2", 2730);
627 graph.addEdge("1", "3", 8199);
628 graph.addEdge("2", "3", 4130);
629
630 Graph minGraph;
631 graph.makeMinSpanningTree(&minGraph);
632 if (debug() >= 6) minGraph.dumpGraphFilePrefixed("minGraph");
633
634 const std::vector<string> oddDegree = minGraph.getOddDegreeKeys();
635 Graph matching;
636 graph.perfectMatching(oddDegree, &matching);
637 if (debug() >= 6) matching.dumpGraphFilePrefixed("matching");
638
639 minGraph.combineGraph(matching);
640
641 std::vector<string> result;
642 minGraph.findEulerTour(&result);
643
644 std::vector<string> expect;
645 expect.emplace_back("0");
646 expect.emplace_back("2");
647 expect.emplace_back("1");
648 expect.emplace_back("2");
649 expect.emplace_back("3");
650
651 if (VL_UNCOVERABLE(expect != result)) {
652 for (const string& i : result) cout << i << " ";
653 cout << endl;
654 v3fatalSrc("TSP string self-test fail. Result (above) did not match expectation.");
655 }
656 }
657
selfTest()658 void V3TSP::selfTest() {
659 selfTestString();
660 selfTestStates();
661 }
662