cloudy  trunk
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ion_trim.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 /*ion_trim raise or lower most extreme stages of ionization considered,
4  * called by ConvBase - ion limits were originally set by */
5 #include "cddefines.h"
6 #include "ion_trim.h"
7 
8 #include "heavy.h"
9 #include "conv.h"
10 #include "rfield.h"
11 #include "phycon.h"
12 #include "mole.h"
13 #include "thermal.h"
14 #include "iso.h"
15 #include "struc.h"
16 #include "ionbal.h"
17 #include "dense.h"
18 #include "dynamics.h"
19 
20 void ion_trim_untrim( long nelem )
21 {
22  DEBUG_ENTRY( "ion_trim_untrim()" );
23  dense.IonLow[nelem] = 0;
24  dense.IonHigh[nelem] = nelem+1;
25 }
26 void ion_trim_invalidate( long nelem )
27 {
28  DEBUG_ENTRY( "ion_trim_invalidate()" );
29  dense.IonLow[nelem] = -1;
30  dense.IonHigh[nelem] = -1;
31 }
32 
33 STATIC void ion_trim_from_set( long nelem);
34 
36 {
37  DEBUG_ENTRY( "ion_trim_init()" );
38  for(long nelem=ipHYDROGEN; nelem < LIMELM; nelem++ )
39  {
40  if( !dense.lgElmtOn[nelem] )
41  {
42  ion_trim_invalidate(nelem);
43  continue;
44  }
45 
46  if( dense.lgSetIoniz[nelem] )
47  {
48  /* >>chng 04 jan 13, add this test, caught by Orly Gnat */
49  /* check on actual zero abundances of lower stages - this will only
50  * happen when ionization is set with element ionization command */
51  ion_trim_from_set(nelem);
52  }
53  else
54  {
55  // IonHigh[n] is the highest stage of ionization present
56  // the IonHigh array index is on the C scale, so [0] is hydrogen
57  // the value is also on the C scale, so element [nelem] can range
58  // from 0 to nelem+1
59  dense.IonHigh[nelem] = nelem + 1;
60 
61  dense.IonLow[nelem] = 0;
62  // for very intense radiation fields very heavy elements, N>Fe, will fail
63  // in ion_solver due to ill conditioned matrix. all populations are in
64  // fully stripped state. Start will fully stripped ion distribution in this case.
65  if( rfield.uh > 1e15 )
66  {
67  //trim down highest stage to be within incident radiation field
68  while ( ionbal.lgTrimhiOn &&
69  rfield.anu(Heavy.ipHeavy[nelem][dense.IonHigh[nelem]-1]) >
70  rfield.anu(rfield.nflux) && dense.IonHigh[nelem]>1 )
71  --dense.IonHigh[nelem];
72 
73  if (ionbal.lgTrimloOn)
74  dense.IonLow[nelem] = max( 0 , dense.IonHigh[nelem]-1 );
75  }
76  }
77 
78  // make low-stage populations zero
79  for( long ion=0; ion<dense.IonLow[nelem]; ++ion )
80  {
81  dense.xIonDense[nelem][dense.IonLow[nelem]] += dense.xIonDense[nelem][ion];
82  dense.xIonDense[nelem][ion] = 0.;
83  }
84  for( long ion=nelem+1; ion>dense.IonHigh[nelem]; --ion )
85  {
86  dense.xIonDense[nelem][dense.IonHigh[nelem]] += dense.xIonDense[nelem][ion];
87  dense.xIonDense[nelem][ion] = 0.;
88  }
89  }
90 }
91 
93 {
94  DEBUG_ENTRY( "ion_trim_from_set()" );
95  for( long nelem=ipHYDROGEN; nelem < LIMELM; nelem++ )
96  {
97  if( dense.lgSetIoniz[nelem] )
98  {
99  ion_trim_from_set(nelem);
100  }
101  }
102 }
103 
104 STATIC void ion_trim_from_set( long nelem )
105 {
106  DEBUG_ENTRY( "ion_trim_from_set()" );
107  dense.IonLow[nelem] = 0;
108  dense.IonHigh[nelem] = nelem + 1;
109  while( ionbal.lgTrimloOn && dense.SetIoniz[nelem][dense.IonLow[nelem]] < dense.density_low_limit )
110  ++dense.IonLow[nelem];
111  while( ionbal.lgTrimhiOn && dense.SetIoniz[nelem][dense.IonHigh[nelem]] < dense.density_low_limit )
112  --dense.IonHigh[nelem];
113 }
114 
115 void ion_trim_small (long nelem, double abund_total)
116 {
117  DEBUG_ENTRY( "ion_trim_small()" );
118  while( ionbal.lgTrimhiOn && dense.IonHigh[nelem] > dense.IonLow[nelem] &&
119  dense.xIonDense[nelem][dense.IonHigh[nelem]] < 1e-25*abund_total &&
120  dense.IonHigh[nelem] > 1)
121  {
122  ASSERT( dense.xIonDense[nelem][dense.IonHigh[nelem]] >= 0. );
123  /* zero out abundance and heating due to stage of ionization we are about to zero out */
124  dense.xIonDense[nelem][dense.IonHigh[nelem]] = 0.;
125  thermal.setHeating(nelem,dense.IonHigh[nelem]-1,0.);
126  /* decrement counter */
127  --dense.IonHigh[nelem];
128  }
129 }
130 
131 void mole_ion_trim(void)
132 {
133  DEBUG_ENTRY( "mole_ion_trim()" );
134  for (ChemNuclideList::iterator atom = nuclide_list.begin();
135  atom != nuclide_list.end(); ++atom)
136  {
137  if( !(*atom)->lgHasLinkedIon())
138  continue;
139  const long int nelem=(*atom)->el->Z-1;
140  if( !dense.lgElmtOn[nelem] )
141  continue;
142 
143  for (long int ion=0;ion<nelem+2;ion++)
144  {
145  if ((*atom)->ipMl[ion] != -1)
146  {
147  if ( ionbal.lgTrimloOn && dense.IonLow[nelem] > ion )
148  {
149  if( dense.xIonDense[nelem][ion] > ( ionbal.trimlo * dense.gas_phase[nelem] ) )
150  {
151  dense.IonLow[nelem] = ion;
152  }
153  else
154  {
155  dense.xIonDense[nelem][dense.IonLow[nelem]] += dense.xIonDense[nelem][ion];
156  dense.xIonDense[nelem][ion] = 0.;
157  }
158  }
159 
160  if ( ionbal.lgTrimhiOn && dense.IonHigh[nelem] < ion )
161  {
162  if( dense.xIonDense[nelem][ion] > ( ionbal.trimhi * dense.gas_phase[nelem] ) )
163  {
164  dense.IonHigh[nelem] = ion;
165  }
166  else
167  {
168  dense.xIonDense[nelem][dense.IonHigh[nelem]] += dense.xIonDense[nelem][ion];
169  dense.xIonDense[nelem][ion] = 0.;
170  }
171  }
172  }
173  }
174  }
175 }
176 
177 void ion_trim(
178  /* nelem is on the C scale, 0 for H, 5 for C */
179  long int nelem )
180 {
181 
182  /* this will remember that higher stages trimed up or down */
183  bool lgHi_Down = false;
184  bool lgHi_Up = false;
185  bool lgHi_Up_enable;
186  /* this will remember that lower stages trimmed up or own*/
187  bool lgLo_Up = false;
188  bool lgLo_Down = false;
189  long int ion_lo_save = dense.IonLow[nelem],
190  ion_hi_save = dense.IonHigh[nelem];
191  long int ion;
192  realnum trimhi , trimlo;
193 
194  /* this is debugging code that turns on print after certain number of calls */
195  /*realnum xlimit = SMALLFLOAT *10.;*/
196  /*static int ncall=0;
197  if( nelem==5 )
198  ++ncall;*/
199 
200  DEBUG_ENTRY( "ion_trim()" );
201 
202  /* this routine should not be called early in the simulation, before
203  * things have settled down */
204  ASSERT( conv.nTotalIoniz>2 );
205 
206  /*confirm all ionization stages are within their range of validity */
207  ASSERT( nelem >= ipHELIUM && nelem < LIMELM );
208  ASSERT( dense.IonLow[nelem] >= 0 );
209  ASSERT( dense.IonHigh[nelem] <= nelem+1 );
210  /* IonHigh can be equal to IonLow */
211  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
212 
213  /* during search phase of mostly neutral matter the electron density
214  * can be vastly too large, and the ionization suppressed as a result.
215  * during search do not trim down or up as much */
216  if( conv.lgSearch )
217  {
218  trimhi = (realnum)ionbal.trimhi * 1e-4f;
219  trimlo = (realnum)ionbal.trimlo * 1e-4f;
220  }
221  else
222  {
223  trimhi = (realnum)ionbal.trimhi;
224  trimlo = (realnum)ionbal.trimlo;
225  }
226 
227  /* helium is special case since abundance is so high, and He+ CT with
228  * CO is the dominant CO destruction process in molecular regions */
229  if( nelem == ipHELIUM )
230  {
231  /* never want to trip up a lower stage of ionization */
232  trimlo = SMALLFLOAT;
233 
234  /* if He+ is highest stage of ionization, probably want to keep it
235  * since important for CO chemistry in molecular clouds */
236  if( dense.IonHigh[ipHELIUM] == 1 )
237  {
238  trimhi = MIN2( trimhi , 1e-20f );
239  }
240  else if( dense.IonHigh[ipHELIUM] == 2 )
241  {
242  if( conv.lgSearch )
243  {
244  /* during search phase we may be quite far from solution, in the
245  * case of a PDR sim the electron density may be vastly higher
246  * than it will be with stable solution. He++ can be very important
247  * for the chemistry and we want to consider it. Make the
248  * threshold for ignoring He++ much higher than normal to prevent
249  * a premature removal of the ion */
250  trimhi = MIN2( trimhi , 1e-17f );
251  }
252  else
253  {
254  /* similar smaller upper limit for ion*/
255  trimhi = MIN2( trimhi , 1e-12f );
256  }
257  }
258  }
259 
260  /* logic for PDRs, for elements included in chemistry, need stable solutions,
261  * keep 3 ion stages in most cases - added by NA to do HII/PDR sims */
262  if( !mole_global.lgNoMole )
263  {
264  trimlo = SMALLFLOAT;
265  if(dense.IonHigh[nelem] ==2)
266  {
267  trimhi = MIN2(trimhi, 1e-20f);
268  }
269  }
270 
271  /* raise or lower highest and lowest stages of ionization to
272  * consider only significant stages
273  * IonHigh[nelem] stage of ionization */
274 
275  /* this is a special block for initialization only - it checks on absurd abundances
276  * and can trim multiple stages of ionization at one time. */
277  if( conv.lgSearch )
278  {
279  /* special - trim down higher stages if they have essentially zero abundance */
280  while( ionbal.lgTrimhiOn &&
281  (dense.xIonDense[nelem][dense.IonHigh[nelem]]/dense.gas_phase[nelem] < dense.density_low_limit ||
282  dense.xIonDense[nelem][dense.IonHigh[nelem]] < dense.density_low_limit ) &&
283  /* >>chng 02 may 12, rm +1 since this had effect of not allowing fully atomic */
284  dense.IonHigh[nelem] > dense.IonLow[nelem] )
285  {
286  /* dense.xIonDense[nelem][i] is density of i+1 th ionization stage (cm^-3)
287  * the -1 is correct for the heating, -1 since heating is caused by ionization of stage below it */
288  dense.xIonDense[nelem][dense.IonHigh[nelem]-1] +=
289  dense.xIonDense[nelem][dense.IonHigh[nelem]];
290  dense.xIonDense[nelem][dense.IonHigh[nelem]] = 0.;
291  thermal.setHeating(nelem,dense.IonHigh[nelem]-1,0.);
292  if( dense.IonHigh[nelem] == nelem+1-NISO )
293  {
294  long int ipISO = nelem - dense.IonHigh[nelem];
295  ASSERT( ipISO>=0 && ipISO<NISO );
296  for( long level = 0; level < iso_sp[ipISO][nelem].numLevels_max; level++ )
297  iso_sp[ipISO][nelem].st[level].Pop() = 0.;
298  }
299 
300  /* decrement high stage limit */
301  --dense.IonHigh[nelem];
302  ASSERT( dense.IonHigh[nelem] >= 0);
303  /* remember this happened */
304  lgHi_Down = true;
305  }
306 
307  /* special - trim up lower stages trim if they have essentially zero abundance */
308  while( ionbal.lgTrimloOn &&
309  (dense.xIonDense[nelem][dense.IonLow[nelem]]/dense.gas_phase[nelem] < dense.density_low_limit ||
310  dense.xIonDense[nelem][dense.IonLow[nelem]] < dense.density_low_limit ) &&
311  dense.IonLow[nelem] < dense.IonHigh[nelem] - 1 )
312  {
313  /* dense.xIonDense[nelem][i] is density of ith ionization stage (cm^-3)
314  * there is no-1 since we are removing the agent that heats */
315  dense.xIonDense[nelem][dense.IonLow[nelem]+1] +=
316  dense.xIonDense[nelem][dense.IonLow[nelem]];
317  dense.xIonDense[nelem][dense.IonLow[nelem]] = 0.;
318  /* >>chng 01 aug 04, remove -1 which clobbers thermal.heating when IonLow == 0 */
319  thermal.setHeating(nelem,dense.IonLow[nelem],0.);
320  if( dense.IonLow[nelem] == nelem+1-NISO )
321  {
322  long int ipISO = nelem - dense.IonLow[nelem];
323  ASSERT( ipISO>=0 && ipISO<NISO );
324  for( long level = 0; level < iso_sp[ipISO][nelem].numLevels_max; level++ )
325  iso_sp[ipISO][nelem].st[level].Pop() = 0.;
326  }
327 
328  /* increment low stage limit */
329  ++dense.IonLow[nelem];
330  lgLo_Up = true;
331  }
332  }
333 
334  /* sanity check */
335  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
336 
337  /* this checks on error condition where the gas is stupendously highly ionized,
338  * the low limit is already one less than high, and we need to raise the low
339  * limit further */
340  if( dense.IonHigh[nelem] > 1 &&
341  (dense.IonLow[nelem]==dense.IonHigh[nelem]-1) &&
342  (dense.xIonDense[nelem][dense.IonLow[nelem]] < dense.density_low_limit) )
343  {
344 # if 0
345  fprintf(ioQQQ,
346  "PROBLEM DISASTER the density of ion %li of element %s is too small to be computed on this cpu.\n",
347  dense.IonLow[nelem]+1,
348  elementnames.chElementName[nelem]);
349  fprintf(ioQQQ,
350  "Turn off the element with the command ELEMENT XXX OFF or do not consider "
351  "gas with low density, the current hydrogen density is %.2e cm-3.\n",
353  fprintf(ioQQQ,
354  "The outer radius of the cloud is %.2e cm - should a stopping "
355  "radius be set?\n",
356  radius.Radius );
357  fprintf(ioQQQ,
358  "The abort flag is being set - the calculation is stopping.\n");
359 # endif
360  //lgAbort = true;
362  return;
363  }
364 
365  /* trim down high stages that have too small an abundance */
366  while( ionbal.lgTrimhiOn &&
367  dense.IonHigh[nelem] > dense.IonLow[nelem] &&
368  ( (dense.xIonDense[nelem][dense.IonHigh[nelem]]/dense.gas_phase[nelem] <=
369  trimhi ) ||
370  (dense.xIonDense[nelem][dense.IonHigh[nelem]] <= dense.density_low_limit ) )
371  )
372  {
373  /* >>chng 03 sep 30, the atom and its first ion are a special case
374  * since we want to compute even trivial ions in molecular clouds */
375  if( dense.IonHigh[nelem]>1 ||
376  (dense.IonHigh[nelem]==1&&dense.xIonDense[nelem][1]<100.*dense.density_low_limit) )
377  {
378  dense.xIonDense[nelem][dense.IonHigh[nelem]-1] +=
379  dense.xIonDense[nelem][dense.IonHigh[nelem]];
380  dense.xIonDense[nelem][dense.IonHigh[nelem]] = 0.;
381  thermal.setHeating(nelem,dense.IonHigh[nelem]-1,0.);
382  if( dense.IonHigh[nelem] == nelem+1-NISO )
383  {
384  long int ipISO = nelem - dense.IonHigh[nelem];
385  ASSERT( ipISO>=0 && ipISO<NISO );
386  for( long level = 0; level < iso_sp[ipISO][nelem].numLevels_max; level++ )
387  iso_sp[ipISO][nelem].st[level].Pop() = 0.;
388  }
389  --dense.IonHigh[nelem];
390  lgHi_Down = true;
391  }
392  else
393  {
394  break;
395  }
396  }
397 
398  /* trim up highest stages - will this be done? */
399  lgHi_Up_enable = true;
400  /* trimming can be turned off */
401  if( !ionbal.lgTrimhiOn )
402  lgHi_Up_enable = false;
403  /* high stage is already fully stripped */
404  if( dense.IonHigh[nelem]>=nelem+1 )
405  lgHi_Up_enable = false;
406  /* we have previously trimmed this stage down */
407  if( lgHi_Down )
408  lgHi_Up_enable = false;
409  /* we are in neutral gas */
411  lgHi_Up_enable = false;
412 
413  if( lgHi_Up_enable )
414  {
415  realnum abundold = 0;
416  if( nzone>2 )
417  abundold = struc.xIonDense[nzone-3][nelem][dense.IonHigh[nelem]]/
418  SDIV( struc.gas_phase[nzone-3][nelem]);
419  realnum abundnew = dense.xIonDense[nelem][dense.IonHigh[nelem]]/dense.gas_phase[nelem];
420  /* only raise highest stage if ionization potential of next highest stage is within
421  * continuum array and the abundance of the highest stage is significant */
422  if( (Heavy.Valence_IP_Ryd[nelem][dense.IonHigh[nelem]] < rfield.anu(rfield.nflux-1) ||
423  Heavy.Valence_IP_Ryd[nelem][dense.IonHigh[nelem]] < phycon.te_ryd*10.) &&
424  dense.xIonDense[nelem][dense.IonHigh[nelem]]/dense.gas_phase[nelem] > trimhi &&
425  /* this checks that abundance of highest stage is increasing */
426  abundnew > abundold*1.01 )
427  {
428  /*fprintf(ioQQQ,"uuppp %li %li \n", nelem, dense.IonHigh[nelem] );*/
429  /* raise highest level of ionization */
430  ++dense.IonHigh[nelem];
431  lgHi_Up = true;
432  /* set abundance to almost zero so that sanity check elsewhere will be ok */
433  dense.xIonDense[nelem][dense.IonHigh[nelem]] = (dense.density_low_limit*10.);
434  dense.xIonDense[nelem][dense.IonHigh[nelem]-1] -=
435  dense.xIonDense[nelem][dense.IonHigh[nelem]];
436  long int ipISO = nelem - dense.IonHigh[nelem];
437  if (ipISO >= 0 && ipISO < NISO)
438  iso_sp[ipISO][nelem].st[0].Pop() = dense.xIonDense[nelem][dense.IonHigh[nelem]];
439  }
440  }
441 
442  /* sanity check */
443  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
444 
445  /* lower lowest stage of ionization if we have significant abundance at current lowest */
446  if( dense.xIonDense[nelem][dense.IonLow[nelem]]/dense.gas_phase[nelem] > trimlo &&
447  dense.IonLow[nelem] > 0 )
448  {
449  /* lower lowest level of ionization */
450  --dense.IonLow[nelem];
451  lgLo_Down = true;
452  /* >>chng 01 aug 02, set this to zero so that sanity check elsewhere will be ok */
454  dense.xIonDense[nelem][dense.IonLow[nelem]+1] -=
455  dense.xIonDense[nelem][dense.IonLow[nelem]];
456  long int ipISO = nelem - dense.IonLow[nelem];
457  if (ipISO >= 0 && ipISO < NISO)
458  iso_sp[ipISO][nelem].st[0].Pop() = dense.xIonDense[nelem][dense.IonLow[nelem]];
459  }
460 
461  /* raise lowest stage of ionization, but only if we are near illuminated face of cloud*/
462  else if( ionbal.lgTrimloOn && nzone < 10 &&
463  (dense.xIonDense[nelem][dense.IonLow[nelem]]/dense.gas_phase[nelem] <= (realnum)trimlo) &&
464  (dense.IonLow[nelem] < (dense.IonHigh[nelem] - 2) ) )
465  {
466  /* raise lowest level of ionization */
467  dense.xIonDense[nelem][dense.IonLow[nelem]+1] +=
468  dense.xIonDense[nelem][dense.IonLow[nelem]];
469  dense.xIonDense[nelem][dense.IonLow[nelem]] = 0.;
470  /* no minus one in below since this is low bound, already bounds at atom */
471  thermal.setHeating(nelem,dense.IonLow[nelem],0.);
472  if( dense.IonLow[nelem] == nelem+1-NISO )
473  {
474  long int ipISO = nelem - dense.IonLow[nelem];
475  ASSERT( ipISO>=0 && ipISO<NISO );
476  for( long level = 0; level < iso_sp[ipISO][nelem].numLevels_max; level++ )
477  iso_sp[ipISO][nelem].st[level].Pop() = 0.;
478  }
479  ++dense.IonLow[nelem];
480  lgLo_Up = true;
481  }
482  /* test on zero */
483  else if( ionbal.lgTrimloOn && (dense.xIonDense[nelem][dense.IonLow[nelem]] < dense.density_low_limit) &&
484  /*>>chng 06 may 24, from IonLow < IonHigh to <IonHigh-1 because
485  * old test would allow low to be raised too high, which then
486  * leads to insanity */
487  (dense.IonLow[nelem] < dense.IonHigh[nelem]-1) )
488  {
489  while(dense.xIonDense[nelem][dense.IonLow[nelem]] < dense.density_low_limit &&
490  /* >>chng 07 feb 27 from < IonHigh to < IonHigh-1 to prevent raising
491  * low to high */
492  (dense.IonLow[nelem] < dense.IonHigh[nelem]-1) )
493  {
494  /* raise lowest level of ionization */
495  dense.xIonDense[nelem][dense.IonLow[nelem]+1] +=
496  dense.xIonDense[nelem][dense.IonLow[nelem]];
497  dense.xIonDense[nelem][dense.IonLow[nelem]] = 0.;
498  /* no minus one in below since this is low bound, already bounds at atom */
499  thermal.setHeating(nelem,dense.IonLow[nelem],0.);
500  if( dense.IonLow[nelem] == nelem+1-NISO )
501  {
502  long int ipISO = nelem - dense.IonLow[nelem];
503  ASSERT( ipISO>=0 && ipISO<NISO );
504  for( long level = 0; level < iso_sp[ipISO][nelem].numLevels_max; level++ )
505  iso_sp[ipISO][nelem].st[level].Pop() = 0.;
506  }
507  ++dense.IonLow[nelem];
508  lgLo_Up = true;
509  }
510  }
511 
512  /* sanity check */
513  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
514 
515  /* these are standard bounds checks that appear throughout this routine
516  * dense.xIonDense[IonLow] is first one with positive abundances
517  *
518  * in case where lower ionization stage was just lowered the abundance
519  * was set to dense.density_low_limit so test must be < dense.density_low_limit */
520  ASSERT( dense.IonLow[nelem] <= 1 ||
521  dense.xIonDense[nelem][dense.IonLow[nelem]-1] == 0. );
522 
523  ASSERT( (dense.IonLow[nelem]==0 && dense.IonHigh[nelem]==0 ) || lgLo_Up ||
524  ! ionbal.lgTrimloOn ||
525  dense.xIonDense[nelem][dense.IonLow[nelem]] >= dense.density_low_limit ||
526  dense.xIonDense[nelem][dense.IonLow[nelem]]/dense.gas_phase[nelem] >= dense.density_low_limit ||
527  /*>>chng 06 may 24, include this to cover case where cap is set by not allowing
528  * lower limit to come up to upper limit */
529  (dense.IonLow[nelem]==dense.IonHigh[nelem]-1 ));
530 
531  {
532  /* option to print out what has happened so far .... */
533  enum {DEBUG_LOC=false};
534  if( DEBUG_LOC && nelem == ipHELIUM )
535  {
536  if( lgHi_Down ||lgHi_Up ||lgLo_Up ||lgLo_Down )
537  {
538  fprintf(ioQQQ,"DEBUG TrimZone\t%li\t",nzone );
539  if( lgHi_Down )
540  {
541  fprintf(ioQQQ,"high dn %li to %li",
542  ion_hi_save ,
543  dense.IonHigh[nelem] );
544  }
545  if( lgHi_Up )
546  {
547  fprintf(ioQQQ,"high up %li to %li",
548  ion_hi_save ,
549  dense.IonHigh[nelem] );
550  }
551  if( lgLo_Up )
552  {
553  fprintf(ioQQQ,"low up %li to %li",
554  ion_lo_save ,
555  dense.IonLow[nelem] );
556  }
557  if( lgLo_Down )
558  {
559  fprintf(ioQQQ,"low dn %li to %li",
560  ion_lo_save ,
561  dense.IonLow[nelem] );
562  }
563  fprintf(ioQQQ,"\n" );
564  }
565  }
566  }
567 
568  /* option to print if any trimming occurred */
569  if( lgHi_Down || lgHi_Up || lgLo_Up || lgLo_Down )
570  {
571  conv.lgIonStageTrimed = true;
572  {
573  /* option to print out what has happened so far .... */
574  enum {DEBUG_LOC=false};
575  if( DEBUG_LOC && nelem==ipHELIUM )
576  {
577  fprintf(ioQQQ,"DEBUG ion_trim zone\t%.2f \t%li\t", fnzone, nelem);
578  if( lgHi_Down )
579  fprintf(ioQQQ,"\tHi_Down");
580  if( lgHi_Up )
581  fprintf(ioQQQ,"\tHi_Up");
582  if( lgLo_Up )
583  fprintf(ioQQQ,"\tLo_Up");
584  if( lgLo_Down )
585  fprintf(ioQQQ,"\tLo_Down");
586  fprintf(ioQQQ,"\n");
587  }
588  }
589  }
590 
591  /* asserts that that densities of ion stages that are now turned
592  * off are zero */
593  for( ion=0; ion<dense.IonLow[nelem]; ++ion )
594  {
595  ASSERT( dense.xIonDense[nelem][ion] == 0. );
596  }
597  for( ion=dense.IonHigh[nelem]+1; ion<nelem+1; ++ion )
598  {
599  ASSERT( dense.xIonDense[nelem][ion] == 0. );
600  }
601 
602  for( ion=dense.IonLow[nelem]; ion<=dense.IonHigh[nelem]; ++ion )
603  {
604  /* in case where ionization stage was changed the
605  * density is zero, we set it to SMALLFLOAT since a density
606  * of zero is not possible */
607  dense.xIonDense[nelem][ion] = MAX2(dense.xIonDense[nelem][ion], SMALLFLOAT);
608  }
609  return;
610 }
611 
612 // Try winnowing down ion_trim
614  /* nelem is on the C scale, 0 for H, 5 for C */
615  long int nelem,
616  long int oldIonRange[2]
617  )
618 {
619 
620  /* this will remember that higher stages trimed up or down */
621  bool lgHi_Down = false;
622  /* this will remember that lower stages trimmed up or own*/
623  bool lgLo_Up = false;
624  long int ion_lo_save = dense.IonLow[nelem],
625  ion_hi_save = dense.IonHigh[nelem];
626  long int ion;
627  realnum trimhi , trimlo, trimcharge;
628 
629  /* this is debugging code that turns on print after certain number of calls */
630  /*realnum xlimit = SMALLFLOAT *10.;*/
631  /*static int ncall=0;
632  if( nelem==5 )
633  ++ncall;*/
634 
635  DEBUG_ENTRY( "ion_trim2()" );
636 
637  /*confirm all ionization stages are within their range of validity */
638  ASSERT( nelem >= ipHYDROGEN && nelem < LIMELM );
639  ASSERT( dense.IonLow[nelem] >= 0 );
640  ASSERT( dense.IonHigh[nelem] <= nelem+1 );
641  /* IonHigh can be equal to IonLow */
642  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
643 
644  // Trimming limit on fraction of total charge in system
645  trimcharge = 1e-6;
646 
647  /* during search phase of mostly neutral matter the electron density
648  * can be vastly too large, and the ionization suppressed as a result.
649  * during search do not trim down or up as much */
650  if( conv.lgSearch )
651  {
652  trimhi = (realnum)ionbal.trimhi * 1e-4f;
653  trimlo = (realnum)ionbal.trimlo * 1e-4f;
654  }
655  else
656  {
657  trimhi = (realnum)ionbal.trimhi;
658  trimlo = (realnum)ionbal.trimlo;
659  }
660 
661  /* helium is special case since abundance is so high, and He+ CT with
662  * CO is the dominant CO destruction process in molecular regions */
663  if( nelem == ipHELIUM )
664  {
665  /* never want to trip up a lower stage of ionization */
666  trimlo = SMALLFLOAT;
667 
668  /* if He+ is highest stage of ionization, probably want to keep it
669  * since important for CO chemistry in molecular clouds */
670  if( dense.IonHigh[ipHELIUM] == 1 )
671  {
672  trimhi = MIN2( trimhi , 1e-20f );
673  }
674  else if( dense.IonHigh[ipHELIUM] == 2 )
675  {
676  if( conv.lgSearch )
677  {
678  /* during search phase we may be quite far from solution, in the
679  * case of a PDR sim the electron density may be vastly higher
680  * than it will be with stable solution. He++ can be very important
681  * for the chemistry and we want to consider it. Make the
682  * threshold for ignoring He++ much higher than normal to prevent
683  * a premature removal of the ion */
684  trimhi = MIN2( trimhi , 1e-17f );
685  }
686  else
687  {
688  /* similar smaller upper limit for ion*/
689  trimhi = MIN2( trimhi , 1e-12f );
690  }
691  }
692  }
693 
694  /* logic for PDRs, for elements included in chemistry, need stable solutions,
695  * keep 3 ion stages in most cases - added by NA to do HII/PDR sims */
696  if( !mole_global.lgNoMole )
697  {
698  trimlo = SMALLFLOAT;
699  if(dense.IonHigh[nelem] ==2)
700  {
701  trimhi = MIN2(trimhi, 1e-20f);
702  }
703  }
704 
705  /* raise or lower highest and lowest stages of ionization to
706  * consider only significant stages
707  * IonHigh[nelem] stage of ionization */
708 
709  /* sanity check */
710  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
711 
712  /* this checks on error condition where the gas is stupendously highly ionized,
713  * the low limit is already one less than high, and we need to raise the low
714  * limit further */
715  if( dense.IonHigh[nelem] > 1 &&
716  (dense.IonLow[nelem]==dense.IonHigh[nelem]-1) &&
717  (dense.xIonDense[nelem][dense.IonLow[nelem]] < dense.density_low_limit) )
718  {
719 # if 0
720  fprintf(ioQQQ,
721  "PROBLEM DISASTER the density of ion %li of element %s is too small to be computed on this cpu.\n",
722  dense.IonLow[nelem]+1,
723  elementnames.chElementName[nelem]);
724  fprintf(ioQQQ,
725  "Turn off the element with the command ELEMENT XXX OFF or do not consider "
726  "gas with low density, the current hydrogen density is %.2e cm-3.\n",
728  fprintf(ioQQQ,
729  "The outer radius of the cloud is %.2e cm - should a stopping "
730  "radius be set?\n",
731  radius.Radius );
732  fprintf(ioQQQ,
733  "The abort flag is being set - the calculation is stopping.\n");
734 # endif
735  //lgAbort = true;
737  return;
738  }
739 
740  // Some hysteresis in trimming criteria should help prevent oscillations
741  double threshextend = 1.0, threshkeep = 0.125;
742  double itrim = ( dense.IonHigh[nelem] > oldIonRange[1] ) ? threshextend : threshkeep;
743  /* trim down high stages that have too small an abundance */
744  if ( ionbal.lgTrimhiOn &&
745  dense.IonHigh[nelem] > dense.IonLow[nelem] )
746  {
747  while (
748  ( ( dense.xIonDense[nelem][dense.IonHigh[nelem]]/dense.gas_phase[nelem] <= trimhi*itrim &&
749  dense.xIonDense[nelem][dense.IonHigh[nelem]] <= trimcharge*dense.EdenTrue*itrim ) ||
750  dense.xIonDense[nelem][dense.IonHigh[nelem]] <= dense.density_low_limit*itrim )
751  )
752  {
753  if ( dense.IonHigh[nelem] <= dense.IonLow[nelem] + 2 )
754  break;
755  itrim = threshkeep;
756  dense.xIonDense[nelem][dense.IonHigh[nelem]-1] +=
757  dense.xIonDense[nelem][dense.IonHigh[nelem]];
758  dense.xIonDense[nelem][dense.IonHigh[nelem]] = 0.;
759 
761  {
762  dynamics.Source[nelem][dense.IonHigh[nelem]-1] +=
763  dynamics.Source[nelem][dense.IonHigh[nelem]];
764  dynamics.Source[nelem][dense.IonHigh[nelem]] = 0.;
765  }
766 
767  thermal.setHeating(nelem,dense.IonHigh[nelem]-1,0.);
768  long int ipISO = nelem - dense.IonHigh[nelem];
769  if ( ipISO>=0 && ipISO<NISO )
770  {
771  for( long level = 0; level < iso_sp[ipISO][nelem].numLevels_max; level++ )
772  iso_sp[ipISO][nelem].st[level].Pop() = 0.;
773  }
774  --dense.IonHigh[nelem];
775  }
776  lgHi_Down = dense.IonHigh[nelem] < oldIonRange[1];
777  }
778 
779  /* sanity check */
780  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
781 
782  itrim = ( dense.IonLow[nelem] < oldIonRange[0] ) ? threshextend : threshkeep ;
783  /* raise lowest stage of ionization, but only if we are near illuminated face of cloud*/
784  if ( ionbal.lgTrimloOn )
785  {
786  while( (dense.xIonDense[nelem][dense.IonLow[nelem]]/dense.gas_phase[nelem] <= (realnum)trimlo*itrim) &&
787  (dense.IonLow[nelem] < (dense.IonHigh[nelem] - 2) ) )
788  {
789  itrim = threshkeep;
790  /* raise lowest level of ionization */
791  dense.xIonDense[nelem][dense.IonLow[nelem]+1] +=
792  dense.xIonDense[nelem][dense.IonLow[nelem]];
793  dense.xIonDense[nelem][dense.IonLow[nelem]] = 0.;
794 
796  {
797  dynamics.Source[nelem][dense.IonLow[nelem]+1] +=
798  dynamics.Source[nelem][dense.IonLow[nelem]];
799  dynamics.Source[nelem][dense.IonLow[nelem]] = 0.;
800  }
801 
802  /* no minus one in below since this is low bound, already bounds at atom */
803  thermal.setHeating(nelem,dense.IonLow[nelem],0.);
804  long int ipISO = nelem - dense.IonLow[nelem];
805  if ( ipISO>=0 && ipISO<NISO )
806  {
807  for( long level = 0; level < iso_sp[ipISO][nelem].numLevels_max; level++ )
808  iso_sp[ipISO][nelem].st[level].Pop() = 0.;
809  }
810  ++dense.IonLow[nelem];
811  }
812  lgLo_Up = dense.IonLow[nelem] > oldIonRange[0];
813  }
814 
815  /* sanity check */
816  ASSERT( dense.IonLow[nelem] <= dense.IonHigh[nelem] );
817 
818  /* these are standard bounds checks that appear throughout this routine
819  * dense.xIonDense[IonLow] is first one with positive abundances
820  *
821  * in case where lower ionization stage was just lowered the abundance
822  * was set to dense.density_low_limit so test must be < dense.density_low_limit */
823  ASSERT( dense.IonLow[nelem] <= 1 ||
824  dense.xIonDense[nelem][dense.IonLow[nelem]-1] == 0. );
825 
826  {
827  /* option to print out what has happened so far .... */
828  enum {DEBUG_LOC=false};
829  if( DEBUG_LOC && nelem == ipHELIUM )
830  {
831  if( lgHi_Down ||lgLo_Up )
832  {
833  fprintf(ioQQQ,"DEBUG TrimZone\t%li\t",nzone );
834  if( lgHi_Down )
835  {
836  fprintf(ioQQQ,"high dn %li to %li",
837  ion_hi_save ,
838  dense.IonHigh[nelem] );
839  }
840  if( lgLo_Up )
841  {
842  fprintf(ioQQQ,"low up %li to %li",
843  ion_lo_save ,
844  dense.IonLow[nelem] );
845  }
846  fprintf(ioQQQ,"\n" );
847  }
848  }
849  }
850 
851  /* option to print if any trimming occurred */
852  if( lgHi_Down || lgLo_Up )
853  {
854  conv.lgIonStageTrimed = true;
855  {
856  /* option to print out what has happened so far .... */
857  enum {DEBUG_LOC=false};
858  if( DEBUG_LOC && nelem==ipHELIUM )
859  {
860  fprintf(ioQQQ,"DEBUG ion_trim zone\t%.2f \t%li\t", fnzone, nelem);
861  if( lgHi_Down )
862  fprintf(ioQQQ,"\tHi_Down");
863  if( lgLo_Up )
864  fprintf(ioQQQ,"\tLo_Up");
865  fprintf(ioQQQ,"\n");
866  }
867  }
868  }
869 
870  /* asserts that that densities of ion stages that are now turned
871  * off are zero */
872  for( ion=0; ion<dense.IonLow[nelem]; ++ion )
873  {
874  ASSERT( dense.xIonDense[nelem][ion] == 0. );
875  }
876  for( ion=dense.IonHigh[nelem]+1; ion<nelem+1; ++ion )
877  {
878  ASSERT( dense.xIonDense[nelem][ion] == 0. );
879  }
880 
881  for( ion=dense.IonLow[nelem]; ion<=dense.IonHigh[nelem]; ++ion )
882  {
883  /* in case where ionization stage was changed the
884  * density is zero, we set it to SMALLFLOAT since a density
885  * of zero is not possible */
886  dense.xIonDense[nelem][ion] = MAX2(dense.xIonDense[nelem][ion], SMALLFLOAT);
887  }
888  return;
889 }
890 
891 void ion_widen(long nelem)
892 {
893  DEBUG_ENTRY( "ion_widen()" );
894  if ( dense.lgSetIoniz[nelem] ||
895  ( dense.IonLow[nelem] == 0 && dense.IonHigh[nelem] == nelem+1 ) )
896  {
897  return;
898  }
899  double abund = 0.0;
900  for (long ion=dense.IonLow[nelem]; ion<=dense.IonHigh[nelem]; ++ion)
901  {
902  abund += dense.xIonDense[nelem][ion];
903  }
904  double abundnew = abund;
905  if (dense.IonLow[nelem] > 0 )
906  {
907  --dense.IonLow[nelem];
908  dense.xIonDense[nelem][dense.IonLow[nelem]] = (dense.density_low_limit*10.);
909  abundnew += (dense.density_low_limit*10.);
910  long ipISO = nelem - dense.IonLow[nelem];
911  if (ipISO >= 0 && ipISO < NISO)
912  iso_sp[ipISO][nelem].st[0].Pop() = dense.xIonDense[nelem][dense.IonLow[nelem]];
913  }
914  if ( dense.IonHigh[nelem] < nelem+1)
915  {
916  ++dense.IonHigh[nelem];
917  dense.xIonDense[nelem][dense.IonHigh[nelem]] = (dense.density_low_limit*10.);
918  abundnew += (dense.density_low_limit*10.);
919  long ipISO = nelem - dense.IonHigh[nelem];
920  if (ipISO >= 0 && ipISO < NISO)
921  iso_sp[ipISO][nelem].st[0].Pop() = dense.xIonDense[nelem][dense.IonHigh[nelem]];
922  }
923  double frac = abund/abundnew;
924  for (long ion=dense.IonLow[nelem];ion<=dense.IonHigh[nelem];++ion)
925  {
926  dense.xIonDense[nelem][ion] *= frac;
927  }
928 }
929 
930 #ifdef NDEBUG
931 void ion_trim_validate(long, bool)
932 {
933  (void)0;
934 }
935 #else
936 void ion_trim_validate(long nelem, bool lgIonizTrimCalled)
937 {
938  DEBUG_ENTRY( "ion_trim_validate()" );
939  for( long ion=0; ion<dense.IonLow[nelem]; ++ion )
940  {
941  ASSERT( dense.xIonDense[nelem][ion] == 0. );
942  }
943  /*if( nelem==5 ) fprintf(ioQQQ,"carbbb\t%li\n", dense.IonHigh[nelem]);*/
944  for( long ion=dense.IonLow[nelem]; ion<=dense.IonHigh[nelem]; ++ion )
945  {
946  /* >>chng 02 feb 06, had been > o., chng to > SMALLFLOAT to
947  * trip over VERY small floats that failed on alphas, but not 386
948  *
949  * in case where lower ionization stage was just lowered or
950  * trimmed down the abundance
951  * was set to SMALLFLOAT so test must be < SMALLFLOAT */
952  /* >>chng 02 feb 19, add check for search phase. During this search
953  * models with extreme ionization (all neutral or all ionized) can
954  * have extreme but non-zero abundances far from the ionization peak for
955  * element with lots of electrons. These will go away once the model
956  * becomes stable */
957  /* >>chng 03 dec 01, add check on whether ion trim was called
958  * conserve.in threw assert when iontrim not called and abund grew small */
959  ASSERT( conv.lgSearch || !lgIonizTrimCalled ||
960  /* this can happen if all C is in the form of CO --
961  generalize test to being that at least one ion must survive */
962  dense.IonLow[nelem] == dense.IonHigh[nelem] ||
963  dense.xIonDense[nelem][ion] >= SMALLFLOAT ||
964  dense.xIonDense[nelem][ion]/dense.gas_phase[nelem] >= SMALLFLOAT );
965  }
966  for( long ion=dense.IonHigh[nelem]+1; ion<=nelem+1; ++ion )
967  {
968  ASSERT( ion >= 0 );
969  ASSERT( dense.xIonDense[nelem][ion] == 0. );
970  }
971 }
972 #endif
void ion_trim(long int nelem)
Definition: ion_trim.cpp:177
t_mole_global mole_global
Definition: mole.cpp:7
double Radius
Definition: radius.h:31
bool lgTrimhiOn
Definition: ionbal.h:90
t_thermal thermal
Definition: thermal.cpp:6
qList st
Definition: iso.h:482
t_struc struc
Definition: struc.cpp:6
t_Heavy Heavy
Definition: heavy.cpp:5
const realnum SMALLFLOAT
Definition: cpu.h:246
realnum SetIoniz[LIMELM][LIMELM+1]
Definition: dense.h:172
const int NISO
Definition: cddefines.h:311
void ion_widen(long nelem)
Definition: ion_trim.cpp:891
long int IonHigh[LIMELM+1]
Definition: dense.h:130
void ion_trim_untrim(long nelem)
Definition: ion_trim.cpp:20
bool lgAdvection
Definition: dynamics.h:66
bool lgTimeDependentStatic
Definition: dynamics.h:102
void ion_trim_invalidate(long nelem)
Definition: ion_trim.cpp:26
t_conv conv
Definition: conv.cpp:5
void mole_ion_trim(void)
Definition: ion_trim.cpp:131
t_phycon phycon
Definition: phycon.cpp:6
double trimhi
Definition: ionbal.h:83
void ion_trim_init()
Definition: ion_trim.cpp:35
bool lgSetIoniz[LIMELM]
Definition: dense.h:167
FILE * ioQQQ
Definition: cddefines.cpp:7
long int nzone
Definition: cddefines.cpp:14
ChemNuclideList nuclide_list
t_dynamics dynamics
Definition: dynamics.cpp:42
#define MIN2(a, b)
Definition: cddefines.h:803
double anu(size_t i) const
Definition: mesh.h:120
t_dense dense
Definition: global.cpp:15
t_elementnames elementnames
Definition: elementnames.cpp:5
t_iso_sp iso_sp[NISO][LIMELM]
Definition: iso.cpp:11
bool lgIonStageTrimed
Definition: conv.h:182
STATIC void ion_trim_from_set(long nelem)
Definition: ion_trim.cpp:104
double xIonDense[LIMELM][LIMELM+1]
Definition: dense.h:135
bool lgSearch
Definition: conv.h:168
t_ionbal ionbal
Definition: ionbal.cpp:8
t_abund abund
Definition: abund.cpp:5
long int IonLow[LIMELM+1]
Definition: dense.h:129
#define STATIC
Definition: cddefines.h:118
double EdenTrue
Definition: dense.h:232
t_rfield rfield
Definition: rfield.cpp:9
float realnum
Definition: cddefines.h:124
realnum uh
Definition: rfield.h:349
long max(int a, long b)
Definition: cddefines.h:817
t_radius radius
Definition: radius.cpp:5
long int nTotalIoniz
Definition: conv.h:159
bool lgElmtOn[LIMELM]
Definition: dense.h:160
void setHeating(long nelem, long ion, double heating)
Definition: thermal.h:190
realnum gas_phase[LIMELM]
Definition: dense.h:76
void ion_trim_small(long nelem, double abund_total)
Definition: ion_trim.cpp:115
#define ASSERT(exp)
Definition: cddefines.h:613
const int LIMELM
Definition: cddefines.h:308
double Valence_IP_Ryd[LIMELM][LIMELM]
Definition: heavy.h:24
#define DEBUG_ENTRY(funcname)
Definition: cddefines.h:723
const int ipHELIUM
Definition: cddefines.h:350
double te_ryd
Definition: phycon.h:27
realnum ** gas_phase
Definition: struc.h:75
#define MAX2(a, b)
Definition: cddefines.h:824
int fprintf(const Output &stream, const char *format,...)
Definition: service.cpp:1121
double ** Source
Definition: dynamics.h:80
sys_float SDIV(sys_float x)
Definition: cddefines.h:1002
double trimlo
Definition: ionbal.h:83
long int numLevels_max
Definition: iso.h:524
char chElementName[LIMELM][CHARS_ELEMENT_NAME]
Definition: elementnames.h:17
bool lgTrimloOn
Definition: ionbal.h:93
double fnzone
Definition: cddefines.cpp:15
double frac(double d)
bool lgNoMole
Definition: mole.h:325
void ion_trim_validate(long nelem, bool lgIonizTrimCalled)
Definition: ion_trim.cpp:936
const int ipHYDROGEN
Definition: cddefines.h:349
long int nflux
Definition: rfield.h:46
realnum *** xIonDense
Definition: struc.h:64
void ion_trim2(long int nelem, long int oldIonRange[2])
Definition: ion_trim.cpp:613
long int ipHeavy[LIMELM][LIMELM]
Definition: heavy.h:11
double density_low_limit
Definition: dense.h:208