bug in iso_level.cpp?

29 views
Skip to first unread message

Marios Chatzikos

unread,
Feb 17, 2021, 6:15:08 PM2/17/21
to cloud...@googlegroups.com
Hello,

Going over the matrix system in iso_level.cpp in the context of level
populations in cooling sims, I think I have spotted a conceptual error.

The code snippet in question is the following:

374                 /* ionization from/recombination from lower
ionization stages */
375                 for(long ion_from=dense.IonLow[nelem]; ion_from <
MIN2( dense.IonHigh[nelem], nelem-ipISO ) ; ion_from++ )
376                 {
377                         if(
ionbal.RateIoniz[nelem][ion_from][nelem-ipISO] >= 0. )
378                         {
379                                 /* ionization from lower ionization
stages, cm-3 s-1 */
380                                 source +=
ionbal.RateIoniz[nelem][ion_from][nelem-ipISO] *
dense.xIonDense[nelem][ion_from];
381                         }
382                         /* recombination to next lower ionization
stage, s-1 */
383                         if( ion_from == nelem-1-ipISO )
384                                 sink +=
ionbal.RateRecomTot[nelem][ion_from];
385                 }
386
387                 ASSERT( source >= 0.f );
388                 if (0)
389                 {
390                         /*
391                          * Collisional ionization and
photoionization can only be to the ground state in H iso-sequences
392                          * to conserve energy.
393                          */
394                         creation[0] += source;
395                         /*for( level=0; level < numlevels_local;
level++ )
396                         {
397                                 z[level][level] += sink;
398                         }*/
399                         /*recombination is only done from the ground
state */
400                         z[0][0] += sink;
401                 }
402                 else
403                 {
404                         // Try Boltzmann weighting to capture LTE
limit correctly
405                         t_iso_sp* sp = &iso_sp[ipISO][nelem];
406                         double partfun=0.0;
407                         for ( level = 0; level < numlevels_local;
level++ )
408                         {
409                                 partfun +=
sp->st[level].Boltzmann()*sp->st[level].g();
410                         }
411                         source /= partfun;
412                         for( level=0; level < numlevels_local; level++ )
413                         {
414                                 creation[level] += source*
415 sp->st[level].Boltzmann()*sp->st[level].g();
416                                 z[level][level] += sink;
417                         }
418                 }
419                 creation[0] += ctsource;
420                 z[0][0] += ctsink;

Lines 404-417 carry out the partition of the source terms among the
various levels.  The sink term, however, in line 416 is used for all
levels without partitioning.  As shown in lines 375-385, it contains the
total recombination rate into the ion.

This is what seems like a conceptual error to me.  The total
recombination should either be partitioned among the levels (although
this is probably inaccurate), or added to the ground state as in line
400 (although this is certainly inaccurate). Level-resolved
recombination ought to be the answer here, but that being years away, I
think partitioning the total recombination rate should be favored.

Yet, this is probably not an oversight, so if you cared to share your
thoughts, that'd be great.

Thanks,

Marios

Gary J. Ferland

unread,
Feb 18, 2021, 8:54:37 AM2/18/21
to cloud...@googlegroups.com
have you looked at the svn blame to see where&when this code came onto the mainline?  wonder if the commit message is helpful?

--
--
http://groups.google.com/group/cloudy-dev
---
You received this message because you are subscribed to the Google Groups "cloudy-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cloudy-dev+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cloudy-dev/43f83ab6-1bb4-6fbc-aeb3-0cc7dec898c9%40gmail.com.


--
Gary J. Ferland
Physics, Univ of Kentucky
Lexington KY 40506 USA
Tel: 859 257-8795
https://pa.as.uky.edu/users/gary

Marios Chatzikos

unread,
Feb 18, 2021, 9:04:02 AM2/18/21
to cloud...@googlegroups.com

$ svn log -r 6023
------------------------------------------------------------------------
r6023 | rporter | 2012-03-17 13:57:04 -0400 (Sat, 17 Mar 2012) | 1 line

