/* This file is part of Cloudy and is copyright (C) 1978-2003 by Gary J. Ferland.
 * For conditions of distribution and use, see copyright notice in license.txt */
/*HeCollid evaluate collisional rates */
/*HeCSInterp interpolate on He1 collision strengths */
/*AtomCSInterp do the atom	*/
/*IonCSInterp do the ions	*/
/*CS_l_mixing - find rate for l-mixing collisions by protons, for neutrals */
#include "cddefines.h" 
#include "iso.h"
#include "dense.h"
#include "helike.h"
#include "helike_cs.h"
#include "phycon.h"
#include "physconst.h"
#include "taulines.h"
#include "hydro_bauman.h"
#include "hydro_vs_rates.h"
#include "trace.h"
#include "ionbal.h"
#include "opacity.h"
#include "heavy.h"
#include "rfield.h"
#include "atmdat.h"

static double Therm_ave_coll_str_int( double EProjectileRyd);
static double collision_strength( double velOrEner, long paramIsRedVel );
static double L_mix_integrand( double alpha );
static double StarkCollTransProb( long n, long l, long lp, double alpha, double deltaPhi);
static double ellip_int_K( double k );

static long	global_n, global_l, global_l_prime, global_z;
static double global_bmax, global_red_vel, global_an, global_collider_charge;
static double kTRyd, global_temp;

