openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
mesher.cc
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: LicenseRef-APL-1.1
3 
22 #include <iostream>
23 #include <fstream>
24 #include <cctype>
25 #include <cstdio>
26 #include <cstring>
27 #include <string>
28 #include <cmath>
29 #include <stdlib.h>
30 #include <limits>
31 #include <algorithm>
32 #include <sys/wait.h>
33 #include <unistd.h>
34 #include <vector>
35 
36 #include "vect.h"
37 #include <mesher_p.h>
38 #include <mesher_d.h>
39 #include "mesher_schema.hpp"
40 #include "runtime.hpp"
41 #include "snapshot_file_io.hpp"
42 
43 
44 using namespace opencarp;
45 
46 typedef enum {Myocardium=1, Isobath, Anisobath } region_t;
47 
48 #define BOX_CENTERS_GRID false
49 #define NODE_GRID true
50 
51 Point p_assign_array( float *p )
52 {
53  Point a;
54  a.x = p[0]; a.y=p[1]; a.z=p[2];
55  return a;
56 }
57 
58 const Point e_circ = {1,0,0};
59 const Point e_long = {0,1,0};
60 const Point e_rad = {0,1,0};
61 
62 
63 
64 const float CM2UM=1.e4;
65 
66 class TisAxes {
67  public:
68  void set_xi(float xi_) { xi=xi_; }
69  void set_axes(float alpha_, float beta_pr_, float gamma_);
70  void set_bath_axes(bool);
71  Point fiber(void) {return f;}
72  Point sheet(void) {return s;}
73  private:
74  float xi;
75  float alpha;
76  float beta_pr;
77  float gamma;
78  Point f;
79  Point s;
80  Point sn;
81 };
82 
83 void
84 TisAxes::set_axes(float b, float c, float d)
85 {
86  alpha = b/180*M_PI;
87  beta_pr = c/180*M_PI;
88  gamma = d/180*M_PI;
89 
90  Point p(cos(alpha), sin(alpha), sin(gamma));
91  f = normalize(p);
92 
93  Point sp(0.0, sin(double(beta_pr)), cos(double(beta_pr)));
94  float lambda = -dot(f, sp)/dot(f, e_circ);
95  s = normalize(sp + scal_X(e_circ,lambda));
96 }
97 
98 void
99 TisAxes::set_bath_axes(bool aniso_bath)
100 {
101  f.y = f.z = 0.0;
102  s.x = s.y = s.z = 0.0;
103 
104  f.x = aniso_bath?1.0:0.0;
105 }
106 
107 class Region {
108  public:
109  Region(Point p, int b): p0_(p), bath_(b), tag_(-1) {}
110  virtual ~Region() {}
111  virtual bool inside(Point) = 0;
112  bool isbath() { return bath_; }
113  int tag() { return tag_; }
114  void tag(int t) { tag_ = t; }
115  protected:
117  int bath_;
118  int tag_;
119 };
120 
121 class BlockRegion: public Region {
122  public:
123  BlockRegion(Point p0, Point p, int bth): Region(p0, bth), p1_(p) {}
124  virtual bool inside(Point p);
125  private:
126  Point p1_;
127 };
128 
129 class SphericalRegion: public Region {
130  public:
131  SphericalRegion(Point ctr, float r, int bth):
132  Region(ctr, bth), radius2_(r*r) {}
133  virtual bool inside(Point p) { return dist_2(p, p0_)<= radius2_; }
134  private:
135  float radius2_; //square of radius
136 };
137 
138 bool
140 {
141  return p.x >= p0_.x && p.x <= p1_.x && p.y >= p0_.y && p.y <= p1_.y &&
142  p.z >= p0_.z && p.z <= p1_.z;
143 }
144 
145 
146 class CylindricalRegion: public Region {
147  public:
148  CylindricalRegion(Point origin, Point dir, float r, float l, int bth):
149  Region(origin, bth), radius2_(r*r)
150  {axis_=normalize(dir);len_=(l==0.)?1.e36:l;}
151  virtual bool inside( Point p );
152  private:
153  Point axis_;
154  float radius2_;
155  float len_;
156 };
157 
158 bool
160 {
161  Point R = p - p0_;
162  float d = dot(R, axis_);
163 
164  return d>=0 && d<=len_ && mag2(R)-d*d <= radius2_;
165 }
166 
167 
168 class Element {
169  public:
170  Element(int N, const char *t):n_(N),p_(new int[N]),type_(t){}
171  ~Element() { delete[] p_; }
172  Point centre(Point *ctr);
173  int num(){ return n_; };
174  friend std::ostream& operator<<(std::ostream &, Element& );
176  protected:
177  int n_;
178  int *p_;
179  std::string type_;
180 };
181 
182 
184  Point result = ctr[p_[0]];
185  for( int i=1; i<n_; i++ ) result = result + ctr[p_[i]];
186  return scal_X( result, 1./(float)n_);
187 }
188 
189 
190 class Tetrahedron:public Element {
191  public:
192  Tetrahedron( int A, int B, int C, int D ): Element(4,"Tt") {
193  p_[0]=A; p_[1]=B; p_[2]=C; p_[3]=D; }
194  void chkNegVolume(Point *pts);
195 };
196 
197 
199  Point P01 = pts[p_[1]] - pts[p_[0]];
200  Point P02 = pts[p_[2]] - pts[p_[0]];
201  Point P03 = pts[p_[3]] - pts[p_[0]];
202  double vol = det3(P01,P02,P03); // 6x volume
203  if( vol < 0. )
204  std::swap(p_[0],p_[1]);
205 }
206 
207 class Hexahedron:public Element {
208  public:
209  Hexahedron( int A, int B, int C, int D, int E, int F, int G, int H ): Element(8,"Hx") {
210  p_[0]=A; p_[1]=B; p_[2]=C; p_[3]=D; p_[4]=E; p_[5]=F; p_[6]=G; p_[7]=H; }
211 };
212 
213 
214 class Quadrilateral:public Element {
215  public:
216  Quadrilateral( int A, int B, int C, int D ): Element(4,"Qd") {
217  p_[0] = A; p_[1] = B; p_[2] = C; p_[3] = D;
218  }
219 };
220 
221 
222 class Triangle:public Element {
223  public:
224  Triangle( int A, int B, int C ): Element(3,"Tr") {
225  p_[0] = A; p_[1] = B; p_[2] = C;
226  }
227 };
228 
229 
230 class Line:public Element {
231  public:
232  Line( int A, int B ): Element(2,"Ln"){p_[0]=A; p_[1]=B;}
233 };
234 
235 
236 std::ostream& operator<<( std::ostream& out, Element &e ) {
237  out << e.type_ << " ";
238  for( int i=0; i<e.num()-1; i++ )
239  out << e.p_[i] << " ";
240  out << e.p_[e.num()-1];
241  return out;
242 }
243 
244 std::ostream& operator<<( std::ostream& out, Point p ) {
245  out << p.x << " " << p.y << " " << p.z;
246  return out;
247 }
248 
249 
250 class tmProfile {
251  public:
252  ~tmProfile() { free(xi); free(ang); }
253  int read(char *fname);
254  float lookup(float xi);
255  int linear(float ang_endo, float ang_epi, float z_endo, float z_epi );
256  private:
257  int N;
258  float *xi;
259  float *ang;
260 };
261 
262 
263 int
264 tmProfile::linear (float ang_endo,float ang_epi, float z_endo, float z_epi)
265 {
266  float dz = 10; // sample the profile at a 10 um resolution
267  if( z_epi==z_endo ) // 2D case
268  N = 0;
269  else
270  N = (z_epi-z_endo)/dz;
271 
272  xi = (float *)malloc(sizeof(float)*(N+1));
273  ang = (float *)malloc(sizeof(float)*(N+1));
274 
275  if(xi==NULL || ang==NULL) {
276  std::cerr << "Memory allocation failed." << std::endl;
277  return -1;
278  }
279  else if( !N ) { // 2D
280  xi[0] = 0;
281  ang[0] = ang_endo;
282  } else {
283  float K = (ang_epi-ang_endo)/(z_epi-z_endo);
284  for(int i=0;i<=N;i++) {
285  float z = z_endo+i*dz;
286  xi[i] = (z-z_endo)/(z_epi-z_endo)-0.5;
287  ang[i] = ang_endo + K*(z-z_endo);
288  }
289  }
290  return 0;
291 }
292 
293 float
295 {
296  if( !N ) return ang[0];
297 
298  int i=0;
299  while(xi[i]<xi_ && i<N) i++;
300 
301  if((i==0)|| (i==N))
302  return ang[i];
303  else
304  return ang[i-1]+(ang[i]-ang[i-1])/(xi[i]-xi[i-1])*(xi_-xi[i-1]);
305 }
306 
307 int
308 tmProfile::read(char *fname)
309 {
310  FILE *profile = fopen(fname,"rt");
311  if(profile==NULL) {
312  fprintf( stderr, "Can't open transmural profile data file %s.\n", fname);
313  return -1;
314  }
315 
316  int err = fscanf(profile,"%d",&N);
317  xi = (float *)malloc(sizeof(float)*N);
318  ang = (float *)malloc(sizeof(float)*N);
319  for(int i=0; i<N; i++) {
320  xi[i] = (float)i/(float)(N-1)-0.5;
321  err = fscanf(profile,"%f",ang+i);
322  }
323  fclose(profile);
324 
325  return err;
326 }
327 
328 class BBoxDef {
329  public:
330  void update( Point p );
331 // private:
332  float xd = 0;
335  float yd = 0;
338  float zd = 0;
341 };
342 
343 void
345 {
346  if(p.x<x_mn) x_mn = p.x;
347  if(p.y<y_mn) y_mn = p.y;
348  if(p.z<z_mn) z_mn = p.z;
349  if(p.x>x_mx) x_mx = p.x;
350  if(p.y>y_mx) y_mx = p.y;
351  if(p.z>z_mx) z_mx = p.z;
352 }
353 
354 
355 class BoundingBox {
356  public:
357  virtual ~BoundingBox() { delete[] bx; delete[] res; }
358  void init( int *_bx, float *_res, Point p0);
359  void dims(void);
360  float z2xi(float z, bool nodeGrid);
361  float mn_z(bool nodeGrid){ return nodeGrid?nodes.z_mn:bctrs.z_mn; };
362  float mx_z(bool nodeGrid){ return nodeGrid?nodes.z_mx:bctrs.z_mx; };
363  public:
364  int *bx;
365  int bx_inds[3][2];
366  protected:
367  float *res;
370 };
371 
372 float
373 BoundingBox::z2xi (float z, bool nodeGrid)
374 {
375  float zd = nodeGrid?nodes.zd:bctrs.zd;
376  float z_mn = nodeGrid?nodes.z_mn:bctrs.z_mn;
377 
378  if(zd==0.)
379  return 0.;
380  else
381  return (z-z_mn)/zd-0.5;
382 }
383 
384 void
385 BoundingBox::init(int *_bx, float *_res, Point p0)
386 {
387  bx = new int [3];
388  res = new float [3];
389  for(int i=0;i<3;i++) {
390  bx [i] = _bx [i];
391  res[i] = _res[i];
392  bx_inds[i][0] = 0;
393  bx_inds[i][1] = bx[i]-1;
394  }
395 
396  // min corner of nodal bbx
397  nodes.update(p0);
398 
399  // min corner of center bbx
400  Point pc0 = p0;
401  pc0.x = p0.x + res[0]/2;
402  pc0.y = p0.y + res[1]/2;
403  pc0.z = p0.z + res[2]/2;
404  bctrs.update(pc0);
405 
406  // max corner of nodal bbx
407  Point p1 = p0;
408  p1.x = p0.x+bx[0]*res[0];
409  p1.y = p0.y+bx[1]*res[1];
410  p1.z = p0.z+bx[2]*res[2];
411  nodes.update(p1);
412 
413  // max corner of center bbx
414  Point pc1 = p1;
415  pc1.x = p1.x - res[0]/2;
416  pc1.y = p1.y - res[1]/2;
417  pc1.z = p1.z - res[2]/2;
418  bctrs.update(pc1);
419 
420  dims();
421 }
422 
423 void
425 {
426  // figure out dims in terms of nodes
427  nodes.xd = nodes.x_mx - nodes.x_mn;
428  nodes.yd = nodes.y_mx - nodes.y_mn;
429  nodes.zd = nodes.z_mx - nodes.z_mn;
430 
431  // and in terms of box centers
432  bctrs.xd = bctrs.x_mx - bctrs.x_mn;
433  bctrs.yd = bctrs.y_mx - bctrs.y_mn;
434  bctrs.zd = bctrs.z_mx - bctrs.z_mn;
435 }
436 
437 class fibDef {
438  public:
439  void setFiberDefs(char *f_prof, float fEndo, float fEpi, float imbr,
440  char *s_prof, float sEndo, float sEpi);
441  bool withSheets(void);
442  char *f_name(void) { return f_Prof; };
443  char *s_name(void) { return s_Prof; };
444  float imbrication() { return f_imbr; };
445  float rotEndo() { return f_Endo; };
446  float rotEpi() { return f_Epi; };
447  float sheetEndo() { return s_Endo; };
448  float sheetEpi() { return s_Epi; };
449 
450  private:
451  char *f_Prof;
452  float f_Endo;
453  float f_Epi;
454  float f_imbr;
455  char *s_Prof;
456  float s_Endo;
457  float s_Epi;
458 };
459 
460 void fibDef::setFiberDefs(char *f_prof, float fEndo, float fEpi, float imbr,
461  char *s_prof, float sEndo, float sEpi)
462 {
463  f_Prof = strdup(f_prof);
464  f_Endo = fEndo;
465  f_Epi = fEpi;
466  f_imbr = imbr;
467  s_Prof = strdup(s_prof);
468  s_Endo = sEndo;
469  s_Epi = sEpi;
470 }
471 
472 bool
474 {
475  if((f_imbr!=0.0) || (s_Endo!=0.0) || (s_Epi!=0.0) || (strcmp(s_Prof,"")))
476  return true;
477  else
478  return false;
479 
480 }
481 
482 
483 class Grid {
484  public:
485  Grid( char *, Region **, int );
486  virtual ~Grid() {}
487  void unPrMFiberDefs(void);
488  virtual void build_mesh(float*,float*,float*,bool *,float*,float,bool,int)=0;
489  virtual void output_boundary( char * )=0;
490  void add_element( Element &, region_t );
491  void set_indx_bounds(bool *sym);
492  bool chk_bath( int i, int j, int k );
493  bool os_good();
494  protected:
496  int npt;
499  std::ofstream pt_os, elem_os, lon_os, elemc_os, vec_os;
501  int num_axes;
505  int dim;
506 };
507 
508 class Grid2D : public Grid {
509  public:
510  virtual void build_mesh(float*, float*, float*, bool *, float*, float, bool, int);
511  virtual void output_boundary( char *fn ){};
512  Grid2D( char *m, Region **r ):Grid(m,r,2){}
513  private:
514  void add_tri( int, int, int );
515 };
516 
517 class Grid3D : public Grid {
518  public:
519  virtual void build_mesh(float*, float*, float*, bool *, float*, float, bool, int);
520  virtual void output_boundary( char *fn ){}
521  Grid3D( char *m, Region **r ):Grid(m,r,3){}
522  private:
523  void add_tet( int, int, int, int );
524 };
525 
526 class Grid1D : public Grid {
527  public:
528  virtual void build_mesh(float*, float*, float*, bool *, float*, float, bool, int);
529  virtual void output_boundary( char *fn ){}
530  Grid1D( char *m, Region **r ):Grid(m,r,1){}
531  private:
532  void add_line( int, int, int, int );
533 };
534 
535 
537 {
538  return pt_os.good() && lon_os.good() && elem_os.good();
539 }
540 
541 
542 Grid::Grid( char *msh, Region **r, int d ): region(r),dim(d)
543 {
544  std::string fname(msh);
545  fname = msh;
546  fname += ".lon";
547  lon_os.open(fname.c_str());
548 
549  fname = msh;
550  fname += ".pts";
551  pt_os.open(fname.c_str());
552 
553  fname = msh;
554  fname += ".elem";
555  elem_os.open(fname.c_str());
556 
557  fname = msh;
558  fname += ".vpts";
559  elemc_os.open(fname.c_str());
560 
561  fname = msh;
562  fname += ".vec";
563  vec_os.open(fname.c_str());
564 }
565 
566 
573 void
575 {
576  for(int i=0;i<3;i++) {
577  if(b_bbx.bx[i]>t_bbx.bx[i]) {
578  t_bbx.bx_inds[i][0] = 0;
579  if(sym[i])
580  t_bbx.bx_inds[i][0] = (b_bbx.bx[i]-t_bbx.bx[i])/2;
581  t_bbx.bx_inds[i][1] = t_bbx.bx_inds[i][0]+t_bbx.bx[i]-1;
582  }
583  }
584 }
585 
586 
595 bool
596 Grid::chk_bath(int i, int j, int k)
597 {
598  bool bth[] = { false, false, false };
599  int idx[] = { i, j, k };
600 
601  for(int cnt=0; cnt<dim; cnt++ )
602  if( idx[cnt]<t_bbx.bx_inds[cnt][0] || idx[cnt]>t_bbx.bx_inds[cnt][1] )
603  return true;
604 
605  return false;
606 }
607 
608 
609 void
611 {
612  f_def.setFiberDefs(param_globals::fibers.tm_fiber_profile,
613  param_globals::fibers.rotEndo,
614  param_globals::fibers.rotEpi,
615  param_globals::fibers.imbrication,
616  param_globals::fibers.tm_sheet_profile,
617  param_globals::fibers.sheetEndo,
618  param_globals::fibers.sheetEpi);
619 }
620 
626 void
628 {
629  elem_os << elem;
630  Point c = elem.centre(pt);
631  elemc_os << c << std::endl;
632  int r=-1;
633  int regid = regtype==Myocardium?1:0;
634  while( region[++r] != NULL ) {
635  if( region[r]->inside( c ) ) {
636  if( region[r]->isbath() ) { // myo or bath becomes bath
637  regid = region[r]->tag()<0 ? 0 : region[r]->tag();
638  regtype = region[r]->isbath()==1?Isobath:Anisobath;
639  } else if( regtype == Myocardium ) { // only myo becomes other myo
640  regid = region[r]->tag()<0 ? r+2 : region[r]->tag();
641  }
642  if(param_globals::first_reg)
643  break;
644  }
645  }
646 
647  if(regtype==Myocardium) {
648  float xi = t_bbx.z2xi(c.z,BOX_CENTERS_GRID);
649  elem.ax.set_xi(xi);
650  elem.ax.set_axes( f_xi.lookup(xi),s_xi.lookup(xi),f_def.imbrication());
651  }
652  else {
653  elem.ax.set_bath_axes(regtype==Anisobath);
654  if(regtype==Anisobath)
655  regid *= -1;
656  }
657 
658  elem_os << " " << regid << std::endl;
659  if(num_axes>1)
660  lon_os << elem.ax.fiber() << " " << elem.ax.sheet() << std::endl;
661  else
662  lon_os << elem.ax.fiber() << std::endl;
663 
664  // write fiber to vector file
665  vec_os << elem.ax.fiber() << std::endl;
666 }
667 
668 
681 void
682 Grid1D::build_mesh(float* x0, float* x, float* tissue, bool *sym, float *res,
683  float pert, bool aniso_bath, int periodic_bc)
684 {
685  bool eletype;
686  int p1, p2, p3, p4;
687  int bnx[3]; // number of cubes in each direction
688  int tnx[3]; // number of tissue cubes in each direction
689 
690  // determine number of boxes
691  for( int i=0; i<3; i++ ) {
692  bnx[i] = (int)(x[i]/res[i]);
693  tnx[i] = (int)(tissue[i]/res[i]);
694  }
695 
696  // determine origins for tissue and bath bounding box
697  Point pt0, pb0;
698  pt0.x = -tnx[0]*res[0]/2 + x0[0];
699  pt0.y = 0;
700  pt0.z = 0;
701 
702  pb0.x = sym[0]?pt0.x-(bnx[0]-tnx[0])*res[0]/2:pt0.x+x0[0];
703  pb0.y = 0.0;
704  pb0.z = 0.0;
705 
706  // determine bounding box of bath and tissue grids
707  b_bbx.init(bnx,res,pb0);
708  t_bbx.init(tnx,res,pt0);
709 
710  // set tissue index boundaries relative to bath grid
711  set_indx_bounds(sym);
712 
713  f_xi.linear( f_def.rotEndo(), f_def.rotEpi(), 0, 0 );
714  s_xi.linear( f_def.sheetEndo(), f_def.sheetEpi(), 0, 0 );
715 
716  // output the points file
717  npt = (bnx[0]+1);
718  std::cout << "Number of points: " << npt << std::endl;
719  pt = new Point[npt];
720  pt_os << npt << std::endl;
721  npt = 0;
722  for( int i=0; i<=bnx[0]; i++ ) {
723  pt[npt].assign<double>(x0[0]+i*res[0], 0, 0);
724 
725  if( i!=bnx[0] ) {
726  pt[npt].x += ((double)random()/(double)RAND_MAX)*pert*res[0];
727  }
728  pt_os << pt[npt++] << std::endl;
729  }
730  pt_os.close();
731 
732  int num_in_layer = (bnx[0]+1);
733  elem_os << bnx[0] << std::endl;
734  std::cout << "Number of linear elements: " << bnx[0] << std::endl;
735 
736  for( int i=0; i<bnx[0]; i++ ) {
737  p1 = i;
738  p2 = p1+1;
739 
740  region_t regtype = Myocardium;
741  if( chk_bath(i,0,0) )
742  regtype = aniso_bath?Anisobath:Isobath;
743 
744  Line l(p1, p2 );
745  add_element( l, regtype );
746  }
747  elem_os.close();
748  lon_os.close();
749  delete[] pt;
750 }
751 
752 
765 void
766 Grid2D::build_mesh(float* x0, float* x, float* tissue, bool *sym, float *res,
767  float pert, bool aniso_bath, int periodic_bc)
768 {
769  bool eletype;
770  int p1, p2, p3, p4;
771  int bnx[3]; // number of cubes in each direction
772  int tnx[3]; // number of tissue cubes in each direction
773 
774  // determine number of boxes
775  for( int i=0; i<3; i++ ) {
776  bnx[i] = (int)(x[i]/res[i]);
777  tnx[i] = (int)(tissue[i]/res[i]);
778  }
779 
780  // determine origins for tissue and bath bounding box
781  Point pt0, pb0;
782  pt0.x = -tnx[0]*res[0]/2 + x0[0];
783  pt0.y = -tnx[1]*res[1]/2 + x0[1];
784  pt0.z = 0.0;
785 
786  pb0.x = sym[0]?pt0.x-(bnx[0]-tnx[0])*res[0]/2:pt0.x+ x0[0];
787  pb0.y = sym[0]?pt0.y-(bnx[0]-tnx[0])*res[0]/2:pt0.y+ x0[1];
788  pb0.z = 0.0;
789 
790  // determine bounding box of bath and tissue grids
791  b_bbx.init(bnx,res,pb0);
792  t_bbx.init(tnx,res,pt0);
793 
794  // set tissue index boundaries relative to bath grid
795  set_indx_bounds(sym);
796 
797  // output the points file
798  npt = (bnx[0]+1)*(bnx[1]+1);
799  std::cout << "Number of points: " << npt << std::endl;
800  pt = new Point[npt];
801  pt_os << npt << std::endl;
802  npt = 0;
803  for( int j=0; j<=bnx[1]; j++ )
804  for( int i=0; i<=bnx[0]; i++ ) {
805  pt[npt].assign<double>(x0[0]+i*res[0], x0[1]+j*res[1], 0);
806 
807  if( j && j!=bnx[1] && i && i!=bnx[0] ) {
808  pt[npt].x += ((double)random()/(double)RAND_MAX)*pert*res[0];
809  pt[npt].y += ((double)random()/(double)RAND_MAX)*pert*res[1];
810  }
811  pt_os << pt[npt++] << std::endl;
812  }
813  pt_os.close();
814 
815  // determine periodic b.c. connections
816  int nper_cnnx = 0;
817  if( (periodic_bc&1) == 1 )
818  nper_cnnx += bnx[1]+1;
819  if( (periodic_bc&2) == 2 )
820  nper_cnnx += bnx[0]+1;
821 
822  int num_in_layer = (bnx[0]+1)*(bnx[1]+1);
823 
824  if(param_globals::tri2D) {
825  elem_os << 2*bnx[0]*bnx[1]+nper_cnnx << std::endl;
826  elemc_os << 2*bnx[0]*bnx[1]+nper_cnnx << std::endl;
827  std::cout << "Number of triangles: " << 2*bnx[0]*bnx[1] << std::endl;
828  } else {
829  elem_os << bnx[0]*bnx[1]+nper_cnnx << std::endl;
830  elemc_os << bnx[0]*bnx[1]+nper_cnnx << std::endl;
831  std::cout << "Number of quadrilaterals: " << bnx[0]*bnx[1] << std::endl;
832  }
833  if( nper_cnnx )
834  std::cout << "Number of periodic connections: " << nper_cnnx << std::endl;
835 
836  f_xi.linear( f_def.rotEndo(), f_def.rotEpi(), 0, 0 );
837  s_xi.linear( f_def.sheetEndo(), f_def.sheetEpi(), 0, 0 );
838 
839  num_axes = f_def.withSheets()?2:1;
840  lon_os << num_axes << std::endl;
841  if(num_axes>1)
842  std::cout << "Using orthotropic fiber setup." << std::endl;
843  else
844  std::cout << "Using transversely isotropic fiber setup." << std::endl;
845 
846  for( int j=0; j<bnx[1]; j++ ) {
847  eletype = j%2;
848  for( int i=0; i<bnx[0]; i++ ) {
849  p1 = j*(bnx[0]+1) + i;
850  p2 = p1+1;
851  p3 = (j+1)*(bnx[0]+1) + i;
852  p4 = p3+1;
853 
854  region_t regtype = Myocardium;
855  if( chk_bath(i,j,0) )
856  regtype = aniso_bath?Anisobath:Isobath;
857 
858  if( param_globals::tri2D ) {
859  if( eletype ) {
860  Triangle t1(p1, p2, p3), t2(p2, p4, p3);
861  add_element( t1, regtype );
862  add_element( t2, regtype );
863  } else {
864  Triangle t1(p1, p2, p4), t2(p1, p4, p3);
865  add_element( t1, regtype );
866  add_element( t2, regtype );
867  }
868  eletype = !eletype;
869  } else {
870  Quadrilateral q(p1,p2,p4,p3);
871  add_element( q, regtype );
872  }
873  }
874  }
875 
876  if( (periodic_bc&1) == 1 ) {
877  for( int i=0; i<=bnx[1]; i++ ) {
878  Line l( i*(bnx[0]+1), (i+1)*(bnx[0]+1)-1 );
879  elem_os << l << " " << param_globals::periodic_tag<<std::endl;
880  lon_os << "1 0 0" << std::endl;
881  }
882  }
883  if( (periodic_bc&2) == 2 ) {
884  for( int i=0; i<=bnx[0]; i++ ) {
885  Line l( i, (bnx[0]+1)*(bnx[1])+i );
886  elem_os << l << " " << param_globals::periodic_tag+1 << std::endl;
887  lon_os << "0 1 0" << std::endl;
888  }
889  }
890 
891  elem_os.close();
892  lon_os.close();
893  delete[] pt;
894 }
895 
896 
907 void
908 Grid3D::build_mesh( float* x0, float* x, float* tissue, bool *sym, float *res,
909  float pert, bool aniso_bath, int periodic_bc)
910 {
911  bool eletype;
912  int p1, p2, p3, p4, p5, p6, p7,p8;
913  int bnx[3]; // number of cubes in each direction
914  int tnx[3]; // number of tissue cubes in each direction
915 
916  // determine number of boxes
917  for( int i=0; i<3; i++ ) {
918  bnx[i] = (int)(x[i]/res[i]);
919  tnx[i] = (int)(tissue[i]/res[i]);
920  }
921 
922  // determine origins for tissue and bath bounding box
923  Point pt0, pb0;
924  pt0 = {x0[0],x0[1],x0[2]};
925 
926  pb0.x = sym[0]?pt0.x-(bnx[0]-tnx[0])*res[0]/2:pt0.x + x0[0];
927  pb0.y = sym[1]?pt0.y-(bnx[1]-tnx[1])*res[1]/2:pt0.y + x0[1];
928  pb0.z = sym[2]?pt0.z-(bnx[2]-tnx[2])*res[2]/2:pt0.z + x0[2];
929 
930  // determine bounding box of bath and tissue grids
931  b_bbx.init(bnx,res,pb0);
932  t_bbx.init(tnx,res,pt0);
933 
934  // set tissue index boundaries relative to bath grid
935  set_indx_bounds(sym);
936 
937 
938  // define fiber and sheet arrangements
939  if(strcmp(f_def.f_name(),"")) {
940  std::cout << "Reading transmural fiber rotation profile from " << f_def.f_name() << std::endl;
941  f_xi.read(f_def.f_name());
942  }
943  else
946 
947  if(strcmp(f_def.s_name(),"")) {
948  std::cout << "Reading transmural sheet profile from " << f_def.s_name() << std::endl;
949  s_xi.read(f_def.s_name());
950  }
951  else
954 
955  // output the points file
956  npt = (bnx[0]+1)*(bnx[1]+1)*(bnx[2]+1);
957  std::cout << "Number of points: " << npt << std::endl;
958  pt = new Point[npt];
959  pt_os << npt << std::endl;
960  npt = 0;
961  for( int k=0; k<=bnx[2]; k++ )
962  for( int j=0; j<=bnx[1]; j++ )
963  for( int i=0; i<=bnx[0]; i++ ) {
964  pt[npt] = {x0[0]+i*res[0], x0[1]+j*res[1], x0[2]+k*res[2]};
965  if( k && k!=bnx[2] && j && j!=bnx[1] && i && i!=bnx[0] ) {
966  pt[npt].x += ((double)random()/(double)RAND_MAX)*pert*res[0];
967  pt[npt].y += ((double)random()/(double)RAND_MAX)*pert*res[1];
968  pt[npt].z += ((double)random()/(double)RAND_MAX)*pert*res[2];
969  }
970  pt_os << pt[npt++] << std::endl;
971  }
972  pt_os.close();
973 
974  int num_in_layer = (bnx[0]+1)*(bnx[1]+1);
975  if(!param_globals::Elem3D) {
976  elem_os << 5*bnx[0]*bnx[1]*bnx[2] << std::endl;
977  elemc_os << 5*bnx[0]*bnx[1]*bnx[2] << std::endl;
978  std::cout << "Number of Tetrahedra: " << 5*bnx[0]*bnx[1]*bnx[2] << std::endl;
979  }
980  else {
981  elem_os << bnx[0]*bnx[1]*bnx[2] << std::endl;
982  elemc_os << bnx[0]*bnx[1]*bnx[2] << std::endl;
983  std::cout << "Number of Hexahedra: " << bnx[0]*bnx[1]*bnx[2] << std::endl;
984  }
985 
986  num_axes = f_def.withSheets()?2:1;
987  lon_os << num_axes << std::endl;
988  if(num_axes>1)
989  std::cout << "Using orthotropic fiber setup." << std::endl;
990  else
991  std::cout << "Using transversely isotropic fiber setup." << std::endl;
992 
993 
994  for( int k=0; k<bnx[2]; k++ ) {
995  eletype = k%2;
996  for( int j=0; j<bnx[1]; j++ ) {
997  // if bnx[0] is even we need to toggle
998  // otherwise we break the chessboard pattern
999  if(j && !(bnx[0]%2) )
1000  eletype = !eletype;
1001 
1002  for( int i=0; i<bnx[0]; i++ ) {
1003  p1 = k*num_in_layer + j*(bnx[0]+1) + i;
1004  p2 = p1+1;
1005  p3 = k*num_in_layer + (j+1)*(bnx[0]+1) + i;
1006  p4 = p3+1;
1007  p5 = (k+1)*num_in_layer + j*(bnx[0]+1) + i;
1008  p6 = p5+1;
1009  p7 = (k+1)*num_in_layer + (j+1)*(bnx[0]+1) + i;
1010  p8 = p7+1;
1011 
1012  region_t regtype = Myocardium;
1013  if(chk_bath(i,j,k))
1014  regtype = aniso_bath?Anisobath:Isobath;
1015 
1016  if(!param_globals::Elem3D) {
1017  // generate tet mesh
1018  if( eletype ) {
1019  Tetrahedron t1(p1, p2, p4, p6),
1020  t2(p1, p3, p4, p7),
1021  t3(p1, p4, p6, p7),
1022  t4(p1, p5, p6, p7),
1023  t5(p4, p6, p7, p8);
1024  t1.chkNegVolume( pt ); add_element( t1, regtype );
1025  t2.chkNegVolume( pt ); add_element( t2, regtype );
1026  t3.chkNegVolume( pt ); add_element( t3, regtype );
1027  t4.chkNegVolume( pt ); add_element( t4, regtype );
1028  t5.chkNegVolume( pt ); add_element( t5, regtype );
1029  } else {
1030  Tetrahedron t1(p1, p2, p3, p5),
1031  t2(p2, p3, p4, p8),
1032  t3(p2, p5, p6, p8),
1033  t4(p2, p3, p5, p8),
1034  t5(p3, p5, p7, p8);
1035  t1.chkNegVolume( pt ); add_element( t1, regtype );
1036  t2.chkNegVolume( pt ); add_element( t2, regtype );
1037  t3.chkNegVolume( pt ); add_element( t3, regtype );
1038  t4.chkNegVolume( pt ); add_element( t4, regtype );
1039  t5.chkNegVolume( pt ); add_element( t5, regtype );
1040  }
1041  eletype = !eletype;
1042  }
1043  else {
1044  // Hex
1045 
1046  // this is the original hex ordering. not compatible with the SF ansatzfunc scheme..
1047  // Hexahedron h1(p4, p3, p1, p2, p8, p6, p5, p7);
1048 
1049  Hexahedron h1(p5, p7, p8, p6, p1, p2, p4, p3);
1050  add_element( h1, regtype );
1051  }
1052  }
1053  }
1054  }
1055  elem_os.close();
1056  lon_os.close();
1057  elemc_os.close();
1058  vec_os.close();
1059  delete[] pt;
1060 }
1061 
1062 namespace {
1063 
1064 enum class ParserCompareMode {
1065  Off,
1066  Warn,
1067  Strict,
1068 };
1069 
1070 enum class ParserFallbackMode {
1071  Off,
1072  Legacy,
1073 };
1074 
1075 struct RuntimeCompatOptions {
1076  ParserFallbackMode fallback_mode = ParserFallbackMode::Off;
1077 };
1078 
1079 struct LegacyCompareInput {
1080  bool available = false;
1081  std::vector<std::string> runtime_args;
1082  std::string unavailable_reason;
1083 };
1084 
1085 std::string trim_copy(const std::string& value)
1086 {
1087  std::string::size_type first = 0;
1088  while (first < value.size() && std::isspace(static_cast<unsigned char>(value[first]))) {
1089  ++first;
1090  }
1091 
1092  std::string::size_type last = value.size();
1093  while (last > first && std::isspace(static_cast<unsigned char>(value[last - 1]))) {
1094  --last;
1095  }
1096 
1097  return value.substr(first, last - first);
1098 }
1099 
1100 std::string to_lower_ascii(std::string value)
1101 {
1102  for (std::string::size_type i = 0; i < value.size(); ++i) {
1103  value[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(value[i])));
1104  }
1105  return value;
1106 }
1107 
1108 bool parse_option_argument(int* index,
1109  int argc,
1110  char** argv,
1111  const std::string& attached_value,
1112  const char* option_name,
1113  std::string* value,
1114  std::string* error)
1115 {
1116  if (!attached_value.empty()) {
1117  *value = attached_value;
1118  return true;
1119  }
1120  if (*index + 1 >= argc) {
1121  *error = "Missing argument after " + std::string(option_name);
1122  return false;
1123  }
1124  *value = argv[++(*index)];
1125  return true;
1126 }
1127 
1128 bool filename_has_suffix(const std::string& filename, const char* suffix)
1129 {
1130  const std::string normalized_filename = to_lower_ascii(trim_copy(filename));
1131  const std::string normalized_suffix = to_lower_ascii(std::string(suffix == NULL ? "" : suffix));
1132  return normalized_filename.size() >= normalized_suffix.size() &&
1133  normalized_filename.compare(normalized_filename.size() - normalized_suffix.size(),
1134  normalized_suffix.size(),
1135  normalized_suffix) == 0;
1136 }
1137 
1138 bool match_long_option(const std::string& token, const char* option, std::string* attached_value)
1139 {
1140  *attached_value = std::string();
1141  if (token == option) {
1142  return true;
1143  }
1144 
1145  const std::string prefix = std::string(option) + "=";
1146  if (token.size() > prefix.size() && token.compare(0, prefix.size(), prefix) == 0) {
1147  *attached_value = token.substr(prefix.size());
1148  return true;
1149  }
1150 
1151  return false;
1152 }
1153 
1154 bool is_help_topic_candidate(const char* token)
1155 {
1156  return token != NULL && token[0] != '\0' && token[0] != '-' && token[0] != '+';
1157 }
1158 
1159 bool is_long_option_argument_error(const std::string& token,
1160  const char* option,
1161  const std::string& attached_value,
1162  std::string* error)
1163 {
1164  if (attached_value.empty()) {
1165  return false;
1166  }
1167 
1168  *error = "Unexpected argument for " + std::string(option) + " in '" + token + "'";
1169  return true;
1170 }
1171 
1172 bool normalize_runtime_args(int argc,
1173  char** argv,
1174  std::vector<std::string>* normalized,
1175  RuntimeCompatOptions* compat,
1176  std::string* error)
1177 {
1178  normalized->clear();
1179  compat->fallback_mode = ParserFallbackMode::Off;
1180 
1181  if (argc <= 0 || argv == NULL || argv[0] == NULL) {
1182  *error = "Missing program name";
1183  return false;
1184  }
1185 
1186  normalized->push_back(argv[0]);
1187 
1188  for (int i = 1; i < argc; ++i) {
1189  const std::string token = argv[i];
1190  if (token == "+") {
1191  break;
1192  }
1193 
1194  std::string attached_value;
1195 
1196  if (token == "+Help" || match_long_option(token, "--help", &attached_value)) {
1197  std::string topic = "PrM";
1198  if (!attached_value.empty()) {
1199  topic = attached_value;
1200  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
1201  topic = argv[++i];
1202  }
1203  normalized->push_back("+Help");
1204  normalized->push_back(topic);
1205  break;
1206  }
1207 
1208  if (token == "+Doc" || match_long_option(token, "--doc", &attached_value)) {
1209  std::string topic = "ALL";
1210  if (!attached_value.empty()) {
1211  topic = attached_value;
1212  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
1213  topic = argv[++i];
1214  }
1215  normalized->push_back("+Doc");
1216  normalized->push_back(topic);
1217  break;
1218  }
1219 
1220  if (token == "+Default" || match_long_option(token, "--default", &attached_value)) {
1221  if (token != "+Default" && is_long_option_argument_error(token, "--default", attached_value, error)) {
1222  return false;
1223  }
1224  normalized->push_back("+Default");
1225  break;
1226  }
1227 
1228  if (token == "+Run" || match_long_option(token, "--run", &attached_value)) {
1229  if (token != "+Run" && is_long_option_argument_error(token, "--run", attached_value, error)) {
1230  return false;
1231  }
1232  normalized->push_back("+Run");
1233  continue;
1234  }
1235 
1236  if (token == "+I" || match_long_option(token, "--interactive", &attached_value)) {
1237  if (!attached_value.empty()) {
1238  *error = "Unexpected argument for --interactive in '" + token + "'";
1239  } else {
1240  *error = "Unsupported option " + token + " (interactive mode is not available)";
1241  }
1242  return false;
1243  }
1244 
1245  if (match_long_option(token, "--param-fallback", &attached_value)) {
1246  std::string mode = attached_value;
1247  if (mode.empty()) {
1248  if (i + 1 >= argc) {
1249  *error = "Missing argument after --param-fallback";
1250  return false;
1251  }
1252  mode = argv[++i];
1253  }
1254 
1255  if (to_lower_ascii(trim_copy(mode)) != "legacy") {
1256  *error = "Unsupported value '" + mode + "' for --param-fallback (expected legacy)";
1257  return false;
1258  }
1259 
1260  compat->fallback_mode = ParserFallbackMode::Legacy;
1261  continue;
1262  }
1263 
1264  if (token == "+F" || match_long_option(token, "--file", &attached_value)) {
1265  std::string filename = attached_value;
1266  if (filename.empty()) {
1267  if (i + 1 >= argc) {
1268  *error = "Missing filename after " + token;
1269  return false;
1270  }
1271  filename = argv[++i];
1272  }
1273  normalized->push_back("+F");
1274  normalized->push_back(filename);
1275  continue;
1276  }
1277 
1278  if (token == "+Save" || match_long_option(token, "--save", &attached_value)) {
1279  std::string filename = attached_value;
1280  if (filename.empty()) {
1281  if (i + 1 >= argc) {
1282  *error = "Missing argument after " + token;
1283  return false;
1284  }
1285  filename = argv[++i];
1286  }
1287  normalized->push_back("+Save");
1288  normalized->push_back(filename);
1289  continue;
1290  }
1291 
1292  normalized->push_back(token);
1293  }
1294 
1295  return true;
1296 }
1297 
1298 bool build_legacy_compare_input(int argc, char** argv, LegacyCompareInput* input, std::string* error)
1299 {
1300  input->available = false;
1301  input->runtime_args.clear();
1302  input->unavailable_reason.clear();
1303 
1304  if (argc <= 0 || argv == NULL || argv[0] == NULL) {
1305  *error = "Missing program name";
1306  return false;
1307  }
1308 
1309  input->runtime_args.push_back(argv[0]);
1310  bool saw_passthrough_token_before_file = false;
1311 
1312  for (int i = 1; i < argc; ++i) {
1313  const std::string token = argv[i];
1314  if (token == "+") {
1315  break;
1316  }
1317 
1318  std::string attached_value;
1319 
1320  if (match_long_option(token, "--param-fallback", &attached_value)) {
1321  std::string ignored;
1322  if (!parse_option_argument(&i, argc, argv, attached_value, "--param-fallback", &ignored, error)) {
1323  return false;
1324  }
1325  continue;
1326  }
1327 
1328  if (token == "+Save" || match_long_option(token, "--save", &attached_value)) {
1329  std::string ignored;
1330  if (!parse_option_argument(&i, argc, argv, attached_value, token.c_str(), &ignored, error)) {
1331  return false;
1332  }
1333  continue;
1334  }
1335 
1336  if (token == "+Help" || match_long_option(token, "--help", &attached_value)) {
1337  std::string topic = "PrM";
1338  if (!attached_value.empty()) {
1339  topic = attached_value;
1340  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
1341  topic = argv[++i];
1342  }
1343  input->runtime_args.push_back("+Help");
1344  input->runtime_args.push_back(topic);
1345  input->available = true;
1346  return true;
1347  }
1348 
1349  if (token == "+Doc" || match_long_option(token, "--doc", &attached_value)) {
1350  std::string topic = "ALL";
1351  if (!attached_value.empty()) {
1352  topic = attached_value;
1353  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
1354  topic = argv[++i];
1355  }
1356  input->runtime_args.push_back("+Doc");
1357  input->runtime_args.push_back(topic);
1358  input->available = true;
1359  return true;
1360  }
1361 
1362  if (token == "+Default" || match_long_option(token, "--default", &attached_value)) {
1363  if (token != "+Default" && is_long_option_argument_error(token, "--default", attached_value, error)) {
1364  return false;
1365  }
1366  input->runtime_args.push_back("+Default");
1367  continue;
1368  }
1369 
1370  if (token == "+Run" || match_long_option(token, "--run", &attached_value)) {
1371  if (token != "+Run" && is_long_option_argument_error(token, "--run", attached_value, error)) {
1372  return false;
1373  }
1374  input->runtime_args.push_back("+Run");
1375  continue;
1376  }
1377 
1378  if (token == "+F" || match_long_option(token, "--file", &attached_value)) {
1379  std::string filename;
1380  if (!parse_option_argument(&i, argc, argv, attached_value, token.c_str(), &filename, error)) {
1381  return false;
1382  }
1383 
1384  if (saw_passthrough_token_before_file) {
1385  input->unavailable_reason =
1386  "legacy compare is unavailable when direct parameter arguments precede a .par input file";
1387  input->runtime_args.clear();
1388  input->runtime_args.push_back(argv[0]);
1389  return true;
1390  }
1391 
1392  if (!filename_has_suffix(filename, ".par")) {
1393  input->unavailable_reason =
1394  "legacy compare is unavailable for non-.par input file '" + filename + "'";
1395  input->runtime_args.clear();
1396  input->runtime_args.push_back(argv[0]);
1397  return true;
1398  }
1399 
1400  input->runtime_args.push_back("+F");
1401  input->runtime_args.push_back(filename);
1402  continue;
1403  }
1404 
1405  input->runtime_args.push_back(token);
1406  saw_passthrough_token_before_file = true;
1407  }
1408 
1409  input->available = input->runtime_args.size() > 1;
1410  if (!input->available && input->unavailable_reason.empty()) {
1411  input->unavailable_reason = "unable to reconstruct a legacy-compatible parameter input";
1412  }
1413  return true;
1414 }
1415 
1416 void populate_arg_pointers(const std::vector<std::string>& values, std::vector<char*>* argv)
1417 {
1418  argv->assign(values.size(), NULL);
1419  for (std::size_t i = 0; i < values.size(); ++i) {
1420  (*argv)[i] = const_cast<char*>(values[i].c_str());
1421  }
1422 }
1423 
1424 void print_lines(FILE* stream, const char* label, const std::vector<std::string>& lines)
1425 {
1426  for (std::size_t i = 0; i < lines.size(); ++i) {
1427  std::fprintf(stream, "%s%s\n", label, lines[i].c_str());
1428  }
1429 }
1430 
1431 ParserCompareMode parser_compare_mode()
1432 {
1433  const char* raw = std::getenv("OPENCARP_PARAM_COMPARE");
1434  if (raw == NULL) {
1435  return ParserCompareMode::Strict;
1436  }
1437 
1438  const std::string normalized = to_lower_ascii(trim_copy(raw));
1439  if (normalized.empty() || normalized == "1" || normalized == "on" || normalized == "true" ||
1440  normalized == "yes" || normalized == "strict" || normalized == "fail" || normalized == "error") {
1441  return ParserCompareMode::Strict;
1442  }
1443  if (normalized == "warn") {
1444  return ParserCompareMode::Warn;
1445  }
1446  if (normalized == "0" || normalized == "off" || normalized == "false" || normalized == "no") {
1447  return ParserCompareMode::Off;
1448  }
1449 
1450  return ParserCompareMode::Strict;
1451 }
1452 
1453 ParserFallbackMode parser_fallback_mode()
1454 {
1455  const char* raw = std::getenv("OPENCARP_PARAM_FALLBACK");
1456  if (raw == NULL) {
1457  return ParserFallbackMode::Off;
1458  }
1459 
1460  const std::string normalized = to_lower_ascii(trim_copy(raw));
1461  if (normalized.empty() || normalized == "0" || normalized == "off" || normalized == "false" ||
1462  normalized == "no") {
1463  return ParserFallbackMode::Off;
1464  }
1465  if (normalized == "legacy") {
1466  return ParserFallbackMode::Legacy;
1467  }
1468 
1469  return ParserFallbackMode::Off;
1470 }
1471 
1472 paramschema::ExecutionResult execute_parser_runtime_args(const std::vector<std::string>& runtime_args,
1473  const bool allow_save)
1474 {
1475  std::vector<char*> argv;
1476  populate_arg_pointers(runtime_args, &argv);
1477 
1478  paramschema::ExecutionOptions options;
1479  options.allow_save = allow_save;
1480  return paramschema::execute_legacy_cli(paramschema::mesher_schema(),
1481  static_cast<int>(argv.size()),
1482  argv.data(),
1483  options);
1484 }
1485 
1486 bool apply_parser_runtime_args(const std::vector<std::string>& runtime_args,
1487  const bool allow_save,
1488  paramschema::ExecutionResult* executed_out)
1489 {
1490  const paramschema::ExecutionResult executed = execute_parser_runtime_args(runtime_args, allow_save);
1491  if (executed_out != NULL) {
1492  *executed_out = executed;
1493  }
1494  print_lines(stderr, "parameter parser warning: ", executed.warnings);
1495  if (!executed.rendered_output.empty()) {
1496  std::fputs(executed.rendered_output.c_str(), stdout);
1497  }
1498  if (!executed.errors.empty() || executed.status == paramschema::ExecutionStatus::Fatal) {
1499  print_lines(stderr, "parameter parser error: ", executed.errors);
1500  return false;
1501  }
1502 
1503  if (executed.status == paramschema::ExecutionStatus::Help) {
1504  exit(EXIT_SUCCESS);
1505  }
1506 
1507  return true;
1508 }
1509 
1510 std::string parent_directory(const std::string& path)
1511 {
1512  const std::string::size_type slash = path.rfind('/');
1513  if (slash == std::string::npos) {
1514  return ".";
1515  }
1516  if (slash == 0) {
1517  return "/";
1518  }
1519  return path.substr(0, slash);
1520 }
1521 
1522 std::string join_path(const std::string& left, const std::string& right)
1523 {
1524  if (left.empty() || left == ".") {
1525  return right;
1526  }
1527  if (!left.empty() && left[left.size() - 1] == '/') {
1528  return left + right;
1529  }
1530  return left + "/" + right;
1531 }
1532 
1533 bool find_executable_on_path(const std::string& name, std::string* resolved_path)
1534 {
1535  if (name.empty() || name.find('/') != std::string::npos) {
1536  return false;
1537  }
1538 
1539  const char* path_env = std::getenv("PATH");
1540  if (path_env == NULL || path_env[0] == '\0') {
1541  return false;
1542  }
1543 
1544  const std::string path_list = path_env;
1545  std::string::size_type start = 0;
1546  while (start <= path_list.size()) {
1547  std::string::size_type end = path_list.find(':', start);
1548  if (end == std::string::npos) {
1549  end = path_list.size();
1550  }
1551 
1552  const std::string directory = path_list.substr(start, end - start);
1553  const std::string candidate = join_path(directory.empty() ? "." : directory, name);
1554  if (access(candidate.c_str(), X_OK) == 0) {
1555  *resolved_path = candidate;
1556  return true;
1557  }
1558 
1559  if (end == path_list.size()) {
1560  break;
1561  }
1562  start = end + 1;
1563  }
1564 
1565  return false;
1566 }
1567 
1568 bool resolve_legacy_snapshot_helper(const std::string& program_path, std::string* helper_path)
1569 {
1570  std::vector<std::string> resolved_program_paths;
1571  if (!program_path.empty()) {
1572  resolved_program_paths.push_back(program_path);
1573  }
1574 
1575  if (program_path.find('/') == std::string::npos) {
1576  std::string resolved_program_path;
1577  if (find_executable_on_path(program_path, &resolved_program_path)) {
1578  resolved_program_paths.push_back(resolved_program_path);
1579  }
1580  }
1581 
1582  for (std::size_t i = 0; i < resolved_program_paths.size(); ++i) {
1583  const std::string program_dir = parent_directory(resolved_program_paths[i]);
1584  const std::string parent_dir = parent_directory(program_dir);
1585 
1586  const std::vector<std::string> candidates = {
1587  join_path(program_dir, "mesher-param-parser-legacy-snapshot"),
1588  join_path(join_path(parent_dir, "tools/mesher"), "mesher-param-parser-legacy-snapshot"),
1589  };
1590 
1591  for (std::size_t j = 0; j < candidates.size(); ++j) {
1592  if (access(candidates[j].c_str(), X_OK) == 0) {
1593  *helper_path = candidates[j];
1594  return true;
1595  }
1596  }
1597  }
1598 
1599  return find_executable_on_path("mesher-param-parser-legacy-snapshot", helper_path);
1600 }
1601 
1602 bool create_temp_output_path(const char* suffix, std::string* path)
1603 {
1604  char temp_path[128];
1605  if (suffix != NULL && suffix[0] != '\0') {
1606  std::snprintf(temp_path, sizeof temp_path, "/tmp/mesher-parser-compare-XXXXXX%s", suffix);
1607  } else {
1608  std::snprintf(temp_path, sizeof temp_path, "/tmp/mesher-parser-compare-XXXXXX");
1609  }
1610 
1611  const int fd = suffix != NULL && suffix[0] != '\0' ? mkstemps(temp_path, static_cast<int>(std::strlen(suffix))) :
1612  mkstemp(temp_path);
1613  if (fd < 0) {
1614  return false;
1615  }
1616  close(fd);
1617  *path = temp_path;
1618  return true;
1619 }
1620 
1621 bool capture_current_parser_snapshot(paramschema::SnapshotResult* snapshot)
1622 {
1623  *snapshot = paramschema::snapshot_schema_state(paramschema::mesher_schema());
1624  print_lines(stderr, "parameter compare warning: ", snapshot->warnings);
1625  if (!snapshot->errors.empty()) {
1626  print_lines(stderr, "parameter compare error: ", snapshot->errors);
1627  return false;
1628  }
1629  return true;
1630 }
1631 
1632 void maybe_force_test_mismatch(paramschema::SnapshotResult* snapshot)
1633 {
1634  const char* raw = std::getenv("OPENCARP_PARAM_TEST_FORCE_MISMATCH");
1635  if (raw == NULL) {
1636  return;
1637  }
1638 
1639  const std::string normalized = to_lower_ascii(trim_copy(raw));
1640  if (normalized.empty() || normalized == "0" || normalized == "off" || normalized == "false" ||
1641  normalized == "no") {
1642  return;
1643  }
1644 
1645  if (!snapshot->entries.empty()) {
1646  snapshot->entries[0].value += "__forced_parser_compare_mismatch__";
1647  return;
1648  }
1649 
1650  paramschema::SnapshotEntry entry;
1651  entry.path = "mesh";
1652  entry.value = "__forced_parser_compare_mismatch__";
1653  snapshot->entries.push_back(entry);
1654 }
1655 
1656 bool run_legacy_snapshot_helper(const std::string& helper_path,
1657  const std::vector<std::string>& runtime_args,
1658  const std::string& output_path)
1659 {
1660  std::vector<std::string> child_values;
1661  child_values.reserve(runtime_args.size() + 4);
1662  child_values.push_back(helper_path);
1663  child_values.push_back("--snapshot-out");
1664  child_values.push_back(output_path);
1665  child_values.push_back("--");
1666  child_values.insert(child_values.end(), runtime_args.begin(), runtime_args.end());
1667 
1668  std::vector<char*> child_argv;
1669  child_argv.reserve(child_values.size() + 1);
1670  for (std::size_t i = 0; i < child_values.size(); ++i) {
1671  child_argv.push_back(const_cast<char*>(child_values[i].c_str()));
1672  }
1673  child_argv.push_back(NULL);
1674 
1675  const pid_t pid = fork();
1676  if (pid < 0) {
1677  std::perror("fork");
1678  return false;
1679  }
1680 
1681  if (pid == 0) {
1682  execv(helper_path.c_str(), child_argv.data());
1683  std::perror("execv");
1684  _exit(127);
1685  }
1686 
1687  int status = 0;
1688  if (waitpid(pid, &status, 0) < 0) {
1689  std::perror("waitpid");
1690  return false;
1691  }
1692 
1693  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
1694  if (WIFSIGNALED(status)) {
1695  std::fprintf(stderr, "parameter compare error: legacy snapshot helper terminated with signal %d\n",
1696  WTERMSIG(status));
1697  } else {
1698  std::fprintf(stderr, "parameter compare error: legacy snapshot helper exited with status %d\n",
1699  WEXITSTATUS(status));
1700  }
1701  return false;
1702  }
1703 
1704  return true;
1705 }
1706 
1707 bool legacy_fallback_requested(const RuntimeCompatOptions& compat)
1708 {
1709  return compat.fallback_mode == ParserFallbackMode::Legacy ||
1710  parser_fallback_mode() == ParserFallbackMode::Legacy;
1711 }
1712 
1713 void print_legacy_fallback_workaround()
1714 {
1715  std::fprintf(stderr,
1716  "parameter compare error: rerun with OPENCARP_PARAM_FALLBACK=legacy to continue with the legacy parameter state\n");
1717  std::fprintf(stderr,
1718  "parameter compare error: or add --param-fallback=legacy to the mesher command line\n");
1719 }
1720 
1721 bool restore_legacy_snapshot_state(const paramschema::SnapshotResult& legacy_snapshot)
1722 {
1723  const paramschema::SnapshotRestoreResult restored =
1724  paramschema::restore_snapshot_state(paramschema::mesher_schema(), legacy_snapshot);
1725  print_lines(stderr, "parameter compare warning: ", restored.warnings);
1726  if (!restored.errors.empty()) {
1727  print_lines(stderr, "parameter compare error: ", restored.errors);
1728  return false;
1729  }
1730  return true;
1731 }
1732 
1733 bool run_parser_legacy_compare(const std::vector<std::string>& runtime_args,
1734  const LegacyCompareInput& legacy_input,
1735  const RuntimeCompatOptions& compat)
1736 {
1737  const ParserCompareMode mode = parser_compare_mode();
1738  if (mode == ParserCompareMode::Off) {
1739  return true;
1740  }
1741  const bool fallback_to_legacy = legacy_fallback_requested(compat);
1742 
1743  if (runtime_args.empty()) {
1744  std::fprintf(stderr, "parameter compare error: unable to reconstruct mesher argv\n");
1745  if (fallback_to_legacy) {
1746  print_legacy_fallback_workaround();
1747  }
1748  return mode != ParserCompareMode::Strict;
1749  }
1750 
1751  paramschema::SnapshotResult parser_snapshot;
1752  if (!capture_current_parser_snapshot(&parser_snapshot)) {
1753  std::fprintf(stderr, "parameter compare error: unable to snapshot the parser runtime state\n");
1754  if (fallback_to_legacy) {
1755  print_legacy_fallback_workaround();
1756  }
1757  return mode != ParserCompareMode::Strict;
1758  }
1759  maybe_force_test_mismatch(&parser_snapshot);
1760 
1761  std::string helper_path;
1762  if (!resolve_legacy_snapshot_helper(runtime_args[0], &helper_path)) {
1763  std::fprintf(stderr, "parameter compare error: unable to locate mesher-param-parser-legacy-snapshot\n");
1764  if (fallback_to_legacy) {
1765  print_legacy_fallback_workaround();
1766  } else {
1767  std::fprintf(stderr,
1768  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
1769  }
1770  return mode != ParserCompareMode::Strict;
1771  }
1772 
1773  if (!legacy_input.available) {
1774  std::fprintf(stderr, "parameter compare warning: %s\n", legacy_input.unavailable_reason.c_str());
1775  if (fallback_to_legacy) {
1776  std::fprintf(stderr,
1777  "parameter compare warning: legacy fallback is unavailable because no legacy baseline exists for this input\n");
1778  }
1779  return true;
1780  }
1781 
1782  std::string snapshot_path;
1783  if (!create_temp_output_path("", &snapshot_path)) {
1784  std::fprintf(stderr, "parameter compare error: unable to create temporary snapshot file\n");
1785  if (fallback_to_legacy) {
1786  print_legacy_fallback_workaround();
1787  } else {
1788  std::fprintf(stderr,
1789  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
1790  }
1791  return mode != ParserCompareMode::Strict;
1792  }
1793 
1794  const bool helper_ok = run_legacy_snapshot_helper(helper_path, legacy_input.runtime_args, snapshot_path);
1795  paramschema::SnapshotResult legacy_snapshot;
1796  std::string read_error;
1797  const bool loaded = helper_ok &&
1798  paramschema::snapshotio::read_snapshot_file(snapshot_path, &legacy_snapshot, &read_error);
1799  unlink(snapshot_path.c_str());
1800 
1801  if (!loaded) {
1802  if (!read_error.empty()) {
1803  std::fprintf(stderr, "parameter compare error: %s\n", read_error.c_str());
1804  } else {
1805  std::fprintf(stderr, "parameter compare error: unable to capture the legacy parameter state\n");
1806  }
1807  if (fallback_to_legacy) {
1808  print_legacy_fallback_workaround();
1809  } else {
1810  std::fprintf(stderr,
1811  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
1812  }
1813  return mode != ParserCompareMode::Strict;
1814  }
1815 
1816  const paramschema::SnapshotComparisonResult comparison =
1817  paramschema::compare_snapshot_results(paramschema::mesher_schema(), parser_snapshot, legacy_snapshot);
1818  if (!comparison.errors.empty() || !comparison.mismatches.empty()) {
1819  print_lines(stderr, "", paramschema::format_snapshot_comparison_report(comparison, "parser compare"));
1820  std::fprintf(stderr,
1821  "parameter compare error: the parser runtime and legacy param() produced different parameter states\n");
1822  std::fprintf(stderr,
1823  "parameter compare error: please open an issue and include the triggering command line and parameter files\n");
1824  if (fallback_to_legacy) {
1825  if (!restore_legacy_snapshot_state(legacy_snapshot)) {
1826  print_legacy_fallback_workaround();
1827  return false;
1828  }
1829  std::fprintf(stderr, "parameter compare warning: continuing with the legacy parameter state\n");
1830  return true;
1831  }
1832 
1833  print_legacy_fallback_workaround();
1834  return mode != ParserCompareMode::Strict;
1835  }
1836 
1837  return true;
1838 }
1839 
1840 } // namespace
1841 
1842 
1843 int main( int argc, char *argv[] )
1844 {
1845  LegacyCompareInput legacy_input;
1846  std::vector<std::string> normalized_args;
1847  RuntimeCompatOptions compat;
1848  std::string normalize_error;
1849  if (!build_legacy_compare_input(argc, argv, &legacy_input, &normalize_error) ||
1850  !normalize_runtime_args(argc, argv, &normalized_args, &compat, &normalize_error)) {
1851  std::fprintf(stderr, "\n*** %s\n\n", normalize_error.c_str());
1852  exit(EXIT_FAILURE);
1853  }
1854 
1855  paramschema::ExecutionResult executed;
1856  if (!apply_parser_runtime_args(normalized_args, true, &executed)) {
1857  exit(EXIT_FAILURE);
1858  }
1859 
1860  if (legacy_input.available) {
1861  for (std::size_t i = 0; i < executed.validation.assignments.size(); ++i) {
1862  if (!executed.validation.assignments[i].synthesized) {
1863  continue;
1864  }
1865  legacy_input.available = false;
1866  legacy_input.runtime_args.clear();
1867  legacy_input.runtime_args.push_back(normalized_args[0]);
1868  legacy_input.unavailable_reason =
1869  "legacy compare is unavailable because the parser inferred optional controller counts from the original input";
1870  break;
1871  }
1872  }
1873 
1874  if (!run_parser_legacy_compare(normalized_args, legacy_input, compat)) {
1875  exit(EXIT_FAILURE);
1876  }
1877 
1878  float tsize[3], x0[3];
1879  bool symbath[3];
1880 
1881 
1882  for( int i=0; i<3; i++ ) {
1883  param_globals::size[i] *= CM2UM;
1884  param_globals::bath[i] *= CM2UM;
1885  param_globals::center[i] *= CM2UM;
1886  // in 2D case we don't allow a bath attached in the direction
1887  // perpendicular to the 2D surface
1888  if(param_globals::size[i]==0.) param_globals::bath[i] = 0.0;
1889  if(param_globals::bath[i]>=0) {
1890  tsize[i] = param_globals::size[i]+param_globals::bath[i];
1891  symbath[i] = false;
1892  x0[i] = -0.5*param_globals::size[i]+param_globals::center[i];
1893  }
1894  else {
1895  tsize[i] = param_globals::size[i]-2*param_globals::bath[i];
1896  symbath[i] = true;
1897  x0[i] = -0.5*tsize[i]+param_globals::center[i];
1898  }
1899  }
1900 
1901  Region **region = (Region**)calloc(param_globals::numRegions+1,sizeof(Region*));
1902  Point ctr;
1903  ctr.x = param_globals::center[0];
1904  ctr.y = param_globals::center[1];
1905  ctr.z = param_globals::center[2];
1906 
1907  RegionDef* regdef = param_globals::regdef;
1908 
1909  for (int r = 0; r < param_globals::numRegions; r++ ) {
1910  Point p0 = scal_X( p_assign_array(regdef[r].p0), CM2UM );
1911  if( !param_globals::size[2]) p0.z = 0;
1912  Point p1;
1913  switch(regdef[r].type) {
1914  case 0:
1915  if( !param_globals::size[2] ) p1.z = 0;
1916  p1 = scal_X( p_assign_array( regdef[r].p1 ), CM2UM );
1917  p1 = p1 + ctr;
1918  if( p0.x>p1.x ) std::swap( p0.x, p1.x );
1919  if( p0.y>p1.y ) std::swap( p0.y, p1.y );
1920  if( p0.z>p1.z ) std::swap( p0.z, p1.z );
1921  region[r] = new BlockRegion( p0, p1, regdef[r].bath );
1922  break;
1923  case 1:
1924  regdef[r].rad *= CM2UM;
1925  region[r] = new SphericalRegion( p0,regdef[r].rad, regdef[r].bath );
1926  break;
1927  case 2:
1928  Point axis;
1929  if( !param_globals::size[2] )
1930  axis = {0,0,1};
1931  else
1932  axis = p_assign_array( regdef[r].p1 );
1933  regdef[r].rad *= CM2UM;
1934  regdef[r].cyllen *= CM2UM;
1935  region[r] = new CylindricalRegion( p0, axis, regdef[r].rad,
1936  regdef[r].cyllen, regdef[r].bath );
1937  break;
1938  }
1939  region[r]->tag(regdef[r].tag);
1940  }
1941  region[param_globals::numRegions] = NULL;
1942 
1943 
1944  Grid *grid;
1945  if( !param_globals::size[1] && !param_globals::size[2] )
1946  grid = new Grid1D( param_globals::mesh, region );
1947  else if( !param_globals::size[2] )
1948  grid = new Grid2D( param_globals::mesh, region );
1949  else {
1950  grid = new Grid3D( param_globals::mesh, region );
1951  }
1952 
1953  if( grid->os_good() ) {
1954  grid->unPrMFiberDefs();
1955  grid->build_mesh(x0, tsize, param_globals::size, symbath, param_globals::resolution,
1956  param_globals::perturb, param_globals::anisoBath, param_globals::periodic);
1957  delete grid;
1958  }
1959  else {
1960  std::cerr << "ERROR:" << std::endl;
1961  std::cerr << "Could not open mesh files " << param_globals::mesh << ".* for writing!" << std::endl;
1962  std::cerr << "Aborting!" << std::endl;
1963  delete grid;
1964  return 1;
1965  }
1966 
1967  return 0;
1968 }
#define M_PI
Definition: ION_IF.h:57
void update(Point p)
Definition: mesher.cc:344
virtual bool inside(Point p)
Definition: mesher.cc:139
BlockRegion(Point p0, Point p, int bth)
Definition: mesher.cc:123
int * bx
Definition: mesher.cc:362
float z2xi(float z, bool nodeGrid)
Definition: mesher.cc:373
BBoxDef bctrs
Definition: mesher.cc:368
float mx_z(bool nodeGrid)
Definition: mesher.cc:362
float * res
Definition: mesher.cc:367
BBoxDef nodes
Definition: mesher.cc:369
void dims(void)
Definition: mesher.cc:424
int bx_inds[3][2]
Definition: mesher.cc:365
virtual ~BoundingBox()
Definition: mesher.cc:357
void init(int *_bx, float *_res, Point p0)
Definition: mesher.cc:385
float mn_z(bool nodeGrid)
Definition: mesher.cc:361
virtual bool inside(Point p)
Definition: mesher.cc:159
CylindricalRegion(Point origin, Point dir, float r, float l, int bth)
Definition: mesher.cc:148
~Element()
Definition: mesher.cc:171
int n_
Definition: mesher.cc:177
int num()
Definition: mesher.cc:173
TisAxes ax
Definition: mesher.cc:175
Element(int N, const char *t)
Definition: mesher.cc:170
Point centre(Point *ctr)
Definition: mesher.cc:183
std::string type_
Definition: mesher.cc:179
int * p_
Definition: mesher.cc:178
virtual void output_boundary(char *fn)
Definition: mesher.cc:529
Grid1D(char *m, Region **r)
Definition: mesher.cc:530
virtual void build_mesh(float *, float *, float *, bool *, float *, float, bool, int)
Definition: mesher.cc:682
Grid2D(char *m, Region **r)
Definition: mesher.cc:512
virtual void output_boundary(char *fn)
Definition: mesher.cc:511
virtual void build_mesh(float *, float *, float *, bool *, float *, float, bool, int)
Definition: mesher.cc:766
virtual void build_mesh(float *, float *, float *, bool *, float *, float, bool, int)
Definition: mesher.cc:908
virtual void output_boundary(char *fn)
Definition: mesher.cc:520
Grid3D(char *m, Region **r)
Definition: mesher.cc:521
Definition: mesher.cc:483
virtual void output_boundary(char *)=0
fibDef f_def
Definition: mesher.cc:502
std::ofstream pt_os
Definition: mesher.cc:499
virtual ~Grid()
Definition: mesher.cc:486
BoundingBox t_bbx
Definition: mesher.cc:498
std::ofstream elem_os
Definition: mesher.cc:499
bool os_good()
Definition: mesher.cc:536
virtual void build_mesh(float *, float *, float *, bool *, float *, float, bool, int)=0
int dim
Definition: mesher.cc:505
void set_indx_bounds(bool *sym)
Definition: mesher.cc:574
std::ofstream lon_os
Definition: mesher.cc:499
std::ofstream vec_os
Definition: mesher.cc:499
Point * pt
Definition: mesher.cc:495
Grid(char *, Region **, int)
Definition: mesher.cc:542
int num_axes
Definition: mesher.cc:501
void add_element(Element &, region_t)
Definition: mesher.cc:627
std::ofstream elemc_os
Definition: mesher.cc:499
int npt
Definition: mesher.cc:496
void unPrMFiberDefs(void)
Definition: mesher.cc:610
tmProfile f_xi
Definition: mesher.cc:503
bool chk_bath(int i, int j, int k)
Definition: mesher.cc:596
BoundingBox b_bbx
Definition: mesher.cc:497
tmProfile s_xi
Definition: mesher.cc:504
Region ** region
Definition: mesher.cc:500
Hexahedron(int A, int B, int C, int D, int E, int F, int G, int H)
Definition: mesher.cc:209
Definition: mesher.cc:230
Line(int A, int B)
Definition: mesher.cc:232
Quadrilateral(int A, int B, int C, int D)
Definition: mesher.cc:216
virtual bool inside(Point)=0
bool isbath()
Definition: mesher.cc:112
int tag_
Definition: mesher.cc:118
Region(Point p, int b)
Definition: mesher.cc:109
Point p0_
Definition: mesher.cc:116
virtual ~Region()
Definition: mesher.cc:110
int tag()
Definition: mesher.cc:113
void tag(int t)
Definition: mesher.cc:114
int bath_
Definition: mesher.cc:117
virtual bool inside(Point p)
Definition: mesher.cc:133
SphericalRegion(Point ctr, float r, int bth)
Definition: mesher.cc:131
Tetrahedron(int A, int B, int C, int D)
Definition: mesher.cc:192
void chkNegVolume(Point *pts)
Definition: mesher.cc:198
void set_axes(float alpha_, float beta_pr_, float gamma_)
Definition: mesher.cc:84
void set_xi(float xi_)
Definition: mesher.cc:68
Point fiber(void)
Definition: mesher.cc:71
void set_bath_axes(bool)
Definition: mesher.cc:99
Point sheet(void)
Definition: mesher.cc:72
Triangle(int A, int B, int C)
Definition: mesher.cc:224
char * s_name(void)
Definition: mesher.cc:443
float sheetEpi()
Definition: mesher.cc:448
float imbrication()
Definition: mesher.cc:444
float rotEpi()
Definition: mesher.cc:446
float rotEndo()
Definition: mesher.cc:445
void setFiberDefs(char *f_prof, float fEndo, float fEpi, float imbr, char *s_prof, float sEndo, float sEpi)
Definition: mesher.cc:460
float sheetEndo()
Definition: mesher.cc:447
bool withSheets(void)
Definition: mesher.cc:473
char * f_name(void)
Definition: mesher.cc:442
int read(char *fname)
Definition: mesher.cc:308
int linear(float ang_endo, float ang_epi, float z_endo, float z_epi)
Definition: mesher.cc:264
float lookup(float xi)
Definition: mesher.cc:294
~tmProfile()
Definition: mesher.cc:252
int main(int argc, char *argv[])
Definition: mesher.cc:1843
const float CM2UM
Definition: mesher.cc:64
const Point e_circ
Definition: mesher.cc:58
const Point e_rad
Definition: mesher.cc:60
region_t
Definition: mesher.cc:46
@ Anisobath
Definition: mesher.cc:46
@ Isobath
Definition: mesher.cc:46
@ Myocardium
Definition: mesher.cc:46
const Point e_long
Definition: mesher.cc:59
#define BOX_CENTERS_GRID
Definition: mesher.cc:48
std::ostream & operator<<(std::ostream &out, Element &e)
Definition: mesher.cc:236
Point p_assign_array(float *p)
Definition: mesher.cc:51
axis
split axis
Definition: kdpart.hpp:48
constexpr T min(T a, T b)
Definition: ion_type.h:18
constexpr T max(T a, T b)
Definition: ion_type.h:16
V det3(const vec3< V > &a, const vec3< V > &b, const vec3< V > &c)
Definition: vect.h:81
vec3< V > scal_X(const vec3< V > &a, S k)
Definition: vect.h:135
V dot(const vec3< V > &p1, const vec3< V > &p2)
Definition: vect.h:110
vec3< V > normalize(vec3< V > a)
Definition: vect.h:183
V dist_2(const vec3< V > &p1, const vec3< V > &p2)
Definition: vect.h:87
V mag2(const vec3< V > &vect)
Definition: vect.h:123
std::vector< std::string > runtime_args
Definition: sim_utils.cc:65
ParserFallbackMode fallback_mode
Definition: sim_utils.cc:59
bool available
Definition: sim_utils.cc:63
std::string unavailable_reason
Definition: sim_utils.cc:66
void assign(S ix, S iy, S iz)
Definition: vect.h:30