source: LMDZ6/trunk/libf/phylmd/ecrad/radiation/radiation_general_cloud_optics_data.F90 @ 4946

Last change on this file since 4946 was 4936, checked in by lguez, 2 months ago

Circumvent a bug of ifort

Same commit as revision 4802. This modification was erased in revision
4853.

File size: 16.3 KB
Line 
1! radiation_general_cloud_optics_data.F90 - Type to store generalized cloud optical properties
2!
3! (C) Copyright 2019- ECMWF.
4!
5! This software is licensed under the terms of the Apache Licence Version 2.0
6! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
7!
8! In applying this licence, ECMWF does not waive the privileges and immunities
9! granted to it by virtue of its status as an intergovernmental organisation
10! nor does it submit to any jurisdiction.
11!
12! Author:  Robin Hogan
13! Email:   r.j.hogan@ecmwf.int
14! License: see the COPYING file for details
15!
16
17#include "ecrad_config.h"
18
19module radiation_general_cloud_optics_data
20
21  use parkind1, only : jprb
22
23  implicit none
24
25  public
26
27  !---------------------------------------------------------------------
28  ! This type holds the configuration information to compute optical
29  ! properties for a particular type of cloud or hydrometeor in one of
30  ! the shortwave or longwave
31  type general_cloud_optics_type
32    ! Band-specific (or g-point-specific) values as a look-up table
33    ! versus effective radius dimensioned (nband,n_effective_radius)
34
35    ! Extinction coefficient per unit mass (m2 kg-1)
36    real(jprb), allocatable, dimension(:,:) :: &
37         &  mass_ext
38
39    ! Single-scattering albedo and asymmetry factor (dimensionless)
40    real(jprb), allocatable, dimension(:,:) :: &
41         &  ssa, asymmetry
42
43    ! Number of effective radius coefficients, start value and
44    ! interval in look-up table
45    integer    :: n_effective_radius = 0
46    real(jprb) :: effective_radius_0, d_effective_radius
47
48    ! Name of cloud/precip type and scattering model
49    ! (e.g. "mie_droplet", "fu-muskatel_ice"). These are used to
50    ! generate the name of the data file from which the coefficients
51    ! are read.
52    character(len=511) :: type_name
53
54    ! Do we use bands or g-points?
55    logical :: use_bands = .false.
56
57   contains
58     procedure :: setup => setup_general_cloud_optics
59     procedure :: add_optical_properties
60     procedure :: save => save_general_cloud_optics_data
61
62  end type general_cloud_optics_type
63
64contains
65
66  ! Provides elemental function "delta_eddington"
67#include "radiation_delta_eddington.h"
68
69  !---------------------------------------------------------------------
70  ! Setup cloud optics coefficients by reading them from a file
71  subroutine setup_general_cloud_optics(this, file_name, specdef, &
72       &                                use_bands, use_thick_averaging, &
73       &                                weighting_temperature, &
74       &                                iverbose)
75
76    use yomhook,                       only : lhook, dr_hook, jphook
77#ifdef EASY_NETCDF_READ_MPI
78    use easy_netcdf_read_mpi, only : netcdf_file
79#else
80    use easy_netcdf,          only : netcdf_file
81#endif
82    use radiation_spectral_definition, only : spectral_definition_type
83    use radiation_io,                  only : nulout, nulerr, radiation_abort
84
85    class(general_cloud_optics_type), intent(inout)    :: this
86    character(len=*), intent(in)               :: file_name
87    type(spectral_definition_type), intent(in) :: specdef
88    logical, intent(in), optional              :: use_bands, use_thick_averaging
89    real(jprb), intent(in), optional           :: weighting_temperature ! K
90    integer, intent(in), optional              :: iverbose
91
92    ! Spectral properties read from file, dimensioned (wavenumber,
93    ! n_effective_radius)
94    real(jprb), dimension(:,:), allocatable :: mass_ext, & ! m2 kg-1
95         &                                     ssa, asymmetry
96
97    ! Reflectance of an infinitely thick cloud, needed for thick
98    ! averaging
99    real(jprb), dimension(:,:), allocatable :: ref_inf
100
101    ! Coordinate variables from file
102    real(jprb), dimension(:), allocatable :: wavenumber       ! cm-1
103    real(jprb), dimension(:), allocatable :: effective_radius ! m
104
105    ! Matrix mapping optical properties in the file to values per
106    ! g-point or band, such that in the thin-averaging case,
107    ! this%mass_ext=matmul(mapping,file%mass_ext), so mapping is
108    ! dimensioned (ngpoint,nwav)
109    real(jprb), dimension(:,:), allocatable :: mapping
110
111    ! The NetCDF file containing the coefficients
112    type(netcdf_file)  :: file
113
114    real(jprb) :: diff_spread
115    integer    :: iverb
116    integer    :: nre  ! Number of effective radii
117    integer    :: nwav ! Number of wavenumbers describing cloud
118
119    logical    :: use_bands_local, use_thick_averaging_local
120
121    real(jphook) :: hook_handle
122
123    if (lhook) call dr_hook('radiation_general_cloud_optics_data:setup',0,hook_handle)
124
125    ! Set local values of optional inputs
126    if (present(iverbose)) then
127      iverb = iverbose
128    else
129      iverb = 2
130    end if
131
132    if (present(use_bands)) then
133      use_bands_local = use_bands
134    else
135      use_bands_local = .false.
136    end if
137
138    if (present(use_thick_averaging)) then
139      use_thick_averaging_local = use_thick_averaging
140    else
141      use_thick_averaging_local = .false.
142    end if
143
144    ! Open the scattering file and configure the way it is read
145    call file%open(trim(file_name), iverbose=iverb)
146    !call file%transpose_matrices()
147
148    ! Read coordinate variables
149    call file%get('wavenumber', wavenumber)
150    call file%get('effective_radius', effective_radius)
151
152    ! Read the band-specific coefficients
153    call file%get('mass_extinction_coefficient', mass_ext)
154    call file%get('single_scattering_albedo', ssa)
155    call file%get('asymmetry_factor', asymmetry)
156
157    ! Close scattering file
158    call file%close()
159
160    ! Check effective radius is evenly spaced
161    nre = size(effective_radius)
162    ! Fractional range of differences, should be near zero for evenly
163    ! spaced data
164    diff_spread = (maxval(effective_radius(2:nre)-effective_radius(1:nre-1))  &
165         &        -minval(effective_radius(2:nre)-effective_radius(1:nre-1))) &
166         &      /  minval(abs(effective_radius(2:nre)-effective_radius(1:nre-1)))
167    if (diff_spread > 0.01_jprb) then
168      write(nulerr, '(a,a,a)') '*** Error: effective_radius in ', &
169           &  trim(file_name), ', is not evenly spaced to 1%'
170      call radiation_abort('Radiation configuration error')
171    end if
172
173    ! Set up effective radius coordinate variable
174    this%n_effective_radius = nre
175    this%effective_radius_0 = effective_radius(1)
176    this%d_effective_radius = effective_radius(2) - effective_radius(1)
177
178    nwav = size(wavenumber)
179
180    ! Define the mapping matrix
181    call specdef%calc_mapping(wavenumber, mapping, &
182         weighting_temperature=weighting_temperature, use_bands=use_bands)
183
184    ! Thick averaging should be performed on delta-Eddington scaled
185    ! quantities (it makes no difference to thin averaging)
186    call delta_eddington(mass_ext, ssa, asymmetry)
187
188    ! Thin averaging
189
190    ! Circumvent ifort bug (see https://github.com/ecmwf-ifs/ecrad/issues/13):
191    allocate(this%mass_ext(size(mapping, 1), nre))
192
193    this%mass_ext  = matmul(mapping, mass_ext)
194    this%ssa       = matmul(mapping, mass_ext*ssa) / this%mass_ext
195    this%asymmetry = matmul(mapping, mass_ext*ssa*asymmetry) / (this%mass_ext*this%ssa)
196
197    if (use_thick_averaging_local) then
198      ! Thick averaging as described by Edwards and Slingo (1996),
199      ! modifying only the single-scattering albedo
200      allocate(ref_inf(nwav, nre))
201
202      ! Eqs. 18 and 17 of Edwards & Slingo (1996)
203      ref_inf = sqrt((1.0_jprb - ssa) / (1.0_jprb - ssa*asymmetry))
204      ref_inf = (1.0_jprb - ref_inf) / (1.0_jprb + ref_inf)
205      ! Here the left-hand side is actually the averaged ref_inf
206      this%ssa = matmul(mapping, ref_inf)
207      ! Eq. 19 of Edwards and Slingo (1996)
208      this%ssa = 4.0_jprb * this%ssa / ((1.0_jprb + this%ssa)**2 &
209           &  - this%asymmetry * (1.0_jprb - this%ssa)**2)
210
211      deallocate(ref_inf)
212    end if
213
214    deallocate(mapping)
215
216    ! Revert back to unscaled quantities
217    call revert_delta_eddington(this%mass_ext, this%ssa, this%asymmetry)
218
219    if (iverb >= 2) then
220      write(nulout,'(a,a)') '  File: ', trim(file_name)
221      if (present(weighting_temperature)) then
222        write(nulout,'(a,f7.1,a)') '  Weighting temperature: ', weighting_temperature, ' K'
223      else
224        write(nulout,'(a,f7.1,a)') '  Weighting temperature: ', specdef%reference_temperature, ' K'
225      end if
226      if (use_thick_averaging_local) then
227        write(nulout,'(a)') '  SSA averaging: optically thick limit'
228      else
229        write(nulout,'(a)') '  SSA averaging: optically thin limit'
230      end if
231      if (use_bands_local) then
232        write(nulout,'(a,i0,a)') '  Spectral discretization: ', specdef%nband, ' bands'
233      else
234        write(nulout,'(a,i0,a)') '  Spectral discretization: ', specdef%ng, ' g-points'
235      end if
236      write(nulout,'(a,i0,a,f6.1,a,f6.1,a)') '  Effective radius look-up: ', nre, ' points in range ', &
237           &  effective_radius(1)*1.0e6_jprb, '-', effective_radius(nre)*1.0e6_jprb, ' um'
238      write(nulout,'(a,i0,a,i0,a)') '  Wavenumber range: ', int(specdef%min_wavenumber()), '-', &
239           &  int(specdef%max_wavenumber()), ' cm-1'
240    end if
241
242    if (lhook) call dr_hook('radiation_general_cloud_optics_data:setup',1,hook_handle)
243
244  end subroutine setup_general_cloud_optics
245
246
247  !---------------------------------------------------------------------
248  ! Add the optical properties of a particular cloud type to the
249  ! accumulated optical properties of all cloud types
250  subroutine add_optical_properties(this, ng, nlev, ncol, &
251       &                            cloud_fraction, &
252       &                            water_path, effective_radius, &
253       &                            od, scat_od, scat_asymmetry)
254
255    use yomhook, only : lhook, dr_hook, jphook
256
257    class(general_cloud_optics_type), intent(in) :: this
258
259    ! Number of g points, levels and columns
260    integer, intent(in) :: ng, nlev, ncol
261
262    ! Properties of present cloud type, dimensioned (ncol,nlev)
263    real(jprb), intent(in) :: cloud_fraction(:,:)
264    real(jprb), intent(in) :: water_path(:,:)       ! kg m-2
265    real(jprb), intent(in) :: effective_radius(:,:) ! m
266
267    ! Optical properties which are additive per cloud type,
268    ! dimensioned (ng,nlev,ncol)
269    real(jprb), intent(inout), dimension(ng,nlev,ncol) &
270         &  :: od             ! Optical depth of layer
271    real(jprb), intent(inout), dimension(ng,nlev,ncol), optional &
272         &  :: scat_od, &     ! Scattering optical depth of layer
273         &     scat_asymmetry ! Scattering optical depth x asymmetry factor
274
275    real(jprb) :: od_local
276
277    real(jprb) :: re_index, weight1, weight2
278    integer :: ire
279
280    integer :: jcol, jlev, jg
281
282    real(jphook) :: hook_handle
283
284    if (lhook) call dr_hook('radiation_general_cloud_optics_data:add_optical_properties',0,hook_handle)
285
286    if (present(scat_od)) then
287      do jcol = 1,ncol
288        do jlev = 1,nlev
289          if (cloud_fraction(jcol, jlev) > 0.0_jprb) then
290            re_index = max(1.0_jprb, min(1.0_jprb + (effective_radius(jcol,jlev)-this%effective_radius_0) &
291                 &              / this%d_effective_radius, this%n_effective_radius-0.0001_jprb))
292            ire = int(re_index)
293            weight2 = re_index - ire
294            weight1 = 1.0_jprb - weight2
295            do jg = 1, ng
296              od_local = water_path(jcol, jlev) * (weight1*this%mass_ext(jg,ire) &
297                 &                                +weight2*this%mass_ext(jg,ire+1))
298              od(jg,jlev,jcol) = od(jg,jlev,jcol) + od_local
299              od_local = od_local * (weight1*this%ssa(jg,ire) &
300                 &                  +weight2*this%ssa(jg,ire+1))
301              scat_od(jg,jlev,jcol) = scat_od(jg,jlev,jcol) + od_local
302              scat_asymmetry(jg,jlev,jcol) = scat_asymmetry(jg,jlev,jcol) &
303                 & + od_local * (weight1*this%asymmetry(jg,ire) &
304                 &              +weight2*this%asymmetry(jg,ire+1))
305            end do
306
307          end if
308        end do
309      end do
310    else
311      ! No scattering: return the absorption optical depth
312      do jcol = 1,ncol
313        do jlev = 1,nlev
314          if (water_path(jcol, jlev) > 0.0_jprb) then
315            re_index = max(1.0_jprb, min(1.0_jprb + (effective_radius(jcol,jlev)-this%effective_radius_0) &
316                 &              / this%d_effective_radius, this%n_effective_radius-0.0001_jprb))
317            ire = int(re_index)
318            weight2 = re_index - ire
319            weight1 = 1.0_jprb - weight2
320            od(:,jlev,jcol) = od(:,jlev,jcol) &
321                 &  + water_path(jcol, jlev) * (weight1*this%mass_ext(:,ire) &
322                 &                             +weight2*this%mass_ext(:,ire+1)) &
323                 &  * (1.0_jprb - (weight1*this%ssa(:,ire)+weight2*this%ssa(:,ire+1)))
324          end if
325        end do
326      end do
327    end if
328
329    if (lhook) call dr_hook('radiation_general_cloud_optics_data:add_optical_properties',1,hook_handle)
330
331  end subroutine add_optical_properties
332
333
334  !---------------------------------------------------------------------
335  ! Return the Planck function (in W m-2 (cm-1)-1) for a given
336  ! wavenumber (cm-1) and temperature (K), ensuring double precision
337  ! for internal calculation
338  elemental function calc_planck_function_wavenumber(wavenumber, temperature)
339
340    use parkind1,            only : jprb, jprd
341    use radiation_constants, only : SpeedOfLight, BoltzmannConstant, PlanckConstant
342
343    real(jprb), intent(in) :: wavenumber  ! cm-1
344    real(jprb), intent(in) :: temperature ! K
345    real(jprb) :: calc_planck_function_wavenumber
346
347    real(jprd) :: freq ! Hz
348    real(jprd) :: planck_fn_freq ! W m-2 Hz-1
349
350    freq = 100.0_jprd * real(SpeedOfLight,jprd) * real(wavenumber,jprd)
351    planck_fn_freq = 2.0_jprd * real(PlanckConstant,jprd) * freq**3 &
352         &  / (real(SpeedOfLight,jprd)**2 * (exp(real(PlanckConstant,jprd)*freq &
353         &     /(real(BoltzmannConstant,jprd)*real(temperature,jprd))) - 1.0_jprd))
354    calc_planck_function_wavenumber = real(planck_fn_freq * 100.0_jprd * real(SpeedOfLight,jprd), jprb)
355
356  end function calc_planck_function_wavenumber
357
358  !---------------------------------------------------------------------
359  ! Save cloud optical properties in the named file
360  subroutine save_general_cloud_optics_data(this, file_name, iverbose)
361
362    use yomhook,     only : lhook, dr_hook, jphook
363    use easy_netcdf, only : netcdf_file
364
365    class(general_cloud_optics_type), intent(in) :: this
366    character(len=*),                 intent(in) :: file_name
367    integer,                optional, intent(in) :: iverbose
368
369    ! Object for output NetCDF file
370    type(netcdf_file) :: out_file
371
372    real(jprb) :: effective_radius(this%n_effective_radius)
373    integer :: ire
374   
375    real(jphook) :: hook_handle
376
377    if (lhook) call dr_hook('radiation_general_cloud_optics_data:save',0,hook_handle)
378
379    ! Create the file
380    call out_file%create(trim(file_name), iverbose=iverbose)
381
382    ! Define dimensions
383    call out_file%define_dimension("band", size(this%mass_ext,1))
384    call out_file%define_dimension("effective_radius", this%n_effective_radius)
385
386    ! Put global attributes
387    call out_file%put_global_attributes( &
388         &   title_str="Optical properties of "//trim(this%type_name) &
389         &   //" hydrometeors using the spectral intervals of ecRad", &
390         &   source_str="ecRad offline radiation model")
391
392    ! Define variables
393    call out_file%define_variable("effective_radius", units_str="m", &
394         &  long_name="Effective radius", dim1_name="effective_radius")
395    call out_file%define_variable("mass_extinction_coefficient", units_str="m2 kg-1", &
396         &  long_name="Mass-extinction coefficient", &
397         &  dim2_name="effective_radius", dim1_name="band")
398    call out_file%define_variable("single_scattering_albedo", units_str="1", &
399         &  long_name="Single scattering albedo", &
400         &  dim2_name="effective_radius", dim1_name="band")
401    call out_file%define_variable("asymmetry_factor", units_str="1", &
402         &  long_name="Asymmetry factor", &
403         &  dim2_name="effective_radius", dim1_name="band")
404
405    ! Define effective radius
406    do ire = 1,this%n_effective_radius
407      effective_radius(ire) = this%effective_radius_0 + this%d_effective_radius*(ire-1)
408    end do
409   
410    ! Write variables
411    call out_file%put("effective_radius", effective_radius)
412    call out_file%put("mass_extinction_coefficient", this%mass_ext)
413    call out_file%put("single_scattering_albedo", this%ssa)
414    call out_file%put("asymmetry_factor", this%asymmetry)
415   
416    call out_file%close()
417
418    if (lhook) call dr_hook('radiation_general_cloud_optics_data:save',1,hook_handle)
419
420  end subroutine save_general_cloud_optics_data
421 
422end module radiation_general_cloud_optics_data
Note: See TracBrowser for help on using the repository browser.