mam  v1.0
A Modal Aerosol Model
optics_lookup.F90
Go to the documentation of this file.
1! Copyright (C) 2021 National Center for Atmospheric Research
2! SPDX-License-Identifier: Apache-2.0
3!
4!> \file
5!> The mam_optics_lookup module
6
7!> The optics_lookup_t type and related functions
9
10 use ai_wavelength_grid, only : wavelength_grid_t
11 use musica_constants, only : musica_dk
12 use musica_lookup_2d_axis, only : lookup_2d_axis_t
13 use musica_string, only : string_t
14
15 implicit none
16 private
17
18 public :: optics_lookup_t
19
20 !> \todo are "specific extinction", "specific absorption", and
21 !! "asymmetry factor" the correct names for the optical
22 !! properties returned from the lookup tables?
23 !> @name Local indices for optics parameters
24 !! @{
25 integer, parameter :: kabsorption = 1
26 integer, parameter :: kextinction = 2
27 integer, parameter :: kasymmetryfactor = 3
28 !> @}
29 !> Number of possible optics parameters
30 integer, parameter :: knumberofparameters = 3
31
33 private
34 type(string_t) :: parameter_name_
35 logical :: is_loaded_ = .false.
36 !> Optics lookup data (Chebychev coefficients, real refractive index,
37 !! imaginary refractive index, band)
38 real(kind=musica_dk), allocatable :: values_(:,:,:,:)
39 contains
40 procedure :: lookup => optics_data_lookup
41 end type optics_data_t
42
43 interface optics_data_t
44 procedure :: optics_data_constructor
45 end interface optics_data_t
46
48 private
49 !> Optics parameter lookup data
51 integer :: number_of_chebyshev_coefficients_ = 0
52 type(wavelength_grid_t) :: grid_
53 real(kind=musica_dk) :: minimum_ln_radius_ = 0.0
54 real(kind=musica_dk) :: maximum_ln_radius_ = 0.0
55 type(lookup_2d_axis_t), allocatable :: axes_(:)
56 contains
57 procedure :: get_optics
58 procedure :: normalize_radius
59 procedure :: maximum_radius__m
60 procedure :: minimum_radius__m
63 procedure :: grid
64 procedure, private :: set_grid
65 end type optics_lookup_t
66
67 interface optics_lookup_t
68 procedure :: constructor
69 end interface optics_lookup_t
70
71contains
72
73!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
74
75 !> Constructor of optics lookup objects
76 function constructor( config ) result( new_lookup )
77
78 use musica_assert, only : assert, assert_msg
79 use musica_config, only : config_t
80 use musica_lookup_axis, only : lookup_axis_t
81 use musica_io, only : io_t
82 use musica_io_netcdf, only : io_netcdf_t
83
84 type(optics_lookup_t) :: new_lookup
85 class(config_t), intent(inout) :: config
86
87 character(len=*), parameter :: my_name = "optics_lookup_t constructor"
88 class(io_t), pointer :: lookup_file
89 type(config_t) :: grid_config
90 type(string_t) :: file_path, variable_name, axis_real_name, axis_imag_name
91 type(lookup_axis_t) :: axis_real, axis_imag
92 real(kind=musica_dk), allocatable :: axis_real_values(:,:), axis_imag_values(:,:)
93 integer :: i_band, i_param
94 logical :: found
95
96 call config%get( "file path", file_path, my_name )
97 lookup_file => io_netcdf_t( file_path )
98 call config%get( "grid", grid_config, my_name )
99 call new_lookup%set_grid( grid_config, lookup_file )
100 call config%get( "real axis", axis_real_name, my_name )
101 call lookup_file%read( axis_real_name, axis_real_values, my_name )
102 call config%get( "imaginary axis", axis_imag_name, my_name )
103 call lookup_file%read( axis_imag_name, axis_imag_values, my_name )
104 call assert_msg( 756675500, size( axis_real_values, 2 ) .eq. &
105 size( axis_imag_values, 2 ), &
106 "Optics lookup axis dimension mismatch" )
107 allocate( new_lookup%axes_( new_lookup%grid_%number_of_sections( ) ) )
108 do i_band = 1, new_lookup%grid_%number_of_sections( )
109 axis_real = lookup_axis_t( axis_real_name, axis_real_values( :, i_band ) )
110 axis_imag = lookup_axis_t( axis_imag_name, axis_imag_values( :, i_band ) )
111 new_lookup%axes_( i_band ) = lookup_2d_axis_t( axis_real, axis_imag )
112 end do
113 variable_name = "minimum_radius"
114 call lookup_file%read( variable_name, &
115 new_lookup%minimum_ln_radius_, my_name )
116 new_lookup%minimum_ln_radius_ = log( new_lookup%minimum_ln_radius_ )
117 variable_name = "maximum_radius"
118 call lookup_file%read( variable_name, &
119 new_lookup%maximum_ln_radius_, my_name )
120 new_lookup%maximum_ln_radius_ = log( new_lookup%maximum_ln_radius_ )
121 new_lookup%data_( kabsorption ) = &
122 optics_data_t( "specific absorption lookup", lookup_file, config )
123 new_lookup%data_( kextinction ) = &
124 optics_data_t( "specific extinction lookup", lookup_file, config )
125 new_lookup%data_( kasymmetryfactor ) = &
126 optics_data_t( "asymmetry factor lookup", lookup_file, config )
127 do i_param = 1, knumberofparameters
128 if( .not. new_lookup%data_( i_param )%is_loaded_ ) cycle
129 if( new_lookup%number_of_chebyshev_coefficients_ .eq. 0 ) then
130 new_lookup%number_of_chebyshev_coefficients_ = &
131 size( new_lookup%data_( i_param )%values_, 1 )
132 else
133 call assert_msg( 168034110, &
134 size( new_lookup%data_( i_param )%values_, 1 ) &
135 .eq. new_lookup%number_of_chebyshev_coefficients_, &
136 "Dimension mismatch in optics_lookup_t data" )
137 end if
138 call assert_msg( 531859329, &
139 size( new_lookup%data_( i_param )%values_, 2 ) &
140 .eq. axis_real%size( ), &
141 "Dimension mismatch in optics_lookup_t data" )
142 call assert_msg( 521820197, &
143 size( new_lookup%data_( i_param )%values_, 3 ) &
144 .eq. axis_imag%size( ), &
145 "Dimension mismatch in optics_lookup_t data" )
146 call assert_msg( 284936197, &
147 size( new_lookup%data_( i_param )%values_, 4 ) &
148 .eq. new_lookup%grid_%number_of_sections( ), &
149 "Dimension mismatch in optics_lookup_t data" )
150 end do
151 deallocate( lookup_file )
152
153 end function constructor
154
155!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
156
157 !> Lookup optics properties for a given wavelength-dependent refractive
158 !! index
159 !!
160 !! \todo there is no check to make sure the requested data is actually
161 !! present, as this would require making this not a pure function.
162 !! Check to see if this being a pure function has any benefit
163 pure subroutine get_optics( this, refractive_indices, absorption, &
164 extinction, asymmetry_factor )
165
166 class(optics_lookup_t), intent(in) :: this
167 !> Complex indices of refraction (wavelength band)
168 complex(kind=musica_dk), intent(in) :: refractive_indices(:)
169 !> @name optical properties (Chebychev coefficient, wavelength band)
170 !! @{
171 real(kind=musica_dk), optional, intent(out) :: absorption(:,:)
172 real(kind=musica_dk), optional, intent(out) :: extinction(:,:)
173 real(kind=musica_dk), optional, intent(out) :: asymmetry_factor(:,:)
174 !> @}
175
176 integer :: indices(2), i_band
177 real(kind=musica_dk) :: residuals(2)
178
179 do i_band = 1, this%grid_%number_of_sections( )
180 associate( axis => this%axes_( i_band ) )
181 indices(:) = 1
182 call axis%find( (/real( refractive_indices( i_band ) ), &
183 abs( aimag( refractive_indices( i_band ) ) )/), &
185 if( present( absorption ) ) then
186 call this%data_( kabsorption )%lookup( i_band, indices, residuals, &
187 axis, absorption( :, i_band ) )
188 end if
189 if( present( extinction ) ) then
190 call this%data_( kextinction )%lookup( i_band, indices, residuals, &
191 axis, extinction( :, i_band ) )
192 end if
193 if( present( asymmetry_factor ) ) then
194 call this%data_( kasymmetryfactor )%lookup( i_band, indices, &
195 residuals, axis, &
197 end if
198 end associate
199 end do
200
201 end subroutine get_optics
202
203!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
204
205 !> Normalizes a radius to the range used to calculate the Chebychev
206 !! coefficients
207 !!
208 !! Radii are converted to ln(radius) prior to normalization
209 !!
210 !! Normalized radii range from -1...1
211 real(kind=musica_dk) elemental function normalize_radius( this, radius__m )
212
213 class(optics_lookup_t), intent(in) :: this
214 !> Aerosol surface mode radius
215 !! (mode radius of the surface area distribution)
216 real(kind=musica_dk), intent(in) :: radius__m
217
218 associate( rmin => this%minimum_ln_radius_, &
219 rmax => this%maximum_ln_radius_ )
220 normalize_radius = log( radius__m )
223 normalize_radius = ( 2.0_musica_dk * normalize_radius - rmax - rmin ) / &
224 ( rmax - rmin )
225 end associate
226
227 end function normalize_radius
228
229!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
230
231 !> Returns the maximum radius of the range used to calculate the Chebychev
232 !! coefficients
233 real(kind=musica_dk) elemental function maximum_radius__m( this )
234
235 class(optics_lookup_t), intent(in) :: this
236
237 maximum_radius__m = exp( this%maximum_ln_radius_ )
238
239 end function maximum_radius__m
240
241!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
242
243 !> Returns the minimum radius of the range used to calculate the Chebychev
244 !! coefficients
245 real(kind=musica_dk) elemental function minimum_radius__m( this )
246
247 class(optics_lookup_t), intent(in) :: this
248
249 minimum_radius__m = exp( this%minimum_ln_radius_ )
250
251 end function minimum_radius__m
252
253!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
254
255 !> Returns the number of Chebyshev coefficients per band and property
256 integer elemental function number_of_chebyshev_coefficients( this )
257
258 class(optics_lookup_t), intent(in) :: this
259
260 number_of_chebyshev_coefficients = this%number_of_chebyshev_coefficients_
261
263
264!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
265
266 !> Returns the number of wavelength bands for each property
267 integer elemental function number_of_wavelength_bands( this )
268
269 class(optics_lookup_t), intent(in) :: this
270
271 number_of_wavelength_bands = this%grid_%number_of_sections( )
272
273 end function number_of_wavelength_bands
274
275!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
276
277 !> Returns the wavelength grid that the properties are returned on
278 type(wavelength_grid_t) function grid( this )
279
280 class(optics_lookup_t), intent(in) :: this
281
282 grid = this%grid_
283
284 end function grid
285
286!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
287!!
288!! Private functions
289!!
290!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
291
292 !> Sets the wavelength grid for an optics_lookup_t object
293 subroutine set_grid( this, config, lookup_file )
294
295 use ai_wavelength_grid, only : kcentimeter, kwavenumber
296 use musica_assert, only : assert_msg
297 use musica_config, only : config_t
298 use musica_io, only : io_t
299
300 class(optics_lookup_t), intent(inout) :: this
301 class(config_t), intent(inout) :: config
302 class(io_t), intent(inout) :: lookup_file
303
304 character(len=*), parameter :: my_name = "optics_lookup_t grid loader"
305 type(string_t) :: var_name, lb_units, ub_units
306 real(kind=musica_dk), allocatable :: lower_bounds(:), upper_bounds(:)
307
308 call config%get( "lower bounds", var_name, my_name )
309 call lookup_file%read( var_name, lower_bounds, my_name )
310 lb_units = lookup_file%variable_units( var_name, my_name )
311 call config%get( "upper bounds", var_name, my_name )
312 call lookup_file%read( var_name, upper_bounds, my_name )
313 ub_units = lookup_file%variable_units( var_name, my_name )
314 call assert_msg( 950480637, lb_units .eq. ub_units, &
315 "Units mismatch for upper and lower wavelength "// &
316 "bounds" )
317 call assert_msg( 709050082, lb_units .eq. "cm-1", &
318 "Expected wavlength bounds in units of 'cm-1'" )
319 this%grid_ = wavelength_grid_t( lower_bounds = lower_bounds, &
320 upper_bounds = upper_bounds, &
321 bounds_in = kwavenumber, &
322 base_unit = kcentimeter )
323
324 end subroutine set_grid
325
326!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
327
328 !> Constructor of optics lookup data objects
329 function optics_data_constructor( parameter_name, lookup_file, config ) &
330 result( new_data )
331
332 use musica_config, only : config_t
333 use musica_io, only : io_t
334
335 type(optics_data_t) :: new_data
336 character(len=*), intent(in) :: parameter_name
337 class(io_t), intent(inout) :: lookup_file
338 class(config_t), intent(inout) :: config
339
340 character(len=*), parameter :: my_name = "optics table loader"
341 type(config_t) :: table_config
342 type(string_t) :: variable_name
343 logical :: found
344
345 new_data%parameter_name_ = parameter_name
346 call config%get( parameter_name, table_config, my_name, found = found )
347 if( .not. found ) then
348 allocate( new_data%values_(0,0,0,0) )
349 return
350 end if
351 call table_config%get( "variable name", variable_name, my_name )
352 call lookup_file%read( variable_name, new_data%values_, my_name )
353 new_data%is_loaded_ = .true.
354
355 end function optics_data_constructor
356
357!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
358
359 !> Looks up optical property data at a given wavelength band
360 pure subroutine optics_data_lookup( this, band_index, indices, residuals, &
361 axis, chebyshev_values )
362
363 class(optics_data_t), intent(in) :: this
364 integer, intent(in) :: band_index
365 integer, intent(in) :: indices(2)
366 real(kind=musica_dk), intent(in) :: residuals(2)
367 type(lookup_2d_axis_t), intent(in) :: axis
368 real(kind=musica_dk), intent(out) :: chebyshev_values(:)
369
370 call axis%interpolate( this%values_( :, &
371 indices(1):indices(1)+1, &
372 indices(2):indices(2)+1, &
373 band_index ), &
374 residuals, chebyshev_values )
375
376 end subroutine optics_data_lookup
377
378!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
379
380end module mam_optics_lookup
The optics_lookup_t type and related functions.
integer elemental function number_of_chebyshev_coefficients(this)
Returns the number of Chebyshev coefficients per band and property.
subroutine set_grid(this, config, lookup_file)
Sets the wavelength grid for an optics_lookup_t object.
integer, parameter knumberofparameters
Number of possible optics parameters.
type(optics_lookup_t) function constructor(config)
Constructor of optics lookup objects.
integer elemental function number_of_wavelength_bands(this)
Returns the number of wavelength bands for each property.
pure subroutine optics_data_lookup(this, band_index, indices, residuals, axis, chebyshev_values)
Looks up optical property data at a given wavelength band.
type(optics_data_t) function optics_data_constructor(parameter_name, lookup_file, config)
Constructor of optics lookup data objects.
real(kind=musica_dk) elemental function normalize_radius(this, radius__m)
Normalizes a radius to the range used to calculate the Chebychev coefficients.
real(kind=musica_dk) elemental function minimum_radius__m(this)
Returns the minimum radius of the range used to calculate the Chebychev coefficients.
integer, parameter kextinction
integer, parameter kasymmetryfactor
real(kind=musica_dk) elemental function maximum_radius__m(this)
Returns the maximum radius of the range used to calculate the Chebychev coefficients.
type(wavelength_grid_t) function grid(this)
Returns the wavelength grid that the properties are returned on.
integer, parameter kabsorption
real(kind=musica_dk), dimension(:,:), intent(out), optional asymmetry_factor
real(kind=musica_dk), dimension(:,:), intent(out), optional absorption
integer i_band
integer, dimension(2) indices
real(kind=musica_dk), dimension(2) residuals
real(kind=musica_dk), dimension(:,:), intent(out), optional extinction