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