/*===================================================================================*/
/* evaluate collisional rates */
void HeCollid( long nelem)
{

	double factor , ConvLTEPOP, *ColIonizPerN, DimaRate, crate;
	long int ipLo , ipHi, n;
	/*long int ipFirstCollapsed;*/
	
	static float TeUsed[LIMELM]={0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,
		0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0};

#	ifdef DEBUG_FUN
	fputs( "<+>HeCollid()\n", debug_fp );
#	endif
	
	if( (ColIonizPerN = (double*)MALLOC(sizeof(double)*
		(unsigned)(iso.n_HighestResolved[ipHE_LIKE][nelem] + iso.nCollapsed[ipHE_LIKE][nelem] + 1) ) )==NULL )
		BadMalloc();

	for( n=0; n <= ( iso.n_HighestResolved[ipHE_LIKE][nelem] + iso.nCollapsed[ipHE_LIKE][nelem] ); ++n)
	{
		ColIonizPerN[n] = 0.;
	}

	/*ipFirstCollapsed = iso.numLevels[ipHE_LIKE][nelem] - iso.nCollapsed[ipHE_LIKE][nelem];*/

	/*lint -e777 */
	if( TeUsed[nelem] != phycon.te )
	{
	/*lint +e777 */
		TeUsed[nelem] = phycon.te;

		if( trace.lgTrace )
		{
			fprintf( ioQQQ, 
				"       HeCollid called nelem %li - will reeval Boltz fac, LTE dens\n",
				nelem );
		}

		/* following factor is actually 4.1412957e-16 (old=4.14158E-16), 
		 * but e- stat weight is included */
		/* ConvLTEPOP = 2.0708e-16/phycon.te32;*/
		/* >>chng 99 jun 02, use codata and infinite mass nuc 
		 * actually correct reduced mass appears below */
		/* HION_LTE_POP	is planck^2 / (2 pi m_e k ), must raise this to 3/2 when used */
		factor = HION_LTE_POP*dense.AtomicWeight[nelem]/
			(dense.AtomicWeight[nelem]+ELECTRON_MASS/ATOMIC_MASS_UNIT) ;

		/* term in () is stat weight of electron * ion */
		ConvLTEPOP = pow(factor,1.5)/(2.*iso.stat_ion[ipHE_LIKE])/phycon.te32;

		iso.lgPopLTE_OK[ipHE_LIKE][nelem] = TRUE;
		/* fully define Boltzmann factors to continuum for model levels */
		for( ipLo=ipHe1s1S; ipLo < (iso.numLevels[ipHE_LIKE][nelem]); ipLo++ )
		{
			/* this boltzmann factor is exp( +ioniz energy / Te ) */
			iso.ConBoltz[ipHE_LIKE][nelem][ipLo] = 
				dsexp(iso.xIsoLevNIonRyd[ipHE_LIKE][nelem][ipLo]/phycon.te_ryd);

			/***********************************************************************
			 *                                                                     *
			 * LTE abundances for all levels, ionization and                       *
			 * exciation between levels                                            *
			 *                                                                     *
			 ***********************************************************************/

			if( iso.ConBoltz[ipHE_LIKE][nelem][ipLo] >= SMALLDOUBLE )
			{
				iso.PopLTE[ipHE_LIKE][nelem][ipLo] = 
					iso.stat[ipHE_LIKE][nelem][ipLo] / iso.ConBoltz[ipHE_LIKE][nelem][ipLo]* ConvLTEPOP;
				ASSERT( iso.PopLTE[ipHE_LIKE][nelem][ipLo] < BIGDOUBLE );
			}
			else
			{
				iso.PopLTE[ipHE_LIKE][nelem][ipLo] = 0.;
			}

			/* now check for any zeros - possible above underflowed to zero */
			if( iso.PopLTE[ipHE_LIKE][nelem][ipLo] <= 0. )
			{
				iso.lgPopLTE_OK[ipHE_LIKE][nelem] = FALSE;
			}
		}

		for( ipHi=ipHe2s3S; ipHi <iso.numLevels[ipHE_LIKE][nelem]; ipHi++ )
		{
			for( ipLo=ipHe1s1S; ipLo < ipHi; ipLo++ )
			{
				/* get collision strength */
  				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].cs = HeCSInterp( nelem , ipHi , ipLo );

				{
					/* option to print particulars of some line when called
					 * a prettier print statement is near where chSpin is defined below*/
					/*@-redef@*/
					enum {DEBUG_LOC=FALSE};
					/*@+redef@*/
					if( DEBUG_LOC && (nelem==ipHELIUM) /*&& (N_(ipHi)==15) && (N_(ipLo)>=14)*/ )
					{
						fprintf(ioQQQ,"Lo %li n %li s %li l %li \t ", 
							ipLo, N_(ipLo), S_(ipLo), L_(ipLo) );
						fprintf(ioQQQ," Hi %li n %li s %li l %li \t", 
							ipHi, N_(ipHi), S_(ipHi), L_(ipHi) );
						fprintf(ioQQQ,"%.4e\t%.0f\t%.0f\t%.4e\t%.4e\t%.4e\n",
						EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyWN,
							iso.stat[ipHE_LIKE][nelem][ipHi],
							iso.stat[ipHE_LIKE][nelem][ipLo],
						EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].Aul,
						EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].gf ,
						EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].cs );
					}
				}

				/* collisional deexcitation rate, can be turned off with 
				 * atom he-like collisions off command */
				/*This is downward collision rate */
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].ColUL = 
					(float)(EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].cs/
					phycon.sqrte*COLL_CONST/(double)iso.stat[ipHE_LIKE][nelem][ipHi] );

				if( N_(ipHi) > iso.n_HighestResolved[ipHE_LIKE][nelem] &&
					N_(ipLo) <= iso.n_HighestResolved[ipHE_LIKE][nelem] )
				{
					EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].ColUL *=
						(float)( (2./3.)*(log(N_(ipHi))+2) );
				}

				/* define line Boltzmann factors 
				 * some energy-degenerate collisions have very small negative energy,
				 * when different l, same n, levels flip over */
				if( EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyK > 0. )
				{
					iso.Boltzmann[ipHE_LIKE][nelem][ipHi][ipLo] = 
						sexp( EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyK / phycon.te );
				}
				else
				{
					/* negative energy, assume h nu/kT is very small */
					iso.Boltzmann[ipHE_LIKE][nelem][ipHi][ipLo] = 1.;
				}
			}

			/* collisional ionization
			 * this is rate coefficient, not rate, for hydrogenic species */
			/*TODO	2	find ioniz rates for helium like species, these are hydrogenic rates */
			ASSERT( ipHi > 0 && ipHi < iso.numLevels[ipHE_LIKE][nelem]);
			if( nelem==ipHELIUM && helike.lgSetBenjamin )
			{
				/* atoms */
				
					double BenCollIonParamA[31] = {
						3.36E-09,9.36E-08,1.33E-07,1.58E-07,1.58E-07,1.58E-07,1.81E-07,5.36E-07,
						6.58E-07,7.23E-07,7.81E-07,7.81E-07,7.93E-07,1.62E-06,1.87E-06,2.00E-06,
						2.11E-06,2.11E-06,2.11E-06,2.11E-06,2.13E-06,3.61E-06,4.02E-06,4.21E-06,
						4.39E-06,4.40E-06,4.40E-06,4.40E-06,4.40E-06,4.40E-06,4.44E-06};

					double BenCollIonParamB[31] = {
						0.499,0.280,0.253,0.240,0.240,0.240,0.229,0.138,0.120,0.111,0.104,0.104,
						0.103,0.035,0.021,0.014,0.008,0.008,0.008,0.008,0.007,-0.047,-0.059,-0.064,
						-0.068,-0.068,-0.068,-0.068,-0.068,-0.068,-0.069};

					double BenCollIonParamC[31] = {
						28.625,5.655,4.733,4.331,4.331,4.331,4.037,2.304,2.072,1.972,1.895,
						1.895,1.880,1.298,1.207,1.167,1.135,1.135,1.134,1.134,1.128,0.866,
						0.821,0.802,0.785,0.785,0.784,0.784,0.784,0.784,0.781};

					ASSERT( ipHi < 31 );

					iso.ColIoniz[ipHE_LIKE][nelem][ipHi] = BenCollIonParamA[ipHi] * 
						pow( (double)phycon.te/10000., BenCollIonParamB[ipHi] ) *
						sexp( 10000.*BenCollIonParamC[ipHi]/phycon.te );
			}
			else 
			{
				if( nelem == ipHELIUM )
				{
					iso.ColIoniz[ipHE_LIKE][nelem][ipHi] = hydro_vs_ioniz( ipHE_LIKE, nelem, ipHi );
				}
				else
				{
					/* ions */
					iso.ColIoniz[ipHE_LIKE][nelem][ipHi] = Hion_coll_ioniz_ratecoef(ipHE_LIKE , nelem , ipHi);
				}

				if( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n > iso.n_HighestResolved[ipHE_LIKE][nelem] )
				{
					iso.ColIoniz[ipHE_LIKE][nelem][ipHi] *= 2.*N_(ipHi);
				}

				/* zero collisional ionization if turned off.	*/
				iso.ColIoniz[ipHE_LIKE][nelem][ipHi] *= iso.lgColl_ionize[ipHE_LIKE];
			}

			ColIonizPerN[iso.quant_desig[ipHE_LIKE][nelem][ipHi].n] += iso.ColIoniz[ipHE_LIKE][nelem][ipHi];
		}

		helike.lgErrGenDone = TRUE;

		/* Here we arbitrary scale the highest level ionization to account for the fact
		 * that, if the atom is not full size, this level should be interacting with higher
		 * levels and not just the continuum.  We did add on collisional excitation terms instead
		 * but this caused a problem at low temperatures because the collisional ionization was 
		 * a sum of terms with different boltzmann factors, while PopLTE had just one boltzmann
		 * factor.  The result was a collisional recombination that had residual exponentials of
		 * the form exp(x/kT), which blew up at small T.	*/
		if( !helike.lgFullSize[nelem] && (nelem != ipHELIUM || !helike.lgSetBenjamin) )
			iso.ColIoniz[ipHE_LIKE][nelem][iso.numLevels[ipHE_LIKE][nelem]-1] *= 10.;

		/* Assert that total coll. ioniz. per n increases monotonically with increasing n.	*/
		if( iso.lgColl_ionize[ipHE_LIKE] && (nelem!=ipHELIUM || !helike.lgSetBenjamin ) )
		{
			for( n = 2; n < iso.n_HighestResolved[ipHE_LIKE][nelem] + iso.nCollapsed[ipHE_LIKE][nelem]; n++  )
			{
				ASSERT( (ColIonizPerN[n] < ColIonizPerN[n+1]) || (ColIonizPerN[n]==0.) );
			}
		}

		/* this is to kill collisional excitations from n=1 and n=2 in the
		 * case b hummer and storey option */
		if( opac.lgCaseB_HummerStorey )
		{

			/* this kills collisional excitation and ionization from the ground
			 * 1s 1S level, and the two singlet n=2 levels, 2s 1S and 2p 1P */
			ipLo = ipHe1s1S;
			iso.ColIoniz[ipHE_LIKE][nelem][ipLo] = 0.;
			for( ipHi=ipLo+1; ipHi<iso.numLevels[ipHE_LIKE][nelem]; ipHi++ )
			{
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].cs = 0.;
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].ColUL = 0.;
			}
			ipLo = ipHe2s1S;
			iso.ColIoniz[ipHE_LIKE][nelem][ipLo] = 0.;
			for( ipHi=ipLo+1; ipHi<iso.numLevels[ipHE_LIKE][nelem]; ipHi++ )
			{
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].cs = 0.;
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].ColUL = 0.;
			}
			ipLo = ipHe2p1P;
			iso.ColIoniz[ipHE_LIKE][nelem][ipLo] = 0.;
			for( ipHi=ipLo+1; ipHi<iso.numLevels[ipHE_LIKE][nelem]; ipHi++ )
			{
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].cs = 0.;
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].ColUL = 0.;
			}
		}
	}
	else
	{
		if( trace.lgTrace )
		{
			fprintf( ioQQQ, 
				"       HeCollid called nelem %li - no reeval Boltz fac, LTE dens\n",
				nelem );
		}
	}

	/* atmdat_coll_ion( Atom num (H=1), Num elec, >=0 */
	DimaRate = atmdat_coll_ion( nelem+1, 2 , phycon.te );

	crate = DimaRate*dense.EdenHCorr*iso.lgColl_ionize[ipHE_LIKE];
	/* NB ColIoniz does not include secondaries since not total, but CollidRate does,
	 * since is total */
	iso.ColIoniz[ipHE_LIKE][nelem][ipHe1s1S] = DimaRate*iso.lgColl_ionize[ipHE_LIKE];
	
	/* collisional ionization rate from ground with only thermal */
	/* this part of routine is always reevaluated, even if c is constant,
	 * this is important since density is in crate */
	ionbal.CollIonRate_Ground[nelem][nelem-1][0] = crate;

	/* cooling due to collisional ionization, which only includes thermal */
	ionbal.CollIonRate_Ground[nelem][nelem-1][1] = (crate*
		rfield.anu[Heavy.ipHeavy[nelem][nelem-1]-1]* EN1RYD);

	/* collisional ionizaiton of atomic helium */
	if( nelem == ipHELIUM && !helike.lgSetBenjamin )
	{
		double c2sion , c2pion;
		/* >>refer	he1	cs	Seaton, M.S. 1964, Plan Sp Sci 12, 55. */
		c2sion = 8.8e-10*(double)phycon.sqrte*iso.ConBoltz[ipHE_LIKE][nelem][ipHe2s3S];
		c2pion = 3.*8.8e-10*(double)phycon.sqrte*iso.ConBoltz[ipHE_LIKE][nelem][ipHe2p3P0];
		iso.ColIoniz[ipHE_LIKE][nelem][ipHe2s3S] = c2sion*iso.lgColl_ionize[ipHE_LIKE];
		/* Zhang and Sampson collision excitation data has that excitations out of the separate
		 * terms in 2^3P are equal.  Since coll. ionization is really just collisional excitation,
		 * the same thing should happen here.  So, this treatment should be correct.	*/
		iso.ColIoniz[ipHE_LIKE][nelem][ipHe2p3P0] = c2pion*iso.lgColl_ionize[ipHE_LIKE]/3.;
		iso.ColIoniz[ipHE_LIKE][nelem][ipHe2p3P1] = c2pion*iso.lgColl_ionize[ipHE_LIKE]/3.;
		iso.ColIoniz[ipHE_LIKE][nelem][ipHe2p3P2] = c2pion*iso.lgColl_ionize[ipHE_LIKE]/3.;
	}
	{
		/* prints table of wavelengths of lines to ground */
		/*@-redef@*/
		enum {PRINTIT=FALSE};
		/*@+redef@*/
		if( PRINTIT )
		{

			nelem = ipHELIUM;
			for( ipHi=1; ipHi<iso.numLevels[ipHE_LIKE][ipHELIUM]; ++ipHi )
			{
				for( ipLo=0; ipLo<ipHi; ++ipLo )
				{
					fprintf(ioQQQ,"Z %li Lo %li n %li s %li l %li \t ", 
						nelem+1, ipLo,
						N_(ipLo),
						S_(ipLo),
						L_(ipLo) );
					fprintf(ioQQQ," Hi %li n %li s %li l %li \t", 
						ipHi, 
						N_(ipHi),
						S_(ipHi),
						L_(ipHi) );
					fprintf(ioQQQ,"%.4e\t%.4e\tcs\t%.4e\n",
						EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyWN,
						EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].Aul ,
						EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].cs);
				}
			}
			exit(1);
		}
	}