trunk - merge newmole branch onto trunk
------------------------------------------------------------------------

Gary J. Ferland

unread,
Feb 18, 2021, 9:19:10 AM2/18/21
to cloud...@googlegroups.com
the newmole branch was a major effort.  maybe talk about this after the meeting with Priyanka?

Marios Chatzikos

unread,
Feb 18, 2021, 9:19:42 AM2/18/21
to cloud...@googlegroups.com

Francisco Guzman Fulgencio

unread,
Feb 18, 2021, 9:26:23 AM2/18/21
to cloud...@googlegroups.com
Hi,

This is an old issue. There is a thread about this on cloudy_dev:

Sent: Monday, November 7, 2016 6:04:52 PM
To: cloudy-dev
Subject: Re: [cloudy-dev] RydDep botches

We met to discuss the status of RydDep - the issue of the partition function is serious but not something that can be fixed easily.  Currently we put all source / sink terms into iso_levels with weighting by the partition function.  That is not correct since each atomic process deposites the product in a particular state.  For instance, our valence photoionization cross sections populate the ground state of the parent ion.  Some charge exchange reactions populate particular excited states.  Most chemical / CX reactions don't have the state specified, although it probably could be figured out.  Not a job for this release.

So our idea is to leave iso_level as it is now, and has been for quite some time, and mark it as a priority for future development.  Leaving the partition function the old way, RydDep gets the following serious botches in auto.   The biggest change was a bugfix - the iso sequences had used Vriem & Smeets for all ions.  Their theory was only for neutrals.  We now use Percival & Richards for ions.  That changes strong lines in the dense BLR sims because, probably, the UV / XUV lines of He II change - we can see that where they are directly monitored, and they produce ionizing radiation which affects the thermal balance.  We have seen changes like this in the BLR sims before.

So this seems fine to me.  Thoughts?
thanks,
Gary



==========================
Francisco Guzmán
Assistant Professor of Physics
Rogers Hall, 106B,
Dahlonega Campus
University of North Georgia

From: cloud...@googlegroups.com <cloud...@googlegroups.com> on behalf of Marios Chatzikos <mchat...@gmail.com>
Sent: Thursday, February 18, 2021 9:19 AM
To: cloud...@googlegroups.com <cloud...@googlegroups.com>
Subject: Re: [cloudy-dev] bug in iso_level.cpp?
 

Francisco Guzman Fulgencio

unread,
Feb 18, 2021, 9:32:16 AM2/18/21
to cloud...@googlegroups.com
In summary, is not correct but it is the best that we can do until somebody fixes it. That will need state resolved ionization-recombination. If you remove the partition (use the first "if" ) the matrix inversion will give negative populations.

On a side note: I think this might be behind condensation not working. One of the things I wanted to do is to remove all other sources of ionization that don't go to the ground level and try again.



==========================
Francisco Guzmán
Assistant Professor of Physics
Rogers Hall, 106B,
Dahlonega Campus
University of North Georgia

From: cloud...@googlegroups.com <cloud...@googlegroups.com> on behalf of Francisco Guzman Fulgencio <Francisco.Gu...@ung.edu>
Sent: Thursday, February 18, 2021 9:26 AM

Marios Chatzikos

unread,
Feb 18, 2021, 9:39:39 AM2/18/21
to cloud...@googlegroups.com

Hi Fran,


I should clarify that I am not taking issue with the partition function per se.  I recognize that this would be a major development effort.


My point is that the total recombination rate should not be added to each and every level.  Either partition it among the levels, or add it to ground only.


Thanks,

Marios

Robin Williams

unread,
Feb 18, 2021, 9:56:09 AM2/18/21
to cloud...@googlegroups.com
As everywhere, rates need to be in detailed balance to get thermodynamics right.  So if you focus on the sinks, the sources should take care of themselves.

