23 use fckit_mpi_module,
only: fckit_mpi_sum,fckit_mpi_min,fckit_mpi_max,fckit_mpi_status
33 real(kind_real),
allocatable :: lonobs(:)
34 real(kind_real),
allocatable :: latobs(:)
35 integer,
allocatable :: obsa_to_obs(:)
78 class(obsop_type),
intent(inout) :: obsop
81 if (
allocated(obsop%lonobs))
deallocate(obsop%lonobs)
82 if (
allocated(obsop%latobs))
deallocate(obsop%latobs)
83 if (
allocated(obsop%obsa_to_obs))
deallocate(obsop%obsa_to_obs)
85 call obsop%com%dealloc
98 class(obsop_type),
intent(inout) :: obsop
99 type(mpl_type),
intent(in) :: mpl
100 type(nam_type),
intent(in) :: nam
104 character(len=1024) :: filename
105 character(len=1024) :: subr =
'obsop_read' 108 write(filename,
'(a,a,i4.4,a,i4.4,a)') trim(nam%prefix),
'_obs_',mpl%nproc,
'-',mpl%myproc,
'.nc' 109 call mpl%ncerr(subr,nf90_open(trim(nam%datadir)//
'/'//trim(filename),nf90_nowrite,ncid))
112 call mpl%ncerr(subr,nf90_get_att(ncid,nf90_global,
'nc0b',obsop%nc0b))
113 call mpl%ncerr(subr,nf90_get_att(ncid,nf90_global,
'nobsa',obsop%nobsa))
117 call obsop%h%read(mpl,ncid)
120 call obsop%com%read(mpl,ncid,
'com')
123 call mpl%ncerr(subr,nf90_close(ncid))
136 class(obsop_type),
intent(inout) :: obsop
137 type(mpl_type),
intent(in) :: mpl
138 type(nam_type),
intent(in) :: nam
142 character(len=1024) :: filename
143 character(len=1024) :: subr =
'obsop_write' 146 write(filename,
'(a,a,i4.4,a,i4.4,a)') trim(nam%prefix),
'_obs_',mpl%nproc,
'-',mpl%myproc,
'.nc' 147 call mpl%ncerr(subr,nf90_create(trim(nam%datadir)//
'/'//trim(filename),or(nf90_clobber,nf90_64bit_offset),ncid))
150 call nam%ncwrite(mpl,ncid)
153 call mpl%ncerr(subr,nf90_put_att(ncid,nf90_global,
'nc0b',obsop%nc0b))
154 call mpl%ncerr(subr,nf90_put_att(ncid,nf90_global,
'nobsa',obsop%nobsa))
157 call mpl%ncerr(subr,nf90_enddef(ncid))
160 call obsop%h%write(mpl,ncid)
163 call obsop%com%write(mpl,ncid)
166 call mpl%ncerr(subr,nf90_close(ncid))
179 class(obsop_type),
intent(inout) :: obsop
180 type(mpl_type),
intent(in) :: mpl
181 type(rng_type),
intent(inout) :: rng
182 type(nam_type),
intent(in) :: nam
183 type(geom_type),
intent(in) :: geom
186 integer :: iobs,jobs,iproc,iobsa,nproc_max
187 integer,
allocatable :: order(:),obs_to_proc(:)
188 real(kind_real),
allocatable :: lonobs(:),latobs(:),list(:)
191 if (nam%nobs<1)
call mpl%abort(
'nobs should be positive for offline observation operator')
194 allocate(lonobs(nam%nobs))
195 allocate(latobs(nam%nobs))
196 allocate(obs_to_proc(nam%nobs))
200 if (abs(maxval(geom%area)-4.0*
pi)<1.0e-1)
then 202 call rng%rand_real(minval(geom%lon),maxval(geom%lon),lonobs)
203 call rng%rand_real(minval(geom%lat),maxval(geom%lat),latobs)
206 call rng%rand_real(-
pi,
pi,lonobs)
207 call rng%rand_real(-1.0_kind_real,1.0_kind_real,latobs)
208 latobs = 0.5*
pi-acos(latobs)
213 call mpl%f_comm%broadcast(lonobs,mpl%ioproc-1)
214 call mpl%f_comm%broadcast(latobs,mpl%ioproc-1)
217 if (
test_no_obs.and.(mpl%nproc==1))
call mpl%abort(
'at least 2 MPI tasks required for test_no_obs')
220 allocate(list(nam%nobs))
221 allocate(order(nam%nobs))
224 call rng%rand_real(0.0_kind_real,1.0_kind_real,list)
225 call qsort(nam%nobs,list,order)
230 nproc_max = mpl%nproc-1
232 nproc_max = mpl%nproc
236 obs_to_proc(jobs) = iproc
238 if (iproc>nproc_max) iproc = 1
243 call mpl%f_comm%broadcast(obs_to_proc,mpl%ioproc-1)
244 obsop%nobsa = count(obs_to_proc==mpl%myproc)
250 allocate(obsop%lonobs(obsop%nobsa))
251 allocate(obsop%latobs(obsop%nobsa))
256 iproc = obs_to_proc(iobs)
257 if (mpl%myproc==iproc)
then 259 obsop%lonobs(iobsa) = lonobs(iobs)
260 obsop%latobs(iobsa) = latobs(iobs)
270 subroutine obsop_from(obsop,nobsa,lonobs,latobs)
275 class(obsop_type),
intent(inout) :: obsop
276 integer,
intent(in) :: nobsa
277 real(kind_real),
intent(in) :: lonobs(nobsa)
278 real(kind_real),
intent(in) :: latobs(nobsa)
287 allocate(obsop%lonobs(obsop%nobsa))
288 allocate(obsop%latobs(obsop%nobsa))
290 if (obsop%nobsa>0)
then 307 class(obsop_type),
intent(inout) :: obsop
308 type(mpl_type),
intent(inout) :: mpl
309 type(rng_type),
intent(inout) :: rng
310 type(nam_type),
intent(in) :: nam
311 type(geom_type),
intent(in) :: geom
314 integer :: offset,iobs,jobs,iobsa,iproc,i_s,ic0,ic0b,i,ic0a,delta,nres,ind(1),lunit
315 integer :: imin(1),imax(1),nmoves,imoves
316 integer,
allocatable :: nop(:),iop(:),srcproc(:,:),srcic0(:,:),order(:),nobs_to_move(:),nobs_to_move_tmp(:),obs_moved(:,:)
317 integer,
allocatable :: obs_to_proc(:),obs_to_obsa(:),proc_to_nobsa(:),c0b_to_c0(:),c0_to_c0b(:),c0a_to_c0b(:)
318 real(kind_real) :: N_max,C_max
319 real(kind_real),
allocatable :: lonobs(:),latobs(:),list(:)
320 logical,
allocatable :: maskobs(:),lcheck_nc0b(:)
321 type(fckit_mpi_status) :: status
322 type(linop_type) :: hfull
325 allocate(proc_to_nobsa(mpl%nproc))
328 call mpl%f_comm%allgather(obsop%nobsa,proc_to_nobsa)
329 obsop%nobs = sum(proc_to_nobsa)
332 write(mpl%info,
'(a7,a)')
'',
'Number of observations per MPI task on input:' 334 write(mpl%info,
'(a10,a,i3,a,i8)')
'',
'Task ',iproc,
': ',proc_to_nobsa(iproc)
336 write(mpl%info,
'(a10,a,i8)')
'',
'Total : ',obsop%nobs
339 allocate(lonobs(obsop%nobs))
340 allocate(latobs(obsop%nobs))
346 if (proc_to_nobsa(iproc)>0)
then 347 if (iproc==mpl%ioproc)
then 349 lonobs(offset+1:offset+proc_to_nobsa(iproc)) = obsop%lonobs
350 latobs(offset+1:offset+proc_to_nobsa(iproc)) = obsop%latobs
353 call mpl%f_comm%receive(lonobs(offset+1:offset+proc_to_nobsa(iproc)),iproc-1,mpl%tag,status)
354 call mpl%f_comm%receive(latobs(offset+1:offset+proc_to_nobsa(iproc)),iproc-1,mpl%tag+1,status)
359 offset = offset+proc_to_nobsa(iproc)
362 if (obsop%nobsa>0)
then 364 call mpl%f_comm%send(obsop%lonobs,mpl%ioproc-1,mpl%tag)
365 call mpl%f_comm%send(obsop%latobs,mpl%ioproc-1,mpl%tag+1)
368 call mpl%update_tag(2)
371 call mpl%f_comm%broadcast(lonobs,mpl%ioproc-1)
372 call mpl%f_comm%broadcast(latobs,mpl%ioproc-1)
375 allocate(maskobs(obsop%nobs))
376 allocate(nop(obsop%nobs))
377 allocate(iop(obsop%nobs))
378 allocate(obs_to_proc(obsop%nobs))
379 allocate(obs_to_obsa(obsop%nobs))
383 write(mpl%info,
'(a7,a)')
'',
'Single level:' 386 call hfull%interp(mpl,geom%mesh,geom%kdtree,geom%nc0,geom%mask_hor_c0,obsop%nobs,lonobs,latobs,maskobs,nam%obsop_interp)
391 iobs = hfull%row(i_s)
392 nop(iobs) = nop(iobs)+1
396 allocate(srcproc(maxval(nop),obsop%nobs))
397 allocate(srcic0(maxval(nop),obsop%nobs))
405 iproc = geom%c0_to_proc(ic0)
406 iobs = hfull%row(i_s)
407 iop(iobs) = iop(iobs)+1
408 srcproc(iop(iobs),iobs) = iproc
409 srcic0(iop(iobs),iobs) = ic0
413 select case (trim(nam%obsdis))
416 write(mpl%info,
'(a7,a)')
'',
'Observations distributed as provided by user' 422 do iobsa=1,proc_to_nobsa(iproc)
424 obs_to_proc(iobs) = iproc
429 write(mpl%info,
'(a7,a)')
'',
'Observations randomly distributed' 434 allocate(list(obsop%nobs))
435 allocate(order(obsop%nobs))
438 call rng%rand_real(0.0_kind_real,1.0_kind_real,list)
439 call qsort(obsop%nobs,list,order)
445 obs_to_proc(jobs) = iproc
447 if (iproc>mpl%nproc) iproc = 1
452 call mpl%f_comm%broadcast(obs_to_proc,mpl%ioproc-1)
453 case (
'local',
'adjusted')
457 if (
isnotmsi(srcproc(2,iobs)).and.(srcproc(2,iobs)==srcproc(3,iobs)))
then 459 obs_to_proc(iobs) = srcproc(2,iobs)
462 obs_to_proc(iobs) = srcproc(1,iobs)
466 if (trim(nam%obsdis)==
'adjusted')
then 468 write(mpl%info,
'(a7,a)')
'',
'Observations distribution adjusted' 472 allocate(nobs_to_move(mpl%nproc))
478 iproc = obs_to_proc(iobs)
481 proc_to_nobsa(iproc) = proc_to_nobsa(iproc)+1
487 delta = obsop%nobs/mpl%nproc
488 if (nres>(mpl%nproc-iproc+1)*delta) delta = delta+1
489 nobs_to_move(iproc) = delta
492 nobs_to_move = int(
real(proc_to_nobsa-nobs_to_move,kind_real))
493 if (sum(nobs_to_move)>0)
then 494 ind = maxloc(nobs_to_move)
495 elseif (sum(nobs_to_move)<0)
then 496 ind = minloc(nobs_to_move)
500 nobs_to_move(ind(1)) = nobs_to_move(ind(1))-sum(nobs_to_move)
503 allocate(nobs_to_move_tmp(mpl%nproc))
504 allocate(obs_moved(maxval(abs(nobs_to_move)),mpl%nproc))
506 nobs_to_move_tmp = nobs_to_move
508 iproc = obs_to_proc(iobs)
509 if (nobs_to_move_tmp(iproc)>0)
then 511 obs_moved(nobs_to_move_tmp(iproc),iproc) = iobs
512 nobs_to_move_tmp(iproc) = nobs_to_move_tmp(iproc)-1
517 do while (any(nobs_to_move<0))
518 imin = minloc(nobs_to_move)
519 imax = maxloc(nobs_to_move)
520 nmoves =
min(nobs_to_move(imax(1)),-nobs_to_move(imin(1)))
522 iobs = obs_moved(nobs_to_move(imax(1)),imax(1))
523 call msi(obs_moved(nobs_to_move(imax(1)),imax(1)))
524 obs_to_proc(iobs) = imin(1)
525 nobs_to_move(imax(1)) = nobs_to_move(imax(1))-1
526 nobs_to_move(imin(1)) = nobs_to_move(imin(1))+1
531 deallocate(nobs_to_move)
534 write(mpl%info,
'(a7,a)')
'',
'Observations distribution based on grid distribution' 538 call mpl%abort(
'wrong obsdis')
542 obsop%nobsa = count(obs_to_proc==mpl%myproc)
543 allocate(obsop%obsa_to_obs(obsop%nobsa))
549 iproc = obs_to_proc(iobs)
552 proc_to_nobsa(iproc) = proc_to_nobsa(iproc)+1
555 iobsa = proc_to_nobsa(iproc)
556 obs_to_obsa(iobs) = iobsa
557 if (iproc==mpl%myproc) obsop%obsa_to_obs(iobsa) = iobs
561 allocate(lcheck_nc0b(geom%nc0))
566 iobs = hfull%row(i_s)
567 iproc = obs_to_proc(iobs)
568 if (iproc==mpl%myproc) obsop%h%n_s = obsop%h%n_s+1
572 lcheck_nc0b = .false.
574 ic0 = geom%c0a_to_c0(ic0a)
575 if (geom%c0_to_proc(ic0)==mpl%myproc) lcheck_nc0b(ic0) = .true.
578 iproc = obs_to_proc(iobs)
579 if (iproc==mpl%myproc)
then 582 lcheck_nc0b(ic0) = .true.
586 obsop%nc0b = count(lcheck_nc0b)
589 allocate(c0b_to_c0(obsop%nc0b))
590 allocate(c0_to_c0b(geom%nc0))
594 if (lcheck_nc0b(ic0))
then 596 c0b_to_c0(ic0b) = ic0
597 c0_to_c0b(ic0) = ic0b
603 obsop%h%n_src = obsop%nc0b
604 obsop%h%n_dst = obsop%nobsa
608 iobs = hfull%row(i_s)
610 iproc = obs_to_proc(iobs)
611 if (iproc==mpl%myproc)
then 612 obsop%h%n_s = obsop%h%n_s+1
613 obsop%h%row(obsop%h%n_s) = obs_to_obsa(iobs)
614 obsop%h%col(obsop%h%n_s) = c0_to_c0b(ic0)
615 obsop%h%S(obsop%h%n_s) = hfull%S(i_s)
620 allocate(c0a_to_c0b(geom%nc0a))
622 ic0 = geom%c0a_to_c0(ic0a)
623 ic0b = c0_to_c0b(ic0)
624 c0a_to_c0b(ic0a) = ic0b
628 call obsop%com%setup(mpl,
'com',geom%nc0,geom%nc0a,obsop%nc0b,c0b_to_c0,c0a_to_c0b,geom%c0_to_proc,geom%c0_to_c0a)
631 call mpl%f_comm%allreduce(
real(obsop%com%nhalo,kind_real),C_max,fckit_mpi_max())
632 c_max = c_max/(3.0*
real(obsop%nobs,kind_real)/
real(mpl%nproc,kind_real))
633 n_max =
real(maxval(proc_to_nobsa),kind_real)/(
real(obsop%nobs,kind_real)/
real(mpl%nproc,kind_real))
636 write(mpl%info,
'(a7,a)')
'',
'Number of observations per MPI task on output:' 638 write(mpl%info,
'(a10,a,i3,a,i8)')
'',
'Task ',iproc,
': ',proc_to_nobsa(iproc)
640 write(mpl%info,
'(a7,a,f5.1,a)')
'',
'Observation repartition imbalance: ',100.0*
real(maxval(proc_to_nobsa) &
& -minval(proc_to_nobsa),kind_real)/(
real(sum(proc_to_nobsa),kind_real)/
real(mpl%nproc,kind_real)),
' %' 641 write(mpl%info,
'(a7,a,i3)')
'',
'Number of grid points, halo size and number of received values for MPI task: ',mpl%myproc
642 write(mpl%info,
'(a10,i8,a,i8,a,i8)')
'',obsop%com%nred,
' / ',obsop%com%next,
' / ',obsop%com%nhalo
643 write(mpl%info,
'(a7,a,f10.2,a,f10.2)')
'',
'Scores (N_max / C_max):',n_max,
' / ',c_max
647 call obsop%write(mpl,nam)
651 if (mpl%nproc>1)
then 652 call mpl%newunit(lunit)
653 open(unit=lunit,file=trim(nam%datadir)//
'/'//trim(nam%prefix)//
'_obs_scores_'//trim(nam%obsdis)//
'.dat',status=
'replace')
654 write(lunit,*) n_max,c_max
670 class(obsop_type),
intent(inout) :: obsop
671 type(mpl_type),
intent(inout) :: mpl
672 type(rng_type),
intent(inout) :: rng
673 type(geom_type),
intent(in) :: geom
676 write(mpl%info,
'(a)')
'-------------------------------------------------------------------' 677 write(mpl%info,
'(a)')
'--- Test observation operator adjoint' 679 call obsop%test_adjoint(mpl,rng,geom)
681 if (
allocated(obsop%obsa_to_obs))
then 683 write(mpl%info,
'(a)')
'-------------------------------------------------------------------' 684 write(mpl%info,
'(a)')
'--- Test observation operator precision' 686 call obsop%test_accuracy(mpl,geom)
700 class(obsop_type),
intent(in) :: obsop
701 type(mpl_type),
intent(in) :: mpl
702 type(geom_type),
intent(in) :: geom
703 real(kind_real),
intent(in) :: fld(geom%nc0a,geom%nl0)
704 real(kind_real),
intent(out) :: obs(obsop%nobsa,geom%nl0)
708 real(kind_real) :: fld_ext(obsop%nc0b,geom%nl0)
711 call obsop%com%ext(mpl,geom%nl0,fld,fld_ext)
713 if (obsop%nobsa>0)
then 717 call obsop%h%apply(mpl,fld_ext(:,il0),obs(:,il0))
733 class(obsop_type),
intent(in) :: obsop
734 type(mpl_type),
intent(in) :: mpl
735 type(geom_type),
intent(in) :: geom
736 real(kind_real),
intent(in) :: obs(obsop%nobsa,geom%nl0)
737 real(kind_real),
intent(out) :: fld(geom%nc0a,geom%nl0)
741 real(kind_real) :: fld_ext(obsop%nc0b,geom%nl0)
743 if (obsop%nobsa>0)
then 747 call obsop%h%apply_ad(mpl,obs(:,il0),fld_ext(:,il0))
756 call obsop%com%red(mpl,geom%nl0,fld_ext,fld)
769 class(obsop_type),
intent(inout) :: obsop
770 type(mpl_type),
intent(in) :: mpl
771 type(rng_type),
intent(inout) :: rng
772 type(geom_type),
intent(in) :: geom
775 real(kind_real) :: sum1,sum2_loc,sum2
776 real(kind_real) :: fld(geom%nc0a,geom%nl0),fld_save(geom%nc0a,geom%nl0)
777 real(kind_real),
allocatable :: yobs(:,:),yobs_save(:,:)
780 allocate(yobs(obsop%nobsa,geom%nl0))
781 allocate(yobs_save(obsop%nobsa,geom%nl0))
784 call rng%rand_real(0.0_kind_real,1.0_kind_real,fld_save)
785 if (obsop%nobsa>0)
call rng%rand_real(0.0_kind_real,1.0_kind_real,yobs_save)
788 call obsop%apply(mpl,geom,fld_save,yobs)
789 call obsop%apply_ad(mpl,geom,yobs_save,fld)
792 call mpl%dot_prod(fld,fld_save,sum1)
793 if (obsop%nobsa>0)
then 794 sum2_loc = sum(yobs*yobs_save)
798 call mpl%f_comm%allreduce(sum2_loc,sum2,fckit_mpi_sum())
801 write(mpl%info,
'(a7,a,e15.8,a,e15.8,a,e15.8)')
'',
'Observation operator adjoint test: ', &
802 & sum1,
' / ',sum2,
' / ',2.0*abs(sum1-sum2)/abs(sum1+sum2)
816 class(obsop_type),
intent(inout) :: obsop
817 type(mpl_type),
intent(in) :: mpl
818 type(geom_type),
intent(in) :: geom
821 integer :: ic0a,ic0,iobsa
822 integer :: iprocmax(1),iobsamax(1),iobsmax(1)
823 real(kind_real) :: ylonmax(1),ylatmax(1)
824 real(kind_real) :: norm,distmin,distmax,distsum
825 real(kind_real) :: norm_tot,distmin_tot,proc_to_distmax(mpl%nproc),distsum_tot
826 real(kind_real),
allocatable :: lon(:,:),lat(:,:)
827 real(kind_real),
allocatable :: ylon(:,:),ylat(:,:)
828 real(kind_real),
allocatable :: dist(:)
831 allocate(lon(geom%nc0a,geom%nl0))
832 allocate(lat(geom%nc0a,geom%nl0))
833 allocate(ylon(obsop%nobsa,geom%nl0))
834 allocate(ylat(obsop%nobsa,geom%nl0))
835 allocate(dist(obsop%nobsa))
839 ic0 = geom%c0a_to_c0(ic0a)
840 lon(ic0a,:) = geom%lon(ic0)
841 lat(ic0a,:) = geom%lat(ic0)
845 call obsop%apply(mpl,geom,lon,ylon)
846 call obsop%apply(mpl,geom,lat,ylat)
848 if (obsop%nobsa>0)
then 851 do iobsa=1,obsop%nobsa
852 if ((abs(obsop%lonobs(iobsa))<0.8*
pi).and.(abs(obsop%latobs(iobsa))<0.4*
pi))
then 853 call sphere_dist(ylon(iobsa,1),ylat(iobsa,1),obsop%lonobs(iobsa),obsop%latobs(iobsa),dist(iobsa))
854 dist(iobsa) = dist(iobsa)*
reqkm 857 norm =
real(count(isnotmsr(dist)),kind_real)
859 distmin = minval(dist,mask=
isnotmsr(dist))
860 distmax = maxval(dist,mask=
isnotmsr(dist))
861 distsum = sum(dist,mask=
isnotmsr(dist))
876 call mpl%f_comm%allreduce(norm,norm_tot,fckit_mpi_sum())
877 call mpl%f_comm%allreduce(distmin,distmin_tot,fckit_mpi_min())
878 call mpl%f_comm%allgather(distmax,proc_to_distmax)
879 call mpl%f_comm%allreduce(distsum,distsum_tot,fckit_mpi_sum())
882 iprocmax = maxloc(proc_to_distmax)
883 if (iprocmax(1)==mpl%myproc)
then 884 iobsamax = maxloc(dist,mask=
isnotmsr(dist))
885 iobsmax = obsop%obsa_to_obs(iobsamax)
886 ylonmax = ylon(iobsamax,1)
887 ylatmax = ylat(iobsamax,1)
891 call mpl%f_comm%broadcast(iobsmax,iprocmax(1)-1)
892 call mpl%f_comm%broadcast(ylonmax,iprocmax(1)-1)
893 call mpl%f_comm%broadcast(ylatmax,iprocmax(1)-1)
896 if (norm_tot>0.0)
then 897 write(mpl%info,
'(a7,a,f10.2,a,f10.2,a,f10.2,a)')
'',
'Interpolation error (min/mean/max): ',distmin_tot, &
898 &
' km / ',distsum_tot/norm_tot,
' km / ',maxval(proc_to_distmax),
' km' 899 write(mpl%info,
'(a7,a)')
'',
'Max. interpolation error location (lon/lat): ' 900 write(mpl%info,
'(a10,a14,f10.2,a,f10.2,a)')
'',
'Observation: ',obsop%lonobs(iobsmax(1))*
rad2deg, &
901 &
' deg. / ' ,obsop%latobs(iobsmax(1))*
rad2deg,
' deg.' 902 write(mpl%info,
'(a10,a14,f10.2,a,f10.2,a)')
'',
'Interpolation:',ylonmax*
rad2deg, &
903 &
' deg. / ' ,ylatmax*
rad2deg,
' deg.' 906 call mpl%abort(
'all observations are out of the test windows')
912 subroutine obsop_dealloc(obsop)
subroutine obsop_test_adjoint(obsop, mpl, rng, geom)
subroutine obsop_read(obsop, mpl, nam)
subroutine obsop_write(obsop, mpl, nam)
subroutine obsop_apply(obsop, mpl, geom, fld, obs)
subroutine obsop_test_accuracy(obsop, mpl, geom)
subroutine obsop_run_obsop_tests(obsop, mpl, rng, geom)
subroutine obsop_apply_ad(obsop, mpl, geom, obs, fld)
subroutine obsop_run_obsop(obsop, mpl, rng, nam, geom)
subroutine obsop_generate(obsop, mpl, rng, nam, geom)
logical, parameter test_no_obs
integer, parameter, public kind_real
subroutine obsop_from(obsop, nobsa, lonobs, latobs)
real(fp), parameter, public pi