#if	0
	{
		double cs;

		global_z = 1;
		fprintf( ioQQQ, "n\tl\tl'\tcs\n" );
		for( global_n = 6; global_n <=20; global_n++ )
		{
			for( global_l = 0; global_l <global_n-1; global_l++ )
			{
				for( global_l_prime = global_l + 1; global_l_prime <=global_n-1; global_l_prime++ )
				{
					cs = New_CS_l_mixing( global_z, global_n, global_l, global_l_prime, 10000. );
					/* cs = collision_strength( 0.1, TRUE ); */
					fprintf( ioQQQ, "%li\t%li\t%li\t%e\n", global_n, global_l, global_l_prime, cs );
				}
			}
			/* 
			for( global_l = global_n-1; global_l <=68; global_l++ )
			{
				fprintf( ioQQQ, "%li\t%li\t%e\n", global_n, global_l, -100. );
			}
			*/
		}
	}
#endif

#if	0
	{
		double cs;
		int logv;

		global_z = 1;
		global_n = 28;
		global_l = 2;
		global_temp = phycon.te;

		fprintf( ioQQQ, "lp\t1E-3\t1E-2\t1E-1\t1E+0\t1E+1\t1E+2\n" );
		for( global_l_prime = 0; global_l_prime <=27; global_l_prime++ )
		{
			fprintf( ioQQQ, "%li", global_l_prime );
			
			for( logv = -20; logv <= 0; logv++ )
			{
				cs = collision_strength( pow(10., logv/10.) , TRUE );
				fprintf( ioQQQ, "\t%e", cs );
			}

			fprintf( ioQQQ, "\n" );
		}
		fprintf( ioQQQ, "\n" );
	}
#endif

	free( ColIonizPerN );

#	ifdef DEBUG_FUN
	fputs( " <->HeCollid()\n", debug_fp );
#	endif
}


/* Choose either AtomCSInterp or IonCSInterp */
float HeCSInterp( long nelem, long ipHi , long ipLo )
{
	float cs, factor;

	/* This variable is for diagnostic purposes:
	 * a string used in the output to designate where each cs comes from.	*/	
	char *where = "      ";

	if( nelem == ipHELIUM )
		cs = AtomCSInterp( nelem, ipHi , ipLo, &factor, &where );
	else
		cs = IonCSInterp( nelem, ipHi , ipLo, &factor, &where );

	if( N_(ipLo)<=2 && N_(ipHi)<=5 )
		putError(nelem, ipHi, ipLo, ipCollis, (float)BRAY_STD_DEV );
	else if( N_(ipHi)==N_(ipLo) )
		putError(nelem, ipHi, ipLo, ipCollis, (float)LMIX_STD_DEV );
	else 
		putError(nelem, ipHi, ipLo, ipCollis, (float)VRIENS_STD_DEV );

	ASSERT( cs >= 0.f );
	
	/* in many cases the correction factor for split states has already been made,
	 * if not then factor is still negative */
	if( factor < 0.f )
	{
		ASSERT( helike.lgCS_Vriens );

		factor = 1.f;
	}

	/* take factor into account, usually 1, ratio of stat weights if within 2 3P 
	 * and with collisions from collapsed to resolved levels */
	cs *= factor;

	{
		/*@-redef@*/
		enum {DEBUG_LOC=FALSE};
		/*@+redef@*/
		
		if( DEBUG_LOC && ( nelem==ipOXYGEN ) && (cs > 0.f) && (iso.quant_desig[ipHE_LIKE][nelem][ipHi].n == 2) 
			&& ( iso.quant_desig[ipHE_LIKE][nelem][ipLo].n <= 2 ) )
			fprintf(ioQQQ,"%li\t%li\t%li\t-\t%li\t%li\t%li\t%.2e\t%s\n", 
				iso.quant_desig[ipHE_LIKE][nelem][ipLo].n , iso.quant_desig[ipHE_LIKE][nelem][ipLo].s ,
				iso.quant_desig[ipHE_LIKE][nelem][ipLo].l ,	iso.quant_desig[ipHE_LIKE][nelem][ipHi].n ,
				iso.quant_desig[ipHE_LIKE][nelem][ipHi].s , iso.quant_desig[ipHE_LIKE][nelem][ipHi].l , cs,where);
	}

	return cs;
}

