! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ! Copyright (c) 2015, Regents of the University of Colorado ! All rights reserved. ! Redistribution and use in source and binary forms, with or without modification, are ! permitted provided that the following conditions are met: ! 1. Redistributions of source code must retain the above copyright notice, this list of ! conditions and the following disclaimer. ! 2. Redistributions in binary form must reproduce the above copyright notice, this list ! of conditions and the following disclaimer in the documentation and/or other ! materials provided with the distribution. ! 3. Neither the name of the copyright holder nor the names of its contributors may be ! used to endorse or promote products derived from this software without specific prior ! written permission. ! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY ! EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ! MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ! THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT ! OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ! INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! History ! May 2009: Robert Pincus - Initial version ! June 2009: Steve Platnick and Robert Pincus - Simple radiative transfer for size ! retrievals ! August 2009: Robert Pincus - Consistency and bug fixes suggested by Rick Hemler (GFDL) ! November 2009: Robert Pincus - Bux fixes and speed-ups after experience with Rick Hemler ! using AM2 (GFDL) ! January 2010: Robert Pincus - Added high, middle, low cloud fractions ! May 2015: Dustin Swales - Modified for COSPv2.0 ! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ! Notes on using the MODIS simulator: ! *) You may provide either layer-by-layer values of optical thickness at 0.67 and 2.1 ! microns, or optical thickness at 0.67 microns and ice- and liquid-water contents ! (in consistent units of your choosing) ! *) Required input also includes the optical thickness and cloud top pressure ! derived from the ISCCP simulator run with parameter top_height = 1. ! *) Cloud particle sizes are specified as radii, measured in meters, though within the ! module we use units of microns. Where particle sizes are outside the bounds used in ! the MODIS retrieval libraries (parameters re_water_min, re_ice_min, etc.) the ! simulator returns missing values (re_fill) ! When error conditions are encountered this code calls the function complain_and_die, ! supplied at the bottom of this module. Users probably want to replace this with ! something more graceful. module mod_modis_sim USE MOD_COSP_CONFIG, only: R_UNDEF,modis_histTau,modis_histPres,numMODISTauBins, & numMODISPresBins,numMODISReffIceBins,numMODISReffLiqBins, & modis_histReffIce,modis_histReffLiq USE COSP_KINDS, ONLY: wp use MOD_COSP_STATS, ONLY: hist2D implicit none ! ########################################################################## ! Retrieval parameters integer, parameter :: & num_trial_res = 15 ! Increase to make the linear pseudo-retrieval of size more accurate real(wp) :: & min_OpticalThickness, & ! Minimum detectable optical thickness CO2Slicing_PressureLimit, & ! Cloud with higher pressures use thermal methods, units Pa CO2Slicing_TauLimit, & ! How deep into the cloud does CO2 slicing see? phase_TauLimit, & ! How deep into the cloud does the phase detection see? size_TauLimit, & ! Depth of the re retreivals phaseDiscrimination_Threshold, & ! What fraction of total extincton needs to be in a single ! category to make phase discrim. work? re_fill, & ! re_water_min, & ! Minimum effective radius (liquid) re_water_max, & ! Maximum effective radius (liquid) re_ice_min, & ! Minimum effective radius (ice) re_ice_max, & ! Minimum effective radius (ice) highCloudPressureLimit, & ! High cloud pressure limit (Pa) lowCloudPressureLimit ! Low cloud pressure limit (Pa) integer :: & phaseIsNone, & ! phaseIsLiquid, & ! phaseIsIce, & ! phaseIsUndetermined ! real(wp),dimension(num_trial_res) :: & trial_re_w, & ! Near-IR optical params vs size for retrieval scheme (liquid) trial_re_i ! Near-IR optical params vs size for retrieval scheme (ice) real(wp),dimension(num_trial_res) :: & g_w, & ! Assymettry parameter for size retrieval (liquid) g_i, & ! Assymettry parameter for size retrieval (ice) w0_w, & ! Single-scattering albedo for size retrieval (liquid) w0_i ! Single-scattering albedo for size retrieval (ice) ! Algorithmic parameters real(wp),parameter :: & ice_density = 0.93_wp ! Liquid density is 1. contains ! ######################################################################################## ! MODIS simulator using specified liquid and ice optical thickness in each layer ! Note: this simulator operates on all points; to match MODIS itself night-time ! points should be excluded ! Note: the simulator requires as input the optical thickness and cloud top pressure ! derived from the ISCCP simulator run with parameter top_height = 1. ! If cloud top pressure is higher than about 700 mb, MODIS can't use CO2 slicing ! and reverts to a thermal algorithm much like ISCCP's. Rather than replicate that ! alogrithm in this simulator we simply report the values from the ISCCP simulator. ! ######################################################################################## subroutine modis_subcolumn(nSubCols, nLevels, pressureLevels, optical_thickness, & tauLiquidFraction, g, w0,isccpCloudTopPressure, & retrievedPhase, retrievedCloudTopPressure, & retrievedTau, retrievedSize) ! INPUTS integer,intent(in) :: & nSubCols, & ! Number of subcolumns nLevels ! Number of levels real(wp),dimension(nLevels+1),intent(in) :: & pressureLevels ! Gridmean pressure at layer edges (Pa) real(wp),dimension(nSubCols,nLevels),intent(in) :: & optical_thickness, & ! Subcolumn optical thickness @ 0.67 microns. tauLiquidFraction, & ! Liquid water fraction g, & ! Subcolumn assymetry parameter w0 ! Subcolumn single-scattering albedo real(wp),dimension(nSubCols),intent(in) :: & isccpCloudTopPressure ! ISCCP retrieved cloud top pressure (Pa) ! OUTPUTS integer, dimension(nSubCols), intent(inout) :: & retrievedPhase ! MODIS retrieved phase (liquid/ice/other) real(wp),dimension(nSubCols), intent(inout) :: & retrievedCloudTopPressure, & ! MODIS retrieved CTP (Pa) retrievedTau, & ! MODIS retrieved optical depth (unitless) retrievedSize ! MODIS retrieved particle size (microns) ! LOCAL VARIABLES logical, dimension(nSubCols) :: & cloudMask real(wp) :: & integratedLiquidFraction, & obs_Refl_nir real(wp),dimension(num_trial_res) :: & predicted_Refl_nir integer :: & i ! ######################################################################################## ! Optical depth retrieval ! This is simply a sum over the optical thickness in each layer. ! It should agree with the ISCCP values after min values have been excluded. ! ######################################################################################## retrievedTau(1:nSubCols) = sum(optical_thickness(1:nSubCols,1:nLevels), dim = 2) ! ######################################################################################## ! Cloud detection ! does optical thickness exceed detection threshold? ! ######################################################################################## cloudMask = retrievedTau(1:nSubCols) >= min_OpticalThickness do i = 1, nSubCols if(cloudMask(i)) then ! ################################################################################## ! Cloud top pressure determination ! MODIS uses CO2 slicing for clouds with tops above about 700 mb and thermal ! methods for clouds lower than that. For CO2 slicing we report the optical-depth ! weighted pressure, integrating to a specified optical depth. ! This assumes linear variation in p between levels. Linear in ln(p) is probably ! better, though we'd need to deal with the lowest pressure gracefully. ! ################################################################################## retrievedCloudTopPressure(i) = cloud_top_pressure(nLevels,(/ 0._wp, optical_thickness(i,1:nLevels) /), & pressureLevels(1:nLevels),CO2Slicing_TauLimit) ! ################################################################################## ! Phase determination ! Determine fraction of total tau that's liquid when ice and water contribute about ! equally to the extinction we can't tell what the phase is. ! ################################################################################## integratedLiquidFraction = weight_by_extinction(nLevels,optical_thickness(i,1:nLevels), & tauLiquidFraction(i, 1:nLevels), & phase_TauLimit) if(integratedLiquidFraction >= phaseDiscrimination_Threshold) then retrievedPhase(i) = phaseIsLiquid else if (integratedLiquidFraction <= 1._wp- phaseDiscrimination_Threshold) then retrievedPhase(i) = phaseIsIce else retrievedPhase(i) = phaseIsUndetermined end if ! ################################################################################## ! Size determination ! ################################################################################## ! Compute observed reflectance obs_Refl_nir = compute_toa_reflectace(nLevels,optical_thickness(i,1:nLevels), g(i,1:nLevels), w0(i,1:nLevels)) ! Compute predicted reflectance if(any(retrievedPhase(i) == (/ phaseIsLiquid, phaseIsUndetermined, phaseIsIce /))) then if (retrievedPhase(i) == phaseIsLiquid .OR. retrievedPhase(i) == phaseIsUndetermined) then predicted_Refl_nir(1:num_trial_res) = two_stream_reflectance(retrievedTau(i), & g_w(1:num_trial_res), w0_w(1:num_trial_res)) retrievedSize(i) = 1.0e-06_wp*interpolate_to_min(trial_re_w(1:num_trial_res), & predicted_Refl_nir(1:num_trial_res), obs_Refl_nir) else predicted_Refl_nir(1:num_trial_res) = two_stream_reflectance(retrievedTau(i), & g_i(1:num_trial_res), w0_i(1:num_trial_res)) retrievedSize(i) = 1.0e-06_wp*interpolate_to_min(trial_re_i(1:num_trial_res), & predicted_Refl_nir(1:num_trial_res), obs_Refl_nir) endif else retrievedSize(i) = re_fill endif else ! Values when we don't think there's a cloud. retrievedCloudTopPressure(i) = R_UNDEF retrievedPhase(i) = phaseIsNone retrievedSize(i) = R_UNDEF retrievedTau(i) = R_UNDEF end if end do where((retrievedSize(1:nSubCols) < 0.).and.(retrievedSize(1:nSubCols) /= R_UNDEF)) & retrievedSize(1:nSubCols) = 1.0e-06_wp*re_fill ! We use the ISCCP-derived CTP for low clouds, since the ISCCP simulator ICARUS ! mimics what MODIS does to first order. ! Of course, ISCCP cloud top pressures are in mb. where(cloudMask(1:nSubCols) .and. retrievedCloudTopPressure(1:nSubCols) > CO2Slicing_PressureLimit) & retrievedCloudTopPressure(1:nSubCols) = isccpCloudTopPressure! * 100._wp end subroutine modis_subcolumn ! ######################################################################################## subroutine modis_column(nPoints,nSubCols,phase, cloud_top_pressure, optical_thickness, particle_size, & Cloud_Fraction_Total_Mean, Cloud_Fraction_Water_Mean, Cloud_Fraction_Ice_Mean, & Cloud_Fraction_High_Mean, Cloud_Fraction_Mid_Mean, Cloud_Fraction_Low_Mean, & Optical_Thickness_Total_Mean, Optical_Thickness_Water_Mean, Optical_Thickness_Ice_Mean, & Optical_Thickness_Total_MeanLog10, Optical_Thickness_Water_MeanLog10, Optical_Thickness_Ice_MeanLog10,& Cloud_Particle_Size_Water_Mean, Cloud_Particle_Size_Ice_Mean, Cloud_Top_Pressure_Total_Mean, & Liquid_Water_Path_Mean, Ice_Water_Path_Mean, & Optical_Thickness_vs_Cloud_Top_Pressure,Optical_Thickness_vs_ReffIce,Optical_Thickness_vs_ReffLiq) ! INPUTS integer,intent(in) :: & nPoints, & ! Number of horizontal gridpoints nSubCols ! Number of subcolumns integer,intent(in), dimension(nPoints, nSubCols) :: & phase real(wp),intent(in),dimension(nPoints, nSubCols) :: & cloud_top_pressure, & optical_thickness, & particle_size ! OUTPUTS real(wp),intent(inout),dimension(nPoints) :: & ! Cloud_Fraction_Total_Mean, & ! Cloud_Fraction_Water_Mean, & ! Cloud_Fraction_Ice_Mean, & ! Cloud_Fraction_High_Mean, & ! Cloud_Fraction_Mid_Mean, & ! Cloud_Fraction_Low_Mean, & ! Optical_Thickness_Total_Mean, & ! Optical_Thickness_Water_Mean, & ! Optical_Thickness_Ice_Mean, & ! Optical_Thickness_Total_MeanLog10, & ! Optical_Thickness_Water_MeanLog10, & ! Optical_Thickness_Ice_MeanLog10, & ! Cloud_Particle_Size_Water_Mean, & ! Cloud_Particle_Size_Ice_Mean, & ! Cloud_Top_Pressure_Total_Mean, & ! Liquid_Water_Path_Mean, & ! Ice_Water_Path_Mean ! real(wp),intent(inout),dimension(nPoints,numMODISTauBins,numMODISPresBins) :: & Optical_Thickness_vs_Cloud_Top_Pressure real(wp),intent(inout),dimension(nPoints,numMODISTauBins,numMODISReffIceBins) :: & Optical_Thickness_vs_ReffIce real(wp),intent(inout),dimension(nPoints,numMODISTauBins,numMODISReffLiqBins) :: & Optical_Thickness_vs_ReffLiq ! LOCAL VARIABLES real(wp), parameter :: & LWP_conversion = 2._wp/3._wp * 1000._wp ! MKS units integer :: j logical, dimension(nPoints,nSubCols) :: & cloudMask, & waterCloudMask, & iceCloudMask, & validRetrievalMask real(wp),dimension(nPoints,nSubCols) :: & tauWRK,ctpWRK,reffIceWRK,reffLiqWRK ! ######################################################################################## ! Include only those pixels with successful retrievals in the statistics ! ######################################################################################## validRetrievalMask(1:nPoints,1:nSubCols) = particle_size(1:nPoints,1:nSubCols) > 0. cloudMask(1:nPoints,1:nSubCols) = phase(1:nPoints,1:nSubCols) /= phaseIsNone .and. & validRetrievalMask(1:nPoints,1:nSubCols) waterCloudMask(1:nPoints,1:nSubCols) = phase(1:nPoints,1:nSubCols) == phaseIsLiquid .and. & validRetrievalMask(1:nPoints,1:nSubCols) iceCloudMask(1:nPoints,1:nSubCols) = phase(1:nPoints,1:nSubCols) == phaseIsIce .and. & validRetrievalMask(1:nPoints,1:nSubCols) ! ######################################################################################## ! Use these as pixel counts at first ! ######################################################################################## Cloud_Fraction_Total_Mean(1:nPoints) = real(count(cloudMask, dim = 2)) Cloud_Fraction_Water_Mean(1:nPoints) = real(count(waterCloudMask, dim = 2)) Cloud_Fraction_Ice_Mean(1:nPoints) = real(count(iceCloudMask, dim = 2)) Cloud_Fraction_High_Mean(1:nPoints) = real(count(cloudMask .and. cloud_top_pressure <= & highCloudPressureLimit, dim = 2)) Cloud_Fraction_Low_Mean(1:nPoints) = real(count(cloudMask .and. cloud_top_pressure > & lowCloudPressureLimit, dim = 2)) Cloud_Fraction_Mid_Mean(1:nPoints) = Cloud_Fraction_Total_Mean(1:nPoints) - Cloud_Fraction_High_Mean(1:nPoints)& - Cloud_Fraction_Low_Mean(1:nPoints) ! ######################################################################################## ! Compute column amounts. ! ######################################################################################## where(Cloud_Fraction_Total_Mean(1:nPoints) > 0) Optical_Thickness_Total_Mean(1:nPoints) = sum(optical_thickness, mask = cloudMask, dim = 2) / & Cloud_Fraction_Total_Mean(1:nPoints) Optical_Thickness_Total_MeanLog10(1:nPoints) = sum(log10(abs(optical_thickness)), mask = cloudMask, & dim = 2) / Cloud_Fraction_Total_Mean(1:nPoints) elsewhere Optical_Thickness_Total_Mean = R_UNDEF Optical_Thickness_Total_MeanLog10 = R_UNDEF endwhere where(Cloud_Fraction_Water_Mean(1:nPoints) > 0) Optical_Thickness_Water_Mean(1:nPoints) = sum(optical_thickness, mask = waterCloudMask, dim = 2) / & Cloud_Fraction_Water_Mean(1:nPoints) Liquid_Water_Path_Mean(1:nPoints) = LWP_conversion*sum(particle_size*optical_thickness, & mask=waterCloudMask,dim=2)/Cloud_Fraction_Water_Mean(1:nPoints) Optical_Thickness_Water_MeanLog10(1:nPoints) = sum(log10(abs(optical_thickness)), mask = waterCloudMask,& dim = 2) / Cloud_Fraction_Water_Mean(1:nPoints) Cloud_Particle_Size_Water_Mean(1:nPoints) = sum(particle_size, mask = waterCloudMask, dim = 2) / & Cloud_Fraction_Water_Mean(1:nPoints) elsewhere Optical_Thickness_Water_Mean = R_UNDEF Optical_Thickness_Water_MeanLog10 = R_UNDEF Cloud_Particle_Size_Water_Mean = R_UNDEF Liquid_Water_Path_Mean = R_UNDEF endwhere where(Cloud_Fraction_Ice_Mean(1:nPoints) > 0) Optical_Thickness_Ice_Mean(1:nPoints) = sum(optical_thickness, mask = iceCloudMask, dim = 2) / & Cloud_Fraction_Ice_Mean(1:nPoints) Ice_Water_Path_Mean(1:nPoints) = LWP_conversion * ice_density*sum(particle_size*optical_thickness,& mask=iceCloudMask,dim = 2) /Cloud_Fraction_Ice_Mean(1:nPoints) Optical_Thickness_Ice_MeanLog10(1:nPoints) = sum(log10(abs(optical_thickness)), mask = iceCloudMask,& dim = 2) / Cloud_Fraction_Ice_Mean(1:nPoints) Cloud_Particle_Size_Ice_Mean(1:nPoints) = sum(particle_size, mask = iceCloudMask, dim = 2) / & Cloud_Fraction_Ice_Mean(1:nPoints) elsewhere Optical_Thickness_Ice_Mean = R_UNDEF Optical_Thickness_Ice_MeanLog10 = R_UNDEF Cloud_Particle_Size_Ice_Mean = R_UNDEF Ice_Water_Path_Mean = R_UNDEF endwhere Cloud_Top_Pressure_Total_Mean = sum(cloud_top_pressure, mask = cloudMask, dim = 2) / & max(1, count(cloudMask, dim = 2)) ! ######################################################################################## ! Normalize pixel counts to fraction. ! ######################################################################################## Cloud_Fraction_High_Mean(1:nPoints) = Cloud_Fraction_High_Mean(1:nPoints) /nSubcols Cloud_Fraction_Mid_Mean(1:nPoints) = Cloud_Fraction_Mid_Mean(1:nPoints) /nSubcols Cloud_Fraction_Low_Mean(1:nPoints) = Cloud_Fraction_Low_Mean(1:nPoints) /nSubcols Cloud_Fraction_Total_Mean(1:nPoints) = Cloud_Fraction_Total_Mean(1:nPoints) /nSubcols Cloud_Fraction_Ice_Mean(1:nPoints) = Cloud_Fraction_Ice_Mean(1:nPoints) /nSubcols Cloud_Fraction_Water_Mean(1:nPoints) = Cloud_Fraction_Water_Mean(1:nPoints) /nSubcols ! ######################################################################################## ! Joint histograms ! ######################################################################################## ! Loop over all points tauWRK(1:nPoints,1:nSubCols) = optical_thickness(1:nPoints,1:nSubCols) ctpWRK(1:nPoints,1:nSubCols) = cloud_top_pressure(1:nPoints,1:nSubCols) reffIceWRK(1:nPoints,1:nSubCols) = merge(particle_size,R_UNDEF,iceCloudMask) reffLiqWRK(1:nPoints,1:nSubCols) = merge(particle_size,R_UNDEF,waterCloudMask) do j=1,nPoints ! Fill clear and optically thin subcolumns with fill where(.not. cloudMask(j,1:nSubCols)) tauWRK(j,1:nSubCols) = -999._wp ctpWRK(j,1:nSubCols) = -999._wp endwhere ! Joint histogram of tau/CTP call hist2D(tauWRK(j,1:nSubCols),ctpWRK(j,1:nSubCols),nSubCols,& modis_histTau,numMODISTauBins,& modis_histPres,numMODISPresBins,& Optical_Thickness_vs_Cloud_Top_Pressure(j,1:numMODISTauBins,1:numMODISPresBins)) ! Joint histogram of tau/ReffICE call hist2D(tauWRK(j,1:nSubCols),reffIceWrk(j,1:nSubCols),nSubCols, & modis_histTau,numMODISTauBins,modis_histReffIce, & numMODISReffIceBins, Optical_Thickness_vs_ReffIce(j,1:numMODISTauBins,1:numMODISReffIceBins)) ! Joint histogram of tau/ReffLIQ call hist2D(tauWRK(j,1:nSubCols),reffLiqWrk(j,1:nSubCols),nSubCols, & modis_histTau,numMODISTauBins,modis_histReffLiq, & numMODISReffLiqBins, Optical_Thickness_vs_ReffLiq(j,1:numMODISTauBins,1:numMODISReffLiqBins)) enddo Optical_Thickness_vs_Cloud_Top_Pressure(1:nPoints,1:numMODISTauBins,1:numMODISPresBins) = & Optical_Thickness_vs_Cloud_Top_Pressure(1:nPoints,1:numMODISTauBins,1:numMODISPresBins)/nSubCols Optical_Thickness_vs_ReffIce(1:nPoints,1:numMODISTauBins,1:numMODISReffIceBins) = & Optical_Thickness_vs_ReffIce(1:nPoints,1:numMODISTauBins,1:numMODISReffIceBins)/nSubCols Optical_Thickness_vs_ReffLiq(1:nPoints,1:numMODISTauBins,1:numMODISReffLiqBins) = & Optical_Thickness_vs_ReffLiq(1:nPoints,1:numMODISTauBins,1:numMODISReffLiqBins)/nSubCols ! Unit conversion where(Optical_Thickness_vs_Cloud_Top_Pressure /= R_UNDEF) & Optical_Thickness_vs_Cloud_Top_Pressure = Optical_Thickness_vs_Cloud_Top_Pressure*100._wp where(Optical_Thickness_vs_ReffIce /= R_UNDEF) Optical_Thickness_vs_ReffIce = Optical_Thickness_vs_ReffIce*100._wp where(Optical_Thickness_vs_ReffLiq /= R_UNDEF) Optical_Thickness_vs_ReffLiq = Optical_Thickness_vs_ReffLiq*100._wp where(Cloud_Fraction_Total_Mean /= R_UNDEF) Cloud_Fraction_Total_Mean = Cloud_Fraction_Total_Mean*100._wp where(Cloud_Fraction_Water_Mean /= R_UNDEF) Cloud_Fraction_Water_Mean = Cloud_Fraction_Water_Mean*100._wp where(Cloud_Fraction_Ice_Mean /= R_UNDEF) Cloud_Fraction_Ice_Mean = Cloud_Fraction_Ice_Mean*100._wp where(Cloud_Fraction_High_Mean /= R_UNDEF) Cloud_Fraction_High_Mean = Cloud_Fraction_High_Mean*100._wp where(Cloud_Fraction_Mid_Mean /= R_UNDEF) Cloud_Fraction_Mid_Mean = Cloud_Fraction_Mid_Mean*100._wp where(Cloud_Fraction_Low_Mean /= R_UNDEF) Cloud_Fraction_Low_Mean = Cloud_Fraction_Low_Mean*100._wp end subroutine modis_column ! ######################################################################################## function cloud_top_pressure(nLevels,tauIncrement, pressure, tauLimit) ! INPUTS integer, intent(in) :: nLevels real(wp),intent(in),dimension(nLevels) :: tauIncrement, pressure real(wp),intent(in) :: tauLimit ! OUTPUTS real(wp) :: cloud_top_pressure ! LOCAL VARIABLES real(wp) :: deltaX, totalTau, totalProduct integer :: i ! Find the extinction-weighted pressure. Assume that pressure varies linearly between ! layers and use the trapezoidal rule. totalTau = 0._wp; totalProduct = 0._wp do i = 2, size(tauIncrement) if(totalTau + tauIncrement(i) > tauLimit) then deltaX = tauLimit - totalTau totalTau = totalTau + deltaX ! Result for trapezoidal rule when you take less than a full step ! tauIncrement is a layer-integrated value totalProduct = totalProduct & + pressure(i-1) * deltaX & + (pressure(i) - pressure(i-1)) * deltaX**2/(2._wp * tauIncrement(i)) else totalTau = totalTau + tauIncrement(i) totalProduct = totalProduct + tauIncrement(i) * (pressure(i) + pressure(i-1)) / 2._wp end if if(totalTau >= tauLimit) exit end do if (totalTau > 0._wp) then cloud_top_pressure = totalProduct/totalTau else cloud_top_pressure = 0._wp endif end function cloud_top_pressure ! ######################################################################################## function weight_by_extinction(nLevels,tauIncrement, f, tauLimit) ! INPUTS integer, intent(in) :: nLevels real(wp),intent(in),dimension(nLevels) :: tauIncrement, f real(wp),intent(in) :: tauLimit ! OUTPUTS real(wp) :: weight_by_extinction ! LOCAL VARIABLES real(wp) :: deltaX, totalTau, totalProduct integer :: i ! Find the extinction-weighted value of f(tau), assuming constant f within each layer totalTau = 0._wp; totalProduct = 0._wp do i = 1, size(tauIncrement) if(totalTau + tauIncrement(i) > tauLimit) then deltaX = tauLimit - totalTau totalTau = totalTau + deltaX totalProduct = totalProduct + deltaX * f(i) else totalTau = totalTau + tauIncrement(i) totalProduct = totalProduct + tauIncrement(i) * f(i) end if if(totalTau >= tauLimit) exit end do if (totalTau > 0._wp) then weight_by_extinction = totalProduct/totalTau else weight_by_extinction = 0._wp endif end function weight_by_extinction ! ######################################################################################## pure function interpolate_to_min(x, y, yobs) ! INPUTS real(wp),intent(in),dimension(num_trial_res) :: x, y real(wp),intent(in) :: yobs ! OUTPUTS real(wp) :: interpolate_to_min ! LOCAL VARIABLES real(wp), dimension(num_trial_res) :: diff integer :: nPoints, minDiffLoc, lowerBound, upperBound ! Given a set of values of y as y(x), find the value of x that minimizes abs(y - yobs) ! y must be monotonic in x nPoints = size(y) diff(1:num_trial_res) = y(1:num_trial_res) - yobs minDiffLoc = minloc(abs(diff), dim = 1) if(minDiffLoc == 1) then lowerBound = minDiffLoc upperBound = minDiffLoc + 1 else if(minDiffLoc == nPoints) then lowerBound = minDiffLoc - 1 upperBound = minDiffLoc else if(diff(minDiffLoc-1) * diff(minDiffLoc) < 0) then lowerBound = minDiffLoc-1 upperBound = minDiffLoc else lowerBound = minDiffLoc upperBound = minDiffLoc + 1 end if end if if(diff(lowerBound) * diff(upperBound) < 0) then ! Interpolate the root position linearly if we bracket the root interpolate_to_min = x(upperBound) - & diff(upperBound) * (x(upperBound) - x(lowerBound)) / (diff(upperBound) - diff(lowerBound)) else interpolate_to_min = re_fill end if end function interpolate_to_min ! ######################################################################################## ! Optical properties ! ######################################################################################## elemental function get_g_nir_old (phase, re) ! Polynomial fit for asummetry parameter g in MODIS band 7 (near IR) as a function ! of size for ice and water ! Fits from Steve Platnick ! INPUTS integer, intent(in) :: phase real(wp),intent(in) :: re ! OUTPUTS real(wp) :: get_g_nir_old ! LOCAL VARIABLES(parameters) real(wp), dimension(3), parameter :: & ice_coefficients = (/ 0.7432, 4.5563e-3, -2.8697e-5 /), & small_water_coefficients = (/ 0.8027, -1.0496e-2, 1.7071e-3 /), & big_water_coefficients = (/ 0.7931, 5.3087e-3, -7.4995e-5 /) ! approx. fits from MODIS Collection 5 LUT scattering calculations if(phase == phaseIsLiquid) then if(re < 8.) then get_g_nir_old = fit_to_quadratic(re, small_water_coefficients) if(re < re_water_min) get_g_nir_old = fit_to_quadratic(re_water_min, small_water_coefficients) else get_g_nir_old = fit_to_quadratic(re, big_water_coefficients) if(re > re_water_max) get_g_nir_old = fit_to_quadratic(re_water_max, big_water_coefficients) end if else get_g_nir_old = fit_to_quadratic(re, ice_coefficients) if(re < re_ice_min) get_g_nir_old = fit_to_quadratic(re_ice_min, ice_coefficients) if(re > re_ice_max) get_g_nir_old = fit_to_quadratic(re_ice_max, ice_coefficients) end if end function get_g_nir_old ! ######################################################################################## elemental function get_ssa_nir_old (phase, re) ! Polynomial fit for single scattering albedo in MODIS band 7 (near IR) as a function ! of size for ice and water ! Fits from Steve Platnick ! INPUTS integer, intent(in) :: phase real(wp),intent(in) :: re ! OUTPUTS real(wp) :: get_ssa_nir_old ! LOCAL VARIABLES (parameters) real(wp), dimension(4), parameter :: ice_coefficients = (/ 0.9994, -4.5199e-3, 3.9370e-5, -1.5235e-7 /) real(wp), dimension(3), parameter :: water_coefficients = (/ 1.0008, -2.5626e-3, 1.6024e-5 /) ! approx. fits from MODIS Collection 5 LUT scattering calculations if(phase == phaseIsLiquid) then get_ssa_nir_old = fit_to_quadratic(re, water_coefficients) if(re < re_water_min) get_ssa_nir_old = fit_to_quadratic(re_water_min, water_coefficients) if(re > re_water_max) get_ssa_nir_old = fit_to_quadratic(re_water_max, water_coefficients) else get_ssa_nir_old = fit_to_cubic(re, ice_coefficients) if(re < re_ice_min) get_ssa_nir_old = fit_to_cubic(re_ice_min, ice_coefficients) if(re > re_ice_max) get_ssa_nir_old = fit_to_cubic(re_ice_max, ice_coefficients) end if end function get_ssa_nir_old elemental function get_g_nir (phase, re) ! Polynomial fit for asummetry parameter g in MODIS band 7 (near IR) as a function ! of size for ice and water ! Fits from Steve Platnick integer, intent(in) :: phase real(wp), intent(in) :: re real(wp) :: get_g_nir real(wp), dimension(3), parameter :: ice_coefficients = (/ 0.7490, 6.5153e-3, -5.4136e-5 /), & small_water_coefficients = (/ 1.0364, -8.8800e-2, 7.0000e-3 /) real(wp), dimension(4), parameter :: big_water_coefficients = (/ 0.6035, 2.8993e-2, -1.1051e-3, 1.5134e-5 /) ! approx. fits from MODIS Collection 6 LUT scattering calculations for 3.7 µm channel size retrievals if(phase == phaseIsLiquid) then if(re < 7.) then get_g_nir = fit_to_quadratic(re, small_water_coefficients) if(re < re_water_min) get_g_nir = fit_to_quadratic(re_water_min, small_water_coefficients) else get_g_nir = fit_to_cubic(re, big_water_coefficients) if(re > re_water_max) get_g_nir = fit_to_cubic(re_water_max, big_water_coefficients) end if else get_g_nir = fit_to_quadratic(re, ice_coefficients) if(re < re_ice_min) get_g_nir = fit_to_quadratic(re_ice_min, ice_coefficients) if(re > re_ice_max) get_g_nir = fit_to_quadratic(re_ice_max, ice_coefficients) end if end function get_g_nir ! -------------------------------------------- elemental function get_ssa_nir (phase, re) integer, intent(in) :: phase real(wp), intent(in) :: re real(wp) :: get_ssa_nir ! Polynomial fit for single scattering albedo in MODIS band 7 (near IR) as a function ! of size for ice and water ! Fits from Steve Platnick real(wp), dimension(4), parameter :: ice_coefficients = (/ 0.9625, -1.8069e-2, 3.3281e-4,-2.2865e-6/) real(wp), dimension(3), parameter :: water_coefficients = (/ 1.0044, -1.1397e-2, 1.3300e-4 /) ! approx. fits from MODIS Collection 6 LUT scattering calculations if(phase == phaseIsLiquid) then get_ssa_nir = fit_to_quadratic(re, water_coefficients) if(re < re_water_min) get_ssa_nir = fit_to_quadratic(re_water_min, water_coefficients) if(re > re_water_max) get_ssa_nir = fit_to_quadratic(re_water_max, water_coefficients) else get_ssa_nir = fit_to_cubic(re, ice_coefficients) if(re < re_ice_min) get_ssa_nir = fit_to_cubic(re_ice_min, ice_coefficients) if(re > re_ice_max) get_ssa_nir = fit_to_cubic(re_ice_max, ice_coefficients) end if end function get_ssa_nir ! ######################################################################################## pure function fit_to_cubic(x, coefficients) ! INPUTS real(wp), intent(in) :: x real(wp), dimension(4), intent(in) :: coefficients ! OUTPUTS real(wp) :: fit_to_cubic fit_to_cubic = coefficients(1) + x * (coefficients(2) + x * (coefficients(3) + x * coefficients(4))) end function fit_to_cubic ! ######################################################################################## pure function fit_to_quadratic(x, coefficients) ! INPUTS real(wp), intent(in) :: x real(wp), dimension(3), intent(in) :: coefficients ! OUTPUTS real(wp) :: fit_to_quadratic fit_to_quadratic = coefficients(1) + x * (coefficients(2) + x * (coefficients(3))) end function fit_to_quadratic ! ######################################################################################## ! Radiative transfer ! ######################################################################################## pure function compute_toa_reflectace(nLevels,tau, g, w0) ! This wrapper reports reflectance only and strips out non-cloudy elements from the ! calculation ! INPUTS integer,intent(in) :: nLevels real(wp),intent(in),dimension(nLevels) :: tau, g, w0 ! OUTPUTS real(wp) :: compute_toa_reflectace ! LOCAL VARIABLES logical, dimension(nLevels) :: cloudMask integer, dimension(count(tau(1:nLevels) > 0)) :: cloudIndicies real(wp),dimension(count(tau(1:nLevels) > 0)) :: Refl,Trans real(wp) :: Refl_tot, Trans_tot integer :: i cloudMask(1:nLevels) = tau(1:nLevels) > 0. cloudIndicies = pack((/ (i, i = 1, nLevels) /), mask = cloudMask) do i = 1, size(cloudIndicies) call two_stream(tau(cloudIndicies(i)), g(cloudIndicies(i)), w0(cloudIndicies(i)), Refl(i), Trans(i)) end do call adding_doubling(count(tau(1:nLevels) > 0),Refl(:), Trans(:), Refl_tot, Trans_tot) compute_toa_reflectace = Refl_tot end function compute_toa_reflectace ! ######################################################################################## pure subroutine two_stream(tauint, gint, w0int, ref, tra) ! Compute reflectance in a single layer using the two stream approximation ! The code itself is from Lazaros Oreopoulos via Steve Platnick ! INPUTS real(wp), intent(in) :: tauint, gint, w0int ! OUTPUTS real(wp), intent(out) :: ref, tra ! LOCAL VARIABLES ! for delta Eddington code ! xmu, gamma3, and gamma4 only used for collimated beam approximation (i.e., beam=1) integer, parameter :: beam = 2 real(wp),parameter :: xmu = 0.866, minConservativeW0 = 0.9999999 real(wp) :: tau, w0, g, f, gamma1, gamma2, gamma3, gamma4, & rh, a1, a2, rk, r1, r2, r3, r4, r5, t1, t2, t3, t4, t5, beta, e1, e2, ef1, ef2, den, th ! Compute reflectance and transmittance in a single layer using the two stream approximation ! The code itself is from Lazaros Oreopoulos via Steve Platnick f = gint**2 tau = (1._wp - w0int * f) * tauint w0 = (1._wp - f) * w0int / (1._wp - w0int * f) g = (gint - f) / (1._wp - f) ! delta-Eddington (Joseph et al. 1976) gamma1 = (7._wp - w0* (4._wp + 3._wp * g)) / 4._wp gamma2 = -(1._wp - w0* (4._wp - 3._wp * g)) / 4._wp gamma3 = (2._wp - 3._wp*g*xmu) / 4._wp gamma4 = 1._wp - gamma3 if (w0int > minConservativeW0) then ! Conservative scattering if (beam == 1) then rh = (gamma1*tau+(gamma3-gamma1*xmu)*(1-exp(-tau/xmu))) ref = rh / (1._wp + gamma1 * tau) tra = 1._wp - ref else if(beam == 2) then ref = gamma1*tau/(1._wp + gamma1*tau) tra = 1._wp - ref endif else ! Non-conservative scattering a1 = gamma1 * gamma4 + gamma2 * gamma3 a2 = gamma1 * gamma3 + gamma2 * gamma4 rk = sqrt(gamma1**2 - gamma2**2) r1 = (1._wp - rk * xmu) * (a2 + rk * gamma3) r2 = (1._wp + rk * xmu) * (a2 - rk * gamma3) r3 = 2._wp * rk *(gamma3 - a2 * xmu) r4 = (1._wp - (rk * xmu)**2) * (rk + gamma1) r5 = (1._wp - (rk * xmu)**2) * (rk - gamma1) t1 = (1._wp + rk * xmu) * (a1 + rk * gamma4) t2 = (1._wp - rk * xmu) * (a1 - rk * gamma4) t3 = 2._wp * rk * (gamma4 + a1 * xmu) t4 = r4 t5 = r5 beta = -r5 / r4 e1 = min(rk * tau, 500._wp) e2 = min(tau / xmu, 500._wp) if (beam == 1) then den = r4 * exp(e1) + r5 * exp(-e1) ref = w0*(r1*exp(e1)-r2*exp(-e1)-r3*exp(-e2))/den den = t4 * exp(e1) + t5 * exp(-e1) th = exp(-e2) tra = th-th*w0*(t1*exp(e1)-t2*exp(-e1)-t3*exp(e2))/den elseif (beam == 2) then ef1 = exp(-e1) ef2 = exp(-2*e1) ref = (gamma2*(1._wp-ef2))/((rk+gamma1)*(1._wp-beta*ef2)) tra = (2._wp*rk*ef1)/((rk+gamma1)*(1._wp-beta*ef2)) endif end if end subroutine two_stream ! ######################################################################################## elemental function two_stream_reflectance(tauint, gint, w0int) ! Compute reflectance in a single layer using the two stream approximation ! The code itself is from Lazaros Oreopoulos via Steve Platnick ! INPUTS real(wp), intent(in) :: tauint, gint, w0int ! OUTPUTS real(wp) :: two_stream_reflectance ! LOCAL VARIABLES ! for delta Eddington code ! xmu, gamma3, and gamma4 only used for collimated beam approximation (i.e., beam=1) integer, parameter :: beam = 2 real(wp),parameter :: xmu = 0.866, minConservativeW0 = 0.9999999 real(wp) :: tau, w0, g, f, gamma1, gamma2, gamma3, gamma4, & rh, a1, a2, rk, r1, r2, r3, r4, r5, t1, t2, t3, t4, t5, beta, e1, e2, ef1, ef2, den f = gint**2 tau = (1._wp - w0int * f) * tauint w0 = (1._wp - f) * w0int / (1._wp - w0int * f) g = (gint - f) / (1._wp - f) ! delta-Eddington (Joseph et al. 1976) gamma1 = (7._wp - w0* (4._wp + 3._wp * g)) / 4._wp gamma2 = -(1._wp - w0* (4._wp - 3._wp * g)) / 4._wp gamma3 = (2._wp - 3._wp*g*xmu) / 4._wp gamma4 = 1._wp - gamma3 if (w0int > minConservativeW0) then ! Conservative scattering if (beam == 1) then rh = (gamma1*tau+(gamma3-gamma1*xmu)*(1-exp(-tau/xmu))) two_stream_reflectance = rh / (1._wp + gamma1 * tau) elseif (beam == 2) then two_stream_reflectance = gamma1*tau/(1._wp + gamma1*tau) endif else ! ! Non-conservative scattering a1 = gamma1 * gamma4 + gamma2 * gamma3 a2 = gamma1 * gamma3 + gamma2 * gamma4 rk = sqrt(gamma1**2 - gamma2**2) r1 = (1._wp - rk * xmu) * (a2 + rk * gamma3) r2 = (1._wp + rk * xmu) * (a2 - rk * gamma3) r3 = 2._wp * rk *(gamma3 - a2 * xmu) r4 = (1._wp - (rk * xmu)**2) * (rk + gamma1) r5 = (1._wp - (rk * xmu)**2) * (rk - gamma1) t1 = (1._wp + rk * xmu) * (a1 + rk * gamma4) t2 = (1._wp - rk * xmu) * (a1 - rk * gamma4) t3 = 2._wp * rk * (gamma4 + a1 * xmu) t4 = r4 t5 = r5 beta = -r5 / r4 e1 = min(rk * tau, 500._wp) e2 = min(tau / xmu, 500._wp) if (beam == 1) then den = r4 * exp(e1) + r5 * exp(-e1) two_stream_reflectance = w0*(r1*exp(e1)-r2*exp(-e1)-r3*exp(-e2))/den elseif (beam == 2) then ef1 = exp(-e1) ef2 = exp(-2*e1) two_stream_reflectance = (gamma2*(1._wp-ef2))/((rk+gamma1)*(1._wp-beta*ef2)) endif end if end function two_stream_reflectance ! ######################################################################################## pure subroutine adding_doubling (npts,Refl, Tran, Refl_tot, Tran_tot) ! Use adding/doubling formulas to compute total reflectance and transmittance from ! layer values ! INPUTS integer,intent(in) :: npts real(wp),intent(in),dimension(npts) :: Refl,Tran ! OUTPUTS real(wp),intent(out) :: Refl_tot, Tran_tot ! LOCAL VARIABLES integer :: i real(wp), dimension(npts) :: Refl_cumulative, Tran_cumulative Refl_cumulative(1) = Refl(1) Tran_cumulative(1) = Tran(1) do i=2, npts ! place (add) previous combined layer(s) reflectance on top of layer i, w/black surface (or ignoring surface): Refl_cumulative(i) = Refl_cumulative(i-1) + Refl(i)*(Tran_cumulative(i-1)**2)/(1._wp - Refl_cumulative(i-1) * Refl(i)) Tran_cumulative(i) = (Tran_cumulative(i-1)*Tran(i)) / (1._wp - Refl_cumulative(i-1) * Refl(i)) end do Refl_tot = Refl_cumulative(size(Refl)) Tran_tot = Tran_cumulative(size(Refl)) end subroutine adding_doubling end module mod_modis_sim