For the sinks, you'd expect the rate to be the product of phase space factors, Boltzmann suppression max[1,exp(-Delta E/kT)] (like Metropolis-Hastings MCMC, NB interactions with partners with excitations may be exothermic), and possibly quantum forbidden rules, averaged over every process that causes coupling.  That's a lot of detail to cover.  Spreading the loss across all levels is an "uninformative prior," which might even be a decent guess at least when kT >> Delta E.

IIRC, the earliest implementation took everything out of the base state, and this didn't work very well -- it was liable to try pulling more population out of the level than was actually present in some circumstances and failed STE.  If the population is essentially all in ground, then equal loss rate in the matrix means the loss all comes from ground when multiplied by the solution vector.

  Robin 

Marios Chatzikos

unread,
Feb 18, 2021, 4:52:07 PM2/18/21
to cloud...@googlegroups.com

I am not arguing that the recombination should come out of the ground state.  Your experience clearly shows otherwise.  But the 'uninformative prior' might be preferable for conservation.

The following is from iso_level.cpp on branch newmole at r2500:

372                 /* derive source/sink terms for ion/rec terms coupling resolved atoms
373                  * to lower ionization stages */
374                 if( nelem-ipISO >= 1 && ionbal.RateIonizTot[nelem][nelem-ipISO-1] > 0.)
375                 {
376                         double sum_recom =0. , sum_ioniz = 0.;
377                         sum_popn_ov_ion = 0.;
378                         /* if lower stage of ionization is itself resolved then we must
379                          * sum over rates between levels and its continuum, the ion we
380                          * are now solving */
381                         if( ipISO+1<NISO )
382                         {
383                                 for( level=0; level < iso.numLevels_local[ipISO+1][nelem]; level++ )
384                                 {
385                                         sum_popn_ov_ion += StatesElem[ipISO+1][nelem][level].Pop;
386
387                                         /* sum of all ionization processes from levels to ion we
388                                          * now consider - use difference in ionization / recombination
389                                          * since at high densities each of these terms are very
390                                          * large but nearly cancel */
391                                         double one = iso.RateCont2Level[ipISO+1][nelem][level] -
392                                                 StatesElem[ipISO+1][nelem][level].Pop *
393                                                 iso.RateLevel2Cont[ipISO+1][nelem][level];
394
395                                         /* sign determines whether this is net ionization or
396                                          * recombination process.  Ground & metastable states have
397                                          * large departure coefficients so are net ionization,
398                                          * excited states have departure coefficients that
399                                          * asymptotically approach unity from below, so are net
400                                          * recombination */
401                                         if( one > 0 )
402                                                 sum_recom += one;
403                                         else
404                                                 sum_ioniz -= one;
405                                 }
406                                 ASSERT( sum_recom>=0. && sum_ioniz>=0. );
407                                 sum_ioniz  /= MAX2(SMALLDOUBLE , sum_popn_ov_ion);
408                         }
409                         else
410                         {
411                                 /* lower stage is not resolved */
412                                 sum_ioniz = ionbal.RateIonizTot[nelem][nelem-ipISO-1];
413                                 sum_recom = ionbal.RateRecomTot[nelem][nelem-ipISO-1];
414                         }
415                         /* now add ionization from next lower stage */
416                         source += sum_ioniz*
417                                 dense.xIonDense[nelem][nelem-ipISO-1];
418
419                         /* now add recombination to next lower stage, units are s-1 */
420                         sink += sum_recom;
421                 }
422
423                 creation[0] += source/SDIV(dense.xIonDense[nelem][nelem+1-ipISO]);
424                 for( level=0; level < numlevels_local; level++ )
425                 {
426                         z[level][level] += sink;
427                 }

It looks like that the total recombination has always been used as a sink for every single level, and I simply don't see the reason for that.

Line 413 above is all that survived to the current version of the file (line 384).  It was used for unresolved species, but even for the resolved ones the total recombination was just lumped together.

Marios

Robin Williams

unread,
Feb 18, 2021, 5:36:58 PM2/18/21
to cloud...@googlegroups.com
> It looks like that the total recombination has always been used as a sink for every single level, and I simply don't see the reason for that.