float AtomCSInterp( long nelem, long ipHi , long ipLo, float *factor, char **where )
{
	long ipArray;
	float cs, flow;

#	ifdef DEBUG_FUN
	fputs( "<+>AtomCSInterp()\n", debug_fp );
#	endif

	ASSERT( nelem == ipHELIUM );

	/* init values, better be positive when we exit */
	cs = -1.f; 

	/* this may be used for splitting up the collision strength within 2 3P when
	 * the lower level is withint 2 3P, and for collisions between resolved and collapsed levels.
	 * It may be set somewhere in routine, so set to negative value here as flag saying not set */
	*factor = -1.f;

	/* for most of the helium iso sequence, the order of the J levels within 2 3P 
	 * in increasing energy, is 0 1 2 - the exception is atomic helium itself,
	 * which is swapped, 2 1 0 */

	/* this branch is for upper and lower levels within 2p3P */
	if( ipLo >= ipHe2p3P0 && ipHi <= ipHe2p3P2 )
	{
		*factor = 1.f;
		/* atomic helium, use Berrington private comm cs */
		
		/* >>refer	he1	cs	Berrington, Keith, 2001, private communication - email follows
		> Dear Gary,
		> I could not find any literature on the He fine-structure
		> problem (but I didn't look very hard, so there may be 
		> something somewhere). However, I did a quick R-matrix run to 
		> see what the magnitude of the collision strengths are... At 
		> 1000K, I get the effective collision strength for 2^3P J=0-1, 
		>  0-2, 1-2 as 0.8,0.7,2.7; for 10000K I get 1.2, 2.1, 6.0
		*/
		/* indices are the same and correct, only thing special is that energies are in inverted order...was right first time.	*/
		if( ipLo == ipHe2p3P0 && ipHi == ipHe2p3P1 )
		{
			cs = 1.2f;
		}
		else if( ipLo == ipHe2p3P0 && ipHi == ipHe2p3P2 )
		{
			cs = 2.1f;
		}
		else if( ipLo == ipHe2p3P1 && ipHi == ipHe2p3P2 )
		{
			cs = 6.0f;
		}
		else
		{
			cs = 1.0f;
			TotalInsanity();
		}
		
		*where = "Berr  ";
	}

	/* >>chng 02 feb 25, Bray data should come first since it is the best we have.	*/
	/* this branch is the Bray et al data, for n <= 5, where quantal calcs exist 
	 * must exclude ipLo >= ipHe2p1P because they give no numbers for those	*/
	else if( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n <= 5 && 
		( ipHi < iso.numLevels[ipHE_LIKE][nelem] - iso.nCollapsed[ipHE_LIKE][nelem] ) &&
		helike.HeCS[nelem][ipHi][ipLo][0] >= 0.f )
	{
		ASSERT( *factor == -1.f );
		ASSERT( ipLo < ipHi );
		ASSERT( ipHe2p3P0 == 3 );

		/* ipLo is within 2^3P	*/
		if( ipLo >= ipHe2p3P0 && ipLo <= ipHe2p3P2 )
		{
			/* *factor is ratio of statistical weights of level to term */
			
			/* ipHe2p3P0, ipHe2p3P1, ipHe2p3P2 have indices 3,4,and 5, but j=0,1,and 2.	*/
			*factor = (2.f*((float)ipLo-3.f)+1.f) / 9.f;
			
			/* ipHi must be above ipHe2p3P2 since 2p3Pj->2p3Pk taken care of above	*/
			ASSERT( ipHi > ipHe2p3P2 );
		}
		/* ipHi is within 2^3P	*/
		else if( ipHi >= ipHe2p3P0 && ipHi <= ipHe2p3P2 )
		{
			ASSERT( ipLo < ipHe2p3P0 );

			*factor = (2.f*((float)ipHi-3.f)+1.f) / 9.f;
		}
		/* neither are within 2^3P...no splitting necessary	*/
		else 
		{
			*factor = 1.f;
		}

		/* SOME OF THESE ARE NOT N-CHANGING!	*/
		/* Must be careful about turning each one on or off.	*/
		
		/* this is the case where we have quantal calculations */
		/* >>refer	He1	cs	Bray, I., Burgess, A., Fursa, D.V., & Tully, J.A., 2000, A&AS, 146, 481-498 */
		/* check whether we are outside temperature array bounds,
		 * and use extreme value if we are */
		if( phycon.alogte <= helike.CSTemp[0] )
		{
			cs = helike.HeCS[nelem][ipHi][ipLo][0];
		}
		else if( phycon.alogte >= helike.CSTemp[helike.nCS-1] )
		{
			cs = helike.HeCS[nelem][ipHi][ipLo][helike.nCS-1];
		}
		else
		{
			/* find which array element within the cs vs temp array */
			ipArray = (long)((phycon.alogte - helike.CSTemp[0])/(helike.CSTemp[1]-helike.CSTemp[0]));
			ASSERT( ipArray < helike.nCS );
			ASSERT( ipArray >= 0 );

			/* when taking the average, this is the fraction from the lower temperature value */
			flow = (float)( (phycon.alogte - helike.CSTemp[ipArray])/
				(helike.CSTemp[ipArray+1]-helike.CSTemp[ipArray])) ;
			ASSERT( (flow >= 0.f) && (flow <= 1.f) );

			cs = helike.HeCS[nelem][ipHi][ipLo][ipArray] * (1.f-flow) +
				helike.HeCS[nelem][ipHi][ipLo][ipArray+1] * flow;
		}

		*where = "Bray ";

		/* options to kill collisional excitation and/or l-mixing	*/
		if ( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n == iso.quant_desig[ipHE_LIKE][nelem][ipLo].n )
			/* iso.lgColl_l_mixing turned off with atom he-like l-mixing collisions off command */
			cs *= (float)iso.lgColl_l_mixing[ipHE_LIKE];
		else
		{
			/* iso.lgColl_excite turned off with atom he-like collisional excitation off command */
			cs *= (float)iso.lgColl_excite[ipHE_LIKE];
			/* This kills collisions to n=5, for comparison to Benjamin et al 99.	*/
			if( ( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n >= 5 ) && helike.lgSetBenjamin )
				cs = 0.f;
		}

		ASSERT( cs >= 0.f );
	}

	/* this branch, n-same, l-changing collision, and not case of He with quantal data */
	else if( (iso.quant_desig[ipHE_LIKE][nelem][ipHi].n == iso.quant_desig[ipHE_LIKE][nelem][ipLo].n ) 
		&& (iso.quant_desig[ipHE_LIKE][nelem][ipHi].s == iso.quant_desig[ipHE_LIKE][nelem][ipLo].s ) )
	{
		ASSERT( *factor == -1.f );
		*factor = 1.f;
		
		ASSERT( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n >= 3 );
		ASSERT( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n <= iso.n_HighestResolved[ipHE_LIKE][nelem] );


		if( helike.lgCS_Vrinceanu == TRUE )
		{
			cs = (float)New_CS_l_mixing( nelem,
				iso.quant_desig[ipHE_LIKE][nelem][ipLo].n,
				iso.quant_desig[ipHE_LIKE][nelem][ipLo].l,
				iso.quant_desig[ipHE_LIKE][nelem][ipHi].l,
				(double)phycon.te );
			
			cs *= iso.quant_desig[ipHE_LIKE][nelem][ipLo].s;

			/* add to existing collision strengths
			* this CS should be multiplied by the proton density,
			* but the code will use the electron density, 
			* so ratio of proton to electron densities precorrects for this. */
			cs *= (float)(dense.xIonDense[ipHYDROGEN][1]/dense.EdenHCorr);

		}
		/* this branch, l changing by one */
		else if( abs(iso.quant_desig[ipHE_LIKE][nelem][ipHi].l - iso.quant_desig[ipHE_LIKE][nelem][ipLo].l)== 1)
		{
			cs = (float)CS_l_mixing( nelem, ipLo, ipHi);

			/* add to existing collision strengths
			* this CS should be multiplied by the proton density,
			* but the code will use the electron density, 
			* so ratio of proton to electron densities precorrects for this. */
			cs *= (float)(dense.xIonDense[ipHYDROGEN][1]/dense.EdenHCorr);
		}
		else
		{
			/* l changes by more than 1, but same-n collision */
			cs = 0.f;
		}

		*where = "lmix  ";
		cs *= (float)iso.lgColl_l_mixing[ipHE_LIKE];
	}

	/* this is an atomic n-changing collision with no quantal calculations */
	else if ( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n != iso.quant_desig[ipHE_LIKE][nelem][ipLo].n )
	{
		ASSERT( *factor == -1.f );
		/* this is an atomic n-changing collision with no quantal calculations */
		/* gbar g-bar goes here */

		/* >>chng 02 jul 25, add option for fits to quantal cs data */
		if( helike.lgCS_Vriens )
		{
			/* this is Vriens & Smeets collision strength for neutral H */
			cs = (float)hydro_vs_deexcit( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n, iso.quant_desig[ipHE_LIKE][nelem][ipLo].n );
			*where = "Vriens";
		}
		else if( helike.lgCS_None )
		{
			cs = 0.f;
			*factor = 1.f;
			*where = "no gb";
		}
		else if( helike.lgCS_new )
		{
			*factor = 1.f;
			
			/* two different fits, allowed and forbidden */
			if( EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].Aul > 1. )
			{
				/* permitted lines - large A */
				double x = 
					log10(MAX2(34.7,EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyWN));

				if( helike.lgCS_new == 1 )
				{
					/* this is the broken power law fit, passing through both quantal
					 * calcs at high energy and asymptotically goes to VS at low energies */
					if( x < 4.5 )
					{
						/* low energy fit for permitted transitions */
						cs = (float)pow( 10. , -1.45*x + 6.75);
					}
					else
					{
						/* higher energy fit for permitted transitions */
						cs = (float)pow( 10. , -3.33*x+15.15);
					}
				}
				else if( helike.lgCS_new == 2 )
				{
					/* single parallel fit for permitted transitions, runs parallel to VS */
					cs = (float)pow( 10. , -2.3*x+10.3);
				}
				else
					TotalInsanity();
			}
			else
			{
				/* fit for forbidden transitions */
				if( EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyWN < 25119.f )
				{
					cs = 0.631f; 
				}
				else
				{
					cs = (float)pow(10., 
						-3.*log10(EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyWN)+12.8);
				}
			}

			*where = "newgb";

			/* option to print the resulting collision strengths
			if( nelem==1 )
				fprintf(ioQQQ,"%.3e\t%.3e\t%.3e\n", 
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].Aul ,
				EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyWN,
				cs ); */
		}
		else
			TotalInsanity();

		/* options to turn off collisions */
		cs *= (float)iso.lgColl_excite[ipHE_LIKE];

		/* This kills collisions to n=5, for comparison to Benjamin et al 99.	*/
		if( ( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n >= 5 ) && helike.lgSetBenjamin )
			cs = 0.f;
	}
	else
	{
		/* If spin changing collisions are prohibited in the l-mixing routine,
		 * they will fall here, and will have been assigned no collision strength.	
		 * Assign zero for now.	*/
		ASSERT( iso.quant_desig[ipHE_LIKE][nelem][ipHi].s != iso.quant_desig[ipHE_LIKE][nelem][ipLo].s );
		cs = 0.f;
		*factor = 1.f;
	}

	ASSERT( cs >= 0.f );

	if( helike.lgSetBenjamin )
	{
		if( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n >=5 && 
			iso.quant_desig[ipHE_LIKE][nelem][ipLo].n != iso.quant_desig[ipHE_LIKE][nelem][ipHi].n )
		{
			ASSERT( cs == 0.f );
		}
		else if( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n >=5 &&
			iso.quant_desig[ipHE_LIKE][nelem][ipLo].s != iso.quant_desig[ipHE_LIKE][nelem][ipHi].s )
		{
			cs = 0.f;
		}
	}

	/*putError(nelem,ipHi,ipLo,ipCollis,-1);*/
	
	return(cs);

