source: palm/trunk/SOURCE/data_output_mask.f90 @ 1585

Last change on this file since 1585 was 1585, checked in by maronga, 6 years ago

Added support for RRTMG radiation code

  • Property svn:keywords set to Id
File size: 22.5 KB
Line 
1 SUBROUTINE data_output_mask( av )
2
3!--------------------------------------------------------------------------------!
4! This file is part of PALM.
5!
6! PALM is free software: you can redistribute it and/or modify it under the terms
7! of the GNU General Public License as published by the Free Software Foundation,
8! either version 3 of the License, or (at your option) any later version.
9!
10! PALM is distributed in the hope that it will be useful, but WITHOUT ANY
11! WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12! A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13!
14! You should have received a copy of the GNU General Public License along with
15! PALM. If not, see <http://www.gnu.org/licenses/>.
16!
17! Copyright 1997-2014 Leibniz Universitaet Hannover
18!--------------------------------------------------------------------------------!
19!
20! Current revisions:
21! -----------------
22! Added support for RRTMG
23!
24! Former revisions:
25! -----------------
26! $Id: data_output_mask.f90 1585 2015-04-30 07:05:52Z maronga $
27!
28! 1438 2014-07-22 14:14:06Z heinze
29! +nr, qc, qr
30!
31! 1359 2014-04-11 17:15:14Z hoffmann
32! New particle structure integrated.
33!
34! 1353 2014-04-08 15:21:23Z heinze
35! REAL constants provided with KIND-attribute
36!
37! 1327 2014-03-21 11:00:16Z raasch
38!
39!
40! 1320 2014-03-20 08:40:49Z raasch
41! ONLY-attribute added to USE-statements,
42! kind-parameters added to all INTEGER and REAL declaration statements,
43! kinds are defined in new module kinds,
44! revision history before 2012 removed,
45! comment fields (!:) to be used for variable explanations added to
46! all variable declaration statements
47!
48! 1318 2014-03-17 13:35:16Z raasch
49! barrier argument removed from cpu_log,
50! module interfaces removed
51!
52! 1092 2013-02-02 11:24:22Z raasch
53! unused variables removed
54!
55! 1036 2012-10-22 13:43:42Z raasch
56! code put under GPL (PALM 3.9)
57!
58! 1031 2012-10-19 14:35:30Z raasch
59! netCDF4 without parallel file support implemented
60!
61! 1007 2012-09-19 14:30:36Z franke
62! Bugfix: calculation of pr must depend on the particle weighting factor,
63! missing calculation of ql_vp added
64!
65! 410 2009-12-04 17:05:40Z letzel
66! Initial version
67!
68! Description:
69! ------------
70! Masked data output in netCDF format for current mask (current value of mid).
71!------------------------------------------------------------------------------!
72
73#if defined( __netcdf )
74    USE arrays_3d,                                                             &
75        ONLY:  e, nr, p, pt, q, qc, ql, ql_c, ql_v, qr, rho, sa, tend, u,      &
76               v, vpt, w
77   
78    USE averaging,                                                             &
79        ONLY:  e_av, lpt_av, nr_av, p_av, pc_av, pr_av, pt_av, q_av, qc_av,    &
80               ql_av, ql_c_av, ql_v_av, ql_vp_av, qv_av, qr_av, rho_av, s_av,  &
81               sa_av, u_av, v_av, vpt_av, w_av
82   
83    USE cloud_parameters,                                                      &
84        ONLY:  l_d_cp, pt_d_t
85   
86    USE control_parameters,                                                    &
87        ONLY:  cloud_physics, domask, domask_no, domask_time_count,            &
88               mask_i, mask_j, mask_k, mask_size, mask_size_l,                 &
89               mask_start_l, max_masks, message_string, mid,                   &
90               netcdf_data_format, nz_do3d, simulated_time
91    USE cpulog,                                                                &
92        ONLY:  cpu_log, log_point
93
94
95   
96    USE indices,                                                               &
97        ONLY:  nbgp, nxl, nxr, nyn, nys, nzb, nzt
98       
99    USE kinds
100   
101    USE netcdf
102   
103    USE netcdf_control
104   
105    USE particle_attributes,                                                   &
106        ONLY:  grid_particles, number_of_particles, particles,                 &
107               particle_advection_start, prt_count
108   
109    USE pegrid
110
111    USE radiation_model_mod,                                                   &
112        ONLY:  rad_lw_in, rad_lw_in_av, rad_lw_out, rad_lw_out_av,             &
113               rad_sw_in, rad_sw_in_av, rad_sw_out, rad_sw_out_av
114
115    IMPLICIT NONE
116
117    INTEGER(iwp) ::  av       !:
118    INTEGER(iwp) ::  ngp      !:
119    INTEGER(iwp) ::  i        !:
120    INTEGER(iwp) ::  if       !:
121    INTEGER(iwp) ::  j        !:
122    INTEGER(iwp) ::  k        !:
123    INTEGER(iwp) ::  n        !:
124    INTEGER(iwp) ::  psi      !:
125    INTEGER(iwp) ::  sender   !:
126    INTEGER(iwp) ::  ind(6)   !:
127   
128    LOGICAL ::  found         !:
129    LOGICAL ::  resorted      !:
130   
131    REAL(wp) ::  mean_r       !:
132    REAL(wp) ::  s_r2         !:
133    REAL(wp) ::  s_r3         !:
134   
135    REAL(wp), DIMENSION(:,:,:), ALLOCATABLE ::  local_pf    !:
136#if defined( __parallel )
137    REAL(wp), DIMENSION(:,:,:), ALLOCATABLE ::  total_pf    !:
138#endif
139    REAL(wp), DIMENSION(:,:,:), POINTER ::  to_be_resorted  !:
140
141!
142!-- Return, if nothing to output
143    IF ( domask_no(mid,av) == 0 )  RETURN
144
145    CALL cpu_log (log_point(49),'data_output_mask','start')
146
147!
148!-- Open output file.
149    IF ( myid == 0  .OR.  netcdf_data_format > 4 )  THEN
150       CALL check_open( 200+mid+av*max_masks )
151    ENDIF
152
153!
154!-- Allocate total and local output arrays.
155#if defined( __parallel )
156    IF ( myid == 0 )  THEN
157       ALLOCATE( total_pf(mask_size(mid,1),mask_size(mid,2),mask_size(mid,3)) )
158    ENDIF
159#endif
160    ALLOCATE( local_pf(mask_size_l(mid,1),mask_size_l(mid,2), &
161                       mask_size_l(mid,3)) )
162
163!
164!-- Update the netCDF time axis.
165    domask_time_count(mid,av) = domask_time_count(mid,av) + 1
166    IF ( myid == 0  .OR.  netcdf_data_format > 4 )  THEN
167       nc_stat = NF90_PUT_VAR( id_set_mask(mid,av), id_var_time_mask(mid,av), &
168                               (/ simulated_time /),                          &
169                               start = (/ domask_time_count(mid,av) /),       &
170                               count = (/ 1 /) )
171       CALL handle_netcdf_error( 'data_output_mask', 460 )
172    ENDIF
173
174!
175!-- Loop over all variables to be written.
176    if = 1
177
178    DO  WHILE ( domask(mid,av,if)(1:1) /= ' ' )
179!
180!--    Reallocate local_pf on PE 0 since its shape changes during MPI exchange
181       IF ( netcdf_data_format < 5   .AND.  myid == 0  .AND.  if > 1 )  THEN
182          DEALLOCATE( local_pf )
183          ALLOCATE( local_pf(mask_size_l(mid,1),mask_size_l(mid,2), &
184                             mask_size_l(mid,3)) )
185       ENDIF
186!
187!--    Store the variable chosen.
188       resorted = .FALSE.
189       SELECT CASE ( TRIM( domask(mid,av,if) ) )
190
191          CASE ( 'e' )
192             IF ( av == 0 )  THEN
193                to_be_resorted => e
194             ELSE
195                to_be_resorted => e_av
196             ENDIF
197
198          CASE ( 'lpt' )
199             IF ( av == 0 )  THEN
200                to_be_resorted => pt
201             ELSE
202                to_be_resorted => lpt_av
203             ENDIF
204
205          CASE ( 'nr' )
206             IF ( av == 0 )  THEN
207                to_be_resorted => nr
208             ELSE
209                to_be_resorted => nr_av
210             ENDIF
211
212          CASE ( 'p' )
213             IF ( av == 0 )  THEN
214                to_be_resorted => p
215             ELSE
216                to_be_resorted => p_av
217             ENDIF
218
219          CASE ( 'pc' )  ! particle concentration (requires ghostpoint exchange)
220             IF ( av == 0 )  THEN
221                tend = prt_count
222                CALL exchange_horiz( tend, nbgp )
223                DO  i = 1, mask_size_l(mid,1)
224                   DO  j = 1, mask_size_l(mid,2)
225                      DO  k = 1, mask_size_l(mid,3)
226                         local_pf(i,j,k) =  tend(mask_k(mid,k), &
227                                   mask_j(mid,j),mask_i(mid,i))
228                      ENDDO
229                   ENDDO
230                ENDDO
231                resorted = .TRUE.
232             ELSE
233                CALL exchange_horiz( pc_av, nbgp )
234                to_be_resorted => pc_av
235             ENDIF
236
237          CASE ( 'pr' )  ! mean particle radius (effective radius)
238             IF ( av == 0 )  THEN
239                IF ( simulated_time >= particle_advection_start )  THEN
240                   DO  i = nxl, nxr
241                      DO  j = nys, nyn
242                         DO  k = nzb, nz_do3d
243                            number_of_particles = prt_count(k,j,i)
244                            IF (number_of_particles <= 0)  CYCLE
245                            particles => grid_particles(k,j,i)%particles(1:number_of_particles)
246                            s_r2 = 0.0_wp
247                            s_r3 = 0.0_wp
248                            DO  n = 1, number_of_particles
249                               IF ( particles(n)%particle_mask )  THEN
250                                  s_r2 = s_r2 + grid_particles(k,j,i)%particles(n)%radius**2 * &
251                                         grid_particles(k,j,i)%particles(n)%weight_factor
252                                  s_r3 = s_r3 + grid_particles(k,j,i)%particles(n)%radius**3 * &
253                                         grid_particles(k,j,i)%particles(n)%weight_factor
254                               ENDIF
255                            ENDDO
256                            IF ( s_r2 > 0.0_wp )  THEN
257                               mean_r = s_r3 / s_r2
258                            ELSE
259                               mean_r = 0.0_wp
260                            ENDIF
261                            tend(k,j,i) = mean_r
262                         ENDDO
263                      ENDDO
264                   ENDDO
265                   CALL exchange_horiz( tend, nbgp )
266                ELSE
267                   tend = 0.0_wp
268                ENDIF
269                DO  i = 1, mask_size_l(mid,1)
270                   DO  j = 1, mask_size_l(mid,2)
271                      DO  k = 1, mask_size_l(mid,3)
272                         local_pf(i,j,k) =  tend(mask_k(mid,k), &
273                                   mask_j(mid,j),mask_i(mid,i))
274                      ENDDO
275                   ENDDO
276                ENDDO
277                resorted = .TRUE.
278             ELSE
279                CALL exchange_horiz( pr_av, nbgp )
280                to_be_resorted => pr_av
281             ENDIF
282
283          CASE ( 'pt' )
284             IF ( av == 0 )  THEN
285                IF ( .NOT. cloud_physics ) THEN
286                   to_be_resorted => pt
287                ELSE
288                   DO  i = 1, mask_size_l(mid,1)
289                      DO  j = 1, mask_size_l(mid,2)
290                         DO  k = 1, mask_size_l(mid,3)
291                            local_pf(i,j,k) =  &
292                                 pt(mask_k(mid,k),mask_j(mid,j),mask_i(mid,i)) &
293                                 + l_d_cp * pt_d_t(mask_k(mid,k)) * &
294                                   ql(mask_k(mid,k),mask_j(mid,j),mask_i(mid,i))
295                         ENDDO
296                      ENDDO
297                   ENDDO
298                   resorted = .TRUE.
299                ENDIF
300             ELSE
301                to_be_resorted => pt_av
302             ENDIF
303
304          CASE ( 'q' )
305             IF ( av == 0 )  THEN
306                to_be_resorted => q
307             ELSE
308                to_be_resorted => q_av
309             ENDIF
310
311          CASE ( 'qc' )
312             IF ( av == 0 )  THEN
313                to_be_resorted => qc
314             ELSE
315                to_be_resorted => qc_av
316             ENDIF
317
318          CASE ( 'ql' )
319             IF ( av == 0 )  THEN
320                to_be_resorted => ql
321             ELSE
322                to_be_resorted => ql_av
323             ENDIF
324
325          CASE ( 'ql_c' )
326             IF ( av == 0 )  THEN
327                to_be_resorted => ql_c
328             ELSE
329                to_be_resorted => ql_c_av
330             ENDIF
331
332          CASE ( 'ql_v' )
333             IF ( av == 0 )  THEN
334                to_be_resorted => ql_v
335             ELSE
336                to_be_resorted => ql_v_av
337             ENDIF
338
339          CASE ( 'ql_vp' )
340             IF ( av == 0 )  THEN
341                IF ( simulated_time >= particle_advection_start )  THEN
342                   DO  i = nxl, nxr
343                      DO  j = nys, nyn
344                         DO  k = nzb, nz_do3d
345                            number_of_particles = prt_count(k,j,i)
346                            IF (number_of_particles <= 0)  CYCLE
347                            particles => grid_particles(k,j,i)%particles(1:number_of_particles)
348                            DO  n = 1, number_of_particles
349                               IF ( particles(n)%particle_mask )  THEN
350                                  tend(k,j,i) = tend(k,j,i) + &
351                                          particles(n)%weight_factor / &
352                                          prt_count(k,j,i)
353                               ENDIF
354                            ENDDO
355                         ENDDO
356                      ENDDO
357                   ENDDO
358                   CALL exchange_horiz( tend, nbgp )
359                ELSE
360                   tend = 0.0_wp
361                ENDIF
362                DO  i = 1, mask_size_l(mid,1)
363                   DO  j = 1, mask_size_l(mid,2)
364                      DO  k = 1, mask_size_l(mid,3)
365                         local_pf(i,j,k) =  tend(mask_k(mid,k), &
366                                   mask_j(mid,j),mask_i(mid,i))
367                      ENDDO
368                   ENDDO
369                ENDDO
370                resorted = .TRUE.
371             ELSE
372                CALL exchange_horiz( ql_vp_av, nbgp )
373                to_be_resorted => ql_vp_av
374             ENDIF
375
376          CASE ( 'qv' )
377             IF ( av == 0 )  THEN
378                DO  i = 1, mask_size_l(mid,1)
379                   DO  j = 1, mask_size_l(mid,2)
380                      DO  k = 1, mask_size_l(mid,3)
381                         local_pf(i,j,k) =  &
382                              q(mask_k(mid,k),mask_j(mid,j),mask_i(mid,i)) -  &
383                              ql(mask_k(mid,k),mask_j(mid,j),mask_i(mid,i))
384                      ENDDO
385                   ENDDO
386                ENDDO
387                resorted = .TRUE.
388             ELSE
389                to_be_resorted => qv_av
390             ENDIF
391
392          CASE ( 'qr' )
393             IF ( av == 0 )  THEN
394                to_be_resorted => qr
395             ELSE
396                to_be_resorted => qr_av
397             ENDIF
398
399          CASE ( 'rad_sw_in' )
400             IF ( av == 0 )  THEN
401                to_be_resorted => rad_sw_in
402             ELSE
403                to_be_resorted => rad_sw_in_av
404             ENDIF
405
406          CASE ( 'rad_sw_out' )
407             IF ( av == 0 )  THEN
408                to_be_resorted => rad_sw_out
409             ELSE
410                to_be_resorted => rad_sw_out_av
411             ENDIF
412
413          CASE ( 'rad_lw_in' )
414             IF ( av == 0 )  THEN
415                to_be_resorted => rad_lw_in
416             ELSE
417                to_be_resorted => rad_lw_in_av
418             ENDIF
419
420          CASE ( 'rad_lw_out' )
421             IF ( av == 0 )  THEN
422                to_be_resorted => rad_lw_out
423             ELSE
424                to_be_resorted => rad_lw_out_av
425             ENDIF
426
427          CASE ( 'rho' )
428             IF ( av == 0 )  THEN
429                to_be_resorted => rho
430             ELSE
431                to_be_resorted => rho_av
432             ENDIF
433
434          CASE ( 's' )
435             IF ( av == 0 )  THEN
436                to_be_resorted => q
437             ELSE
438                to_be_resorted => s_av
439             ENDIF
440
441          CASE ( 'sa' )
442             IF ( av == 0 )  THEN
443                to_be_resorted => sa
444             ELSE
445                to_be_resorted => sa_av
446             ENDIF
447
448          CASE ( 'u' )
449             IF ( av == 0 )  THEN
450                to_be_resorted => u
451             ELSE
452                to_be_resorted => u_av
453             ENDIF
454
455          CASE ( 'v' )
456             IF ( av == 0 )  THEN
457                to_be_resorted => v
458             ELSE
459                to_be_resorted => v_av
460             ENDIF
461
462          CASE ( 'vpt' )
463             IF ( av == 0 )  THEN
464                to_be_resorted => vpt
465             ELSE
466                to_be_resorted => vpt_av
467             ENDIF
468
469          CASE ( 'w' )
470             IF ( av == 0 )  THEN
471                to_be_resorted => w
472             ELSE
473                to_be_resorted => w_av
474             ENDIF
475
476          CASE DEFAULT
477!
478!--          User defined quantity
479             CALL user_data_output_mask(av, domask(mid,av,if), found, local_pf )
480             resorted = .TRUE.
481
482             IF ( .NOT. found )  THEN
483                WRITE ( message_string, * ) 'no output available for: ', &
484                                            TRIM( domask(mid,av,if) )
485                CALL message( 'data_output_mask', 'PA0327', 0, 0, 0, 6, 0 )
486             ENDIF
487
488       END SELECT
489
490!
491!--    Resort the array to be output, if not done above
492       IF ( .NOT. resorted )  THEN
493          DO  i = 1, mask_size_l(mid,1)
494             DO  j = 1, mask_size_l(mid,2)
495                DO  k = 1, mask_size_l(mid,3)
496                   local_pf(i,j,k) =  to_be_resorted(mask_k(mid,k), &
497                                      mask_j(mid,j),mask_i(mid,i))
498                ENDDO
499             ENDDO
500          ENDDO
501       ENDIF
502
503!
504!--    I/O block. I/O methods are implemented
505!--    (1) for parallel execution
506!--     a. with netCDF 4 parallel I/O-enabled library
507!--     b. with netCDF 3 library
508!--    (2) for serial execution.
509!--    The choice of method depends on the correct setting of preprocessor
510!--    directives __parallel and __netcdf4_parallel as well as on the parameter
511!--    netcdf_data_format.
512#if defined( __parallel )
513#if defined( __netcdf4_parallel )
514       IF ( netcdf_data_format > 4 )  THEN
515!
516!--       (1) a. Parallel I/O using netCDF 4 (not yet tested)
517          nc_stat = NF90_PUT_VAR( id_set_mask(mid,av),  &
518               id_var_domask(mid,av,if),  &
519               local_pf,  &
520               start = (/ mask_start_l(mid,1), mask_start_l(mid,2),  &
521                          mask_start_l(mid,3), domask_time_count(mid,av) /),  &
522               count = (/ mask_size_l(mid,1), mask_size_l(mid,2),  &
523                          mask_size_l(mid,3), 1 /) )
524          CALL handle_netcdf_error( 'data_output_mask', 461 )
525       ELSE
526#endif
527!
528!--       (1) b. Conventional I/O only through PE0
529!--       PE0 receives partial arrays from all processors of the respective mask
530!--       and outputs them. Here a barrier has to be set, because otherwise
531!--       "-MPI- FATAL: Remote protocol queue full" may occur.
532          CALL MPI_BARRIER( comm2d, ierr )
533
534          ngp = mask_size_l(mid,1) * mask_size_l(mid,2) * mask_size_l(mid,3)
535          IF ( myid == 0 )  THEN
536!
537!--          Local array can be relocated directly.
538             total_pf( &
539               mask_start_l(mid,1):mask_start_l(mid,1)+mask_size_l(mid,1)-1, &
540               mask_start_l(mid,2):mask_start_l(mid,2)+mask_size_l(mid,2)-1, &
541               mask_start_l(mid,3):mask_start_l(mid,3)+mask_size_l(mid,3)-1 ) &
542               = local_pf
543!
544!--          Receive data from all other PEs.
545             DO  n = 1, numprocs-1
546!
547!--             Receive index limits first, then array.
548!--             Index limits are received in arbitrary order from the PEs.
549                CALL MPI_RECV( ind(1), 6, MPI_INTEGER, MPI_ANY_SOURCE, 0,  &
550                     comm2d, status, ierr )
551!
552!--             Not all PEs have data for the mask
553                IF ( ind(1) /= -9999 )  THEN
554                   ngp = ( ind(2)-ind(1)+1 ) * (ind(4)-ind(3)+1 ) *  &
555                         ( ind(6)-ind(5)+1 )
556                   sender = status(MPI_SOURCE)
557                   DEALLOCATE( local_pf )
558                   ALLOCATE(local_pf(ind(1):ind(2),ind(3):ind(4),ind(5):ind(6)))
559                   CALL MPI_RECV( local_pf(ind(1),ind(3),ind(5)), ngp,  &
560                        MPI_REAL, sender, 1, comm2d, status, ierr )
561                   total_pf(ind(1):ind(2),ind(3):ind(4),ind(5):ind(6)) &
562                        = local_pf
563                ENDIF
564             ENDDO
565
566             nc_stat = NF90_PUT_VAR( id_set_mask(mid,av),  &
567                  id_var_domask(mid,av,if), total_pf, &
568                  start = (/ 1, 1, 1, domask_time_count(mid,av) /), &
569                  count = (/ mask_size(mid,1), mask_size(mid,2), &
570                             mask_size(mid,3), 1 /) )
571             CALL handle_netcdf_error( 'data_output_mask', 462 )
572
573          ELSE
574!
575!--          If at least part of the mask resides on the PE, send the index
576!--          limits for the target array, otherwise send -9999 to PE0.
577             IF ( mask_size_l(mid,1) > 0 .AND.  mask_size_l(mid,2) > 0 .AND. &
578                  mask_size_l(mid,3) > 0  ) &
579                  THEN
580                ind(1) = mask_start_l(mid,1)
581                ind(2) = mask_start_l(mid,1) + mask_size_l(mid,1) - 1
582                ind(3) = mask_start_l(mid,2)
583                ind(4) = mask_start_l(mid,2) + mask_size_l(mid,2) - 1
584                ind(5) = mask_start_l(mid,3)
585                ind(6) = mask_start_l(mid,3) + mask_size_l(mid,3) - 1
586             ELSE
587                ind(1) = -9999; ind(2) = -9999
588                ind(3) = -9999; ind(4) = -9999
589                ind(5) = -9999; ind(6) = -9999
590             ENDIF
591             CALL MPI_SEND( ind(1), 6, MPI_INTEGER, 0, 0, comm2d, ierr )
592!
593!--          If applicable, send data to PE0.
594             IF ( ind(1) /= -9999 )  THEN
595                CALL MPI_SEND( local_pf(1,1,1), ngp, MPI_REAL, 0, 1, comm2d, &
596                     ierr )
597             ENDIF
598          ENDIF
599!
600!--       A barrier has to be set, because otherwise some PEs may proceed too
601!--       fast so that PE0 may receive wrong data on tag 0.
602          CALL MPI_BARRIER( comm2d, ierr )
603#if defined( __netcdf4_parallel )
604       ENDIF
605#endif
606#else
607!
608!--    (2) For serial execution of PALM, the single processor (PE0) holds all
609!--    data and writes them directly to file.
610       nc_stat = NF90_PUT_VAR( id_set_mask(mid,av),  &
611            id_var_domask(mid,av,if),       &
612            local_pf, &
613            start = (/ 1, 1, 1, domask_time_count(mid,av) /), &
614            count = (/ mask_size_l(mid,1), mask_size_l(mid,2), &
615                       mask_size_l(mid,3), 1 /) )
616       CALL handle_netcdf_error( 'data_output_mask', 463 )
617#endif
618
619       if = if + 1
620
621    ENDDO
622
623!
624!-- Deallocate temporary arrays.
625    DEALLOCATE( local_pf )
626#if defined( __parallel )
627    IF ( myid == 0 )  THEN
628       DEALLOCATE( total_pf )
629    ENDIF
630#endif
631
632
633    CALL cpu_log( log_point(49), 'data_output_mask', 'stop' )
634#endif
635
636 END SUBROUTINE data_output_mask
Note: See TracBrowser for help on using the repository browser.