cloudy  trunk
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
atom_leveln.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 /*atom_levelN compute an arbitrary N level atom */
4 #include "cddefines.h"
5 #include "atoms.h"
6 #include "physconst.h"
7 #include "thermal.h"
8 #include "conv.h"
9 #include "phycon.h"
10 #include "dense.h"
11 #include "trace.h"
12 #include "newton_step.h"
13 #include "dynamics.h"
14 #include "conv.h"
15 #include "vectorize.h"
16 #include "prt.h"
17 
18 // #define EIGEN
19 #ifdef EIGEN
20 extern "C" {
21  void dgeev_(const char*, const char*,int*,double*,int*,double*,
22  double*,
23  double*,int*,double*,int*,double*,int*,int*,int,int);
24 }
25 #endif
26 
27 // Set collision rate from collision strength
29  long nlev,
30  double TeInverse,
31  double **col_str,
32  const double ex[],
33  const double g[],
34  double **CollRate
35 )
36 {
37  resize(nlev);
38  for( long ihi=1; ihi < nlev; ++ihi )
39  {
40  double fac = dsexp((ex[ihi]-ex[ihi-1])*TeInverse);
41  for( long ilo=0; ilo < ihi-1; ++ilo )
42  {
43  excit[ihi][ilo] = fac*excit[ihi-1][ilo];
44  }
45  excit[ihi][ihi-1] = fac;
46  }
47 
48  if( trace.lgTrace && trace.lgTrLevN )
49  {
50  fprintf( ioQQQ, " coll str\n" );
51  fprintf( ioQQQ, " hi lo" );
52  for( long ilo=0; ilo < nlev-1; ilo++ )
53  {
54  fprintf( ioQQQ, "%4ld ", ilo );
55  }
56  fprintf( ioQQQ, " \n" );
57 
58  for( long ihi=1; ihi < nlev; ihi++ )
59  {
60  fprintf( ioQQQ, "%3ld", ihi );
61  for( long ilo=0; ilo < ihi; ilo++ )
62  {
63  fprintf( ioQQQ, "%10.2e", (col_str)[ihi][ilo] );
64  }
65  fprintf( ioQQQ, "\n" );
66  }
67 
68  fprintf( ioQQQ, " excit, te=%10.2e\n", phycon.te );
69  fprintf( ioQQQ, " hi lo" );
70 
71  for( long ilo=0; ilo < (nlev-1); ilo++ )
72  {
73  fprintf( ioQQQ, "%4ld ", ilo );
74  }
75  fprintf( ioQQQ, " \n" );
76 
77  for( long ihi=1; ihi < nlev; ihi++ )
78  {
79  fprintf( ioQQQ, "%3ld", ihi );
80  for( long ilo=0; ilo < ihi; ilo++ )
81  {
82  fprintf( ioQQQ, "%10.2e", excit[ihi][ilo] );
83  }
84  fprintf( ioQQQ, "\n" );
85  }
86  }
87 
88  for( long ihi=1; ihi < nlev; ihi++ )
89  {
90  for( long ilo=0; ilo < ihi; ilo++ )
91  {
92  /* this should be a collision strength */
93  ASSERT( (col_str)[ihi][ilo]>= 0. );
94  /* this is deexcitation rate */
95  (CollRate)[ihi][ilo] = (col_str)[ihi][ilo]/g[ihi]*dense.cdsqte;
96  /* this is excitation rate */
97  (CollRate)[ilo][ihi] = (CollRate)[ihi][ilo]*g[ihi]/g[ilo]*
98  excit[ihi][ilo];
99  }
100  }
101 }
102 
104  long int nLevelCalled,
105  /* ABUND is total abundance of species, used for nth equation
106  * if balance equations are homogeneous */
107  realnum abund,
108  /* G(nlev) is stat weight of levels */
109  const double g[],
110  /* EX(nlev) is excitation potential of levels, deg K or wavenumbers
111  * 0 for lowest level, all are energy rel to ground NOT d(ENER) */
112  const double ex[],
113  /* this is 'K' for ex[] as Kelvin deg, is 'w' for wavenumbers */
114  char chExUnits,
115  /* populations [cm-3] of each level as deduced here */
116  double pops[],
117  /* departure coefficient, derived below */
118  double depart[],
119  /* net transition rate, A * esc prob, s-1 */
120  double **AulEscp,
121  /* AulDest[ihi][ilo] is destruction rate, trans from ihi to ilo, A * dest prob,
122  * asserts confirm that [ihi][ilo] is zero */
123  double **AulDest,
124  /* AulPump[ilo][ihi] is pumping rate from lower to upper level (s^-1), (hi,lo) must be zero */
125  double **AulPump,
126  /* collision rates (s^-1), evaluated here and returned for cooling by calling function,
127  * unless following flag is true. If true then calling function has already filled
128  * in these rates. CollRate[i][j] is rate from i to j */
129  double **CollRate,
130  /* this is an additional creation rate from continuum, normally zero, units cm-3 s-1 */
131  const double source[],
132  /* this is an additional destruction rate to continuum, normally zero, units s-1 */
133  const double sink[],
134  /* total cooling and its derivative, set here but nothing done with it*/
135  double *cooltl,
136  double *coolder,
137  /* string used to identify species in case of error */
138  const char *chLabel,
139  /* flag to print matrices input to solvers */
140  const bool lgPrtMatrix,
141  /* nNegPop flag indicating what we have done
142  * positive if negative populations occurred
143  * zero if normal calculation done
144  * negative if too cold, matrix not solved, since highest level had little excitation
145  * (for some atoms other routine will be called in this case) */
146  int *nNegPop,
147  /* true if populations are zero, either due to zero abundance of very low temperature */
148  bool *lgZeroPop,
149  /* option to print debug information */
150  bool lgDeBug,
151  /* option to do atom in LTE, can be omitted, default value false */
152  bool lgLTE,
153  /* array that will be set to the cooling per line, can be omitted, default value NULL */
154  multi_arr<double,2> *Cool,
155  /* array that will be set to the cooling derivative per line, can be omitted, default value NULL */
156  multi_arr<double,2> *dCooldT,
157  /* total excitation rate out of ground state */
158  double *grnd_excit)
159 {
160  long int level,
161  ihi,
162  ilo,
163  j;
164 
165  int32 ner;
166 
167  double cool1,
168  TeInverse,
169  TeConvFac;
170 
171  DEBUG_ENTRY( "atom_levelN()" );
172 
173  *nNegPop = -1;
174  if (grnd_excit != NULL) *grnd_excit = 0.0;
175 
176  /* >>chng 05 dec 14, units of ex[] can be Kelvin (old default) or wavenumbers */
177  if( chExUnits=='K' )
178  {
179  /* ex[] is in temperature units - this will multiply ex[] to
180  * obtain Boltzmann factor */
181  TeInverse = 1./phycon.te;
182  /* this multiplies ex[] to obtain energy difference between levels */
183  TeConvFac = 1.;
184  }
185  else if( chExUnits=='w' )
186  {
187  /* ex[] is in wavenumber units */
188  TeInverse = 1./phycon.te_wn;
189  TeConvFac = T1CM;
190  }
191  else
192  TotalInsanity();
193 
194  avx_ptr<double> arg(1,nLevelCalled), excit_gnd(1,nLevelCalled);
195  for( ihi=1; ihi < nLevelCalled; ++ihi )
196  arg[ihi] = -(ex[ihi]-ex[0])*TeInverse;
197  vexp( arg.ptr0(), excit_gnd.ptr0(), 1, nLevelCalled );
198 
199  long int nlev = nLevelCalled;
200  // decrement number of levels until we have positive excitation rate,
201  while( nlev > 1 && excit_gnd[nlev-1]+AulPump[0][nlev-1] < SMALLFLOAT &&
202  source[nlev-1] == 0. )
203  {
204  pops[nlev-1] = 0.;
205  depart[nlev-1] = 0.;
206  --nlev;
207  }
208 
209  /* exit if zero abundance or all population in ground */
210  ASSERT( abund>= 0. );
211  if( abund == 0. || nlev==1 )
212  {
213  *cooltl = 0.;
214  *coolder = 0.;
215  /* says calc was ok */
216  *nNegPop = 0;
217  *lgZeroPop = true;
218 
219  pops[0] = abund;
220  depart[0] = 1.;
221  for( level=1; level < nlev; level++ )
222  {
223  pops[level] = 0.;
224  depart[level] = 0.;
225  }
226  return;
227  }
228 
229 # ifndef NDEBUG
230  /* excitation temperature of lowest level must be zero */
231  ASSERT( ex[0] == 0. );
232 
233  for( ihi=0; ihi < nlev; ihi++ )
234  {
235  for( ilo=0; ilo < nlev; ilo++ )
236  {
237  /* AulDest[ilo][ihi] - so that spontaneous transitions only proceed from high energy to low
238  * AulEscp[ilo][ihi] - so that spontaneous transitions only proceed from high energy to low */
239  ASSERT( ihi == ilo || (AulDest)[ihi][ilo] >= 0. );
240  ASSERT( ihi == ilo || (AulEscp)[ihi][ilo] >= 0 );
241  }
242  }
243 
244  for( ihi=1; ihi < nlev; ihi++ )
245  {
246  for( ilo=0; ilo < ihi; ilo++ )
247  {
248  ASSERT( (AulPump)[ihi][ilo] >= 0. );
249  }
250  }
251 # endif
252 
253  if( lgDeBug || (trace.lgTrace && trace.lgTrLevN) )
254  {
255  fprintf( ioQQQ, " atom_levelN trace printout for atom=%s with tot abund %e \n", chLabel, abund);
256  fprintf( ioQQQ, " AulDest\n" );
257 
258  fprintf( ioQQQ, " hi lo" );
259 
260  for( ilo=0; ilo < nlev-1; ilo++ )
261  {
262  fprintf( ioQQQ, "%4ld ", ilo );
263  }
264  fprintf( ioQQQ, " \n" );
265 
266  for( ihi=1; ihi < nlev; ihi++ )
267  {
268  fprintf( ioQQQ, "%3ld", ihi );
269  for( ilo=0; ilo < ihi; ilo++ )
270  {
271  fprintf( ioQQQ, "%10.2e", (AulDest)[ihi][ilo] );
272  }
273  fprintf( ioQQQ, "\n" );
274  }
275 
276  fprintf( ioQQQ, " A*esc\n" );
277  fprintf( ioQQQ, " hi lo" );
278  for( ilo=0; ilo < nlev-1; ilo++ )
279  {
280  fprintf( ioQQQ, "%4ld ", ilo );
281  }
282  fprintf( ioQQQ, " \n" );
283 
284  for( ihi=1; ihi < nlev; ihi++ )
285  {
286  fprintf( ioQQQ, "%3ld", ihi );
287  for( ilo=0; ilo < ihi; ilo++ )
288  {
289  fprintf( ioQQQ, "%10.2e", (AulEscp)[ihi][ilo] );
290  }
291  fprintf( ioQQQ, "\n" );
292  }
293 
294  fprintf( ioQQQ, " AulPump\n" );
295 
296  fprintf( ioQQQ, " hi lo" );
297  for( ilo=0; ilo < nlev-1; ilo++ )
298  {
299  fprintf( ioQQQ, "%4ld ", ilo );
300  }
301  fprintf( ioQQQ, " \n" );
302 
303  for( ihi=1; ihi < nlev; ihi++ )
304  {
305  fprintf( ioQQQ, "%3ld", ihi );
306  for( ilo=0; ilo < ihi; ilo++ )
307  {
308  fprintf( ioQQQ, "%10.2e", (AulPump)[ilo][ihi] );
309  }
310  fprintf( ioQQQ, "\n" );
311  }
312 
313  fprintf( ioQQQ, " coll rate\n" );
314  fprintf( ioQQQ, " hi lo" );
315  for( ilo=0; ilo < nlev-1; ilo++ )
316  {
317  fprintf( ioQQQ, "%4ld ", ilo );
318  }
319  fprintf( ioQQQ, " \n" );
320 
321  for( ihi=1; ihi < nlev; ihi++ )
322  {
323  fprintf( ioQQQ, "%3ld", ihi );
324  for( ilo=0; ilo < ihi; ilo++ )
325  {
326  fprintf( ioQQQ, "%10.2e", (CollRate)[ihi][ilo] );
327  }
328  fprintf( ioQQQ, "\n" );
329  }
330  }
331 
332  /* we will predict populations */
333  *lgZeroPop = false;
334 
335  if( !lgLTE )
336  {
337  bvec.resize(nlev);
338  amat.alloc(nlev,nlev);
339 
340  /* rate equations equal zero */
341  amat.zero();
342 
343  /* eqns for destruction of level
344  * AulEscp[iho][ilo] are A*esc p, CollRate is coll excit in direction
345  * AulPump[low][high] is excitation rate from low to high */
346  for( level=0; level < nlev; level++ )
347  {
348  /* leaving level to below */
349  for( ilo=0; ilo < level; ilo++ )
350  {
351  double rate = (CollRate)[level][ilo] + (AulEscp)[level][ilo] +
352  (AulDest)[level][ilo] + (AulPump)[level][ilo];
353  amat[level][level] += rate;
354  amat[level][ilo] = -rate;
355  }
356 
357  /* leaving level to above */
358  for( ihi=level + 1; ihi < nlev; ihi++ )
359  {
360  double rate = (CollRate)[level][ihi] + (AulPump)[level][ihi];
361  amat[level][level] += rate;
362  amat[level][ihi] = -rate;
363  }
364  }
365  if (grnd_excit != NULL) *grnd_excit = amat[0][0];
366 
369  {
370  double slowrate = -1.0;
371  long slowlevel = -1;
372  // level == 0 is intentionally excluded...
373  for (level=1; level < nlev; ++level)
374  {
375  double rate = dynamics.timestep*amat[level][level];
376  if (rate < slowrate || slowrate < 0.0)
377  {
378  slowrate = rate;
379  slowlevel = level;
380  }
381  }
382  if ( slowrate < 3.0 )
383  {
384  static map<string,double> failures;
385  ostringstream oss;
386  oss << chLabel << "[level=" << slowlevel << ']';
387  string str=oss.str();
388  if (failures.find(str) == failures.end())
389  {
390  failures[str] = slowrate;
391  // >>chng 16 feb 18: demoted the following message from a PROBLEM
392  // to a CAUTION to prevent a flood of messages in minor.txt...
393  fprintf(ioQQQ," CAUTION in atom_levelN: "
394  "non-equilibrium appears important for %s, "
395  "rate * timestep is %g\n",
396  str.c_str(), slowrate);
397  }
398  else
399  {
400  if ( slowrate < failures[str])
401  failures[str] = slowrate;
402  }
403  }
404  }
405 
406  double maxdiag = 0.;
407  for( level=0; level < nlev; level++ )
408  {
409  // source terms from elsewhere
410  bvec[level] = source[level];
411  if( amat[level][level] > maxdiag )
412  maxdiag = amat[level][level];
413  amat[level][level] += sink[level];
414  }
415 
416  // If there are no sources or sinks, then one of the rows of the
417  // linear system is linearly dependent on the others so there is
418  // no matrix inverse. If the sources are sufficiently small,
419  // the answer will be numerically ill-conditioned.
420  //
421  // For strictly zero sources, this may be remedied by applying a
422  // conservation constraint instead of one of the redundant rows.
423  // To handle the broader case, we here add this conservation
424  // constraint scaled to switch in smoothly as the condition
425  // number of the matrix becomes poorer (and hence the accuracy
426  // of the linear system becomes poor).
427  //
428  // The condition number estimate here is quick but very crude.
429  //
430  // Need to specify smallest condition number before we decide
431  // that conservation will get a better answer than the raw
432  // linear solver. Numerical Recipes (3rd edition, S2.6.2)
433  // suggests that ~1e-14 might be appropriate for the solution of
434  // matrices in double precision.
435 
436  const double CONDITION_SCALE = 1e-10;
437  double conserve_scale = maxdiag*CONDITION_SCALE;
438  ASSERT( conserve_scale > 0.0 );
439 
440  for( level=0; level<nlev; ++level )
441  {
442  amat[level][0] += conserve_scale;
443  }
444  bvec[0] += abund*conserve_scale;
445 
446  if( lgDeBug )
447  {
448  fprintf(ioQQQ," amat matrix follows:\n");
449  for( level=0; level < nlev; level++ )
450  {
451  for( j=0; j < nlev; j++ )
452  {
453  fprintf(ioQQQ," %.4e" , amat[level][j]);
454  }
455  fprintf(ioQQQ,"\n");
456  }
457  fprintf(ioQQQ," Vector follows:\n");
458  for( j=0; j < nlev; j++ )
459  {
460  fprintf(ioQQQ," %.4e" , bvec[j] );
461  }
462  fprintf(ioQQQ,"\n");
463  }
464 
465 #ifdef EIGEN
466  if (lgDeBug)
467  {
468  multi_arr<double,2,C_TYPE> mcol(nlev,nlev),mrad(nlev,nlev),
469  vl(nlev,nlev),vr(nlev,nlev);
470  mcol.zero();
471  mrad.zero();
472 
473  for( level=0; level < nlev; level++ )
474  {
475  /* leaving level to below */
476  for( ilo=0; ilo < level; ilo++ )
477  {
478  double rate = (*CollRate)[level][ilo];
479  mcol[level][level] += rate;
480  mcol[level][ilo] = -rate;
481  rate = (*AulEscp)[level][ilo] +
482  (*AulDest)[level][ilo] + (*AulPump)[level][ilo];
483  mrad[level][level] += rate;
484  mrad[level][ilo] = -rate;
485  }
486  /* leaving level to above */
487  for( ihi=level + 1; ihi < nlev; ihi++ )
488  {
489  double rate = (*CollRate)[level][ihi];
490  mcol[level][level] += rate;
491  mcol[level][ihi] = -rate;
492  rate = (*AulPump)[level][ihi];
493  mrad[level][level] += rate;
494  mrad[level][ihi] = -rate;
495  }
496  }
497  multi_arr<double,2,C_TYPE> mtst(nlev,nlev);
498  for( ilo=0; ilo < nlev; ilo++ )
499  {
500  for( ihi=0; ihi < nlev; ++ihi)
501  {
502  mtst[ilo][ihi] = mcol[ilo][ihi];
503  }
504  }
505  const char job[2] = "V";
506  int lwork = 4*nlev,info,inlev=nlev;
507  vector<double> wr(nlev),wi(nlev),work(lwork);
508  dgeev_(job,job,&inlev,get_ptr(mtst.vals()),&inlev,get_ptr(wr),
509  get_ptr(wi),get_ptr(vl.vals()),&inlev,
510  get_ptr(vr.vals()),&inlev,get_ptr(work),&lwork,&info,1,1);
511  fprintf(ioQQQ,"info %d\nW=",info);
512  for( level=0; level < nlev; level++ )
513  {
514  fprintf(ioQQQ,"%ld %15.8g+%15.8gi\n",level,wr[level],wi[level]);
515  }
516 
517  // Reconstruct collision matrix
518  // mcol = u w v^t
519  // mcol[ihi][ilo] = sum(level,vl[level][ilo]*w[level]*vr[level][ihi])
520  // ( u^t mrad v + w ) v^t b = 0
521 
522  // Normalize left eigen-vectors so L & R are inverses
523  for( level=0; level < nlev; level++ )
524  {
525  double tot=0.0;
526  for( ilo=0; ilo < nlev; ilo++ )
527  {
528  tot += vl[level][ilo]*vr[level][ilo];
529  }
530  tot = 1.0/tot;
531  for( ilo=0; ilo < nlev; ilo++ )
532  {
533  vl[level][ilo] *= tot;
534  }
535  }
536 
537  mtst.zero();
538  for( ilo=0; ilo < nlev; ilo++ )
539  {
540  for( ihi=0; ihi < nlev; ihi++ )
541  {
542  for( level=0; level < nlev; level++ )
543  {
544  mtst[ilo][ihi] += mrad[level][ihi] * vr[ilo][level]; // or mcol to test...
545  }
546  }
547  }
548  multi_arr<double,2,C_TYPE> mtst1(nlev,nlev);
549  mtst1.zero();
550  for( ilo=0; ilo < nlev; ilo++ )
551  {
552  for( ihi=0; ihi < nlev; ihi++ )
553  {
554  for( level=0; level < nlev; level++ )
555  {
556  mtst1[ilo][ihi] += vl[ihi][level]*mtst[ilo][level];
557  }
558  }
559  }
560  for( level=0; level < nlev; level++ )
561  {
562  fprintf(ioQQQ,"%ld %15.8g %15.8g\n",level,wr[level],mtst1[level][level]);
563  }
564  }
565 #endif
566 
567  if( lgPrtMatrix )
568  {
569  prt.matrix.prtRates( nlev, amat, bvec );
570  }
571 
572  ner = solve_system(amat.vals(), bvec, nlev, NULL);
573 
574  if( ner != 0 )
575  {
576  fprintf( ioQQQ, " PROBLEM atom_levelN: dgetrs finds singular or ill-conditioned matrix for %s Search? %c Te:%.3e\n",
577  chLabel , TorF(conv.lgSearch) , phycon.te );
578  fprintf( ioQQQ, " Old, new level pops follow\n" );
579  for( level=0; level < nlev; level++ )
580  {
581  /* save bvec into populations */
582  fprintf(ioQQQ, "%ld %.2e %.2e \n" , level, pops[level] , bvec[level] );
583  }
585  }
586 
587  /* set populations */
588  for( level=0; level < nlev; level++ )
589  {
590  /* save bvec into populations */
591  pops[level] = bvec[level];
592  }
593 
594  /* now find total energy exchange rate, normally net cooling and its
595  * temperature derivative */
596  *cooltl = 0.;
597  *coolder = 0.;
598  for( ihi=1; ihi < nlev; ihi++ )
599  {
600  for( ilo=0; ilo < ihi; ilo++ )
601  {
602  /* this is now net cooling rate [erg cm-3 s-1] */
603  cool1 = (pops[ilo]*(CollRate)[ilo][ihi] - pops[ihi]*(CollRate)[ihi][ilo])*
604  (ex[ihi] - ex[ilo])*BOLTZMANN*TeConvFac;
605  *cooltl += cool1;
606  if( Cool != NULL )
607  (*Cool)[ihi][ilo] = cool1;
608  /* derivative wrt temperature - use Boltzmann factor relative to ground */
609  /* >>chng 03 aug 28, use real cool1 */
610  double dcooldT1 = cool1*( (ex[ihi] - ex[0])*thermal.tsq1 - thermal.halfte );
611  *coolder += dcooldT1;
612  if( dCooldT != NULL )
613  (*dCooldT)[ihi][ilo] = dcooldT1;
614  }
615  }
616 
617  /* fill in departure coefficients */
618  if( pops[0] > SMALLFLOAT && excit_gnd[nlev-1] > SMALLFLOAT )
619  {
620  /* >>chng 00 aug 10, loop had been from 1 and 0 was set to total abundance */
621  depart[0] = 1.;
622  for( ihi=1; ihi < nlev; ihi++ )
623  {
624  /* these are off by one - lowest index is zero */
625  depart[ihi] = (pops[ihi]/pops[0])*(g[0]/g[ihi])/excit_gnd[ihi];
626  }
627  }
628 
629  else
630  {
631  /* >>chng 00 aug 10, loop had been from 1 and 0 was set to total abundance */
632  for( ihi=0; ihi < nlev; ihi++ )
633  {
634  /* Boltzmann factor or abundance too small, set departure coefficient to zero */
635  depart[ihi] = 0.;
636  }
637  depart[0] = 1.;
638  }
639  }
640  else
641  {
642  /* this branch calculates LTE level populations */
643 
644  /* get the value of the partition function and the derivative */
645  valarray<double> help1(nlev), help2(nlev);
646  double partfun = help1[0] = g[0];
647  double dpfdT = help2[0] = 0.; /* help2 is d(help1)/dT */
648  for( level=1; level < nlev; level++ )
649  {
650  help1[level] = g[level]*excit_gnd[level];
651  partfun += help1[level];
652  help2[level] = help1[level]*(ex[level]-ex[0])*TeInverse/phycon.te;
653  dpfdT += help2[level];
654  }
655 
656  /* calculate the level populations and departure coefficients,
657  * as well as the derivative of the level pops */
658  valarray<double> dndT(nlev);
659  for( level=0; level < nlev; level++ )
660  {
661  pops[level] = abund*help1[level]/partfun;
662  dndT[level] = abund*(help2[level]*partfun - dpfdT*help1[level])/pow2(partfun);
663  depart[level] = 1.;
664  }
665 
666  /* now find the net cooling from the atom */
667  *cooltl = 0.;
668  *coolder = 0.;
669  for( ihi=1; ihi < nlev; ihi++ )
670  {
671  for( ilo=0; ilo < ihi; ilo++ )
672  {
673  double deltaE = (ex[ihi] - ex[ilo])*BOLTZMANN*TeConvFac;
674  double cool1 = (pops[ihi]*((AulEscp)[ihi][ilo] + (AulPump)[ihi][ilo]) -
675  pops[ilo]*(AulPump)[ilo][ihi])*deltaE;
676  *cooltl += cool1;
677  if( Cool != NULL )
678  (*Cool)[ihi][ilo] = cool1;
679  double dcooldT1 = (dndT[ihi]*((AulEscp)[ihi][ilo] + (AulPump)[ihi][ilo]) -
680  dndT[ilo]*(AulPump)[ilo][ihi])*deltaE;
681  *coolder += dcooldT1;
682  if( dCooldT != NULL )
683  (*dCooldT)[ihi][ilo] = dcooldT1;
684  }
685  }
686  }
687 
688  /* sanity check for valid solution - non negative populations */
689  *nNegPop = 0;
690  /* the limit we allow the fractional population to go below zero before announcing failure. */
691  // >>chng 20 jul 28 from 1e-10 to 1e-7 to pass Morriset PN sims which failed with
692  // nearly all Ca atomic, Ca 6 - 9 about -1e-10
693  double poplimit = 1.0e-10;
694  /* identifies special case where nearly all atomic */
695  if( pops[0]/abund > 0.99 )
696  poplimit = 1.0e-7;
697 
698  for( level=0; level < nlev; level++ )
699  {
700  if( pops[level] < 0. )
701  {
702  if( fabs(pops[level]/abund) > poplimit )
703  {
704  //nNegPop >= 1 leads to a failure
705  *nNegPop = *nNegPop + 1;
706  }
707  else
708  {
709  pops[level] = SMALLFLOAT;
710  //fprintf( ioQQQ, "\n PROBLEM Small negative populations were found in atom = %s . "
711  // "The problem was ignored and the negative populations were set to SMALLFLOAT",chLabel );
712  }
713  }
714  }
715 
716  if( *nNegPop!=0 )
717  {
718  ASSERT( *nNegPop>=1 );
719  // *nNegPop 0 is valid solution, nNegPop> 1 negative populations found
720  fprintf( ioQQQ, "\n PROBLEM atom_levelN found negative population, nNegPop=%i, atom=%s lgSearch=%c Te=%10.2e fnzone %.2f \n",
721  *nNegPop , chLabel , TorF( conv.lgSearch ) , phycon.te , fnzone );
722 
723  // absolute then relative abundances
724  fprintf(ioQQQ," Absolute: ");
725  for( level=0; level < nlev; level++ )
726  {
727  fprintf( ioQQQ, "%10.2e", pops[level] );
728  }
729  fprintf( ioQQQ, "\n" );
730  fprintf(ioQQQ," Relative: ");
731  for( level=0; level < nlev; level++ )
732  {
733  fprintf( ioQQQ, "%10.2e", pops[level]/abund );
734  }
735  fprintf( ioQQQ, "\n" );
736 
737  for( level=0; level < nlev; level++ )
738  {
739  pops[level] = (double)MAX2(0.,pops[level]);
740  }
741  }
742 
743  if( lgDeBug || (trace.lgTrace && trace.lgTrLevN) )
744  {
745  fprintf( ioQQQ, "\n atom_leveln absolute population " );
746  for( level=0; level < nlev; level++ )
747  {
748  fprintf( ioQQQ, " %10.2e", pops[level] );
749  }
750  fprintf( ioQQQ, "\n" );
751 
752  fprintf( ioQQQ, " departure coefficients" );
753  for( level=0; level < nlev; level++ )
754  {
755  fprintf( ioQQQ, " %10.2e", depart[level] );
756  }
757  fprintf( ioQQQ, "\n\n" );
758  }
759 
760 # ifndef NDEBUG
761  /* these were reset to non zero values by the solver, but we will
762  * assert that they are zero (for safety) when routine reenters so must
763  * set to zero here, but only if asserts are in place */
764  for( ilo=0; ilo < nlev; ilo++ )
765  {
766  for( ihi=ilo+1; ihi < nlev; ihi++ )
767  {
768  /* zero ots destruction rate */
769  AulDest[ilo][ihi] = 0.;
770  }
771  }
772  for( ihi=1; ihi < nlev; ihi++ )
773  {
774  for( ilo=0; ilo < ihi; ilo++ )
775  {
776  /* both AulDest and AulPump (low, hi) are not used, should be zero */
777  AulPump[ihi][ilo] = 0.;
778  }
779  }
780  for( ilo=0; ilo < nlev; ilo++ )
781  {
782  for( ihi=ilo+1; ihi < nlev; ihi++ )
783  {
784  AulEscp[ilo][ihi] = 0;
785  }
786  }
787 # endif
788 
789  // -1 return had meant too cool and no populations determined, no longer used
790  // since we now decrement until populations can be determined
791  ASSERT( *nNegPop>=0 );
792 
793  return;
794 }
t_thermal thermal
Definition: thermal.cpp:6
T * ptr0()
Definition: vectorize.h:240
T * get_ptr(T *v)
Definition: cddefines.h:1109
NORETURN void TotalInsanity(void)
Definition: service.cpp:971
double depart(const genericState &gs)
void resize(long int nlev)
Definition: atoms.h:52
const realnum SMALLFLOAT
Definition: cpu.h:246
void prtRates(const long nlevels_local, const multi_arr< double, 2, C_TYPE > &a, valarray< double > &b)
Definition: prt.cpp:234
double cdsqte
Definition: dense.h:246
char TorF(bool l)
Definition: cddefines.h:749
bool lgAdvection
Definition: dynamics.h:66
bool lgTimeDependentStatic
Definition: dynamics.h:102
t_conv conv
Definition: conv.cpp:5
t_phycon phycon
Definition: phycon.cpp:6
FILE * ioQQQ
Definition: cddefines.cpp:7
t_dynamics dynamics
Definition: dynamics.cpp:42
double dsexp(double x)
Definition: service.cpp:1038
t_dense dense
Definition: global.cpp:15
void vexp(const double x[], double y[], long nlo, long nhi)
long int iteration
Definition: cddefines.cpp:16
bool lgSearch
Definition: conv.h:168
t_trace trace
Definition: trace.cpp:5
t_abund abund
Definition: abund.cpp:5
bool lgTrace
Definition: trace.h:12
double tsq1
Definition: thermal.h:142
double timestep
Definition: dynamics.h:187
float realnum
Definition: cddefines.h:124
#define EXIT_FAILURE
Definition: cddefines.h:168
int32 solve_system(const valarray< double > &a, valarray< double > &b, long int n, error_print_t error_print)
void operator()(long nLevelCalled, realnum abund, const double g[], const double ex[], char chExUnits, double pops[], double depart[], double **AulEscp, double **AulDest, double **AulPump, double **CollRate, const double create[], const double destroy[], double *cooltl, double *coolder, const char *chLabel, bool lgPrtMatrix, int *nNegPop, bool *lgZeroPop, bool lgDeBug, bool lgLTE=false, multi_arr< double, 2 > *Cool=NULL, multi_arr< double, 2 > *dCooldT=NULL, double *grnd_excit=NULL)
#define cdEXIT(FAIL)
Definition: cddefines.h:482
double halfte
Definition: thermal.h:142
t_prt prt
Definition: prt.cpp:14
vector< double * > excit
Definition: atoms.h:48
valarray< T > & vals()
#define ASSERT(exp)
Definition: cddefines.h:613
multi_arr< double, 2, C_TYPE > amat
Definition: atoms.h:82
T pow2(T a)
Definition: cddefines.h:981
t_prt_matrix matrix
Definition: prt.h:238
#define DEBUG_ENTRY(funcname)
Definition: cddefines.h:723
#define MAX2(a, b)
Definition: cddefines.h:824
int fprintf(const Output &stream, const char *format,...)
Definition: service.cpp:1121
long int n_initial_relax
Definition: dynamics.h:132
double te_wn
Definition: phycon.h:30
double fnzone
Definition: cddefines.cpp:15
double te
Definition: phycon.h:21
bool lgTrLevN
Definition: trace.h:31
void operator()(long nlev, double TeInverse, double **col_str, const double ex[], const double g[], double **CollRate)
Definition: atom_leveln.cpp:28
valarray< double > bvec
Definition: atoms.h:81