#	ifdef DEBUG_FUN
	fputs( " <->AtomCSInterp()\n", debug_fp );
#	endif

}

/* IonCSInterp interpolate on collision strengths for element nelem */
float IonCSInterp( long nelem , long ipHi , long ipLo, float *factor, char **where  )
{
	float cs;

#	ifdef DEBUG_FUN
	fputs( "<+>IonCSInterp()\n", debug_fp );
#	endif

	ASSERT( nelem > ipHELIUM );
	ASSERT( nelem < LIMELM );

	/* init values, better be positive when we exit */
	cs = -1.f; 

	/* this may be used for splitting up the collision strength for collisions between
	 * resolved and collapsed levels.  It may be set somewhere in routine, so set to 
	 * negative value here as flag saying not set */
	*factor = -1.f;


	/* >>chng 02 mar 04,  the approximation here for transitions within 2p3P was not needed,
	 * because the Zhang data give these transitions.  They are of the same order, but are 
	 * specific to the three transitions	*/

	/* this branch is ground to n=2 or from n=2 to n=2, for ions only	*/
	/*>>refer Helike	CS	Zhang, Honglin, & Sampson, Douglas H. 1987, ApJS 63, 487	*/
	if( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n==2 
		&& iso.quant_desig[ipHE_LIKE][nelem][ipLo].n<=2 )
	{
		*where = "Zhang";
		*factor = 1.;
		
		/* Collisions from gound	*/
		if( ipLo == ipHe1s1S )
		{
			switch( ipHi )
			{
			case 1:	/* to 2tripS	*/
				cs = 0.25f/(float)POW2(nelem+1.);
				break;
			case 2: /* to 2singS	*/
				cs = 0.4f/(float)POW2(nelem+1.);
				break;
			case 3: /* to 2tripP0	*/
				cs = 0.15f/(float)POW2(nelem+1.);
				break;
			case 4: /* to 2tripP1	*/
				cs = 0.45f/(float)POW2(nelem+1.);
				break;
			case 5: /* to 2tripP2	*/
				cs = 0.75f/(float)POW2(nelem+1.);
				break;
			case 6: /* to 2singP	*/
				cs = 1.3f/(float)POW2(nelem+1.);
				break;
			default:
				TotalInsanity();
				break;
			}
			cs *= (float)iso.lgColl_excite[ipHE_LIKE];
		}
		/* collisions from 2tripS to n=2	*/
		else if( ipLo == ipHe2s3S )
		{
			switch( ipHi )
			{
			case 2: /* to 2singS	*/
				cs = 2.75f/(float)POW2(nelem+1.);
				break;
			case 3: /* to 2tripP0	*/
				cs = 60.f/(float)POW2(nelem+1.);
				break;
			case 4: /* to 2tripP1	*/
				cs = 180.f/(float)POW2(nelem+1.);
				break;
			case 5: /* to 2tripP2	*/
				cs = 300.f/(float)POW2(nelem+1.);
				break;
			case 6: /* to 2singP	*/
				cs = 5.8f/(float)POW2(nelem+1.);
				break;
			default:
				TotalInsanity();
				break;
			}
			cs *= (float)iso.lgColl_l_mixing[ipHE_LIKE];
		}
		/* collisions from 2singS to n=2	*/
		else if( ipLo == ipHe2s1S )
		{
			switch( ipHi )
			{
			case 3: /* to 2tripP0	*/
				cs = 0.56f/(float)POW2(nelem+1.);
				break;
			case 4: /* to 2tripP1	*/
				cs = 1.74f/(float)POW2(nelem+1.);
				break;
			case 5: /* to 2tripP2	*/
				cs = 2.81f/(float)POW2(nelem+1.);
				break;
			case 6: /* to 2singP	*/
				cs = 190.f/(float)POW2(nelem+1.);
				break;
			default:
				TotalInsanity();
				break;
			}
			cs *= (float)iso.lgColl_l_mixing[ipHE_LIKE];
		}
		/* collisions from 2tripP0 to n=2	*/
		else if( ipLo == ipHe2p3P0 )
		{
			switch( ipHi )
			{
			case 4: /* to 2tripP1	*/
				cs = 8.1f/(float)POW2(nelem+1.);
				break;
			case 5: /* to 2tripP2	*/
				cs = 8.2f/(float)POW2(nelem+1.);
				break;
			case 6: /* to 2singP	*/
				cs = 3.9f/(float)POW2(nelem+1.);
				break;
			default:
				TotalInsanity();
				break;
			}
			cs *= (float)iso.lgColl_l_mixing[ipHE_LIKE];
		}
		/* collisions from 2tripP1 to n=2	*/
		else if( ipLo == ipHe2p3P1 )
		{
			switch( ipHi )
			{
			case 5: /* to 2tripP2	*/
				cs = 30.f/(float)POW2(nelem+1.);
				break;
			case 6: /* to 2singP	*/
				cs = 11.7f/(float)POW2(nelem+1.);
				break;
			default:
				TotalInsanity();
				break;
			}
			cs *= (float)iso.lgColl_l_mixing[ipHE_LIKE];
		}
		/* collisions from 2tripP2 to n=2	*/
		else if( ipLo == ipHe2p3P2 )
		{
			/* to 2singP	*/
			cs = 19.5f/(float)POW2(nelem+1.) * (float)iso.lgColl_l_mixing[ipHE_LIKE];
		}
		else
			TotalInsanity();
	}

	/* this branch, n-same, l-changing collisions */
	else if( (iso.quant_desig[ipHE_LIKE][nelem][ipHi].n == iso.quant_desig[ipHE_LIKE][nelem][ipLo].n ) &&
		(iso.quant_desig[ipHE_LIKE][nelem][ipHi].s == iso.quant_desig[ipHE_LIKE][nelem][ipLo].s ) )
	{
		*factor = 1.f;
		
		ASSERT( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n <= iso.n_HighestResolved[ipHE_LIKE][nelem] );

		if( helike.lgCS_Vrinceanu == TRUE )
		{
			cs = (float)New_CS_l_mixing( nelem,
				iso.quant_desig[ipHE_LIKE][nelem][ipLo].n,
				iso.quant_desig[ipHE_LIKE][nelem][ipLo].l,
				iso.quant_desig[ipHE_LIKE][nelem][ipHi].l,
				(double)phycon.te );
			
			cs *= iso.quant_desig[ipHE_LIKE][nelem][ipLo].s;

			/* add to existing collision strengths
			* this CS should be multiplied by the proton density,
			* but the code will use the electron density, 
			* so ratio of proton to electron densities precorrects for this. */
			cs *= (float)(dense.xIonDense[ipHYDROGEN][1]/dense.EdenHCorr);

		}
		/* this branch, l changing by one */
		else if( abs(iso.quant_desig[ipHE_LIKE][nelem][ipHi].l - iso.quant_desig[ipHE_LIKE][nelem][ipLo].l)== 1)
		{
			cs = (float)CS_l_mixing( nelem, ipLo, ipHi);

			/* add to existing collision strengths
			* this CS should be multiplied by the proton density,
			* but the code will use the electron density, 
			* so ratio of proton to electron densities precorrects for this. */
			cs *= (float)(dense.xIonDense[ipHYDROGEN][1]/dense.EdenHCorr*iso.lgColl_l_mixing[ipHE_LIKE]);
		}
		else
		{
			/* l changes by more than 1, but same-n collision */
			cs = 0.f;
		}
		*where = "lmix  ";
	}

	/* this branch, n changing collisions for ions */
	else if( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n != iso.quant_desig[ipHE_LIKE][nelem][ipLo].n )
	{
		long int nlo = iso.quant_desig[ipHE_LIKE][nelem][ipLo].n;
		
		/* TODO Even spin-changing collisions are included here.  Is this okay?	*/

		/* for this routine 2s is 1, grnd is 0 */
		if( nlo == 1)
			nlo = 0;

		/* ionic n-changing collision */
		cs = (float)Hion_colldeexc_cs(iso.quant_desig[ipHE_LIKE][nelem][ipHi].n, nlo, nelem, ipHE_LIKE );
		cs *= iso.lgColl_excite[ipHE_LIKE];

		*where = "hydro";
	}
	else
	{
		/* what's left are deltaN=0, spin changing collisions.
		 * These have not been accounted for.	*/
		/* Make sure what comes here is what we think it is.	*/
		ASSERT( iso.quant_desig[ipHE_LIKE][nelem][ipHi].n == iso.quant_desig[ipHE_LIKE][nelem][ipLo].n );
		ASSERT( iso.quant_desig[ipHE_LIKE][nelem][ipHi].s != iso.quant_desig[ipHE_LIKE][nelem][ipLo].s );
		cs = 0.f;
		*where = "spin ";
	}

	ASSERT( cs >= 0.f );

#	ifdef DEBUG_FUN
	fputs( " <->IonCSInterp()\n", debug_fp );
#	endif

	/*putError(nelem,ipHi,ipLo,ipCollis,-1);*/

	return(cs);

}

