doc/app/iofiles/pids/aerosol: create_basic_salsa_dynamic_driver.py

File create_basic_salsa_dynamic_driver.py, 15.3 KB (added by monakurppa, 5 years ago)

An example Python script for creating a salsa dynamic driver (containing background concentrations)

Line 
1#------------------------------------------------------------------------------#
2# This file is part of the PALM model system.
3#
4# PALM is free software: you can redistribute it and/or modify it under the
5# terms of the GNU General Public License as published by the Free Software
6# Foundation, either version 3 of the License, or (at your option) any later
7# version.
8#
9# PALM is distributed in the hope that it will be useful, but WITHOUT ANY
10# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# PALM. If not, see <http://www.gnu.org/licenses/>.
15#
16# Copyright 2019-2019 University of Helsinki
17#------------------------------------------------------------------------------#
18
19import math
20from   netCDF4 import Dataset
21import numpy as np
22import datetime
23
24
25class SalsaDynamicDriver:
26    """ This is an example script to generate dynamic drivers that include aerosol
27    backgound concentrations for PALM.
28
29    You can use it as a starting point for creating your setup specific
30    driver. 
31    """
32   
33   
34    def __init__(self):
35        """ Open the salsa dynamic driver as NetCDF4 file. Here, you have to
36        give the full path to the static driver that shall be created. Existing
37        file with same name is deleted.
38        """
39        print('Opening file...')
40        self.nc_file = Dataset('salsa_dynamic_driver.nc', 'w', format='NETCDF4')
41
42
43    def write_global_attributes(self):
44        """ Write global attributes to static driver. """
45        print("Writing global attributes...")
46
47        # optional global attributes
48        self.nc_file.origin_lon = 55.0 # used to initialize coriolis parameter
49        self.nc_file.origin_lat = 0.0 # (overwrite initialization_parameters)
50        self.nc_file.origin_time = '2000-06-21 12:00:00 +00'
51        self.nc_file.origin_x = 308124
52        self.nc_file.origin_y = 6098908
53        self.nc_file.origin_z = 0.0
54        self.nc_file.rotation_angle = 0.0
55        self.nc_file.author = 'Your Name'
56        self.nc_file.comment = 'Miscellaneous information about the data ' \
57                               'or methods to produce it.'
58        self.nc_file.creation_date = str(datetime.datetime.now())
59        self.nc_file.institution = 'INAR/Physics, University of Helsinki'
60        self.nc_file.history = ''
61        self.nc_file.palm_revision = ''
62        self.nc_file.title = 'Salsa dynamic driver for some arbitrary PALM setup'
63
64
65    def define_dimensions(self):
66        """ Set dimensions on which variables are defined. """
67        print("Writing dimensions...")
68
69        # General grid parameters:
70
71        self.nx = 19
72        self.ny = 19
73        self.nz = 20
74        dx = 2
75        dy = 2
76        dz = 2
77
78        # time steps in the background concentration data:
79        self.times = np.arange( 0.0, 7201.0, 3600.0 )
80
81
82        # Aerosol composition and size distribution:
83
84        # number of chemical components
85        self.ncomposition_index = 7
86
87        # number of aerosol size bins in the subrange 1 and 2
88        nbin   = [1, 7]
89
90        # subrange diameter limit (e.g. subrange 1: 3-10 nm, subrange 2: 10 nm - 2.5 um
91        reglim = [3.0e-9, 10.0e-9, 2.5e-6]
92
93
94        # Mandatory dimensions
95        self.nmax_string_length = 25
96
97        self.ntime = len( self.times )
98
99        self.nc_file.createDimension('x' ,self.nx+1)
100        self.x = self.nc_file.createVariable('x', 'f8', ('x',))
101        self.x.units = 'm'
102        self.x.standard_name = 'x coordinate of cell centers'
103        self.x[:] = np.arange( 0.5*dx, ( self.nx+1 ) * dx, dx )
104
105        self.nc_file.createDimension('y', self.ny+1)
106        self.y = self.nc_file.createVariable('y', 'f8', ('y',))
107        self.y.units = 'm'
108        self.y.standard_name = 'y coordinate of cell centers'
109        self.y[:] = np.arange( 0.5*dy, ( self.ny+1 ) * dy, dy )
110
111        self.nc_file.createDimension('z', self.nz)
112        self.z = self.nc_file.createVariable('z', 'f8', ('z',))
113        self.z.units = 'm'
114        self.z.standard_name = 'z coordinate of cell centers'
115        self.z[:] = np.arange( 0.5*dz, ( self.nz ) * dz, dz )  # NO STRETCHING!
116
117        self.nc_file.createDimension('max_string_length', self.nmax_string_length )
118        self.max_string_length = self.nc_file.createVariable('max_string_length', 'i4',
119                                                             ('max_string_length',))
120        self.max_string_length.units = ''
121        self.max_string_length[:] = np.linspace( 1, self.nmax_string_length, self.nmax_string_length )
122
123        self.nc_file.createDimension('composition_index', self.ncomposition_index )
124        self.composition_index = self.nc_file.createVariable('composition_index', 'i4', 
125                                                             ('composition_index',))
126        self.composition_index.units = ''
127        self.composition_index.long_name = 'composition index'
128        self.composition_index[:] = np.linspace( 1, self.ncomposition_index, self.ncomposition_index )
129
130        self.nc_file.createDimension('time' , self.ntime )
131        self.time = self.nc_file.createVariable('time', 'f8', ('time',))
132        self.time.units = 's'
133        self.time.standard_name = 'time since utc init'
134        self.time[:] = self.times
135
136        self.ndmid, self.bin_limits = define_bins( nbin, reglim )
137        self.nc_file.createDimension('Dmid' , np.sum( nbin ) )
138        self.Dmid = self.nc_file.createVariable('Dmid', 'f8', ('Dmid',))
139        self.Dmid.units = 'm'
140        self.Dmid.standard_name = 'mean diamater per size bin'
141        self.Dmid[:] = self.ndmid
142
143    def add_variables(self):
144        """ Uncomment variables below as you like.
145
146        Be aware that some variables depend on others. For a description of
147        each variable, please have a look at the documentation at
148        https://palm.muk.uni-hannover.de/trac/wiki/doc/app/iofiles/pids/aerosol
149
150        An example of how you modify the variables is given below:
151
152        building_2d_array = np.ones((self.ny+1,self.nx+1)) * -9999.0
153        south_wall, north_wall, left_wall, right_wall = 20, 25, 20, 25
154        building_2d_array[south_wall:north_wall,left_wall:right_wall] = 50
155        nc_buildings_2d = self.nc_file.createVariable(
156            'buildings_2d', 'f4', ('y','x'),fill_value=-9999.0)
157        nc_buildings_2d.lod = 1
158        nc_buildings_2d[:,:] = building_2d_array
159        """
160        print("Writing variables...")
161
162        composition_name_list = ['H2SO4                    ','OC                       ',
163                                 'BC                       ','DU                       ',
164                                 'SS                       ','HNO3                     ', 
165                                 'NH3                      ']
166
167        lsf_mf_a = np.ones( ( self.ntime, self.nz, self.ncomposition_index ) )
168        lsf_mf_b = np.ones( ( self.ntime, self.nz, self.ncomposition_index ) )
169        lsf_psd  = np.ones( ( self.ntime, self.nz, len( self.ndmid ) ) )
170
171        dpg       = np.array([20.3, 72.0])
172        n_lognorm = np.array([18960.0, 13750.0])
173        sigmag    = np.array([1.7, 1.6])
174
175        # sectional size distribution
176        nsect = np.zeros( len( self.ndmid ), dtype=float )
177        for l in range( 1, len( self.ndmid )+1 ): 
178          d1 = self.bin_limits[l-1]
179          d2 = self.bin_limits[l]
180          delta_d = ( d2 - d1 ) / 10.0
181          for ib in range( 1, len( self.ndmid )+1 ):
182            d1 = self.bin_limits[l-1] + ( ib - 1 ) * delta_d
183            d2 = d1 + delta_d
184            dmidi = ( d1 + d2 ) / 2.0
185            deltadp = np.log10( d2 / d1 )
186            nsect[l-1] = np.sum( n_lognorm * 1.0E6 * deltadp / ( np.sqrt( 2.0 * np.pi ) * 
187                                                                 np.log10( sigmag ) ) *
188                         np.exp( -np.log10( dmidi / ( 1.0E-9 * dpg ) )**2.0 / 
189                           ( 2.0 * np.log10( sigmag ) ** 2.0 ) ) )
190
191        # set a constant profile:
192        for t in range( self.ntime ):
193          for k in range( self.nz ):
194            lsf_mf_a[t,k,:] = np.array( [0.001, 0.699, 0.1, 0.0, 0.0, 0.1, 0.1] )
195            lsf_mf_b[t,k,:] = np.array( [0.0,   0.0,   0.0, 0.0, 0.0, 0.0, 0.0] )
196            lsf_psd[t,k,:]  = nsect
197
198
199        # Save into the file
200        nc_composition_name = self.nc_file.createVariable( 'composition_name', 'S1',
201                                                         ('composition_index','max_string_length',) )
202        nc_composition_name[:] = list( map( lambda x : list(x), composition_name_list ) )
203        nc_composition_name.long_name = 'aerosol composition name'
204        nc_composition_name.standard_name = 'composition_name'
205
206
207        nc_init_mf_a = self.nc_file.createVariable( 'init_atmosphere_mass_fracs_a', 'f4',
208                                                    ('z','composition_index',), fill_value=-9999.0 )
209        nc_init_mf_a[:] = lsf_mf_a[0,:,:]
210        nc_init_mf_a.long_name = "initial mass fraction profile: a bins"
211        nc_init_mf_a.standard_name = 'init_atmosphere_mass_fracs_a'
212
213
214        nc_init_mf_b = self.nc_file.createVariable( 'init_atmosphere_mass_fracs_b', 'f4',
215                                                    ('z','composition_index',), fill_value=-9999.0 )
216        nc_init_mf_b[:] = lsf_mf_b[0,:,:]
217        nc_init_mf_b.long_name = "initial mass fraction profile: b bins"
218        nc_init_mf_b.standard_name = 'init_atmosphere_mass_fracs_b'
219
220
221        nc_init_psd = self.nc_file.createVariable( 'init_atmosphere_aerosol', 'f4', ('z','Dmid',),
222                                                   fill_value=-9999.0 )
223        nc_init_psd[:] = lsf_psd[0,:,:]
224        nc_init_psd.unit = '#/m3'
225        nc_init_psd.long_name = 'initial vertical profile of aerosol concentration'
226        nc_init_psd.standard_name = 'init_atmosphere_aerosol'
227        nc_init_psd.lod = 1
228
229        ls_names = ['left','right','north','south']
230        ls_dims = [ self.y, self.y, self.x, self.x ]
231        ls_dimsname = ['y','y','x','x']
232
233        nvi = 0
234        for nv in ls_names:
235          dummy = np.zeros( [ self.ntime, self.nz, len( ls_dims[nvi] ), self.ncomposition_index ] )
236
237          for i in range( len( ls_dims[nvi] ) ):
238            dummy[:,:,i,:] = lsf_mf_a
239          namev = 'ls_forcing_{}_mass_fracs_a'.format( nv )
240          nc_lsf_mf_a = self.nc_file.createVariable( namev, 'f4', ('time','z', ls_dimsname[nvi],
241                                                                   'composition_index',),
242                                                     fill_value=-9999.0 )
243          nc_lsf_mf_a[:] = dummy
244          nc_lsf_mf_a.long_name = "boundary conditions of mass fraction profile: a bins"
245          nc_lsf_mf_a.standard_name = namev
246
247          for i in range( len( ls_dims[nvi] ) ):
248            dummy[:,:,i,:] = lsf_mf_b
249          namev = 'ls_forcing_{}_mass_fracs_b'.format( nv )
250          nc_lsf_mf_b = self.nc_file.createVariable( namev, 'f4', ('time','z',ls_dimsname[nvi],
251                                                                   'composition_index',),
252                                                     fill_value=-9999.0 )
253          nc_lsf_mf_b[:] = dummy
254          nc_lsf_mf_b.long_name = "boundary conditions of mass fraction profile: b bins"
255          nc_lsf_mf_b.standard_name = namev
256
257          nvi += 1
258
259        dummy = np.zeros( [ self.ntime, self.ny+1, self.nx+1, self.ncomposition_index ] )
260        for j in range( self.ny ):
261          for i in range( self.nx ):
262            dummy[:,j,i,:] = lsf_mf_a[:,-1,:]
263        namev = 'ls_forcing_top_mass_fracs_a'
264        nc_lsf_mf_a = self.nc_file.createVariable( namev, 'f4', ('time','y','x','composition_index',), 
265                                                   fill_value=-9999.0 )
266        nc_lsf_mf_a[:] = dummy
267        nc_lsf_mf_a.long_name = "boundary conditions of mass fraction profile: a bins"
268        nc_lsf_mf_a.standard_name = namev
269
270        for j in range( self.ny ):
271          for i in range( self.nx ):
272            dummy[:,j,i,:] = lsf_mf_b[:,-1,:]
273        namev = 'ls_forcing_top_mass_fracs_b'
274        nc_lsf_mf_b = self.nc_file.createVariable( namev, 'f4', ('time','y','x','composition_index',), 
275                                                   fill_value=-9999.0 )
276        nc_lsf_mf_b[:] = dummy
277        nc_lsf_mf_b.long_name = "boundary conditions of mass fraction profile: b bins"
278        nc_lsf_mf_b.standard_name = namev
279
280
281        nvi = 0
282        for nv in ls_names:
283          dummy = np.zeros( [ self.ntime, self.nz, len( ls_dims[nvi] ), len( self.ndmid ) ] )
284          for i in range( len( ls_dims[nvi] ) ):
285            dummy[:,:,i,:] = lsf_psd
286          namev = 'ls_forcing_{}_aerosol'.format( nv )
287          nc_lsf_psd = self.nc_file.createVariable( namev, 'f4', ('time','z',ls_dimsname[nvi],'Dmid',), 
288                                                    fill_value=-9999.0 )
289          nc_lsf_psd[:] = dummy
290          nc_lsf_psd.long_name = 'boundary condition of aerosol concentration'
291          nc_lsf_psd.standard_name = namev
292          nc_lsf_psd.unit = '#/m3'
293          nc_lsf_psd.lod = 1
294          nvi += 1
295
296        dummy =  np.zeros( [ self.ntime, self.ny+1, self.nx+1, len( self.ndmid ) ], dtype=float ) 
297        for j in range( self.ny ):
298          for i in range( self.nx ):
299            dummy[:,j,i,:] = lsf_psd[:,-1,:]
300        namev = 'ls_forcing_top_aerosol'
301        nc_lsf_psd = self.nc_file.createVariable( 'ls_forcing_top_aerosol', 'f4', 
302                                                 ('time','y','x','Dmid',), fill_value=-9999.0 )
303        nc_lsf_psd[:] = dummy
304        nc_lsf_psd.long_name = 'boundary condition of aerosol concentration'
305        nc_lsf_psd.standard_name = 'ls_forcing_top_aerosol'
306        nc_lsf_psd.unit = '#/m3'
307        nc_lsf_psd.lod = 1
308        nvi += 1
309
310
311    def finalize(self):
312        """ Close file """
313        print("Closing file...")
314       
315        self.nc_file.close()
316
317def define_bins( nbin, reglim ):
318    """ This function defines the sectional bin limits based on number of bins
319        (nbin) and diameter limits (reglim) per subrange
320    """
321
322    nbins = np.sum( nbin ) # = subrange 1 + subrange 2
323
324    # Log-normal to sectional
325
326    vlolim = np.zeros( nbins )
327    vhilim = np.zeros( nbins )
328    dmid   = np.zeros( nbins )
329    bin_limits = np.zeros( nbins )
330
331    # Sectional bin limits
332    ratio_d = reglim[1] / reglim[0]
333    for b in range( nbin[0] ):
334      vlolim[b] = np.pi / 6.0 * ( reglim[0] * ratio_d **( float(b) / nbin[0] ) )**3
335      vhilim[b] = np.pi / 6.0 * ( reglim[0] * ratio_d **( float(b+1) / nbin[0] ) )**3
336      dmid[b] = np.sqrt( ( 6.0 * vhilim[b] / np.pi )**0.33333333 * ( 6.0 * vlolim[b] / np.pi
337                                                                    )**0.33333333 )
338
339    ratio_d = reglim[2] / reglim[1]
340    for b in np.arange( nbin[0], np.sum( nbin ),1 ):
341      c = b-nbin[0]
342      vlolim[b] = np.pi / 6.0 * ( reglim[1] * ratio_d ** ( float(c) / nbin[1] ) )**3
343      vhilim[b] = np.pi / 6.0 * ( reglim[1] * ratio_d ** ( float(c+1) / nbin[1] ) ) ** 3
344      dmid[b] = np.sqrt( ( 6.0 * vhilim[b] / np.pi )**0.33333333 * ( 6.0 * vlolim[b] / np.pi
345                                                                    )**0.33333333 )
346
347    bin_limits = ( 6.0 * vlolim / np.pi )**0.33333333
348    bin_limits = np.append( bin_limits, reglim[-1] )
349
350    return dmid, bin_limits
351
352
353if __name__ == '__main__':
354    driver = SalsaDynamicDriver()
355    driver.write_global_attributes()
356    driver.define_dimensions()
357    driver.add_variables()
358    driver.finalize()
359