!============================================================================= ! ! $Id: phot_lookup.F90,v 1.13 2007/05/24 17:03:55 kouatch Exp $ ! ! CODE DEVELOPER ! Dan Bergmann & John Tannahill, LLNL ! ! FILE ! phot_lookup.F ! ! ROUTINES ! Lookup_Qj ! ! HISTORY ! * September 23, 2004 - Jules Kouatchou ! Modifications to accomodate for the combined strat/trop chemical ! mechanism. New variables are: o1d_a, o1d_b, qj_adjust, o1d_T. ! New calculations are carried out to: ! - Interpolate the O(1D) correlation factor in solar zenith angle, ! column ozone, and pressure (see 1d_a, o1d_b, and o1d_T) ! - Nudge O3 photolysis to represent higher resolution around the ! O(1D) falloff region (308-328 nm) (see qj_adjust). !============================================================================= !----------------------------------------------------------------------------- ! ! ROUTINE ! Lookup_Qj ! ! DESCRIPTION ! This is the LLNL implementation of Randy Kawa's photolysis lookup table. ! It requires a NetCDF file which has the radiative source as a function ! of wavelength, pressure, column ozone, and solar zenith angle. This ! NetCDF file also contains cross sections as a function of species, ! temperature, and wavelength. ! ! ARGUMENTS ! do_clear_sky : do clear sky photolysis? ! phot_opt : photolysis option ! io3_num : const array index for ozone ! nymd : current year/month/day (YYYYMMDD) ! photintv : photolysis time step (s) ! rsec_jan1 : seconds from Jan. 1st (s) ! press3c : atmospheric pressure at the center of each grid box (mb) ! kel : temperature (degK) ! const : species concentration, known at zone centers (mixing ratio) ! latdeg : latitude (deg) ! mcor : area of grid box (m^2) ! mass : total mass of the atmosphere within each grid box (kg) ! fracCloudCover: fractional cloud cover ! qjgmi : photolysis rate constants (s^-1) ! !----------------------------------------------------------------------------- subroutine Lookup_Qj & 1,10 & (do_clear_sky, phot_opt, io3_num, nymd, photintv, & & rsec_jan1, press3c, kel, concentration, latdeg, mcor, mass, fracCloudCover, qjgmi, & & pr_diag, loc_proc, num_species, num_qjs, num_qjo, & & ilo, ihi, julo, jhi, i2_gl, ju1_gl, j2_gl, i1, i2, ju1, j2, k1, k2) use GmiInterpolation_mod , only : Interp use GmiArrayBundlePointer_mod, only : t_GmiArrayBundle use solar_cycle_mod use GmiTimeControl_mod , only : GmiSplitDateTime use GmiPrintError_mod, only : GmiPrintError implicit none # include "gmi_time_constants.h" # include "gmi_phys_constants.h" # include "phot_lookup_constants.h" # include "phot_lookup.h" # include "phot_lookup_arrays.h" # include "gmi_chemcase.h" ! ---------------------- ! Argument declarations. ! ---------------------- logical, intent(in) :: pr_diag integer, intent(in) :: loc_proc integer, intent(in) :: num_species, num_qjs, num_qjo integer, intent(in) :: ilo, ihi, julo, jhi integer, intent(in) :: i2_gl, ju1_gl, j2_gl integer, intent(in) :: i1, i2, ju1, j2, k1, k2 logical :: do_clear_sky integer :: phot_opt integer :: io3_num integer :: nymd real*8 :: photintv real*8 :: rsec_jan1 real*8 :: press3c (ilo:ihi, julo:jhi, k1:k2) real*8 :: kel (ilo:ihi, julo:jhi, k1:k2) real*8 :: latdeg (ju1_gl:j2_gl) real*8 :: mcor (i1:i2, ju1:j2) real*8 :: mass (i1:i2, ju1:j2, k1:k2) real*8 :: qjgmi (i1:i2, ju1:j2, k1:k2, num_qjo) real*8 , intent(in ) :: fracCloudCover(i1:i2, ju1:j2) type (t_GmiArrayBundle), intent(in) :: concentration(num_species) ! ---------------------- ! Variable declarations. ! ---------------------- character (len=75) :: err_msg logical, save :: first = .true. integer :: idumday, idumyear integer :: iik, ilam, io3 integer :: isza integer :: il, ij, ik, iq integer :: month integer :: o3_pl_ind, o3_ph_ind integer :: o3_pl_indp1, o3_ph_indp1 integer :: p_ind, p_ind_old, p_indm1 integer :: sza_ind, t_index !... variable containing index corresponding to current model year/month, !... repeats last 11 years after 2001 integer :: iscyr integer :: inyr, inmon real*8 :: days real*8 :: decl real*8 :: half_the_box real*8 :: loc_col_o3 real*8 :: o3_ph_frac, o3_pl_frac real*8 :: p_frac real*8 :: rdistsq, ri2_gl, ril real*8 :: stime_loc, time real*8 :: sza, sza_frac real*8 :: total real*8, save :: cos94 real*8 :: cossza(1) real*8 :: loc_col_o3ary(1) real*8 :: minary(1) real*8 :: rsf(NUMLAM) real*8, save :: lon_dummy(1) = 0.0d0 real*8 :: o1d_a, o1d_b, qj_adjust real*8 :: o1d_T(5) ! ---------------- ! Begin execution. ! ---------------- if (pr_diag) then Write (6,*) 'Lookup_Qj called by ', loc_proc end if ! ========== if (first) then ! ========== first = .false. cos94 = Cos (RADPDEG * 94.0d0) end if !... figure out index for solar cycle array from year and month inyr = int(nymd/10000) inmon = int(nymd/100)-100*inyr if(inyr.ge.70) then iscyr = 12*(inyr-47)+inmon else iscyr = 12*(inyr+100-47)+inmon endif !... recycle last 11 years if beyond end of data DO WHILE ((iscyr.gt.NUM_SOLAR_CYC_MON)) iscyr = iscyr-11*12 enddo !... recycle previous 11 years if before beginning of data DO WHILE ((iscyr.lt.1)) iscyr = iscyr+11*12 enddo if (pr_diag) then print *,'Solar cycle debug: ',iscyr, loc_proc,s_cycle(3,iscyr) end if time = rsec_jan1 + (0.5d0 * photintv) ri2_gl = i2_gl call GmiSplitDateTime (nymd, idumyear, month, idumday) ! ====================== illoop: do il = i1, i2 ! ====================== ril = il stime_loc = time + (SECPDY * (ril - 1.0d0) / ri2_gl) days = stime_loc / SECPDY ! ============ call Solrpos & ! ============ & (days, decl, rdistsq) ! ======================= ijloop: do ij = ju1, j2 ! ======================= ! ----------------------------------------------- ! Calculate solar zenith angle and index into the ! radiative source function table. ! ----------------------------------------------- ! =========== call Solrza & ! =========== & (days, decl, latdeg(ij), lon_dummy, 1, cossza) ! --------------------------------------------------------- ! If the solar zenith angle exceeds 94 degrees, set all qjs ! to zero and exit loop. ! --------------------------------------------------------- if (cossza(1) < cos94) then do ik = k1, k2 do iq = 1, num_qjs qjgmi(il,ij,ik,iq) = 0.0d0 end do end do ! ============ cycle ijloop ! ============ end if sza = Acos (cossza(1)) / RADPDEG isza_loop: do isza = 2, NUMSZA if (sza <= sza_phot(isza)) then sza_ind = isza - 1 ! ============== exit isza_loop ! ============== end if if (isza == NUMSZA) then err_msg = & & 'Error finding solar zenith angle index in Lookup_Qj.' call GmiPrintError & & (err_msg, .true., 0, 0, 0, 0, 0.0d0, 0.0d0) end if end do isza_loop sza_frac = (sza_phot(sza_ind+1) - sza) / & & (sza_phot(sza_ind+1) - sza_phot(sza_ind)) ! --------------------------------------------------- ! Initialize pressure index into the radiative source ! function table. ! --------------------------------------------------- p_ind = NUMPRS loc_col_o3 = 0.0d0 ! ========================== ikloop: do ik = k2, k1, -1 ! ========================== if (phot_opt == 5) then ! ------------------------------------------------- ! Find the column ozone from the climatology in the ! lookup table. ! ------------------------------------------------- minary(1) = Min (press3c(il,ij,ik), o3_clim_prs(1)) loc_col_o3ary(1) = loc_col_o3 ! =========== call Interp & ! =========== & (o3_clim_prs(:), o3_clim(:,il,ij,month), NUM_O3CLIM_PRS, & & minary(:), loc_col_o3ary(:), 1) loc_col_o3 = loc_col_o3ary(1) else ! ----------------------- ! Calculate column ozone. ! ----------------------- half_the_box = & & 0.5d0 * concentration(io3_num)%pArray3D(il,ij,ik) * mass(il,ij,ik) * & & AVOGAD / (mcor(il,ij) * MWTAIR * 10.0d0) loc_col_o3 = loc_col_o3 + half_the_box end if ! ------------------------------------- ! Find new pressure index in the table. ! ------------------------------------- p_ind_old = p_ind iik_loop: do iik = p_ind_old, 1, -1 p_indm1 = iik if (prs_phot(iik) >= press3c(il,ij,ik)) then ! ============= exit iik_loop ! ============= end if end do iik_loop if (p_indm1 == 1) then p_frac = 0.0d0 p_ind = p_indm1 else if (p_indm1 == NUMPRS) then p_frac = 1.0d0 p_ind = NUMPRS else p_ind = p_indm1 + 1 p_frac = (prs_phot(p_indm1) - press3c(il,ij,ik)) / & & (prs_phot(p_indm1) - prs_phot(p_ind)) end if ! ----------------------------------------------------------- ! Find new column ozone indices in the table for both ! pressure levels surrounding the pressure for this grid box. ! ----------------------------------------------------------- io3_loop1: do io3 = 2, NUMO3 if (loc_col_o3 < col_o3(io3, p_ind)) then o3_pl_ind = io3 - 1 o3_pl_indp1 = io3 o3_pl_frac = & & (col_o3(o3_pl_indp1,p_ind) - loc_col_o3) / & & (col_o3(o3_pl_indp1,p_ind) - col_o3(o3_pl_ind,p_ind)) o3_pl_frac = Max (o3_pl_frac, 0.0d0) ! ============== exit io3_loop1 ! ============== end if if (io3 == NUMO3) then o3_pl_ind = NUMO3 o3_pl_indp1 = NUMO3 o3_pl_frac = 1.0d0 end if end do io3_loop1 io3_loop2: do io3 = 2, NUMO3 if (loc_col_o3 < col_o3(io3,p_indm1)) then o3_ph_ind = io3 - 1 o3_ph_indp1 = io3 o3_ph_frac = & & (col_o3(o3_ph_indp1,p_indm1) - loc_col_o3) / & & (col_o3(o3_ph_indp1,p_indm1) - & & col_o3(o3_ph_ind,p_indm1)) o3_ph_frac = Max (o3_ph_frac, 0.0d0) ! ============== exit io3_loop2 ! ============== end if if (io3 == NUMO3) then o3_ph_ind = NUMO3 o3_ph_indp1 = NUMO3 o3_ph_frac = 1.0d0 end if end do io3_loop2 ! -------------------------------------------------- ! Interpolate the radiative source function in solar ! zenith angle, column ozone, and pressure. ! -------------------------------------------------- do ilam = 1, NUMLAM rsf(ilam) = & & sza_frac * & & (p_frac * & & ((o3_pl_frac) * & & rad_source(ilam,sza_ind,o3_pl_ind,p_ind) + & & (1.0d0 - o3_pl_frac) * & & rad_source(ilam,sza_ind,o3_pl_indp1,p_ind)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & rad_source(ilam,sza_ind,o3_ph_ind,p_indm1) + & & (1.0d0 - o3_ph_frac) * & & rad_source(ilam,sza_ind,o3_ph_indp1,p_indm1))) + & & (1.0d0 - sza_frac) * & & (p_frac * & & ((o3_pl_frac) * & & rad_source(ilam,sza_ind+1,o3_pl_ind,p_ind) + & & (1.0d0 - o3_pl_frac) * & & rad_source(ilam,sza_ind+1,o3_pl_indp1,p_ind)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & rad_source(ilam,sza_ind+1,o3_ph_ind,p_indm1) + & & (1.0d0 - o3_ph_frac) * & & rad_source(ilam,sza_ind+1,o3_ph_indp1,p_indm1))) end do if (chem_mecha == 'strat_trop') then ! -------------------------------------------------- ! Interpolate the O(1D) correlation factor in solar ! zenith angle, column ozone, and pressure. ! -------------------------------------------------- do ilam = 1, 5 o1d_a = & & sza_frac * & & (p_frac * & & ((o3_pl_frac) * & & o1d_coef(ilam,sza_ind,o3_pl_ind,p_ind,1) + & & (1.0d0 - o3_pl_frac) * & & o1d_coef(ilam,sza_ind,o3_pl_indp1,p_ind,1)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & o1d_coef(ilam,sza_ind,o3_ph_ind,p_indm1,1) + & & (1.0d0 - o3_ph_frac) * & & o1d_coef(ilam,sza_ind,o3_ph_indp1,p_indm1,1))) + & & (1.0d0 - sza_frac) * & & (p_frac * & & ((o3_pl_frac) * & & o1d_coef(ilam,sza_ind+1,o3_pl_ind,p_ind,1) + & & (1.0d0 - o3_pl_frac) * & & o1d_coef(ilam,sza_ind+1,o3_pl_indp1,p_ind,1)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & o1d_coef(ilam,sza_ind+1,o3_ph_ind,p_indm1,1) + & & (1.0d0 - o3_ph_frac) * & & o1d_coef(ilam,sza_ind+1,o3_ph_indp1,p_indm1,1))) o1d_b = & & sza_frac * & & (p_frac * & & ((o3_pl_frac) * & & o1d_coef(ilam,sza_ind,o3_pl_ind,p_ind,2) + & & (1.0d0 - o3_pl_frac) * & & o1d_coef(ilam,sza_ind,o3_pl_indp1,p_ind,2)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & o1d_coef(ilam,sza_ind,o3_ph_ind,p_indm1,2) + & & (1.0d0 - o3_ph_frac) * & & o1d_coef(ilam,sza_ind,o3_ph_indp1,p_indm1,2))) + & & (1.0d0 - sza_frac) * & & (p_frac * & & ((o3_pl_frac) * & & o1d_coef(ilam,sza_ind+1,o3_pl_ind,p_ind,2) + & & (1.0d0 - o3_pl_frac) * & & o1d_coef(ilam,sza_ind+1,o3_pl_indp1,p_ind,2)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & o1d_coef(ilam,sza_ind+1,o3_ph_ind,p_indm1,2) + & & (1.0d0 - o3_ph_frac) * & & o1d_coef(ilam,sza_ind+1,o3_ph_indp1,p_indm1,2))) o1d_T(ilam) = o1d_a + (kel(il,ij,ik) - 298.0d0) * o1d_b end do end if ! ------------------------------------------- ! Interpolate the O2 photolysis rate in solar ! zenith angle, column ozone, and pressure. ! ------------------------------------------- !... weighted by solar_cycle in bin 3 of wavelength bins (primarily 185-220 nm) if (num_qj_o2 /= 0) then qjgmi(il,ij,ik,num_qj_o2) = & & sza_frac * s_cycle(3,iscyr) * & & (p_frac * & & ((o3_pl_frac) * & & o2_qj(sza_ind,o3_pl_ind,p_ind) + & & (1.0d0 - o3_pl_frac) * & & o2_qj(sza_ind,o3_pl_indp1,p_ind)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & o2_qj(sza_ind,o3_ph_ind,p_indm1) + & & (1.0d0 - o3_ph_frac) * & & o2_qj(sza_ind,o3_ph_indp1,p_indm1))) + & & (1.0d0 - sza_frac) * & & (p_frac * & & ((o3_pl_frac) * & & o2_qj(sza_ind+1,o3_pl_ind,p_ind) + & & (1.0d0 - o3_pl_frac) * & & o2_qj(sza_ind+1,o3_pl_indp1,p_ind)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & o2_qj(sza_ind+1,o3_ph_ind,p_indm1) + & & (1.0d0 - o3_ph_frac) * & & o2_qj(sza_ind+1,o3_ph_indp1,p_indm1))) end if ! ------------------------------------------- ! Interpolate the NO photolysis rate in solar ! zenith angle, column ozone, and pressure. ! ------------------------------------------- !... weighted by solar_cycle in bin 6 of wavelength bins (primarily 190.9 and !... 182.7 nm) in the middle atmosphere, best fit to average of bin 5 and 10. if (num_qj_no /= 0) then qjgmi(il,ij,ik,num_qj_no) = & & sza_frac * s_cycle(6,iscyr) * & & (p_frac * & & ((o3_pl_frac) * & & no_qj(sza_ind,o3_pl_ind,p_ind) + & & (1.0d0 - o3_pl_frac) * & & no_qj(sza_ind,o3_pl_indp1,p_ind)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & no_qj(sza_ind,o3_ph_ind,p_indm1) + & & (1.0d0 - o3_ph_frac) * & & no_qj(sza_ind, o3_ph_indp1,p_indm1))) + & & (1.0d0 - sza_frac) * & & (p_frac * & & ((o3_pl_frac) * & & no_qj(sza_ind+1,o3_pl_ind,p_ind) + & & (1.0d0 - o3_pl_frac) * & & no_qj(sza_ind+1,o3_pl_indp1,p_ind)) + & & (1.0d0 - p_frac) * & & ((o3_ph_frac) * & & no_qj(sza_ind+1,o3_ph_ind,p_indm1) + & & (1.0d0 - o3_ph_frac) * & & no_qj(sza_ind+1,o3_ph_indp1,p_indm1))) end if ! ------------------------------------------------------ ! Calculate the temperature index into the cross section ! data which lists coss sections for temperatures from ! 150 to 349 degrees K. Make sure the index is a value ! between 1 and 200. ! ------------------------------------------------------ t_index = kel(il,ij,ik) - 148.5d0 t_index = Min (200, Max (t_index, 1)) ! ------------------------------------------------------------------ ! For each species, integrate over the wavelengths to ! calculate a qj. ! ! There are special cases for: ! oxygen : photolytic rxn, O2 + hv = O + O ! nitric oxide : photolytic rxn, NO + hv = N + O ! formaldehyde : photolytic rxn, CH2O + hv = CO + H2 ! acetone : photolytic rxn, ACET + hv = MCO3 + MO2 ! methyl glyoxal : photolytic rxn, MGLY + hv = MCO3 + HO2 + CO ! hydroxy acetone : photolytic rxn, HACN + hv = MCO3 + HCHO + HO2 ! ------------------------------------------------------------------ do iq = 1, num_qjs if ((iq /= num_qj_o2) .and. (iq /= num_qj_no)) then total = 0.0d0 do ilam = 1, NUMLAM total = total + & & (rsf(ilam) * s_cycle(ilam,iscyr) * & & cross_section(ilam,t_index,iq)) end do qjgmi(il,ij,ik,iq) = total end if end do if (chem_mecha == 'strat_trop') then ! -------------------------------------------------- ! Nudge O3 photolysis to represent higher resolution ! around the O(1D) falloff region (308-328 nm). ! -------------------------------------------------- qj_adjust = sum( rsf(49:53) * s_cycle(49:53,iscyr) * & & cross_section(49:53,t_index,3) * & & (o1d_T(:) - 1.0d0) & & ,DIM = 1) qjgmi(il,ij,ik,2) = qjgmi(il,ij,ik,2) - qj_adjust qjgmi(il,ij,ik,3) = qjgmi(il,ij,ik,3) + qj_adjust end if ! -------------------------------------------------- ! Patch up the pressure dependent quantum yields for ! formaldehyde (CH2O) photolytic reaction. ! -------------------------------------------------- if (num_qj_ch2o /= 0) then do ilam = 55, 59 qjgmi(il,ij,ik,num_qj_ch2o) = & & qjgmi(il,ij,ik,num_qj_ch2o) - & & ((rsf(ilam) * s_cycle(ilam,iscyr) * & & cross_section(ilam,t_index,num_qj_ch2o)) * & & (1.0d0 - CH2O_CONST_1(ilam) / & & (1.0d0 + (press3c(il,ij,ik) / 1013.25d0) * & & CH2O_CONST_2(ilam)))) end do end if ! ------------------------------------------------------------- ! Patch up the pressure dependent quantum yields for ! acetone (ACET) or hydroxy acetone (HACN) photolytic reaction. ! ------------------------------------------------------------- if (num_qj_acet /= 0) then do ilam = 47, 58 qjgmi(il,ij,ik,num_qj_acet) = & & qjgmi(il,ij,ik,num_qj_acet) - & & ((rsf(ilam) * s_cycle(ilam,iscyr) * & & cross_section(ilam,t_index,num_qj_acet)) * & & (1.0d0 - & & (1.0d0 / & & (ACET_CONST_1(ilam) + & & ACET_CONST_2(ilam) * press3c(il,ij,ik) / & & (kel(il,ij,ik) * BOLTZMN_E * BPMB))))) end do end if if (num_qj_hacn /= 0) then do ilam = 47, 58 qjgmi(il,ij,ik,num_qj_hacn) = & & qjgmi(il,ij,ik,num_qj_hacn) - & & ((rsf(ilam) * s_cycle(ilam,iscyr) * & & cross_section(ilam,t_index,num_qj_hacn)) * & & (1.0d0 - & & (1.0d0 / & & (ACET_CONST_1(ilam) + & & ACET_CONST_2(ilam) * press3c(il,ij,ik) / & & (kel(il,ij,ik) * BOLTZMN_E * BPMB))))) end do end if ! -------------------------------------------------- ! Patch up the pressure dependent quantum yields for ! methyl glyoxal (MGLY) photolytic reaction. ! -------------------------------------------------- if (num_qj_mgly /= 0) then do ilam = 53, 76 qjgmi(il,ij,ik,num_qj_mgly) = & & qjgmi(il,ij,ik,num_qj_mgly) - & & ((rsf(ilam) * s_cycle(ilam,iscyr) * & & cross_section(ilam,t_index,num_qj_mgly)) * & & (1.0d0 - & & (1.0d0 / & & (MGLY_CONST_1(ilam) + & & MGLY_CONST_2(ilam) * press3c(il,ij,ik) / & & (kel(il,ij,ik) * BOLTZMN_E * BPMB))))) end do end if ! ------------------------------------------------ ! Add in the bottom half of this box to the column ! ozone calculation. ! ------------------------------------------------ if (phot_opt /= 5) then loc_col_o3 = loc_col_o3 + half_the_box end if ! ============= end do ikloop ! ============= ! ============= end do ijloop ! ============= ! ============= end do illoop ! ============= ! ----------------------------------------------------------------------- ! Check to see if cloud fractions should be used to attenuate the ! photolysis rate constants. If so, calculate a clear sky fraction (csf). ! ----------------------------------------------------------------------- if (.not. do_clear_sky) then do ik = k1, k2 do iq = 1, num_qjs qjgmi(:,:,ik,iq) = & & qjgmi(:,:,ik,iq) * & & (0.5d0 * fracCloudCover(:,:) + 0.5d0) end do end do end if return end