/*CS_l_mixing - find rate for l-mixing collisions by protons, for neutrals */
double CS_l_mixing(
	long nelem /* the chemical element, 1 for He */,
	long ipLo /* lower level, 0 for ground */,
	long ipHi /* upper level, 0 for ground */ )
{
	/* >>refer	He	l-mixing	Pengelly, R.M., & Seaton, M.J., 1964, MNRAS, 127, 165 */
	double cs, Dul,
		TwoLogDebye, TwoLogRc1, TwoLogRc2,
		factor1, factor2, factor3,
		bestfactor,	factorpart,
		reduced_mass, reduced_mass_2_emass,
		rate, radint, tau, RMSv, deltaE;
	/* only do proton collisions for now */
	const double ChargIncoming = 1.;

#	ifdef DEBUG_FUN
	fputs( "<+>CS_l_mixing()\n", debug_fp );
#	endif

	/* In this routine, three different cutoff radii are calculated, and as per PS64,
	 * the least of these is selected.  Must be careful to take least positive result!	*/
	
	/* This reduced mass is in grams.	*/
	reduced_mass = dense.AtomicWeight[nelem]*dense.AtomicWeight[ipHYDROGEN]/
		(dense.AtomicWeight[nelem]+dense.AtomicWeight[ipHYDROGEN])*ATOMIC_MASS_UNIT;
	/* reduced mass in gm, only do proton collisions for now
	 * this mass always appears relative to the electron mass, so define it that way */
	reduced_mass_2_emass = reduced_mass / ELECTRON_MASS;

	/* this is root mean squared velocity */
	/* >>chng 02 mar 17 replaced numbers with constant.	*/
	/* >>chng 03 nov 11 this was wrong...RMSv was two order of magnitude too small!	*/
	RMSv = sqrt( 2. * BOLTZMANN * phycon.te / reduced_mass );
	/* This is the difference between energy levels, in eV.	*/
	deltaE = EmisLines[ipHE_LIKE][nelem][ipHi][ipLo].EnergyWN * WAVNRYD * EVRYD;
	
	/* This is the lifetime of ipLo.	*/
	tau = helike.Lifetime[nelem][ipLo];
	
	/* equation 46 of PS64 */
	/* min on density added to prevent this from becoming large and negative
	 * at very high n_e - Pengelly & Seaton did not apply this above
	 * 1e12 cm^-3 */
	/* This is actually 2 times the log of the Debye radius.	*/
	TwoLogDebye = 1.68 + log10( phycon.te / MIN2(1e12 , dense.eden ) );

	/* This is cutoff = 0.72v(tau), where tau is the lifetime of the level nl.	*/
	TwoLogRc1 = 10.95 + log10( phycon.te * tau * tau / reduced_mass_2_emass );
	
	/* This is cutoff = 1.12hbar*v/deltaE.	*/
	/* If deltaE is zero or negative, set this cutoff to zero, so that it will be excluded below.	*/
	if ( deltaE > 0. )
		TwoLogRc2 = 2. * log10( 1.12 * HBAReV * RMSv / deltaE );
	else
		TwoLogRc2 = 0.;

	/* Calculate the hydrogenic radial integral */
	if (iso.quant_desig[ipHE_LIKE][nelem][ipHi].l > iso.quant_desig[ipHE_LIKE][nelem][ipLo].l)
		radint = hri(iso.quant_desig[ipHE_LIKE][nelem][ipHi].n,iso.quant_desig[ipHE_LIKE][nelem][ipHi].l,
			iso.quant_desig[ipHE_LIKE][nelem][ipLo].n, iso.quant_desig[ipHE_LIKE][nelem][ipLo].l, nelem);
	else if (iso.quant_desig[ipHE_LIKE][nelem][ipHi].l < iso.quant_desig[ipHE_LIKE][nelem][ipLo].l)
		radint = hri(iso.quant_desig[ipHE_LIKE][nelem][ipHi].n,iso.quant_desig[ipHE_LIKE][nelem][ipLo].l,
			iso.quant_desig[ipHE_LIKE][nelem][ipLo].n, iso.quant_desig[ipHE_LIKE][nelem][ipHi].l, nelem);
	else 
		radint = 0.;

	ASSERT( radint > 0. );

	Dul = 8./3. * radint * radint * ChargIncoming * ChargIncoming;

	factorpart = (11.54 + log10( phycon.te  / Dul / reduced_mass_2_emass ) );

	if( (factor1 = factorpart + TwoLogDebye) <= 0.)
		factor1 = BIGDOUBLE;
	if( (factor2 = factorpart + TwoLogRc1) <= 0.)
		factor2 = BIGDOUBLE;
	if( (TwoLogRc2 == 0.) || ( (factor3 = factorpart + TwoLogRc2) <= 0.) )
		factor3 = BIGDOUBLE;
	
	/* Now we find the least positive result.	*/
	/* It would be suspect if bestfactor gets above the double digit range,
	 * so let's make sure less than 100.	*/
	if( (bestfactor = MIN3(factor1,factor2,factor3)) > 100.)
	{
		/* this calculates the TOTAL collision strength from nl to nl+/-1. Use as last resort.	*/
		/* equation 44 of PS64 */
		/* NB - this only works for helium-like ions, the nelem in pow2 is the residual nuclear charge */
		Dul = POW2( ChargIncoming / (nelem) ) * 6. * POW2( (double)iso.quant_desig[ipHE_LIKE][nelem][ipLo].n ) *
			( POW2((double)iso.quant_desig[ipHE_LIKE][nelem][ipLo].n) - 
			POW2((double)iso.quant_desig[ipHE_LIKE][nelem][ipLo].l) - iso.quant_desig[ipHE_LIKE][nelem][ipLo].l - 1);

		bestfactor = (11.54 + log10( phycon.te  / Dul / reduced_mass_2_emass ) );
	}

	ASSERT( bestfactor > 0.);

	/* This is NOT rate...it is rate over eden over population of level.   Units: cm^3 s-1	*/
	rate = 9.93e-6 * sqrt( reduced_mass_2_emass  ) * Dul / phycon.sqrte * bestfactor;

	/* convert rate to collision strength */
	cs = rate / COLL_CONST * phycon.sqrte * iso.stat[ipHE_LIKE][nelem][ipLo] ;

#if	0
	/* convert from collsion strength to cross_section */
	cs *= PI/(2.*iso.quant_desig[ipHE_LIKE][nelem][ipLo].l+1.);
	cs /= POW2( reduced_mass * RMSv / H_BAR );
#endif

	ASSERT( cs > 0. );

#	ifdef DEBUG_FUN
	fputs( " <->CS_l_mixing()\n", debug_fp );
#	endif

	/*putError(nelem,ipHi,ipLo,ipCollis,-1);*/

	return cs ;
}

