cloudy  trunk
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
sanity_check.cpp
Go to the documentation of this file.
1 /* This file is part of Cloudy and is copyright (C)1978-2022 by Gary J. Ferland and
2  * others. For conditions of distribution and use see copyright notice in license.txt */
3 /*SanityCheck, check that various parts of the code still work, called by Cloudy after continuum
4  * and optical depth arrays are set up, but before initial temperature and ionization */
5 #include "cddefines.h"
6 #include "dense.h"
7 #include "elementnames.h"
8 #include "continuum.h"
9 #include "rfield.h"
10 #include "iso.h"
11 #include "opacity.h"
12 #include "hydro_bauman.h"
13 #include "heavy.h"
14 #include "trace.h"
15 #include "freebound.h"
16 #include "integrate.h"
17 #include "atmdat_gaunt.h"
18 #include "cloudy.h"
19 
20 #ifndef NDEBUG
21 namespace {
22  class my_Integrand: public std::unary_function<double, double>
23  {
24  public:
25  double operator() (double x) const
26  {
27  return sin(x);
28  }
29  // the constructor is needed to avoid warnings by the Sun Studio compiler
30  my_Integrand() {}
31  };
32 
33  // create a version of sin with C++ linkage
34  // this is done because we need a C++ pointer to this routine below
35  double mySin(double x)
36  {
37  return sin(x);
38  }
39 }
40 #endif
41 
42 /* NB - this routine must not change any global variables - any that are changed as part of
43  * a test must be reset, so that the code retains state */
44 
47 STATIC void SanityCheckGaunt(long Z, double loggam2, double logu, double refval, double relerr);
48 
49 /* chJob is either "begin" or "final"
50  * "begin is before code starts up
51  * "final is after model is complete */
52 void SanityCheck( const char *chJob )
53 {
54  DEBUG_ENTRY( "SanityCheck()" );
55 
56  if( strcmp(chJob,"begin") == 0 )
57  {
59  }
60  else if( strcmp(chJob,"final") == 0 )
61  {
63  }
64  else
65  {
66  fprintf(ioQQQ,"SanityCheck called with insane argument.\n");
68  }
69 }
70 
72 {
73  /* PrtComment also has some ending checks on sanity */
74 }
75 
77 {
78  bool lgOK=true;
79  int lgFlag;// error return for spsort, 0 success, >=1 for errors
80  int32 ner, ipiv[3];
81  long i ,
82  j ,
83  nelem ,
84  ion ,
85  nshells;
86  double *A;
87 
88  /* this will be charge to the 4th power */
89  double Aul ,
90  error,
91  Z4;
92 
93  long n;
94 
95  const int NDIM = 10;
96  double xMatrix[NDIM][NDIM] , yVector[NDIM] ,
97  rcond;
98  realnum *fvector;
99  long int *ipvector;
100 
101  DEBUG_ENTRY( "SanityCheck()" );
102 
103  /*********************************************************
104  * *
105  * confirm that various part of cloudy still work *
106  * *
107  *********************************************************/
108 
109  /* if this is no longer true at end, we have a problem */
110  lgOK = true;
111 
112  /*********************************************************
113  * *
114  * check that all the Lyas As are ok *
115  * *
116  *********************************************************/
117  for( nelem=0; nelem<LIMELM; ++nelem )
118  {
119  /* this element may be turned off */
120  if( dense.lgElmtOn[nelem] )
121  {
122  /* H_Einstein_A( n, l, np, lp, iz ) - all are on physics scale */
123  Aul = H_Einstein_A( 2, 1, 1, 0, nelem+1 );
124  /*fprintf(ioQQQ,"%li\t%.4e\n", nelem+1, iso_sp[ipH_LIKE][nelem].trans(ipH2p,ipH1s).Emis().Aul() );*/
125  if( fabs(Aul - iso_sp[ipH_LIKE][nelem].trans(ipH2p,ipH1s).Emis().Aul() ) /Aul > 0.01 )
126  {
127  fprintf(ioQQQ," SanityCheck found insane H-like As.\n");
128  lgOK = false;
129  }
130  }
131  }
132 
133  /*********************************************************
134  * *
135  * check that gaunt factors are good *
136  * *
137  *********************************************************/
138  // our Gaunt factors are merged relativistic and non-relativistic data
139  // for high gamma^2 we have non-relativistic data, these are compared to Sutherland (1998)
140  // for low gamma^2 we have relativistic data, these are compared to Nozawa+ (1998)
141  // more elaborate checks are done in the unit test suite
142 
143  // these data were taken from
144  // >>refer HI gff Sutherland R.S., 1998, MNRAS, 300, 321
145  SanityCheckGaunt( 1, 4., -4., 2.7008, 0.0004 );
146  SanityCheckGaunt( 1, 4., 4., 1.1040, 0.0004 );
147  SanityCheckGaunt( 1, 4., 0., 1.0237, 0.0004 );
148  SanityCheckGaunt( 2, 3., -3., 2.2126, 0.0004 );
149  SanityCheckGaunt( 2, 1., -1., 1.5088, 0.0004 );
150  // these data were taken from
151  // >>refer gff Nozawa S., Itoh N., Kohyama Y., 1998, ApJ, 507, 530
152  SanityCheckGaunt( 1, -4., -4., 6.120, 0.005 );
153  SanityCheckGaunt( 2, -4., -4., 8.934, 0.005 );
154  SanityCheckGaunt( 1, -3., -1., 1.852, 0.005 );
155  SanityCheckGaunt( 2, -3., -1., 1.921, 0.005 );
156  SanityCheckGaunt( 1, 0., 3., 0.2001, 0.005 );
157  SanityCheckGaunt( 2, 0., 3., 0.2041, 0.005 );
158 
159  /*********************************************************
160  * *
161  * check some transition probabililties for he-like ions *
162  * *
163  *********************************************************/
164  for( nelem=1; nelem<LIMELM; ++nelem )
165  {
166  /* the helike 9-1 transition, A(3^3P to 2^3S) */
167  double as[]={
168  /* updated with Johnson values */
169  0. ,9.47e+006 ,3.44e+008 ,1.74e+009 ,5.51e+009 ,1.34e+010 ,
170  2.79e+010 ,5.32E+010 ,8.81e+010 ,1.46E+011 ,2.15e+011 ,3.15e+011 ,
171  4.46e+011 ,6.39E+011 ,8.26e+011 ,1.09e+012 ,1.41e+012 ,1.86E+012 ,
172  2.26e+012 ,2.80e+012 ,3.44e+012 ,4.18e+012 ,5.04e+012 ,6.02e+012 ,
173  7.14e+012 ,8.40e+012 ,9.83e+012 ,1.14e+013 ,1.32e+013 ,1.52e+013
174  };
175 
176  if( iso_sp[ipHE_LIKE][nelem].numLevels_max > 8 && dense.lgElmtOn[nelem])
177  {
178  /* following used to print current values of As */
179  if( fabs( as[nelem] - iso_sp[ipHE_LIKE][nelem].trans(9,1).Emis().Aul() ) /as[nelem] > 0.025 )
180  {
181  fprintf(ioQQQ,
182  " SanityCheck found insane He-like As: expected, nelem=%li found: %.2e %.2e.\n",
183  nelem,
184  as[nelem] ,
185  iso_sp[ipHE_LIKE][nelem].trans(9,1).Emis().Aul() );
186  lgOK = false;
187  }
188  }
189  }
190 
191  /* only do this test if case b is not in effect */
192  if( !opac.lgCaseB )
193  {
194 
195  for( i = 0; i <=110; i++ )
196  {
197  double DrakeTotalAuls[111] = {
198  -1.0000E+00, -1.0000E+00, -1.0000E+00, 1.02160E+07,
199  1.02160E+07, 1.02160E+07, 1.80090E+09, 2.78530E+07,
200  1.82990E+07, 1.05480E+07, 7.07210E+07, 6.37210E+07,
201  5.79960E+08, 1.60330E+07, 1.13640E+07, 7.21900E+06,
202  3.11920E+07, 2.69830E+07, 1.38380E+07, 1.38330E+07,
203  2.52270E+08, 9.20720E+06, 6.82220E+06, 4.56010E+06,
204  1.64120E+07, 1.39290E+07, 7.16030E+06, 7.15560E+06,
205  4.25840E+06, 4.25830E+06, 1.31150E+08, 5.62960E+06,
206  4.29430E+06, 2.95570E+06, 9.66980E+06, 8.12340E+06,
207  4.19010E+06, 4.18650E+06, 2.48120E+06, 2.48120E+06,
208  1.64590E+06, 1.64590E+06, 7.65750E+07, 3.65330E+06,
209  2.84420E+06, 1.99470E+06, 6.16640E+06, 5.14950E+06,
210  2.66460E+06, 2.66200E+06, 1.57560E+06, 1.57560E+06,
211  1.04170E+06, 1.04170E+06, 7.41210E+05, 7.41210E+05,
212  4.84990E+07, 2.49130E+06, 1.96890E+06, 1.39900E+06,
213  4.16900E+06, 3.46850E+06, 1.79980E+06, 1.79790E+06,
214  1.06410E+06, 1.06410E+06, 7.02480E+05, 7.02480E+05,
215  4.98460E+05, 4.98460E+05, -1.0000E+00, -1.0000E+00,
216  3.26190E+07, 1.76920E+06, 1.41440E+06, 1.01460E+06,
217  2.94830E+06, 2.44680E+06, 1.27280E+06, 1.27140E+06,
218  7.52800E+05, 7.52790E+05, 4.96740E+05, 4.96740E+05,
219  3.51970E+05, 3.51970E+05, -1.0000E+00, -1.0000E+00,
220  -1.0000E+00, -1.0000E+00, 2.29740E+07, 1.29900E+06,
221  1.04800E+06, 7.57160E+05, 2.16090E+06, 1.79030E+06,
222  9.33210E+05, 9.32120E+05, 5.52310E+05, 5.52310E+05,
223  3.64460E+05, 3.64460E+05, 2.58070E+05, 2.58070E+05,
224  -1.0000E+00, -1.0000E+00, -1.0000E+00, -1.0000E+00,
225  -1.0000E+00, -1.0000E+00, 1.67840E+07};
226 
227  if( DrakeTotalAuls[i] > 0. &&
228  i < iso_sp[ipHE_LIKE][ipHELIUM].numLevels_max - iso_sp[ipHE_LIKE][ipHELIUM].nCollapsed_max)
229  {
230  if( fabs( DrakeTotalAuls[i] - (1./iso_sp[ipHE_LIKE][ipHELIUM].st[i].lifetime()) ) /DrakeTotalAuls[i] > 0.001 )
231  {
232  fprintf(ioQQQ,
233  " SanityCheck found helium lifetime outside 0.1 pct of Drake values: index, expected, found: %li %.4e %.4e\n",
234  i,
235  DrakeTotalAuls[i],
236  (1./iso_sp[ipHE_LIKE][ipHELIUM].st[i].lifetime()) );
237  lgOK = false;
238  }
239  }
240  }
241  }
242 
243  /*********************************************************
244  * *
245  * check the threshold photoionization cs for He I *
246  * *
247  *********************************************************/
248  if( dense.lgElmtOn[ipHELIUM] )
249  {
250  /* HeI photoionization cross sections at threshold for lowest 20 levels */
251  const int NHE1CS = 20;
252  double he1cs[NHE1CS] =
253  {
254  5.480E-18 , 9.253E-18 , 1.598E-17 , 1.598E-17 , 1.598E-17 , 1.348E-17 ,
255  8.025E-18 , 1.449E-17 , 2.852E-17 , 1.848E-17 , 1.813E-17 , 2.699E-17 ,
256  1.077E-17 , 2.038E-17 , 4.159E-17 , 3.670E-17 , 3.575E-17 , 1.900E-17 ,
257  1.900E-17 , 4.175E-17
258  };
259 
260  /* loop over levels and check on photo cross section */
261  j = MIN2( NHE1CS+1 , iso_sp[ipHE_LIKE][ipHELIUM].numLevels_max -iso_sp[ipHE_LIKE][ipHELIUM].nCollapsed_max );
262  for( n=1; n<j; ++n )
263  {
264  /* above list of levels does not include the ground */
265  i = iso_sp[ipHE_LIKE][ipHELIUM].fb[n].ipOpac;
266  ASSERT( i>0 );
267  /*fprintf(ioQQQ,"%li\t%lin", n , i );*/
268  /* >>chng 02 apr 10, from 0.01 to 0.02, values stored
269  * where taken from calc at low contin resolution, when continuum
270  * resolution changed this changes too */
271  /*fprintf(ioQQQ,"%li %.2e\n", n,( he1cs[n-1] - opac.OpacStack[i - 1] ) /he1cs[n-1] );*/
272  /* >>chng 02 jul 16, limt from 0.02 to 0.04, so that "set resolution 4" will work */
273  /* >>chng 04 may 18, levels 10 and 11 are about 12% off - because of energy binning, chng from 0.08 to 0.15 */
274  if( fabs( he1cs[n-1] - opac.OpacStack[i - 1] ) /he1cs[n-1] > 0.15 )
275  {
276  fprintf(ioQQQ,
277  " SanityCheck found insane HeI photo cs: expected, n=%li found: %.3e %.3e.\n",
278  n,
279  he1cs[n-1] ,
280  opac.OpacStack[i - 1]);
281  fprintf(ioQQQ,
282  " n=%li, l=%li, s=%li\n",
283  iso_sp[ipHE_LIKE][ipHELIUM].st[n].n() ,
284  iso_sp[ipHE_LIKE][ipHELIUM].st[n].l() ,
285  iso_sp[ipHE_LIKE][ipHELIUM].st[n].S());
286  lgOK = false;
287  }
288  }
289  }
290 
291  for( long ipISO=ipH_LIKE; ipISO<NISO; ipISO++ )
292  {
293  long nelem = ipISO;
294  /* Check for agreement between on-the-fly and interpolation calculations
295  * of recombination, but only if interpolation is turned on. */
296  if( !iso_ctrl.lgNoRecombInterp[ipISO] )
297  {
298  /* check the recombination coefficients for ground state */
299  error = fabs( iso_recomb_check( ipISO, nelem , 0 , 7500. ) );
300  if( error > 0.01 )
301  {
302  fprintf(ioQQQ,
303  " SanityCheck found insane1 %s %s recom coef: expected, n=%i error: %.2e \n",
304  iso_ctrl.chISO[ipISO],
305  elementnames.chElementSym[nelem],
306  0,
307  error );
308  lgOK = false;
309  }
310 
311  /* check the recombination coefficients for ground state of the root of each iso sequence */
312  error = fabs( iso_recomb_check( ipISO, nelem , 1 , 12500. ) );
313  if( error > 0.01 )
314  {
315  fprintf(ioQQQ,
316  " SanityCheck found insane2 %s %s recom coef: expected, n=%i error: %.2e \n",
317  iso_ctrl.chISO[ipISO],
318  elementnames.chElementSym[nelem],
319  1,
320  error );
321  lgOK = false;
322  }
323  }
324  }
325 
326  /*********************************************************
327  * *
328  * check out the sorting routine *
329  * *
330  *********************************************************/
331 
332  const int NSORT = 100 ;
333 
334  fvector = (realnum *)MALLOC((NSORT)*sizeof(realnum) );
335 
336  ipvector = (long *)MALLOC((NSORT)*sizeof(long int) );
337 
338  nelem = 1;
339  /* make up some unsorted values */
340  for( i=0; i<NSORT; ++i )
341  {
342  nelem *= -1;
343  fvector[i] = (realnum)nelem * ((realnum)NSORT-i);
344  }
345 
346  /*spsort netlib routine to sort array returning sorted indices */
347  spsort(fvector,
348  NSORT,
349  ipvector,
350  /* flag saying what to do - 1 sorts into increasing order, not changing
351  * the original routine */
352  1,
353  &lgFlag);
354 
355  if( lgFlag ) lgOK = false;
356 
357  for( i=1; i<NSORT; ++i )
358  {
359  /*fprintf(ioQQQ," %li %li %.0f\n",
360  i, ipvector[i],fvector[ipvector[i]] );*/
361  if( fvector[ipvector[i]] <= fvector[ipvector[i-1]] )
362  {
363  fprintf(ioQQQ," SanityCheck found insane sort\n");
364  lgOK = false;
365  }
366  }
367 
368  free( fvector );
369  free( ipvector);
370 
371 # if 0
372  ttemp = (realnum)sqrt(phycon.te);
373  /* check that the temperatures make sense */
374  if( fabs(ttemp - phycon.sqrte )/ttemp > 1e-5 )
375  {
376  fprintf(ioQQQ , "SanityCheck finds insane te %e sqrt te %e sqrte %e dif %e\n",
377  phycon.te ,
378  sqrt(phycon.te) ,
379  phycon.sqrte ,
380  fabs(sqrt(phycon.te) - phycon.sqrte ) );
381  lgOK = false;
382  }
383 # endif
384 
385  /*********************************************************
386  * *
387  * confirm the validity of the mesh *
388  * *
389  *********************************************************/
390 
392  rfield.CheckMesh();
393 
394  /*********************************************************
395  * *
396  * confirm that hydrogen einstein As are still valid *
397  * *
398  *********************************************************/
399  for( nelem=0; nelem<2; ++nelem )
400  {
401  /* this element may be turned off */
402  if( dense.lgElmtOn[nelem] )
403  {
404  /*Z4 = (double)(POW2(nelem+1)*POW2(nelem+1));*/
405  /* form charge to the 4th power */
406  Z4 = (double)(nelem+1);
407  Z4 *= Z4;
408  Z4 *= Z4;
409  /* H Lya */
410  double ans1 = iso_sp[ipH_LIKE][nelem].trans(ipH2p,ipH1s).Emis().Aul();
411  double ans2 = 6.265e8*Z4;
412  if( fabs(ans1-ans2)/ans2 > 1e-3 )
413  {
414  fprintf(ioQQQ , "SanityCheck finds insane A for H Lya %g %g nelem=%li\n",
415  ans1 , ans2 , nelem );
416  lgOK = false;
417  }
418  }
419  }
420 
421  /* check that hydrogenic branching ratios add up to unity */
422  for( nelem=0; nelem<LIMELM; ++nelem )
423  {
424  if( dense.lgElmtOn[nelem] )
425  {
426  int ipHi, ipLo;
427  for( ipHi=4; ipHi< iso_sp[ipH_LIKE][nelem].numLevels_max-iso_sp[ipH_LIKE][nelem].nCollapsed_max; ++ipHi )
428  {
429  double sum = 0.;
430  for( ipLo=0; ipLo<ipHi; ++ipLo )
431  {
432  sum += iso_sp[ipH_LIKE][nelem].BranchRatio[ipHi][ipLo];
433  }
434  if( fabs(sum-1.)>0.01 )
435  {
436  fprintf(ioQQQ ,
437  "SanityCheck H branching ratio sum not unity for nelem=%li upper level=%i sum=%.3e\n",
438  nelem, ipHi, sum );
439  lgOK = false;
440  }
441  }
442  }
443  }
444 
445  /* check that hydrogenic lifetimes are sane (compare inverse sum of As with closed form of lifetime) */
446  for( nelem=0; nelem<LIMELM; ++nelem )
447  {
448  if( dense.lgElmtOn[nelem] )
449  {
450  int ipHi, ipLo;
451  for( ipHi=1; ipHi< iso_sp[ipH_LIKE][nelem].numLevels_max-iso_sp[ipH_LIKE][nelem].nCollapsed_max; ++ipHi )
452  {
453  double inverse_sum = 0.;
454  double sum = 0.;
455  long ipISO = ipH_LIKE;
456 
457  /* we do not have an accurate closed form for l=0 lifetimes. Everything else should be very accurate. */
458  if( L_(ipHi)==0 )
459  continue;
460 
461  for( ipLo=0; ipLo<ipHi; ++ipLo )
462  {
463  sum += iso_sp[ipH_LIKE][nelem].trans(ipHi,ipLo).Emis().Aul();
464  }
465 
466  inverse_sum = 1./sum;
467 
468  double lifetime = iso_state_lifetime( ipH_LIKE, nelem, N_(ipHi), L_(ipHi) );
469  double percent_error = (1. - inverse_sum/lifetime)*100;
470  /* The closed form seems to consistently yield lifetimes roughly 0.2% smaller than the inverse sum
471  * Transition probabilities go like energy cubed. The cube of the finite mass rydberg is about 0.16% less than unity.
472  * is that the difference? */
473  /* for now enforce to better than 0.5% */
474  if( fabs(percent_error) > 0.5 )
475  {
476  fprintf(ioQQQ ,
477  "SanityCheck hydrogenic lifetime not consistent with closed form for nelem=%2li upper level=%2i inverse-sum= %.3e lifetime= %.3e error= %.2f%%\n",
478  nelem, ipHi, inverse_sum, lifetime, percent_error );
479  lgOK = false;
480  }
481  }
482  }
483  }
484 
485  /* check photo cross sections for H */
486  long ipISO = ipH_LIKE;
487  nelem = 0;
488  for( long n=1; n <= iso_sp[ipISO][nelem].n_HighestResolved_max; ++n )
489  {
490  // This sanity check is of little value, as the cross-section is calculated
491  // with the same routine here as when populating the opacity stack.
492  // Make sure to use the cell mid-point energy which was also used in populating OpacStack
493  // as it can be greater than the threshold energy (especially if the freq mesh is coarse)
494  // For very Rydberg, very yrare levels, the cross-section can fall off fast
495  // enough over that energy difference to fail this check otherwise.
496  // Will reported a sim that failed the check for some levels with n >= 71.
497  // So just don't bother checking above 60.
498  if( n > 60 )
499  break;
500 
501  for( long l=0; l < n; l++ )
502  {
503  long index = iso_sp[ipISO][nelem].QuantumNumbers2Index[n][l][2];
504 
505  double anu = rfield.anu(iso_sp[ipISO][nelem].fb[index].ipIsoLevNIonCon-1);
506  double rel_photon_energy = max(anu/iso_sp[ipISO][nelem].fb[index].xIsoLevNIonRyd, 1. + FLT_EPSILON*2.);
507  double cs = H_photo_cs( rel_photon_energy, n, l, nelem+1 );
508 
509  error = fabs(cs - opac.OpacStack[iso_sp[ipISO][nelem].fb[index].ipOpac-1] )/
510  ( (cs + opac.OpacStack[iso_sp[ipISO][nelem].fb[index].ipOpac-1] )/2.);
511 
512  if( error > 0.05 )
513  {
514  fprintf(ioQQQ , "SanityCheck finds insane H photo cs, n,l = %li, %li, expected, found = %e, %e, error = %e\n",
515  n, l, opac.OpacStack[iso_sp[ipISO][nelem].fb[index].ipOpac-1], cs, error );
516  lgOK = false;
517  }
518  }
519  }
520 
521  /*********************************************************
522  * *
523  * confirm that Gaussian integration routines still work *
524  * *
525  *********************************************************/
526  ASSERT( fp_equal( qg32( 0., PI, mySin ) , 2. ) );
527 
528 #ifndef NDEBUG
529  /* And again with the new structure */
530  my_Integrand func;
532 #endif
533  ASSERT( fp_equal( mySine.sum( 0., PI ), 2. ) );
534 
535  /*********************************************************
536  * *
537  * confirm that exponential integral routines still work *
538  * *
539  *********************************************************/
540 
541  /* check that first and second exponential integrals are ok,
542  * step through range of values, beginning with following */
543  double x = 1e-3;
544  do
545  {
546  {
547  /* check that fast e1 routine is ok */
548  double ans1 = e1(x);
549  double ans2 = expn( 1 , x );
550  error = safe_div(ans1-ans2,ans1+ans2,0.);
551  if( fabs(error) > 3e-7 )
552  {
553  fprintf(ioQQQ , "SanityCheck finds insane E1 %g %g %g error %g\n",
554  x , ans1 , ans2, error );
555  lgOK = false;
556  }
557  }
558  {
559  /* check that e2 is ok */
560  double ans1 = e2(x);
561  double ans2 = expn( 2 , x );
562  error = safe_div(ans1-ans2,ans1+ans2,0.);
563  if( fabs(error) > 1e-8 )
564  {
565  fprintf(ioQQQ , "SanityCheck finds insane E2 %g %g %g error %g\n",
566  x , ans1 , ans2, error );
567  lgOK = false;
568  }
569  }
570  /* now increment x */
571  x *= 2.;
572  /* following limit set by values not underflowing to zero */
573  } while( x < 700. );
574 
575  /*********************************************************
576  * *
577  * confirm that matrix inversion routine still works *
578  * *
579  *********************************************************/
580 
581  /* these are the answer, chosen to get xvec 1,2,3 */
582  yVector[0] = 1.;
583  yVector[1] = 3.;
584  yVector[2] = 3.;
585 
586  /* zero out the main matrix */
587  for(i=0;i<3;++i)
588  {
589  for( j=0;j<3;++j )
590  {
591  xMatrix[i][j] = 0.;
592  }
593  }
594 
595  /* remember that order is column, row, alphabetical order, rc */
596  xMatrix[0][0] = 1.;
597  xMatrix[0][1] = 1.;
598  xMatrix[1][1] = 1.;
599  xMatrix[2][2] = 1.;
600 
601  /* this is the default matrix solver */
602  /* this test is the 1-d matrix with 2-d macro simulation */
603  /* LDA is right dimension of matrix */
604 
605  /* MALLOC space for the 1-d array */
606  A = (double*)MALLOC( sizeof(double)*NDIM*NDIM );
607 
608  /* copy over the main matrix */
609  for( i=0; i < 3; ++i )
610  {
611  for( j=0; j < 3; ++j )
612  {
613  A[i*NDIM+j] = xMatrix[i][j];
614  }
615  }
616 
617  ner = 0;
618 
619  /*void DGETRF(long,long,double*,long,long[],long*);*/
620  /*void DGETRF(int,int,double*,int,int[],int*);*/
621  getrf_wrapper(3, 3, A, NDIM, ipiv, &ner);
622  if( ner != 0 )
623  {
624  fprintf( ioQQQ, " SanityCheck DGETRF error\n" );
626  }
627 
628  /* usage DGETRS, 'N' = no transpose
629  * order of matrix,
630  * number of cols in bvec, =1
631  * array
632  * leading dim of array */
633  /*void DGETRS(char,int,int,double*,int,int[],double*,int,int*);*/
634  getrs_wrapper('N', 3, 1, A, NDIM, ipiv, yVector, 3, &ner);
635 
636  if( ner != 0 )
637  {
638  fprintf( ioQQQ, " SanityCheck DGETRS error\n" );
640  }
641  /* release the vector */
642  free( A );
643 
644  /* now check on validity of the solution, demand that this
645  * simple problem have come within a few epsilons of the
646  * correct answer */
647 
648  /* find largest deviation */
649  rcond = 0.;
650  for(i=0;i<3;++i)
651  {
652  x = fabs( yVector[i]-i-1.);
653  rcond = MAX2( rcond, x );
654  /*printf(" %g ", yVector[i]);*/
655  }
656 
657  if( rcond>DBL_EPSILON)
658  {
659  fprintf(ioQQQ,
660  "SanityCheck found too large a deviation in matrix solver = %g \n",
661  rcond);
662  /* set flag saying that things are not ok */
663  lgOK = false;
664  }
665  /* end matrix inversion check */
666 
667  /* these pointers were set to INT_MIN in ContCreatePointers,
668  * then set to valid numbers in ipShells and OpacityCreate1Element
669  * this checks that all values have been properly filled */
670  for( nelem=0; nelem<LIMELM; ++nelem )
671  {
672  /* must reset state of code after tests performed, remember state here */
673  realnum xIonF[NISO][LIMELM];
674  double hbn[NISO][LIMELM], hn[NISO][LIMELM];
675 
676  if( dense.lgElmtOn[nelem] )
677  {
678  /* set these abundances so that opacities can be checked below */
679  hbn[ipH_LIKE][nelem] = iso_sp[ipH_LIKE][nelem].st[0].DepartCoef();
680  hn[ipH_LIKE][nelem] = iso_sp[ipH_LIKE][nelem].st[0].Pop();
681  xIonF[ipH_LIKE][nelem] = dense.xIonDense[nelem][nelem+1-ipH_LIKE];
682 
683  iso_sp[ipH_LIKE][nelem].st[0].DepartCoef() = 0.;
684  iso_sp[ipH_LIKE][nelem].st[0].Pop() = 1.;
685  dense.xIonDense[nelem][nelem+1-ipH_LIKE] = 1.;
686 
687  if( nelem > ipHYDROGEN )
688  {
689 
690  hbn[ipHE_LIKE][nelem] = iso_sp[ipHE_LIKE][nelem].st[0].DepartCoef();
691  hn[ipHE_LIKE][nelem] = iso_sp[ipHE_LIKE][nelem].st[0].Pop();
692  xIonF[ipHE_LIKE][nelem] = dense.xIonDense[nelem][nelem+1-ipHE_LIKE];
693 
694  /* this does not exist for hydrogen itself */
695  iso_sp[ipHE_LIKE][nelem].st[0].DepartCoef() = 0.;
696  iso_sp[ipHE_LIKE][nelem].st[0].Pop() = 1.;
697  dense.xIonDense[nelem][nelem+1-ipHE_LIKE] = 1.;
698  }
699 
700  for( ion=0; ion<=nelem; ++ion )
701  {
702  /* loop over all shells that are defined */
703  for( nshells=0; nshells<Heavy.nsShells[nelem][ion]; ++nshells )
704  {
705  for( j=0; j<3; ++j )
706  {
707  /* >>chng 00 apr 05, array index is on fortran scale so must be
708  * >= 1. This test had been <0, correct for C. Caught by Peter van Hoof */
709  if( opac.ipElement[nelem][ion][nshells][j] <=0 )
710  {
711  /* this is not possible */
712  fprintf(ioQQQ,
713  "SanityCheck found insane ipElement for nelem=%li ion=%li nshells=%li j=%li \n",
714  nelem , ion , nshells, j );
715  fprintf(ioQQQ,
716  "value was %li \n", opac.ipElement[nelem][ion][nshells][j] );
717  /* set flag saying that things are not ok */
718  lgOK = false;
719  }
720  }
721  }
722 
723  if( nelem > 1 )
724  {
725  vector<realnum> saveion;
726  saveion.resize(nelem+2);
727  /* check that photoionization cross sections are ok */
728  for( j=0; j <= (nelem + 1); j++ )
729  {
730  saveion[j] = dense.xIonDense[nelem][j];
731  dense.xIonDense[nelem][j] = 0.;
732  }
733 
734  dense.xIonDense[nelem][ion] = 1.;
735 
736  OpacityZero();
737  opac.lgRedoStatic = true;
738 
739  /* generate opacity with standard routine - this is the one
740  * called in OpacityAddTotal to make opacities in usual calculations */
741  OpacityAdd1Element(nelem);
742 
743  /* this starts one beyond energy of threshold since cs may be zero there */
744  for( j=Heavy.ipHeavy[nelem][ion]; j < MIN2(rfield.nflux,continuum.KshellLimit); j++ )
745  {
746  if( opac.opacity_abs[j]+opac.OpacStatic[j] < FLT_MIN )
747  {
748  /* this is not possible */
749  fprintf(ioQQQ,
750  "SanityCheck found non-positive photo cs for nelem=%li ion=%li \n",
751  nelem , ion );
752  fprintf(ioQQQ,
753  "value was %.2e + %.2e nelem %li ion %li at energy %.2e\n",
754  opac.opacity_abs[j] ,
755  opac.OpacStatic[j] ,
756  nelem ,
757  ion ,
758  rfield.anu(j));
759  /* set flag saying that things are not ok */
760  lgOK = false;
761  break;
762  }
763  }
764  /* reset the ionization distribution */
765  for( j=0; j <= (nelem + 1); j++ )
766  {
767  dense.xIonDense[nelem][j] = saveion[j];
768  }
769 
770  }
771  }
772  iso_sp[ipH_LIKE][nelem].st[ipH1s].DepartCoef() = hbn[ipH_LIKE][nelem];
773  iso_sp[ipH_LIKE][nelem].st[ipH1s].Pop() = hn[ipH_LIKE][nelem];
774  dense.xIonDense[nelem][nelem+1-ipH_LIKE] = xIonF[ipH_LIKE][nelem];
775 
776  if( nelem > ipHYDROGEN )
777  {
778  iso_sp[ipHE_LIKE][nelem].st[ipHe1s1S].DepartCoef() = hbn[ipHE_LIKE][nelem];
779  iso_sp[ipHE_LIKE][nelem].st[ipHe1s1S].Pop() = hn[ipHE_LIKE][nelem];
780  dense.xIonDense[nelem][nelem+1-ipHE_LIKE] = xIonF[ipHE_LIKE][nelem];
781  }
782  }
783  }
784 
785 
786  /*********************************************************
787  * *
788  * everything is done, all checks make, did we pass them?*
789  * *
790  *********************************************************/
791 
792  if( lgOK )
793  {
794  /*return if ok */
795  if( trace.lgTrace )
796  {
797  fprintf( ioQQQ, " SanityCheck returns OK\n");
798  }
799  return;
800  }
801 
802  else
803  {
804  /* stop since problem encountered, lgEOF set false */
805  fprintf(ioQQQ , "SanityCheck finds insanity so exiting\n");
806  ShowMe();
808  }
809 }
810 
811 STATIC void SanityCheckGaunt(long Z, double loggam2, double logu, double refval, double relerr)
812 {
813  DEBUG_ENTRY( "SanityCheckGaunt()" );
814  double gam2 = exp10(loggam2);
815  double u = exp10(logu);
816  double ZZ = double(Z);
817  double Te = TE1RYD*pow2(ZZ)/gam2;
818  double anu = pow2(ZZ)*u/gam2;
819  double val = t_gaunt::Inst().gauntff( Z, Te, anu );
820  if( fabs(val/refval-1.) > relerr )
821  {
822  fprintf( ioQQQ, "Gaunt sanity check failed: log(gam2) = %e, log(u) = %e, Z = %ld, "
823  "expected gff = %e, got gff = %e\n", loggam2, logu, Z, refval, val );
825  }
826 }
void OpacityAdd1Element(long int ipZ)
long int ipElement[LIMELM][LIMELM][7][3]
Definition: opacity.h:223
void getrs_wrapper(char trans, long N, long nrhs, double *A, long lda, int32 *ipiv, double *B, long ldb, int32 *info)
double * OpacStack
Definition: opacity.h:164
double * opacity_abs
Definition: opacity.h:104
qList st
Definition: iso.h:482
double exp10(double x)
Definition: cddefines.h:1368
const int ipHE_LIKE
Definition: iso.h:65
t_opac opac
Definition: opacity.cpp:5
t_Heavy Heavy
Definition: heavy.cpp:5
void CheckMesh() const
Definition: mesh.cpp:322
double * OpacStatic
Definition: opacity.h:123
double iso_recomb_check(long ipISO, long nelem, long level, double temperature)
t_isoCTRL iso_ctrl
Definition: iso.cpp:9
const int NISO
Definition: cddefines.h:311
double e1(double x)
long int KshellLimit
Definition: continuum.h:98
long int nCollapsed_max
Definition: iso.h:518
t_phycon phycon
Definition: phycon.cpp:6
FILE * ioQQQ
Definition: cddefines.cpp:7
void SanityCheck(const char *chJob)
const int ipHe1s1S
Definition: iso.h:43
double expn(int n, double x)
vector< freeBound > fb
Definition: iso.h:481
#define MIN2(a, b)
Definition: cddefines.h:803
double anu(size_t i) const
Definition: mesh.h:120
void ValidateEdges() const
Definition: mesh.cpp:274
t_dense dense
Definition: global.cpp:15
static t_gaunt & Inst()
Definition: cddefines.h:209
t_elementnames elementnames
Definition: elementnames.cpp:5
t_iso_sp iso_sp[NISO][LIMELM]
Definition: iso.cpp:11
double xIonDense[LIMELM][LIMELM+1]
Definition: dense.h:135
t_trace trace
Definition: trace.cpp:5
bool fp_equal(sys_float x, sys_float y, int n=3)
Definition: cddefines.h:854
#define MALLOC(exp)
Definition: cddefines.h:554
multi_arr< double, 2 > BranchRatio
Definition: iso.h:480
long int n_HighestResolved_max
Definition: iso.h:536
#define L_(A_)
Definition: iso.h:23
void getrf_wrapper(long M, long N, double *A, long lda, int32 *ipiv, int32 *info)
long int nsShells[LIMELM][LIMELM]
Definition: heavy.h:28
const int ipH1s
Definition: iso.h:29
#define STATIC
Definition: cddefines.h:118
bool lgTrace
Definition: trace.h:12
EmissionList::reference Emis() const
Definition: transition.h:447
#define N_(A_)
Definition: iso.h:22
t_continuum continuum
Definition: continuum.cpp:6
const char * chISO[NISO]
Definition: iso.h:348
t_rfield rfield
Definition: rfield.cpp:9
bool lgCaseB
Definition: opacity.h:174
float realnum
Definition: cddefines.h:124
#define EXIT_FAILURE
Definition: cddefines.h:168
long max(int a, long b)
Definition: cddefines.h:817
STATIC void SanityCheckFinal()
#define cdEXIT(FAIL)
Definition: cddefines.h:482
sys_float safe_div(sys_float x, sys_float y, sys_float res_0by0)
Definition: cddefines.h:1011
double sum(double min, double max) const
Definition: integrate.h:44
multi_arr< long, 3 > QuantumNumbers2Index
Definition: iso.h:490
bool lgElmtOn[LIMELM]
Definition: dense.h:160
double gauntff(long Z, double Te, double anu)
TransitionProxy trans(const long ipHi, const long ipLo)
Definition: iso.h:473
char chElementSym[LIMELM][CHARS_ELEMENT_SYM]
Definition: elementnames.h:25
double H_photo_cs(double rel_photon_energy, long int n, long int l, long int iz)
const int ipH2p
Definition: iso.h:31
STATIC void SanityCheckBegin()
#define ASSERT(exp)
Definition: cddefines.h:613
double qg32(double, double, double(*)(double))
Definition: service.cpp:1175
const int ipH_LIKE
Definition: iso.h:64
const int LIMELM
Definition: cddefines.h:308
T pow2(T a)
Definition: cddefines.h:981
#define DEBUG_ENTRY(funcname)
Definition: cddefines.h:723
double iso_state_lifetime(long ipISO, long nelem, long n, long l)
const int ipHELIUM
Definition: cddefines.h:350
bool lgRedoStatic
Definition: opacity.h:160
#define MAX2(a, b)
Definition: cddefines.h:824
int fprintf(const Output &stream, const char *format,...)
Definition: service.cpp:1121
double e2(double x)
#define S(I_, J_)
long int numLevels_max
Definition: iso.h:524
double sqrte
Definition: phycon.h:58
void ShowMe(void)
Definition: service.cpp:205
double te
Definition: phycon.h:21
const int ipHYDROGEN
Definition: cddefines.h:349
long int nflux
Definition: rfield.h:46
realnum & Aul() const
Definition: emission.h:690
double H_Einstein_A(long int n, long int l, long int np, long int lp, long int iz)
long int ipHeavy[LIMELM][LIMELM]
Definition: heavy.h:11
void spsort(realnum x[], long int n, long int iperm[], int kflag, int *ier)
Definition: service.cpp:1222
void OpacityZero(void)
Definition: opacity_zero.cpp:8
STATIC void SanityCheckGaunt(long Z, double loggam2, double logu, double refval, double relerr)
bool lgNoRecombInterp[NISO]
Definition: iso.h:405