1module obsOperatorsChem_mod
2 ! MODULE obsOperatorsChem_mod (prefix='oopc' category='5. Observation operators')
3 !
4 !:Purpose: Observation operators for CH obs family, including nonlinear, tangent-linear
5 ! and adjoint versions, and related setup and input routines.
6 !
7 use earthConstants_mod
8 use mathPhysConstants_mod
9 use obsSpaceData_mod
10 use columnData_mod
11 use bufr_mod
12 use physicsFunctions_mod
13 use midasMpi_mod
14 use utilities_mod
15 use varNameList_mod
16 use obsSubSpaceData_mod
17 use obsFiles_mod
18 use codtyp_mod
19 use ozoneClim_mod
20 use presProfileOperators_mod
21 use timeCoord_mod
22 use bCovarSetupChem_mod
23
24 implicit none
25 save
26 private
27
28 ! public procedures
29 public :: oopc_CHobsoperators, oopc_diagnOnly, oopc_addEfftempObsfile
30
31 !-------------------------------------------------------------------------
32 ! Various structures and parameters for the CH family
33
34 ! module structures
35 ! -----------------
36
37 type :: struct_oopc_obsoperators
38
39 ! Structure holding work variables for individual observation operator
40 ! applications
41 !
42 ! Variable Description
43 ! -------- -----------
44 ! nobslev Number of observations in the profile
45 ! nmodlev Number of model levels in the column
46 ! varno BUFR descriptor element for obs units
47 ! constituentId BUFR code element of local GRIB Table 08046 identifying the constituent
48 ! (similar to BUFR Table 08043)
49 ! operatorCategory CH obs operator category. One of the following.
50 ! 'Interp': interpolators
51 ! 'Surface': surface point operators
52 ! 'Integ': integration operators
53 ! 'LayerAvg': layer averaging operators
54 ! Automatically assigned based on obs BUFR element
55 ! and, when relevant, assocciated obsinfo_chm content.
56 ! layerIdentified .true. if a layer (with identified layer boundaries)
57 ! .false if layer boundaries are not available.
58 ! valertop Layer top (final work values in Pa)
59 ! vlayerbottom Layer bottom (final work values in Pa)
60 ! zh Initial innovation model array (other than conversion constants)
61 ! zhp Part of innovation operator not related to resolution.
62 ! modlevindexTop Top level of non-zero values in zh
63 ! modlevindexBot Bottom level of non-zero values in zh
64 ! trial Trial (background) profile on model levels at observation location
65 ! tt Temperature profile on model levels (Kelvin)
66 ! hu Specific humidity on model levels
67 ! height Height on model levels (m)
68 ! pp Pressure on model levels (Pa)
69 ! lat Latitude of observation (radians)
70 ! lon Longitude of observation (radians)
71 ! obslev Observation profile level values (OBS_PPP)
72 ! obsSpaceTrial obs estimate on obs levels based on trial/background profile
73 ! varName Variable/obs nomvar
74 ! stnid Observation station ID
75 ! date YYYYMMDD (date of obs)
76 ! hhmm HHMM (time of obs)
77 ! obs_index Observation index
78 ! Note: Depending on the data of interest, the index of a required array element or
79 ! profile associated to an observation can be identified from (lat,long,date,hhmm,
80 ! stnid, optional task-dependent identifier if needed) or "obs_index".
81 ! The latter is for associations of data identified within
82 ! processing of individual CPUs. Each of the two index identifiers is represented
83 ! by the unique character string identifier 'code' of struct_oss_obsdata
84 ! (e.g. see obsdata_get_header_code_r for use of (lat,long,date,hhmm,stnid)).
85 ! vco Index of vertical coord type for obs
86 ! 1 - Altitudes (m)
87 ! 2 - Pressure (Pa)
88 ! 3 - Channel index
89 ! 4 - not provided with obs. Obs is for total column values.
90 ! 5 - not provided with obs. Obs is a surface point value.
91 ! iavgkern Integer indicating if averaging kernels are to be applied. Value
92 ! of zero indicates no averaging kernel to be applied. Non-zero value
93 ! indicates index in oopc_avgkern%obsSubSpace arrays.
94 ! applyGenOper Indicates if the generalized observation operator should be applied
95 ! columnBound Boundary imposed on a column measurement
96 ! ixtr Indicates when outside model vertical range (when >0)
97 ! success Indicates if the observation is to be assimilated
98 ! or was successfully assimilated
99
100 integer :: nobslev,nmodlev,constituentId,vco,varno,date,hhmm,iavgkern,obs_index
101 logical :: layerIdentified,applyGenOper
102 real(8) :: lat,lon,columnBound
103 character(len=12) :: stnid
104 character(len=10) :: operatorCategory
105 character(len=4) :: varName
106 real(8), allocatable :: vlayertop(:),vlayerbottom(:),tt(:),height(:),pp(:)
107 real(8), allocatable :: zh(:,:),zhp(:,:),obslev(:),hu(:)
108 integer, allocatable :: ixtr(:)
109 logical, allocatable :: success(:)
110 real(8), pointer :: trial(:)
111 integer, allocatable :: modlevindexTop(:),modlevindexBot(:)
112 real(8), allocatable :: obsSpaceTrial(:)
113
114 end type struct_oopc_obsoperators
115
116 type(struct_oopc_obsoperators) :: obsoper
117
118 type :: struct_oopc_operatorsDepot
119
120 ! Structure holding saved arrays for observation operators
121 ! A subset of 'struct_oopc_obsoperators'
122 !
123 ! Variable Description
124 ! -------- -----------
125 ! nobslev Number of observations in the profile
126 ! valertop Layer top (final work values in Pa)
127 ! vlayerbottom Layer bottom (final work values in Pa)
128 ! zh Initial innovation model array (other than conversion constants)
129 ! zhp Part of innovation operator not related to resolution.
130 ! modlevindexTop Top level of non-zero values in zh
131 ! modlevindexBot Bottom level of non-zero values in zh
132 ! trial Trial (background) profile on model levels at observation location
133 ! iavgkern Integer indicating if averaging kernels are to be applied. Value
134 ! of zero indicates no averaging kernel to be applied. Non-zero value
135 ! indicates index in oopc_avgkern%obsSubSpace arrays.
136 ! applyGenOper Indicates if the generalized observation operator should be applied
137 ! ixtr Indicates when outside model vertical range (when >0)
138 ! success Indicates if the observation is to be assimilated
139 ! or was successfully assimilated
140
141 integer :: nobslev,iavgkern
142 logical :: applyGenOper
143 real(8), allocatable :: vlayertop(:),vlayerbottom(:)
144 real(8), allocatable :: zh(:,:),zhp(:,:)
145 integer, allocatable :: ixtr(:)
146 logical, allocatable :: success(:)
147 real(8), pointer :: trial(:)
148 integer, allocatable :: modlevindexTop(:),modlevindexBot(:)
149
150 end type struct_oopc_operatorsDepot
151
152 type :: struct_oopc_info
153
154 ! Information arrays retrieved from auxiliary file regarding vertical levels
155 ! or averaging kernels
156 !
157 ! Variable Description
158 ! -------- -----------
159 ! n_stnid Number of sub-families (identified via STNIDs)
160 ! stnids Sub-families (STNIDs; * are wild cards)
161 ! element BUFR element in data block
162 ! profElement BUFR element of profile required by obs operator if
163 ! if it differs from 'element' (e.g. see 'n_col' and 'type' below).
164 ! source 0: Set entirely from the auxiliary file being read. No
165 ! initial values read from observation files
166 ! 1: Initial values in observation files for constant number
167 ! of vertical levels (may be adjusted after input)
168 ! vco Index of vertical coord type for obs
169 ! 1 - Altitudes (m)
170 ! 2 - Pressure (Pa)
171 ! 3 - Channel index
172 ! 4 - not provided with obs. Obs is for total column values.
173 ! 5 - not provided with obs. Obs is a surface point value.
174 ! 6 - sigma vertical coordinate (*Psfc to give levels in Pa); in this module only.
175 ! ibegin Position index of start of data for given
176 ! sub-family.
177 ! n_lvl Lengh of corresponding obs profile (number of rows)
178 ! n_col Length of array for each obs element (number of columns; optional)
179 ! - Usually n_lvl will be the number of vertical levels of profiles
180 ! and n_col is not needed.
181 ! - For averaging kernels:
182 ! - When the obs are single level data, n_lvl=1 or 2. If it requires more levels
183 ! or data for observation operators, n_col = number of additional data
184 ! - The a-priori contribution (I-A)xa can be added as the element on each row.
185 ! When done and n_lvl>1, n_col would be expected to be n_lvl+1
186 ! - If an integeration over the n_lvl values (>1) needs to be performed,
187 ! the column n_col = n_lvl+2 provides the integration weights for a summations
188 ! that gives the integral.
189 ! - For vertical levels:
190 ! - n_col will be set to 2 as default for layer boundaries
191 ! - n_col needs to be set to 1 to use this simply as a single vertical level profile
192 ! n_lat Number of latitudes
193 ! lat Latitudes (degrees; ordered in increasing size)
194 !
195 ! vlayertop Layer top
196 ! vlayerbottom Layer bottom
197 ! rak Averaging kernel matrices
198 ! type Operation subtypes.
199 !
200 ! Currently relevant only for averaging kernels:
201 !
202 ! Type name Description
203 ! ============================================================
204 ! 'default' No special treatment (default)
205 ! 'log' Application of log-space averaging kernel
206 ! ============================================================
207 !
208 integer :: n_stnid
209 character(len=12), allocatable :: stnids(:)
210 character(len=15), allocatable :: type(:)
211 integer, allocatable :: element(:),source(:),profElement(:)
212 integer, allocatable :: vco(:),n_lat(:)
213 integer, allocatable :: ibegin(:),n_lvl(:),n_col(:)
214 real(8), allocatable :: rak(:),vlayertop(:),vlayerbottom(:)
215 real(8), allocatable :: lat(:)
216
217 type(struct_oss_obsdata), allocatable :: obsSubSpace(:)
218
219 end type struct_oopc_info
220
221 type(struct_oopc_info) :: oopc_levels
222 type(struct_oopc_info) :: oopc_avgkern
223
224 ! Arrays for integration upper boundary of retrieved total column measurements
225 type(struct_oss_obsdata) :: oopc_columnBoundary
226
227 ! File name of auxiliary text file constaining supplemental observation information
228 character(len=50), parameter :: oopc_aux_filename="obsinfo_chm"
229
230 ! Max nummber of constituents (max size of related arrays)
231 integer, parameter :: oopc_constituentsSize=30 ! = max allowed value of "iconstituentId" for Table 08046.
232 ! Value to be increased as needed up to a max of 6999 as values
233 ! > 7000 (and less 0) are assumed assigned to non-constituent fields
234
235 ! Arrays containing input reference fields and fields interpolated
236 ! to obs locations
237
238 type :: struct_oopc_field
239
240 ! Structure for storing reference (climatological) fields needed for
241 ! operatorSubType(2,:) == 'genOper' with
242 ! genOperConstraintType == 'Diff' (see below).
243 !
244 ! Variable Description
245 ! -------- -----------
246 ! field Gridded 3D field (lon,lat,vlev) or 2D field (1,lat,vlev)
247 ! nlat number of latitudes
248 ! nlon number of longitudes
249 ! nlev number of vertical levels
250 ! lat,lon lat,lon grid in radians
251 ! vlev vertical levels
252 ! ivkind Index of vertical coordinate type. Defintion may vary according to source.
253 ! For fields read from RPN files and use of convip:
254 ! 0: P is in height [m] (metres) with respect to sea level
255 ! 1: P is in stddev [sg] (0.0 -> 1.0)
256 ! 2: P is in pressure [mb] (millibars)
257 ! 3: P is in an arbitrary code
258 ! 4: P is in height [M] (metres) with respect to ground level
259 ! 5: P is in hybrid coordinates [hy]
260 ! 6: P is in theta [th]
261 ! For use with obs
262
263 real(8), allocatable :: field(:,:,:),lat(:),lon(:),vlev(:)
264 integer :: nlev,nlon,nlat,ivkind
265
266 end type struct_oopc_field
267
268 type(struct_oss_obsdata) :: oopc_bgRef
269 type(struct_oopc_field) :: oopc_climFields(0:oopc_constituentsSize,2)
270
271 ! Arrays to contain the calculated concentration-weighted effective temperature
272 ! associated to total column data. It will be stored in the observation file.
273
274 type(struct_oss_obsdata) :: oopc_efftemp
275
276 type(struct_bcsc_bgStats) :: bgStats ! Background covariances
277
278 ! Setup initialization key
279 logical :: initializedChem = .false.
280
281 ! Operator storage initialization key
282 logical :: initializedOperators = .false.
283
284 ! General config/setup information parameters
285 ! See description list of NAMCHEM namelist parameters and others
286 ! in routine oopc_readNamchem.
287 ! Following variables/parameters could be placed in a data structure/type
288 ! (e.g. struct_oopc_nmlparm)
289
290 character(len=5) :: oopc_genOperConstraintType(0:oopc_constituentsSize)
291 real(8) :: oopc_genOperHCorrlenExpnt(0:oopc_constituentsSize)
292 real(8) :: oopc_genOperOmAStatsFactor(0:oopc_constituentsSize)
293 integer :: oopc_tropo_mode(0:oopc_constituentsSize),oopc_tropo_bound(0:oopc_constituentsSize)
294 integer :: oopc_obsdata_maxsize
295 real(8) :: oopc_tropo_column_top(0:oopc_constituentsSize)
296 logical :: oopc_storeOperators
297
298 integer, parameter :: assim_maxfamnum=1 ! Could be used for other families as well with >1
299 integer, parameter :: assim_maxsize=100 ! max size of assim_* arrays
300
301 integer :: assim_famNum
302 character(len=2) :: assim_fam(assim_maxfamnum) ! List of families to which filt_diagnOnly is to apply
303 logical :: assim_all(assim_maxfamnum) ! Choose to assimilate all obs of specified family
304 integer :: assim_num(assim_maxfamnum) ! Number of combinations identified for assimilation
305 integer :: assim_varno(assim_maxfamnum,assim_maxsize) ! List of bufr elements to assimilate (0 means all)
306 integer :: assim_nlev(assim_maxfamnum,assim_maxsize) ! 0: multi- and uni-lev; 1: uni-lev; >1 multi-lev
307 integer :: assim_exclude_nflag(assim_maxfamnum) ! List of bits for excluding obs from assimilation
308 integer :: assim_exclude_flag(assim_maxfamnum,assim_maxsize) ! Number of bits for excluding obs
309 character(len=9) :: assim_stnid(assim_maxfamnum,assim_maxsize) ! List of stnid to assimilation '*' for wild card
310 character(len=20) :: operatorSubType(2,assim_maxsize) ! Operator sub-type name
311 character(len=10) :: modelName = 'GEM-MACH' ! Identification of the model
312
313 !**************************************************************************
314
315 contains
316
317 !--------------------------------------------------------------------------
318 ! oopc_setupCH
319 !--------------------------------------------------------------------------
320 subroutine oopc_setupCH(kmode)
321 !
322 !:Purpose: To set up additional information required by constituent obs and
323 ! not provided in obsSpaceData. Also to assign observation layer
324 ! top and bottom levels (and averaging kernel matrices).
325 ! See 'oopc_CHobsoperators'.
326 !
327 implicit none
328
329 ! Arguments:
330 integer, intent(in) :: kmode ! Mode of observation operator
331
332 ! Locals:
333 logical :: success
334
335 write(*,*) 'Begin oopc_setupCH'
336
337 ! Read NAMCHEM namelist and set related parameters
338
339 call oopc_readNamchem
340 write(*,*) 'oopc_setupCH: Completed oopc_readNamchem'
341
342 ! Read reference vertical levels (where needed) or
343 ! top and bottom layer boundaries of partial (or total) column meausurements
344
345 call oopc_readLevels
346 write(*,*) 'oopc_setupCH: Completed oopc_readLevels'
347
348 ! To deallocate space if required elsewhere, one should use
349 ! call oopc_deallocLevels
350
351 ! Read averaging kernel matrices
352
353 call oopc_readAvgkern
354 write(*,*) 'oopc_setupCH: Completed oopc_readAvgkern'
355
356 ! To deallocate space if required elsewhere, one should use
357 ! call oopc_deallocAvgkern
358
359 ! Read reference (e.g. climatological) fields
360
361 call oopc_readFields(oopc_climFields,oopc_aux_filename,'CH', &
362 oopc_constituentsSize,2,oopc_genOperConstraintType, &
363 success,filetype_opt='TXT')
364 if ( .not. success ) then
365 call utl_abort('oopc_setupCH: Failed in oopc_readFields')
366 end if
367 write(*,*) 'oopc_setupCH: Completed oopc_readFields'
368
369 ! Allocation of oopc_efftemp done in oopc_setupCH instead of obsdata_add_data1d
370 ! to ensure allocation is done for all processors, including those without associated data.
371 ! This is to ensure that rpn_comm_allgather will work in routine obsdata_MPIGather.
372
373 if (.not.associated(oopc_efftemp%data1d)) then
374 call oss_obsdata_alloc(oopc_efftemp,oopc_obsdata_maxsize,dim1=1)
375 oopc_efftemp%nrep=0
376 end if
377
378 ! Get background error std dev stats when they may be required
379
380 bgStats%initialized = .false.
381
382 if (kmode == 1 .or. any(operatorSubType(2,:) == 'genOper') ) then
383 call bcsc_getCovarCH(bgStats)
384 end if
385
386 write(*,*) 'Completed oopc_setupCH'
387
388 end subroutine oopc_setupCH
389
390 !--------------------------------------------------------------------------
391 ! oopc_readNamchem
392 !--------------------------------------------------------------------------
393 subroutine oopc_readNamchem
394 !:Purpose: Read and store miscellaneous flags and constants.
395 !
396 !:Comment: assim_* arrays could instead be made available to all families
397 ! by moving them to a different input namelist (and changing its
398 ! dimensions settings).
399 !
400 !
401 ! :genOperConstraintType:
402 ! Reference profile type for weighted integration
403 ! or layer averaging (generalized observation)
404 ! operator.
405 ! Relevant for operatorSubType(2,i)='genOper'.
406 ! ================================================
407 ! 'Trial' use trial field xb for mass weighted
408 ! increment distribution
409 ! 'Diff' use a combination of the difference of
410 ! an external reference xc and the trial
411 ! field xb, i.e. mass weighted increment
412 ! distribution as a(xc-xb) + b*xc where a
413 ! and b depend on the size of
414 ! sum[(xc-xb)/sig(xb)]^2 over the profile
415 ! ==============================================
416 !
417 ! :genOperHCorrlenExpnt: Used with operatorSubType(2,i) ='genOper'
418 ! Exponent for partially mitigating the effect of
419 ! the influence of neighbouring column amonunt obs
420 ! from background error correlations.
421 ! Emperically obtained exponent value.
422 ! Not optimal for all possible local horizontal data densities.
423 !
424 ! :genOperOmAStatsFactor: OmA RMS (or std dev) conservation factor for
425 ! operatorSubType(2,i) ='genOper'.
426 !
427 !
428 ! :assim_fam: List of families to which filt_diagnOnly is to
429 ! apply.
430 !
431 ! :assim_exclude_flag: Array specifying bits for identifying
432 ! diagnostic-only observations for observations
433 ! that would otherwise be assimilated according to
434 ! the other assim_* arrays
435 !
436 ! :assim_exclude_nflag: Number of bit flags to specify in
437 ! assim_exclude_flag array
438 !
439 ! :assim_all: Logical indicating if all assimilatable obs of
440 ! the specified family will be assimilated
441 ! (default is .true.)
442 ! When assim_all is .false., account for the setttings
443 ! of assim_num, assim_varno, assim_stnid, assim_nlev.
444 !
445 ! :assim_num: Relevant when assim_all = ,false.
446 ! Number combinations (stnid, bufr element,
447 ! multi/uni-level) identified for assimilation.
448 ! All others will not be assimilated. OmP and OmA
449 ! diagnostics and output will still be produced
450 ! for non-assimilated datasets.
451 !
452 ! === =======================================
453 ! 0 none are to be assimilated when assim_all
454 ! is .false. (default)
455 ! >0 sets of (stnid, bufr varno,
456 ! multi/uni-levels) to be assimilated
457 ! === =======================================
458 !
459 ! :assim_varno: Bufr elements of obs sets for assimilation. A
460 ! value of 0 implies that all are to be used.
461 !
462 ! :assim_stnid: Stnids of obs sets for assimilation. '*' denote
463 ! wild cards
464 !
465 ! :assim_nlev: === =========================
466 ! 0 multi-level and uni-level
467 ! 1 uni_level
468 ! >1 multi-level
469 ! === =========================
470 !
471 ! :tropo_mode: Integer indicating if special treatment is to be
472 ! given to the troposphere when assimilating total
473 ! column measurements. Values indicate
474 ! === =======================================
475 ! 0 No special treatment given (default)
476 ! 1 Values of the adjoint model above
477 ! obsoper%columnBound set to zero. If
478 ! specified, generalized innovation
479 ! operator only applied below
480 ! obsoper%columnBound in the tangent
481 ! linear model.
482 ! 2 Values of tangent linear model and
483 ! adjoint model above obsoper%columnBound
484 ! set to zero.
485 ! === =======================================
486 ! Array index refers to BUFR code element of Table
487 ! 08046 (iconstituentId) identifying the
488 ! constituent. Relevant for total column
489 ! measurements only.
490 !
491 ! :tropo_bound: Integer indicating which column top value to use
492 ! if tropo_mode is non-zero.
493 ! === =======================================
494 ! 0 Use fixed value of tropo_column_top
495 ! 1 Use model determination of tropopause
496 ! 2 Use model determination of PBL
497 ! === =======================================
498 ! Options 1 and 2 will default to the value set
499 ! in tropo_column_top if the model derived column
500 ! top could not be determined. Relevant for total
501 ! column measurements only.
502 !
503 ! :tropo_column_top: Default value to use for the column boundary
504 ! (in Pa). Array index refers to BUFR code element
505 ! of Table 08046 (iconstituentId) identifying
506 ! the constituent. Relevant for total column
507 ! measurements only.
508 !
509 ! :obsdata_maxsize: Max allowed size of work arrays (in terms of
510 ! number of obs) associated to ordered observation
511 ! indices
512 !
513 !
514 ! :modelName: Identifier of forecast model
515 ! Default: 'GEM-MACH'
516 ! Set to 'GEM' for varNames of 'O3L', 'CH4L', and 'N2OL'
517 !
518 ! :operatorSubType(2,i):Operator sub-type name.
519 ! Index (2,i) for sub-type to apply for stnid in element (1,i)
520 ! See related "obsoper@operatorCategory" automatically assigned
521 ! based on obs BUFR element and obsinfo_chm content.
522 !
523 ! Operator Sub-type name Description
524 ! Category
525 ! ============= =====================================================================
526 ! 'Interp' 'default' Piecewise linear interpolation (default)
527 ! 'wgtAvg' Piecewise weighted averaging interpolator
528 ! 'Surface' 'default' No special treatment (default)
529 ! 'Integ' 'default' Simple/basic vertical integration (default)
530 ! 'genOper' Weighted vertical integration - see 'genOper*' parameters
531 ! 'LayerAvg' 'default' Simple layer averaging (default)
532 ! 'genOper' Weighted vertical layer averaging - see 'genOper*' parameters
533 ! ====================================================================================
534 !
535 ! Notes:
536 !
537 ! - 'genOper' requires NAMBCHM namelist parameter settings
538 ! getPhysSpaceStats=.true. and
539 ! getPhysSpaceHCorrel=.true.
540 ! - Application of averaging kernels is directed only
541 ! by the content of the obsinfo_chm file 'SECTION III'
542 !
543 ! :storeOperators: Logical indicating if linear operators are stored for re-use in TL and AD calc.
544 ! If so, the linear operators will not be re-calculated at different iterations.
545 ! Not used when tropo_mode>=1
546 !
547 implicit none
548
549 ! Locals:
550 integer :: fnom, fclos
551 integer :: ierr, ios, nulnam, i
552 character(len=10) :: namfile
553
554 ! Namelist variables (local):
555 integer :: tropo_mode(0:oopc_constituentsSize) ! Special treatment for troposphere of total column obs
556 integer :: tropo_bound(0:oopc_constituentsSize) ! Indicate which column top value used for special treatment
557 real(8) :: tropo_column_top(0:oopc_constituentsSize) ! Default for column boundary (in Pa) of total column obs
558 logical :: storeOperators ! Choose to store linear operators for re-use in TL/AD
559 character(len=5) :: genOperConstraintType(0:oopc_constituentsSize) ! Strong constraint for generalized obs operator (see oopc_genOper)
560 real(8) :: genOperHCorrlenExpnt(0:oopc_constituentsSize) ! Exponent for horiz. correl. length weighting in oopc_genOper
561 real(8) :: genOperOmAStatsFactor(0:oopc_constituentsSize) ! Additional OmAStats normalization factor for oopc_genOper
562 integer :: obsdata_maxsize ! Max number of obs associated with ordered obs indices
563
564 external fnom,fclos
565
566 namelist /namchem/ assim_fam,assim_all,assim_num,assim_stnid,assim_varno, &
567 assim_nlev, assim_exclude_nflag,assim_exclude_flag, &
568 tropo_mode,tropo_bound,tropo_column_top,obsdata_maxsize, &
569 modelName,operatorSubType,storeOperators, &
570 genOperConstraintType,genOperHCorrlenExpnt,genOperOmAStatsFactor
571
572 ! Default NAMCHEM values
573
574 genOperConstraintType(:)='Trial'
575 genOperHCorrlenExpnt(:)=1.7
576 genOperOmAStatsFactor(:)=1.0
577
578 obsdata_maxsize=90000
579
580 storeOperators=.false.
581 assim_fam(:)=''
582 assim_fam(1)='CH'
583 assim_famNum=1
584 assim_all(:)=.true.
585 assim_num(:)=0
586 assim_stnid(:,:)='*********'
587 assim_varno(:,:)=0
588 assim_nlev(:,:)=0
589
590 assim_exclude_nflag(:)=1
591 assim_exclude_flag(:,1)=6 ! this is for the 'in reserve' bit for a BURP marker
592 assim_exclude_flag(:,2:)=0
593
594 tropo_mode(:) = 0
595 tropo_bound(:) = 0
596 tropo_column_top(:) = 0.0
597
598 operatorSubType(1,:) = ''
599 operatorSubType(2,:) = 'default'
600
601 ! Read from namelist file NAMCHEM
602
603 namfile=trim("flnml")
604 nulnam=0
605 ierr=fnom(nulnam,namfile,'R/O',0)
606
607 read(nulnam,nml=namchem,iostat=ios)
608 if (ios < -4 .or. ios > 0) then
609 call utl_abort('oopc_readNamchem: Error in reading NAMCHEM namelist. iostat = ' // trim(utl_str(ios)) )
610 else if (mmpi_myid == 0) then
611 write(*,nml=namchem)
612 end if
613
614 ierr=fclos(nulnam)
615
616 do i=size(assim_fam),1,-1
617 if (assim_fam(i) /= '') exit
618 end do
619 assim_famnum=i
620
621 oopc_genOperConstraintType(:) = genOperConstraintType(:)
622 oopc_genOperHCorrlenExpnt(:) = genOperHCorrlenExpnt(:)
623 oopc_genOperOmAStatsFactor(:) = genOperOmAStatsFactor(:)
624 oopc_tropo_mode(:) = tropo_mode(:)
625 oopc_tropo_bound(:) = tropo_bound(:)
626 oopc_tropo_column_top(:) = tropo_column_top(:)
627 oopc_storeOperators = storeOperators
628 oopc_obsdata_maxsize=obsdata_maxsize
629
630 end subroutine oopc_readNamchem
631
632 !--------------------------------------------------------------------------
633 !------- Routines related to reference or layer top & bottom levels -------
634
635 !--------------------------------------------------------------------------
636 ! oopc_readLevels
637 !--------------------------------------------------------------------------
638 subroutine oopc_readLevels
639 !
640 !:Purpose: To read and to store reference levels (where needed) or top- and
641 ! bottom-layer boundaries for CH sub-families.
642 !
643 implicit none
644
645 ! Locals:
646 integer :: fnom, fclos
647 integer :: ierr, jlev, jelm, nulstat, ios, isize, icount
648 logical :: LnewExists,newread
649 character (len=128) :: ligne
650 external :: fnom,fclos
651
652 ! Initialization
653
654 oopc_levels%n_stnid=0
655
656 inquire(file=trim(oopc_aux_filename),EXIST=LnewExists)
657 if (.not.LnewExists )then
658 write(*,*) '---------------------------------------------------------------'
659 write(*,*) 'oopc_readLevels: COULD NOT FIND AUXILIARY file ' // trim(oopc_aux_filename)
660 write(*,*) '---------------------------------------------------------------'
661 return
662 end if
663
664 ! Check for available layer info.
665
666 nulstat=0
667 ierr=fnom(nulstat,trim(oopc_aux_filename),'SEQ',0)
668 if ( ierr == 0 ) then
669 open(unit=nulstat, file=trim(oopc_aux_filename), status='OLD')
670 else
671 call utl_abort('oopc_readLevels: COULD NOT OPEN AUXILIARY file ' // trim(oopc_aux_filename))
672 end if
673
674 ios=0
675 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
676 do while (trim(adjustl(ligne(1:13))) /= 'SECTION II:')
677 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
678 end do
679
680 ! Read number of observation set sub-families (STNIDs and ...) and allocate space
681
682 read(nulstat,*,iostat=ios,err=10,end=10) oopc_levels%n_stnid
683 read(nulstat,*,iostat=ios,err=10,end=10) isize
684
685 allocate(oopc_levels%stnids(oopc_levels%n_stnid))
686 allocate(oopc_levels%vco(oopc_levels%n_stnid))
687 allocate(oopc_levels%source(oopc_levels%n_stnid),oopc_levels%ibegin(oopc_levels%n_stnid))
688 allocate(oopc_levels%element(oopc_levels%n_stnid),oopc_levels%n_lvl(oopc_levels%n_stnid))
689 allocate(oopc_levels%vlayertop(isize),oopc_levels%vlayerbottom(isize))
690 allocate(oopc_levels%n_col(oopc_levels%n_stnid))
691
692 oopc_levels%element(:)=0
693 oopc_levels%vco(:)=0
694 oopc_levels%source(:)=0
695 oopc_levels%n_lvl(:)=1
696 oopc_levels%n_col(:)=2
697
698 ! Begin reading for each sub-family
699 ! Important: Combination of STNID, BUFR element and number of vertical levels
700 ! to determine association to the observations.
701
702 icount=0
703 do jelm=1,oopc_levels%n_stnid
704 oopc_levels%ibegin(jelm)=icount+1
705
706 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
707
708 ! Read STNID (* is a wildcard)
709
710 read(nulstat,'(2X,A9)',iostat=ios,err=10,end=10) oopc_levels%stnids(jelm)
711
712 ! Read (1) Obs BUFR element.
713 ! (2) Vertical coord type (1, 2, or 3)
714 ! (3) Flag indication if EOR provided from this auxiliary file or
715 ! to be read from the observation file,
716 ! (4) Number of vertical levels (rows)
717 ! (5) Number of vertical level profiles/columns (optional; default is 2)
718 !
719 ! Important: Combination of STNID, BUFR element and number of vertical levels
720 ! to determine association to the observations.
721
722 newread=.true.
723 read(nulstat,*,iostat=ios,err=20,end=20) oopc_levels%element(jelm),oopc_levels%vco(jelm), &
724 oopc_levels%source(jelm),oopc_levels%n_lvl(jelm),oopc_levels%n_col(jelm)
725 newread=.false.
726 20 if (newread) then
727 backspace(nulstat,iostat=ios,err=10)
728 backspace(nulstat,iostat=ios,err=10)
729 read(nulstat,*,iostat=ios,err=10,end=10) oopc_levels%element(jelm),oopc_levels%vco(jelm), &
730 oopc_levels%source(jelm),oopc_levels%n_lvl(jelm)
731 end if
732
733 if (icount+oopc_levels%n_lvl(jelm) > isize) then
734 call utl_abort('oopc_readLevels: READING PROBLEM. ' // &
735 'Max array size exceeded: ' // &
736 trim(utl_str(isize)))
737 end if
738
739 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
740
741 if ( oopc_levels%n_lvl(jelm) > 0 ) then
742 if (oopc_levels%n_col(jelm) > 1) then
743
744 do jlev=1,oopc_levels%n_lvl(jelm)
745 icount=icount+1
746
747 ! Read top and bottom levels
748
749 read(nulstat,*,iostat=ios,err=10,end=10) &
750 oopc_levels%vlayertop(icount),oopc_levels%vlayerbottom(icount)
751 end do
752
753 else
754
755 do jlev=1,oopc_levels%n_lvl(jelm)
756 icount=icount+1
757
758 ! Read single level
759
760 read(nulstat,*,iostat=ios,err=10,end=10) &
761 oopc_levels%vlayertop(icount)
762
763 end do
764 oopc_levels%vlayerbottom(:)= oopc_levels%vlayertop(:)
765
766 end if
767 end if
768
769 end do
770
771 10 if (ios > 0) then
772 call utl_abort('oopc_readLevels: READING PROBLEM. ' // &
773 'File read error message number: ' // trim(utl_str(ios)))
774 end if
775
776 11 CLOSE(UNIT=nulstat)
777 ierr=fclos(nulstat)
778
779 end subroutine oopc_readLevels
780
781 !--------------------------------------------------------------------------
782 ! oopc_getLevels
783 !--------------------------------------------------------------------------
784 subroutine oopc_getLevels
785 !
786 !:Purpose: To return reference model levels or layer boundaries for an observation.
787 ! Combination of STNID, element variable number and number of vertical
788 ! levels to determine association to the observations. Default values
789 ! for top and bottom layers for total column measurements are to be
790 ! provided.
791 !
792 implicit none
793
794 ! Locals:
795 integer :: nlev ! number of levels in the observation
796 integer :: istnid,stnidIndex,startIndex,levelIndex,level
797 logical :: iset
798 real(8), allocatable :: levelsTop(:),levelsBot(:)
799
800 nlev=obsoper%nobslev
801
802 ! Find stnid with same number of vertical levels, and same BUFR element
803
804 istnid=0
805 obsoper%layerIdentified=.false.
806
807 do stnidIndex=1,oopc_levels%n_stnid
808
809 ! First compare STNID values allowing for * and blanks in
810 ! oopc_levels%stnids(JN) as wildcards
811 iset = utl_stnid_equal(oopc_levels%stnids(stnidIndex),obsoper%stnid)
812
813 ! Check if number of levels, code, and vertical coordinate type are equal.
814 ! If number of levels is one and no vertical coordinate provided for total column measurement (i.e. obsoper%vco == 4),
815 ! then check of vertical coordinate type is disregarded afterwards.
816 if (iset) then
817 if ( obsoper%varno == oopc_levels%element(stnidIndex) .and. &
818 (nlev == oopc_levels%n_lvl(stnidIndex) .or. &
819 (nlev == 1 .and. oopc_levels%n_lvl(stnidIndex) > 1) ) .and. &
820 (obsoper%vco == oopc_levels%vco(stnidIndex) .or. &
821 obsoper%vco == 4) ) then
822
823 istnid=stnidIndex
824 exit
825
826 end if
827 end if
828
829 end do
830
831 if (istnid == 0) then
832 ! If integrated layer information not found, if a total column measurement
833 ! set to defaults, else do nothing
834
835 if (bufr_IsIntegral(obsoper%varno) .and. nlev == 1) then
836 obsoper%layerIdentified=.true.
837 obsoper%vlayertop(1) = obsoper%pp(1)
838 obsoper%vlayerbottom(1) = obsoper%pp(obsoper%nmodlev)
839 end if
840
841 else
842 ! level or layer information has been found in auxiliary file
843
844 if (nlev == oopc_levels%n_lvl(stnidIndex) .and. nlev > 1) then
845 ! layer information has been found in auxiliary file
846 obsoper%layerIdentified=.true.
847 startIndex = oopc_levels%ibegin(stnidIndex)
848 obsoper%vlayertop(:) = oopc_levels%vlayertop(startIndex:startIndex+nlev-1)
849 obsoper%vlayerbottom(:) = oopc_levels%vlayerbottom(startIndex:startIndex+nlev-1)
850 else
851
852 ! Alternative level or layer information has been found
853 ! Must be pressure or sigma
854
855 if (oopc_levels%vco(stnidIndex) /= 2 &
856 .and. oopc_levels%vco(stnidIndex) /= 6) then
857
858 call utl_abort('oopc_getLevels: Cannot handle this vertical coordinate type')
859 end if
860
861 obsoper%nobslev=oopc_levels%n_lvl(stnidIndex)
862
863 if (oopc_levels%n_col(stnidIndex) > 1) obsoper%layerIdentified=.true.
864
865 startIndex = oopc_levels%ibegin(stnidIndex)
866
867 allocate(levelsTop(obsoper%nobslev))
868 allocate(levelsBot(obsoper%nobslev))
869 levelsTop(:) = oopc_levels%vlayertop(startIndex:startIndex+obsoper%nobslev-1)
870 levelsBot(:) = oopc_levels%vlayerbottom(startIndex:startIndex+obsoper%nobslev-1)
871 if (oopc_levels%vco(stnidIndex) == 6) then
872 levelsTop(:) = levelsTop(:)*obsoper%pp(obsoper%nmodlev)
873 levelsBot(:) = levelsBot(:)*obsoper%pp(obsoper%nmodlev)
874 end if
875
876 ! Ensure levels do not go below the surface
877
878 do levelIndex=1,obsoper%nobslev
879 if (levelsBot(levelIndex) >= obsoper%pp(obsoper%nmodlev)) then
880 obsoper%nobslev = levelIndex
881 levelsBot(levelIndex) = obsoper%pp(obsoper%nmodlev)
882 if (levelsTop(levelIndex) >= obsoper%pp(obsoper%nmodlev)) then
883 levelsTop(levelIndex) = obsoper%pp(obsoper%nmodlev)
884 end if
885 exit
886 end if
887 end do
888
889 ! Reset work array sizes and settings
890 deallocate(obsoper%vlayertop,obsoper%vlayerbottom,obsoper%obslev)
891 deallocate(obsoper%modlevindexTop,obsoper%modlevindexBot,obsoper%zh,obsoper%zhp)
892
893 allocate(obsoper%obslev(obsoper%nobslev)) ! Reference vertical levels
894 allocate(obsoper%vlayertop(obsoper%nobslev)) ! Layer tops for layer measurements
895 allocate(obsoper%vlayerbottom(obsoper%nobslev)) ! Layer bottoms for layer measurements
896 allocate(obsoper%modlevindexTop(obsoper%nobslev)) ! Index of highest model level (lowest index) involved with obs element
897 allocate(obsoper%modlevindexBot(obsoper%nobslev)) ! Index of lowest model level (highest index) involved with obs element
898 allocate(obsoper%zh(obsoper%nobslev,obsoper%nmodlev)) ! Local model operator H (excluding conversion constants and horizontal interpolation)
899 allocate(obsoper%zhp(obsoper%nobslev,obsoper%nmodlev)) ! Part of zh that excludes aspects related to vertical resolition
900
901 obsoper%vlayertop(:) = levelsTop(1:obsoper%nobslev)
902 obsoper%vlayerbottom(:) = levelsBot(1:obsoper%nobslev)
903 obsoper%obslev(:) = levelsTop(1:obsoper%nobslev)
904 obsoper%zh(:,:)=0.0d0
905 obsoper%zhp(:,:)=0.0d0
906
907 deallocate(levelsTop,levelsBot)
908
909 end if
910
911 obsoper%modlevindexTop(:)=1
912 startIndex=2
913 do level=1,obsoper%nobslev
914 if ( obsoper%vlayertop(level) < obsoper%pp(2)) then
915 obsoper%modlevindexTop(level)=1
916 else
917 do levelIndex=startIndex,obsoper%nmodlev
918 if (obsoper%vlayertop(level) == obsoper%pp(levelIndex)) then
919 obsoper%modlevindexTop(level)=levelIndex
920 exit
921 else if (obsoper%vlayertop(level) < obsoper%pp(levelIndex)) then
922 obsoper%modlevindexTop(level)=levelIndex-1
923 exit
924 end if
925 end do
926 if (level > 1) then
927 if (obsoper%modlevindexTop(level) < obsoper%modlevindexTop(level-1)) &
928 obsoper%modlevindexTop(level)=obsoper%modlevindexTop(level-1)
929 end if
930 startIndex=levelIndex
931 end if
932 end do
933
934
935 obsoper%modlevindexBot(:)=obsoper%modlevindexTop(:)+1
936 startIndex=obsoper%nmodlev-1
937 do level=obsoper%nobslev,1,-1
938 if ( obsoper%vlayertop(level) > obsoper%pp(obsoper%nmodlev-1)) then
939 obsoper%modlevindexTop(level)=obsoper%nmodlev
940 else
941 do levelIndex=startIndex,1,-1
942 if (obsoper%vlayerbottom(level) == obsoper%pp(levelIndex)) then
943 obsoper%modlevindexBot(level)=levelIndex
944 exit
945 else if (obsoper%vlayerbottom(level) > obsoper%pp(levelIndex)) then
946 obsoper%modlevindexBot(level)=levelIndex+1
947 exit
948 end if
949 end do
950 if (level < obsoper%nobslev) then
951 if (obsoper%modlevindexBot(level) > obsoper%modlevindexBot(level+1)) &
952 obsoper%modlevindexBot(level)=obsoper%modlevindexBot(level+1)
953 end if
954 startIndex=levelIndex
955 end if
956 end do
957
958 end if
959
960 end subroutine oopc_getLevels
961
962 !--------------------------------------------------------------------------
963 ! oopc_deallocLevels
964 !--------------------------------------------------------------------------
965 subroutine oopc_deallocLevels
966 !
967 !:Purpose: To deallocate temporary storage space used for layer info
968 !
969 implicit none
970
971 if (oopc_levels%n_stnid == 0) return
972
973 call oopc_deallocInfo(oopc_levels)
974
975 end subroutine oopc_deallocLevels
976
977 !--------------------------------------------------------------------------
978 !-------------- Routines related to averaging kernel matrices -------------
979
980 !--------------------------------------------------------------------------
981 ! oopc_readAvgkern
982 !--------------------------------------------------------------------------
983 subroutine oopc_readAvgkern
984 !
985 !:Purpose: To read averaging kernels from auxiliary file or observation file
986 !
987 implicit none
988
989 ! Locals:
990 integer, parameter :: ndim=2
991 integer :: istnid
992
993 ! read the averaging kernel information from the auxiliary file
994 call oopc_readAvgkernAuxfile
995
996 ! set size of observation file array
997 allocate(oopc_avgkern%obsSubSpace(oopc_avgkern%n_stnid))
998
999 ! read from observation file
1000 do istnid=1,oopc_avgkern%n_stnid
1001 if (oopc_avgkern%source(istnid) == 1) then
1002
1003 ! retrieve data from stats blocks (with bkstp=14 and block_type='DATA')
1004 oopc_avgkern%obsSubSpace(istnid) = obsf_obsSub_read('CH', &
1005 oopc_avgkern%stnids(istnid),bufr_avgkern,oopc_avgkern%n_lvl(istnid), &
1006 ndim, numColumns_opt=oopc_avgkern%n_col(istnid), &
1007 bkstp_opt=14, block_opt='DATA', match_nlev_opt=.true.)
1008
1009 end if
1010 end do
1011
1012 end subroutine oopc_readAvgkern
1013
1014 !--------------------------------------------------------------------------
1015 ! oopc_readAvgkernAuxfile
1016 !--------------------------------------------------------------------------
1017 subroutine oopc_readAvgkernAuxfile
1018 !
1019 !:Purpose: To read and to store averaging kernel matricesfor CH sub-families
1020 !
1021 !:Comments:
1022 ! - Currently implemented for only one latitude band
1023 !
1024
1025 implicit none
1026
1027 ! Locals:
1028 integer :: fnom, fclos
1029 integer :: ierr, jlev, jelm, nulstat, ios, isize, icount, iend
1030 logical :: LnewExists,newread
1031 character (len=128) :: ligne
1032 external :: fnom,fclos
1033
1034 ! Initialization
1035
1036 oopc_avgkern%n_stnid=0
1037
1038 inquire(file=trim(oopc_aux_filename),EXIST=LnewExists)
1039 if (.not.LnewExists )then
1040 write(*,*) '-------------------------------------------------------'
1041 write(*,*) 'oopc_readAvgkernAuxfile: COULD NOT FIND AUXILIARY file ' // trim(oopc_aux_filename)
1042 write(*,*) '-------------------------------------------------------'
1043 return
1044 end if
1045
1046 ! Check for available layer info.
1047
1048 nulstat=0
1049 ierr=fnom(nulstat,trim(oopc_aux_filename),'SEQ',0)
1050 if ( ierr == 0 ) then
1051 open(unit=nulstat, file=trim(oopc_aux_filename), status='OLD')
1052 else
1053 call utl_abort('oopc_readAvgkernAuxfile: COULD NOT OPEN AUXILIARY file ' // trim(oopc_aux_filename))
1054 end if
1055
1056 ios=0
1057 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
1058 do while (trim(adjustl(ligne(1:14))) /= 'SECTION III:')
1059 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
1060 end do
1061
1062 ! Read number of observation set sub-families (STNIDs and ...) and allocate space
1063
1064 read(nulstat,*,iostat=ios,err=10,end=10) oopc_avgkern%n_stnid
1065 read(nulstat,*,iostat=ios,err=10,end=10) isize
1066
1067 allocate(oopc_avgkern%stnids(oopc_avgkern%n_stnid))
1068 allocate(oopc_avgkern%source(oopc_avgkern%n_stnid))
1069 allocate(oopc_avgkern%ibegin(oopc_avgkern%n_stnid))
1070 allocate(oopc_avgkern%element(oopc_avgkern%n_stnid))
1071 allocate(oopc_avgkern%n_lvl(oopc_avgkern%n_stnid))
1072 allocate(oopc_avgkern%n_col(oopc_avgkern%n_stnid))
1073 allocate(oopc_avgkern%type(oopc_avgkern%n_stnid))
1074 allocate(oopc_avgkern%profElement(oopc_avgkern%n_stnid))
1075 allocate(oopc_avgkern%rak(isize))
1076
1077 oopc_avgkern%element(:)=0
1078 oopc_avgkern%source(:)=0
1079 oopc_avgkern%n_lvl(:)=1
1080 oopc_avgkern%n_col(:)=1
1081 oopc_avgkern%type(:) = 'default'
1082
1083 ! Begin reading for each sub-family
1084 ! Important: Combination of STNID, BUFR element and number of vertical levels
1085 ! to determine association to the observations.
1086
1087 icount=1
1088 STNIDLOOP: do jelm=1,oopc_avgkern%n_stnid
1089 oopc_avgkern%ibegin(jelm)=icount
1090
1091 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
1092
1093 ! Read STNID (* is a wildcard)
1094
1095 read(nulstat,'(2X,A9,1X,A15)',iostat=ios,err=10,end=10) oopc_avgkern%stnids(jelm),oopc_avgkern%type(jelm)
1096 if (trim(oopc_avgkern%type(jelm)) /= 'log') then
1097 oopc_avgkern%type(jelm) = 'default'
1098 end if
1099
1100 ! Read (1) Obs BUFR element.
1101 ! (2) Flag indication if avgkern provided from this auxiliary file or
1102 ! to be read from an observation file,
1103 ! (3) Number of obs in profile (number of rows)
1104 ! (4) Number of columns (optional read; number of obs operator vertical levels with or without the
1105 ! resultant a priori contribution (I-A)xa).
1106 ! If number of rows (n_lvl) equals 1, the actual levels for the columns need to be specified from
1107 ! section II (see routine oopc_readLevels)
1108 !
1109 ! Important: Combination of STNID, BUFR element and number of vertical levels
1110 ! to determine association to the observations.
1111
1112 newread=.true.
1113 read(nulstat,*,iostat=ios,err=20,end=20) oopc_avgkern%element(jelm), &
1114 oopc_avgkern%source(jelm),oopc_avgkern%n_lvl(jelm),oopc_avgkern%n_col(jelm),oopc_avgkern%profElement(jelm)
1115 newread=.false.
1116 20 if (newread) then
1117 backspace(nulstat,iostat=ios,err=10)
1118 backspace(nulstat,iostat=ios,err=10)
1119 read(nulstat,*,iostat=ios,err=10,end=10) oopc_avgkern%element(jelm), &
1120 oopc_avgkern%source(jelm),oopc_avgkern%n_lvl(jelm)
1121 oopc_avgkern%n_col(jelm)=oopc_avgkern%n_lvl(jelm)
1122 oopc_avgkern%profElement(jelm)= oopc_avgkern%element(jelm)
1123 end if
1124
1125 if (icount+oopc_avgkern%n_lvl(jelm) > isize) then
1126 call utl_abort('oopc_readAvgkernAuxfile: READING PROBLEM. ' // &
1127 'Max array size exceeded:' // trim(utl_str(isize)))
1128 end if
1129 read(nulstat,'(A)',iostat=ios,err=10,end=10) ligne
1130
1131 ! disregard data section if values to be specified in BUFR file
1132 if (oopc_avgkern%source(jelm) == 1) cycle STNIDLOOP
1133
1134 if (oopc_avgkern%n_lvl(jelm) > 0 .and. oopc_avgkern%n_col(jelm) > 1) then
1135 do jlev=1,oopc_avgkern%n_lvl(jelm)
1136
1137 iend=icount+oopc_avgkern%n_col(jelm)-1
1138
1139 ! Read averaging kernel matrix
1140 read(nulstat,*,iostat=ios,err=10,end=10) oopc_avgkern%rak(icount:iend)
1141
1142 icount=iend+1
1143
1144 end do
1145 end if
1146
1147 end do STNIDLOOP
1148
1149 10 if (ios > 0) then
1150 call utl_abort('oopc_readAvgkernAuxfile: READING PROBLEM. ' // &
1151 'File read error message number: ' // trim(utl_str(ios)))
1152 end if
1153
1154 11 CLOSE(UNIT=nulstat)
1155 ierr=fclos(nulstat)
1156
1157 end subroutine oopc_readAvgkernAuxfile
1158
1159 !--------------------------------------------------------------------------
1160 ! oopc_deallocAvgkern
1161 !--------------------------------------------------------------------------
1162 subroutine oopc_deallocAvgkern
1163 !
1164 !:Purpose: To deallocate temporary storage space used for averaging kernels
1165 !
1166 implicit none
1167
1168 ! Locals:
1169 integer :: istnid
1170
1171 if (oopc_avgkern%n_stnid == 0) return
1172
1173 if (allocated(oopc_avgkern%obsSubSpace)) then
1174 do istnid=1,oopc_avgkern%n_stnid
1175 if (oopc_avgkern%source(istnid) == 1) then
1176 call oss_obsdata_dealloc(oopc_avgkern%obsSubSpace(istnid))
1177 end if
1178 end do
1179 deallocate(oopc_avgkern%obsSubSpace)
1180 end if
1181
1182 call oopc_deallocInfo(oopc_avgkern)
1183
1184 end subroutine oopc_deallocAvgkern
1185
1186 !--------------------------------------------------------------------------
1187 ! oopc_findAvgkern
1188 !--------------------------------------------------------------------------
1189 function oopc_findAvgkern(cstnid,varno,nlev) result(istnid)
1190 !
1191 !:Purpose: To find the averaging kernel for an observation if one is
1192 ! specified. Returns 0 if either not found or not specified.
1193 ! Combination of STNID, BUFR element and number of vertical levels
1194 ! to determine association to the observations.
1195 !
1196 implicit none
1197
1198 ! Arguments:
1199 character(len=12), intent(in) :: cstnid ! station id
1200 integer, intent(in) :: varno ! BUFR descriptor element
1201 integer, intent(in) :: nlev ! number of levels in the observation
1202 ! Result:
1203 integer :: istnid ! Index of averaging kernel in oopc_avgkern. Zero if not found.
1204
1205 ! Locals:
1206 integer :: stnidIndex
1207 logical :: iset
1208
1209 ! Find stnid with same number of vertical levels, and same BUFR element
1210
1211 istnid=0
1212
1213 do stnidIndex=1,oopc_avgkern%n_stnid
1214
1215 ! First compare STNID values allowing for * and blanks in
1216 ! oopc_avgkern%stnids(stnidIndex) as wildcards
1217 iset = utl_stnid_equal(oopc_avgkern%stnids(stnidIndex),CSTNID)
1218
1219 ! Check if number of levels and BUFR code are equal.
1220 if (iset) then
1221 if ( varno == oopc_avgkern%element(stnidIndex) ) then
1222 if (nlev < 1 .or. ( nlev == oopc_avgkern%n_lvl(stnidIndex) .or. &
1223 nlev == oopc_avgkern%n_col(stnidIndex) .or. &
1224 nlev == oopc_avgkern%n_col(stnidIndex)-1 .or. &
1225 nlev == oopc_avgkern%n_col(stnidIndex)-2) ) then
1226
1227 istnid=stnidIndex
1228 exit
1229 end if
1230 end if
1231 end if
1232
1233 end do
1234
1235 end function oopc_findAvgkern
1236
1237 !--------------------------------------------------------------------------
1238 ! oopc_getAvgkern
1239 !--------------------------------------------------------------------------
1240 subroutine oopc_getAvgkern(istnid,nlev,ncol,code,avg_kern)
1241 !
1242 !:Purpose: To return averaging kernel for an observation.
1243 !
1244 implicit none
1245
1246 ! Arguments:
1247 integer, intent(in) :: istnid ! index of averaging kernel in oopc_avgkern
1248 character(len=*), intent(in) :: code ! measurement identifier
1249 integer, intent(in) :: nlev ! number of observation levels
1250 integer, intent(in) :: ncol ! number of columns for avg kernel info (without the a priori contribution)
1251 real(8), intent(out) :: avg_kern(nlev,ncol+2) ! averaging kernel, plus possible a priori contribution and integration weights
1252
1253 ! Locals:
1254 integer :: startIndex,endIndex
1255
1256 if (istnid > 0 .and. istnid <= oopc_avgkern%n_stnid) then
1257
1258 if (oopc_avgkern%source(istnid) == 0) then
1259 ! Check number of columns
1260 if (ncol < oopc_avgkern%n_col(istnid) .or. &
1261 ncol+2 > oopc_avgkern%n_col(istnid) ) then
1262
1263 call utl_abort('oopc_getAvgkern: Inconsistency ' // &
1264 'in avg kern size for ' // oopc_avgkern%stnids(istnid) )
1265 end if
1266
1267 ! get averaging kernel from auxiliary file
1268 startIndex = oopc_avgkern%ibegin(istnid)
1269 endIndex = nlev*(startIndex+oopc_avgkern%n_col(istnid)-1)
1270 avg_kern = RESHAPE(oopc_avgkern%rak(startIndex:endIndex), &
1271 (/nlev,oopc_avgkern%n_col(istnid)/),ORDER =(/2,1/))
1272 else
1273 ! get averaging kernel from observation file
1274 avg_kern(1:nlev,1:oopc_avgkern%n_col(istnid)) = &
1275 oss_obsdata_get_array2d(oopc_avgkern%obsSubSpace(istnid),code)
1276 end if
1277
1278 else
1279 call utl_abort("oopc_getAvgkern: Invalid station ID index.")
1280 end if
1281
1282 end subroutine oopc_getAvgkern
1283
1284 !----------------------------------- Misc ---------------------------------
1285 !--------------------------------------------------------------------------
1286
1287 !--------------------------------------------------------------------------
1288 ! oopc_dealocInfo
1289 !--------------------------------------------------------------------------
1290 subroutine oopc_deallocInfo(info)
1291 !
1292 !:Purpose: To deallocate struct_oopc_info instance
1293 !
1294 implicit none
1295
1296 ! Arguments:
1297 type(struct_oopc_info), intent(inout) :: info
1298
1299 if (allocated(info%stnids)) deallocate(info%stnids)
1300 if (allocated(info%element)) deallocate(info%element)
1301 if (allocated(info%source)) deallocate(info%source)
1302 if (allocated(info%vco)) deallocate(info%vco)
1303 if (allocated(info%n_lat)) deallocate(info%n_lat)
1304 if (allocated(info%ibegin)) deallocate(info%ibegin)
1305 if (allocated(info%n_lvl)) deallocate(info%n_lvl)
1306 if (allocated(info%rak)) deallocate(info%rak)
1307 if (allocated(info%vlayertop)) deallocate(info%vlayertop)
1308 if (allocated(info%vlayerbottom)) deallocate(info%vlayerbottom)
1309 if (allocated(info%lat)) deallocate(info%lat)
1310
1311 end subroutine oopc_deallocInfo
1312
1313 !--------------------------------------------------------------------------
1314 ! oopc_diagnOnly
1315 !--------------------------------------------------------------------------
1316 logical function oopc_diagnOnly(cfamName,cstnid,varno,nobslev,flag)
1317 !
1318 !:Purpose: To identify whether or not the obs set identified by the
1319 ! combination of (cstnid,varno,nobslev) will be assimilated or
1320 ! else used for independent verifications after
1321 ! assimilation/minimization
1322 !
1323 implicit none
1324
1325 ! Arguments:
1326 character(len=*), intent(in) :: cfamName ! Family name
1327 character(len=*), intent(in) :: cstnid ! Input station id
1328 integer, intent(in) :: varno ! Obs BUFR number
1329 integer, intent(in) :: nobslev ! Number of levels
1330 integer, intent(in) :: flag ! observation integer flag
1331
1332 ! Locals:
1333 integer :: i,elemId,ifam
1334
1335 ifam=0
1336 if (assim_famNum > 0) then
1337 do i=1,assim_famNum
1338 if (assim_fam(i) == cfamName) then
1339 ifam=i
1340 exit
1341 end if
1342 end do
1343 end if
1344
1345 if (ifam == 0) then
1346 ! assimilate all observations
1347 oopc_diagnOnly = .false.
1348 return
1349 end if
1350
1351 if (assim_all(ifam)) then
1352 ! assimilate all observations
1353 oopc_diagnOnly = .false.
1354 else if (assim_num(ifam) <= 0) then
1355 ! assimilate no observations
1356 oopc_diagnOnly = .true.
1357 else if (assim_num(ifam) > 0) then
1358 ! check if this observation is listed in the assim_* arrays
1359 elemId=0
1360 do i=1,assim_num(ifam)
1361 if (utl_stnid_equal(trim(assim_stnid(ifam,i)),trim(cstnid))) then
1362 if (assim_varno(ifam,i) == 0 .or. assim_varno(ifam,i) == varno) then
1363 if (assim_nlev(ifam,i) == 0 .or. (nobslev == 1 .and. &
1364 assim_nlev(ifam,i) == 1) .or. &
1365 (nobslev > 1 .and. assim_nlev(ifam,i) > 1)) then
1366
1367 elemId=i
1368 exit
1369 end if
1370 end if
1371 end if
1372 end do
1373 oopc_diagnOnly = elemId == 0
1374 end if
1375
1376 if (oopc_diagnOnly) return
1377
1378 ! check if the observation integer flag has a bit marked by assim_exclude_flag (same flagging as in filt_suprep)
1379 if (assim_exclude_nflag(ifam) > 0) then
1380 do i=1,assim_exclude_nflag(ifam)
1381 if (btest(flag, 13 - assim_exclude_flag(ifam,i) )) then
1382 oopc_diagnOnly = .true.
1383 return
1384 end if
1385 end do
1386 end if
1387
1388 end function oopc_diagnOnly
1389
1390 !--------------------------------------------------------------------------
1391 ! oopc_checkType
1392 !--------------------------------------------------------------------------
1393 function oopc_checkType(StnidSet,TypeSet,stnid,type) result(sameType)
1394 !
1395 !:Purpose: To determine if specified combination of (stnid,type) found
1396 ! in (StnidSet,TypeSet).
1397 !
1398 implicit none
1399
1400 ! Arguments:
1401 character(len=*), intent(in) :: StnidSet(:)
1402 character(len=*), intent(in) :: TypeSet(:)
1403 character(len=*), intent(in) :: stnid
1404 character(len=*), intent(in) :: type
1405 ! Result:
1406 logical :: sametype
1407
1408 ! Locals:
1409 integer :: stnidIndex
1410
1411 sameType = .false.
1412
1413 do stnidIndex=1,size(StnidSet)
1414
1415 if ( trim(TypeSet(stnidIndex)) == '' .or. &
1416 trim(StnidSet(stnidIndex)) == '' ) exit
1417
1418 if (utl_stnid_equal(StnidSet(stnidIndex),stnid)) then
1419 if ( trim(StnidSet(stnidIndex)) == trim(stnid) ) then
1420 if ( trim(TypeSet(stnidIndex)) == trim(type) ) then
1421 sameType=.true.
1422 else
1423 sameType=.false.
1424 end if
1425 exit
1426 else
1427 if ( trim(TypeSet(stnidIndex)) == trim(type) ) sameType=.true.
1428 end if
1429 end if
1430
1431 end do
1432
1433 if ( .not.sameType .and. trim(type) == 'default' ) sameType = .true.
1434
1435 end function oopc_checkType
1436
1437 !--------------------------------------------------------------------------
1438 ! oopc_getType
1439 !--------------------------------------------------------------------------
1440 function oopc_getType(StnidSet,TypeSet,stnid) result(type)
1441 !
1442 !:Purpose: To determine "type" for specified "stnid" found in "(StnidSet,Typesef)".
1443 !
1444 implicit none
1445
1446 ! Arguments:
1447 character(len=*), intent(in) :: StnidSet(:)
1448 character(len=*), intent(in) :: TypeSet(:)
1449 character(len=*), intent(in) :: stnid
1450 ! Result:
1451 character(len=15) :: type
1452
1453 ! Locals:
1454 integer :: stnidIndex
1455
1456 type = 'default'
1457
1458 do stnidIndex=1,size(StnidSet)
1459
1460 if ( trim(TypeSet(stnidIndex)) == '' .or. &
1461 trim(StnidSet(stnidIndex)) == '' ) exit
1462
1463 if (utl_stnid_equal(StnidSet(stnidIndex),stnid)) then
1464 type=trim(TypeSet(stnidIndex))
1465 if ( trim(StnidSet(stnidIndex)) == trim(stnid) ) exit
1466 end if
1467
1468 end do
1469
1470 end function oopc_getType
1471
1472 !--------------------------------------------------------------------------
1473 !------------------ Routines associated to oopc_efftemp --------------------
1474
1475 !--------------------------------------------------------------------------
1476 ! oopc_addEfftempObsdata
1477 !--------------------------------------------------------------------------
1478 subroutine oopc_addEfftempObsdata(code,temp_eff)
1479 !
1480 !:Purpose: To add effective temperature value to its obsdata object
1481 !
1482 implicit none
1483
1484 ! Arguments:
1485 character(len=*), intent(in) :: code ! unique identifying code
1486 real(8), intent(in) :: temp_eff(:) ! effective temperature
1487
1488 call oss_obsdata_add_data1d(oopc_efftemp,temp_eff,code,oopc_obsdata_maxsize)
1489
1490 end subroutine oopc_addEfftempObsdata
1491
1492 !--------------------------------------------------------------------------
1493 ! oopc_addEfftempObsfile
1494 !--------------------------------------------------------------------------
1495 subroutine oopc_addEfftempObsfile()
1496 !
1497 !:Purpose: To add effective temperatures in obs file.
1498 !
1499 implicit none
1500
1501 ! Locals:
1502 integer :: nrep_modified,varno(1)
1503
1504 ! If needed, add effective temperature values in obs file for total column measurements
1505
1506 call oss_obsdata_MPIallgather(oopc_efftemp)
1507
1508 if (oopc_efftemp%nrep > 0) then
1509 varno(1)=12001
1510 nrep_modified = obsf_obsSub_update(oopc_efftemp,'CH', &
1511 varno(1:max(1,oopc_efftemp%dim2)),bkstp_opt=0, &
1512 block_opt='INFO',multi_opt='UNI')
1513 write(*,*) 'oopc_addEfftempObsfile: Added ',nrep_modified, &
1514 ' effective temperature values in the obs file.'
1515 end if
1516
1517 end subroutine oopc_addEfftempObsfile
1518
1519 !-------------- CONTROL ROUTINES FOR OBSERVATION OPERATORS ----------------
1520 !--------------------------------------------------------------------------
1521
1522 !--------------------------------------------------------------------------
1523 ! oopc_CHobsoperators
1524 !--------------------------------------------------------------------------
1525 subroutine oopc_CHobsoperators(columnTrl, obsSpaceData, mode, &
1526 columnAnlInc_opt, jobs_opt, destObsColumn_opt)
1527 !
1528 !:Purpose: To apply the observation operators for chemical constituents.
1529 ! Mode of operator set by mode (see also kmode below).
1530 !
1531 !:Comments:
1532 ! - See type struct_oopc_obsoperators for description of obsoper elements.
1533 ! - Currently can only handle the case when nlev_bkgrnd == nlev_inc
1534 !
1535 !:Arguments:
1536 ! :columnTrl: Column of x_background interpolated to observation
1537 ! location. Can have the same vertical levels as the
1538 ! trial field (columnhr) or as the increment field
1539 ! (columng)
1540 ! :mode: (kmode retained internally following the switch to mode as input)
1541 ! +------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
1542 ! | mode |kmode| Mode of | Results |
1543 ! | | | Observation Operator | |
1544 ! +=======+=====+=======================+===================================+
1545 ! | nl | 0 |for general simulation |OmP and total Jo(x_background) |
1546 ! | | |operator |for CH. OmP saved in OBS_OMP of |
1547 ! | | |(non-linear and linear)|obsSpaceData |
1548 ! +-------+-----+-----------------------+-----------------------------------+
1549 ! | HBHT | 1 |for identification or |background error standard dev. |
1550 ! | | |determination of |in observation space saved in |
1551 ! | | |of sqrt(diag(H*B*H^T). |in OBS_HPHT of obsSpaceData |
1552 ! | | |Depends on the presence|if OmP error std dev not initially
1553 ! | | |of OmP error std dev |available in OBS_OMPE |
1554 ! +-------+-----+-----------------------+-----------------------------------+
1555 ! | tl | 2 |for tangent linear |Hdx saved in OBS_WORK of |
1556 ! | | |operator |obsSpaceData |
1557 ! +------+-----+-----------------------+-----------------------------------+
1558 ! |adjoint| 3 |for adjoint of tangent |H^T * R^-1 (OmP-Hdx) in |
1559 ! | | |linear operator |columnAnlInc_opt |
1560 ! +-------+-----+-----------------------+-----------------------------------+
1561 !
1562 ! :columnAnlInc_opt: Optional argument for input/output of column of
1563 ! increment (column). For kmode=2, used as input for
1564 ! increment H_horiz dx interpolated to observation
1565 ! location. For kmode=3, used as output for H^T * R^-1
1566 ! (OmP-Hdx). Required for kmode=2,3.
1567 !
1568 !
1569 ! :jobs_opt: Optional output of total Jo(x_background) for chemical
1570 ! constituents. Required for kmode=0 and not provided
1571 ! otherwise.
1572 !
1573 !
1574
1575
1576 ! More Comments (not rendered in Sphinx):
1577 ! - Two equivalent methods for looping over a report body.
1578 !
1579 ! Method 1:
1580 !
1581 ! call obs_set_current_body_list(obsSpaceData,headerIndex)
1582 ! BODY: do
1583 !
1584 ! bodyIndex = obs_getBodyIndex(obsSpaceData)
1585 ! if (bodyIndex < 0) exit BODY1
1586 !
1587 ! ... obs_bodyElem_r(obsSpaceData, ... ,bodyIndex)
1588 !
1589 ! end do BODY
1590 !
1591 ! Method 2:
1592 !
1593 ! bodyIndex_start = obs_headElem_i(obsSpaceData,OBS_RLN,headerIndex)
1594 ! bodyIndex_end = bodyIndex_start+obs_headElem_i(obsSpaceData,OBS_NLV,headerIndex)-1
1595 ! do bodyIndex=bodyIndex_start,bodyIndex_end
1596 ! ... obs_bodyElem_r(obsSpaceData, ... ,bodyIndex)
1597 ! end do
1598 !
1599 implicit none
1600
1601 ! Arguments:
1602 type(struct_columnData), intent(in) :: columnTrl
1603 type(struct_obs), intent(inout) :: obsSpaceData ! Observation-space data object
1604 character(len=*), intent(in) :: mode
1605 type(struct_columnData), optional, intent(in) :: columnAnlInc_opt
1606 real(8), optional, intent(out) :: jobs_opt
1607 integer, optional, intent(in) :: destObsColumn_opt
1608
1609 ! Locals:
1610 real(8) :: zomp,zinc,zoer,zhbht
1611 integer :: kmode ! Mode of observation operator
1612 integer, external :: fclos
1613 integer :: headerIndex,bodyIndex,bodyIndex_start,bodyIndex_end
1614 integer :: icodtyp,obslevIndex,nobslev,varno,maxnumHeaders,headerCount
1615 integer :: destObsColumn
1616 character(len=12) :: stnid
1617 integer, allocatable :: iass(:),flag(:)
1618 logical, allocatable :: process_obs(:)
1619 real(8), allocatable :: obs_col(:)
1620 real(8), pointer :: col(:),model_col(:)
1621 integer :: nlev_bkgrnd,nlev_inc,modlevIndex
1622 character(len=2), parameter :: varLevel = 'TH'
1623
1624 if ( mode == 'nl' ) then
1625 kmode = 0
1626 else if ( mode == 'HBHT' ) then
1627 kmode = 1
1628 else if ( mode == 'tl' ) then
1629 kmode = 2
1630 else if ( mode == 'adjoint' ) then
1631 kmode = 3
1632 end if
1633
1634 ! Apply setup on first call
1635 if (.not.initializedChem) then
1636 call oopc_setupCH(kmode)
1637 initializedChem = .true.
1638 else if ( mode == 'HBHT' .and. .not.bgStats%initialized ) then
1639 call bcsc_getCovarCH(bgStats)
1640 end if
1641
1642 if ((kmode == 2 .or. kmode == 3) .and. (.not.present(columnAnlInc_opt))) then
1643 call utl_abort("oopc_CHobsoperators: columnAnlInc_opt must " // &
1644 "be specified for kmode = " // utl_str(kmode))
1645 end if
1646
1647 ! Initializations
1648
1649 if ( present(destObsColumn_opt) ) then
1650 destObsColumn = destObsColumn_opt
1651 else
1652 destObsColumn = obs_omp
1653 end if
1654
1655 if (present(jobs_opt)) jobs_opt = 0.d0
1656
1657 nlev_bkgrnd = col_getNumLev(columnTrl,varLevel)
1658
1659 ! Allocate memory for model_col. Not necessary for kmode=0 since model_col points to obsoper%trial.
1660 select case(kmode)
1661 case(2)
1662 nlev_inc = col_getNumLev(columnAnlInc_opt,varLevel)
1663 allocate(model_col(nlev_inc))
1664 if (nlev_inc /= nlev_bkgrnd) then
1665 write(*,*) "oopc_CHobsoperators: nlev_inc ", &
1666 "and nlev_bkgrnd not the same: ",nlev_inc, nlev_bkgrnd
1667 end if
1668 case(1,3)
1669 allocate(model_col(nlev_bkgrnd))
1670 end select
1671
1672 ! Allocations outside oopc_obsoperInit since this can be done outside the HEADER loop.
1673 ! See oopc_obsoperInit for assignment of array content.
1674
1675 ! Model obs background, height, TT, and HU profiles.
1676 allocate(obsoper%trial(nlev_bkgrnd),obsoper%height(nlev_bkgrnd),obsoper%tt(nlev_bkgrnd),obsoper%hu(nlev_bkgrnd))
1677 ! Model PP and pressure model layer boundaries taken as the middle between model levels.
1678 allocate(obsoper%pp(nlev_bkgrnd))
1679
1680 ! Determine number of obs
1681 maxnumHeaders=0
1682 call obs_set_current_header_list(obsSpaceData,'CH')
1683 do
1684 maxnumHeaders=maxnumHeaders+1
1685 if (obs_getHeaderIndex(obsSpaceData) < 0) exit
1686 end do
1687
1688 ! Loop over all header indices of the 'CH' family:
1689
1690 headerCount=0
1691 call obs_set_current_header_list(obsSpaceData,'CH')
1692 HEADER: do
1693
1694 headerCount = headerCount+1
1695 headerIndex = obs_getHeaderIndex(obsSpaceData)
1696 if (headerIndex < 0) exit HEADER
1697
1698 icodtyp = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
1699 if (icodtyp /= codtyp_get_codtyp('CHEMREMOTE') .and. &
1700 icodtyp /= codtyp_get_codtyp('CHEMINSITU')) cycle HEADER
1701
1702 stnid = obs_elem_c(obsSpaceData,'STID',headerIndex)
1703 bodyIndex_start = obs_headElem_i(obsSpaceData,OBS_RLN,headerIndex)
1704 bodyIndex_end = bodyIndex_start+obs_headElem_i(obsSpaceData,OBS_NLV,headerIndex)-1
1705
1706 ! Set number of obs profile elements by removing count of BUFR_SCALE_EXPONENT elements
1707 nobslev = obs_headElem_i(obsSpaceData,OBS_NLV,headerIndex)
1708 do bodyIndex=bodyIndex_start,bodyIndex_end
1709 if (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) == &
1710 BUFR_SCALE_EXPONENT) then
1711 nobslev = nobslev-1
1712 end if
1713 end do
1714
1715 ! varno is expected to be the same for all profile points where OBS_VNM value /= BUFR_SCALE_EXPONENT
1716 do bodyIndex=bodyIndex_start,bodyIndex_end
1717 if (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) &
1718 /= BUFR_SCALE_EXPONENT) then
1719
1720 varno = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
1721 exit
1722 end if
1723 end do
1724
1725 ! Allocate memory for remaining profile data not in obsoper
1726 allocate(obs_col(nobslev),iass(nobslev),process_obs(nobslev),flag(nobslev))
1727
1728 if (allocated(obsoper%success)) deallocate(obsoper%success,obsoper%ixtr)
1729 allocate(obsoper%success(nobslev),obsoper%ixtr(nobslev))
1730
1731 ! Check to see if background error variances available
1732 if (kmode == 1) then
1733 process_obs(:) = bcsc_StatsExistForVarName(vnl_varnameFromVarnum(varno, &
1734 obs_headElem_i(obsSpaceData,OBS_CHM,headerIndex), &
1735 modelName))
1736 end if
1737
1738 ! Prepare for checking if any processing is needed according to initial flag values
1739 obslevIndex=0
1740
1741 do bodyIndex=bodyIndex_start,bodyIndex_end
1742 if (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) &
1743 /= BUFR_SCALE_EXPONENT) then
1744
1745 obslevIndex=obslevIndex+1
1746
1747 obsoper%ixtr(obslevIndex) = &
1748 obs_bodyElem_i(obsSpaceData,OBS_XTR,bodyIndex) ! indicates if obs extends outside model profile vertical range
1749 iass(obslevIndex) = obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) ! indicates if obs is to be assimilated
1750 flag(obslevIndex) = obs_bodyElem_i(obsSpaceData,OBS_FLG,bodyIndex) ! observation integer flag
1751
1752 ! Indicates if this obs should be processed by oopc_obsoperators
1753 if (kmode == 1) then
1754 process_obs(obslevIndex) = obsoper%ixtr(obslevIndex) == 0 &
1755 .and. iass(obslevIndex) == obs_assimilated .and. &
1756 process_obs(obslevIndex)
1757 else
1758 process_obs(obslevIndex) = obsoper%ixtr(obslevIndex) == 0 &
1759 .and. iass(obslevIndex) == obs_assimilated
1760 end if
1761
1762 end if
1763 end do
1764
1765 ! Initialize processing success flag
1766
1767 obsoper%success(1:nobslev) = process_obs(1:nobslev)
1768
1769 if (all(.not.process_obs)) then
1770
1771 ! All observations in the profile flagged so can skip obs operator for current measurement
1772
1773 if (kmode == 3) then
1774 model_col(:) = 0.0D0
1775 obsoper%varName = vnl_varnameFromVarnum(varno, &
1776 obs_headElem_i(obsSpaceData,OBS_CHM,headerIndex),modelName)
1777 end if
1778
1779 else
1780
1781 if ( kmode == 1 ) then
1782 ! Check if "OmP error std dev" is already available
1783 call obs_set_current_body_list(obsSpaceData,headerIndex)
1784 obslevIndex=0
1785 BODYINDEX1: do bodyIndex=bodyIndex_start,bodyIndex_end
1786
1787 if (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) == &
1788 BUFR_SCALE_EXPONENT) cycle
1789
1790 obslevIndex = obslevIndex + 1
1791 if ( process_obs(obslevIndex) ) then
1792 if (obs_bodyElem_r(obsSpaceData,OBS_OMPE,bodyIndex) > 0.0d0 ) then
1793 ! "OmP error std dev" is already available for this measurement.
1794 ! Go to the next measurement.
1795
1796 ! TEMPORARY: First, estimate OBS_HPHT for storage in output files (in the event it is needed externally for
1797 ! other purposes (e.g. total column ozone bias correction and corresponding re-doing for marker settings)
1798 if ( obs_bodyElem_r(obsSpaceData,OBS_OMPE,bodyIndex) > &
1799 1.1d0*obs_bodyElem_r(obsSpaceData,OBS_OER,bodyIndex) ) then
1800
1801 zhbht = sqrt(obs_bodyElem_r(obsSpaceData,OBS_OMPE,bodyIndex)**2 &
1802 -obs_bodyElem_r(obsSpaceData,OBS_OER,bodyIndex)**2)
1803 call obs_bodySet_r(obsSpaceData,OBS_HPHT,bodyIndex,zhbht)
1804 else
1805 call obs_bodySet_r(obsSpaceData,OBS_HPHT,bodyIndex, &
1806 0.5d0*obs_bodyElem_r(obsSpaceData,OBS_OER,bodyIndex))
1807 end if
1808
1809 deallocate(process_obs,obsoper%success,obsoper%ixtr,iass,obs_col,flag)
1810
1811 cycle HEADER
1812 else
1813 ! Proceed with the calc of sqrt(diag(HBHT))
1814 exit BODYINDEX1
1815 end if
1816 end if
1817 end do BODYINDEX1
1818 end if
1819
1820 ! Initialize obsoper variables and allocate arrays
1821 call oopc_obsoperInit(obsSpaceData,headerIndex,columnTrl,nlev_bkgrnd,nobslev,kmode,varno,stnid)
1822
1823 ! Initialize model_col, dependent on kmode. Used for input for kmode=0,2, output for kmode=3.
1824 ! model_col represents for kmode 0) the horizontally interpolated background H_horiz(x_b)
1825 ! 1) not used
1826 ! 2) the analysis increment H_horiz dx
1827 ! 3) the result of applying the adjoint of H_vert
1828
1829 select case(kmode)
1830 case(0)
1831 model_col => obsoper%trial
1832 case(2)
1833 do modlevIndex=1,nlev_inc
1834 model_col(modlevIndex) = col_getElem(columnAnlInc_opt,modlevIndex,headerIndex,obsoper%varName)
1835 end do
1836 case(1,3)
1837 model_col(:) = 0.0D0
1838 end select
1839
1840 ! Loop over all body indices (profile elements) to acquire remaining data
1841
1842 obslevIndex=0
1843 call obs_set_current_body_list(obsSpaceData,headerIndex)
1844 BODY1: do
1845
1846 bodyIndex = obs_getBodyIndex(obsSpaceData)
1847 if (bodyIndex < 0) exit BODY1
1848
1849 ! Get position in profile and skip over BUFR_SCALE_EXPONENT elements
1850
1851 if (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) /= &
1852 BUFR_SCALE_EXPONENT) then
1853
1854 obslevIndex=obslevIndex+1
1855 else
1856 cycle BODY1
1857 end if
1858
1859 ! Get vertical coordinate data. Valid for point data values in profiles.
1860 ! For layer data values, vertical coordinate data will instead be assigned within oopc_obsoperators.
1861
1862 obsoper%obslev(obslevIndex) = &
1863 obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
1864
1865 ! Get normalized increment
1866 if (kmode == 3) then
1867 if (iass(obslevIndex) == 1) then
1868 obs_col(obslevIndex) = obs_bodyElem_r(obsSpaceData,OBS_WORK,bodyIndex)
1869 else
1870 obs_col(obslevIndex) = 0.0D0
1871 end if
1872 end if
1873
1874 ! Get obs estimate from trial field (if kmode>1)
1875
1876 if (kmode <= 1) then
1877 obsoper%obsSpaceTrial(obslevIndex) = 0.0
1878 else
1879 ! Store for use by TL and AD of non-linear operators
1880 obsoper%obsSpaceTrial(obslevIndex) = &
1881 obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex) &
1882 - obs_bodyElem_r(obsSpaceData,OBS_OMP,bodyIndex)
1883 end if
1884
1885 end do BODY1
1886
1887 ! Apply observation operator. model_col,obs_col are the inputs/outputs in model,observation
1888 ! space, respectively. Other required inputs are in obsoper. Input/output is as follows:
1889 !
1890 ! kmode model_col obs_col
1891 ! ----- --------- -------
1892 ! 0 in out
1893 ! 1 not used out
1894 ! 2 in out
1895 ! 3 out in
1896
1897 call oopc_obsoperators(model_col,obs_col,maxnumHeaders,headerCount,kmode)
1898
1899 end if
1900
1901 ! Output results
1902
1903 if (kmode == 3) then
1904 ! Store H^T * R^-1 (OmP-Hdx) in columnInc
1905
1906 col => col_getColumn(columnAnlInc_opt,headerIndex,obsoper%varName)
1907 col(1:nlev_bkgrnd) = model_col(1:nlev_bkgrnd)
1908
1909 else
1910 ! Store results in obsSpaceData
1911
1912 obslevIndex=0
1913 call obs_set_current_body_list(obsSpaceData,headerIndex)
1914 BODY2: do
1915 bodyIndex = obs_getBodyIndex(obsSpaceData)
1916 if (bodyIndex < 0) exit BODY2
1917
1918 ! Get position in profile and skip over BUFR_SCALE_EXPONENT elements
1919 if (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) /= &
1920 BUFR_SCALE_EXPONENT) then
1921
1922 obslevIndex=obslevIndex+1
1923 else
1924 cycle BODY2
1925 end if
1926
1927 ! Check for success in calculations
1928 if (process_obs(obslevIndex) .and. .not.obsoper%success(obslevIndex)) then
1929 ! Observation was flagged within this call of oopc_CHobsoperators
1930 call obs_bodySet_i(obsSpaceData,OBS_ASS,bodyIndex,obs_notAssimilated)
1931 call obs_bodySet_r(obsSpaceData,OBS_OMP,bodyIndex,0.0D0)
1932 call obs_bodySet_r(obsSpaceData,OBS_OMA,bodyIndex,0.0D0)
1933 call obs_bodySet_r(obsSpaceData,OBS_HPHT,bodyIndex,0.0D0)
1934 call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex,0.0D0)
1935 call obs_bodySet_i(obsSpaceData,OBS_FLG,bodyIndex, &
1936 ibset(obs_bodyElem_i(obsSpaceData,OBS_FLG,bodyIndex),9) )
1937 cycle BODY2
1938 else if (iass(obslevIndex) == 0) then
1939 ! Observation was flagged previous to this call of oopc_CHobsoperators
1940 call obs_bodySet_i(obsSpaceData,OBS_ASS,bodyIndex,obs_notAssimilated)
1941 call obs_bodySet_r(obsSpaceData,OBS_OMP,bodyIndex,0.0D0)
1942 call obs_bodySet_r(obsSpaceData,OBS_OMA,bodyIndex,0.0D0)
1943 call obs_bodySet_r(obsSpaceData,OBS_HPHT,bodyIndex,0.0D0)
1944 call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex,0.0D0)
1945 cycle BODY2
1946 else if (.not.process_obs(obslevIndex) .and. kmode == 1) then
1947 call obs_bodySet_r(obsSpaceData,OBS_HPHT,bodyIndex,0.0D0)
1948 cycle BODY2
1949 end if
1950
1951 ! Store result in appropriate location in obsSpaceData
1952 select case(kmode)
1953 case(0)
1954
1955 ! Store OmP in OBS_OMP and add to Jo(x_background) of CH.
1956
1957 zomp = obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex) - obs_col(obslevIndex)
1958 call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex,zomp)
1959
1960 if (oopc_diagnOnly('CH',stnid,varno,nobslev,flag(obslevIndex))) then
1961 ! Observation is for diagnostics and is not to be assimilated
1962 call obs_bodySet_i(obsSpaceData,OBS_ASS,bodyIndex,obs_notAssimilated)
1963 else if (present(jobs_opt) .and. iass(obslevIndex) == 1) then
1964 ! Add to Jo contribution (factor of 0.5 to be applied outside report loop)
1965 zinc = zomp/obs_bodyElem_r(obsSpaceData,OBS_OER,bodyIndex)
1966 jobs_opt = jobs_opt + zinc**2
1967 end if
1968
1969 case(1)
1970
1971 ! Background error standard deviations in
1972 ! observation space, sqrt(diag(H*B_static*H^T)),
1973 ! saved in OBS_HPHT of obsSpaceData.
1974 ! Resulting OmP error std dev estimate saved in OBS_OMPE
1975
1976 call obs_bodySet_r(obsSpaceData,OBS_HPHT,bodyIndex,obs_col(obslevIndex))
1977
1978 zoer = obs_bodyElem_r(obsSpaceData,OBS_OER,bodyIndex)
1979 zomp = sqrt(obs_col(obslevIndex)*obs_col(obslevIndex) + zoer*zoer)
1980 call obs_bodySet_r(obsSpaceData,OBS_OMPE,bodyIndex,zomp)
1981
1982 case(2)
1983
1984 ! Store Hdx in OBS_WORK of obsSpaceData
1985 call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex,obs_col(obslevIndex))
1986 end select
1987
1988 end do BODY2
1989
1990 end if
1991
1992 ! Deallocate profile data
1993 deallocate(process_obs,iass,obs_col,flag)
1994 call oopc_obsoper_dealloc
1995
1996 end do HEADER
1997
1998 deallocate(obsoper%trial,obsoper%pp,obsoper%tt)
1999 deallocate(obsoper%height,obsoper%hu)
2000 if (kmode.ne.0) deallocate(model_col)
2001
2002 if (present(jobs_opt)) jobs_opt = 0.5d0*jobs_opt
2003
2004 end subroutine oopc_CHobsoperators
2005
2006 !--------------------------------------------------------------------------
2007 ! oopc_obsoperInit
2008 !--------------------------------------------------------------------------
2009 subroutine oopc_obsoperInit(obsSpaceData,headerIndex,columnTrl, &
2010 nmodlev,nobslev,kmode,varno,stnid)
2011 !
2012 !:Purpose: To initialize struct_oopc_obsoperators variables and to allocate
2013 ! arrays.
2014 !
2015 !:Comments:
2016 ! - Allocation of arrays that are dependent on only nlev_bkgrd
2017 ! (nmodlev) have been moved outside this subroutine so that they
2018 ! are allocated only once.
2019 !
2020 !:Arguments:
2021 !
2022 ! :columnTrl: Column of x_background interpolated to observation
2023 ! location. Can have the same vertical levels as the
2024 ! trial field (columnTrl) or as the increment field
2025 ! (columnAnlInc)
2026 ! :kmode: Mode of observation operator
2027 ! - 0 for non-linear/linear model in assimilation (all models
2028 ! included are currently linear)
2029 ! - 1 for determination of sqrt(diag(H*B*H^T))
2030 ! - 2 for tangent linear model
2031 ! - 3 for adjoint model
2032 !
2033 implicit none
2034
2035 ! Arguments:
2036 type(struct_obs), intent(inout) :: obsSpaceData ! Obs-Space Data object
2037 integer, intent(in) :: headerIndex ! Measurement index in obsSpaceData
2038 type(struct_columnData), intent(in) :: columnTrl
2039 integer, intent(in) :: nmodlev ! Number of background field (model) levels
2040 integer, intent(in) :: nobslev ! Number of obs elements (see oopc_obsoper_proceed)
2041 integer, intent(in) :: kmode ! Mode of observation operator
2042 integer, intent(in) :: varno ! obs unit BUFR number
2043 character(len=12), intent(in) :: stnid ! Station ID
2044
2045 ! Locals:
2046 integer :: bodyIndex ! Measurement element index in obsSpaceDate (see oopc_obsoper_proceed)
2047 integer :: jl,nmodlev_uv
2048 real(8), pointer :: col_height_ptr(:)
2049 real(8), allocatable :: uu(:),vv(:)
2050 character(len=2), parameter :: varLevel = 'TH'
2051 real(8) :: checkID
2052
2053 obsoper%nmodlev = nmodlev
2054 obsoper%nobslev = nobslev
2055 obsoper%obs_index = headerIndex
2056 obsoper%varno = varno
2057 obsoper%stnid = stnid
2058
2059 ! Get obs space info that are part of the profile header
2060 obsoper%date = obs_headElem_i(obsSpaceData,OBS_DAT,headerIndex)
2061 obsoper%hhmm = obs_headElem_i(obsSpaceData,OBS_ETM,headerIndex)
2062 ! Constituent identifyer following local version of WMO GRIB Table 08046 (similar to BUFR Table 08043)
2063 obsoper%constituentId = obs_headElem_i(obsSpaceData,OBS_CHM,headerIndex)
2064
2065 ! Check if constituent id is recognized (function will abort if not recognized)
2066 if ( obsoper%constituentId >= 0 .and. obsoper%constituentId < 7000) then
2067 checkID = vnl_varMassFromVarnum(obsoper%constituentId)
2068 end if
2069
2070 obsoper%lat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
2071 obsoper%lon = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
2072
2073 ! Body info that we only need for first point in the profile
2074 bodyIndex = obs_headElem_i(obsSpaceData,OBS_RLN,headerIndex)
2075 ! Index of vertical coordinate type
2076 obsoper%vco = obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex)
2077 ! Model field name (NOMVAR value)
2078 obsoper%varName = vnl_varnameFromVarnum(obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex),obsoper%constituentId,modelName)
2079
2080 ! Allocate arrays
2081
2082 allocate(obsoper%obslev(nobslev)) ! Reference vertical levels
2083 allocate(obsoper%vlayertop(nobslev)) ! Layer tops for layer measurements
2084 allocate(obsoper%vlayerbottom(nobslev)) ! Layer bottoms for layer measurements
2085 allocate(obsoper%modlevindexTop(nobslev)) ! Index of highest model level (lowest index) involved with obs element
2086 allocate(obsoper%modlevindexBot(nobslev)) ! Index of lowest model level (highest index) involved with obs element
2087 allocate(obsoper%obsSpaceTrial(nobslev)) ! Obs estimate from trial field
2088
2089 allocate(obsoper%zh(nobslev,nmodlev)) ! Local model operator H (excluding conversion constants and horizontal interpolation)
2090 allocate(obsoper%zhp(nobslev,nmodlev)) ! Part of zh that excludes aspects related to vertical resolution
2091
2092 obsoper%zh(:,:)=0.0D0
2093 obsoper%zhp(:,:)=0.0D0
2094 obsoper%modlevindexTop(:)=1
2095 obsoper%modlevindexBot(:)=nmodlev
2096
2097 if (.not.col_varExist(columnTrl,'TT')) then
2098 if (oopc_required_field('TT',obsoper%varno)) then
2099 call utl_abort("chem_opsoper_init: TT required for BUFR code " // trim(utl_str(obsoper%varno)))
2100 end if
2101 end if
2102
2103 ! Get background profiles at observation location
2104 do jl=1,nmodlev
2105 obsoper%pp(jl) = col_getPressure(columnTrl,jl,headerIndex,varLevel)
2106 obsoper%trial(jl) = col_getElem(columnTrl,jl,headerIndex,obsoper%varName)
2107 obsoper%tt(jl) = col_getElem(columnTrl,jl,headerIndex,'TT')
2108 end do
2109
2110 if (col_varExist(columnTrl,'TT').and.col_varExist(columnTrl,'HU') &
2111 .and.col_varExist(columnTrl,'P0')) then
2112
2113 ! Height would have been generated in the call to sugomobs.
2114 ! Convert from geopotential to geopotential height.
2115 col_height_ptr => col_getColumn(columnTrl,headerIndex,'Z_T')
2116 obsoper%height(1:nmodlev) = col_height_ptr(1:nmodlev)
2117 else
2118 obsoper%height(:) = -1.
2119 end if
2120
2121 ! Get specific humidity if available
2122 if (col_varExist(columnTrl,'HU')) then
2123 do jl=1,nmodlev
2124 obsoper%hu(jl) = col_getElem(columnTrl,jl,headerIndex,'HU') ! lnq was replaced by q
2125 end do
2126 else
2127 obsoper%hu(:)=-1
2128 end if
2129
2130 ! If applicable, get column upper boundaries for use with total column
2131 ! measurements when the related increment profile is to be restricted to
2132 ! the lower atmosphere (e.g. troposphere or PBL; when tropo_bound>0 )
2133 if (obsoper%vco == 4 .and. nobslev == 1 .and. kmode /= 1) then
2134 if (kmode == 0) then
2135
2136 if (col_varExist(columnTrl,'HU')) then
2137 if (col_varExist(columnTrl,'UU').and.col_varExist(columnTrl,'VV')) then
2138 nmodlev_uv=col_getNumLev(columnTrl,'MM')
2139 allocate(uu(nmodlev_uv),vv(nmodlev_uv))
2140 do jl=1,nmodlev_uv
2141 uu(jl) = col_getElem(columnTrl,jl,headerIndex,'UU')
2142 vv(jl) = col_getElem(columnTrl,jl,headerIndex,'VV')
2143 end do
2144 obsoper%columnBound = oopc_getColBoundary(obsoper%constituentId,&
2145 nmodlev,obsoper%pp,obsoper%tt,obsoper%height, &
2146 hu_opt=obsoper%hu,uu_opt=uu,vv_opt=vv)
2147 deallocate(uu,vv)
2148 else
2149 obsoper%columnBound = oopc_getColBoundary(obsoper%constituentId,nmodlev,obsoper%pp,obsoper%tt,obsoper%height,hu_opt=obsoper%hu)
2150 end if
2151
2152 else
2153 obsoper%columnBound = oopc_getColBoundary(obsoper%constituentId,nmodlev,obsoper%pp,obsoper%tt,obsoper%height)
2154 end if
2155
2156 call oopc_addColBoundary(headerIndex,obsoper%columnBound) ! save boundary for kmode>0 calls using headerIndex
2157
2158 else
2159 obsoper%columnBound = oopc_retrieveColBoundary(headerIndex)
2160 end if
2161 else
2162 obsoper%columnBound = -1.
2163 end if
2164
2165 end subroutine oopc_obsoperInit
2166
2167 !--------------------------------------------------------------------------
2168 ! oopc_obsoper_dealloc
2169 !--------------------------------------------------------------------------
2170 subroutine oopc_obsoper_dealloc
2171 !
2172 !:Purpose: To deallocate arrays for struct_oopc_obsoperators.
2173 !
2174 !:Comments:
2175 ! - Deallocation of arrays that are dependent on only nmodlev have
2176 ! been moved outside this subroutine so that they are
2177 ! deallocated only once.
2178 !
2179 implicit none
2180
2181 if (allocated(obsoper%obslev)) deallocate(obsoper%obslev)
2182 if (allocated(obsoper%vlayertop)) deallocate(obsoper%vlayertop)
2183 if (allocated(obsoper%vlayerbottom)) deallocate(obsoper%vlayerbottom)
2184 if (allocated(obsoper%zh)) deallocate(obsoper%zh)
2185 if (allocated(obsoper%zhp)) deallocate(obsoper%zhp)
2186 if (allocated(obsoper%modlevindexTop)) deallocate(obsoper%modlevindexTop)
2187 if (allocated(obsoper%modlevindexBot)) deallocate(obsoper%modlevindexBot)
2188 if (allocated(obsoper%obsSpaceTrial)) deallocate(obsoper%obsSpaceTrial)
2189 if (allocated(obsoper%success)) deallocate(obsoper%success)
2190 if (allocated(obsoper%ixtr)) deallocate(obsoper%ixtr)
2191
2192 end subroutine oopc_obsoper_dealloc
2193
2194 !---------------------- Routines for observation operators ----------------
2195 !--------------------------------------------------------------------------
2196
2197 !--------------------------------------------------------------------------
2198 ! oopc_obsoperators
2199 !--------------------------------------------------------------------------
2200 subroutine oopc_obsoperators(model_col,obs_col,maxnumHeaders,headerCount,kmode)
2201 !
2202 !:Purpose: To apply observation operator for indicated observation data and
2203 ! condition.
2204 !
2205 !:Arguments:
2206 ! :kmode: mode of observation operator
2207 ! 0) general (potentially non-linear) simulation operator
2208 ! 1) determination of sqrt(diag(H*B*H^T))
2209 ! 2) tangent linear operator
2210 ! 3) linear adjoint operator
2211 !
2212 ! :obs_col: Observation space input/output profile
2213 !
2214 ! +------+------+------+------+------+------+------+
2215 ! |kmode | input/output | profile |
2216 ! +======+====================+====================+
2217 ! | 0 | out | H(xb) |
2218 ! +------+--------------------+--------------------+
2219 ! | 1 | out | sqrt(diag(H*B*H^T))|
2220 ! +------+--------------------+--------------------+
2221 ! | 2 | out | H*dx |
2222 ! +------+--------------------+--------------------+
2223 ! | 3 | in | R**-1 (Hdx-d) |
2224 ! +------+--------------------+--------------------+
2225 !
2226 ! :model_col: Model space input/output profile
2227 !
2228 ! +------+------+------+------+------+------+------+
2229 ! |kmode | input/output | profile |
2230 ! +======+====================+====================+
2231 ! | 0 | in | xb |
2232 ! +------+--------------------+--------------------+
2233 ! | 1 | not used | not used |
2234 ! +------+--------------------+--------------------+
2235 ! | 2 | in | dx at obs location |
2236 ! +------+--------------------+--------------------+
2237 ! | 3 | out | adjoint product |
2238 ! | | | H^T(...) |
2239 ! +------+--------------------+--------------------+
2240 !
2241 ! :maxnumHeaders: Total number of CH obs for this CPU
2242 ! :headerCount: Obs number up to maxnumHeaders
2243 !
2244 ! Further changes required for generalization:
2245 !
2246 ! 1) Add layer average operators.
2247 ! 2) Add AOD operators (summation over model layers).
2248 ! 3) Add option to include use of obs error correlation matrix for kmode=2,3
2249 ! (This may/will need to be done in oop_Hchm and oop_HTchm where the division
2250 ! by stddev_obs is applied. A new routine will be needed for this
2251 ! operation - and others for reading the correlation matrices similarly to the
2252 ! averaging kernels.)
2253 !
2254 ! Comments:
2255 ! - When kmode=0, call from oopc_CHobsoperators passes model_col as a pointer to
2256 ! obsoper%trial.
2257 ! - Does not yet account for potential future applications of obs
2258 ! vertical correlation matrices.
2259 ! - Potential specification of background error std. dev. (fdeStddev(:,1:2)) and correlation matrices
2260 ! for the ensemble-based and lam cases to/could be done when stats for these become in use with constituents.
2261 !
2262 implicit none
2263
2264 ! Arguments:
2265 integer, intent(in) :: kmode
2266 integer, intent(in) :: maxnumHeaders
2267 integer, intent(in) :: headerCount
2268 real(8), intent(inout) :: model_col(obsoper%nmodlev)
2269 real(8), intent(inout) :: obs_col(obsoper%nobslev)
2270
2271 ! Locals:
2272 logical :: successLocal(obsoper%nobslev)
2273 real(8) :: zwork(obsoper%nmodlev),unit_conversion(obsoper%nmodlev)
2274 real(8) :: fdeStddev(obsoper%nmodlev,2),temp_eff(1)
2275 real(8), allocatable :: avg_kern(:,:)
2276 integer :: obslevIndex,modlevIndex,varIndex,nobslevOriginal,varnoOriginal
2277 integer, parameter :: code_len=90
2278 character(len=code_len) :: code ! Must be at least as large as oopc_code_len
2279
2280 if (code_len < oss_obsdata_code_len()) then
2281 call utl_abort('oopc_obsoperators: Length of code string' // &
2282 ' needs to be increased to ' // &
2283 trim(utl_str(oss_obsdata_code_len())))
2284 end if
2285
2286 ! Determine if layer boundaries are assigned to this data source OR, for obsoper%nobslev = 1,
2287 ! if the number of observation operator levels differ (i.e. use of 1D averaging kernels)
2288 ! If so, obtain them for use in this routine.
2289 ! Routine provides obsoper%layerIdentified,
2290 ! obsoper%vlayertop(nobslev),
2291 ! obsoper%vlayerbottom(nobslev)
2292 ! and resets obsoper%nobslev, obsoper%obslev for alternative work vertical levels.
2293 ! The original obsoper%nobslev is saved as nobslevOriginal
2294
2295 nobslevOriginal = obsoper%nobslev
2296 varnoOriginal = obsoper%varno
2297 call oopc_getLevels
2298
2299 ! Prepare observation operator
2300
2301 call oopc_prepareOperator(model_col,avg_kern,nobslevOriginal,maxnumHeaders, &
2302 headerCount,kmode,unit_conversion,successLocal)
2303
2304 ! Finalize required quantities depending on kmode
2305
2306 select case(kmode)
2307
2308 case(0)
2309
2310 ! Finalize non-linear/linear operator step
2311
2312 if (obsoper%iavgkern > 0) then
2313 if (oopc_checkType(oopc_avgkern%stnids, &
2314 oopc_avgkern%type,obsoper%stnid,'log')) then
2315
2316 zwork(1:obsoper%nobslev) = 0.0d0
2317 do obslevIndex=1,obsoper%nobslev
2318 if (successLocal(obslevIndex)) &
2319 zwork(obslevIndex)=log(dot_product(obsoper%zh(obslevIndex, &
2320 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)), &
2321 model_col(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) ))
2322 end do
2323
2324 do obslevIndex=1,oopc_avgkern%n_lvl(obsoper%iavgkern)
2325 if (obsoper%success(obslevIndex)) then
2326 obs_col(obslevIndex)= dot_product(avg_kern(obslevIndex, &
2327 1:obsoper%nobslev),zwork(1:obsoper%nobslev))
2328 end if
2329
2330 ! Add a priori contribution when provided
2331 if (oopc_avgkern%n_col(obsoper%iavgkern) >= obsoper%nobslev+1) then
2332 obs_col(obslevIndex) = obs_col(obslevIndex) + &
2333 avg_kern(obslevIndex,obsoper%nobslev+1)
2334 end if
2335
2336 obs_col(obslevIndex) = exp(obs_col(obslevIndex))
2337 end do
2338
2339 if ( oopc_avgkern%n_col(obsoper%iavgkern) == obsoper%nobslev+2 &
2340 .and. nobslevOriginal == 1 ) then
2341
2342 obs_col(1) = sum( avg_kern(1:oopc_avgkern%n_lvl(obsoper%iavgkern), &
2343 obsoper%nobslev+2)*obs_col(1:oopc_avgkern%n_lvl(obsoper%iavgkern)) )
2344 end if
2345 else
2346
2347 ! Standard treatment for linear model
2348
2349 do obslevIndex=1,oopc_avgkern%n_lvl(obsoper%iavgkern)
2350 if (obsoper%success(obslevIndex)) then
2351 obs_col(obslevIndex)=dot_product(obsoper%zh(obslevIndex,&
2352 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)), &
2353 model_col(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)))
2354 end if
2355 ! Add a priori contribution when provided
2356 if ( oopc_avgkern%n_col(obsoper%iavgkern) >= obsoper%nobslev+1) then
2357 obs_col(obslevIndex) = obs_col(obslevIndex) + &
2358 avg_kern(obslevIndex,obsoper%nobslev+1)
2359 end if
2360 end do
2361
2362 ! Account for integration via weighted summation
2363 if ( oopc_avgkern%n_col(obsoper%iavgkern) == obsoper%nobslev+2 &
2364 .and. nobslevOriginal == 1 .and. &
2365 oopc_avgkern%n_lvl(obsoper%iavgkern) > 1 ) then
2366
2367 obs_col(1) = sum( avg_kern(1:oopc_avgkern%n_lvl(obsoper%iavgkern), &
2368 obsoper%nobslev+2)*obs_col(1:oopc_avgkern%n_lvl(obsoper%iavgkern)) )
2369 end if
2370 end if
2371
2372 else
2373
2374 ! Standard treatment for linear model
2375
2376 do obslevIndex=1,nobslevOriginal
2377 if (obsoper%success(obslevIndex)) then
2378 obs_col(obslevIndex)=dot_product(obsoper%zh(obslevIndex, &
2379 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)), &
2380 model_col(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)))
2381 end if
2382 end do
2383
2384 end if
2385
2386 ! Calculate concentration-weighted effective temperature (for output purpose)
2387
2388 if ((obsoper%constituentId >= 0 .and. obsoper%constituentId < 7000) .and. &
2389 trim(obsoper%operatorCategory) == 'Integ' .and. obsoper%nobslev == 1 .and. &
2390 obsoper%vco == 4 .and. obsoper%success(1)) then
2391
2392 if (all(obsoper%tt > 0.0) .and. obs_col(1) > 0.0) then
2393 temp_eff(1)=dot_product(obsoper%zh(1, &
2394 obsoper%modlevindexTop(1):obsoper%modlevindexBot(1)), &
2395 obsoper%tt(obsoper%modlevindexTop(1):obsoper%modlevindexBot(1))* &
2396 model_col(obsoper%modlevindexTop(1):obsoper%modlevindexBot(1))) &
2397 /obs_col(1)
2398 code=oss_obsdata_get_header_code(obsoper%lon,obsoper%lat,&
2399 obsoper%date,obsoper%hhmm,obsoper%stnid)
2400 call oopc_addEfftempObsdata(code,temp_eff)
2401 end if
2402 end if
2403
2404 case(1)
2405
2406 ! Compute sqrt(diag(H*B*H^T))
2407
2408 ! Apply unit conversion to observation operator
2409 do obslevIndex=1,nobslevOriginal
2410 if (obsoper%success(obslevIndex)) then
2411 obsoper%zh(obslevIndex,:) = unit_conversion * obsoper%zh(obslevIndex,:)
2412 end if
2413 end do
2414
2415 ! Get background error std dev profile at obs location
2416 fdeStddev(:,:)=0
2417 call bcsc_getBgStddev(obsoper%varName,obsoper%nmodlev, &
2418 obsoper%lat,obsoper%lon,fdeStddev(:,1))
2419
2420 ! Identify variable position index in background error correlation matrices
2421
2422 varIndex=1
2423 do while (trim(bgStats%varNameList(varIndex)) /= '')
2424 if (trim(bgStats%varNameList(varIndex)) == trim(obsoper%varName)) exit
2425 varIndex=varIndex+1
2426 end do
2427 if (trim(bgStats%varNameList(varIndex)) == '') then
2428 call utl_abort('oopc_obsoperators: Correlation matrix not found for ' &
2429 // trim(obsoper%varName) )
2430 end if
2431
2432 do obslevIndex=1,nobslevOriginal
2433 if (obsoper%success(obslevIndex)) then
2434 do modlevIndex=obsoper%modlevindexTop(obslevIndex), &
2435 obsoper%modlevindexBot(obslevIndex)
2436
2437 zwork(modlevIndex)=sum(obsoper%zh(obslevIndex, &
2438 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) &
2439 *bgStats%corvert(modlevIndex,obsoper%modlevindexTop(obslevIndex): &
2440 obsoper%modlevindexBot(obslevIndex),varIndex) &
2441 *fdeStddev(obsoper%modlevindexTop(obslevIndex): &
2442 obsoper%modlevindexBot(obslevIndex),1)) &
2443 *fdeStddev(modlevIndex,1)
2444 end do
2445 obs_col(obslevIndex)=sum(obsoper%zh(obslevIndex, &
2446 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) &
2447 *zwork(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)))
2448
2449 obs_col(obslevIndex) = sqrt(obs_col(obslevIndex)) ! save as sqrt(h*B*h^T)
2450 else
2451 obs_col(obslevIndex) = 0.0
2452 end if
2453 end do
2454
2455 case(2)
2456
2457 ! Finalize TL operator application
2458
2459 ! Standard treatment for linear and linearized model
2460
2461 if (obsoper%iavgkern > 0) then
2462
2463 do obslevIndex=1,oopc_avgkern%n_lvl(obsoper%iavgkern)
2464 if (obsoper%success(obslevIndex)) then
2465 obs_col(obslevIndex)=dot_product(obsoper%zh(obslevIndex, &
2466 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)), &
2467 model_col(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)))
2468 end if
2469 end do
2470
2471 ! Account for integration via weighted summation
2472 if ( oopc_avgkern%n_col(obsoper%iavgkern) == obsoper%nobslev+2 &
2473 .and. nobslevOriginal == 1 .and. &
2474 oopc_avgkern%n_lvl(obsoper%iavgkern) > 1 ) then
2475
2476 obs_col(1) = sum( avg_kern(1:oopc_avgkern%n_lvl(obsoper%iavgkern), &
2477 obsoper%nobslev+2)*obs_col(1:oopc_avgkern%n_lvl(obsoper%iavgkern)) )
2478
2479 end if
2480 else
2481
2482 do obslevIndex=1,nobslevOriginal
2483 if (obsoper%success(obslevIndex)) then
2484 obs_col(obslevIndex)=dot_product(obsoper%zh(obslevIndex, &
2485 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)), &
2486 model_col(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)))
2487 else
2488 obs_col(obslevIndex)=0.0d0
2489 end if
2490 end do
2491
2492 end if
2493
2494 case(3)
2495
2496 ! H^T*grad contribution from adjoint of tangent linear model.
2497
2498 model_col(:) = 0.0
2499
2500 ! Standard treatment for linear and linearized model
2501
2502 if (obsoper%iavgkern > 0) then
2503
2504 if ( oopc_avgkern%n_col(obsoper%iavgkern) == obsoper%nobslev+2 &
2505 .and. nobslevOriginal == 1 .and. &
2506 oopc_avgkern%n_lvl(obsoper%iavgkern) > 1 ) then
2507
2508 ! Account for integration via weighted summation
2509 zwork(:)=0.0d0
2510 do obslevIndex=obsoper%modlevindexTop(obslevIndex),obsoper%modlevindexBot(obslevIndex)
2511 zwork(obslevIndex)= &
2512 sum(obsoper%zh(1:oopc_avgkern%n_lvl(obsoper%iavgkern),obslevIndex)* &
2513 avg_kern(1:oopc_avgkern%n_lvl(obsoper%iavgkern),obsoper%nobslev+2) )
2514 end do
2515 obsoper%zh(1,:) = 0.0d0
2516 obsoper%zh(1,obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) = &
2517 zwork(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex))
2518 end if
2519 end if
2520
2521 do obslevIndex=1,nobslevOriginal
2522 if (obsoper%success(obslevIndex)) then
2523 zwork(:)=0.0D0
2524 zwork(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) = &
2525 obs_col(obslevIndex)*obsoper%zh(obslevIndex, &
2526 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex))
2527
2528 call oopc_convertUnits(zwork,incr_opt=.true.)
2529
2530 model_col(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) = &
2531 model_col(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) + &
2532 zwork(obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex))
2533 end if
2534 end do
2535
2536 end select
2537
2538 if (obsoper%iavgkern > 0) deallocate(avg_kern)
2539
2540 end subroutine oopc_obsoperators
2541
2542 !--------------------------------------------------------------------------
2543 ! oopc_prepareOperator
2544 !--------------------------------------------------------------------------
2545 subroutine oopc_prepareOperator(model_col,avg_kern,nobslevOriginal, &
2546 maxnumHeaders,headerCount,kmode,unit_conversion,successLocal)
2547 !
2548 !:Purpose: To prepare observation operator
2549 !
2550 implicit none
2551
2552 ! Arguments:
2553 integer, intent(in) :: kmode ! Mode of observation operator
2554 integer, intent(in) :: nobslevOriginal ! Number of actual obs elements in measurement
2555 integer, intent(in) :: maxnumHeaders ! Total number of CH obs for this CPU
2556 integer, intent(in) :: headerCount ! Obs counter/index
2557 real(8), intent(inout) :: model_col(obsoper%nmodlev)
2558 real(8), intent(out) :: unit_conversion(obsoper%nmodlev)
2559 logical, intent(out) :: successLocal(obsoper%nobslev)
2560 real(8), allocatable, intent(out) :: avg_kern(:,:)
2561
2562 ! Locals:
2563 real(8), allocatable :: press_obs(:)
2564 integer, allocatable :: ixtrLocal(:)
2565 real(8) :: fdeStddev(obsoper%nmodlev,2),zhmin
2566 integer :: obslevIndex,modlevIndex
2567 integer, parameter :: code_len=90
2568 character(len=code_len) :: code ! Must be at least as large as oopc_code_len
2569 character(len=20) :: message
2570 logical :: successDepot
2571
2572 ! Check if obs BUFR element needs to be changed with use of averaging kernels
2573
2574 if ( obsoper%iavgkern > 0 ) then
2575 if ( oopc_avgkern%n_col(obsoper%iavgkern) == obsoper%nobslev+2 &
2576 .and. nobslevOriginal == 1 ) then
2577
2578 obsoper%varno=oopc_avgkern%ProfElement(obsoper%iavgkern)
2579 end if
2580 end if
2581
2582 ! Identify observation operator based on observation units and presence or
2583 ! not of layer boundaries
2584
2585 if (bufr_IsIntegral(obsoper%varno)) then
2586 if (.not.obsoper%layerIdentified) then
2587 write(*,*) '----------------------------------------------------------'
2588 write(*,*) 'STNID, BUFR index, nobslev: ',obsoper%stnid,' ', &
2589 obsoper%varno,obsoper%nobslev
2590 call utl_abort('oopc_obsoperators: Required layer boundaries not available!')
2591 else
2592 ! Vertical integration operator
2593 obsoper%operatorCategory='Integ'
2594 end if
2595 else if (obsoper%layerIdentified) then
2596 ! Layer averaging operator
2597 obsoper%operatorCategory='LayerAvg'
2598 else if (obsoper%vco == 5 .and. obsoper%nobslev == 1) then
2599 ! Surface point (in-situ) measurement
2600 obsoper%operatorCategory='Surface'
2601 else
2602 ! Vertical interpolation operator
2603 obsoper%operatorCategory='Interp'
2604 end if
2605
2606 ! Look for pre-calculated operator
2607
2608 successDepot = oopc_operatorDepot(headerCount,maxnumHeaders,kmode,'get')
2609
2610 if ( successDepot ) then
2611
2612 ! Get averaging kernels if requested
2613
2614 if (obsoper%iavgkern > 0) then
2615
2616 if (allocated(avg_kern)) deallocate(avg_kern)
2617 allocate(avg_kern(oopc_avgkern%n_lvl(obsoper%iavgkern), &
2618 oopc_avgkern%n_col(obsoper%iavgkern)))
2619
2620 code=oss_obsdata_get_header_code(obsoper%lon,obsoper%lat,obsoper%date, &
2621 obsoper%hhmm,obsoper%stnid)
2622 call oopc_getAvgkern(obsoper%iavgkern,oopc_avgkern%n_lvl(obsoper%iavgkern), &
2623 oopc_avgkern%n_col(obsoper%iavgkern),code,avg_kern)
2624 end if
2625
2626 ! Apply unit conversion (apply later when kmode=3)
2627 if (kmode == 2) call oopc_convertUnits(model_col,incr_opt=.true.)
2628
2629 return
2630 end if
2631
2632 ! Indicate if the generalized innovation operator is to be applied.
2633
2634 obsoper%applyGenOper=.false.
2635 if (obsoper%constituentId >= 0 .and. &
2636 oopc_checkType(operatorSubType(1,:),operatorSubType(2,:), &
2637 obsoper%stnid,'genOper')) then
2638
2639 if (kmode /= 1 .and. (trim(obsoper%operatorCategory) == 'Integ' .or. &
2640 trim(obsoper%operatorCategory) == 'LayerAvg' )) then
2641
2642 if ( kmode == 2) then
2643 ! Set reference profiles for use with generalized innovation operator
2644 ! when kmode>=2
2645 call oopc_addToProfileSet(oopc_climFields,oopc_bgRef, &
2646 oopc_constituentsSize,2,obsoper%nmodlev,obsoper%pp, &
2647 obsoper%height,obsoper%lat,obsoper%lon, obsoper%obs_index, &
2648 oopc_obsdata_maxsize,varKind_opt='CH', &
2649 varNumber_opt=obsoper%constituentId, &
2650 tt_opt=obsoper%tt,hu_opt=obsoper%hu)
2651
2652 ! Get background error std dev profile at obs locations
2653 fdeStddev(:,:)=0.D0
2654
2655 call bcsc_getBgStddev(obsoper%varName,obsoper%nmodlev,obsoper%lat, &
2656 obsoper%lon,fdeStddev(:,1),vlev_opt=obsoper%pp)
2657
2658 call bcsc_addBgStddev(obsoper%obs_index,fdeStddev, &
2659 oopc_obsdata_maxsize)
2660
2661 end if
2662 if (kmode >= 2) obsoper%applyGenOper = .true.
2663
2664 end if
2665 end if
2666
2667 ! Apply unit conversion (apply unit conversion later for kmode=3)
2668
2669 select case(kmode)
2670 case(0)
2671 ! Perform transformation and unit conversion on obsoper%trial.
2672 ! Note that model_col => obsoper%trial for kmode=0.
2673 call oopc_convertUnits(obsoper%trial)
2674 case(1)
2675 ! Save the conversion factor in <unit_conversion>
2676 unit_conversion(:) = 1.0
2677 call oopc_convertUnits(unit_conversion,incr_opt=.true.)
2678 case(2)
2679 call oopc_convertUnits(model_col,incr_opt=.true.)
2680 end select
2681
2682 if (obsoper%applyGenOper) then
2683 ! Perform unit conversion on obsoper%trial when applying the generalized
2684 ! obs operator for kmode=2,3. Keep obsoper%trial in ug/kg in this case.
2685 call oopc_convertUnits(obsoper%trial,ppb_opt=.true.)
2686 end if
2687
2688 if (allocated(press_obs)) deallocate(press_obs,ixtrLocal)
2689 allocate(press_obs(obsoper%nobslev),ixtrLocal(obsoper%nobslev))
2690 if (nobslevOriginal == obsoper%nobslev) then
2691 ixtrLocal(:) = obsoper%ixtr(:)
2692 successLocal(:) = obsoper%success(:)
2693 else
2694 if (any(obsoper%ixtr(:) == 0)) then
2695 ixtrLocal(:)=0
2696 else
2697 ixtrLocal(:)=1
2698 end if
2699 if (any(obsoper%success)) then
2700 successLocal(:) = .true.
2701 else
2702 successLocal(:) = .false.
2703 end if
2704 end if
2705
2706 ! Convert observation vertical coordinate value(s) to pressure if needed
2707
2708 select case(obsoper%vco)
2709 case(1)
2710 ! Convert altitude to pressure
2711 if (trim(obsoper%operatorCategory) == 'Interp') then
2712 press_obs = phf_convert_z_to_pressure(obsoper%obslev,obsoper%height, &
2713 obsoper%pp, &
2714 obsoper%nobslev,obsoper%nmodlev,obsoper%lat,successLocal)
2715 ! Allows for obs levels below the lowest TH level and above the surface
2716 where (obsoper%obslev(1:obsoper%nobslev) < &
2717 obsoper%height(obsoper%nmodlev)) &
2718 press_obs(1:obsoper%nobslev)= obsoper%pp(obsoper%nmodlev)
2719
2720 else if (trim(obsoper%operatorCategory) == 'Integ' .or. &
2721 trim(obsoper%operatorCategory) == 'LayerAvg') then
2722 obsoper%vlayertop = phf_convert_z_to_pressure(obsoper%vlayertop, &
2723 obsoper%height, &
2724 obsoper%pp,obsoper%nobslev,obsoper%nmodlev, &
2725 obsoper%lat,successLocal)
2726 obsoper%vlayerbottom = phf_convert_z_to_pressure(obsoper%vlayerbottom, &
2727 obsoper%height, &
2728 obsoper%pp,obsoper%nobslev,obsoper%nmodlev, &
2729 obsoper%lat,successLocal)
2730 end if
2731 case(2)
2732 ! Pressure, no conversion needed
2733 if (trim(obsoper%operatorCategory) == 'Interp') press_obs = obsoper%obslev
2734 case(4,5)
2735 ! No actions taken
2736 case default
2737 call utl_abort("oopc_obsoperators: vertical coordinate type vco = " &
2738 // trim(utl_str(obsoper%vco)) // &
2739 " not available for this operator.")
2740 end select
2741
2742 ! Determine if averaging kernel is to be applied
2743
2744 if (nobslevOriginal == obsoper%nobslev) then
2745 obsoper%iavgkern = oopc_findAvgkern(obsoper%stnid,obsoper%varno, &
2746 obsoper%nobslev)
2747 else
2748 obsoper%iavgkern = oopc_findAvgkern(obsoper%stnid,obsoper%varno,0)
2749 end if
2750
2751 ! Apply appropriate core observation operator
2752
2753 select case(trim(obsoper%operatorCategory))
2754 case('Interp')
2755
2756 ! Vertical interpolation operator
2757
2758 if ( obsoper%iavgkern /= 0 ) then
2759 message = 'doAll&noExtrap'
2760 else
2761 message = 'noExtrap'
2762 end if
2763 call ppo_vertInterpWgts(obsoper%pp,press_obs,obsoper%nmodlev, &
2764 obsoper%nobslev,obsoper%zh,obsoper%modlevindexTop, &
2765 obsoper%modlevindexBot,method_opt=oopc_getType(operatorSubType(1,:), &
2766 operatorSubType(2,:),obsoper%stnid),skipType_opt=message, &
2767 outbound_opt=ixtrLocal,success_opt=successLocal)
2768
2769 case('Surface')
2770
2771 ! Surface point measurement
2772
2773 ! Set weight to unity for lowest model level.
2774 obsoper%zh(1,1:obsoper%nmodlev-1)=0.0
2775 obsoper%zh(1,obsoper%nmodlev) = 1.0
2776
2777 ! Set range of elements for model vertical levels
2778 obsoper%modlevindexTop(1) = obsoper%nmodlev
2779 obsoper%modlevindexBot(1) = obsoper%nmodlev
2780
2781 case('Integ')
2782
2783 ! Layer integration operator
2784
2785 if ( obsoper%iavgkern /= 0 ) then
2786 message = 'doAll&noExtrap'
2787 else
2788 message = 'default'
2789 end if
2790
2791 call oopc_vertObsLayersWgts('integ',ixtrLocal,successLocal,kmode,message)
2792
2793 case('LayerAvg')
2794
2795 ! Layer averaging operator
2796
2797 if ( obsoper%iavgkern /= 0 ) then
2798 message = 'doAll&noExtrap'
2799 else
2800 message = 'default'
2801 end if
2802
2803 call oopc_vertObsLayersWgts('avg',ixtrLocal,successLocal,kmode,message)
2804
2805 end select
2806
2807 if (nobslevOriginal == obsoper%nobslev) then
2808 obsoper%ixtr(:) = ixtrLocal(:)
2809 obsoper%success(:) = successLocal(:)
2810 else
2811 if (all(ixtrLocal(:) == 0)) then
2812 obsoper%ixtr(:)=0
2813 else
2814 obsoper%ixtr(:)=1
2815 end if
2816 if (all(successLocal)) then
2817 obsoper%success(:) = .true.
2818 else
2819 obsoper%success(:) = .false.
2820 end if
2821 end if
2822 deallocate(press_obs,ixtrLocal)
2823
2824 ! Apply averaging kernels if requested
2825
2826 if (obsoper%iavgkern > 0) then
2827
2828 allocate(avg_kern(oopc_avgkern%n_lvl(obsoper%iavgkern), &
2829 oopc_avgkern%n_col(obsoper%iavgkern)))
2830
2831 code=oss_obsdata_get_header_code(obsoper%lon,obsoper%lat, &
2832 obsoper%date,obsoper%hhmm,obsoper%stnid)
2833 call oopc_getAvgkern(obsoper%iavgkern, &
2834 oopc_avgkern%n_lvl(obsoper%iavgkern), &
2835 oopc_avgkern%n_col(obsoper%iavgkern),code,avg_kern)
2836
2837 if (oopc_checkType(oopc_avgkern%stnids,oopc_avgkern%type,obsoper%stnid, &
2838 'default')) then
2839 do obslevIndex=1,oopc_avgkern%n_lvl(obsoper%iavgkern)
2840 if (obsoper%success(obslevIndex)) then
2841
2842 ! Apply averaging kernels to observation operator(s)
2843
2844 obsoper%zh(obslevIndex,:) = matmul(avg_kern(obslevIndex, &
2845 1:obsoper%nobslev),obsoper%zh(:,:))
2846 if (obsoper%applyGenOper) then
2847 obsoper%zhp(obslevIndex,:) = &
2848 matmul(avg_kern(obslevIndex,1:obsoper%nobslev),obsoper%zhp(:,:))
2849 end if
2850
2851 ! Extend vertical range of obs operator according to the influence of
2852 ! the averaging kernel. Either extend to the entire model vertical range
2853 ! (commented out below) or to the vertical range with non-negligable values.
2854
2855 ! obsoper%modlevindexTop(obslevIndex) = 1
2856 ! obsoper%modlevindexBot(obslevIndex) = obsoper%nmodlev
2857
2858 zhmin=1.0D-10*maxval(abs(obsoper%zh(obslevIndex,:)))
2859 do modlevIndex=1,obsoper%modlevindexTop(obslevIndex)
2860 if (abs(obsoper%zh(obslevIndex,modlevIndex)) > zhmin) exit
2861 end do
2862 if (modlevIndex > obsoper%modlevindexTop(obslevIndex)) then
2863 modlevIndex=obsoper%modlevindexTop(obslevIndex)
2864 end if
2865 obsoper%modlevindexTop(obslevIndex) = modlevIndex
2866 do modlevIndex=obsoper%nmodlev,obsoper%modlevindexBot(obslevIndex),-1
2867 if (abs(obsoper%zh(obslevIndex,modlevIndex)) > zhmin) exit
2868 end do
2869 if (modlevIndex.lt.obsoper%modlevindexBot(obslevIndex)) then
2870 modlevIndex=obsoper%modlevindexBot(obslevIndex)
2871 end if
2872 obsoper%modlevindexBot(obslevIndex) = modlevIndex
2873
2874 end if
2875 end do
2876
2877 else if (oopc_checkType(oopc_avgkern%stnids,oopc_avgkern%type, &
2878 obsoper%stnid,'log')) then
2879
2880 if (kmode == 0) then
2881
2882 ! Apply log-space averaging kernel below - no transformation needed here
2883 ! Do not merge the averaging kernel (avgkern) and the vertical interpolator (zh)
2884
2885 else
2886
2887 ! Apply linearization of operator involving the log-space averaging kernel
2888
2889 do obslevIndex=1,obsoper%nobslev
2890 avg_kern(1:oopc_avgkern%n_lvl(obsoper%iavgkern),obslevIndex) = &
2891 avg_kern(1:oopc_avgkern%n_lvl(obsoper%iavgkern),obslevIndex) / &
2892 dot_product( obsoper%zh(obslevIndex, &
2893 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)), &
2894 obsoper%trial(obsoper%modlevindexTop(obslevIndex): &
2895 obsoper%modlevindexBot(obslevIndex)) )
2896 end do
2897
2898 do obslevIndex=1,oopc_avgkern%n_lvl(obsoper%iavgkern)
2899 if (obsoper%success(obslevIndex)) then
2900 avg_kern(obslevIndex,1:obsoper%nobslev) = &
2901 obsoper%obsSpaceTrial(obslevIndex)*avg_kern(obslevIndex, &
2902 1:obsoper%nobslev)
2903
2904 ! Merge the averaging kernel matrix (avgkern) and the
2905 ! vertical interpolator (initial zh)
2906 obsoper%zh(obslevIndex,:) = matmul(avg_kern(obslevIndex, &
2907 1:obsoper%nobslev),obsoper%zh(:,:))
2908
2909 end if
2910 end do
2911
2912 end if
2913
2914 ! Note that obsoper%applyGenOper=.true. is not set up for this case
2915 if (obsoper%applyGenOper) then
2916 call utl_abort('prepareOperator: Log ' // &
2917 'space averaging kernels not currently usable with ' // &
2918 'obsoper%applyGenOper=.true.')
2919 end if
2920 else
2921 call utl_abort('oopc_prepareOperator: This averaging kernel ' // &
2922 'application not yet available')
2923 end if
2924 else
2925 if ( nobslevOriginal /= obsoper%nobslev ) then
2926 call utl_abort('oops_prepareOperator: Case of differing obs ' // &
2927 'and calculation levels not recognized.')
2928 end if
2929 end if
2930
2931 ! Apply generalized innovation operator if requested
2932
2933 if (obsoper%applyGenOper) call oopc_genOper(kmode)
2934
2935 ! Save operator if needed
2936
2937 successDepot = oopc_operatorDepot(headerCount,maxnumHeaders,kmode,'save')
2938
2939 end subroutine oopc_prepareOperator
2940
2941 !--------------------------------------------------------------------------
2942 ! oopc_operatorDepot
2943 !--------------------------------------------------------------------------
2944 function oopc_operatorDepot(headerCount,maxnumHeaders,kmode,action) result(success)
2945 !
2946 !:Purpose: To save or get previously calculated linear observation operator
2947 ! for use by TL and AD operators
2948 !
2949 implicit none
2950
2951 ! Arguments:
2952 integer, intent(in) :: kmode ! Mode of observation operator
2953 integer, intent(in) :: headerCount ! Obs counter/index
2954 integer, intent(in) :: maxnumHeaders ! Total number of CH obs for this CPU
2955 character(len=*), intent(in) :: action ! 'save' or 'get' operator for current obs
2956 ! Result:
2957 logical :: success ! Succes of action succeeded
2958
2959 ! Locals:
2960 integer, save :: maxnumOperators
2961 type(struct_oopc_operatorsDepot), allocatable, save :: operators(:)
2962
2963 success = .false.
2964
2965 if ( .not.oopc_storeOperators .or. kmode < 2 .or. headerCount <= 0 .or. &
2966 ( any(oopc_tropo_mode(:) >= 1) .and. &
2967 trim(obsoper%operatorCategory) == 'Integ') ) return
2968
2969 if ( trim(action) == 'get' .and. initializedOperators .and. kmode >=2 ) then
2970
2971 ! Check for available pre-calculated operator
2972
2973 if (operators(headerCount)%nobslev > 0) then
2974
2975 ! Update arrays/values that might have changed or would otherwise be set
2976 ! below when not stored earlier.
2977
2978 obsoper%trial(:) = operators(headerCount)%trial(:)
2979 obsoper%vlayertop(:) = operators(headerCount)%vlayertop(:)
2980 obsoper%vlayerbottom(:) = operators(headerCount)%vlayerbottom(:)
2981 obsoper%modlevindexTop(:) = operators(headerCount)%modlevindexTop(:)
2982 obsoper%modlevindexBot(:) = operators(headerCount)%modlevindexBot(:)
2983 obsoper%zh(:,:) = operators(headerCount)%zh(:,:)
2984 obsoper%ixtr(:) = operators(headerCount)%ixtr(:)
2985 obsoper%success(:) = operators(headerCount)%success(:)
2986 obsoper%iavgkern = operators(headerCount)%iavgkern
2987 obsoper%applyGenOper = operators(headerCount)%applyGenOper
2988
2989 if ( obsoper%applyGenOper ) then
2990 obsoper%zhp(:,:) = operators(headerCount)%zhp(:,:)
2991 end if
2992
2993 success = .true.
2994 else
2995 ! Calculations will be performed in th calling routine to prepare
2996 ! the operator fields
2997 end if
2998
2999 else if ( trim(action) == 'save' .and. kmode == 2 ) then
3000
3001 ! Save operator for later use
3002
3003 if ( .not.initializedOperators ) then
3004 if (allocated(operators)) deallocate(operators)
3005 maxnumOperators = maxnumHeaders
3006 allocate(operators(maxnumOperators))
3007 initializedOperators = .true.
3008
3009 operators(:)%nobslev = 0
3010
3011 write(*,*) 'oopc_operatorDepot: Max number of operators to save: ', &
3012 maxnumOperators
3013
3014 end if
3015
3016 if (allocated(operators(headerCount)%zh)) then
3017 deallocate(operators(headerCount)%trial)
3018 deallocate(operators(headerCount)%vlayertop)
3019 deallocate(operators(headerCount)%vlayertop)
3020 deallocate(operators(headerCount)%modlevindexTop)
3021 deallocate(operators(headerCount)%modlevindexBot)
3022 deallocate(operators(headerCount)%zh)
3023 deallocate(operators(headerCount)%ixtr)
3024 deallocate(operators(headerCount)%success)
3025 if ( obsoper%applyGenOper ) deallocate(operators(headerCount)%zhp)
3026 end if
3027
3028 allocate(operators(headerCount)%trial(obsoper%nmodlev))
3029 allocate(operators(headerCount)%vlayertop(obsoper%nobslev))
3030 allocate(operators(headerCount)%vlayerbottom(obsoper%nobslev))
3031 allocate(operators(headerCount)%modlevindexTop(obsoper%nobslev))
3032 allocate(operators(headerCount)%modlevindexBot(obsoper%nobslev))
3033 allocate(operators(headerCount)%zh(obsoper%nobslev,obsoper%nmodlev))
3034 allocate(operators(headerCount)%success(obsoper%nobslev))
3035 allocate(operators(headerCount)%ixtr(obsoper%nobslev))
3036
3037 operators(headerCount)%nobslev=obsoper%nobslev
3038 operators(headerCount)%iavgkern=obsoper%iavgkern
3039 operators(headerCount)%applyGenOper=obsoper%applyGenOper
3040 operators(headerCount)%trial(:) = obsoper%trial(:)
3041 operators(headerCount)%vlayertop(:) = obsoper%vlayertop(:)
3042 operators(headerCount)%vlayerbottom(:) = obsoper%vlayerbottom(:)
3043 operators(headerCount)%modlevindexTop(:) = obsoper%modlevindexTop(:)
3044 operators(headerCount)%modlevindexBot(:) = obsoper%modlevindexBot(:)
3045 operators(headerCount)%zh(:,:) = obsoper%zh(:,:)
3046 operators(headerCount)%ixtr(:) = obsoper%ixtr(:)
3047 operators(headerCount)%success(:) = obsoper%success(:)
3048
3049 if ( obsoper%applyGenOper ) then
3050 allocate(operators(headerCount)%zhp(obsoper%nobslev,obsoper%nmodlev))
3051 operators(headerCount)%zhp(:,:) = obsoper%zhp(:,:)
3052 end if
3053
3054 success = .true.
3055
3056 end if
3057
3058 end function oopc_operatorDepot
3059
3060 !--------------------------------------------------------------------------
3061 ! oopc_convertUnits
3062 !--------------------------------------------------------------------------
3063 subroutine oopc_convertUnits(model_col,ppb_opt,incr_opt)
3064 !
3065 !:Purpose: To set unit-conversion factor for consistency of Hx units with
3066 ! obs units.
3067 !
3068 !:Arguments:
3069 ! :ppb_opt: indicates whether model_col should be kept in
3070 ! ug/kg instead of the units dictated by the BUFR
3071 ! number (optional, .false. by default)
3072 ! :incr_opt: indicates if model_col is actually an increment
3073 ! (optional, .false. by default). Needed for non-linear
3074 ! transformations (i.e. for 'HU')
3075 ! :model_col Array to be converted. Either trial or increment-related.
3076 !
3077 !:Comments:
3078 !
3079 ! A. Standard model/analysis species field provided as mass mixing
3080 ! ratio in ug/kg (ppb). Conversion to ppb is applied when this is
3081 ! not the case except for AOD and surface emissions.
3082 ! As this is hard-coded, any changes in analysis variable must
3083 ! be reflected by correspondingly modifying this module.
3084 !
3085 ! B. Unit-conversion factor is calculated in oopc_convertUnits
3086 ! from the following factors:
3087 ! (1) physical constants
3088 ! (2) parameters related to a particular species such as molecular
3089 ! mass
3090 ! (3) variables such as T and P from background field at each
3091 ! iteration
3092 !
3093 ! C. The baseline integral observation operator can be interpreted as
3094 ! being integrals of the gas partial pressure, giving products in
3095 ! kg/m^2, e.g. with sample discretized layer integrals::
3096 ! (mass density) * dz = - d(gas partial pressure)/g
3097 ! = - [rho(gas)/rho(air)]*dP/g
3098 ! = - 1E-9 * [mass mixing ratio in parts per billion (ppb)]*dP/g
3099 !
3100 ! The actual integration in pressure (in Pascal) is performed
3101 ! outside this routine. For integral products in kg/m^2, the output
3102 ! of this routine is to be in mmr/(m/s^2) (mmr=mass mixing ratio),
3103 ! which is equivalent to (1E-9 ug/kg)/(m/s^2) and kg/(m^2*Pa).
3104 ! Therefore, the input value in ug/kg has to be multiplied by 1E-9/g
3105 ! (g=RG below).
3106 !
3107 ! For integral products in other units, additional conversion
3108 ! factors are also to be applied.
3109 !
3110 ! D. List should be revised following changes to the 'tableburp' file.
3111 !
3112 !
3113 ! E. Coefficients related to unit conversion
3114 !
3115 ! rho_stp=1.293 Air density at STP (1.293 kg/m^3)
3116 ! RG=9.807 (=g) Acceleration due to gravity (m/s^2)
3117 ! MPC_AVOGADRO_R8 = Na Avogadro's number. 6.023E23 molecules/mole
3118 ! MPC_MOLAR_MASS_DRY_AIR_R8 (m_air) Dry air molecular mass. 28.9644 g/mole
3119 ! MPC_RGAS_IDEAL_R8 = R Ideal gas constant. 8.341 J/mole/K (J=kg m^2/s^2)
3120 !
3121 ! PV = nRT (n=number of moles)
3122 !
3123 ! MPC_RGAS_DRY_AIR_R8 = Rd Dry air constant. 287.1 J/kg/K (J=kg m^2/s^2)
3124 ! = MPC_RGAS_IDEAL_R8 * 1000 g/km / MPC_MOLAR_MASS_DRY_AIR_R8
3125 !
3126 ! P=rho*Rd*T = [n*m_air*0.001 kg/g]*Rd*T
3127 ! = n*[m_air*0.001*Rd]*T
3128 ! = nRT
3129 ! Further changes required for generalization
3130 !
3131 ! 1) Conversion for surface emissions not included as yet (if any is
3132 ! needed)
3133 !
3134 implicit none
3135
3136 ! Arguments:
3137 real(8), intent(inout) :: model_col(obsoper%nmodlev) ! Model-space profile to have its units changed
3138 logical, optional, intent(in) :: ppb_opt
3139 logical, optional, intent(in) :: incr_opt
3140
3141 ! Locals:
3142 real(8) :: zcoef
3143 integer :: exp_P,exp_T
3144 logical :: ppb_out, incr_out
3145 real(8), parameter :: rho_stp=1.293 ! kg/m^3
3146
3147 if (obsoper%constituentId < 0) return
3148
3149 ! No conversion necessary for these BUFR numbers
3150 if (any( obsoper%varno == (/ BUFR_UNIT_OptDepth,BUFR_UNIT_OptDepth2, &
3151 BUFR_UNIT_OptDepth3, BUFR_UNIT_MR_NVaerosol, BUFR_NETT /) )) return
3152
3153 if (present(ppb_opt)) then
3154 ppb_out = ppb_opt
3155 else
3156 ppb_out = .false.
3157 end if
3158
3159 if (present(incr_opt)) then
3160 incr_out = incr_opt
3161 else
3162 incr_out = .false.
3163 end if
3164
3165 zcoef = 1.
3166 exp_T = 0 ! exponent of multiplicative factor obsoper%tt
3167 exp_P = 0 ! exponent of multiplicative factor obsoper%pp
3168
3169 ! Convert to ug/kg if not already in those units
3170
3171 if (obsoper%varName(1:2) == 'AF' .or. obsoper%varName(1:2) == 'AC') then
3172
3173 ! PM2.5 or PM10
3174
3175 if (any(obsoper%varno == (/ BUFR_UNIT_VMR, BUFR_UNIT_VMR2, &
3176 BUFR_UNIT_MolePerMole, BUFR_UNIT_MolePerMole2, &
3177 BUFR_UNIT_NumberDensity, BUFR_UNIT_MolarDensity, &
3178 BUFR_UNIT_PartPress, BUFR_UNIT_PartPress2, &
3179 BUFR_UNIT_DU, BUFR_UNIT_DU2, BUFR_UNIT_DU3, BUFR_UNIT_DU4, &
3180 BUFR_UNIT_IntegND, BUFR_UNIT_IntegND2 /) )) then
3181
3182 call utl_abort("oopc_convertUnits: BUFR # " // trim(utl_str(obsoper%varno)) // " is not valid for PM" )
3183 end if
3184
3185 ! Conversion from ug/m^3 to ug/kg (scaling by Rd*T/P)
3186 zcoef = zcoef * MPC_RGAS_DRY_AIR_R8
3187 exp_T = exp_T+1 ! multiply by T
3188 exp_P = exp_P-1 ! divide by P
3189
3190 else if (obsoper%varName(1:2) == 'HU') then
3191
3192 if (.not.incr_out) then
3193 ! Converts specific humidity (q) to mass mixing ratio mmr = q/(1-q)
3194 model_col = model_col / (1.0d0 - model_col)
3195 else
3196 ! For conversion of q increment (dq) to mass mixing ratio increment (dmmr)
3197 ! dmmr = dq/(1-q)^2
3198 model_col = model_col/(1.0d0 - obsoper%trial)**2
3199 end if
3200 ! Conversion factor for kg/kg to ug/kg
3201 zcoef = zcoef * 1.0d9
3202
3203 end if
3204
3205 ! Convert from ug/kg to desired unit if ppb_out = .false.
3206
3207 if (.not.ppb_out) then
3208 select case (obsoper%varno)
3209
3210 ! The first four cases below are for integral observations which
3211 ! require a conversion, in this routine, from ug/kg to the units of
3212 ! the integrand values for integrals in pressure (Pascal). Comment C above.
3213 ! Note: 1 ug/kg = 1 ppb = 1E9 mmr (mass mixing ratio)
3214
3215 case(BUFR_UNIT_IntegDens, BUFR_UNIT_IntegDens2, BUFR_UNIT_IntegDens3)
3216
3217 ! For conversion from ug/kg to integrand values in kg/(m^2*Pa)
3218 ! Note: 1 kg/(m^2**Pa) = = 1 mmr / RG = 1E-9 ug/kg / RG
3219 !
3220 zcoef = zcoef * 1.0d-9 / ec_rg
3221
3222 case(BUFR_UNIT_IntegMolarDens)
3223
3224 ! For conversion from ug/kg to integrand values in moles/(m^2*Pa)
3225 ! Note: 1 moles/(m^2*Pa) = 1E-9 ug/kg / [RG * (1E-3 kg/g * m_gas)]
3226 !
3227 ! To convert from kg/m^2 for the gas to moles/m^2, one must
3228 ! divide by the molar mass of the gas in kg/mole.
3229 !
3230 ! Note: One u or Da (unified atomic mass unit or dalton) is numerically equivalent to 1 g/mole.
3231 ! So 1 kg is equivalent to (1E3/(atomic mass)) moles
3232
3233 zcoef = zcoef * 1.0d-6 / (ec_rg*vnl_varMassFromVarNum(obsoper%constituentId))
3234
3235 case(BUFR_UNIT_IntegND, BUFR_UNIT_IntegND2)
3236
3237 ! For conversion from ug/kg to integrand values in molecules/(m^2*Pa)
3238 ! Note: 1 molecule/(m^2*Pa) = 1E-9 ug/kg * Na / [RG * (1E-3 kg/g * m_gas)]
3239 !
3240 ! To convert from kg/m^2 for the gas to molecules/m^2, one must
3241 ! divide by the gas molar mass (kg/mole) and multiply by the Avogrado number
3242
3243 zcoef = zcoef * 1.0d-6 * MPC_AVOGADRO_R8 &
3244 / (ec_rg*vnl_varMassFromVarNum(obsoper%constituentId))
3245 case(BUFR_UNIT_DU, BUFR_UNIT_DU2, BUFR_UNIT_DU3, BUFR_UNIT_DU4)
3246
3247 ! For conversion from ug/kg to integrand values in DU/Pa
3248 ! Note: 1 DU/Pa = 1E-9 ug/kg / [RG * m_gas * rho_stp/m_air * 1E-5 m]
3249 !
3250 ! 1 DU = 0.01 mm of gas at STP = 1E-5 m of gas at STP
3251 ! = integral of gas number density at STP over 1E-5 m.
3252 ! = Na*P/(RT) * 1E-5 at STP (Na=Avogadro's number)
3253 ! = Na*(molar density) * 1E-5 at STP
3254 ! = Na*rho(STP)/(molar mass) * 1E-5 m
3255 ! = integral of air number density at STP over 1E-5 m.
3256 ! = Na*rho(air,STP)/m_air * 1.E-5 m
3257 !
3258 ! Hence 1 DU equivalent to Na*rho_stp/m_air * 1E-5 m (= 2.69E20 molecules/m^2)
3259 !
3260 ! To convert from kg/m^2 for the gas to molecules/m^2, one must
3261 ! divide by the gas molar mass (kg/mole) and multiply by the Avogrado number
3262 !
3263 ! To convert from molecules/m^2 to DU, one must divide by 2.69E20 or (Na*rho_stp/m_air * 1E-5).
3264 ! So for conversion from kg/m^2 to DU, must divide by (m_gas*rho_stp/m_air * 1E-5)
3265
3266 zcoef = zcoef * 1.0d-4 * MPC_MOLAR_MASS_DRY_AIR_R8 &
3267 /(vnl_varMassFromVarNum(obsoper%constituentId)*ec_rg*rho_stp)
3268
3269 case(BUFR_UNIT_Density, BUFR_UNIT_Density2, &
3270 BUFR_UNIT_AirDensity, BUFR_UNIT_PMDensity)
3271
3272 ! For conversion from ug/kg to kg/m^3
3273 !
3274 ! rho(gas) = mass mixing ratio * rho(air) = mass mixing ratio * P/Rd/T
3275
3276 zcoef = zcoef * 1.0d-9 / MPC_RGAS_DRY_AIR_R8
3277 exp_T = exp_T-1 ! divide by T
3278 exp_P = exp_P+1 ! multiply by P
3279
3280 case(BUFR_UNIT_MMR, BUFR_UNIT_MMR2)
3281
3282 ! For conversion from ug/kg to kg/kg
3283
3284 zcoef = zcoef * 1.0d-9
3285
3286 case(BUFR_UNIT_PartPress, BUFR_UNIT_PartPress2)
3287
3288 ! For conversion from ug/kg to partial pressure (PA)
3289 !
3290 ! parial pressure = P * vmr
3291 ! = P * m_air/m_gas * mass mixing ratio
3292
3293 zcoef = zcoef * 1.0d-9 * MPC_MOLAR_MASS_DRY_AIR_R8 &
3294 /vnl_varMassFromVarNum(obsoper%constituentId)
3295 exp_P = exp_P+1 ! multiply by P
3296
3297 case(BUFR_UNIT_NumberDensity)
3298
3299 ! For conversion from ug/kg to molecules/m^3
3300 !
3301 ! Number density of gas = Na*rho(gas)/m_gas = Na*rho(air) * mass mixing ratio /m_gas
3302 ! = Na * P/Rd/T * mass mixing ratio /m_gas
3303
3304 zcoef = zcoef * 1.0d-6 * MPC_AVOGADRO_R8/MPC_RGAS_DRY_AIR_R8 &
3305 /vnl_varMassFromVarNum(obsoper%constituentId)
3306 exp_T = exp_T-1 ! divide by T
3307 exp_P = exp_P+1 ! multiply by P
3308
3309 case(BUFR_UNIT_MolarDensity)
3310
3311 ! For conversion from ug/kg to moles/m^3
3312 !
3313 ! Mole density of gas = rho(gas)/m_gas = rho(air) * mass mixing ratio /m_gas
3314 ! = P/Rd/T * mass mixing ratio /m_gas
3315
3316 zcoef = zcoef * 1.0d-6 /MPC_RGAS_DRY_AIR_R8 &
3317 /vnl_varMassFromVarNum(obsoper%constituentId)
3318 exp_T = exp_T-1 ! divide by T
3319 exp_P = exp_P+1 ! multiply by P
3320
3321 case(BUFR_UNIT_VMR, BUFR_UNIT_VMR2, BUFR_UNIT_MolePerMole, &
3322 BUFR_UNIT_MolePerMole2)
3323
3324 ! For conversion from ug/kg to vmr (or moles/mole)
3325
3326 zcoef = zcoef * 1.0d-9 * MPC_MOLAR_MASS_DRY_AIR_R8 &
3327 /vnl_varMassFromVarNum(obsoper%constituentId)
3328
3329 case default
3330
3331 call utl_abort('oopc_convertUnits: Unknown obs units ' // &
3332 'for varno = ' // trim(utl_str(obsoper%varno)) )
3333
3334 end select
3335 end if
3336
3337 ! Apply constant scaling
3338 model_col = model_col * zcoef
3339
3340 if (exp_T /= 0) then
3341 if (any(obsoper%tt <= 0.)) then
3342 call utl_abort("oopc_convertUnits: " // &
3343 "Missing valid temperature for conversion.")
3344 end if
3345 model_col = model_col * obsoper%tt**exp_T
3346 end if
3347
3348 if (exp_P /= 0) model_col = model_col * obsoper%pp**exp_P
3349
3350 end subroutine oopc_convertUnits
3351
3352 !--------------------------------------------------------------------------
3353 ! oopc_required_field
3354 !--------------------------------------------------------------------------
3355 function oopc_required_field(varName,varno) result(needed)
3356 !
3357 !:Purpose: To determine whether the specifed field name is required
3358 ! somewhere in the observation operators for a particular
3359 ! observation type.
3360 !
3361 implicit none
3362
3363 ! Arguments:
3364 character(len=*), intent(in) :: varName ! Name of field
3365 integer, intent(in) :: varno ! BUFR descriptor element
3366 ! Result:
3367 logical :: needed
3368
3369 select case(trim(varName))
3370 case('TT')
3371
3372 select case (varno)
3373 case(BUFR_UNIT_Density,BUFR_UNIT_Density2,BUFR_UNIT_AirDensity, &
3374 BUFR_UNIT_PMDensity,BUFR_UNIT_NumberDensity,BUFR_UNIT_MolarDensity)
3375 needed = .true.
3376 case default
3377 needed = .false.
3378 end select
3379
3380 case default
3381 needed = .false.
3382 end select
3383
3384 end function oopc_required_field
3385
3386 !--------------------------------------------------------------------------
3387 ! oopc_vertObsLayersWgts
3388 !--------------------------------------------------------------------------
3389 subroutine oopc_vertObsLayersWgts(operator,ixtr,success,kmode,skipType)
3390 !
3391 !:Purpose: To calculate integration (or averaging) weights "wgts" required for vertical
3392 ! integration (or averaging) for the full vertical range or a set of target layers.
3393 ! Given the calculated weights and a user input array vector X, the integral
3394 ! (or average) for a given layer i would be given by sum(wgts(i,:)*X(:))
3395 !
3396 !:Arguments:
3397 ! :operator: Operator type: 'integ' or 'avg'
3398 ! :ixtr: Flag indicating if obs outside model vertical range: 0 for no.
3399 ! :successs: Success of integration (or averaging)
3400 ! :kmode: Observation model stage used to allow option of tropo
3401 ! increment determination from total (or avg) column data when
3402 ! obsoper%columnBound > obsoper%vlayertop for
3403 ! nobslev=1. For kmode=3, calc only for region between
3404 ! obsoper%columnBound and surface. For kmode=2, split
3405 ! calc for model top to obsoper%columnBound and
3406 ! obsoper%columnBound and surface.
3407 !
3408 ! :skipType: Skipping processing of specific target layers depending on case:
3409 ! 'default' - skipping application via input success_opt only
3410 ! 'doAll&noExtrap' - application of both success_opt and outbound_opt
3411 !
3412 implicit none
3413
3414 ! Arguments:
3415 integer, intent(inout) :: ixtr(obsoper%nobslev) ! Flag indicating if obs outside model vertical range: 0 for no.
3416 logical, intent(inout) :: success(obsoper%nobslev) ! success of integration (or averaging)
3417 integer, intent(in) :: kmode ! Mode of observation operator
3418 character(len=*), intent(in) :: operator ! Operator type: 'integ' or 'avg'
3419 character(len=*), intent(in) :: skipType ! Skipping processing of specific target layers depending on case
3420
3421 ! Locals:
3422 integer :: obslevIndex,tropo_mode
3423 real(8) :: vlayertop_ref,vlayerbottom_ref,modlevindexBot_ref,checkID
3424
3425 ! Conduct initial setup for vertical integration (or avegaging) components
3426
3427 call ppo_vertLayersSetup(operator,obsoper%pp,obsoper%nmodlev)
3428
3429 ! Ensure that each layer is within model vertical range.
3430
3431 do obslevIndex=1,obsoper%nobslev
3432
3433 if (obsoper%vlayerbottom(obslevIndex) < obsoper%vlayertop(obslevIndex)) then
3434 success(1:obsoper%nobslev)=.false.
3435 write(*,*) 'oopc_vertObsLayersWgts: WARNING. ' // &
3436 'Layer top/bot value problem.', &
3437 obsoper%vlayertop(obslevIndex), &
3438 obsoper%vlayerbottom(obslevIndex), &
3439 '. Entire profile skipped over.'
3440 return
3441 else if (obsoper%vlayerbottom(obslevIndex) < obsoper%pp(1)*1.01 .or. &
3442 obsoper%vlayertop(obslevIndex) > obsoper%pp(obsoper%nmodlev)*0.99) then
3443
3444 success(obslevIndex)=.false.
3445 if (obsoper%vlayerbottom(obslevIndex) < obsoper%pp(1)*1.01) then
3446 ixtr(obslevIndex)=1
3447 else
3448 ixtr(obslevIndex)=2
3449 end if
3450 write(*,*) 'oopc_vertObsLayersWgts: WARNING. Layer top/bot value problem.', &
3451 obsoper%vlayertop(obslevIndex), obsoper%vlayerbottom(obslevIndex)
3452 cycle
3453 end if
3454 if (obsoper%vlayerbottom(obslevIndex) > &
3455 obsoper%pp(obsoper%nmodlev)*0.999) then
3456
3457 obsoper%vlayerbottom(obslevIndex)=obsoper%pp(obsoper%nmodlev)*0.999
3458
3459 end if
3460 if (obsoper%vlayertop(obslevIndex) < obsoper%pp(1)*1.001) then
3461 obsoper%vlayertop(obslevIndex)=obsoper%pp(1)*1.001
3462 end if
3463
3464 end do
3465
3466 tropo_mode=0
3467
3468 if (obsoper%nobslev == 1 .and. kmode >= 2 .and. trim(operator) == 'integ' &
3469 .and. obsoper%vlayerbottom(1) > obsoper%pp(obsoper%nmodlev)*0.99 .and. &
3470 obsoper%constituentId >= 0 .and. obsoper%vco == 4) then
3471
3472 ! Check if constituent id is recognized (function will abort if not recognized)
3473 if ( obsoper%constituentId >= 0 ) then
3474 checkID = vnl_varMassFromVarnum(obsoper%constituentId)
3475 end if
3476
3477 vlayerbottom_ref=obsoper%vlayerbottom(1)
3478
3479 ! Check for special treatment if tropo_mode>=1, kmode=2,3, and nobslev=1 for
3480 ! column observations (obsoper%vco=4) that extend to the surface.
3481
3482 tropo_mode = oopc_tropo_mode(obsoper%constituentId)
3483
3484 if ( tropo_mode >= 1 .and. obsoper%columnBound > obsoper%vlayertop(1) ) then
3485
3486 if (obsoper%iavgkern /= 0) then
3487 call utl_abort("oopc_vertObsLayersWgts: Use of averaging ' // &
3488 'kernels not possible with reduced range of increment profile.")
3489 end if
3490
3491 if (kmode == 2 .and. tropo_mode == 1) then
3492
3493 ! When kmode=2, split calc in two. This is done due to difference in
3494 ! calc at the interface region when producing zh and zhp. The tangent linear
3495 ! model in the lower region for kmode=2 must be consistent with
3496 ! that associated with kmode=3.
3497
3498 ! Start with bottom region in order to use correct zhp with oopc_genOper
3499 ! when use of this operator is requested.
3500
3501 vlayertop_ref=obsoper%vlayertop(1)
3502
3503 obsoper%vlayertop(1) = obsoper%columnBound
3504
3505 call ppo_vertIntegWgts(obsoper%vlayertop,obsoper%vlayerbottom, &
3506 obsoper%nmodlev,obsoper%nobslev,obsoper%modlevindexTop, &
3507 obsoper%modlevindexBot,obsoper%zh,wgts_opt=obsoper%zhp, &
3508 skipType_opt=skipType,outbound_opt=ixtr,success_opt=success)
3509
3510 ! Apply generalized innovation operator if requested
3511 if (obsoper%applyGenOper) call oopc_genoper(kmode)
3512
3513 obsoper%applyGenOper=.false.
3514
3515 modlevindexBot_ref=obsoper%modlevindexBot(1)
3516
3517 ! Reset top and bottom values for integration (or averaging) of the remaining region.
3518 ! The second integration (or averaging) provides the change in upper level contributions to the
3519 ! total column (or average) from the assimilation of other observations.
3520
3521 obsoper%vlayertop(1)=vlayertop_ref
3522 obsoper%vlayerbottom(1)=obsoper%columnBound
3523 else
3524 ! Reset top new value. Restricts adjoint/tangent linear calcs to this reduced region.
3525 obsoper%vlayertop(1)=obsoper%columnBound
3526 end if
3527
3528 end if
3529 else
3530 vlayerbottom_ref=0.0
3531 end if
3532
3533 ! Calculate vertical integration (or averaging) components for specified obs layer(s).
3534
3535 if ( trim(operator) == 'integ' ) then
3536
3537 call ppo_vertIntegWgts(obsoper%vlayertop,obsoper%vlayerbottom, &
3538 obsoper%nmodlev,obsoper%nobslev,obsoper%modlevindexTop, &
3539 obsoper%modlevindexBot,obsoper%zh,wgts_opt=obsoper%zhp, &
3540 skipType_opt=skipType,outbound_opt=ixtr,success_opt=success, &
3541 dealloc_opt=.true.)
3542
3543 ! If tropo_mode=1, reset original vertical range
3544 ! for the tangent linear operator
3545 if (obsoper%nobslev == 1 .and. kmode == 2 .and. &
3546 obsoper%constituentId >= 0 .and. obsoper%vco == 4) then
3547
3548 if (tropo_mode == 1 .and. &
3549 obsoper%columnBound > obsoper%vlayertop(1) .and. &
3550 vlayerbottom_ref > obsoper%pp(obsoper%nmodlev)*0.99) then
3551
3552 obsoper%vlayerbottom(1)=vlayerbottom_ref
3553 obsoper%modlevindexBot(1)=modlevindexBot_ref
3554 end if
3555 end if
3556 else
3557
3558 call ppo_vertAvgWgts(obsoper%vlayertop,obsoper%vlayerbottom, &
3559 obsoper%nmodlev,obsoper%nobslev,obsoper%modlevindexTop, &
3560 obsoper%modlevindexBot,obsoper%zh,wgts_opt=obsoper%zhp, &
3561 skipType_opt=skipType,outbound_opt=ixtr,success_opt=success, &
3562 dealloc_opt=.true.)
3563
3564 end if
3565
3566 end subroutine oopc_vertObsLayersWgts
3567
3568 !--------------------------------------------------------------------------
3569 ! oopc_genOper
3570 !--------------------------------------------------------------------------
3571 subroutine oopc_genOper(kmode)
3572 !
3573 !:Purpose: Set generalized innovation operator for integral or layer avg
3574 ! obs. Relevant only for incremental fields. This version is
3575 ! intended to vertically distribute (approximately) the obs increments
3576 ! proportionally to the (a) background state or (b) the differences
3577 ! between a reference/climatological state and the background state.
3578 ! Option (b) is recommended for a spinup period when the shape of the
3579 ! background state is not physically representative. This is to force
3580 ! the analysis shape toward the reference/climatology shape in the absence
3581 ! of local profiles observations.
3582 !
3583 !:Input:
3584 ! :kmode: index indicating if the operator is to be applied
3585 ! :obsoper%zh,zhp: see routine ppo_vertIntegSetup
3586 ! :oopc_genOperConstraintType: index specifying the reference state
3587 ! :oopc_genOperhCorrlenExpnt: Exponent for horiz. correl. length weighting
3588 ! :oopc_genOperOmaStatsFactor: Additional OmAStats normalization factor
3589 ! :bgStats: structure containing the background stats data
3590 !
3591 !:Output:
3592 ! :obsoper%zh(obsoper%nmodlev): a*w: Final innovation model array
3593 ! (other than conversion constants)
3594 ! :obsoper%zhp(obsoper%nmodlev): w (see comments section)
3595 !
3596 ! Comments:
3597 !
3598 ! (1) This routine prepares an alternative innovation operator g,
3599 ! called the generalized innovation operator, to take the place of
3600 ! the innovation (TLM) operator h (row of zh). The operator g is
3601 ! specified as:
3602 !
3603 ! g = a*w
3604 !
3605 ! where the innovation weight operator 'w' can be set as,
3606 ! for the 1D case,
3607 !
3608 ! w = P[ (h'x)^T ] * B^{-1}
3609 !
3610 ! with h' is the part of h which excludes resolution dependence
3611 ! (only/mostly contains the physics part of h; zhp),
3612 !
3613 ! - x is the state profile rval
3614 ! - P is a window cutoff operator (sets small values to zero)
3615 ! - B is the original/initial total "vertical" covariance matrix
3616 ! (2D)
3617 !
3618 !
3619 ! and 'a' is a proportionality constant ensuring that the
3620 ! innovation increment remains unchanged for the 1D case in the
3621 ! absence of other obs., i.e.,
3622 !
3623 ! a^2 = (h*B*h^T)(w*B*w^T)^{-1},
3624 !
3625 ! Application of the state profile x (rval) is to make the
3626 ! increment profile be more proportional to the state profile.
3627 !
3628 ! The presence of B^{-1} is to negate the weight re-distribution
3629 ! from the later application of B in grad(Jo). Its specification
3630 ! is approximate to reduce the effect of numerical error in the
3631 ! generating the inverse of B.
3632 !
3633 ! A scaling by the 1/bgStats%hcorrlen^q profile is addded to
3634 ! partially mitigate effect of the variation in the vertical
3635 ! of the influence of neighbouring obs via background horizontal
3636 ! error correlations.
3637 !
3638 ! The specification of the q value is to roughly give (a) a more
3639 ! constant-like increment in the absence of the trial profile form
3640 ! constraint and (b), in the presence of the form constraint, a
3641 ! better match of the max increment level to the max concentration
3642 ! level.
3643 !
3644 ! As the horizontal density of obs is somewhat variable
3645 ! and not known locally, the specified q value is not optimal for each
3646 ! individual case but better than not applying any related mitigation.
3647 !
3648 ! (2) The matrix B is the total error covariance matrix (in physical space)
3649 ! with the related error correlation matrix
3650 ! *corvert* and std dev provided from *bCovarSetupChem_mod.ftn90*.
3651 !
3652 ! (3) NOTE: Cases with ensemble-based and or lam-based background
3653 ! covariances are not taken into account in this version.
3654 !
3655 implicit none
3656
3657 ! Arguments:
3658 integer, intent(in) :: kmode ! Index specifying if content to be applied (i.e. if kmode>1)
3659
3660 ! Locals:
3661 real(8), parameter :: pwin=0.01
3662 integer :: obslevIndex,modlevIndex,irmse,varIndex
3663 real(8) :: zwbw,zhbh,za,work(obsoper%nmodlev),fdeStddev(obsoper%nmodlev,2)
3664 real(8), parameter :: threshold=1.D-20
3665 real(8) :: zmin,rvalw(obsoper%nmodlev),rvalr(obsoper%nmodlev)
3666 real(8) :: rvalc(obsoper%nmodlev),rmse
3667 character(len=22) :: code
3668
3669 if (kmode <= 1) return
3670
3671 ! Retrieve from stored background error std dev [elemements (:,1-2)] at obs location [and inverses at elements (:,3-4)]
3672 fdeStddev = bcsc_retrieveBgStddev(obsoper%nmodlev,2,obsoper%obs_index)
3673 if (obsoper%constituentId == 0) then
3674 ! To mitigate combined effects of approximations in expressions below and
3675 ! the forced strong reduction of fdeStddev(:,1) in the top level(s) of the
3676 ! model. Bkgd ozone error std dev was set to near zero at the lid. The
3677 ! absence of a resettting such as below would otherwise
3678 ! lead to oscillatory numerical error effects in the first few levels.
3679 ! This change will still force a near-zero (small) increment at the lid.
3680 fdeStddev(1,2) = fdeStddev(2,2)
3681 end if
3682
3683 ! Identify variable position index in background error correlation matrices
3684
3685 varIndex=1
3686 do while (trim(bgStats%varNameList(varIndex)) /= '')
3687 if (trim(bgStats%varNameList(varIndex)) == trim(obsoper%varName)) exit
3688 varIndex=varIndex+1
3689 end do
3690
3691 if (trim(bgStats%varNameList(varIndex)) == '') then
3692 call utl_abort('oopc_genOper: Background stats not found for ' // &
3693 trim(obsoper%varName) )
3694 end if
3695
3696 ! Initialize reference mass (mixing ratio) weighting profile as profile from trial field
3697
3698 rvalr(1:obsoper%nmodlev)=obsoper%trial(1:obsoper%nmodlev)
3699
3700 ! Check on rvalr
3701
3702 if (any(abs(rvalr(:)) <= threshold)) then
3703 if (all(abs(rvalr(:)) <= threshold)) then
3704 rvalr(:)=1.0
3705 else
3706 zmin=minval(abs(rvalr(:)), &
3707 mask=abs(rvalr(:)) > threshold)
3708 where (abs(rvalr(:)) <= threshold) rvalr(:)=zmin
3709 end if
3710 end if
3711
3712 ! Loop over obs elements
3713
3714 write(code,'(I22)') obsoper%obs_index
3715
3716 do obslevIndex=1,obsoper%nobslev
3717
3718 if (.not.obsoper%success(obslevIndex)) cycle
3719
3720 ! Set reference mass (mixing ratio) weighting profile
3721
3722 rvalw(1:obsoper%nmodlev)=rvalr(1:obsoper%nmodlev)
3723 if (trim(oopc_genOperConstraintType(obsoper%constituentId)) == 'Diff') then
3724
3725 ! Set reference mass weighting profile according to the difference between
3726 ! an external reference (such as a climatology) and trial field profiles.
3727 !
3728 ! This is a mechanism to force the solution profile shape somewhat towards that
3729 ! of the external reference when the reliability of the vertical structure of the
3730 ! trial field is not high.
3731 !
3732 ! This can be used at the beginning of long assimilation periods if the initial
3733 ! trial field is not as realistic as may be desired or somewhat mitigate
3734 ! gradual biasing of vertical structures that might otherwise occur from assimilation
3735 ! of integrated quantities (when there is insufficient data from other observation types).
3736 !
3737 ! The larger the rms difference of xc (external reference) with xb (trial field profile),
3738 ! the greater is the influence of this difference in the weighting. If there is
3739 ! little difference, then either xb or xc can be directly used as the weighting
3740 ! profile; xb is used below.
3741
3742 rmse=0.0d0
3743 irmse=0
3744 rvalc(1:obsoper%nmodlev) = oopc_getProfile(oopc_bgRef,code)
3745
3746 zmin=pwin*maxval(abs(obsoper%zhp(obslevIndex,1:obsoper%nmodlev)))
3747 do modlevIndex=1,obsoper%nmodlev
3748 if (obsoper%zhp(obslevIndex,modlevIndex) > zmin) then
3749 irmse=irmse+1
3750 rmse=rmse+((rvalc(modlevIndex)-obsoper%trial(modlevIndex))* &
3751 fdeStddev(modlevIndex,2))**2
3752 end if
3753 end do
3754 if (irmse > 0) rmse=rmse*2.0d0/irmse
3755 if (rmse < 1.0d0) then
3756 rvalw(1:obsoper%nmodlev)=rvalc(1:obsoper%nmodlev)
3757 else
3758 rvalw(1:obsoper%nmodlev)=rvalc(1:obsoper%nmodlev)- &
3759 obsoper%trial(1:obsoper%nmodlev)
3760 where (abs(rvalw(1:obsoper%nmodlev)) < 0.01d0* &
3761 rvalc(1:obsoper%nmodlev))
3762 rvalw(1:obsoper%nmodlev)=sign(0.01d0*rvalc(1:obsoper%nmodlev), &
3763 rvalw(1:obsoper%nmodlev))
3764 end where
3765 end if
3766 if (all(abs(rvalw(:)) <= threshold)) then
3767 rvalw(1:obsoper%nmodlev)=rvalc(1:obsoper%nmodlev)
3768 else if (any(abs(rvalw(:)) <= threshold)) then
3769 zmin=minval(abs(rvalw(:)),mask=abs(rvalw(:)) > threshold)
3770 where (abs(rvalw(:)) <= threshold) rvalw(:)= zmin
3771 end if
3772 end if
3773
3774 ! Begin preparation of the new innovation operator w (=new zhp)
3775
3776 if (obsoper%nobslev == 1 .and. obsoper%modlevindexTop(obslevIndex) == 1 .and. &
3777 obsoper%modlevindexBot(obslevIndex) == obsoper%nmodlev) then
3778
3779 ! Treat as total column obs. Here, zhp would otherwise be approx. equal
3780 ! to 1 except for the near-end points of the model vertical domain,
3781 ! the latter due to the discretized domain. Not using zhp avoids this
3782 ! discretization issue from weakly affecting results at the boundaries.
3783
3784 work(1:obsoper%nmodlev)=1.0
3785
3786 else
3787
3788 ! Account for localized obs function (e.g. partial columns, Jacobians. For Jacobians,
3789 ! zhp must also be independent of the model layer thicknesses.)
3790
3791 ! Apply cutoff
3792 zmin=pwin*maxval(abs(obsoper%zhp(obslevIndex,1:obsoper%nmodlev)))
3793 where (abs(obsoper%zhp(obslevIndex,1:obsoper%nmodlev)) < zmin)
3794 work(1:obsoper%nmodlev)=0.0d0
3795 elsewhere
3796 work(1:obsoper%nmodlev)=obsoper%zhp(obslevIndex,1:obsoper%nmodlev)
3797 endwhere
3798 end if
3799
3800 ! Application of 1D space vertical covariance inverse B^{-1} for partially
3801 ! mitigate the weight impact of the later application of B in finalizing
3802 ! grad(Jo). Note: fdeStddev(:,2)=1.0/fdeStddev(:,1)
3803
3804 ! Also impose
3805 ! - approximate mitigation of horizontal correlation effect
3806 ! - shape forcing profile via rvalw
3807
3808 work(1:obsoper%nmodlev)=work(1:obsoper%nmodlev)*rvalw(1:obsoper%nmodlev) &
3809 *fdeStddev(1:obsoper%nmodlev,2)
3810
3811 obsoper%zhp(obslevIndex,:)=0.0D0
3812 !$OMP PARALLEL DO PRIVATE(modlevIndex)
3813 do modlevIndex=obsoper%modlevindexTop(obslevIndex), &
3814 obsoper%modlevindexBot(obslevIndex)
3815 obsoper%zhp(obslevIndex,modlevIndex) = fdeStddev(modlevIndex,2) &
3816 /bgStats%hcorrlen(modlevIndex,varIndex)**oopc_genOperHCorrlenExpnt(varIndex) &
3817 *sum(work(1:obsoper%nmodlev) &
3818 *bgStats%corverti(1:obsoper%nmodlev,modlevIndex,varIndex))
3819 end do
3820 !$OMP END PARALLEL DO
3821
3822 ! Determine proportionality factor 'a' = (h*B*h^T)(w*B*w^T)^{-1}
3823
3824 ! First determine/estimate w*B*w^T (zwbw)
3825
3826 !$OMP PARALLEL DO PRIVATE(modlevIndex)
3827 do modlevIndex=obsoper%modlevindexTop(obslevIndex), &
3828 obsoper%modlevindexBot(obslevIndex)
3829
3830 work(modlevIndex)=sum(obsoper%zhp(obslevIndex, &
3831 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) &
3832 *bgStats%corvert(modlevIndex,obsoper%modlevindexTop(obslevIndex): &
3833 obsoper%modlevindexBot(obslevIndex),varIndex) &
3834 *fdeStddev(obsoper%modlevindexTop(obslevIndex): &
3835 obsoper%modlevindexBot(obslevIndex),1))*fdeStddev(modlevIndex,1)
3836 end do
3837 !$OMP END PARALLEL DO
3838
3839 zwbw=sum(obsoper%zhp(obslevIndex,obsoper%modlevindexTop(obslevIndex): &
3840 obsoper%modlevindexBot(obslevIndex)) &
3841 *work(obsoper%modlevindexTop(obslevIndex): &
3842 obsoper%modlevindexBot(obslevIndex)))
3843
3844 ! Determine/estimate h*B*h^T (zhbh)
3845
3846 !$OMP PARALLEL DO PRIVATE(modlevIndex)
3847 do modlevIndex=obsoper%modlevindexTop(obslevIndex), &
3848 obsoper%modlevindexBot(obslevIndex)
3849
3850 work(modlevIndex)=sum(obsoper%zh(obslevIndex, &
3851 obsoper%modlevindexTop(obslevIndex):obsoper%modlevindexBot(obslevIndex)) &
3852 *bgStats%corvert(modlevIndex,obsoper%modlevindexTop(obslevIndex): &
3853 obsoper%modlevindexBot(obslevIndex),varIndex) &
3854 *fdeStddev(obsoper%modlevindexTop(obslevIndex): &
3855 obsoper%modlevindexBot(obslevIndex),1))*fdeStddev(modlevIndex,1)
3856 end do
3857 !$OMP END PARALLEL DO
3858
3859 zhbh=sum(obsoper%zh(obslevIndex,obsoper%modlevindexTop(obslevIndex): &
3860 obsoper%modlevindexBot(obslevIndex)) &
3861 *work(obsoper%modlevindexTop(obslevIndex): &
3862 obsoper%modlevindexBot(obslevIndex)))
3863
3864 ! Set proportionality factor 'a'
3865
3866 za=sqrt(zhbh/zwbw)*oopc_genOperOmAStatsFactor(varIndex)
3867
3868 ! Set final innovation operator
3869
3870 obsoper%zh(obslevIndex,1:obsoper%nmodlev)= &
3871 obsoper%zhp(obslevIndex,1:obsoper%nmodlev)*za
3872
3873 end do
3874
3875 end subroutine oopc_genOper
3876
3877 !--------------------------------------------------------------------------
3878 ! oopc_getColBoundary
3879 !--------------------------------------------------------------------------
3880 function oopc_getColBoundary(iconstituentId,nmodlev,pressmod,tt,height,hu_opt, &
3881 uu_opt,vv_opt) result(boundPress)
3882 !
3883 !:Purpose: To determine and to store the boundary (e.g. tropopause or PBL)
3884 ! pressure levels if needed by the observation operators.
3885 implicit none
3886
3887 ! Arguments:
3888 integer, intent(in) :: iconstituentId ! BUFR code element of Table 08046 identifying the constituent
3889 integer, intent(in) :: nmodlev ! number of model levels for variables other than uu and vv
3890 real(8), intent(in) :: pressmod(nmodlev)! model pressure array
3891 real(8), intent(in) :: tt(nmodlev) ! model temperature (Kelvin)
3892 real(8), intent(in) :: height(nmodlev) ! height (meters)
3893 real(8), optional, intent(in) :: hu_opt(nmodlev) ! specific humidity
3894 real(8), optional, intent(in) :: uu_opt(:) ! model zonal wind component(m/s)
3895 real(8), optional, intent(in) :: vv_opt(:) ! model meridional wind component (m/s)
3896 ! Result:
3897 real(8) :: boundPress ! pressure level of boundary to be imposed
3898
3899 ! Locals:
3900 integer :: tropo_bound
3901
3902 boundPress = -1.0d0
3903
3904 if (oopc_tropo_mode(iconstituentId) == 0) return
3905
3906 tropo_bound = oopc_tropo_bound(iconstituentId)
3907
3908 if (tropo_bound > 0) then
3909 if (.not.all(tt < 0.) .and. .not.all(height < 0.) ) then
3910
3911 select case(tropo_bound)
3912 case(1)
3913
3914 ! Get tropopause pressure level
3915
3916 boundPress = phf_get_tropopause(nmodlev,pressmod,tt,height,hu_opt=hu_opt)
3917
3918 case(2)
3919
3920 ! Get PBL pressure level
3921
3922 boundPress = phf_get_pbl(nmodlev,pressmod,tt,height,hu_opt=hu_opt, &
3923 uu_opt=uu_opt,vv_opt=vv_opt)
3924
3925 case default
3926 call utl_abort("oopc_getColBoundary: Unrecognized value for tropo_bound of " &
3927 // trim(utl_str(tropo_bound)) )
3928 end select
3929
3930 end if
3931 end if
3932
3933 ! Use tropo_column_top value if tropo_bound=0 or model derived boundary was unsuccessful
3934 if (boundPress < 0.0d0) boundPress = oopc_tropo_column_top(iconstituentId)
3935
3936 end function oopc_getColBoundary
3937
3938 !--------------------------------------------------------------------------
3939 ! oopc_addColBoundary
3940 !--------------------------------------------------------------------------
3941 subroutine oopc_addColBoundary(headerIndex,boundPress)
3942 !
3943 !:Purpose: To add column boundary data to oopc_columnBoundary which can be
3944 ! retrieved later using a header index.
3945 !
3946 implicit none
3947
3948 ! Arguments:
3949 integer, intent(in) :: headerIndex ! Header index
3950 real(8), intent(in) :: boundPress ! Pressure boundary
3951
3952 if (.not.associated(oopc_columnBoundary%data1d)) then
3953 call oss_obsdata_alloc(oopc_columnBoundary,oopc_obsdata_maxsize, dim1=1)
3954 oopc_columnBoundary%nrep = 0
3955 end if
3956
3957 ! In this case nrep will count the number of filled reps in the data arrays
3958 oopc_columnBoundary%nrep = oopc_columnBoundary%nrep+1
3959
3960 if (oopc_columnBoundary%nrep > oopc_obsdata_maxsize) then
3961 call utl_abort('oopc_addColBoundary: Reach max size of array ' // &
3962 trim(utl_str(oopc_obsdata_maxsize)) )
3963 end if
3964
3965 ! Use the header number as the unique code for this obs data
3966 write(oopc_columnBoundary%code(oopc_columnBoundary%nrep),'(I22)') headerIndex
3967
3968 oopc_columnBoundary%data1d(1,oopc_columnBoundary%nrep) = boundPress
3969
3970 end subroutine oopc_addColBoundary
3971
3972 !--------------------------------------------------------------------------
3973 ! oopc_retrieveColBoundary
3974 !--------------------------------------------------------------------------
3975 function oopc_retrieveColBoundary(headerIndex) result(boundPress)
3976 !
3977 !:Purpose: To retrieve previously saved column boundary data in
3978 ! oopc_columnBoundary from the header index.
3979 !
3980 implicit none
3981
3982 ! Arguments:
3983 integer, intent(in) :: headerIndex
3984 ! Result:
3985 real(8) :: boundPress
3986
3987 ! Locals:
3988 character(len=22) :: code
3989
3990 write(code,'(I22)') headerIndex
3991
3992 boundPress = oss_obsdata_get_element(oopc_columnBoundary,code,1)
3993
3994 end function oopc_retrieveColBoundary
3995
3996 !==========================================================================
3997 !-------- Stand-alone routines to read and extract climatology fields -----
3998
3999 ! Could be placed in a category 4 module.
4000 ! public :: oopc_readFields, oopc_addToProfileSet, oopc_getProfile
4001
4002 !--------------------------------------------------------------------------
4003 ! oopc_readFields
4004 !--------------------------------------------------------------------------
4005 subroutine oopc_readFields(climatFields,filename,variable, &
4006 maxNumFields,maxNumTypes, &
4007 fieldRequired,success,filetype_opt)
4008 !
4009 !:Purpose: To read climatrology (reference) fields as directed by input
4010 !
4011 ! Comments:
4012 ! - Fields are provided in RPN/fst files
4013 ! - Reference fields can be in a separate RPN file with name provided
4014 ! within 'filename' if filetype='TXT' or provided as 'filename' if it
4015 ! refers to an RPN standaard file.
4016 ! - Fields assumed to be of the same units as those of the
4017 ! corresponding input trial fields
4018 !
4019 implicit none
4020
4021 ! Arguments:
4022 type(struct_oopc_field), intent(out) :: climatFields(0:maxNumFields,maxNumTypes)
4023 character(len=*), intent(in) :: filename
4024 integer, intent(in) :: maxNumFields,maxNumTypes
4025 logical, intent(out) :: success
4026 character(len=*), intent(in) :: variable
4027 character(len=*), intent(in) :: fieldRequired(0:maxNumFields)
4028 character(len=*), optional, intent(in) :: filetype_opt
4029
4030 ! Locals:
4031 character(len=3) :: filetype
4032 character(len=256) :: fname
4033 character(len=4) :: varName
4034 character(len=12) :: etiket
4035 integer :: varIndex,id,nd,j,numvar,ijour,imonth,iday,itime,latIndex
4036 real(8) :: day
4037 integer :: datestamp
4038 integer, external :: newdate
4039 integer, external :: fnom, fclos
4040 integer :: ierr, nulun, ios
4041 logical :: fileExists
4042 logical :: timeInterp
4043 integer :: ni, nj, nkeys, kind
4044 real(8), allocatable :: array1(:,:,:),array2(:,:,:),lvls(:),xlat(:),xlong(:)
4045 real(8), allocatable :: pressclim(:),ozoneclim(:,:)
4046 character (len=128) :: ligne
4047
4048 ! Initialize dimensions to zero
4049
4050 climatFields(:,:)%nlon=0
4051 climatFields(:,:)%nlat=0
4052 climatFields(:,:)%nlev=1
4053
4054 if ( trim(variable) == 'CH' ) then
4055 if ( all(fieldRequired(:) == 'Trial') ) then
4056 ! Not needed
4057 success=.true.
4058 return
4059 end if
4060 end if
4061
4062 inquire(file=trim(filename),exist=fileExists)
4063 if ( .not.fileExists ) then
4064 write(*,*) '----------------------------------------------------'
4065 write(*,*) 'oopc_readFields: COULD NOT FIND file ' // trim(filename)
4066 write(*,*) '----------------------------------------------------'
4067 success = .false.
4068 return
4069 else
4070 success = .true.
4071 end if
4072
4073 ! Check for file names containing climatological fields or input directives
4074
4075 if ( present(filetype_opt) ) then
4076 filetype = trim(fileType_opt)
4077 else
4078 filetype = 'RPN'
4079 end if
4080
4081 nulun=0
4082 ierr=0
4083 if ( filetype == 'TXT' ) then
4084 ierr=fnom(nulun,trim(filename),'SEQ',0)
4085 if ( ierr == 0 ) then
4086 open(unit=nulun, file=trim(filename), status='OLD')
4087 ios=0
4088
4089 if ( trim(variable) == 'CH' ) then
4090
4091 ! CH variable kind (for constituent fields)
4092
4093 read(nulun,'(A)',iostat=ios,err=10,end=10) ligne
4094 do while (trim(adjustl(ligne(1:14))) /= 'SECTION IV:')
4095 read(nulun,'(A)',iostat=ios,err=10,end=11) ligne
4096 end do
4097
4098 ! Read number of constituents with associated input file(s)
4099
4100 read(nulun,*,iostat=ios,err=10,end=10) numvar
4101 if (numvar <= 0) go to 10
4102 else
4103 numvar=1
4104 nd=1
4105 call utl_abort('oopc_readFields: Variable kind or name ' // &
4106 trim(variable) // ' not taken into account')
4107 end if
4108 end if
4109 else if ( filetype == 'RPN' ) then
4110 numvar=1
4111 nd=1
4112 else if ( filetype /= 'RPN' ) then
4113 call utl_abort('oopc_readFields: File type ' // trim(filetype) // &
4114 ' not recognized')
4115 end if
4116
4117 if ( ierr /= 0 ) then
4118 call utl_abort('oopc_readFields: COULD NOT OPEN file ' // trim(filename))
4119 end if
4120
4121 ! Initialization
4122
4123 timeInterp = .true.
4124 datestamp=tim_getDateStamp()
4125 ierr = newdate(datestamp,ijour,itime,-3)
4126 if ( ierr < 0 ) then
4127 call utl_abort('oopc_readFields: Invalid datestamp ' // &
4128 trim(utl_str(datestamp)) )
4129 end if
4130 imonth = MOD(ijour/100,100)
4131 iday = MOD(ijour,100)
4132 day=iday+itime*1.0D-8
4133 if (day > 15.) then
4134 day=day-15.0
4135 else
4136 day=day+15.0
4137 end if
4138
4139 ! Get needed fields for each file/varIndex
4140
4141 do varIndex=1,numvar
4142
4143 if ( trim(variable) == 'CH' ) then
4144
4145 ! Read id,nd
4146 ! id: constituent identifier code; (0 for ozone, ...)
4147 ! nd: number of sets; 1 or 2 (nd=2 required when different profile
4148 ! sets need to be merged according to the tropopause height
4149 ! when the first set referring to strato files and teh second
4150 ! to tropo fields)
4151
4152 read(nulun,*,iostat=ios,err=10,end=10)
4153 read(nulun,*,iostat=ios,err=10,end=10) id,nd
4154 varName=vnl_varnameFromVarnum(0,id)
4155
4156 read(nulun,*,iostat=ios,err=10,end=10) fname
4157 inquire(file=trim(fname),exist=fileExists)
4158 if ( .not. fileExists ) then
4159 call utl_abort('oopc_readFields: Did not find file ' // trim(fname))
4160 end if
4161 else
4162 id=varIndex
4163 ! Currently assumes nunmar = 1 and fname = filename. Could be extended
4164 fname = filename
4165 varname = trim(variable)
4166 end if
4167
4168 do j=1,nd
4169
4170 if ( trim(fname) == 'ozoneclim98' ) then
4171 timeInterp = .false.
4172 call ozo_read_Climatology(datestamp,nlat_opt=nj,nlev_opt=nkeys, &
4173 press_opt=pressclim,ozone_opt=ozoneclim)
4174 id=0
4175 ni=1
4176 allocate(array1(1,nj,nkeys),lvls(nkeys),xlat(nj),xlong(1))
4177 ! Convert from ppmv to microgram/kg
4178 array1(1,1:nj,1:nkeys) = ozoneclim(1:nj,1:nkeys) * &
4179 MPC_MOLAR_MASS_O3_R8 / (1.0d-3 * MPC_MOLAR_MASS_DRY_AIR_R8)
4180 lvls(1:nkeys) = pressclim(1:nkeys)
4181 deallocate(ozoneclim,pressclim)
4182 kind = 2
4183 xlong(1)=0.0d0
4184 do latIndex = 1, nj
4185 xlat(latIndex) = (latIndex-1)*180.0d0/(nj-1) - 90.0d0
4186 end do
4187 etiket = ' '
4188 else
4189 if ( nd == 2 ) then
4190 read(nulun,*,iostat=ios,err=10,end=10) etiket
4191 else
4192 etiket = ' '
4193 end if
4194 call utl_readFstField(trim(fname),varName,-1,imonth,-1,etiket, &
4195 ni,nj,nkeys,array1,xlat_opt=xlat,xlong_opt=xlong, &
4196 lvls_opt=lvls,kind_opt=kind)
4197 end if
4198
4199 climatFields(id,j)%nlon=ni
4200 climatFields(id,j)%nlat=nj
4201 climatFields(id,j)%nlev=nkeys
4202 climatFields(id,j)%ivkind=kind
4203
4204 allocate(climatFields(id,j)%field(ni,nj,nkeys))
4205 allocate(climatFields(id,j)%vlev(nkeys),climatFields(id,j)%lon(ni))
4206 allocate(climatFields(id,j)%lat(nj))
4207
4208 climatFields(id,j)%lat(1:nj)=xlat(1:nj)*MPC_RADIANS_PER_DEGREE_R8
4209 climatFields(id,j)%lon(1:ni)=xlong(1:ni)*MPC_RADIANS_PER_DEGREE_R8
4210 where (climatFields(id,j)%lon(1:ni) < 0.0)
4211 climatFields(id,j)%lon(1:ni)=2.0*MPC_PI_R8 + climatFields(id,j)%lon(1:ni)
4212 end where
4213 climatFields(id,j)%vlev(1:nkeys)=lvls(1:nkeys)
4214
4215 if (.not.timeInterp) then
4216
4217 climatFields(id,j)%field(:,:,:) = array1(:,:,:)
4218
4219 else
4220
4221 ! Following for interpolation as a function of days from mid-months.
4222
4223 if (iday > 15) then
4224 if (imonth == 12) then
4225 call utl_readFstField(trim(fname),varName,-1,1,-1,etiket, &
4226 ni,nj,nkeys,array2,lvls_opt=lvls,kind_opt=kind)
4227 else
4228 call utl_readFstField(trim(fname),varName,-1,imonth+1,-1, &
4229 etiket,ni,nj,nkeys,array2,lvls_opt=lvls,kind_opt=kind)
4230 end if
4231
4232 ! Linearly interpolate in time
4233 ! (approximately - assumes 30 day months)
4234
4235 climatFields(id,j)%field(:,:,:) = (array1(:,:,:)*(30.0-day)+array2(:,:,:)*day)/30.0
4236
4237 else if (iday <= 15) then
4238 if (imonth == 1) then
4239 call utl_readFstField(trim(fname),varName,-1,12,-1,etiket, &
4240 ni,nj,nkeys,array2,lvls_opt=lvls,kind_opt=kind)
4241 else
4242 call utl_readFstField(trim(fname),varName,-1,imonth-1,-1, &
4243 etiket,ni,nj,nkeys,array2,lvls_opt=lvls,kind_opt=kind)
4244 end if
4245
4246 ! Linearly interpolate in time
4247 ! (approximately - assumes 30 day months)
4248
4249 climatFields(id,j)%field(:,:,:) = (array2(:,:,:)* &
4250 (30.0-day)+array1(:,:,:)*day)/30.0
4251 end if
4252 end if
4253
4254 if (allocated(array1)) deallocate(array1,lvls,xlat,xlong)
4255 if (allocated(array2)) deallocate(array2)
4256
4257 end do
4258 end do
4259
4260 10 if (ios > 0) then
4261 call utl_abort('oopc_readFields: READING PROBLEM.' // &
4262 ' File read error message number: ' // trim(utl_str(ios)))
4263 end if
4264 close(unit=nulun)
4265 ierr = fclos(nulun)
4266 if ( any(fieldRequired(:) == 'Diff') .and. trim(variable) == 'CH' ) then
4267 do j=0,maxNumFields
4268 if ( climatFields(j,1)%nlon == 0 .and. trim(fieldRequired(j)) == 'Diff' ) then
4269 call utl_abort('oopc_readFields: READING PROBLEM. Did not' // &
4270 ' find SECTION IV required for constituent ID ' // &
4271 trim(utl_str(j)))
4272 end if
4273 end do
4274 end if
4275
4276 return
4277
4278 11 close(unit=nulun)
4279 ierr = fclos(nulun)
4280 if ( any(fieldRequired(:) == 'Diff') .and. trim(variable) == 'CH' ) then
4281 call utl_abort('oopc_readFields: READING PROBLEM. Did not find ' // &
4282 'SECTION IV.')
4283 end if
4284
4285 end subroutine oopc_readFields
4286
4287 !--------------------------------------------------------------------------
4288 ! oopc_addToProfileSet
4289 !--------------------------------------------------------------------------
4290 subroutine oopc_addToProfileSet(climatFields,climatProfileSet,maxNumFields,maxNumTypes, &
4291 numModelLevs,modelPressLevs,modelHeightLevs,obsLat, &
4292 obsLong,obsIndex,maxsize,varKind_opt,varNumber_opt,tt_opt,hu_opt)
4293
4294 !:Purpose: To determine and to store a profile at obs location as part of a cumulative
4295 ! profile set for a specific variable
4296 !
4297 !:Input:
4298 !
4299 ! :climatFields: Input fields from which interpolations are done
4300 ! :climatProfileSet: Input profile set
4301 ! :maxNumFields: Size of first dimension for climatFields
4302 ! :maxNumTypes: Size of second dimension for climatFields
4303 ! :numModelLevs: Number of model levels
4304 ! :modelPressLevs Model pressure array (Pa)
4305 ! :modelHeightLevs: Model height (m)
4306 ! :obsLat: Latitude (rad)
4307 ! :obsLong: Longitude (rad)
4308 ! :obsIndex: Unique measurement identifier
4309 ! :varKind_opt: variable kind (currently only relevant for 'CH')
4310 ! :varNumber_opt: Constituent id
4311 ! :tt_opt: Model temperature (Kelvin)
4312 ! :hu_opt: Specific humidity
4313 ! :maxsize: Max number of obs for which climatProfileSet will be used
4314 !
4315 !:Output:
4316 !
4317 ! :climatProfileSet: Updated profile set (with one profile added for (obs_long,obs_lat))
4318 !
4319 implicit none
4320
4321 ! Arguments:
4322 type(struct_oopc_field), intent(in) :: climatFields(0:maxNumFields,maxNumTypes)
4323 type(struct_oss_obsdata), intent(inout) :: climatProfileSet
4324 integer, intent(in) :: maxNumFields
4325 integer, intent(in) :: maxNumTypes
4326 integer, intent(in) :: obsIndex
4327 integer, intent(in) :: numModelLevs
4328 integer, intent(in) :: maxsize
4329 real(8), intent(in) :: modelPressLevs(numModelLevs)
4330 real(8), intent(in) :: modelHeightLevs(numModelLevs)
4331 real(8), intent(in) :: obsLat
4332 real(8), intent(in) :: obsLong
4333 integer, optional, intent(in) :: varNumber_opt
4334 real(8), optional, intent(in) :: tt_opt(:)
4335 real(8), optional, intent(in) :: hu_opt(:)
4336 character(len=*), optional, intent(in) :: varKind_opt
4337
4338 ! Locals
4339 integer :: level,start,id
4340 real(8) :: tropo_press, refprof(numModelLevs),refprof2(numModelLevs),dt
4341 real(8), allocatable :: pressrefin(:)
4342 logical, allocatable :: success(:)
4343
4344 if ( present(varNumber_opt) ) then
4345 if ( varNumber_opt < 0 ) return
4346 id = varNumber_opt
4347 else
4348 id = 0
4349 end if
4350
4351 if ( present(varKind_opt) ) then
4352 ! Not currently used
4353 end if
4354
4355 if (climatFields(id,1)%nlat == 0) return
4356
4357 ! Set vertical levels of reference.
4358 ! Convert to pressure coordinate if needed.
4359
4360 if (allocated(pressrefin)) deallocate(pressrefin)
4361 allocate(pressrefin(climatFields(id,1)%nlev))
4362 pressrefin(:) = climatFields(id,1)%vlev(1:climatFields(id,1)%nlev)
4363
4364 if (allocated(success)) deallocate(success)
4365 allocate(success(climatFields(id,1)%nlev))
4366 success(:)=.true.
4367
4368 if (climatFields(id,1)%ivkind == 2) then
4369 pressrefin(:)=pressrefin(:)*100. ! Conversion from hPa to Pa.
4370 else if (climatFields(id,1)%ivkind == 0) then
4371 where (pressrefin < modelHeightLevs(numModelLevs))
4372 pressrefin=modelHeightLevs(numModelLevs)
4373 end where
4374 pressrefin(:) = phf_convert_z_to_pressure(pressrefin,modelHeightLevs,modelPressLevs, &
4375 climatFields(id,1)%nlev,numModelLevs,obsLat,success)
4376 else if (climatFields(id,1)%ivkind == 4) then
4377 pressrefin(:)=pressrefin(:) + modelHeightLevs(numModelLevs)
4378 pressrefin(:) = phf_convert_z_to_pressure(pressrefin,modelHeightLevs,modelPressLevs, &
4379 climatFields(id,1)%nlev,numModelLevs,obsLat,success)
4380 else if (climatFields(id,1)%ivkind == 1) then
4381 pressrefin(:)=pressrefin(:)*modelPressLevs(numModelLevs) ! Convert from sigma to Pa
4382 else
4383 call utl_abort('oopc_addToProfileSet: Cannot handle vertical coordinate of kind ' // trim(utl_str(climatFields(id,1)%ivkind)))
4384 end if
4385
4386 ! Interpolate to obs lat/long (or lat) location and model level
4387
4388 call oopc_column_hbilin(climatFields(id,1)%field,pressrefin, &
4389 climatFields(id,1)%nlon,climatFields(id,1)%nlat,climatFields(id,1)%nlev, &
4390 climatFields(id,1)%lon,climatFields(id,1)%lat,obsLong,obsLat, &
4391 refprof,modelPressLevs,numModelLevs)
4392
4393 if (climatFields(id,2)%nlat > 0 .and. climatFields(id,2)%nlon > 0 &
4394 .and. climatFields(id,2)%nlev > 0) then
4395
4396 if ( .not. present(tt_opt) ) then
4397 call utl_abort('oopc_addToProfileSet: Missing TT for determining ' // &
4398 'tropopause pressure')
4399 end if
4400 if ( any(tt_opt <= 0.0d0) ) then
4401 call utl_abort('oopc_addToProfileSet: Invalid TT for determining ' // &
4402 'tropopause pressure')
4403 end if
4404
4405 ! Get second reference field (for troposphere)
4406
4407 tropo_press=-1.0
4408
4409 if ( present(hu_opt) ) then
4410 if (all(hu_opt >= 0.0D0)) then
4411 tropo_press=phf_get_tropopause(numModelLevs,modelPressLevs, &
4412 tt_opt,modelHeightLevs,hu_opt=hu_opt)
4413 else
4414 tropo_press=phf_get_tropopause(numModelLevs,modelPressLevs, &
4415 tt_opt,modelHeightLevs)
4416 end if
4417 else
4418 tropo_press=phf_get_tropopause(numModelLevs,modelPressLevs,tt_opt,modelHeightLevs)
4419 end if
4420
4421 if (tropo_press > 0) then
4422
4423 ! Set vertical levels of reference.
4424 ! Convert to pressure coordinate if needed
4425
4426 if (allocated(pressrefin)) deallocate(pressrefin)
4427 allocate(pressrefin(climatFields(id,2)%nlev))
4428 pressrefin(:)= climatFields(id,2)%vlev(1:climatFields(id,2)%nlev)
4429
4430 if (allocated(success)) deallocate(success)
4431 allocate(success(climatFields(id,2)%nlev))
4432 success(:)=.true.
4433
4434 if (climatFields(id,2)%ivkind == 2) then
4435 pressrefin(:)=pressrefin(:)*100. ! Conversion from hPa to Pa.
4436 else if (climatFields(id,2)%ivkind == 0) then
4437 where (pressrefin < modelHeightLevs(numModelLevs))
4438 pressrefin=modelHeightLevs(numModelLevs)
4439 end where
4440 pressrefin(:) = phf_convert_z_to_pressure(pressrefin, &
4441 modelHeightLevs,modelPressLevs, &
4442 climatFields(id,2)%nlev,numModelLevs, &
4443 obsLat,success)
4444 else if (climatFields(id,2)%ivkind == 4) then
4445 pressrefin(:)=pressrefin(:) + modelHeightLevs(numModelLevs)
4446 pressrefin(:) = phf_convert_z_to_pressure(pressrefin, &
4447 modelHeightLevs,modelPressLevs, &
4448 climatFields(id,2)%nlev,numModelLevs,obsLat, &
4449 success)
4450 else if (climatFields(id,2)%ivkind == 1) then
4451 pressrefin(:)=pressrefin(:)*modelPressLevs(numModelLevs) ! Convert from sigma to Pa
4452 else
4453 call utl_abort('oopc_addToProfileSet: Cannot handle vertical ' // &
4454 'coordinate of kind ' // trim(utl_str(climatFields(id,2)%ivkind)))
4455 end if
4456
4457 ! Interpolate to obs lat/long (or lat) and model levels
4458
4459 call oopc_column_hbilin(climatFields(id,2)%field,pressrefin, &
4460 climatFields(id,2)%nlon,climatFields(id,2)%nlat,climatFields(id,2)%nlev, &
4461 climatFields(id,2)%lon,climatFields(id,2)%lat,obsLong,obsLat, &
4462 refprof2,modelPressLevs,numModelLevs)
4463
4464 end if
4465
4466 ! Combine with upper level profile
4467
4468 do level=numModelLevs,3,-1
4469 if (modelPressLevs(level) < tropo_press) exit
4470 refprof(level)=refprof2(level)
4471 end do
4472 start=level
4473
4474 ! Apply linear combination of four levels just above the tropopause
4475
4476 do level=start,max(2,start-3),-1
4477 dt=(start+1.0-level)/5.0
4478 refprof(level)=dt*refprof2(level) + (1.0-dt)*refprof(level)
4479 end do
4480
4481 end if
4482
4483 if (allocated(pressrefin)) deallocate(pressrefin)
4484 if (allocated(success)) deallocate(success)
4485
4486 ! ------- Save in climatProfileSet ---------
4487
4488 if (.not.associated(climatProfileSet%data1d)) then
4489 call oss_obsdata_alloc(climatProfileSet, maxsize, dim1=numModelLevs)
4490 climatProfileSet%nrep = 0
4491 end if
4492
4493 ! Here, nrep will count the number of filled elements in the data arrays
4494 climatProfileSet%nrep = climatProfileSet%nrep+1
4495
4496 if (climatProfileSet%nrep > maxsize) then
4497 call utl_abort('oopc_addToProfilesSet: Reach max size of array ' // &
4498 trim(utl_str(maxsize)) )
4499 end if
4500
4501 ! obsIndex serves as the unique locator code
4502 write(climatProfileSet%code(climatProfileSet%nrep),'(I22)') obsIndex
4503
4504 ! Save profile in climatProfileSet
4505
4506 climatProfileSet%data1d(:,climatProfileSet%nrep) = refprof(:)
4507
4508 end subroutine oopc_addToProfileSet
4509
4510 !--------------------------------------------------------------------------
4511 ! oopc_getProfile
4512 !--------------------------------------------------------------------------
4513 function oopc_getProfile(climatProfileSet,code) result(profile)
4514 !
4515 !:Purpose: To extract and provide profile from climatProfileSet according to
4516 ! code value.
4517 !
4518 implicit none
4519
4520 ! Arguments
4521 type(struct_oss_obsdata), intent(inout) :: climatProfileSet ! Profile set
4522 character(len=*), intent(in) :: code ! unique obs identifying code
4523 ! Result:
4524 real(8) :: profile(climatProfileSet%dim1) ! retrieved array from obsdata%data1d of dimension obsdata%dim1
4525
4526 ! Locals:
4527 integer :: status ! search success (0 = found; 1 = no data; 2 = not found)
4528
4529 profile = oss_obsdata_get_array1d(climatProfileSet,code,status)
4530 if (status > 0) then
4531 call utl_abort("oopc_getProfile: Code not found - " // trim(code))
4532 end if
4533
4534 end function oopc_getProfile
4535
4536 !--------------------------------------------------------------------------
4537 ! oopc_column_hbilin
4538 !--------------------------------------------------------------------------
4539 subroutine oopc_column_hbilin(field,vlev,nlong,nlat,nlev,xlong,xlat, &
4540 plong,plat,vprof,vlevout,nlevout)
4541 !
4542 !:Purpose: Horizontal bilinear interpolation from a 3D field to a profile at (plong,plat).
4543 ! Assumes vertical interpolation not needed or already done.
4544 !
4545 ! This version can be used with fields that are not part of the background state,
4546 ! such as climatologies.
4547 !
4548 ! This version does not depend in column_data and gridstatevector modules.
4549 !
4550 implicit none
4551
4552 ! Arguments:
4553 integer, intent(in) :: nlong ! number or longitudes
4554 integer, intent(in) :: nlat ! number or latitudes
4555 integer, intent(in) :: nlev ! number of vertical levels
4556 integer, intent(in) :: nlevout ! number of target vertical levels
4557 real(8), intent(in) :: field(nlong,nlat,nlev) ! 3D field
4558 real(8), intent(in) :: vlev(nlev) ! vertical levels of input field (in pressure)
4559 real(8), intent(in) :: xlong(nlong) ! longitudes (radians)
4560 real(8), intent(in) :: xlat(nlat) ! latitudes (radians)
4561 real(8), intent(in) :: plong ! target longitude (radians)
4562 real(8), intent(in) :: plat ! target latitude (radian)
4563 real(8), intent(in) :: vlevout(nlevout) ! target vertical levels (in pressure)
4564 real(8), intent(out) :: vprof(nlevout) ! profile at (plong,plat)
4565
4566 ! Locals:
4567 real(8) :: lnvlev(nlev),lnvlevout(nlevout),plong2
4568 integer :: ilev,lonIndex,latIndex,i,j
4569 real(8) :: DLDX, DLDY, DLDP, DLW1, DLW2, DLW3, DLW4
4570
4571 call utl_tmg_start(30,'--StateToColumn')
4572
4573 ! Find near lat/long grid points
4574
4575 if ( nlong > 1 ) then
4576 plong2 = plong
4577 if (plong2 < 0.0) plong2 = 2.D0*MPC_PI_R8 + plong2
4578 do lonIndex = 2, nlong
4579 if (xlong(lonIndex-1) < xlong(lonIndex)) then
4580 if (plong2 >= xlong(lonIndex-1) .and. plong2 <= xlong(lonIndex)) exit
4581 else
4582 ! Assumes this is a transition between 360 to 0 (if it exists). Skip over.
4583 end if
4584 end do
4585 lonIndex = lonIndex-1
4586 else
4587 lonIndex=0
4588 end if
4589
4590 do latIndex = 2, nlat
4591 if (plat <= xlat(latIndex)) exit
4592 end do
4593 latIndex = latIndex-1
4594
4595 if ( lonIndex == 0 ) then
4596
4597 ! Set lat interpolation weights
4598
4599 DLDY = (plat - xlat(latIndex))/(xlat(latIndex+1)-xlat(latIndex))
4600
4601 DLW1 = (1.d0-DLDY)
4602 DLW2 = DLDY
4603
4604 ! Set vertical interpolation weights (assumes pressure vertical coordinate)
4605
4606 lnvlevout(:) = log(vlevout(:))
4607 lnvlev(:) = log(vlev(:))
4608
4609 ilev = 1
4610 do i = 1, nlevout
4611 do j = ilev, nlev
4612 if (lnvlevout(i) < lnvlev(j)) exit ! assumes lnvlevout and lnvlev increase with index
4613 end do
4614 ilev = j-1
4615 if (ilev < 1) then
4616 ilev = 1
4617 else if (ilev >= nlev) then
4618 ilev = nlev-1
4619 end if
4620
4621 DLDP = (lnvlev(ilev+1)-lnvlevout(i))/(lnvlev(ilev+1)-lnvlev(ilev))
4622
4623 vprof(i) = DLDP* (DLW1 * field(lonIndex,latIndex,ilev) &
4624 + DLW2 * field(lonIndex,latIndex+1,ilev)) &
4625 + (1.d0-DLDP)* (DLW1 * field(lonIndex,latIndex,ilev+1) &
4626 + DLW2 * field(lonIndex,latIndex+1,ilev+1))
4627 end do
4628
4629 else
4630
4631 ! Set lat/long interpolation weights
4632
4633 DLDX = (plong - xlong(lonIndex))/(xlong(lonIndex+1)-xlong(lonIndex))
4634 DLDY = (plat - xlat(latIndex))/(xlat(latIndex+1)-xlat(latIndex))
4635
4636 DLW1 = (1.d0-DLDX) * (1.d0-DLDY)
4637 DLW2 = DLDX * (1.d0-DLDY)
4638 DLW3 = (1.d0-DLDX) * DLDY
4639 DLW4 = DLDX * DLDY
4640
4641 ! Set vertical interpolation weights (assumes pressure vertical coordinate)
4642
4643 lnvlevout(:) = log(vlevout(:))
4644 lnvlev(:) = log(vlev(:))
4645
4646 ilev = 1
4647 do i = 1, nlevout
4648 do j = ilev, nlev
4649 if (lnvlevout(i) < lnvlev(j)) exit ! assumes lnvlevout and lnvlev increase with index
4650 end do
4651 ilev = j-1
4652 if (ilev < 1) then
4653 ilev = 1
4654 else if (ilev >= nlev) then
4655 ilev = nlev-1
4656 end if
4657
4658 DLDP = (lnvlev(ilev+1)-lnvlevout(i))/(lnvlev(ilev+1)-lnvlev(ilev))
4659
4660 vprof(i) = DLDP* (DLW1 * field(lonIndex,latIndex,ilev) &
4661 + DLW2 * field(lonIndex+1,latIndex,ilev) &
4662 + DLW3 * field(lonIndex,latIndex+1,ilev) &
4663 + DLW4 * field(lonIndex+1,latIndex+1,ilev)) &
4664 + (1.d0-DLDP)* (DLW1 * field(lonIndex,latIndex,ilev+1) &
4665 + DLW2 * field(lonIndex+1,latIndex,ilev+1) &
4666 + DLW3 * field(lonIndex,latIndex+1,ilev+1) &
4667 + DLW4 * field(lonIndex+1,latIndex+1,ilev+1))
4668 end do
4669 end if
4670
4671 call utl_tmg_stop(30)
4672
4673 end subroutine oopc_column_hbilin
4674
4675end module obsOperatorsChem_mod