/*CS_l_mixing - find collision strength for l-mixing collisions by protons, for neutrals */
double New_CS_l_mixing( long nelem, long n,	long l,	long lp, double temp)
{
	double Therm_ave_coll_str;

	global_z = nelem;
	global_n = n;
	global_l = l;
	global_l_prime = lp;
	global_temp = temp;
	global_collider_charge = 1.;

	kTRyd = temp / TE1RYD;
	Therm_ave_coll_str =  qg32( 0.0, 1.0, Therm_ave_coll_str_int);
	Therm_ave_coll_str += qg32( 1.0, 10.0, Therm_ave_coll_str_int);

	return Therm_ave_coll_str;
}

/* The integrand for calculating the thermal average of collision strengths */
static double Therm_ave_coll_str_int( double EOverKT )
{
	double integrand;
	
	integrand = exp( -1.*EOverKT ) * collision_strength( EOverKT * kTRyd, FALSE );

	return integrand;
}

static double collision_strength( double velOrEner, long paramIsRedVel )
{
	double cross_section, coll_str, RMSv, aveRadius, reduced_vel;
	double ConstantFactors, reduced_mass, CSIntegral;
	double ColliderCharge = global_collider_charge;
	double quantum_defect;
	double reduced_b_max, reduced_b_min, alphamax, alphamin, step, alpha1, alpha2;
	double Bohr_rad = 5.29177249e-9;
	double quantum_defect2;

	long nelem, n, l, lp;

	nelem = global_z;
	n = global_n;
	l = global_l;
	lp = global_l_prime;

	/* This reduced mass is in grams.	*/
	reduced_mass = dense.AtomicWeight[nelem]*dense.AtomicWeight[ipHYDROGEN]/
		(dense.AtomicWeight[nelem]+dense.AtomicWeight[ipHYDROGEN])*ATOMIC_MASS_UNIT;

	/* this is root mean squared velocity */
	/* use this as projectile velocity for thermally-averaged cross-section? */
	/*RMSv = sqrt( 2. * BOLTZMANN * global_temp / reduced_mass );*/
	aveRadius = (n*n/nelem)*Bohr_rad;
	global_an = aveRadius;
	
	RMSv = nelem*POW2(ELEM_CHARGE_ESU)/n/H_BAR;

	if( paramIsRedVel == TRUE )
	{
		/* velOrEner is a reduced velocity */
		reduced_vel = velOrEner;
	}
	else
	{	
		/* velOrEner is a projectile energy in Rydbergs */
		reduced_vel = sqrt( 2.*velOrEner*EN1RYD/PROTON_MASS )/RMSv;
	}
	
	global_red_vel = reduced_vel;

	/* Factors outside integral	*/
	ConstantFactors = 4.5*PI*POW2(ColliderCharge*aveRadius/reduced_vel);

	/* Reduced here means in units of aveRadius: */
	reduced_b_min = 1.5 * ColliderCharge / reduced_vel;
	
	/* Debye radius: appears to be too large, results in 1/v^2 variation. */
	/* reduced_b_max = sqrt( BOLTZMANN*global_temp/ColliderCharge/dense.eden ) 
		/ (PI2*ELEM_CHARGE_ESU); */

	/* This one was derived by Keith MacAdam. */
	if( l==0 )
	{
		return SMALLFLOAT;
	}
	else
	{
		/* quantum_defect = 0.75*1.0015*( pow((double)l,-5.) - 0.6*pow((double)l,-3.)*pow((double)n,-2.) ); */
		double helium_10_defects[10]=
			{2.97063E-01,6.81567E-02,2.82381E-03,4.27703E-04,1.17319E-04,
			4.25254E-05,1.85549E-05,9.24641E-06,5.30882E-06,3.02877E-06};
		if( l <= 9 )
		{
			quantum_defect = helium_10_defects[l];
		}
		else
		{
			quantum_defect = 0.0656*pow((double)l, -4.5606);
		}

		if( lp <= 9 )
		{
			quantum_defect2 = helium_10_defects[lp];
		}
		else
		{
			quantum_defect2 = 0.0656*pow((double)lp, -4.5606);
		}
	}

#if	0
	if( lp - l == 1 )
	{
		double defectdiff, D;
		/* do percival and richards 77 */

		defectdiff = quantum_defect - quantum_defect2;
		D = 9.*l*(1.-POW2((double)l/(double)n))/(4.*l-2.);

		/* Calculate cross section */
		cross_section = 1.3333*PI*POW2(aveRadius*n/reduced_vel)*log(1.436*reduced_vel*reduced_vel/defectdiff/sqrt(D)/n);
	}
	else
#endif
	{
		
		reduced_b_max = sqrt( 0.3 * ColliderCharge * l / quantum_defect / (1.-0.6*l*l/n/n));
		
		reduced_b_max = MAX2( reduced_b_max, reduced_b_min );
		global_bmax = reduced_b_max*aveRadius;

		alphamin = 1.5*ColliderCharge/(reduced_vel * reduced_b_max);
		alphamax = 1.5*ColliderCharge/(reduced_vel * reduced_b_min);
		
		CSIntegral = 0.;

		if( alphamax > alphamin )
		{

			step = alphamin;
			alpha1 = alphamin;
			alpha2 = MIN2( alphamax, alpha1 + step );
			do
			{
				CSIntegral += qg32(  alpha1, alpha2, L_mix_integrand);
				step *= 3.;
				alpha1 = alpha2;
				alpha2 = MIN2( alphamax, alpha1 + step );
			}
			while( alpha1 < alphamax );
		}

		/* Calculate cross section */
		cross_section = ConstantFactors * CSIntegral;

	}
	/* convert to collision strength */
	coll_str = cross_section * (2.*l+1.)/PI;
	coll_str *= POW2( ELECTRON_MASS * RMSv / H_BAR );

	coll_str = MAX2( SMALLFLOAT, coll_str);
	return coll_str;
}

