Zoltan2
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
test_driver.cpp
Go to the documentation of this file.
1 // @HEADER
2 // *****************************************************************************
3 // Zoltan2: A package of combinatorial algorithms for scientific computing
4 //
5 // Copyright 2012 NTESS and the Zoltan2 contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
10 /* \file test_driver.cpp
11  * \brief Test driver for Zoltan2. Facilitates generation of test problem via
12  * a simple .xml input interface
13  */
14 
15 // taking headers from existing driver template
16 // will keep or remove as needed
17 #include <UserInputForTests.hpp>
18 #include <Zoltan2_Typedefs.hpp>
19 #include <AdapterForTests.hpp>
22 
25 
30 
31 #include <Zoltan2_Parameters.hpp>
32 
33 #include <Teuchos_DefaultComm.hpp>
34 #include <Teuchos_XMLObject.hpp>
35 #include <Teuchos_FileInputSource.hpp>
36 #include <Teuchos_StackedTimer.hpp>
37 
38 #include <sstream>
39 #include <string>
40 #include <map>
41 #include <iostream>
42 #include <queue>
43 
44 using Teuchos::ParameterList;
45 using Teuchos::Comm;
46 using Teuchos::RCP;
47 using Teuchos::ArrayRCP;
48 using Teuchos::XMLObject;
49 using namespace Zoltan2_TestingFramework;
50 
51 using std::string;
52 using std::map;
53 using std::pair;
54 using std::exception;
55 using std::ostringstream;
56 using std::queue;
57 
58 #define ERRMSG(msg) if (rank == 0){ std::cerr << "FAIL: " << msg << std::endl; }
59 #define EXC_ERRMSG(msg, e) \
60 if (rank==0){ std::cerr << "FAIL: " << msg << std::endl << e.what() << std::endl;}
61 
62 void xmlToModelPList(const Teuchos::XMLObject &xml,
63  Teuchos::ParameterList & plist)
64 {
65  // This method composes a plist for the problem
66  Teuchos::XMLParameterListReader reader;
67  plist = reader.toParameterList(xml);
68 
69  // Get list of valid Zoltan2 Parameters
70  // Zoltan 2 parameters appear in the input file
71  // Right now we have default values stored in
72  // the parameter list, we would like to apply
73  // the options specified by the user in their
74  // input file
75  Teuchos::ParameterList zoltan2Parameters;
76  Zoltan2::createAllParameters(zoltan2Parameters);
77 
78  if (plist.isSublist("Zoltan2Parameters")) {
79  // Apply user specified zoltan2Parameters
80  ParameterList &sub = plist.sublist("Zoltan2Parameters");
81  zoltan2Parameters.setParameters(sub);
82  }
83 }
84 
85 bool getParameterLists(const string &inputFileName,
86  queue<ParameterList> &problems,
87  queue<ParameterList> &comparisons,
88  const RCP<const Teuchos::Comm<int> > & comm)
89 {
90  int rank = comm->getRank();
91  // return a parameter list of problem definitions
92  // and a parameter list for solution comparisons
93  Teuchos::FileInputSource inputSource(inputFileName);
94  if(rank == 0) {
95  std::cout << "Input file source: " << inputFileName << std::endl;
96  }
97  XMLObject xmlInput;
98 
99  // Try to get xmlObject from inputfile
100  try
101  {
102  xmlInput = inputSource.getObject();
103  }
104  catch(exception &e)
105  {
106  EXC_ERRMSG("Test Driver error: reading", e); // error reading input
107  return false;
108  }
109 
110  // get the parameter lists for each model
111  for(int i = 0; i < xmlInput.numChildren(); i++)
112  {
113  ParameterList plist;
114  xmlToModelPList(xmlInput.getChild(i), plist);
115 
116  if(plist.name() == "Comparison") {
117  comparisons.emplace(plist);
118  }
119  else {
120  problems.emplace(plist);
121  }
122  }
123 
124  return true;
125 }
126 
127 // Utility function for safe type conversion of adapter
128 bool analyzeMetrics(RCP<EvaluateFactory> evaluateFactory,
129  std::ostringstream & msg,
130  const ParameterList &problem_parameters) {
131  #define ANALYZE_METRICS(adapterClass, metricAnalyzerClass) \
132  RCP<EvaluateBaseClass<adapterClass>> pCast = \
133  rcp_dynamic_cast<EvaluateBaseClass<adapterClass>>( \
134  evaluateFactory->getEvaluateClass()); \
135  if(pCast == Teuchos::null) throw std::logic_error( \
136  "Bad evaluate class cast in analyzeMetrics!" ); \
137  metricAnalyzerClass analyzer(pCast); \
138  return analyzer.analyzeMetrics( \
139  problem_parameters.sublist("Metrics"), msg);
140 
141  #define ANALYZE_METRICS_PARTITIONING(adapterClass) \
142  ANALYZE_METRICS(adapterClass, \
143  MetricAnalyzerEvaluatePartition<adapterClass>)
144 
145  #define ANALYZE_METRICS_ORDERING(adapterClass) \
146  ANALYZE_METRICS(adapterClass, \
147  MetricAnalyzerEvaluateOrdering<adapterClass>)
148 
149  if(evaluateFactory->getProblemName() == "partitioning") {
150  Z2_TEST_UPCAST(evaluateFactory->getAdapterType(), ANALYZE_METRICS_PARTITIONING)
151  }
152  else if(evaluateFactory->getProblemName() == "ordering") {
153  Z2_TEST_UPCAST(evaluateFactory->getAdapterType(), ANALYZE_METRICS_ORDERING)
154  }
155  else {
156  throw std::logic_error(
157  "analyzeMetrics not implemented for this problem type!" );
158  }
159 }
160 
161 // Utility function for safe type conversion of adapter
163  RCP<ProblemFactory> problemFactory) {
164  #define GET_LOCAL_ORDERING(adapterClass) \
165  return (rcp_dynamic_cast<OrderingProblem<adapterClass>>( \
166  problemFactory->getProblem()))->getLocalOrderingSolution();
167  Z2_TEST_UPCAST(problemFactory->getAdapterType(), GET_LOCAL_ORDERING)
168 }
169 
170 // Utility function for safe type conversion of adapter
171 const zpart_t * getPartListView(RCP<ProblemFactory> problemFactory) {
172  #define GET_PROBLEM_PARTS(adapterClass) \
173  return (rcp_dynamic_cast<PartitioningProblem<adapterClass>>( \
174  problemFactory->getProblem()))->getSolution().getPartListView();
175  Z2_TEST_UPCAST(problemFactory->getAdapterType(), GET_PROBLEM_PARTS)
176 }
177 
178 // Utility function for safe type conversion of adapter
179 void getIDsView(RCP<AdapterFactory> adapterFactory, const zgno_t *&Ids) {
180  #define GET_IDS_VIEW(adapterClass) \
181  return dynamic_cast<adapterClass*>( \
182  adapterFactory->getMainAdapter())->getIDsView(Ids);
183  Z2_TEST_UPCAST(adapterFactory->getMainAdapterType(), GET_IDS_VIEW);
184  throw std::logic_error( "getIDsView() failed to match adapter name" );
185 }
186 
187 bool run(const UserInputForTests &uinput,
188  const ParameterList &problem_parameters,
189  bool bHasComparisons,
190  RCP<ComparisonHelper> & comparison_helper,
191  const RCP<const Teuchos::Comm<int> > & comm)
192 {
193  // Major steps in running a problem in zoltan 2
194  // 1. get an input adapter
195  // 2. construct the problem
196  // 3. solve the problem
197  // 4. analyze metrics
198  // 5. clean up
199 
200  int rank = comm->getRank();
201  if(!problem_parameters.isParameter("kind"))
202  {
203  if(rank == 0) {
204  std::cout << "Problem kind not provided" << std::endl;
205  }
206  return false;
207  }
208  if(!problem_parameters.isParameter("InputAdapterParameters"))
209  {
210  if(rank == 0) {
211  std::cout << "Input adapter parameters not provided" << std::endl;
212  }
213  return false;
214  }
215  if(!problem_parameters.isParameter("Zoltan2Parameters"))
216  {
217  if(rank == 0) {
218  std::cout << "Zoltan2 problem parameters not provided" << std::endl;
219  }
220  return false;
221  }
222 
223  if(rank == 0) {
224  std::cout << "\n\nRunning test: " << problem_parameters.name() << std::endl;
225  }
226 
228  // 0. add comparison source
230  auto comparison_source = comparison_helper->AddSource(problem_parameters.name());
231 
232  comparison_source->startBaseTimer();
233 
235  // 1. get basic input adapter
237  const ParameterList &adapterPlist =
238  problem_parameters.sublist("InputAdapterParameters");
239  comparison_source->startTimer("adapter construction time");
240 
241  // a pointer to a basic type
242  RCP<AdapterFactory> adapterFactory = rcp(new AdapterFactory(
243  const_cast<UserInputForTests*>(&uinput), adapterPlist, comm));
244 
245  comparison_source->stopTimer("adapter construction time");
246 
247  comparison_source->adapterFactory = adapterFactory; // saves until done
248 
250  // 2. construct a Zoltan2 problem
252  // If we are here we have an input adapter, no need to check for one.
253  string adapter_name = adapterPlist.get<string>("input adapter");
254  // get Zoltan2 Parameters
255  ParameterList zoltan2_parameters =
256  const_cast<ParameterList &>(problem_parameters.sublist("Zoltan2Parameters"));
257  if(rank == 0) {
258  std::cout << std::endl;
259  }
260 
261  comparison_source->startTimer("problem construction time");
262  std::string problem_kind = problem_parameters.get<std::string>("kind");
263  if (rank == 0) {
264  std::cout << "Creating a new " << problem_kind << " problem." << std::endl;
265  }
266 
267  RCP<ProblemFactory> problemFactory = rcp(new ProblemFactory(
268  problem_kind,
269  adapterFactory,
270  &zoltan2_parameters
271  #ifdef HAVE_ZOLTAN2_MPI
272  ,MPI_COMM_WORLD
273  #endif
274  ));
275 
276  if(rank == 0) {
277  std::cout << "Using input adapter type: " + adapter_name << std::endl;
278  }
279 
280  comparison_source->stopTimer("problem construction time");
281 
282  comparison_source->problemFactory = problemFactory; // saves until done
283 
285  // 3. Solve the problem
287  comparison_source->startTimer("solve time");
288 
289  problemFactory->getProblem()->solve();
290 
291  comparison_source->stopTimer("solve time");
292  if (rank == 0) {
293  std::cout << problem_kind + " problem solved." << std::endl;
294  }
295 
296 #undef KDDKDD
297 #ifdef KDDKDD
298  if(problem_kind == "partitioning") {
299  const base_adapter_t::gno_t *kddIDs = NULL;
300  getIDsView(adapterFactory, kddIDs);
301  for (size_t i = 0;
302  i < adapterFactory->getMainAdapter()->getLocalNumIDs(); i++) {
303  std::cout << rank << " LID " << i
304  << " GID " << kddIDs[i]
305  << " PART "
306  << getPartListView(problemFactory)[i]
307  << std::endl;
308  }
309  }
310  if (adapter_name == "XpetraCrsGraph") {
311  typedef xCG_xCG_t::lno_t lno_t;
312  typedef xCG_xCG_t::gno_t gno_t;
313  typedef xCG_xCG_t::scalar_t scalar_t;
314  const xCG_xCG_t * xscrsGraphAdapter =
315  dynamic_cast<const xCG_xCG_t *>(adapterFactory->getMainAdapter());
316 
317  int ewgtDim = xscrsGraphAdapter->getNumWeightsPerEdge();
318  lno_t localNumObj = xscrsGraphAdapter->getLocalNumVertices();
319  const gno_t *vertexIds;
320  xscrsGraphAdapter->getVertexIDsView(vertexIds);
321  const offset_t *offsets;
322  const gno_t *adjIds;
323  xscrsGraphAdapter->getEdgesView(offsets, adjIds);
324  for (int edim = 0; edim < ewgtDim; edim++) {
325  const scalar_t *weights;
326  int stride=0;
327  xscrsGraphAdapter->getEdgeWeightsView(weights, stride, edim);
328  for (lno_t i=0; i < localNumObj; i++)
329  for (offset_t j=offsets[i]; j < offsets[i+1]; j++)
330  std::cout << edim << " " << vertexIds[i] << " "
331  << adjIds[j] << " " << weights[stride*j] << std::endl;
332  }
333  }
334 #endif
335 
337  // 4. Print problem metrics
339  bool bSuccess = true;
340  if(problem_parameters.isSublist("Metrics") || bHasComparisons) {
341  RCP<EvaluateFactory> evaluateFactory = rcp(new EvaluateFactory(
342  problem_kind,
343  adapterFactory,
344  &zoltan2_parameters,
345  problemFactory));
346 
347  if(rank == 0) {
348  std::cout << "Create evaluate class for: " + problem_kind << std::endl;
349  }
350 
351  // must add for proper deletion
352  comparison_source->evaluateFactory = evaluateFactory; // saves until done
353 
354  std::ostringstream msgSummary;
355 
356  evaluateFactory->getEvaluateClass()->printMetrics(msgSummary);
357  if(rank == 0) {
358  std::cout << msgSummary.str();
359  }
360 
361  std::ostringstream msgResults;
362  if (!analyzeMetrics(evaluateFactory, msgResults, problem_parameters)) {
363  bSuccess = false;
364  std::cout << "MetricAnalyzer::analyzeMetrics() "
365  << "returned false and the test is FAILED." << std::endl;
366  }
367  if(rank == 0) {
368  std::cout << msgResults.str();
369  }
370 
371 //#define BDD
372 #ifdef BDD
373  if (problem_kind == "ordering") {
374  std::cout << "\nLet's examine the solution..." << std::endl;
375  LocalOrderingSolution<zlno_t> * localOrderingSolution =
376  getLocalOrderingSolution(problemFactory);
377  if (localOrderingSolution->haveSeparators() ) {
378  std::cout << "Number of column blocks: "
379  << localOrderingSolution->getNumSeparatorBlocks() << std::endl;
380  {
381  if (localOrderingSolution->havePerm()) {
382  std::cout << "permutation: {";
383  for (auto &x : localOrderingSolution->getPermutationRCPConst(false))
384  std::cout << " " << x;
385  std::cout << "}" << std::endl;
386  }
387 
388  if (localOrderingSolution->haveInverse()) {
389  std::cout << "inverse permutation: {";
390  for (auto &x : localOrderingSolution->getPermutationRCPConst(true))
391  std::cout << " " << x;
392  std::cout << "}" << std::endl;
393  }
394 
395  if (localOrderingSolution->haveSeparatorRange()) {
396  std::cout << "separator range: {";
397  for (auto &x : localOrderingSolution->getSeparatorRangeRCPConst())
398  std::cout << " " << x;
399  std::cout << "}" << std::endl;
400  }
401 
402  if (localOrderingSolution->haveSeparatorTree()) {
403  std::cout << "separator tree: {";
404  for (auto &x : localOrderingSolution->getSeparatorTreeRCPConst())
405  std::cout << " " << x;
406  std::cout << "}" << std::endl;
407  }
408  }
409  }
410  }
411 #endif
412 
413  comparison_source->stopBaseTimer();
414 
415  // write mesh solution
416  // if(problem_kind == "partitioning") {
417  // auto sol = reinterpret_cast<partitioning_problem_t *>(problem)->getSolution();
418  // MyUtils::writePartionSolution(sol.getPartListView(), ia->getLocalNumIDs(), comm);
419  // }
420  }
421 
422  return bSuccess;
423 }
424 
425 bool mainExecute(int narg, char *arg[], RCP<const Comm<int> > &comm)
426 {
427  Teuchos::RCP<Teuchos::StackedTimer> stacked_timer = Teuchos::rcp(new Teuchos::StackedTimer("Zoltan2_Driver"));;
428 
430  // (0) Set up MPI environment and timer
432  int rank = comm->getRank(); // get rank
433 
435  // (1) Get and read the input file
436  // the input file defines tests to be run
438  string inputFileName("");
439  if(narg > 1)
440  inputFileName = arg[1]; // user has provided an input file
441  else{
442  if(rank == 0){
443  std::cout << "\nFAILED to specify xml input file!" << std::endl;
444  std::ostringstream msg;
445  msg << "\nStandard use of test_driver.cpp:\n";
446  msg << "mpiexec -n <procs> ./Zoltan2_test_driver.exe <input_file.xml>\n";
447  std::cout << msg.str() << std::endl;
448  }
449  return false;
450  }
451 
453  // (2) Get All Input Parameter Lists
455  queue<ParameterList> problems, comparisons;
456  if( !getParameterLists(inputFileName, problems, comparisons, comm) ) {
457  return false;
458  }
459 
461  // (3) Get Input Data Parameters
463 
464  // assumes that first block will always be the input block
465  const ParameterList inputParameters = problems.front();
466  if(inputParameters.name() != "InputParameters")
467  {
468  if(rank == 0)
469  std::cout << "InputParameters not defined. Testing FAILED." << std::endl;
470  return false;
471  }
472 
473  // get the user input for all tests
474  // UserInputForTests uinput(inputParameters,comm);
475 
476  problems.pop();
477  comm->barrier();
478 
479  bool bPass = true;
480  if(true)
481  {
483  // (4) Perform all tests
485 // pamgen debugging
486 // MyUtils::writeMesh(uinput,comm);
487 // MyUtils::getConnectivityGraph(uinput, comm);
488 
489  RCP<ComparisonHelper> comparison_manager = rcp(new ComparisonHelper(stacked_timer));
490  while (!problems.empty()) {
491  UserInputForTests uinput(inputParameters,comm);
492 
493  if(uinput.hasInput()) {
494  if (!run(uinput, problems.front(), !comparisons.empty(),
495  comparison_manager, comm)) {
496  std::cout << "Problem run returned false" << std::endl;
497  bPass = false;
498  }
499  }
500  problems.pop();
501  }
502 
504  // (5) Compare solutions
506 
507  while (!comparisons.empty()) {
508  if (!comparison_manager->Compare(comparisons.front(),comm)) {
509  if (rank == 0) {
510  std::cout << "Comparison manager returned false so the "
511  << "test should fail." << std::endl;
512  }
513  bPass = false;
514  }
515  comparisons.pop();
516  }
517  }
518  else {
519  if(rank == 0) {
520  std::cout << "\nFAILED to load input data source. Skipping "
521  "all tests." << std::endl;
522  return false;
523  }
524  }
525 
526  stacked_timer->stopBaseTimer();
527  Teuchos::StackedTimer::OutputOptions options;
528  options.output_fraction = options.output_histogram = options.output_minmax = true;
529  stacked_timer->report(std::cout, comm, options);
530  std::string watchrProblemName = std::string("Zoltan2 test driver ") + std::to_string(comm->getSize()) + " ranks";
531  auto xmlOut = stacked_timer->reportWatchrXML(watchrProblemName, comm);
532  if (xmlOut.length())
533  std::cout << "\nAlso created Watchr performance report " << xmlOut << '\n';
534 
535  return bPass;
536 }
537 
538 int main(int narg, char *arg[])
539 {
540  Tpetra::ScopeGuard tscope(&narg, &arg);
541  Teuchos::RCP<const Teuchos::Comm<int> > comm = Tpetra::getDefaultComm();
542 
543  int result = 0;
544  int rank = comm->getRank();
545  try {
546  result = mainExecute(narg, arg, comm) ? 0 : 1; // code 0 is ok,
547  // 1 is a failed test
548  }
549  catch(std::logic_error &e) {
550  // logic_error exceptions can be thrown by EvaluatePartition or
551  // MetricAnalyzer if any problem is detected in the formatting of the
552  // input xml
553  if (rank == 0) {
554  std::cout << "Test driver for rank " << rank
555  << " caught the following exception: " << e.what() << std::endl;
556  }
557  result = 1; // fail for any exception
558  }
559  catch(std::runtime_error &e) {
560  std::cout << "Test driver for rank " << rank
561  << " caught the following exception: " << e.what() << std::endl;
562  result = 1; // fail for any exception
563  }
564  catch(std::exception &e) {
565  std::cout << "Test driver for rank " << rank
566  << " caught the following exception: " << e.what() << std::endl;
567  result = 1; // fail for any exception
568  }
569 
570  // clean up - reduce the result codes
571  comm->barrier();
572  int resultReduced;
573 
574  // for a passed test all of these values should return 0 -
575  // if any result is 1 this will reduce to 1 and the test will fail
576  reduceAll<int,int>(*comm, Teuchos::EReductionType::REDUCE_MAX, 1,
577  &result, &resultReduced);
578 
579  // provide a final message which guarantees that the test will fail
580  // if any of the processes failed
581  if (rank == 0) {
582  std::cout << "Test Driver with " << comm->getSize()
583  << " processes has reduced result code " << resultReduced
584  << " and is exiting in the "
585  << ((resultReduced == 0 ) ? "PASSED" : "FAILED") << " state."
586  << std::endl;
587  }
588 
589  return result;
590 }
keep typedefs that commonly appear in many places localized
#define GET_LOCAL_ORDERING(adapterClass)
Generate input for testing purposes.
const zpart_t * getPartListView(RCP< ProblemFactory > problemFactory)
LocalOrderingSolution< zlno_t > * getLocalOrderingSolution(RCP< ProblemFactory > problemFactory)
#define ANALYZE_METRICS_PARTITIONING(adapterClass)
bool getParameterLists(const string &inputFileName, queue< ParameterList > &problems, queue< ParameterList > &comparisons, const RCP< const Teuchos::Comm< int > > &comm)
Definition: test_driver.cpp:85
typename InputTraits< User >::scalar_t scalar_t
ProblemFactory class contains 1 static factory method.
Defines Parameter related enumerators, declares functions.
static ArrayRCP< ArrayRCP< zscalar_t > > weights
ArrayRCP< lno_t > & getPermutationRCPConst(bool inverse=false) const
Get (local) permuted GIDs by const RCP.
Returns a pointer to new test classes. Is not responsible for memory management!
ArrayRCP< lno_t > & getSeparatorRangeRCPConst() const
Get separator range by const RCP.
Store and compare solution sets from different problems.
map_t::global_ordinal_type gno_t
Definition: mapRemotes.cpp:27
Provides access for Zoltan2 to Xpetra::CrsGraph data.
bool havePerm() const
Do we have the direct permutation?
int main(int narg, char **arg)
Definition: coloring1.cpp:164
size_t getLocalNumVertices() const override
Returns the number of vertices on this process.
void createAllParameters(Teuchos::ParameterList &pList)
Create a list of all Zoltan2 parameters and validators.
bool haveSeparatorRange() const
Do we have the separator range?
ProblemFactory class contains 1 static factory method.
Defines the XpetraMultiVectorAdapter.
bool analyzeMetrics(RCP< EvaluateFactory > evaluateFactory, std::ostringstream &msg, const ParameterList &problem_parameters)
Defines XpetraCrsGraphAdapter class.
Defines the XpetraCrsMatrixAdapter class.
int zpart_t
#define Z2_TEST_UPCAST(adptr, TEMPLATE_ACTION)
int getNumWeightsPerEdge() const override
Returns the number (0 or greater) of edge weights.
lno_t getNumSeparatorBlocks() const
Get number of separator column blocks.
typename InputTraits< User >::gno_t gno_t
map_t::local_ordinal_type lno_t
Definition: mapRemotes.cpp:26
bool haveSeparators() const
Do we have the separators?
A class for comparing solutions, metrics, and timing data of Zoltan2 problems.
Defines the BasicIdentifierAdapter class.
void getVertexIDsView(const gno_t *&ids) const override
void xmlToModelPList(const Teuchos::XMLObject &xml, Teuchos::ParameterList &plist)
Definition: test_driver.cpp:62
void getEdgeWeightsView(const scalar_t *&weights, int &stride, int idx) const override
Provide a pointer to the edge weights, if any.
bool haveSeparatorTree() const
Do we have the separator tree?
void getIDsView(RCP< AdapterFactory > adapterFactory, const zgno_t *&Ids)
int run(const RCP< const Comm< int > > &comm, int numGlobalParts, int testCnt, std::string *thisTest)
void getEdgesView(const offset_t *&offsets, const gno_t *&adjIds) const override
#define ANALYZE_METRICS_ORDERING(adapterClass)
bool mainExecute(int narg, char *arg[], RCP< const Comm< int > > &comm)
#define EXC_ERRMSG(msg, e)
Definition: test_driver.cpp:59
#define GET_IDS_VIEW(adapterClass)
Tpetra::Map::global_ordinal_type zgno_t
typename InputTraits< User >::lno_t lno_t
ArrayRCP< lno_t > & getSeparatorTreeRCPConst() const
Get separator tree by const RCP.
#define GET_PROBLEM_PARTS(adapterClass)
Generate Adapter for testing purposes.
bool haveInverse() const
Do we have the inverse permutation?