Zoltan2
partitioningTree.cpp
Go to the documentation of this file.
1 // @HEADER
2 //
3 // ***********************************************************************
4 //
5 // Zoltan2: A package of combinatorial algorithms for scientific computing
6 // Copyright 2012 Sandia Corporation
7 //
8 // Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
9 // the U.S. Government retains certain rights in this software.
10 //
11 // Redistribution and use in source and binary forms, with or without
12 // modification, are permitted provided that the following conditions are
13 // met:
14 //
15 // 1. Redistributions of source code must retain the above copyright
16 // notice, this list of conditions and the following disclaimer.
17 //
18 // 2. Redistributions in binary form must reproduce the above copyright
19 // notice, this list of conditions and the following disclaimer in the
20 // documentation and/or other materials provided with the distribution.
21 //
22 // 3. Neither the name of the Corporation nor the names of the
23 // contributors may be used to endorse or promote products derived from
24 // this software without specific prior written permission.
25 //
26 // THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
27 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
30 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 //
38 // Questions? Contact Karen Devine (kddevin@sandia.gov)
39 // Erik Boman (egboman@sandia.gov)
40 // Siva Rajamanickam (srajama@sandia.gov)
41 //
42 // ***********************************************************************
43 //
44 // @HEADER
48 #include <Zoltan2_TestHelpers.hpp>
49 #include <iostream>
50 #include <limits>
51 #include <Teuchos_ParameterList.hpp>
52 #include <Teuchos_RCP.hpp>
53 #include <Teuchos_FancyOStream.hpp>
54 #include <Teuchos_CommandLineProcessor.hpp>
55 #include <Tpetra_CrsMatrix.hpp>
56 #include <Tpetra_DefaultPlatform.hpp>
57 #include <Tpetra_Vector.hpp>
58 #include <MatrixMarket_Tpetra.hpp>
59 
60 using Teuchos::RCP;
61 using namespace std;
62 
63 typedef zlno_t z2TestLO;
64 typedef zgno_t z2TestGO;
66 
67 typedef Tpetra::CrsMatrix<z2TestScalar, z2TestLO, z2TestGO> SparseMatrix_t;
68 typedef Tpetra::Vector<z2TestScalar, z2TestLO, z2TestGO> Vector;
69 typedef Vector::node_type Node;
70 
71 typedef Tpetra::MultiVector<z2TestScalar, z2TestLO, z2TestGO,znode_t> tMVector_t;
72 
73 
75 
77 
79 
80 
81 
82 int testForRCB(SparseMatrixAdapter_t &matAdapter, int myrank, part_t numparts,
83  RCP<tMVector_t> coords, RCP<const Teuchos::Comm<int> > comm);
84 int testForPHG(SparseMatrixAdapter_t &matAdapter, int myrank, part_t numparts,
85  RCP<tMVector_t> coords, RCP<const Teuchos::Comm<int> >);
86 int testForMJ(SparseMatrixAdapter_t &matAdapter, int myrank, part_t numparts,
87  RCP<tMVector_t> coords, RCP<const Teuchos::Comm<int> >);
88 
89 
92 int main(int argc, char** argv)
93 {
94  std::string inputFile = ""; // Matrix Market or Zoltan file to read
95  std::string inputPath = testDataFilePath; // Directory with input file
96  bool distributeInput = true;
97  int success = 0;
98  part_t numParts = 8;
99 
100 
104  Teuchos::GlobalMPISession mpiSession(&argc, &argv, NULL);
105  RCP<const Teuchos::Comm<int> > comm =
106  Tpetra::DefaultPlatform::getDefaultPlatform().getComm();
107  int me = comm->getRank();
109 
111  // Read run-time options.
113  Teuchos::CommandLineProcessor cmdp (false, false);
114  cmdp.setOption("inputPath", &inputPath,
115  "Path to the MatrixMarket or Zoltan file to be read; "
116  "if not specified, a default path will be used.");
117  cmdp.setOption("inputFile", &inputFile,
118  "Name of the Matrix Market or Zoltan file to read; "
119  "");
120  cmdp.setOption("distribute", "no-distribute", &distributeInput,
121  "for Zoltan input files only, "
122  "indicate whether or not to distribute "
123  "input across the communicator");
124  cmdp.setOption("numParts", &numParts,
125  "Global number of parts;");
126 
127  Teuchos::CommandLineProcessor::EParseCommandLineReturn
128  parseReturn= cmdp.parse( argc, argv );
129 
130  if( parseReturn == Teuchos::CommandLineProcessor::PARSE_HELP_PRINTED )
131  {
132  return 0;
133  }
135 
137  // Construct matrix from file
139  RCP<UserInputForTests> uinput;
140 
141  if (inputFile != "") // Input file specified; read a matrix
142  {
143  uinput = rcp(new UserInputForTests(inputPath, inputFile, comm,
144  true, distributeInput));
145  }
146  else
147  {
148  std::cout << "Input file must be specified." << std::endl;
149  }
150 
151  RCP<SparseMatrix_t> origMatrix = uinput->getUITpetraCrsMatrix();
152 
153 
154  if (me == 0)
155  {
156  cout << "NumRows = " << origMatrix->getGlobalNumRows() << endl
157  << "NumNonzeros = " << origMatrix->getGlobalNumEntries() << endl
158  << "NumProcs = " << comm->getSize() << endl
159  << "NumParts = " << numParts << endl;
160  }
161 
162  if (origMatrix->getGlobalNumRows() < 40)
163  {
164  Teuchos::FancyOStream out(Teuchos::rcp(&std::cout,false));
165  origMatrix->describe(out, Teuchos::VERB_EXTREME);
166  }
168 
170  // Get coordinates, corresponding to graph vertices
172  RCP<tMVector_t> coords;
173  try
174  {
175  coords = uinput->getUICoordinates();
176  }
177  catch(...)
178  {
179  if (me == 0)
180  std::cout << "FAIL: get coordinates" << std::endl;
181  return 1;
182  }
184 
188  SparseMatrixAdapter_t matAdapter(origMatrix);
189 
190  MultiVectorAdapter_t *ca = NULL;
191 
192  try
193  {
194  ca = new MultiVectorAdapter_t(coords);
195  }
196  catch(...)
197  {
198  if (me == 0)
199  std::cout << "FAIL: vector adapter" << std::endl;
200  return 1;
201  }
202 
203  matAdapter.setCoordinateInput(ca);
205 
206  // MDM - MJ disabled - not implemented yet
207  // int errMJ = testForMJ(matAdapter, me, numParts, coords, comm); -check err
208  int errRCB = testForRCB(matAdapter, me, numParts, coords, comm);
209  int errPHG = testForPHG(matAdapter, me, numParts, coords, comm);
210 
211  // Currently just for internal development - sweep from 1 - 25 numParts
212  // and validate each to check for any bad results.
213  // #define BRUTE_FORCE_SEARCH
214  #ifdef BRUTE_FORCE_SEARCH
215  for(int setParts = 1; setParts <= 25; ++setParts) {
216  // numParts is a parameter so should not be set like for for production
217  // code - this is just a 2nd development check to run try a bunch of tests
218  numParts = setParts;
219  std::cout << "Brute force testing num parts: " << numParts << std::endl;
220  // MJ not implemented yet
221  // if(testForMJ(matAdapter, me, numParts, coords, comm) != 0) { return 1; }
222  if(testForRCB(matAdapter, me, numParts, coords, comm) != 0) { return 1; }
223  if(testForPHG(matAdapter, me, numParts, coords, comm) != 0) { return 1; }
224  }
225  #endif
226 
227  delete ca;
228 
229  // if(errMJ==0)
230  if(errRCB==0)
231  if(errPHG==0)
232  {
233  std::cout << "PASS" << std::endl;
234  return success;
235  }
236  return 1;
237 }
239 
240 
242 // Runs validation on the results to make sure the arrays are sensible
244 bool validate(part_t numTreeVerts,
245  std::vector<part_t> permPartNums,
246  std::vector<part_t> splitRangeBeg,
247  std::vector<part_t> splitRangeEnd,
248  std::vector<part_t> treeVertParents
249 )
250 {
251  // by design numTreeVerts does not include the root
252  if(numTreeVerts != static_cast<part_t>(splitRangeBeg.size()) - 1) {
253  return false;
254  }
255  if(numTreeVerts != static_cast<part_t>(splitRangeEnd.size()) - 1) {
256  return false;
257  }
258  if(numTreeVerts != static_cast<part_t>(treeVertParents.size()) - 1) {
259  return false;
260  }
261  // now search every node and validate it's properties
262  for(part_t n = 0; n <= numTreeVerts; ++n) {
263  if(n < static_cast<part_t>(permPartNums.size())) {
264  // terminal so children should just be range 1
265  if(splitRangeEnd[n] != splitRangeBeg[n] + 1) {
266  std::cout << "Invalid terminal - range should be 1" << std::endl;
267  return false;
268  }
269  if(splitRangeBeg[n] != n) {
270  std::cout << "Invalid terminal - not pointing to myself!" << std::endl;
271  return false;
272  }
273  }
274  else {
275  part_t beg = splitRangeBeg[n];
276  part_t end = splitRangeEnd[n];
277  part_t count = end - beg;
278  std::vector<bool> findChildren(count, false);
279  for(part_t n2 = 0; n2 <= numTreeVerts; ++n2) {
280  if(treeVertParents[n2] == n) {
281  part_t beg2 = splitRangeBeg[n2];
282  part_t end2 = splitRangeEnd[n2];
283  for(part_t q = beg2; q < end2; ++q) {
284  part_t setIndex = q - beg; // rel to parent so beg, not beg2
285  if(setIndex < 0 || setIndex >= count) {
286  std::cout << "Found bad child index - invalid results!" << std::endl;
287  return false;
288  }
289  if(findChildren[setIndex]) {
290  std::cout << "Found child twice - invalid results!" << std::endl;
291  return false;
292  }
293  findChildren[setIndex] = true;
294  }
295  }
296  }
297  for(part_t q = 0; q < count; ++q) {
298  if(!findChildren[q]) {
299  std::cout << "Did not find all children. Invalid results!" << std::endl;
300  return false;
301  }
302  }
303  }
304  if(n == numTreeVerts) {
305  // this is the root
306  if(splitRangeBeg[n] != 0) {
307  std::cout << "Root must start at 0!" << std::endl;
308  return false;
309  }
310  if(splitRangeEnd[n] != static_cast<part_t>(permPartNums.size())) {
311  std::cout << "Root must contain all parts!" << std::endl;
312  return false;
313  }
314  }
315  }
316  return true;
317 }
319 
321 // General test function to analyze solution and print out some information
324  RCP<const Teuchos::Comm<int> > comm)
325 {
326  part_t numTreeVerts = 0;
327  std::vector<part_t> permPartNums; // peritab in Scotch
328  std::vector<part_t> splitRangeBeg;
329  std::vector<part_t> splitRangeEnd;
330  std::vector<part_t> treeVertParents;
331 
333  problem.getSolution();
334 
335  solution.getPartitionTree(numTreeVerts,permPartNums,splitRangeBeg,
336  splitRangeEnd,treeVertParents);
337 
338  comm->barrier(); // for tidy output...
339  if(comm->getRank() == 0) { // for now just plot for rank 0
340 
341  // print the acquired information about the tree
342 
343  // Header
344  cout << endl << "Printing partition tree info..." << endl << endl;
345 
346  // numTreeVerts
347  cout << " numTreeVerts: " << numTreeVerts << endl << endl;
348 
349  // array index values 0 1 2 3 ...
350  cout << " part array index:";
351  for(part_t n = 0; n < static_cast<part_t>(permPartNums.size()); ++n) {
352  cout << setw(4) << n << " ";
353  }
354  cout << endl;
355 
356  // permParNums
357  cout << " permPartNums: ";
358  for(part_t n = 0; n < static_cast<part_t>(permPartNums.size()); ++n) {
359  cout << setw(4) << permPartNums[n] << " ";
360  }
361  cout << endl << endl;
362 
363  // node index values 0 1 2 3 ...
364  cout << " node index: ";
365  for(part_t n = 0; n < static_cast<part_t>(splitRangeBeg.size()); ++n) {
366  cout << setw(4) << n << " ";
367  }
368  cout << endl;
369 
370  // splitRangeBeg
371  cout << " splitRangeBeg: ";
372  for(part_t n = 0; n < static_cast<part_t>(splitRangeBeg.size()); ++n) {
373  cout << setw(4) << splitRangeBeg[n] << " ";
374  }
375  cout << endl;
376 
377  // splitRangeEnd
378  cout << " splitRangeEnd: ";
379  for(part_t n = 0; n < static_cast<part_t>(splitRangeEnd.size()); ++n) {
380  cout << setw(4) << splitRangeEnd[n] << " ";
381  }
382  cout << endl;
383 
384  // treeVertParents
385  cout << " treeVertParents: ";
386  for(part_t n = 0; n < static_cast<part_t>(treeVertParents.size()); ++n) {
387  cout << setw(4) << treeVertParents[n] << " ";
388  }
389  cout << endl << endl;
390  }
391  comm->barrier(); // for tidy output...
392 
393  if(!validate(numTreeVerts, permPartNums, splitRangeBeg, splitRangeEnd,
394  treeVertParents)) {
395  return 1;
396  }
397 
398  return 0;
399 }
400 
402 // Test partitioning tree access for RCB partitioning. This partitioning tree
403 // should be binary..
405 int testForRCB(SparseMatrixAdapter_t &matAdapter, int me, part_t numParts,
406  RCP<tMVector_t> coords, RCP<const Teuchos::Comm<int> > comm)
407 {
408 
412  Teuchos::ParameterList params;
413 
414  params.set("num_global_parts", numParts);
415  params.set("partitioning_approach", "partition");
416  params.set("algorithm", "rcb");
417  params.set("keep_partition_tree", true);
418 
420 
424  Zoltan2::PartitioningProblem<SparseMatrixAdapter_t> problem(&matAdapter, &params);
425 
426  try
427  {
428  if (me == 0) cout << "Calling solve() " << endl;
429  problem.solve();
430  if (me == 0) cout << "Done solve() " << endl;
431  }
432  catch (std::runtime_error &e)
433  {
434  cout << "Runtime exception returned from solve(): " << e.what();
435  if (!strncmp(e.what(), "BUILD ERROR", 11)) {
436  // Catching build errors as exceptions is OK in the tests
437  cout << " PASS" << endl;
438  return 0;
439  }
440  else {
441  // All other runtime_errors are failures
442  cout << " FAIL" << endl;
443  return -1;
444  }
445  }
446  catch (std::logic_error &e)
447  {
448  cout << "Logic exception returned from solve(): " << e.what()
449  << " FAIL" << endl;
450  return -1;
451  }
452  catch (std::bad_alloc &e)
453  {
454  cout << "Bad_alloc exception returned from solve(): " << e.what()
455  << " FAIL" << endl;
456  return -1;
457  }
458  catch (std::exception &e)
459  {
460  cout << "Unknown exception returned from solve(). " << e.what()
461  << " FAIL" << endl;
462  return -1;
463  }
465 
466 
468 
469  bool binary = solution.isPartitioningTreeBinary();
470 
471  if (binary == false)
472  {
473  cout << "RCB should produce a binary partitioning tree. FAIL" << std::endl;
474  return -1;
475  }
476 
477  return analyze(problem, comm);
478 }
480 
482 // Test partitioning tree access for PHG partitioning. This partitioning tree
483 // should be balanced.
485 int testForPHG(SparseMatrixAdapter_t &matAdapter, int me, part_t numParts,
486  RCP<tMVector_t> coords, RCP<const Teuchos::Comm<int> > comm)
487 {
488 
492  Teuchos::ParameterList params;
493 
494  params.set("num_global_parts", numParts);
495  params.set("partitioning_approach", "partition");
496  params.set("algorithm", "zoltan");
497  params.set("keep_partition_tree", true);
498 
499  Teuchos::ParameterList &zparams = params.sublist("zoltan_parameters",false);
500  zparams.set("LB_METHOD","phg");
501  zparams.set("FINAL_OUTPUT", "1");
502 
504 
508  Zoltan2::PartitioningProblem<SparseMatrixAdapter_t> problem(&matAdapter, &params);
509 
510  try
511  {
512  if (me == 0) cout << "Calling solve() " << endl;
513  problem.solve();
514  if (me == 0) cout << "Done solve() " << endl;
515  }
516  catch (std::runtime_error &e)
517  {
518  cout << "Runtime exception returned from solve(): " << e.what();
519  if (!strncmp(e.what(), "BUILD ERROR", 11)) {
520  // Catching build errors as exceptions is OK in the tests
521  cout << " PASS" << endl;
522  return 0;
523  }
524  else {
525  // All other runtime_errors are failures
526  cout << " FAIL" << endl;
527  return -1;
528  }
529  }
530  catch (std::logic_error &e)
531  {
532  cout << "Logic exception returned from solve(): " << e.what()
533  << " FAIL" << endl;
534  return -1;
535  }
536  catch (std::bad_alloc &e)
537  {
538  cout << "Bad_alloc exception returned from solve(): " << e.what()
539  << " FAIL" << endl;
540  return -1;
541  }
542  catch (std::exception &e)
543  {
544  cout << "Unknown exception returned from solve(). " << e.what()
545  << " FAIL" << endl;
546  return -1;
547  }
549 
551 
552  bool binary = solution.isPartitioningTreeBinary();
553 
554  if (binary == false)
555  {
556  cout << "PHG should produce a binary partitioning tree. FAIL" << std::endl;
557  return -1;
558  }
559 
560  return analyze(problem, comm);
561 }
563 
565 // Test partitioning tree access for MJ partitioning. This partitioning tree
566 // should be balanced.
568 int testForMJ(SparseMatrixAdapter_t &matAdapter, int me, part_t numParts,
569  RCP<tMVector_t> coords, RCP<const Teuchos::Comm<int> > comm)
570 {
571 
575  Teuchos::ParameterList params;
576 
577  params.set("num_global_parts", numParts);
578  params.set("algorithm", "multijagged");
579  params.set("rectilinear", true);
580  // params.set("mj_keep_part_boxes", true); // allows getPartBoxesView on solution
581  params.set("keep_partition_tree", true);
582 
584 
588  Zoltan2::PartitioningProblem<SparseMatrixAdapter_t> problem(&matAdapter, &params);
589 
590  try
591  {
592  if (me == 0) cout << "Calling solve() " << endl;
593  problem.solve();
594  if (me == 0) cout << "Done solve() " << endl;
595  }
596  catch (std::runtime_error &e)
597  {
598  cout << "Runtime exception returned from solve(): " << e.what();
599  if (!strncmp(e.what(), "BUILD ERROR", 11)) {
600  // Catching build errors as exceptions is OK in the tests
601  cout << " PASS" << endl;
602  return 0;
603  }
604  else {
605  // All other runtime_errors are failures
606  cout << " FAIL" << endl;
607  return -1;
608  }
609  }
610  catch (std::logic_error &e)
611  {
612  cout << "Logic exception returned from solve(): " << e.what()
613  << " FAIL" << endl;
614  return -1;
615  }
616  catch (std::bad_alloc &e)
617  {
618  cout << "Bad_alloc exception returned from solve(): " << e.what()
619  << " FAIL" << endl;
620  return -1;
621  }
622  catch (std::exception &e)
623  {
624  cout << "Unknown exception returned from solve(). " << e.what()
625  << " FAIL" << endl;
626  return -1;
627  }
629 
631 
632  // copied from the MultiJaggedTest.cpp to inspect boxes
633  // To activate set mj_keep_part_boxes true above
634  /*
635  std::vector<Zoltan2::coordinateModelPartBox<scalar_t, part_t> >
636  & pBoxes = solution.getPartBoxesView();
637  int coordDim = coords->getNumVectors();
638  std::cout << std::endl;
639  std::cout << "Plot final boxes..." << std::endl;
640  for (size_t i = 0; i < pBoxes.size(); i++) {
641  zscalar_t *lmin = pBoxes[i].getlmins();
642  zscalar_t *lmax = pBoxes[i].getlmaxs();;
643  int dim = pBoxes[i].getDim();
644  std::set<int> * pNeighbors = pBoxes[i].getNeighbors();
645  std::vector<int> * pGridIndices = pBoxes[i].getGridIndices();
646 
647  std::cout << me << " pBox " << i << " pid " << pBoxes[i].getpId()
648  << " dim: " << dim
649  << " (" << lmin[0] << "," << lmin[1] << ") "
650  << "x"
651  << " (" << lmax[0] << "," << lmax[1] << ")" << std::endl;
652  }
653  */
654 
655  bool binary = solution.isPartitioningTreeBinary();
656 
657  if (binary == true)
658  {
659  cout << "MJ should not produce a binary partitioning tree for this problem. FAIL" << std::endl;
660  return -1;
661  }
662 
663  return analyze(problem, comm);
664 }
666 
667 
668 
669 
Vector::node_type Node
Zoltan2::XpetraCrsMatrixAdapter< SparseMatrix_t, tMVector_t > SparseMatrixAdapter_t
Tpetra::CrsMatrix< z2TestScalar, z2TestLO, z2TestGO > SparseMatrix_t
Provides access for Zoltan2 to Xpetra::CrsMatrix data.
double zscalar_t
Tpetra::MultiVector< z2TestScalar, z2TestLO, z2TestGO, znode_t > tMVector_t
void getPartitionTree(part_t &numTreeVerts, std::vector< part_t > &permPartNums, std::vector< part_t > &splitRangeBeg, std::vector< part_t > &splitRangeEnd, std::vector< part_t > &treeVertParents) const
get the partition tree - fill the relevant arrays
int analyze(Zoltan2::PartitioningProblem< SparseMatrixAdapter_t > &problem, RCP< const Teuchos::Comm< int > > comm)
int main(int argc, char **argv)
int zlno_t
void setCoordinateInput(VectorAdapter< UserCoord > *coordData)
Allow user to provide additional data that contains coordinate info associated with the MatrixAdapter...
common code used by tests
Defines the XpetraMultiVectorAdapter.
SparseMatrixAdapter_t::part_t part_t
Defines the XpetraCrsMatrixAdapter class.
A PartitioningSolution is a solution to a partitioning problem.
bool validate(part_t numTreeVerts, std::vector< part_t > permPartNums, std::vector< part_t > splitRangeBeg, std::vector< part_t > splitRangeEnd, std::vector< part_t > treeVertParents)
InputTraits< User >::part_t part_t
An adapter for Xpetra::MultiVector.
int testForRCB(SparseMatrixAdapter_t &matAdapter, int myrank, part_t numparts, RCP< tMVector_t > coords, RCP< const Teuchos::Comm< int > > comm)
Zoltan2::XpetraMultiVectorAdapter< tMVector_t > MultiVectorAdapter_t
int zgno_t
const PartitioningSolution< Adapter > & getSolution()
Get the solution to the problem.
PartitioningProblem sets up partitioning problems for the user.
zgno_t z2TestGO
int testForMJ(SparseMatrixAdapter_t &matAdapter, int myrank, part_t numparts, RCP< tMVector_t > coords, RCP< const Teuchos::Comm< int > >)
zscalar_t z2TestScalar
Defines the PartitioningProblem class.
int testForPHG(SparseMatrixAdapter_t &matAdapter, int myrank, part_t numparts, RCP< tMVector_t > coords, RCP< const Teuchos::Comm< int > >)
virtual bool isPartitioningTreeBinary() const
calculate if partition tree is binary.
void solve(bool updateInputData=true)
Direct the problem to create a solution.
Tpetra::Vector< z2TestScalar, z2TestLO, z2TestGO > Vector
zlno_t z2TestLO
std::string testDataFilePath(".")