static double L_mix_integrand( double alpha )
{
	double integrand, deltaPhi, b;

	b = 1.5*global_collider_charge*global_an/(global_red_vel * alpha);
	if( b < global_bmax )
	{
		deltaPhi = -1.*PI + 2.*asin(b/global_bmax);
	}
	else
	{
		deltaPhi = 0.;
	}
	integrand = 1./alpha/alpha/alpha;
	integrand *= StarkCollTransProb( global_n, global_l, global_l_prime, alpha, deltaPhi );
	
	return integrand;
}

static double StarkCollTransProb( long n, long l, long lp, double alpha, double deltaPhi)
{
	double probability;
	double cosU1, cosU2, sinU1, sinU2, cosChiOver2, sinChiOver2, cosChi, A, B;
	
	ASSERT( alpha > 0. );

	cosU1 = 2.*POW2((double)l/(double)n) - 1.;
	cosU2 = 2.*POW2((double)lp/(double)n) - 1.;

	sinU1 = sqrt( 1. - cosU1*cosU1 );
	sinU2 = sqrt( 1. - cosU2*cosU2 );

	cosChiOver2 = (1. + alpha*alpha*cos( sqrt(1.+alpha*alpha) * deltaPhi ) )/(1.+alpha*alpha);
	sinChiOver2 = sqrt( 1. - cosChiOver2*cosChiOver2 );
	cosChi = 2. * POW2( cosChiOver2 ) - 1.;

	A = (cosU1*cosU2 - sinU1*sinU2 - cosChi)/(1. - cosChi);
	B = (cosU1*cosU2 + sinU1*sinU2 - cosChi)/(1. - cosChi);

	if( B <= 0. )
	{
		probability = 0.;
	}
	else
	{
		probability = 2.*lp/(PI* /*H_BAR* */ n*n*POW2( sinChiOver2 ));

		if( A < 0. )
		{
			probability *= ellip_int_K( sqrt(B/(B-A)) );
			probability /= sqrt( B - A );
		}
		else
		{
			probability *= ellip_int_K( sqrt((B-A)/B) );
			probability /= sqrt( B );
		}
	}

	/* 
	if( n==28 && l>=15 )
	{
		fprintf( ioQQQ, "l\t%li\talpha\t%e\tProb\t%e\n", l, alpha, probability ); 
	}
	*/

	return probability;
}


/* This is a modified version of two routines
 * in the GNU science library, version 1.4:
 *		gsl_sf_ellint_Kcomp_e
 *		gsl_sf_ellint_RF_e
 * The routines include references:
 *		[Carlson, Numer. Math. 33 (1979) 1, (4.5)]
 *		[Abramowitz+Stegun, 17.3.33]
 */
static double ellip_int_K( double k )
{
	double result;

	ASSERT( k*k < 1.0 );
  
	if(k*k >= 1.0 - sqrt(DBL_EPSILON))
	{
		/* [Abramowitz+Stegun, 17.3.33] */
		const double y = 1.0 - k*k;
		const double a[] = { 1.38629436112, 0.09666344259, 0.03590092383 };
		const double b[] = { 0.5, 0.12498593597, 0.06880248576 };
		const double ta = a[0] + y*(a[1] + y*a[2]);
		const double tb = -log(y) * (b[0] * y*(b[1] + y*b[2]));
		result = ta + tb;
	}
	else
	{
		double y = 1.0 - k*k;
		
		const double lolim = 5.0 * DBL_MIN;
		const double uplim = 0.2 * DBL_MAX;

		const double c1 = 1.0 / 24.0;
		const double c2 = 3.0 / 44.0;
		const double c3 = 1.0 / 14.0;
		double xn = 0.0;
		double yn = y;
		double zn = 1.0;
		double mu =0., xndev=0., yndev=0., zndev=0., e2, e3, s;

		ASSERT( y >= lolim );
		ASSERT( y < uplim );

		while(1)
		{
			double epslon, lamda;
			double xnroot, ynroot, znroot;
			mu = (xn + yn + zn) / 3.0;
			xndev = 2.0 - (mu + xn) / mu;
			yndev = 2.0 - (mu + yn) / mu;
			zndev = 2.0 - (mu + zn) / mu;
			epslon = MAX3(fabs(xndev), fabs(yndev), fabs(zndev));
			if (epslon < 0.001) break;
			xnroot = sqrt(xn);
			ynroot = sqrt(yn);
			znroot = sqrt(zn);
			lamda = xnroot * (ynroot + znroot) + ynroot * znroot;
			xn = (xn + lamda) * 0.25;
			yn = (yn + lamda) * 0.25;
			zn = (zn + lamda) * 0.25;
		}
		e2 = xndev * yndev - zndev * zndev;
		e3 = xndev * yndev * zndev;
		s = 1.0 + (c1 * e2 - 0.1 - c2 * e3) * e2 + c3 * e3;
		result = s / sqrt(mu);
	}
	return result;
}