So if you have an ion in an excited state A^{n+}*, what stops it from recombining?  It seems like an incoming electron might not care much about the excitation.  Indeed, for an ion which had a full shell in its ground state, the increased polarizability due to an excitation might actually increase the recombination rate.

Marios Chatzikos

unread,
Feb 18, 2021, 5:45:09 PM2/18/21
to cloud...@googlegroups.com

No doubt, but the sum over all levels should be equal to the total recombination rate.  At the moment we are effectively inflating the recombination rate by a factor equal to the number of levels.

Robin Williams

unread,
Feb 18, 2021, 5:48:53 PM2/18/21
to cloud...@googlegroups.com
I think you're muddling the total rate cm^-3 s^-1 with the rate per atom/level s^-1.  The source is the former, it's added to the rhs vector, the sink is the latter, it's added to the diagonal of the matrix.

Marios Chatzikos

unread,
Feb 18, 2021, 8:36:08 PM2/18/21
to 'Robin Williams' via cloudy-dev
Hi Robin,

No, I'm not confusing the two rates.  RateRecomTot is the sum over all recombination processes (RR, DR, etc) times the electron density.  The RR data, say, come from Badnell 2006:


which are summed over final states (see end of section 2.2 in the second reference).  That is, the rates in RateRecomTot refer to the entire ion.  It seems odd to me that we use the total rate for each level separately.

You, Ryan, and Gary (I think) were the main authors of that bit of physics in Cloudy.  I encountered this inconsistency while working on the dynamics.  Improving the iso-sequence solver is not on my to-do list.  I leave it up to you to decide if this requires a fix or not.

Thanks,
Marios

Francisco Guzman Fulgencio

unread,
Feb 18, 2021, 8:51:40 PM2/18/21
to 'Robin Williams' via cloudy-dev

RateRecomTot is the sum over all recombination processes (RR, DR, etc) times the electron density. The RR data, say, come from Badnell 2006:

https://ui.adsabs.harvard.edu/abs/2006ApJ...651L..73B
https://ui.adsabs.harvard.edu/abs/2006ApJS..167..334B

which are summed over final states (see end of section 2.2 in the second reference).

The final states are in the recombined ion, not in the recombining ion.
That is, the rates in RateRecomTot refer to the entire ion. It seems odd to me that we use the total rate for each level separately.
As Robin said, each level has the same probability of recombining.



==========================
Francisco Guzmán
Assistant Professor of Physics
Rogers Hall, 106B,
Dahlonega Campus
University of North Georgia
Sent: Thursday, February 18, 2021 8:35 PM
To: 'Robin Williams' via cloudy-dev <cloud...@googlegroups.com>

Robin Williams

unread,
Feb 19, 2021, 3:00:36 AM2/19/21
to cloud...@googlegroups.com
It's like if 0.1% of the population get Covid each day.  The default assumption if you stratify the population in any way is that the sub-groups all get Covid at 0.1% each day -- not just one specific group -- although obviously an improved understanding may vary the risk rate between different demographics.

This is certainly tricky to understand and easy to goof up -- but it's not wrong in the way you think it is.

  Robin

Robin Williams

unread,
Feb 19, 2021, 4:09:57 AM2/19/21
to cloud...@googlegroups.com
> RateRecomTot is the sum over all recombination processes (RR, DR, etc) times the electron density.

The total cm^{-3} s^{-1} recombination rate is beta * n_e * n_i -- NB the factor n_i, which is what is split up in the iso-level coding.

If you have two equal sub-populations n_1 + n_2 = N, then the total infection rate is 0.1*n_1 + 0.1*n_2 = 0.1*N, not 0.2*N.

I think I remember reading a piece once (by Groucho Marx?) which used something like the second form of logic, adding up hours sleeping, public holidays, weekends, ball games, etc., etc. to establish that the author was never working.

  Robin
Reply all
Reply to author
Forward
0 new messages