From 52a3ff3f74d291fa82688a65449ca259d3c4ffff Mon Sep 17 00:00:00 2001 From: Fredrik Jansson Date: Mon, 5 Jun 2023 12:49:42 +0200 Subject: [PATCH] Ruisdael: rebase emission and new land surface model onto v4.4.1. Land surface model by Bart van Stratum Emission model by Marco de Bruine Merged and updated for mixed precision by Fredrik Jansson The land surface model allows reading in a map of land surface properties. The emission model allows reading in a map of scalar emission sources. Squashed commit of the following: commit 44b5a584861593a63d94c2fea79fc4195dd71e67 Author: Fredrik Jansson Date: Mon Jun 5 12:07:44 2023 +0200 rebasing Ruisdael emission and LSM on 4.4.1. Fixes for mixed precision. commit d9f0dba64e9e35080cfe8cdf5502380e50442ee9 Author: Bart van Stratum Date: Wed Sep 8 14:54:23 2021 +0200 Added CO2 scalar for A-Gs net assimilation, for offline budget checks commit 378607e40dc127394584b25510abb131f7b62b32 Author: Bart van Stratum Date: Tue Sep 7 09:43:17 2021 +0200 Added A-Gs option without emission module commit ec0a375c4f562c7b279efb9e3a83d04b4a483047 Author: Bart van Stratum Date: Tue Sep 7 08:08:14 2021 +0200 Added CO2 respiration/assimilation to cross-section and time series. commit 1e3a000292e5067301fee3f578407f0befdcaaa8 Author: Bart van Stratum Date: Mon Sep 6 16:36:23 2021 +0200 Added A-Gs to the new land surface model commit 1e64628ba65679857a82c11c5b8d40fa819bcd6b Author: Bart van Stratum Date: Mon Sep 6 12:05:31 2021 +0200 Added basic infrastructure for A-Gs and communication with emission module commit 170eb9f7c182baf436a8ce31568c0922392d1b44 Author: Marco de Bruine Date: Tue Jun 8 16:56:12 2021 +0200 AG-s <-> emission interface indicators now relative to full list of scalar fields commit 280e180082b7a02a0cfa0e7c331a0ca19092e307 Author: Marco de Bruine Date: Tue Jun 8 15:53:49 2021 +0200 Emission <-> AGS interface commit c1ed132ea2eb221550d37ae7efdb22901a9aef56 Author: Bart van Stratum Date: Fri Nov 6 11:30:33 2020 +0100 Calculate obukhov length from tile averaged fluxes, instead of calculating a tiled mean commit 314b3f30b3d874f363e65f86ab80e927b1324bc5 Author: Bart van Stratum Date: Tue Nov 3 09:12:35 2020 +0100 Bugfix harmonic mean calculation (which is currently not used in the code..) commit b852be87c837978c8f1c9443d331d19bcdcb8153 Author: Fredrik Jansson Date: Tue Oct 27 11:25:58 2020 +0100 update fix for #58 - allow lsmcrossection also for new LSM isurf=11 commit ca74c6d8d42a968e413ace9b567f8468698a2f44 Author: Fredrik Jansson Date: Fri Oct 23 11:03:29 2020 +0200 emission: remove MPI-parallel .nc read, add better namelist error reporting commit b2e1b2c0de9b90d81911e42c188048bb443fc0ce Author: Bart van Stratum Date: Fri Oct 16 10:18:03 2020 +0200 Added NetCDF file with van Genuchten parameters commit 06ad4d1eeb5dfbc368d289496d40cbc7862cff54 Author: julietbravo Date: Mon Sep 7 10:31:11 2020 +0200 Small fix in calculation liq. water fraction, plus added cleanup of arrays commit c7978ffda75a1e23802ebe611485bb3c7819ff67 Author: julietbravo Date: Fri Sep 4 15:02:36 2020 +0200 Fixed dynamic tile fraction for cases with open water commit a687c221a3cfcebabf14dc34f00a1b4fc31d7ae7 Author: julietbravo Date: Fri Sep 4 11:33:18 2020 +0200 Added water tile to time series statistics commit 3c7651d3846266735c7b404a35e054dd7e4cf97f Author: julietbravo Date: Thu Jul 30 15:43:29 2020 +0200 Added additional variables to surface cross-sections commit e5f807d10c8d2d2c8fdd47dbc5bc4848a25fe3ef Author: julietbravo Date: Tue Jul 21 14:12:40 2020 +0200 Modified modlsmcrosssection for new LSM commit cfb67ebdff01dcb1e9c5caee228685c7a435290e Author: julietbravo Date: Tue Jul 21 12:45:14 2020 +0200 Store/load the water tiles for restarts (results = bitwise identical now) commit b66d74a81ca0df84cd2fe00689271f18be9df952 Author: julietbravo Date: Tue Jul 21 09:19:12 2020 +0200 Fixed some MPI issues in new LSM commit 600cf7c8baef59aad9a68d7df7c9358225c8f893 Author: julietbravo Date: Mon Jul 20 11:55:18 2020 +0200 First ~working version with water tile (still has some issues...) commit 2b150cf1da4cac62c8f075afb4b4b77f399d5c26 Author: julietbravo Date: Tue Jul 7 17:28:34 2020 +0200 Added option to read the spatial data for heterogeneous land surfaces commit 5ab96410f234f02dbf62eb70f66b06a7dc27a8a9 Author: julietbravo Date: Tue Jul 7 11:53:21 2020 +0200 Added restart functionality to new LSM commit fe7ee50813fb863a9d15d7dc9abc6faf1b01629f Author: julietbravo Date: Tue Jul 7 11:00:32 2020 +0200 Added old soil grid definition, different soil interpolation types, plus fixed bug in buoyancy calculation commit 30f7d88376e5e6d2b027088919a981fc372fe893 Author: julietbravo Date: Mon Jul 6 16:29:59 2020 +0200 Added calculation of liquid water reservoir (plus bugfix ra calculation) commit ccd9203704aa102ef7b6cdb04d8c861dbbf9bbca Author: julietbravo Date: Mon Jul 6 12:21:21 2020 +0200 Added root water extraction to soil water budget commit a08b2a55d642ccc1b6517a5409accfafbd0421bc Author: julietbravo Date: Mon Jul 6 11:23:16 2020 +0200 Added bulk (grid point mean) quantities needed for SGS model commit e2bb8edf036e01f2911fae5b01e13b1323a0effd Author: julietbravo Date: Mon Jul 6 09:01:42 2020 +0200 Added calculation of bulk (grid point mean) scalar BCs commit 8091785397a9d84b67fc4199c4a54c4910c8f53c Author: julietbravo Date: Fri Jul 3 18:02:56 2020 +0200 Working surface solver, with fluxes per tile commit fc4f9c63dd90ca39137ced1a2421a0b9b3153956 Author: julietbravo Date: Thu Jul 2 16:32:56 2020 +0200 Bugfix calculation canopy resistances commit 58583106e102f5d984885481b35a70d129ccf933 Author: julietbravo Date: Thu Jul 2 16:30:10 2020 +0200 Added calculation canopy and soil resistance commit 7bee52c774053dabf6f991489d11c56ff46d32b5 Author: julietbravo Date: Thu Jul 2 12:13:05 2020 +0200 Working tiled surface layer solver commit 1d4ecc26b81c63d685bb7b41aff48479d5921fc3 Author: julietbravo Date: Wed Jul 1 16:25:10 2020 +0200 Start of tiled surface layer solver commit 9b97bb6986df8415812ee41f6e69434a65834eb2 Author: julietbravo Date: Wed Jul 1 10:00:08 2020 +0200 Added calculation hydraulic properties (van Genuchten) and diffusion solver for soil moisture commit 87aafb11966a0dce14a0829f07a73ef8ba7b0ad3 Author: julietbravo Date: Tue Jun 30 16:51:10 2020 +0200 Added calculation of soil thermal properties, and (explicit) temperature diffusion solver commit b41d465c1d4fb7e6ff0bd509cd2d89662270d868 Author: julietbravo Date: Tue Jun 30 14:39:31 2020 +0200 Added calculation derived van Genuchten parameters commit 7f7c4efb71aec947a721ce51259af7a1fc30bc23 Author: julietbravo Date: Tue Jun 30 11:38:47 2020 +0200 Added minimal set of time series statistics for the new LSM commit 495aa432ab2d2ea09f4f288bdb74260464fb3cef Author: julietbravo Date: Tue Jun 30 11:08:45 2020 +0200 Modified modlsmstat to work with the new LSM commit 2b9ea85438bb2e5245c8e626e4adad58783df660 Author: julietbravo Date: Tue Jun 30 09:52:26 2020 +0200 Minor changes new LSM commit 441e90c58aba0fef028af6293fe11f33996fa334 Author: julietbravo Date: Mon Jun 29 15:45:35 2020 +0200 Added soil grid and initialisation of soil commit 8704449727833cc7b71d4db9123697f5f3dfd718 Author: julietbravo Date: Mon Jun 29 13:52:11 2020 +0200 Added homogeneous initialisation of LSM commit 9aadb79fac33d87f048eeb33e67a04093bfc81d3 Author: julietbravo Date: Mon Jun 29 12:23:47 2020 +0200 Added data structures for land-surface tiles commit ad8a3647b1720baff5a8b9d67a043ad6899306db Author: julietbravo Date: Mon Jun 15 12:06:48 2020 +0200 Start at implemention realistic LSM commit 5aaefd41648409269def3823622773fdd650481a Author: Fredrik Jansson Date: Thu Oct 15 14:43:57 2020 +0200 modnudgebounday: use random_number() instead of rand() for better intel/gnu compatibility commit 93d62083e5ec0859a125f671cf95d0ef70e20714 Author: Marco de Bruine Date: Thu Oct 15 13:43:10 2020 +0200 Renaming of emission related variables commit bea90adf8bb8a1ab3ad71d4651e4dab716791a90 Author: Marco de Bruine Date: Tue Sep 15 15:13:54 2020 +0200 Improved bookkeeping of scalar field numbers and names commit 76d377c27d744a5448d083f9fd8d3c53c3e7f60e Author: Marco de Bruine Date: Tue Sep 1 15:05:35 2020 +0200 Removed temporary forcing at lateral boundaries commit 1a19cd569de6426996c94e24a1f7a863769a6db5 Author: Marco de Bruine Date: Tue Sep 1 15:02:30 2020 +0200 Added boundary nudging of scalar fields commit 9a2bedbf99a65b300640a55b45da5004f5d19b8f Author: Marco de Bruine Date: Fri Jul 3 14:13:06 2020 +0200 3D emission and namelist options; emission switch (l_emission) and vertical upper limit to apply emission (kemis) commit 74bdfad13342e19f26532dbf056ee14ccfb36342 Author: Marco de Bruine Date: Mon Jun 29 16:49:25 2020 +0200 Typo in modemisdata.f90 commit 19909689ffc946398ab5f769168a46ce562ab11b Author: Marco de Bruine Date: Mon Jun 29 16:13:57 2020 +0200 Corrected emission scalar list to work with microphysics, i.e. skip first 2 scalar fields commit 8733870f68ed4bf2bd4175b6433995952799e87b Author: Marco de Bruine Date: Mon Jun 29 16:12:01 2020 +0200 Fixed incorrect NORTH/EAST boundary indices commit 9df88ce817f20efb04fdfa02b6d041b0bbd754b7 Author: Marco de Bruine Date: Thu Jun 25 11:27:58 2020 +0200 Add file moddatetime for reading emission files. commit 080a1359a6454d1ad0e7ade6206d2532927aa025 Author: Marco de Bruine Date: Fri Jun 5 15:17:20 2020 +0200 Added scaling for model resolution and domain dboundary 'nullifier' commit 71481ef261a13975e8a1a32ed67fa4da64751a3e Author: Marco de Bruine Date: Thu Feb 27 15:03:53 2020 +0100 Realistic surface emissions (WIP) commit d487543970fa5ef12881b7ac64e071470592d48d Author: Marco de Bruine Date: Mon Dec 9 15:37:52 2019 +0100 New emission routines: Reading (parallel netCDF) of 2D surface emission on native grid commit 578358547ba4a4b0eabaef9b7918bdc0bf7da6bf Author: Fredrik Jansson Date: Tue Apr 14 15:43:41 2020 +0200 boundary nudging: add check for tile size being sufficient to contain the nudging domain commit 66dc2c312e8ef1cec016ac4d6308cb209ce29d95 Author: Fredrik Jansson Date: Fri Apr 10 14:56:32 2020 +0200 Print warning when unspecified iadv_sv values are set to iadv_mom commit cb8e7a74b2910edad777181c5f2885b0501f5a38 Author: Fredrik Jansson Date: Tue Apr 7 13:11:48 2020 +0200 Add boundary nudging by BvS --- data/van_genuchten_parameters.nc | Bin 0 -> 16564 bytes src/daleslib.f90 | 6 + src/modboundary.f90 | 1 + src/moddatetime.f90 | 247 ++++ src/modemisdata.f90 | 64 + src/modemission.f90 | 268 ++++ src/modfields.f90 | 55 +- src/modgenstat.f90 | 3 + src/modglobal.f90 | 9 + src/modlsm.f90 | 2212 ++++++++++++++++++++++++++++++ src/modlsmcrosssection.f90 | 197 ++- src/modlsmstat.f90 | 101 +- src/modmpi.f90 | 2 +- src/modmpiinterface.f90 | 8 + src/modnudgeboundary.f90 | 480 +++++++ src/modstartup.f90 | 92 +- src/modstat_nc.f90 | 11 + src/modsurface.f90 | 49 +- src/modsurfdata.f90 | 6 +- src/modtimestat.f90 | 335 ++++- src/program.f90 | 15 +- 21 files changed, 3991 insertions(+), 170 deletions(-) create mode 100644 data/van_genuchten_parameters.nc create mode 100644 src/moddatetime.f90 create mode 100644 src/modemisdata.f90 create mode 100644 src/modemission.f90 create mode 100644 src/modlsm.f90 create mode 100644 src/modnudgeboundary.f90 diff --git a/data/van_genuchten_parameters.nc b/data/van_genuchten_parameters.nc new file mode 100644 index 0000000000000000000000000000000000000000..bd450be8ffde121cdf47482d6de926d71eca1de9 GIT binary patch literal 16564 zcmeHO3tZI2_McrAMMK0y!$-9#z6IaCkl!rGQxOmpv8%2dtgr?P?JlC$KP&a7X{Dx_ zsf1q5G&LVROsmD@ntv-Fh0oORt*NPqpi*+@%$!-)#l`OZ`2YL=-^=*H^E-3q`8{W5 z=gebTba-T=hAkTs2wq-9LwxzC{I3ebCpr-$c_|+yKitsx=U zj|24uO-9;KO;ix$%Uf}@w~#jFaqPPu@S+Lc{oz|he25qOr;qEGV9CxgT5KkxmBlw@ ze^8~&IM!&fnzGFyJ-eEXw$!xIA-#hh59-x5BW-k_kRCxjx(D_2W-U^&e-H5TaxB?n zjHx!gDUB7v0+i>){$zQHxh89x!I(+ZASGnN+^v0iR~rpcsr(^Er4ng71H*!(9^O!h zFDnenBSeRN9i-tb@xv-KJRPLLDqi+_5K?%k3R;Sk37E23Yo;|O@IhpximEB}4ccCh z_pM)SXi^g6p-%ua7DK{g21djs#l*+yllq0mMiB4zAX5=fo+~bNU<6CdbAcLUE|WRU zIF5KTO@Zosh&M~EkK-Ws4mV|S0HL>{ds-;Mkr%$b9|M_eTdvt;v$9xd8`L0c2|WH< zyG9iP;dk$bupeY=!SILPRrk<~c>04xtyZfbmIt8(UXO<7yOTsBPq=0YDL|D-!uyN3Y2Ay-mnwmDsIHe?yGcK~Ap_9tOX$~I+cLGGQv42DHFX88C! zX+Ab4X)wc%WdR%>Vt)XTv}IY$Z4?n4%F;Cs59k!%FM5EdFVqM)gHYP9cTd7>Nk!Z@ z6=oq>Hg767E|?2Eh~JY;$U>w{utf0xTmSoP8aV!6sjIEYKT6mwgR%CRf!kjbE#;UK zq9eT@Aib+Rm@c?czKL@rpZHi)s%Hq%uKXxzrC^>D_ z95~L?R#q=?8yp?ZMlQ4b#w>qY$D_>jAvAO#a`4+^n~0PDPaEE5el<9k+lu%xDE?=9HNA7@ zI^F_LXaW4>vKDj~1y#4;hs(pZ5E9idK9=LoO{}`FSgjnqXp)2sz~L8&38aRHW?LB= z+OQsv3qg=O`4d9W&7B;FIX0KiUXT>ak7v^z50b*{20_XZXdKo7g|CHJ8rSlrUgV_a zf`6FQPZXA4Lyr21b|**u#JH2Ae)_wUqkaaslcRoOQSRD5Iqn9!k;`$%CRQF?^UHA; z??x`iU4k3A9Cw3Ij;m%e+GOU7oKOKiW@qcj>Kip0H z2siN~-NftN#A7tsEfp&x>S!GTbqLfUP=`Pr0(A(~Ay9|F{|f~8dKMR{u$Fb^Va?0e z!zhO}GGA{a4{LC~3`8E*-h5q+Jgl{y<=|5|^WZ}`^WZZ$^WY;m^WYOW^Wft-^Wf7t z^Weid^Wd{_e+%0OKAJNRKAAHg?ZU^n@Ze)Prw5D?#?{; zTFyN9R?a;5QqDa1PR=~|O3pm^zRo=Oy3RcKw$AMXU)Gt2Jq2eT_7I$T*fVhEVUNI> zhdlvj9`*p7d7TR{eS8+}*n?vmT0F?zLFxZ_V!ZUvq<@F17X4&tczujYKhGQUHA3ox zv@ud&q)m`Mh_oqEKcvl&Hb?ppQh%f^kmA`5!5vz{H#?c^VU$0D^iiZ*q>mwOgA}(1 zNdWTgkhVwK0cl61oshmap9lJa-0x5x13&*)d~S?jMEpX5`DCdlo!gOq=SKmwfW=YWhJ$L3H0CU(>++;Q2FeY^JaD|FF}fGexxhjKJ1Q zD~o9vsR+GgE2gUxlAqf1%Vauu%e21vm2>H$oZklj`07mB_3VQSc9k!pJxzta%coDK ztE1XIo*loAF6m$L>B@kev@&AT4xiKw^wHh{o1SaCmU`_C$$b9&78<^1^2DR7x6zB= zeYIfKirutX*s{E)!TV_Uq_!)(b{5dF_LL(ROE%N0nATxyZxz!0>u1hedu|84^j?ee z%~W5~rJ5rzo9pkQakutcJ_^`J4|v6`dujXus#^KY3Dfe!^sSOukBHb~bo`_V9pby5 zqSmd!DX+}mK}qa{N4)ZO(LkTp%RI}9>1a!zZ+n#Pr6-N*Q(1F9rvs+j6Y8mdqPv6N zNt@KFn2w#g<>rXR`{_3q1`ppl{{W3m8}sxVnFr~XN9J6ceWrw}3)-b6{&AG9ZQXOu zXK(GJ&wr5EY}<|lbi|?)%f7BWKm%r-?J#Qp30mk?zHLyOlQeAe#AO$KPtl$W=Y(7e zveUTA(gF6F+o&NtK{uq;4q9q?I(@S5F8XYrp&RC2T}n06FOHn_>Tat2dDfzkgd=pt zypINYt=~`MmJh$N*oP%_ebB&BTjoqMFPfrYv`(T#RCu>f(sHWSM{9EILl^$OyMbp<5{H^!L$~*e9 ziBrGWrUYEm4aDa?UF4?ahAc zov>NiJ7(7UmWi{K(dvC~dYybtnbToUlc=wAm4)_s-L!KTD{F%4=UqO!RIyiR@*4K~ zLfM_2GVomaIOWM>(4^I;%KXTzqBjFJC|?|lP1+N=UAg$w(iN}f?N)wEPk(LA%^#Jo z4o}&rUa}6$o23lz5@>Al{7l6%wfN1VZJU(E3+k7hS~XE=@~pY&m74{MzJ)QW?8VoV z!r!!KD>p7wO5Q&;b-~h?l@8|Tif-87QK|+uctZWfZbjIkk51^XfsPpiQ}C}8tfJ%a zM!H@+c`1i3L)VFa{Eqp5N4uz^!j5D;Dr|2hrn^qXaPiZLbT_aaSE*>ILWZy8^O?feP*e;V6=*-@Urqg)=V$e*hkw3c&Iwj72l9PW?jVQqrPe!|C$4#nYA~(y+qLM9E&J|V3cx3IhG*W)t*Twmuy&Mr_%#ey^}?C=Xp=EB zO`B@SF{GMo1^}a2W~ni8 zq~)gCOk>$x0kcQ76`a-d53Qwnp?9kj9BWx;vcas4GMaN!+0C2`ZH~dhZo1iw7H#H@ znQxyRQ+jVK;xxL_cb{3hKPHUkep&=S4|cJLxasp&)&e;j#&@;|4=1Rzm_0>2*{BcLu8zF(~bHgo;S8^Pa9B`>hgR-{`ukELWiPcZqjmOl~L*?)*sd^F`8 zVRIJCw)M&b*U2dM*@{>jMSspd>cam41i1d$#E(I!&0#e{&G4(ngU}>1D-lL9(u!SJ z#~=(rx-Mufa#f19;UyCb3*%=i5GAb4Vc769*)Us0@d!tGw46PA}ivW z#HU1Tkpv4{cu3?$GT8m@Y|D851O-mCr1kW(7!5XK8oTUm%g~M*YcT86VW|nr8NJQ$ zY^G6f%hsDygL1}?(wfZLQSizpCZb!G$(lM!KDU6!FFE=GeTHACBR=#a!t-fW!#PiGq4R;m#nEkUWyD>jy6 z`nLu%i~*;h!}XJl3Rb;+I5IjsjK~*SAb9-y<)>5{Ic_}ps|UO#G3x0N+XA*Ctqv&AOb3l z9G|L8iGoTa2dT>3GDu?bpGINScs~ht1rI`O?58j>A~GT|BCcPAUOW(L&a@0CqiF`4 z!D_VK6(Beb1udkRvW#YUh=)0(X51A}}PH+F|6rd;{h}Cnff~p_o;oQS6QNW`pF@^z8Jv=Niq;gPU40mFG za)^Z)EH*8hdbCM4wuh3Nr47_R%c6oU~3;AEF3kxsg_w8DI+&<{J_wp7lsj`b+YIO4KSN2+* z!CkhCv)}J99U-Gm?QZk)jeYhlI~GTLcD2$TK0WhH<%)C2s|x>%_8GY;MpgJrJRCwd zF8pNs*stGz@1eq<20rVX@4%y4?k)K`tqj=m6JJde6-&Onk&i-?Y8%hA6|ZiW{#V{l3b-vObca@{Z7L;a22#26f^xg \ file moddatetime.f90 +!! Utility module for date and time calculations + +!> +!! \author Marco de Bruine, VU +! This file is part of DALES. +! +! DALES is free software; you can redistribute it and/or modify +! it under the terms of the GNU General Public License as published by +! the Free Software Foundation; either version 3 of the License, or +! (at your option) any later version. +! +! DALES is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU General Public License for more details. +! +! You should have received a copy of the GNU General Public License +! along with this program. If not, see . +! +! Copyright 1993-2020 Delft University of Technology, Wageningen +! University, Utrecht University, KNMI, Vrije Universiteit Amsterdam +! + +module moddatetime + + implicit none + + save + + ! Namelist variables + logical :: l_datetime = .false. + integer :: startyear = 0, & + startmonth = 0, & + startday = 0, & + timezone = 0 + + integer :: jday + integer, dimension(6) :: datex = 0, prevday = 0, nextday = 0 + + contains + + subroutine initdatetime + ! Using the namelist, construct datetime relevant variables + + use modmpi, only : myid, comm3d, mpi_logical, D_MPI_BCAST + use modglobal, only : ifnamopt, fname_options, xtime + + implicit none + + ! Auxiliary variables + integer :: ierr + + ! Read & broadcast namelist DATETIME ----------------------------------- + namelist/NAMDATETIME/ l_datetime, startyear, startmonth, startday, timezone + + if (myid == 0) then + + open(ifnamopt, file=fname_options, status='old', iostat=ierr) + read(ifnamopt, NAMDATETIME, iostat=ierr) + + if (ierr > 0) then + print *, 'iostat error: ', ierr + stop 'ERROR: Problem in namoptions NAMDATETIME' + endif + + write(6, NAMDATETIME) + close(ifnamopt) + + endif + + call d_mpi_bcast(l_datetime, 1, 0, comm3d, ierr) + call d_mpi_bcast(startyear, 1, 0, comm3d, ierr) + call d_mpi_bcast(startmonth, 1, 0, comm3d, ierr) + call d_mpi_bcast(startday, 1, 0, comm3d, ierr) + call d_mpi_bcast(timezone, 1, 0, comm3d, ierr) + + ! Initialize datetime array ----------------------------------------------- + if (l_datetime) then + + datex(1) = startyear ! year + datex(2) = startmonth ! month + + if (xtime + timezone > 24) then + datex(3) = startday + 1 ! day (LT is a day ahead of GMT) + elseif (xtime + timezone < 0) then + datex(3) = startday - 1 ! day (LT is a day behind GMT) + else + datex(3) = startday ! day + endif + + datex(4) = mod(int(xtime) + timezone + 24, 24) ! hour + datex(5) = 0 ! minutes + datex(6) = 0 ! seconds + + ! Initialize julian day number -------------------------------------------- + jday = julday(datex(1), datex(2), datex(3)) + + call caldat(jday-1, prevday(1), prevday(2), prevday(3)) + call caldat(jday+1, nextday(1), nextday(2), nextday(3)) + + ! Catch non specified starting date while l_datetime = True --------------- + ! We check on date only, because time is filled from variables shared with + ! other functionalities, that thus have non-zero values. + if (sum(datex(1:3)) == 0) stop 'ERROR: Trying to use datetime functionality without specified date.' + endif + end subroutine initdatetime + + subroutine datetime + ! -------------------------------------------------------------------------- + ! + ! Keep track of the (local) date and time by updating the datex array. + ! + ! -------------------------------------------------------------------------- + + use modglobal, only : rtimee, xtime + ! TODO: Check if rtimee really is seconds of if it is scaled + + implicit none + integer :: xhr, itimee + + itimee = int(rtimee) + xhr = mod(itimee/3600 + int(xtime) + timezone + 24, 24) + + if ( xhr < datex(4) ) then ! New day + jday = jday + 1 + call caldat(jday, datex(1), datex(2), datex(3)) + endif + + datex(4) = xhr ! hours + datex(5) = mod(itimee/60, 60) ! minutes + datex(6) = mod(itimee, 60) ! seconds + + end subroutine datetime + + subroutine caldat(julian, iyyy, mm, id) + !-------------------------------------------------------------------------- + ! caldat + ! + ! purpose + ! ------- + ! calculate date from given julian day + ! + ! parameters + ! ---------- + ! on input : julday contains the julian day + ! on output: mm, id, iyyy contain month, day and year + ! + ! reference + ! --------- + ! J. Meeus, "Astronomical formulea for calculators, 4th Edition" 1988 + !-------------------------------------------------------------------------- + implicit none + + ! input/output + integer,intent(in) :: julian + integer,intent(out) :: mm + integer,intent(out) :: id + integer,intent(out) :: iyyy + + ! local + integer,parameter :: igreg=2299161 + integer :: jalpha, ja, jb, jc, jd, je + ! + ! handle gregorian and julian date + ! + if ( julian >= igreg )then + jalpha=int(((julian-1867216)-0.25)/36524.25) + ja=julian+1+jalpha-int(0.25*jalpha) + else + ja=julian + end if + jb=ja+1524 + jc=int(6680.+((jb-2439870)-122.1)/365.25) + jd=365*jc+int(0.25*jc) + je=int((jb-jd)/30.6001) + id=jb-jd-int(30.6001*je) + mm=je-1 + if ( mm > 12 ) mm=mm-12 + iyyy=jc-4715 + if ( mm > 2 ) iyyy=iyyy-1 + ! + ! handle dates before 0 AD + ! + if ( iyyy <= 0 ) iyyy=iyyy-1 + + end subroutine caldat + + integer function julday(iy,mm,id) + !----------------------------------------------------------------------- + !**** julday + ! + ! purpose + ! ------- + ! calculate julian day from given date + ! + ! parameters + ! ---------- + ! on input : mm, id, iyyy contain month, day and year + ! on output: julday contains the julian day + ! + ! reference + ! --------- + ! J. Meeus, "Astronomical formulea for calculators, 4th Edition" 1988 + !----------------------------------------------------------------------- + implicit none + + ! input, output + integer,intent(in) :: mm ! month + integer,intent(in) :: id ! day + integer,intent(in) :: iy ! year + + ! local + integer,parameter :: igreg=15+31*(10+12*1582) + integer :: jy, jm, ja, iyyy + + ! handle dates before 0 AD + ! + iyyy=iy + if ( iy == 0 ) then + stop 'julday: ERROR invalid year 0 AD' + end if + if ( iy < 0 ) then + iyyy=iy+1 + end if + ! + !calculate julian day from date in gregorian calendar + ! + if ( mm > 2 ) then + jy=iyyy + jm=mm+1 + else + jy=iyyy-1 + jm=mm+13 + end if + julday=int(365.25*jy)+int(30.6001*jm)+id+1720995 + ! + !handle julian calender + ! + if ( id+31*(mm+12*iyyy) >= igreg ) then + ja=int(0.01*jy) + julday=julday+2-ja+int(0.25*ja) + end if + + end function julday + +end module moddatetime diff --git a/src/modemisdata.f90 b/src/modemisdata.f90 new file mode 100644 index 00000000..ba7f36da --- /dev/null +++ b/src/modemisdata.f90 @@ -0,0 +1,64 @@ +!> \file modemisdata.f90 +!! Variable definitions and auxilary routines for emissions + +!> +!! Variable definitions and auxilary routines for emission +!> +!! This routine should have no dependency on any other routine, save +!perhaps modglobal or modfields. +!! \author Marco de Bruine, VU +!! \todo Documentation +!! \par Revision list +! This file is part of DALES. +! +! DALES is free software; you can redistribute it and/or modify +! it under the terms of the GNU General Public License as published by +! the Free Software Foundation; either version 3 of the License, or +! (at your option) any later version. +! +! DALES is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU General Public License for more details. +! +! You should have received a copy of the GNU General Public License +! along with this program. If not, see . +! +! Copyright 1993-2020 Delft University of Technology, Wageningen +! University, Utrecht University, KNMI, Vrije Universiteit Amsterdam +! +! TODO INTEGRATION WITH MODCHEM, possibly switch? +! For example, nchem, firstchem etc, but also structure for species +! 'location', i.e. switch scalar field represents which species? + +module modemisdata + + implicit none + save + + integer :: iname + ! ---------------------------------------------------------------------! + ! Namelist variables ! + ! ---------------------------------------------------------------------! + + logical :: l_emission = .false. ! scalar emission switch + integer :: kemis = -1, & ! no. of layers to include for emission + svskip = 0 ! no. scalars to exclude for emission + + character(len = 6), dimension(100) :: & + emisnames = (/ (' ', iname=1, 100) /) ! list with scalar names, + ! each name must(!) be 6 characters long for now + + ! Interaction with AGs ------------------------------------------------ + integer :: svco2ags = -1 ! Scalar field number for AGs soil respiration + integer :: svco2veg = -1 ! Scalar field number for AGs net CO2 flux + integer :: svco2sum = -1 ! Scalar field which holds the sum of CO2 + integer, allocatable :: co2fields(:) ! Array defnining co2 fields for AGs + + ! ---------------------------------------------------------------------! + ! Main variables ! + ! ---------------------------------------------------------------------! + + real, allocatable :: emisfield(:,:,:,:,:) !array for emission fields + +end module modemisdata diff --git a/src/modemission.f90 b/src/modemission.f90 new file mode 100644 index 00000000..c47220c2 --- /dev/null +++ b/src/modemission.f90 @@ -0,0 +1,268 @@ +!> \file modemission.f90 +!! (Anthropogenic) emissions + +!> +!! \author Marco de Bruine, VU +! This file is part of DALES. +! +! DALES is free software; you can redistribute it and/or modify +! it under the terms of the GNU General Public License as published by +! the Free Software Foundation; either version 3 of the License, or +! (at your option) any later version. +! +! DALES is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU General Public License for more details. +! +! You should have received a copy of the GNU General Public License +! along with this program. If not, see . +! +! Copyright 1993-2020 Delft University of Technology, Wageningen +! University, Utrecht University, KNMI +! + +module modemission + +use modemisdata + +implicit none + +contains + + subroutine initemission + + use modglobal, only : i2, j2,kmax, nsv, ifnamopt, fname_options, checknamelisterror + use modmpi, only : myid, comm3d, d_mpi_bcast + use moddatetime, only : datex, prevday, nextday + + implicit none + + ! Auxiliary variables + integer :: ierr + + ! --- Read & broadcast namelist EMISSION ----------------------------------- + namelist/NAMEMISSION/ l_emission, kemis, svskip, emisnames, svco2sum + + if (myid == 0) then + + open(ifnamopt,file=fname_options,status='old',iostat=ierr) + read (ifnamopt,NAMEMISSION,iostat=ierr) + call checknamelisterror(ierr, ifnamopt, 'NAMEMISSION') + write(6, NAMEMISSION) + close(ifnamopt) + + endif + + call d_mpi_bcast(l_emission, 1, 0, comm3d, ierr) + call d_mpi_bcast(kemis, 1, 0, comm3d, ierr) + call d_mpi_bcast(svskip, 1, 0, comm3d, ierr) + call d_mpi_bcast(svco2sum, 1, 0, comm3d, ierr) + call d_mpi_bcast(emisnames(1:100), 100, 0, comm3d, ierr) + + ! -- Interaction with AGs ---------------------------------------------------- + + allocate(co2fields(nsv)) + + co2fields = 0 + co2fields(svskip+1:nsv) = index(emisnames(1:nsv-svskip), "co2") + + svco2ags = findloc(emisnames(1:nsv-svskip), value = "co2ags", dim = 1) + svco2ags = svco2ags + svskip + + svco2veg = findloc(emisnames(1:nsv-svskip), value = "co2veg", dim = 1) + svco2veg = svco2veg + svskip + + if (myid == 0) then + write(6,*) 'modemission: co2fields (scalar fields with CO2 0=no, 1=yes)' + write(6,*) co2fields + write(6,*) 'modemission: svco2ags (scalar field number for AGS emissions)' + write(6,*) svco2ags + write(6,*) 'modemission: svco2veg (scalar field number for AGS emissions)' + write(6,*) svco2veg + endif + + ! --- Local pre-calculations and settings + if (.not. (l_emission)) return + + if (kemis == -1) kemis = kmax + + ! --- Read emission files for first time step ---------------------------------- + + ! Two hourly emission fields are loaded at all times: + ! (1) before model time, t_field < t_model, "in-the-past" + ! (2) ahead of model time, t_field > t_model, "in-the-future" + allocate(emisfield(i2, j2, kemis, svskip+1:nsv, 2)) + + if (datex(5) >= 30) then + call reademission( datex(1), datex(2), datex(3), datex(4), emisfield(:,:,:,:,1)) + + if (datex(4) == 23) then + call reademission(nextday(1), nextday(2), nextday(3), 0, emisfield(:,:,:,:,2)) + else + call reademission( datex(1), datex(2), datex(3), datex(4)+1, emisfield(:,:,:,:,2)) + endif + + else + call reademission( datex(1), datex(2), datex(3), datex(4), emisfield(:,:,:,:,2)) + + if (datex(4) == 0) then + call reademission(prevday(1), prevday(2), prevday(3), 23, emisfield(:,:,:,:,1)) + else + call reademission( datex(1), datex(2), datex(3), datex(4)-1, emisfield(:,:,:,:,1)) + endif + + endif + end subroutine initemission + + subroutine reademission(iyear, imonth, iday, ihour, emisfield) + + ! ---------------------------------------------------------------------- + ! Reading of emission files + ! Multiple/all tracers + ! ---------------------------------------------------------------------- + + use mpi, only : MPI_INFO_NULL + use netcdf + use modmpi, only : myid, myidx, myidy + use modglobal, only : i1, j1, i2, j2, imax, jmax, nsv + use moddatetime, only : datex + + implicit none + + integer, intent(in) :: iyear, imonth, iday, ihour + real, intent(out) :: emisfield(i2, j2, kemis, 1+svskip:nsv) + + integer, parameter :: ndim = 3 + integer :: start(ndim), count(ndim) + integer :: ncid, varid + integer :: isv + + character(len=12) :: sdatetime + + ! Create string from given date + write(sdatetime, "(I0.4,2I0.2,2I0.2)") iyear, imonth, iday, ihour, 0 + + write(6,"(A18, A12)") "Reading emission: ", sdatetime + + do isv = 1+svskip, nsv + + call check( nf90_open( trim(emisnames(isv-svskip))//'_emis_'//sdatetime//'_3d.nc', NF90_NOWRITE, ncid)) + + call check( nf90_inq_varid( ncid, emisnames(isv-svskip), varid) ) + call check( nf90_get_var ( ncid, varid, emisfield(2:i1,2:j1,1:kemis,isv), & + start = (/1 + myidx * imax, 1 + myidy * jmax, 1, 1/), & + count = (/imax, jmax, kemis, 1/) ) ) + call check( nf90_close( ncid ) ) + + end do + + contains + + subroutine check(status) + integer, intent(in) :: status + + if(status /= nf90_noerr) then + print *, trim(nf90_strerror(status)) + stop 'NetCDF error in modemission. See outputfile for more information.' + end if + end subroutine check + + end subroutine reademission + + subroutine emission + ! ---------------------------------------------------------------------- + ! Read appropriate emission fields, interpolate and transfer to svp + ! + ! NOTES + ! 1. Emission files (currently) in kg per gridbox per hour! + ! What results from this routine now is ug/g, i.e. we scale for time, + ! gridbx size and air density AND apply a factor of 1e6. + ! + ! TODO + ! 1. MDB Align properly with "non-emitted" tracers, i.e. cloud scalars from e.g. + ! microphysics/chemistry + ! ---------------------------------------------------------------------- + + use modfields, only : svp + use modglobal, only : i1, j1, ih, jh, nsv, & + rdt, rtimee, rk3step, & + dzf, dx, dy + use modfields, only : rhof + use moddatetime, only : datex, nextday + + implicit none + + integer :: i, j, k, l + + real :: emistime_s, emistime_e ! Emission timers + real, parameter :: div3600 = 1./3600. ! Quick division + real :: tend + + if (.not. (l_emission)) return + + ! -------------------------------------------------------------------------- + ! Interpolate and apply emission + ! -------------------------------------------------------------------------- + emistime_s = mod(rtimee + 1800., 3600.)*div3600 + + ! MdB NOTE : Better way to do this? Problem is the broadcasting of 1D arrays + ! rhof and dzf to emisfield. For now, loop over k. + ! BvS NOTE: I wrote out the loop, to prevent needing a temporary 2D field to store `tend`. + + do k = 1, kemis + do i = 2, i1 + do j = 2, j1 + do l = svskip+1, nsv + tend = ((1. - emistime_s)*emisfield(i,j,k,l,1) + & + emistime_s *emisfield(i,j,k,l,2))/(3600.*rhof(k)*dzf(k)*dx*dy*1e-6) + + ! Add tendency to specific CO2 field + svp(i,j,k,l) = svp(i,j,k,l) + tend + + ! Add tendency to CO2 sum field + svp(i,j,k,svco2sum) = svp(i,j,k,svco2sum) + tend + end do + end do + end do + end do + + ! -------------------------------------------------------------------------- + ! Read emission files when neccesary, i.e. simulation reaches half hour mark + ! after current timestep + ! -------------------------------------------------------------------------- + + if ( rk3step == 3 ) then + emistime_e = mod(rtimee + rdt + 1800., 3600.)*div3600 + + if ( emistime_e < emistime_s ) then + ! Transfer data from 'ahead-of-modeltime' field to 'past-modeltime' field + emisfield(:,:,:,:,1) = emisfield(:,:,:,:,2) + + ! Read new 'ahead-of-modeltime' emission field + if ( datex(4) == 23 ) then + call reademission(nextday(1), nextday(2), nextday(3), 0, emisfield(:,:,:,:,2)) + else + call reademission( datex(1), datex(2), datex(3), datex(4)+1, emisfield(:,:,:,:,2)) + endif + endif + + endif + + end subroutine emission + + ! -------------------------------------------------------------------------- + ! Cleanup after run. + ! -------------------------------------------------------------------------- + subroutine exitemission + + implicit none + + if (.not. (l_emission)) return + + deallocate(emisfield) + deallocate(co2fields) + + end subroutine exitemission + +end module modemission diff --git a/src/modfields.f90 b/src/modfields.f90 index 75ba2b12..e6833857 100644 --- a/src/modfields.f90 +++ b/src/modfields.f90 @@ -127,7 +127,7 @@ module modfields real(field_r), allocatable :: esl(:,:,:) real(field_r), allocatable :: qsat(:,:,:) - real(field_r), allocatable :: surf_rain(:,:) !< integrated surface rain + real(field_r), allocatable :: surf_rain(:,:) !< integrated surface rain contains !> Allocate and initialize the prognostic variables @@ -137,33 +137,32 @@ subroutine initfields ! Allocation of prognostic variables implicit none - allocate(um (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(vm (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(wm (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(thlm (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(e12m (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(qtm (2-ih:i1+ih,2-jh:j1+jh,k1)) - - allocate(u0 (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(v0 (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(w0 (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(thl0 (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(e120 (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(qt0 (2-ih:i1+ih,2-jh:j1+jh,k1)) - - allocate(up (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(vp (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(wp (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(thlp (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(e12p (2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(qtp (2-ih:i1+ih,2-jh:j1+jh,k1)) - - allocate(svm (2-ih:i1+ih,2-jh:j1+jh,k1,nsv)) - allocate(sv0 (2-ih:i1+ih,2-jh:j1+jh,k1,nsv)) - allocate(svp (2-ih:i1+ih,2-jh:j1+jh,k1,nsv)) - - allocate(thl0h(2-ih:i1+ih,2-jh:j1+jh,k1)) - allocate(qt0h (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(um (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(vm (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(wm (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(thlm (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(e12m (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(qtm (2-ih:i1+ih,2-jh:j1+jh,k1)) + + allocate(u0 (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(v0 (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(w0 (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(thl0 (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(thl0h (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(e120 (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(qt0 (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(qt0h (2-ih:i1+ih,2-jh:j1+jh,k1)) + + allocate(up (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(vp (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(wp (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(thlp (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(e12p (2-ih:i1+ih,2-jh:j1+jh,k1)) + allocate(qtp (2-ih:i1+ih,2-jh:j1+jh,k1)) + + allocate(svm (2-ih:i1+ih,2-jh:j1+jh,k1,nsv)) + allocate(sv0 (2-ih:i1+ih,2-jh:j1+jh,k1,nsv)) + allocate(svp (2-ih:i1+ih,2-jh:j1+jh,k1,nsv)) ! Allocation of base state variables allocate(rhobf (k1)) diff --git a/src/modgenstat.f90 b/src/modgenstat.f90 index ba943228..3eb9773f 100644 --- a/src/modgenstat.f90 +++ b/src/modgenstat.f90 @@ -170,6 +170,7 @@ subroutine initgenstat cexpnr,dtav_glob,timeav_glob,dt_lim,btime,tres,lwarmstart,checknamelisterror use modstat_nc, only : lnetcdf, open_nc,define_nc,ncinfo,nctiminfo,writestat_dims_nc use modsurfdata, only : isurf,ksoilmax + use modlsm, only : kmax_soil implicit none @@ -419,6 +420,8 @@ subroutine initgenstat if (isurf==1) then call open_nc(fname, ncid,nrec,n3=kmax,ns=ksoilmax) + else if (isurf==11) then + call open_nc(fname, ncid,nrec,n3=kmax,ns=kmax_soil) else call open_nc(fname, ncid,nrec,n3=kmax) endif diff --git a/src/modglobal.f90 b/src/modglobal.f90 index 8c594f11..f2a62a43 100644 --- a/src/modglobal.f90 +++ b/src/modglobal.f90 @@ -91,6 +91,14 @@ module modglobal real,parameter :: epscloud = 1.e-5 !< *limit for cloud calculation 0.01 g/kg real,parameter :: boltz = 5.67e-8 !< *Stefan-Boltzmann constant + ! Land-surface + real,parameter :: rho_solid_soil = 2700 !< Density of dry solid soil (kg m-3) + real,parameter :: rho_C_matrix = 1.6e6 !< Volumetric soil heat capacity [J m-3 K-1] + real,parameter :: rho_C_water = 4.18e6 !< Volumetric water heat capacity [J m-3 K-1] + real,parameter :: gamma_T_matrix = 3.4293695508945325 !< Heat conductivity soil [J s-1 m-1 K-1] + real,parameter :: gamma_T_water = 0.57 !< Heat conductivity water [J s-1 m-1 K-1] + + logical :: lcoriol = .true. !< switch for coriolis force logical :: lpressgrad = .true. !< switch for horizontal pressure gradient force @@ -351,6 +359,7 @@ subroutine initglobal do n = 1, nsv if(iadv_sv(n) < 0) then iadv_sv(n) = iadv_mom + write (*,*) "Warning: iadv_sv(", n, ") not specified, defaulting to iadv_mom = ", iadv_mom end if end do diff --git a/src/modlsm.f90 b/src/modlsm.f90 new file mode 100644 index 00000000..a788eef2 --- /dev/null +++ b/src/modlsm.f90 @@ -0,0 +1,2212 @@ +! +! Copyright (c) 2020-2020 Wageningen University and Research (WUR) +! +! This file is part of DALES +! +! DALES is free software: you can redistribute it and/or modify +! it under the terms of the GNU General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! DALES is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU General Public License for more details. +! +! You should have received a copy of the GNU General Public License +! along with DALES. If not, see . +! + +module modlsm + use netcdf + implicit none + + public :: initlsm, lsm, exitlsm, init_lsm_tiles, lags + + logical :: llsm ! On/off switch LSM + logical :: lfreedrainage ! Free drainage bottom BC for soil moisture + logical :: lags ! Switch for A-Gs scheme + + ! Interpolation types soil from full to half level + integer :: iinterp_t, iinterp_theta + integer, parameter :: iinterp_amean = 1 ! val = 0.5*(v1+v2) + integer, parameter :: iinterp_gmean = 2 ! val = sqrt(v1*v2) + integer, parameter :: iinterp_hmean = 3 ! val = ((dz1+dz2)*v1*v2)/(dz1*v2+dz2*v1) + integer, parameter :: iinterp_max = 4 ! val = max(a,b) + + ! Soil grid + integer :: kmax_soil + real :: z_size_soil + real, allocatable :: z_soil(:), zh_soil(:) + real, allocatable :: dz_soil(:), dzh_soil(:) + real, allocatable :: dzi_soil(:), dzhi_soil(:) + + ! Soil index in `van_genuchten_parameters.nc` lookup table: + integer, allocatable :: soil_index(:,:,:) + + ! Source term in soil water budget + real, allocatable :: phiw_source(:,:,:) + + ! Precipitation, interception et al. + real, allocatable :: throughfall(:,:) + real, allocatable :: interception(:,:) + real, allocatable :: wl_max(:,:) + + ! Dependency factor canopy resistance on VPD (high veg only) + real, allocatable :: gD(:,:) + + ! Reduction functions canopy resistance + real, allocatable :: f1(:,:), f2_lv(:,:), f2_hv(:,:), f2b(:,:), f3(:,:) + + ! Random + real, allocatable :: du_tot(:,:), thv_1(:,:), land_frac(:,:) + + ! A-Gs + real, allocatable :: an_co2(:,:), resp_co2(:,:) + integer :: co2_index = -1 + + ! Data structure for sub-grid tiles + type lsm_tile + ! Static properties: + real, allocatable :: z0m(:,:), z0h(:,:) + ! Base tile fraction (i.e. without liquid water) + real, allocatable :: base_frac(:,:) + ! Dynamic tile fraction: + real, allocatable :: frac(:,:) + ! Monin-obukhov / surface layer: + real, allocatable :: obuk(:,:), ustar(:,:), ra(:,:) + ! Conductivity skin layer: + real, allocatable :: lambda_stable(:,:), lambda_unstable(:,:) + ! Surface fluxes: + real, allocatable :: H(:,:), LE(:,:), G(:,:) + real, allocatable :: wthl(:,:), wqt(:,:) + ! Surface (potential) temperature and humidity: + real, allocatable :: tskin(:,:), thlskin(:,:), qtskin(:,:) + ! Buoyancy difference surface - atmosphere + real, allocatable :: db(:,:) + ! Vegetation properties: + real, allocatable :: lai(:,:), rs_min(:,:), rs(:,:) + ! Root fraction in soil + real, allocatable :: a_r(:,:), b_r(:,:) + real, allocatable :: root_frac(:,:,:) + ! Root fraction weighted mean soil water content + real, allocatable :: phiw_mean(:,:) + end type lsm_tile + + ! Tiles for low veg (lv), high veg (hv), bare soil (bs), wet skin (ws), water (aq): + type(lsm_tile) tile_lv, tile_hv, tile_bs, tile_ws, tile_aq + + ! Land-surface / van Genuchten parameters from NetCDF input table. + real, allocatable :: & + theta_res(:), theta_wp(:), theta_fc(:), theta_sat(:), & + gamma_theta_sat(:), vg_a(:), vg_l(:), vg_n(:) + + ! Derived soil parameters + real, allocatable :: & + vg_m(:), & + lambda_theta_min(:), lambda_theta_max(:), & + gamma_theta_min(:), gamma_theta_max(:), & + gamma_t_dry(:), rho_C(:) + +contains + +subroutine lsm + implicit none + + if (.not. llsm) return + + ! Calculate dynamic tile fractions, + ! based on the amount of liquid water on vegetation. + call calc_tile_fractions + + ! Calculate root fraction weighted mean soil water content. + call calc_theta_mean(tile_lv) + call calc_theta_mean(tile_hv) + + ! Calculate canopy/soil resistances. + if (lags) then + call calc_canopy_resistance_ags + else + call calc_canopy_resistance_js + endif + + ! Calculate aerodynamic resistance (and u*, obuk). + call calc_stability + + ! Set grid point averaged boundary conditions (thls, qts, gradients, ..) + call calc_bulk_bcs + + ! Calculate soil tendencies + ! Calc diffusivity heat: + call calc_thermal_properties + ! Solve diffusion equation: + call integrate_t_soil + + ! Calc diffusivity and conductivity soil moisture: + call calc_hydraulic_properties + ! Calculate tendency due to root water extraction + call calc_root_water_extraction + ! Solve diffusion equation: + call integrate_theta_soil + + ! Update liquid water reservoir + call calc_liquid_reservoir + +end subroutine lsm + +! +! Calculate dynamic tile fractions, based on liquid water on vegetation +! +subroutine calc_tile_fractions + use modglobal, only : i1, j1 + use modsurfdata, only : wl, wmax + implicit none + + integer :: i, j + real :: c_liq + + ! Water tile fraction is not variable + tile_aq%frac(:,:) = tile_aq%base_frac(:,:) + + do j=2, j1 + do i=2, i1 + c_liq = min(1., wl(i,j)/wl_max(i,j)) + tile_ws%frac(i,j) = land_frac(i,j) * c_liq + tile_lv%frac(i,j) = (1.-c_liq) * tile_lv%base_frac(i,j) + tile_hv%frac(i,j) = (1.-c_liq) * tile_hv%base_frac(i,j) + tile_bs%frac(i,j) = (1.-c_liq) * (1.-tile_lv%base_frac(i,j)-tile_hv%base_frac(i,j)-tile_aq%base_frac(i,j)) + end do + end do + +end subroutine calc_tile_fractions + +! +! Calculate changes in the liquid water reservoir +! +subroutine calc_liquid_reservoir + use modglobal, only : rk3step, rdt, i1, j1, rhow, rlv + use modsurfdata, only : wl, wlm + use modmicrodata, only : imicro, sed_qr + implicit none + + integer :: i, j + real :: tend, rk3coef, rainrate, c_veg, wl_tend_max, wl_tend_min + real :: wl_tend_liq, wl_tend_dew, wl_tend_precip, wl_tend_sum, wl_tend_lim + + real, parameter :: intercept_eff = 0.5 + real, parameter :: to_ms = 1./(rhow*rlv) + + rk3coef = rdt / (4. - dble(rk3step)) + if(rk3step == 1) wlm(:,:) = wl(:,:) + + do j=2, j1 + do i=2, i1 + c_veg = tile_lv%base_frac(i,j)+tile_hv%base_frac(i,j) + + ! Max and min possible tendencies + wl_tend_min = -wlm(i,j) / rk3coef + wl_tend_max = (wl_max(i,j) - wlm(i,j)) / rk3coef + + ! Tendency due to evaporation from liquid water reservoir/tile. + wl_tend_liq = -max(0., tile_ws%frac(i,j) * tile_ws%LE(i,j) * to_ms) + + ! Tendency due to dewfall into vegetation/soil/liquid water tiles + wl_tend_dew = & + -( min(0., tile_lv%frac(i,j) * tile_lv%LE(i,j) * to_ms) + & + min(0., tile_hv%frac(i,j) * tile_hv%LE(i,j) * to_ms) + & + min(0., tile_bs%frac(i,j) * tile_bs%LE(i,j) * to_ms) + & + min(0., tile_ws%frac(i,j) * tile_ws%LE(i,j) * to_ms) ) + + ! Tendency due to interception of precipitation by vegetation + if (imicro == 0) then + rainrate = 0. + else + rainrate = -sed_qr(i,j,1)/rhow + end if + + wl_tend_precip = intercept_eff * c_veg * rainrate + + ! Total and limited tendencies + wl_tend_sum = wl_tend_liq + wl_tend_dew + wl_tend_precip; + wl_tend_lim = min(wl_tend_max, max(wl_tend_min, wl_tend_sum)); + + ! Diagnose interception and throughfall + throughfall(i,j) = & + -(1.-c_veg) * rainrate & + -(1.-intercept_eff) * c_veg * rainrate & + + min(0., wl_tend_lim - wl_tend_sum) + interception(i,j) = max(0., wl_tend_lim) + + ! Integrate + wl(i,j) = wlm(i,j) + rk3coef * wl_tend_lim + + end do + end do + +end subroutine calc_liquid_reservoir + + +! +! Calculate root fraction weighted mean soil water content +! +subroutine calc_theta_mean(tile) + use modglobal, only : i1, j1 + use modsurfdata, only : phiw + implicit none + + type(lsm_tile), intent(inout) :: tile + integer :: i, j, k, si + real :: theta_lim + + tile%phiw_mean(:,:) = 0. + + do k=1, kmax_soil + do j=2,j1 + do i=2,i1 + si = soil_index(i,j,k) + + theta_lim = max(phiw(i,j,k), theta_wp(si)) + tile%phiw_mean(i,j) = tile%phiw_mean(i,j) + tile%root_frac(i,j,k) * & + (theta_lim - theta_wp(si)) / (theta_fc(si) - theta_wp(si)) + end do + end do + end do + +end subroutine calc_theta_mean + +! +! Calculate canopy and soil resistances using Jarvis-Stewart method. +! +subroutine calc_canopy_resistance_js + use modglobal, only : i1, j1 + use modfields, only : thl0, qt0, exnf, presf + use modsurface, only : ps + use modraddata, only : swd + use modsurfdata, only : phiw + implicit none + + integer :: i, j, k, si + real :: swd_pos, T, esat, e, theta_min, theta_rel, c_veg + + ! Constants f1 calculation: + real, parameter :: a_f1 = 0.81 + real, parameter :: b_f1 = 0.004 + real, parameter :: c_f1 = 0.05 + + k = kmax_soil + do j=2,j1 + do i=2,i1 + si = soil_index(i,j,k) + + ! f1: reduction vegetation resistance as f(sw_in): + swd_pos = max(0., -swd(i,j,1)) + f1(i,j) = 1./min(1., (b_f1*swd_pos + c_f1) / (a_f1 * (b_f1*swd_pos + 1.))) + + ! f2: reduction vegetation resistance as f(theta): + f2_lv(i,j) = 1./min(1., max(1.e-9, tile_lv%phiw_mean(i,j))) + f2_hv(i,j) = 1./min(1., max(1.e-9, tile_hv%phiw_mean(i,j))) + + ! f3: reduction vegetation resistance as f(VPD) (high veg only): + T = thl0(i,j,1) * exnf(1) + esat = 0.611e3 * exp(17.2694 * (T - 273.16) / (T - 35.86)) + e = qt0(i,j,1) * presf(1) / 0.622 + + f3(i,j) = 1./exp(-gD(i,j) * (esat-e)) + + ! f2b: reduction soil resistance as f(theta) + c_veg = tile_lv%base_frac(i,j) + tile_hv%base_frac(i,j) + theta_min = c_veg * theta_wp(si) + (1.-c_veg) * theta_res(si); + theta_rel = (phiw(i,j,k) - theta_min) / (theta_fc(si) - theta_min); + f2b(i,j) = 1./min(1., max(1.e-9, theta_rel)) + + ! Calculate canopy and soil resistance + tile_lv%rs(i,j) = tile_lv%rs_min(i,j) / tile_lv%lai(i,j) * f1(i,j) * f2_lv(i,j) + tile_hv%rs(i,j) = tile_hv%rs_min(i,j) / tile_hv%lai(i,j) * f1(i,j) * f2_hv(i,j) * f3(i,j) + tile_bs%rs(i,j) = tile_bs%rs_min(i,j) / f2b(i,j) + tile_ws%rs(i,j) = 0. + + end do + end do + +end subroutine calc_canopy_resistance_js + +! +! Calculate canopy and soil resistances using A-Gs (plant physiology). +! In addition, this calculates/sets the surface CO2 fluxes... +! +subroutine calc_canopy_resistance_ags + use modglobal, only : i1, j1 + use modfields, only : rhof, exnh, qt0, presf, svm + use modsurface, only : E1, ps + use modsurfdata, only : svflux, tskin, phiw, tsoil + use modraddata, only : swd + use modemisdata, only : l_emission, svco2ags, svco2sum, svco2veg + + implicit none + + ! NOTE: these should become a namelist switches... + logical, parameter :: lsplitleaf = .false. + logical, parameter :: lrelaxgc = .false. + + real :: Ts, co2_comp, gm, fmin0, fmin, esatsurf, e, Ds, Dmax, cfrac, co2_abs, ci, to_ppb, from_ppb + real :: Ammax, fstr, Am, Rdark, PAR, alphac, AGSa1, Dstar, tempy, An, gc_inf, gcco2, fw, t_mean, th_mean, rs_co2 + real :: cveg, cland, theta_min, theta_rel + integer :: i, j, k, si + + ! Fixed constants (** = same in DALES and IFS, !! = different in DALES and IFS) + real, parameter :: Q10gm = 2.0 ! (**) Parameter to calculate the mesophyll conductance + real, parameter :: Q10am = 2.0 ! (**) Parameter to calculate max primary productivity + real, parameter :: Q10lambda = 1.5 ! (!!) Parameter to calculate the CO2 compensation concentration. (2 in IFS, 1.5 in DALES) + + ! Reference temperatures calculation mesophyll conductance: + real, parameter :: T1gm = 278 ! (**) + real, parameter :: T2gm = 301 ! (!!: IFS=309, DALES=301 (default), 309 (C4)) + + ! Reference temperatues calculation max primary productivity: + real, parameter :: T1Am = 286 ! (!!: IFS=281, DALES=286 (C4)) + real, parameter :: T2Am = 311 ! (**) + + real, parameter :: nuco2q = 1.6 ! Ratio molecular viscosity water to carbon dioxide + real, parameter :: gmin = 2.5e-4 ! Cuticular (minimum) conductance. NOTE: = g_cu in IFS? + real, parameter :: ad = 0.07 ! Regression coefficient to calculate Cfrac + real, parameter :: Kx = 0.7 ! Extinction coefficient PAR + + !???? + real, parameter :: alpha0 = 0.014 ! Initial low light conditions (?) + + real, parameter :: Mair = 28.97 + real, parameter :: Mco2 = 44 + + ! Parameters respiration Jacobs (2006) + real, parameter :: Cw = 1.6e-3 ! Constant water stress correction + real, parameter :: wsmax = 0.55 ! Upper reference value soil water # NOTE: I guess these are soil dependant? + real, parameter :: wsmin = 0.005 ! Lower reference value soil water # NOTE: see line above :-) + real, parameter :: R10 = 0.23 ! Respiration at 10oC (Jacobs 2007) + real, parameter :: Eact0 = 53.3e3 ! Activation energy + + ! Vegetation specific constants + ! TODO: link to lookup table IFS, for now hardcoded for single veg type..... + real, parameter :: gm298 = 7 !1.3 ! (mm s-1) NOTE: Much lower than DALES... + real, parameter :: Ammax298 = 1.7 ! CO2 maximal primary productivity + real, parameter :: f0 = 0.85 ! Maximum value Cfrac (constant or equation in IFS) + real, parameter :: co2_comp298 = 68.5 ! CO2 compensation concentration = Lambda(25) in IFS + + ! For sw_splitleaf + !nr_gauss = 3 ! Amount of bins to use for Gaussian integrations + !weight_g = np.array([0.2778,0.4444,0.2778]) ! Weights of the Gaussian bins (must add up to 1) + !angle_g = np.array([0.1127, 0.5,0.8873]) ! Sines of the leaf angles compared to the sun in the first Gaussian integration + !LAI_g = np.array([0.1127, 0.5,0.8873]) ! Ratio of integrated LAI at locations where shaded leaves are evaluated in the second Gaussian integration + !sigma = 0.2 ! Scattering coefficient + !kdfbl = 0.8 ! Diffuse radiation extinction coefficient for black leaves + + ! Conversion factors + ! NOTE: this should use the surface density, not `rhof(1)`. + to_ppb = Mair/Mco2/rhof(1)*1000 + from_ppb = 1./to_ppb + + do j=2,j1 + do i=2,i1 + si = soil_index(i, j, kmax_soil) + + Ts = tskin(i,j) * exnh(1) + + ! Calculate the CO2 compensation concentration (IFS eq. 8.92) + ! "The compensation point Γ is defined as the CO2 concentration at which the net CO2 assimilation of a fully lit leaf becomes zero." + ! NOTE: The old DALES LSM used the atmospheric `thl`, IFS uses the surface temperature. + co2_comp = rhof(1) * co2_comp298 * Q10lambda**(0.1 * (Ts - 298.0)) + + ! Calculate the mesophyll conductance (IFS eq. 8.93) + ! "The mesophyll conductance gm describes the transport of CO2 from the substomatal cavities to the mesophyll cells where the carbon is fixed." + ! NOTE: The old DALES LSM used the atmospheric `thl`, IFS uses the surface temperature. + gm = gm298 * Q10gm**(0.1 * (Ts - 298.0)) / ((1. + exp(0.3 * (T1gm - Ts))) * (1. + exp(0.3 * (Ts - T2gm)))) / 1000. + + ! Calculate CO2 concentration inside the leaf (ci) + ! NOTE: Differs from IFS + fmin0 = gmin/nuco2q - (1./9.)*gm + fmin = (-fmin0 + (fmin0**2 + 4*gmin/nuco2q*gm)**0.5) / (2.*gm) + + ! Calculate atmospheric moisture deficit + ! NOTE: "Therefore Ci/Cs is specified as a function of atmospheric moisture deficit Ds at the leaf surface" + ! In DALES, this uses a mix between esat(surface) and e(atmosphere) + ! In IFS, this uses (in kg kg-1): qsat(Ts)-qs instead of qsat(Ts)-qa! + ! NOTE: Old DALES LSM used the surface pressure in the calculation of `e`, not sure why... + esatsurf = 0.611e3 * exp(17.2694 * (Ts - 273.16) / (Ts - 35.86)) + e = qt0(i,j,1) * presf(1) / 0.622 + Ds = (esatsurf - e) / 1000. + + ! This seems to differ from IFS? + Dmax = (f0 - fmin) / ad + + ! Coupling factor (IFS eq. 8.101) + !cfrac = f0 * (1.0 - Ds/Dmax) + fmin * (Ds/Dmax) + cfrac = max(0.01, f0 * (1.0 - Ds/Dmax) + fmin * (Ds/Dmax)) + + ! Absolute CO2 concentration. + if (l_emission) then + co2_abs = svm(i,j,1,svco2sum) * from_ppb + else + co2_abs = svm(i,j,1,co2_index) * from_ppb + endif + + !if (lrelaxci) then + ! if (ci_old_set) then + ! ci_inf = cfrac * (co2_abs - co2_comp) + co2_comp + ! ci = ci_old(i,j) + min(kci*rk3coef, 1.0) * (ci_inf - ci_old(i,j)) + ! if (rk3step == 3) then + ! ci_old(i,j) = ci + ! endif + ! else + ! ci = cfrac * (co2_abs - co2_comp) + co2_comp + ! ci_old(i,j) = ci + ! endif + !else + ! ci = cfrac * (co2_abs - co2_comp) + co2_comp + !endif + + ! CO2 concentration in leaf (IFS eq. ???): + ci = cfrac * (co2_abs - co2_comp) + co2_comp + + ! Max gross primary production in high light conditions (Ag) (IFS eq. 8.94) + ! NOTE: The old DALES LSM used the atmospheric `thl`, IFS uses the surface temperature. + Ammax = Ammax298 * Q10am**(0.1 * (Ts - 298.0)) / ((1.0 + exp(0.3 * (T1Am - Ts))) * (1. + exp(0.3 * (Ts - T2Am)))) + + ! Effect of soil moisture stress on gross assimilation rate. + ! NOTE: this seems to be different in IFS... + ! NOTE: for now, this uses the relative soil moisture content from the low vegetaion only! + fstr = max(1.0e-3, min(1.0, tile_lv%phiw_mean(i,j))) + + ! Gross assimilation rate (Am, IFS eq. 8.97) + Am = Ammax * (1 - exp(-(gm * (ci - co2_comp) / Ammax))) + + ! Autotrophic dark respiration (IFS eq. 8.99) + Rdark = Am/9. + + !PAR = 0.40 * max(0.1,-swdav * cveg(i,j)) + PAR = 0.5 * max(0.1, -swd(i,j,1)) + + ! Light use efficiency + alphac = alpha0 * (co2_abs - co2_comp) / (co2_abs + 2*co2_comp) + + if (lsplitleaf) then + print*,'Splitleaf A-Gs not (yet) implemented!' + stop + ! PARdir = 0.5 * max(0.1, sw_in[j,i]) + ! PARdif = 0.5 * max(0.1, sw_in[j,i]) + ! + ! cos_sza = max(1.e-10, calc_zenith_angle(lat, lon, doy, time)) + ! + ! kdrbl = 0.5 / cos_sza ! Direct radiation extinction coefficient for black leaves + ! kdf = kdfbl * np.sqrt(1.0-sigma) + ! kdr = kdrbl * np.sqrt(1.0-sigma) + ! ref = (1.0 - np.sqrt(1.0-sigma)) / (1.0 + np.sqrt(1.0-sigma)) ! Reflection coefficient + ! ref_dir = 2 * ref / (1.0 + 1.6 * cos_sza) + ! + ! Hleaf = np.zeros(nr_gauss+1) + ! Fleaf = np.zeros(nr_gauss+1) + ! Agl = np.zeros(nr_gauss+1) + ! + ! Fnet = np.zeros(nr_gauss) + ! gnet = np.zeros(nr_gauss) + ! + ! ! Loop over different LAI locations + ! for it in range(nr_gauss): + ! + ! iLAI = LAI * LAI_g[it] ! Integrated LAI between here and canopy top; Gaussian distributed + ! fSL = np.exp(-kdrbl * iLAI) ! Fraction of sun-lit leaves + ! + ! PARdfD = PARdif * (1.0-ref) * np.exp(-kdf * iLAI) ! Total downward PAR due to diffuse radiation at canopy top + ! PARdrD = PARdir * (1.0-ref_dir) * np.exp(-kdr * iLAI) ! Total downward PAR due to direct radiation at canopy top + ! PARdfU = PARdif * (1.0-ref) * np.exp(-kdf * LAI) * \ + ! albedo * (1.0-ref) * np.exp(-kdf * (LAI-iLAI)) ! Total upward (reflected) PAR that originates as diffuse radiation + ! PARdrU = PARdir * (1.0-ref_dir) * np.exp(-kdr * LAI) * \ + ! albedo * (1.0-ref) * np.exp(-kdf * (LAI-iLAI)) ! Total upward (reflected) PAR that originates as direct radiation + ! PARdfT = PARdfD + PARdfU ! Total PAR due to diffuse radiation at canopy top + ! PARdrT = PARdrD + PARdrU ! Total PAR due to direct radiation at canopy top + ! + ! dirPAR = (1.0-sigma) * PARdir * fSL ! Purely direct PAR (can only be downward) + ! difPAR = PARdfT + PARdrT - dirPAR ! Total diffuse radiation + ! + ! HdfT = kdf * PARdfD + kdf * PARdfU + ! HdrT = kdr * PARdrD + kdf * PARdrU + ! dirH = kdrbl * dirPAR + ! Hshad = HdfT + HdrT - dirH + ! + ! Hsun = Hshad + angle_g * (1.0-sigma) * kdrbl * PARdir / np.sum(angle_g * weight_g) + ! + ! Hleaf[0] = Hshad + ! Hleaf[1:] = Hsun + ! + ! Agl = fstr * (Am + Rdark) * (1 - np.exp(-alphac*Hleaf/(Am + Rdark))) + ! gleaf = gmin/nuco2q + Agl/(co2_abs-ci) + ! Fleaf = Agl - Rdark + ! + ! Fshad = Fleaf[0] + ! Fsun = np.sum(weight_g * Fleaf[1:]) + ! gshad = gleaf[0] + ! gsun = np.sum(weight_g * gleaf[1:]) + ! + ! Fnet[it] = Fsun * fSL + Fshad * (1 - fSL) + ! gnet[it] = gsun * fSL + gshad * (1 - fSL) + ! + ! An = LAI * np.sum(weight_g * Fnet) + ! gc_inf = LAI * np.sum(weight_g * gnet) + ! + else + ! Calculate upscaling from leaf to canopy: net flow CO2 into the plant (An) + ! NOTE: this only uses LAI from low vegetation (for now..) + AGSa1 = 1.0 / (1 - f0) + Dstar = Dmax / (AGSa1 * (f0 - fmin)) + tempy = alphac * Kx * PAR / (Am + Rdark) + An = (Am + Rdark) * (1 - 1.0 / (Kx * tile_lv%lai(i,j)) * (E1(tempy * exp(-Kx*tile_lv%lai(i,j))) - E1(tempy))) + gc_inf = tile_lv%lai(i,j) * (gmin/nuco2q + AGSa1 * fstr * An / ((co2_abs - co2_comp) * (1 + Ds / Dstar))) + endif + + if (lrelaxgc) then + print*,'Relax GC A-Gs not (yet) implemented!' + stop + ! if (gc_old_set) then + ! gcco2 = gc_old(i,j) + min(kgc*rk3coef, 1.0) * (gc_inf - gc_old(i,j)) + ! if (rk3step ==3) then + ! gc_old(i,j) = gcco2 + ! endif + ! else + ! gcco2 = gc_inf + ! gc_old(i,j) = gcco2 + ! endif + else + gcco2 = gc_inf + endif + + ! Calculate mean t_soil and theta_soil for calculation soil respiration + t_mean = 0 + th_mean = 0 + k = kmax_soil + do k=1, kmax_soil + t_mean = t_mean + tsoil(i,j,k) * dz_soil(k) + th_mean = th_mean + phiw(i,j,k) * dz_soil(k) + enddo + t_mean = t_mean / (-zh_soil(1)) + th_mean = th_mean / (-zh_soil(1)) + + ! Sub-grid fractions of low+high veg, and low+high veg + bare soil + cveg = tile_lv%base_frac(i,j) + tile_hv%base_frac(i,j) + cland = cveg + tile_bs%base_frac(i,j) + + ! Water stress function: + fw = Cw * wsmax / (th_mean + wsmin) + + ! Soil resistance, using old/JS approach + theta_min = cveg * theta_wp(si) + (1.-cveg) * theta_res(si) + theta_rel = (phiw(i,j,kmax_soil) - theta_min) / (theta_fc(si) - theta_min); + f2b(i,j) = 1./min(1., max(1.e-9, theta_rel)) + + ! Surface resistances for moisture and carbon dioxide + tile_lv%rs(i,j) = 1.0 / (1.6 * gcco2) + tile_hv%rs(i,j) = 1.0 / (1.6 * gcco2) + tile_bs%rs(i,j) = tile_bs%rs_min(i,j) / f2b(i,j) + tile_ws%rs(i,j) = 0. + + rs_co2 = 1. / gcco2 + + ! NOTE:Combined assimilation for low and high veg, using only low veg as vegetation type: + resp_co2(i,j) = R10 * (1.-fw) * exp(Eact0 / (283.15 * 8.314) * (1.0 - 283.15 / t_mean)) + ! Scale with vegetation fraction, and translate to `ppb m s-1`. + resp_co2(i,j) = cveg * resp_co2(i,j) * to_ppb + + ! Calculate net assimilation + ! NOTE: this uses the aerodynamic resistance from low vegetation... + an_co2(i,j) = -(co2_abs - ci) / (tile_lv%ra(i,j) + rs_co2) + ! Scale with vegetation + bare soil fraction, and translate to `ppb m s-1`. + an_co2(i,j) = cland * an_co2(i,j) * to_ppb + + ! Set CO2 fluxes: + if (l_emission) then + ! Total flux into the CO2 field holding the sum of all CO2 concentrations: + svflux(i,j,svco2sum) = resp_co2(i,j) + an_co2(i,j) + ! Respiration flux into the respiration specific field: + svflux(i,j,svco2ags) = resp_co2(i,j) + ! Net update into the vegetation specific field: + svflux(i,j,svco2veg) = an_co2(i,j) + else + svflux(i,j,co2_index) = resp_co2(i,j) + an_co2(i,j) + endif + + end do + end do + +end subroutine calc_canopy_resistance_ags + +! +! Calculate Obukhov length, ustar, and aerodynamic resistance, for all tiles +! +subroutine calc_stability + use modglobal, only : i1, j1, cu, cv, rv, rd + use modfields, only : u0, v0, thl0, qt0 + implicit none + + real, parameter :: du_min = 0.1 + real :: du, dv + integer :: i, j + + ! Calculate properties shared by all tiles: + ! Absolute wind speed difference, and virtual potential temperature atmosphere + do j=2,j1 + do i=2,i1 + du = 0.5*(u0(i,j,1) + u0(i+1,j,1)) + cu + dv = 0.5*(v0(i,j,1) + v0(i,j+1,1)) + cv + du_tot(i,j) = max(0.1, sqrt(du**2 + dv**2)) + + thv_1(i,j) = thl0(i,j,1) * (1.+(rv/rd-1.)*qt0(i,j,1)) + end do + end do + + call calc_obuk_ustar_ra(tile_lv) + call calc_obuk_ustar_ra(tile_hv) + call calc_obuk_ustar_ra(tile_bs) + call calc_obuk_ustar_ra(tile_ws) + call calc_obuk_ustar_ra(tile_aq) + +end subroutine calc_stability + +! +! Calculate Obukhov length and ustar, for single tile +! +subroutine calc_obuk_ustar_ra(tile) + use modglobal, only : i1, j1, rd, rv, grav, zf + use modfields, only : u0, v0 + implicit none + + type(lsm_tile), intent(inout) :: tile + integer :: i, j + real :: thvs + + do j=2,j1 + do i=2,i1 + if (tile%frac(i,j) > 0) then + ! Buoyancy difference surface - atmosphere + thvs = tile%thlskin(i,j) * (1.+(rv/rd-1.)*tile%qtskin(i,j)) + !tile%db(i,j) = grav/thvs * (thvs - thv_1(i,j)) + tile%db(i,j) = grav/thvs * (thv_1(i,j) - thvs) + + ! Iteratively find Obukhov length + tile%obuk(i,j) = calc_obuk_dirichlet( & + tile%obuk(i,j), du_tot(i,j), tile%db(i,j), zf(1), tile%z0m(i,j), tile%z0h(i,j)) + end if + end do + end do + + do j=2,j1 + do i=2,i1 + if (tile%frac(i,j) > 0) then + ! Calculate friction velocity and aerodynamic resistance + tile%ustar(i,j) = du_tot(i,j) * fm(zf(1), tile%z0m(i,j), tile%obuk(i,j)) + tile%ra(i,j) = 1./(tile%ustar(i,j) * fh(zf(1), tile%z0h(i,j), tile%obuk(i,j))) + end if + end do + end do + +end subroutine calc_obuk_ustar_ra + +! +! Calculate surface temperature for each tile, and calculate +! surface fluxes (H, LE, G0, wthl, wqt) and values (thlskin, qtskin) +! +subroutine calc_tile_bcs(tile) + use modglobal, only : i1, j1, cp, rlv, boltz + use modfields, only : exnh, exnf, presh, thl0, qt0, rhof + use modraddata, only : swd, swu, lwd, lwu + use modsurfdata, only : ps, tsoil + implicit none + + type(lsm_tile), intent(inout) :: tile + integer :: i, j + real :: Ts, thvs, esats, qsats, desatdTs, dqsatdTs, & + rs_lim, fH, fLE, fG, num, denom, Ta, qsat_new, & + rhocp_i, rholv_i, Qnet + + rhocp_i = 1. / (rhof(1) * cp) + rholv_i = 1. / (rhof(1) * rlv) + + ! tmp + !real :: HLEG, lwu_new, Qnet_new + + do j=2, j1 + do i=2, i1 + if (tile%frac(i,j) > 0) then + + ! Disable canopy resistance in case of dew fall + Ts = tile%thlskin(i,j) * exnh(1) + esats = 0.611e3 * exp(17.2694 * (Ts - 273.16) / (Ts - 35.86)) + qsats = 0.622 * esats / ps + + if (qsats < qt0(i,j,1)) then + rs_lim = 0. + else + rs_lim = tile%rs(i,j) + end if + + ! Calculate recuring terms + ! NOTE: this should use the surface density, not rhof(1). + ! Not sure if rhoh is available somewhere... + fH = rhof(1) * cp / tile%ra(i,j) + fLE = rhof(1) * rlv / (tile%ra(i,j) + rs_lim) + + if (tile%db(i,j) > 0) then + fG = tile%lambda_stable(i,j) + else + fG = tile%lambda_unstable(i,j) + end if + + ! Net radiation; negative sign = net input of radiation at surface + Qnet = swd(i,j,1) + swu(i,j,1) + lwd(i,j,1) + lwu(i,j,1) + + ! Solve new skin temperature from SEB + desatdTs = esats * (17.2694 / (Ts - 35.86) - 17.2694 * (Ts - 273.16) / (Ts - 35.86)**2.) + dqsatdTs = 0.622 * desatdTs / ps + Ta = thl0(i,j,1) * exnf(1) + + num = -(Qnet - lwu(i,j,1) & + - fH * Ta + (qsats - dqsatdTs * Ts - qt0(i,j,1)) * fLE & + - fG * tsoil(i,j,kmax_soil) - 3.*boltz * Ts**4) + denom = (fH + fLE * dqsatdTs + fG + 4.*boltz * Ts**3) + tile%tskin(i,j) = num / denom + + ! Update qsat with linearised relation, to make sure that the SEB closes + qsat_new = qsats + dqsatdTs * (tile%tskin(i,j) - Ts) + + ! Calculate energetic surface fluxes + tile%H (i,j) = fH * (tile%tskin(i,j) - Ta) + tile%LE(i,j) = fLE * (qsat_new - qt0(i,j,1)) + tile%G (i,j) = fG * (tsoil(i,j,kmax_soil) - tile%tskin(i,j)) + + ! Calculate kinematic surface fluxes + tile%wthl(i,j) = tile%H (i,j) * rhocp_i + tile%wqt (i,j) = tile%LE(i,j) * rholv_i + + ! Calculate surface values + tile%thlskin(i,j) = thl0(i,j,1) + tile%wthl(i,j) * tile%ra(i,j) + tile%qtskin (i,j) = qt0 (i,j,1) + tile%wqt (i,j) * tile%ra(i,j) + end if + end do + end do + +end subroutine calc_tile_bcs + +! +! Calculate BCs and fluxes for the water tile +! +subroutine calc_water_bcs + use modglobal, only : i1, j1, cp, rlv + use modfields, only : exnh, thl0, qt0, rhof + use modsurfdata, only : ps + implicit none + + integer :: i, j + real :: esats + + do j=2, j1 + do i=2, i1 + if (tile_aq%frac(i,j) > 0) then + ! Calculate BCs. `tile_aq%tskin` is fixed in time (for now...) + tile_aq%thlskin(i,j) = tile_aq%tskin(i,j) / exnh(1) + esats = 0.611e3 * exp(17.2694 * (tile_aq%tskin(i,j) - 273.16) / (tile_aq%tskin(i,j) - 35.86)) + tile_aq%qtskin(i,j) = 0.622 * esats / ps + + ! Calculate kinematic fluxes + tile_aq%wthl(i,j) = 1./tile_aq%ra(i,j) * (tile_aq%thlskin(i,j) - thl0(i,j,1)) + tile_aq%wqt (i,j) = 1./tile_aq%ra(i,j) * (tile_aq%qtskin (i,j) - qt0 (i,j,1)) + + ! Calculate energetic fluxes + tile_aq%H (i,j) = tile_aq%wthl(i,j) * rhof(1) * cp + tile_aq%LE(i,j) = tile_aq%wqt (i,j) * rhof(1) * rlv + tile_aq%G (i,j) = 0. + end if + end do + end do + +end subroutine calc_water_bcs + +! +! Set grid point mean boundary conditions, as used by +! the diffusion scheme, thermodynamics, ... +! +subroutine calc_bulk_bcs + use modglobal, only : i1, j1, cp, rlv, fkar, zf, cu, cv, grav, rv, rd + use modfields, only : rhof, thl0, qt0, u0, v0, thvh + use modsurface, only : phim, phih + use modmpi, only : excjs + use modsurfdata, only : & + H, LE, G0, tskin, qskin, thlflux, qtflux, dthldz, dqtdz, & + dudz, dvdz, ustar, obl, cliq, ra, rsveg, rssoil + implicit none + + integer :: i, j + real :: rhocp_i, rholv_i, ucu, vcv, cveg, bflux + real, pointer :: ustar_3D(:,:,:) + + rhocp_i = 1. / (rhof(1) * cp) + rholv_i = 1. / (rhof(1) * rlv) + + ! Calculate surface temperature for each tile, and calculate + ! surface fluxes (H, LE, G0, wthl, wqt) and values (thlskin, qtskin) + call calc_tile_bcs(tile_lv) + call calc_tile_bcs(tile_hv) + call calc_tile_bcs(tile_bs) + call calc_tile_bcs(tile_ws) + call calc_water_bcs + + do j=2,j1 + do i=2,i1 + ! Calc grid point averaged quantities + H(i,j) = tile_lv%frac(i,j) * tile_lv%H(i,j) + & + tile_hv%frac(i,j) * tile_hv%H(i,j) + & + tile_bs%frac(i,j) * tile_bs%H(i,j) + & + tile_ws%frac(i,j) * tile_ws%H(i,j) + & + tile_aq%frac(i,j) * tile_aq%H(i,j) + + LE(i,j) = tile_lv%frac(i,j) * tile_lv%LE(i,j) + & + tile_hv%frac(i,j) * tile_hv%LE(i,j) + & + tile_bs%frac(i,j) * tile_bs%LE(i,j) + & + tile_ws%frac(i,j) * tile_ws%LE(i,j) + & + tile_aq%frac(i,j) * tile_aq%LE(i,j) + + G0(i,j) = tile_lv%frac(i,j) * tile_lv%G(i,j) + & + tile_hv%frac(i,j) * tile_hv%G(i,j) + & + tile_bs%frac(i,j) * tile_bs%G(i,j) + & + tile_ws%frac(i,j) * tile_ws%G(i,j) + & + tile_aq%frac(i,j) * tile_aq%G(i,j) + + ustar(i,j) = tile_lv%frac(i,j) * tile_lv%ustar(i,j) + & + tile_hv%frac(i,j) * tile_hv%ustar(i,j) + & + tile_bs%frac(i,j) * tile_bs%ustar(i,j) + & + tile_ws%frac(i,j) * tile_ws%ustar(i,j) + & + tile_aq%frac(i,j) * tile_aq%ustar(i,j) + + tskin(i,j) = tile_lv%frac(i,j) * tile_lv%thlskin(i,j) + & + tile_hv%frac(i,j) * tile_hv%thlskin(i,j) + & + tile_bs%frac(i,j) * tile_bs%thlskin(i,j) + & + tile_ws%frac(i,j) * tile_ws%thlskin(i,j) + & + tile_aq%frac(i,j) * tile_aq%thlskin(i,j) + + qskin(i,j) = tile_lv%frac(i,j) * tile_lv%qtskin(i,j) + & + tile_hv%frac(i,j) * tile_hv%qtskin(i,j) + & + tile_bs%frac(i,j) * tile_bs%qtskin(i,j) + & + tile_ws%frac(i,j) * tile_ws%qtskin(i,j) + & + tile_aq%frac(i,j) * tile_aq%qtskin(i,j) + + ! Kinematic surface fluxes + thlflux(i,j) = H(i,j) * rhocp_i + qtflux (i,j) = LE(i,j) * rholv_i + + ! Calculate mean Obukhov length from mean fluxes + bflux = grav/thvh(1) * (thlflux(i,j) * (1.-(1.-rv/rd)*qskin(i,j)) - & + (1.-rv/rd)*tskin(i,j)*qtflux(i,j)) + obl(i,j) = -ustar(i,j)**3 / (fkar * bflux) + + ! MO gradients + dthldz(i,j) = -thlflux(i,j) / (fkar * zf(1) * ustar(i,j)) * phih(zf(1)/obl(i,j)) + dqtdz (i,j) = -qtflux (i,j) / (fkar * zf(1) * ustar(i,j)) * phih(zf(1)/obl(i,j)) + + ! NOTE: dudz, dvdz are at the grid center (full level), not the velocity locations. + ucu = 0.5*(u0(i,j,1) + u0(i+1,j,1))+cu + vcv = 0.5*(v0(i,j,1) + v0(i,j+1,1))+cv + + dudz(i,j) = ustar(i,j) / (fkar * zf(1)) * phim(zf(1)/obl(i,j)) * (ucu/du_tot(i,j)) + dvdz(i,j) = ustar(i,j) / (fkar * zf(1)) * phim(zf(1)/obl(i,j)) * (vcv/du_tot(i,j)) + + ! Cyclic BCs where needed. + !call excjs(ustar,2,i1,2,j1,1,1,1,1) + ustar_3D(1:size(ustar,1),1:size(ustar,2),1:1) => ustar + call excjs(ustar_3D,2,i1,2,j1,1,1,1,1) + + ! Just for diagnostics (modlsmcrosssection) + cliq(i,j) = tile_ws%frac(i,j) / land_frac(i,j) + + ! Calculate ra consistent with mean flux and temperature difference: + ra(i,j) = (tskin(i,j) - thl0(i,j,1)) / thlflux(i,j) + + cveg = tile_lv%frac(i,j) + tile_hv%frac(i,j) + if (cveg > 0) then + rsveg(i,j) = (tile_lv%frac(i,j) * tile_lv%rs(i,j) + & + tile_hv%frac(i,j) * tile_hv%rs(i,j)) / cveg + else + rsveg(i,j) = 0. + end if + + if (tile_bs%frac(i,j) > 0) then + rssoil(i,j) = tile_bs%rs(i,j) + else + rssoil(i,j) = 0. + end if + + end do + end do + +end subroutine calc_bulk_bcs + +! +! Interpolation soil from full to half levels, +! using various interpolation methods +! +subroutine interpolate_soil(fieldh, field, iinterp) + use modglobal, only : i1, j1 + implicit none + real, intent(inout) :: fieldh(:,:,:) + real, intent(in) :: field(:,:,:) + integer, intent(in) :: iinterp + integer i, j, k + + if (iinterp == iinterp_amean) then + do k=2,kmax_soil + do j=2,j1 + do i=2,i1 + fieldh(i,j,k) = 0.5*(field(i,j,k-1) + field(i,j,k)) + end do + end do + end do + else if (iinterp == iinterp_gmean) then + do k=2,kmax_soil + do j=2,j1 + do i=2,i1 + fieldh(i,j,k) = sqrt(field(i,j,k-1) * field(i,j,k)) + end do + end do + end do + else if (iinterp == iinterp_hmean) then + do k=2,kmax_soil + do j=2,j1 + do i=2,i1 + fieldh(i,j,k) = ((dz_soil(k-1)+dz_soil(k))*field(i,j,k-1)*field(i,j,k)) / & + (dz_soil(k-1)*field(i,j,k) + dz_soil(k)*field(i,j,k-1)) + end do + end do + end do + else if (iinterp == iinterp_max) then + do k=2,kmax_soil + do j=2,j1 + do i=2,i1 + fieldh(i,j,k) = max(field(i,j,k-1), field(i,j,k)) + end do + end do + end do + else + print*,'ERROR: invalid soil interpolation type iinterp=',iinterp + stop + end if + +end subroutine interpolate_soil + +! +! Calculate temperature diffusivity soil at full and half levels +! +subroutine calc_thermal_properties + use modglobal, only : i1, j1, gamma_t_matrix, gamma_t_water + use modsurfdata, only : lambda, lambdah, phiw + implicit none + + integer :: i, j, k, si + real :: lambda_t_sat, kersten, gamma_t + + ! Calculate diffusivity heat + do k=1,kmax_soil + do j=2,j1 + do i=2,i1 + si = soil_index(i,j,k) + + ! Heat conductivity at saturation (from IFS code..) + lambda_T_sat = gamma_T_matrix**(1.-theta_sat(si)) & + * gamma_T_water**phiw(i,j,k) & + * 2.2**(theta_sat(si) - phiw(i,j,k)) + + ! Kersten number for fine soils [IFS eq 8.64] (-) + kersten = log10(max(0.1, phiw(i,j,k) / theta_sat(si))) + 1. + + ! Heat conductivity soil [IFS eq 8.62] (W m-1 K-1) + gamma_t = kersten * (lambda_T_sat - gamma_t_dry(si)) + gamma_t_dry(si) + + ! Heat diffusivity (m2 s-1) + lambda(i,j,k) = gamma_t / rho_C(si) + end do + end do + end do + + ! Interpolate to half levels + call interpolate_soil(lambdah, lambda, iinterp_t) + +end subroutine calc_thermal_properties + +! +! Calculate soil moisture diffusivity and conductivity, +! using van Genuchten parameterisation. +! +subroutine calc_hydraulic_properties + use modglobal, only : i1, j1 + use modsurfdata, only : phiw, lambdas, lambdash, gammas, gammash + implicit none + + integer :: i, j, k, si + real :: theta_lim, theta_norm + + ! Calculate diffusivity and conductivity soil moisture + do k=1,kmax_soil + do j=2,j1 + do i=2,i1 + si = soil_index(i,j,k) + + ! Limit soil moisture just above the residual soil moisture content + theta_lim = max(phiw(i,j,k), 1.001*theta_res(si)) + + ! Dimensionless soil water content + theta_norm = (theta_lim - theta_res(si)) / (theta_sat(si) - theta_res(si)) + + ! Calculate & limit the diffusivity + lambdas(i,j,k) = calc_diffusivity_vg( & + theta_norm, vg_a(si), vg_l(si), vg_m(si), & + gamma_theta_sat(si), theta_sat(si), theta_res(si)) + lambdas(i,j,k) = max(min(lambdas(i,j,k), lambda_theta_max(si)), lambda_theta_min(si)) + + ! Calculate & limit the conductivity + gammas(i,j,k) = calc_conductivity_vg( & + theta_norm, vg_l(si), vg_m(si), gamma_theta_sat(si)) + gammas(i,j,k) = max(min(gammas(i,j,k), gamma_theta_max(si)), gamma_theta_min(si)) + end do + end do + end do + + ! Interpolate to half levels + call interpolate_soil(lambdash, lambdas, iinterp_theta) + call interpolate_soil(gammash, gammas, iinterp_theta) + + ! Optionally, set free drainage bottom BC + if (lfreedrainage) then + gammash(:,:,1) = gammash(:,:,2) + else + gammash(:,:,1) = 0. + end if + +end subroutine calc_hydraulic_properties + +! +! Calculate soil moisture tendency from root water extraction +! +subroutine calc_root_water_extraction + use modglobal, only : i1, j1, rhow, rlv + use modsurfdata, only : phiw + implicit none + + integer :: i, j, k + real :: phiw_rf_lv, phiw_rf_hv, phi_frac_lv, phi_frac_hv, LE_lv, LE_hv + real, parameter :: fac = 1./(rhow * rlv) + + do j=2, j1 + do i=2, i1 + + LE_lv = tile_lv%frac(i,j) * tile_lv%LE(i,j) + LE_hv = tile_hv%frac(i,j) * tile_hv%LE(i,j) + + phiw_rf_lv = 0. + phiw_rf_hv = 0. + do k=1, kmax_soil + phiw_rf_lv = phiw_rf_lv + tile_lv%root_frac(i,j,k) * phiw(i,j,k) + phiw_rf_hv = phiw_rf_hv + tile_hv%root_frac(i,j,k) * phiw(i,j,k) + end do + + do k=1, kmax_soil + phi_frac_lv = tile_lv%root_frac(i,j,k) * phiw(i,j,k) / phiw_rf_lv + phi_frac_hv = tile_hv%root_frac(i,j,k) * phiw(i,j,k) / phiw_rf_hv + + phiw_source(i,j,k) = -max(0., LE_lv) * fac * dzi_soil(k) * phi_frac_lv & + -max(0., LE_hv) * fac * dzi_soil(k) * phi_frac_hv + end do + end do + end do + +end subroutine calc_root_water_extraction + +! +! Solve diffusion equation (explicit) for soil temperature. +! Top flux = G0/rho_C, bottom flux = zero. +! +subroutine integrate_t_soil + use modglobal, only : rk3step, rdt, i1, j1 + use modsurfdata, only : tsoil, tsoilm, lambdah, G0 + + implicit none + integer :: i, j, k, si + real :: tend, rk3coef, flux_top + + rk3coef = rdt / (4. - dble(rk3step)) + if(rk3step == 1) tsoilm(:,:,:) = tsoil(:,:,:) + + ! Top soil layer + k = kmax_soil + do j=2,j1 + do i=2,i1 + si = soil_index(i,j,k) + flux_top = G0(i,j) / rho_C(si) + tend = (-flux_top - (lambdah(i,j,k) * (tsoil(i,j,k) - tsoil(i,j,k-1)) * dzhi_soil(k)))*dzi_soil(k) + + tsoil(i,j,k) = tsoilm(i,j,k) + rk3coef * tend + end do + end do + + ! Bottom soil layer + k = 1 + do j=2,j1 + do i=2,i1 + tend = ((lambdah(i,j,k+1) * (tsoil(i,j,k+1) - tsoil(i,j,k)) * dzhi_soil(k+1)))*dzi_soil(k) + + tsoil(i,j,k) = tsoilm(i,j,k) + rk3coef * tend + end do + end do + + ! Interior + do k=2,kmax_soil-1 + do j=2,j1 + do i=2,i1 + tend = ((lambdah(i,j,k+1) * (tsoil(i,j,k+1) - tsoil(i,j,k )) * dzhi_soil(k+1)) & + - (lambdah(i,j,k ) * (tsoil(i,j,k ) - tsoil(i,j,k-1)) * dzhi_soil(k ))) * dzi_soil(k) + + tsoil(i,j,k) = tsoilm(i,j,k) + rk3coef * tend + end do + end do + end do + +end subroutine integrate_t_soil + +! +! Solve diffusion equation (explicit) for soil moisture, +! including a source term for root water extraction. +! +subroutine integrate_theta_soil + use modglobal, only : rk3step, rdt, i1, j1, rhow, rlv + use modsurfdata, only : phiw, phiwm, lambdash, gammash + + implicit none + integer :: i, j, k, si + real :: tend, rk3coef, flux_top, fac + + rk3coef = rdt / (4. - dble(rk3step)) + if(rk3step == 1) phiwm(:,:,:) = phiw(:,:,:) + + fac = 1./(rhow * rlv) + + ! Top soil layer + k = kmax_soil + do j=2,j1 + do i=2,i1 + flux_top = tile_bs%frac(i,j) * tile_bs%LE(i,j) * fac + throughfall(i,j) + tend = (-flux_top - (lambdash(i,j,k) * (phiw(i,j,k) - phiw(i,j,k-1)) * dzhi_soil(k)))*dzi_soil(k) & + - gammash(i,j,k) * dzi_soil(k) + phiw_source(i,j,k) + + phiw(i,j,k) = phiwm(i,j,k) + rk3coef * tend + end do + end do + + ! Bottom soil layer + k = 1 + do j=2,j1 + do i=2,i1 + tend = ((lambdash(i,j,k+1) * (phiw(i,j,k+1) - phiw(i,j,k)) * dzhi_soil(k+1)))*dzi_soil(k) & + + (gammash(i,j,k+1) - gammash(i,j,k)) * dzi_soil(k) + phiw_source(i,j,k) + + phiw(i,j,k) = phiwm(i,j,k) + rk3coef * tend + end do + end do + + ! Interior + do k=2,kmax_soil-1 + do j=2,j1 + do i=2,i1 + tend = ((lambdash(i,j,k+1) * (phiw(i,j,k+1) - phiw(i,j,k )) * dzhi_soil(k+1)) & + - (lambdash(i,j,k ) * (phiw(i,j,k ) - phiw(i,j,k-1)) * dzhi_soil(k ))) * dzi_soil(k) & + + (gammash(i,j,k+1) - gammash(i,j,k)) * dzi_soil(k) + phiw_source(i,j,k) + + phiw(i,j,k) = phiwm(i,j,k) + rk3coef * tend + end do + end do + end do + +end subroutine integrate_theta_soil + +! +! Initialise the land-surface model +! +subroutine initlsm + use modglobal, only : ifnamopt, fname_options, checknamelisterror, lwarmstart + use modmpi, only : myid, comm3d, mpierr, D_MPI_BCAST + use modsurfdata, only : isurf + use modemisdata, only : l_emission + implicit none + + integer :: ierr + logical :: lheterogeneous + + ! Namelist definition + namelist /NAMLSM/ & + lheterogeneous, lfreedrainage, lags, dz_soil, iinterp_t, iinterp_theta, co2_index + + llsm = (isurf == 11) + + if (llsm) then + + allocate(dz_soil(kmax_soil)) + + ! Read namelist + if (myid == 0) then + open(ifnamopt, file=fname_options, status='old', iostat=ierr) + read(ifnamopt, NAMLSM, iostat=ierr) + call checknamelisterror(ierr, ifnamopt, 'NAMLSM') + write(6, NAMLSM) + close(ifnamopt) + end if + + ! Broadcast namelist values to all MPI tasks + call D_MPI_BCAST(lheterogeneous, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lfreedrainage, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lags, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(iinterp_t, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(iinterp_theta, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(co2_index, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(dz_soil, kmax_soil, 0, comm3d, mpierr) + + ! Checks on input + if (lags .and. .not. l_emission .and. (co2_index == -1)) then + print*,'With A-Gs enabled and without the emission module, the `co2_index`' + print*,'in the scalar array should be specified in the `NAMLSM` group.' + stop + endif + + ! Create/calculate soil grid properties + call create_soil_grid + + ! Allocate required fields in modsurfacedata, and arrays / tiles from this module: + call allocate_fields + + if (lheterogeneous) then + ! Initialise heterogeneous LSM from external input + call init_heterogeneous + else + ! Initialise homogeneous LSM from namelist input + call init_homogeneous + end if + + ! Read the soil parameter table + call read_soil_table + + ! Calculate derived soil properties + call calc_soil_properties + + ! Calculate root fractions soil (low and high veg) + call calc_root_fractions + end if + +end subroutine initlsm + +! +! Cleanup (deallocate) the land-surface model +! +subroutine exitlsm + use modsurfdata, only : & + tsoil, tsoilm, phiw, phiwm, & + lambda, lambdah, lambdas, lambdash, gammas, gammash, & + H, LE, G0, Qnet, wl, wlm, & + tendskin, cliq, rsveg, rssoil + implicit none + + if (.not. llsm) return + + ! Allocated from `read_soil_table`: + deallocate( theta_res, theta_wp, theta_fc, theta_sat, gamma_theta_sat, vg_a, vg_l, vg_n ) + + ! Allocated from `allocate_fields`: + deallocate( soil_index, tsoil, tsoilm, phiw, phiwm, phiw_source ) + deallocate( wl, wlm, wl_max ) + deallocate( throughfall, interception ) + deallocate( gD, f1, f2_lv, f2_hv, f2b, f3 ) + deallocate( du_tot, thv_1, land_frac ) + deallocate( lambda, lambdah, lambdas, lambdash, gammas, gammash ) + deallocate( Qnet, H, LE, G0 ) + deallocate( tendskin, cliq, rsveg, rssoil ) + + ! Allocated from `create_soil_grid`: + deallocate( z_soil, dz_soil, dzi_soil, zh_soil, dzh_soil, dzhi_soil ) + + if (lags) deallocate(an_co2, resp_co2) + + ! Tiles, allocated from `allocate_tile`: + call deallocate_tile(tile_lv) + call deallocate_tile(tile_hv) + call deallocate_tile(tile_bs) + call deallocate_tile(tile_ws) + call deallocate_tile(tile_aq) + +end subroutine exitlsm + +! +! Calculate soil grid properties +! +subroutine create_soil_grid + implicit none + integer :: k + + allocate(z_soil (kmax_soil )) + allocate(dzi_soil (kmax_soil )) + allocate(zh_soil (kmax_soil+1)) + allocate(dzh_soil (kmax_soil+1)) + allocate(dzhi_soil(kmax_soil+1)) + + ! + ! Full level is in the middle of two half levels + ! + z_size_soil = -sum(dz_soil) + + ! Half level height + zh_soil(1) = z_size_soil + zh_soil(kmax_soil+1) = 0. + do k=2, kmax_soil + zh_soil(k) = zh_soil(k-1) + dz_soil(k-1) + end do + + ! Full level height + do k=1, kmax_soil + z_soil(k) = 0.5*(zh_soil(k) + zh_soil(k+1)) + end do + + do k=2, kmax_soil + dzh_soil(k) = z_soil(k) - z_soil(k-1) + end do + + dzh_soil(1) = 2*(z_soil(1) - zh_soil(1)) + dzh_soil(kmax_soil+1) = 2*(-z_soil(kmax_soil)) + + ! Inverse grid spacings + dzi_soil(:) = 1./dz_soil(:) + dzhi_soil(:) = 1./dzh_soil(:) + + ! + ! Half level is in the middle of two full levels + ! + !! Half level heights + !zh_soil(1) = z_size_soil + !zh_soil(kmax_soil+1) = 0. + + !do k=2, kmax_soil + ! zh_soil(k) = 0.5*(z_soil(k) + z_soil(k-1)) + !end do + + !! Grid spacing full and half levels + !do k=1, kmax_soil + ! dz_soil(k) = zh_soil(k+1) - zh_soil(k) + !end do + + !do k=2, kmax_soil + ! dzh_soil(k) = z_soil(k) - z_soil(k-1) + !end do + + !dzh_soil(1) = 2*(z_soil(1) - zh_soil(1)) + !dzh_soil(kmax_soil+1) = 2*(-z_soil(kmax_soil)) + + !! Inverse grid spacings + !dzi_soil(:) = 1./dz_soil(:) + !dzhi_soil(:) = 1./dzh_soil(:) + +end subroutine create_soil_grid + +! +! Allocate all LSM fields +! +subroutine allocate_fields + use modglobal, only : i2, j2 + use modsurfdata, only : & + tsoil, tsoilm, phiw, phiwm, & + lambda, lambdah, lambdas, lambdash, gammas, gammash, & + H, LE, G0, Qnet, wl, wlm, & + tendskin, cliq, rsveg, rssoil + implicit none + + ! Allocate soil variables + allocate(soil_index(i2, j2, kmax_soil)) + + allocate(tsoil (i2, j2, kmax_soil)) + allocate(tsoilm(i2, j2, kmax_soil)) + allocate(phiw (i2, j2, kmax_soil)) + allocate(phiwm (i2, j2, kmax_soil)) + + allocate(wl (i2, j2)) + allocate(wlm(i2, j2)) + + allocate(wl_max(i2, j2)) + + allocate(phiw_source(i2, j2, kmax_soil)) + + allocate(throughfall(i2, j2)) + allocate(interception(i2, j2)) + + allocate(gD(i2, j2)) + allocate(f1(i2, j2)) + allocate(f2_lv(i2, j2)) + allocate(f2_hv(i2, j2)) + allocate(f2b(i2, j2)) + allocate(f3(i2, j2)) + + allocate(du_tot(i2, j2)) + allocate(thv_1(i2, j2)) + allocate(land_frac(i2, j2)) + + ! NOTE: names differ from what is described in modsurfdata! + ! Diffusivity temperature: + allocate(lambda (i2, j2, kmax_soil )) + allocate(lambdah(i2, j2, kmax_soil+1)) + + ! Diffusivity theta: + allocate(lambdas (i2, j2, kmax_soil)) + allocate(lambdash(i2, j2, kmax_soil+1)) + + ! Conductivity theta: + allocate(gammas (i2, j2, kmax_soil)) + allocate(gammash(i2, j2, kmax_soil+1)) + + ! Tile averaged surface fluxes + allocate(Qnet (i2, j2)) + allocate(H (i2, j2)) + allocate(LE (i2, j2)) + allocate(G0 (i2, j2)) + + ! TMP, to prevent segfaults in modlsmcrosssection.f90 + allocate(tendskin(i2, j2)) + allocate(cliq(i2, j2)) + allocate(rsveg(i2, j2)) + allocate(rssoil(i2, j2)) + + ! A-Gs + if (lags) then + allocate(an_co2(i2, j2)) + allocate(resp_co2(i2, j2)) + endif + + ! Allocate the tiled variables + call allocate_tile(tile_lv) + call allocate_tile(tile_hv) + call allocate_tile(tile_bs) + call allocate_tile(tile_ws) + call allocate_tile(tile_aq) + +end subroutine allocate_fields + +! +! Allocate all fields of a LSM tile +! +subroutine allocate_tile(tile) + use modglobal, only : i2, j2 + implicit none + type(lsm_tile), intent(inout) :: tile + + ! Static properties: + allocate(tile % z0m(i2, j2)) + allocate(tile % z0h(i2, j2)) + + ! Base and dynamic tile fraction: + allocate(tile % base_frac(i2, j2)) + allocate(tile % frac(i2, j2)) + + ! Monin-obukhov / surface layer: + allocate(tile % obuk(i2, j2)) + allocate(tile % ustar(i2, j2)) + allocate(tile % ra(i2, j2)) + + ! Conductivity skin layer: + allocate(tile % lambda_stable(i2, j2)) + allocate(tile % lambda_unstable(i2, j2)) + + ! Surface fluxes: + allocate(tile % H(i2, j2)) + allocate(tile % LE(i2, j2)) + allocate(tile % G(i2, j2)) + allocate(tile % wthl(i2, j2)) + allocate(tile % wqt(i2, j2)) + + ! Surface temperature and humidity: + allocate(tile % tskin(i2, j2)) + allocate(tile % thlskin(i2, j2)) + allocate(tile % qtskin(i2, j2)) + + ! Buoyancy difference surface-atmosphere + allocate(tile % db(i2, j2)) + + ! Vegetation properties: + allocate(tile % lai(i2, j2)) + allocate(tile % rs_min(i2, j2)) + allocate(tile % rs(i2, j2)) + + ! Root fraction in soil: + allocate(tile % a_r(i2, j2)) + allocate(tile % b_r(i2, j2)) + allocate(tile % root_frac(i2, j2, kmax_soil)) + + ! Root fraction weigthed mean soil water content + allocate(tile % phiw_mean(i2, j2)) + +end subroutine allocate_tile + +! +! Deallocate all fields of a LSM tile +! +subroutine deallocate_tile(tile) + implicit none + type(lsm_tile), intent(inout) :: tile + + deallocate( tile%z0m, tile%z0h, tile%base_frac, tile%frac ) + deallocate( tile%obuk, tile%ustar, tile%ra ) + deallocate( tile%lambda_stable, tile%lambda_unstable ) + deallocate( tile%H, tile%LE, tile%G, tile%wthl, tile%wqt) + deallocate( tile%tskin, tile%thlskin, tile%qtskin) + deallocate( tile%db, tile%lai, tile%rs_min, tile%rs ) + deallocate( tile%a_r, tile%b_r, tile%root_frac, tile%phiw_mean ) + +end subroutine deallocate_tile + +! +! Init some of the tiled variables, in case of cold start. +! Called from modstartup -> readinitfiles() +! +subroutine init_lsm_tiles + use modfields, only : thlprof, qtprof + implicit none + + tile_lv % thlskin(:,:) = thlprof(1) + tile_lv % qtskin (:,:) = qtprof(1) + tile_lv % obuk (:,:) = -0.1 + + tile_hv % thlskin(:,:) = thlprof(1) + tile_hv % qtskin (:,:) = qtprof(1) + tile_hv % obuk (:,:) = -0.1 + + tile_bs % thlskin(:,:) = thlprof(1) + tile_bs % qtskin (:,:) = qtprof(1) + tile_bs % obuk (:,:) = -0.1 + + tile_ws % thlskin(:,:) = thlprof(1) + tile_ws % qtskin (:,:) = qtprof(1) + tile_ws % obuk (:,:) = -0.1 + + tile_aq % thlskin(:,:) = thlprof(1) + tile_aq % qtskin (:,:) = qtprof(1) + tile_aq % obuk (:,:) = -0.1 + +end subroutine init_lsm_tiles + +! +! Initialise the LSM homogeneous from namelist input +! +subroutine init_homogeneous + use modglobal, only : ifnamopt, fname_options, checknamelisterror, lwarmstart, eps1 + use modmpi, only : myid, comm3d, mpierr, D_MPI_BCAST + use modsurfdata, only : tsoil, tsoilm, phiw, phiwm, wl, wlm, wmax + implicit none + + integer :: ierr, k + real :: c_low, c_high, c_bare, c_water + real :: z0m_low, z0m_high, z0m_bare, z0m_water + real :: z0h_low, z0h_high, z0h_bare, z0h_water + real :: lambda_s_low, lambda_s_high, lambda_s_bare + real :: lambda_us_low, lambda_us_high, lambda_us_bare + real :: lai_low, lai_high + real :: rs_min_low, rs_min_high, rs_min_bare + real :: ar_low, br_low, ar_high, br_high + real :: gD_high + real :: tskin_water + + real, allocatable :: t_soil_p(:), theta_soil_p(:) + integer, allocatable :: soil_index_p(:) + + ! Read namelist + namelist /NAMLSM_HOMOGENEOUS/ & + c_low, c_high, c_bare, c_water, & + z0m_low, z0m_high, z0m_bare, z0m_water, & + z0h_low, z0h_high, z0h_bare, z0h_water, & + lambda_s_low, lambda_s_high, lambda_s_bare, & + lambda_us_low, lambda_us_high, lambda_us_bare, & + lai_low, lai_high, & + rs_min_low, rs_min_high, rs_min_bare, & + t_soil_p, theta_soil_p, soil_index_p, & + ar_low, br_low, ar_high, br_high, & + gD_high, tskin_water + + allocate(t_soil_p(kmax_soil), theta_soil_p(kmax_soil)) + allocate(soil_index_p(kmax_soil)) + + if (myid == 0) then + open(ifnamopt, file=fname_options, status='old', iostat=ierr) + read(ifnamopt, NAMLSM_HOMOGENEOUS, iostat=ierr) + call checknamelisterror(ierr, ifnamopt, 'NAMLSM_HOMOGENEOUS') + write(6, NAMLSM_HOMOGENEOUS) + close(ifnamopt) + end if + + ! Broadcast to all MPI tasks + call D_MPI_BCAST(c_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(c_high, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(c_bare, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(c_water, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(z0m_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(z0m_high, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(z0m_bare, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(z0m_water, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(z0h_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(z0h_high, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(z0h_bare, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(z0h_water, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(lambda_s_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lambda_s_high, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lambda_s_bare, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(lambda_us_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lambda_us_high, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lambda_us_bare, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(lai_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lai_high, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(rs_min_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(rs_min_high, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(rs_min_bare, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(t_soil_p, kmax_soil, 0, comm3d, mpierr) + call D_MPI_BCAST(theta_soil_p, kmax_soil, 0, comm3d, mpierr) + call D_MPI_BCAST(soil_index_p, kmax_soil, 0, comm3d, mpierr) + + call D_MPI_BCAST(ar_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(br_low, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(ar_high, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(br_high, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(gD_high, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(tskin_water, 1, 0, comm3d, mpierr) + + ! Set values + tile_lv % base_frac(:,:) = c_low + tile_hv % base_frac(:,:) = c_high + tile_bs % base_frac(:,:) = c_bare + tile_aq % base_frac(:,:) = c_water + tile_ws % base_frac(:,:) = 0. + + tile_lv % z0m(:,:) = z0m_low + tile_hv % z0m(:,:) = z0m_high + tile_bs % z0m(:,:) = z0m_bare + tile_aq % z0m(:,:) = z0m_water + + tile_lv % z0h(:,:) = z0h_low + tile_hv % z0h(:,:) = z0h_high + tile_bs % z0h(:,:) = z0h_bare + tile_aq % z0h(:,:) = z0h_water + + tile_lv % lambda_stable(:,:) = lambda_s_low + tile_hv % lambda_stable(:,:) = lambda_s_high + tile_bs % lambda_stable(:,:) = lambda_s_bare + + tile_lv % lambda_unstable(:,:) = lambda_us_low + tile_hv % lambda_unstable(:,:) = lambda_us_high + tile_bs % lambda_unstable(:,:) = lambda_us_bare + + tile_lv % lai(:,:) = lai_low + tile_hv % lai(:,:) = lai_high + + tile_lv % rs_min(:,:) = rs_min_low + tile_hv % rs_min(:,:) = rs_min_high + tile_bs % rs_min(:,:) = rs_min_bare + + tile_lv % a_r(:,:) = ar_low + tile_lv % b_r(:,:) = br_low + tile_hv % a_r(:,:) = ar_high + tile_hv % b_r(:,:) = br_high + + gD(:,:) = gD_high + + tile_aq % tskin(:,:) = tskin_water + + + if (.not. lwarmstart) then + ! Init prognostic variables in case of cold start. + ! For a warm start, these are read from restartfiles + ! in modstartup. + do k=1, kmax_soil + tsoil(:,:,k) = t_soil_p(k) + phiw (:,:,k) = theta_soil_p(k) + end do + + tsoilm(:,:,:) = tsoil(:,:,:) + phiwm (:,:,:) = phiw (:,:,:) + + wl(:,:) = 0. + wlm(:,:) = 0. + end if + + do k=1, kmax_soil + soil_index(:,:,k) = soil_index_p(k) + end do + + ! Set properties wet skin tile + tile_ws % z0m(:,:) = c_low*z0m_low + c_high*z0m_high + c_bare*z0m_bare + tile_ws % z0h(:,:) = c_low*z0h_low + c_high*z0h_high + c_bare*z0h_bare + + tile_ws % lambda_stable(:,:) = c_low*lambda_s_low + c_high*lambda_s_high + c_bare*lambda_s_bare + tile_ws % lambda_unstable(:,:) = c_low*lambda_us_low + c_high*lambda_us_high + c_bare*lambda_us_bare + + ! Calculate land fraction, and limit to prevent div/0's + land_frac(:,:) = 1.-c_water + where (land_frac == 0) land_frac = eps1 + + ! Max liquid water per grid point, accounting for LAI + wl_max(:,:) = wmax * ( & + tile_lv%base_frac(:,:) * tile_lv%lai(:,:) + & + tile_hv%base_frac(:,:) * tile_hv%lai(:,:) + & + tile_bs%base_frac(:,:)) / land_frac(:,:) + where (wl_max == 0) wl_max = eps1 + + ! Cleanup! + deallocate(t_soil_p, theta_soil_p, soil_index_p) + +end subroutine init_homogeneous + +! +! Init LSM properties from external input +! +subroutine init_heterogeneous + use modglobal, only : i1, j1, lwarmstart, iexpnr, eps1 + use modmpi, only : myidx, myidy + use modsurfdata, only : tsoil, phiw, wl, wlm, wmax + implicit none + + character(20) :: input_file = 'lsm.inp.x___y___.___' + write(input_file(10:12), '(i3.3)') myidx + write(input_file(14:16), '(i3.3)') myidy + write(input_file(18:20), '(i3.3)') iexpnr + + open(666, file=input_file, form='unformatted', status='unknown', action='read', access='stream') + + ! 2D surface fields + read(666) tile_lv%base_frac(2:i1, 2:j1) + read(666) tile_hv%base_frac(2:i1, 2:j1) + read(666) tile_bs%base_frac(2:i1, 2:j1) + read(666) tile_aq%base_frac(2:i1, 2:j1) + + read(666) tile_lv%z0m(2:i1, 2:j1) + read(666) tile_hv%z0m(2:i1, 2:j1) + read(666) tile_bs%z0m(2:i1, 2:j1) + read(666) tile_aq%z0m(2:i1, 2:j1) + + read(666) tile_lv%z0h(2:i1, 2:j1) + read(666) tile_hv%z0h(2:i1, 2:j1) + read(666) tile_bs%z0h(2:i1, 2:j1) + read(666) tile_aq%z0h(2:i1, 2:j1) + + read(666) tile_lv%lambda_stable(2:i1, 2:j1) + read(666) tile_hv%lambda_stable(2:i1, 2:j1) + read(666) tile_bs%lambda_stable(2:i1, 2:j1) + + read(666) tile_lv%lambda_unstable(2:i1, 2:j1) + read(666) tile_hv%lambda_unstable(2:i1, 2:j1) + read(666) tile_bs%lambda_unstable(2:i1, 2:j1) + + read(666) tile_lv%lai(2:i1, 2:j1) + read(666) tile_hv%lai(2:i1, 2:j1) + + read(666) tile_lv%rs_min(2:i1, 2:j1) + read(666) tile_hv%rs_min(2:i1, 2:j1) + read(666) tile_bs%rs_min(2:i1, 2:j1) + + read(666) tile_lv%a_r(2:i1, 2:j1) + read(666) tile_lv%b_r(2:i1, 2:j1) + read(666) tile_hv%a_r(2:i1, 2:j1) + read(666) tile_hv%b_r(2:i1, 2:j1) + + read(666) gD(2:i1, 2:j1) + + read(666) tile_aq%tskin(2:i1, 2:j1) + + ! 3D soil fields + read(666) soil_index(2:i1, 2:j1, 1:kmax_soil) + + if (.not. lwarmstart) then + read(666) tsoil(2:i1, 2:j1, 1:kmax_soil) + read(666) phiw (2:i1, 2:j1, 1:kmax_soil) + + wl(:,:) = 0. + wlm(:,:) = 0. + end if + + close(666) + + ! Derived quantities + tile_ws%base_frac(:,:) = 0. + + ! Set properties wet skin tile + tile_ws%z0m(:,:) = & + tile_lv%base_frac(:,:)*tile_lv%z0m(:,:) + & + tile_hv%base_frac(:,:)*tile_lv%z0m(:,:) + & + tile_bs%base_frac(:,:)*tile_bs%z0m(:,:) + + tile_ws%z0h(:,:) = & + tile_lv%base_frac(:,:)*tile_lv%z0h(:,:) + & + tile_hv%base_frac(:,:)*tile_lv%z0h(:,:) + & + tile_bs%base_frac(:,:)*tile_bs%z0h(:,:) + + tile_ws%lambda_stable(:,:) = & + tile_lv%base_frac(:,:)*tile_lv%lambda_stable(:,:) + & + tile_hv%base_frac(:,:)*tile_lv%lambda_stable(:,:) + & + tile_bs%base_frac(:,:)*tile_bs%lambda_stable(:,:) + + tile_ws%lambda_unstable(:,:) = & + tile_lv%base_frac(:,:)*tile_lv%lambda_unstable(:,:) + & + tile_hv%base_frac(:,:)*tile_lv%lambda_unstable(:,:) + & + tile_bs%base_frac(:,:)*tile_bs%lambda_unstable(:,:) + + ! Calculate land fraction, and limit to prevent div/0's + land_frac(:,:) = 1.-tile_aq%base_frac(:,:) + where (land_frac == 0) land_frac = eps1 + + ! Max liquid water per grid point, accounting for LAI + wl_max(:,:) = wmax * ( & + tile_lv%base_frac(:,:) * tile_lv%lai(:,:) + & + tile_hv%base_frac(:,:) * tile_hv%lai(:,:) + & + tile_bs%base_frac(:,:)) / land_frac(:,:) + where (wl_max == 0) wl_max = eps1 + +end subroutine init_heterogeneous + +! +! Read the input table with the (van Genuchten) soil parameters +! +subroutine read_soil_table + use modmpi, only : myid, comm3d, mpierr, D_MPI_BCAST + implicit none + integer :: table_size, ncid, dimid, varid + + if (myid == 0) then + ! Open the NetCDF file and read the table size + print*,'Reading "van_genuchten_parameters.nc"' + call check( nf90_open('van_genuchten_parameters.nc', nf90_nowrite, ncid) ) + call check( nf90_inq_dimid(ncid, 'index', dimid) ) + call check( nf90_inquire_dimension(ncid, dimid, len=table_size) ) + end if + + call D_MPI_BCAST(table_size, 1, 0, comm3d, mpierr) + + ! Allocate variables + allocate( theta_res(table_size), theta_wp(table_size), theta_fc(table_size) ) + allocate( theta_sat(table_size), gamma_theta_sat(table_size) ) + allocate( vg_a(table_size), vg_l(table_size), vg_n(table_size) ) + + if (myid == 0) then + ! Read variables + call check( nf90_inq_varid(ncid, 'theta_res', varid) ) + call check( nf90_get_var(ncid, varid, theta_res) ) + + call check( nf90_inq_varid(ncid, 'theta_wp', varid) ) + call check( nf90_get_var(ncid, varid, theta_wp) ) + + call check( nf90_inq_varid(ncid, 'theta_fc', varid) ) + call check( nf90_get_var(ncid, varid, theta_fc) ) + + call check( nf90_inq_varid(ncid, 'theta_sat', varid) ) + call check( nf90_get_var(ncid, varid, theta_sat) ) + + call check( nf90_inq_varid(ncid, 'gamma_sat', varid) ) + call check( nf90_get_var(ncid, varid, gamma_theta_sat) ) + + call check( nf90_inq_varid(ncid, 'alpha', varid) ) + call check( nf90_get_var(ncid, varid, vg_a) ) + + call check( nf90_inq_varid(ncid, 'l', varid) ) + call check( nf90_get_var(ncid, varid, vg_l) ) + + call check( nf90_inq_varid(ncid, 'n', varid) ) + call check( nf90_get_var(ncid, varid, vg_n) ) + + call check( nf90_close(ncid) ) + end if + + ! Broadcast to other MPI tasks + call D_MPI_BCAST(theta_res, table_size, 0, comm3d, mpierr) + call D_MPI_BCAST(theta_wp, table_size, 0, comm3d, mpierr) + call D_MPI_BCAST(theta_fc, table_size, 0, comm3d, mpierr) + call D_MPI_BCAST(theta_sat, table_size, 0, comm3d, mpierr) + call D_MPI_BCAST(gamma_theta_sat, table_size, 0, comm3d, mpierr) + call D_MPI_BCAST(vg_a, table_size, 0, comm3d, mpierr) + call D_MPI_BCAST(vg_l, table_size, 0, comm3d, mpierr) + call D_MPI_BCAST(vg_n, table_size, 0, comm3d, mpierr) + +end subroutine read_soil_table + +! +! Calculate derived (tabulated) soil properties +! +subroutine calc_soil_properties + use modglobal, only : rho_solid_soil, rho_c_matrix, rho_c_water, eps1 + implicit none + + integer :: i, table_size + real :: theta_norm_min, theta_norm_max, rho_dry + + table_size = size(vg_n) + allocate(vg_m(table_size)) + allocate(lambda_theta_min(table_size)) + allocate(lambda_theta_max(table_size)) + allocate(gamma_theta_min(table_size)) + allocate(gamma_theta_max(table_size)) + allocate(gamma_t_dry(table_size)) + allocate(rho_C(table_size)) + + do i=1, table_size + ! van Genuchten parameter `m` + vg_m(i) = (1. - (1. / vg_n(i))) + + ! Min/max values diffusivity soil moisture + theta_norm_min = (1.001 * theta_res(i) - theta_res(i)) / (theta_sat(i) - theta_res(i)) + eps1 + theta_norm_max = (0.999 * theta_sat(i) - theta_res(i)) / (theta_sat(i) - theta_res(i)) + + lambda_theta_min(i) = calc_diffusivity_vg( & + theta_norm_min, vg_a(i), vg_l(i), vg_m(i), gamma_theta_sat(i), & + theta_sat(i), theta_res(i)) + lambda_theta_max(i) = calc_diffusivity_vg( & + theta_norm_max, vg_a(i), vg_l(i), vg_m(i), gamma_theta_sat(i), & + theta_sat(i), theta_res(i)) + + ! Min/max values conductivity soil moisture + gamma_theta_min(i) = 0. + gamma_theta_max(i) = gamma_theta_sat(i) + + ! Conductivity temperature + rho_dry = (1. - theta_sat(i)) * rho_solid_soil ! Density of soil (kg m-3) + gamma_t_dry(i) = (0.135 * rho_dry + 64.7) / (rho_solid_soil - 0.947 * rho_dry) + rho_C(i) = (1. - theta_sat(i)) * rho_C_matrix + theta_fc(i) * rho_C_water + end do + +end subroutine calc_soil_properties + +! +! Calculate root fraction for the low and high vegetation tiles +! +subroutine calc_root_fractions + use modglobal, only : i1, j1 + implicit none + real :: root_sum_lv, root_sum_hv + integer i, j, k + + do j=2,j1 + do i=2,i1 + root_sum_lv = 0 + root_sum_hv = 0 + do k=2, kmax_soil + + tile_lv%root_frac(i,j,k) = 0.5 * (& + exp(tile_lv%a_r(i,j) * zh_soil(k+1)) + & + exp(tile_lv%b_r(i,j) * zh_soil(k+1)) - & + exp(tile_lv%a_r(i,j) * zh_soil(k )) - & + exp(tile_lv%b_r(i,j) * zh_soil(k ))) + + tile_hv%root_frac(i,j,k) = 0.5 * (& + exp(tile_hv%a_r(i,j) * zh_soil(k+1)) + & + exp(tile_hv%b_r(i,j) * zh_soil(k+1)) - & + exp(tile_hv%a_r(i,j) * zh_soil(k )) - & + exp(tile_hv%b_r(i,j) * zh_soil(k ))) + + root_sum_lv = root_sum_lv + tile_lv%root_frac(i,j,k) + root_sum_hv = root_sum_hv + tile_hv%root_frac(i,j,k) + end do + + ! Make sure that the fractions sum to one. + tile_lv%root_frac(i,j,1) = 1. - root_sum_lv + tile_hv%root_frac(i,j,1) = 1. - root_sum_hv + end do + end do + +end subroutine calc_root_fractions + +! +! Iterative Rib -> Obukhov length solver +! +function calc_obuk_dirichlet(L_in, du, db_in, zsl, z0m, z0h) result(res) + use modglobal, only : fkar + implicit none + real, intent(in) :: L_in, du, db_in, zsl, z0m, z0h + + integer :: m, n, nlim + real :: res, L, db, Lmax, Ri, L0, Lstart, Lend, fx0, fxdif + + m = 0 + nlim = 10 + Lmax = 1e10 + L = L_in + db = db_in + + ! The solver does not have a solution for large Ri numbers, + ! i.e. the `fx` equation below has no zero crossing. + ! The limit of 0.13 typically results in a minimum (positive) L of ~1. + ! IFS caps z/L at 5, so for a typical zsl at ~L=2. + !Ri = fkar * db * zsl / du**2 + !if (Ri > 0.13) then + ! print*,'WARNING: Ri out of range, returning L=1' + ! res = 1 + ! return + !end if + + ! Avoid buoyancy difference of zero: + if (db >= 0) then + db = max(db, 1e-9) + else + db = min(db, -1e-9) + end if + + ! Allow for one restart of iterative procedure: + do while (m <= 1) + ! if L and db are of different sign, or the last calculation did not converge, + ! the stability has changed and the procedure needs to be reset + if (L*db <= 0) then + nlim = 200 + if (db >= 0) then + L = 1e-9 + else + L = -1e-9 + end if + end if + + ! Make sure the iteration starts + if (db >= 0) then + L0 = 1e30 + else + L0 = -1e30 + end if + + ! Exit on convergence or on iteration count + n = 0 + fxdif = 1 + do while (abs((L - L0) / L0) > 0.001 .and. n < nlim .and. abs(L) < Lmax) + L0 = L + Lstart = L - 0.001*L + Lend = L + 0.001*L + + fx0 = fx(zsl, L0, du, db, z0h, z0m) + fxdif = (fx(zsl, Lend, du, db, z0h, z0m) - fx(zsl, Lstart, du, db, z0h, z0m)) / (Lend - Lstart) + L = L - fx0/fxdif + n = n+1 + end do + + if (n < nlim .and. abs(L) < Lmax) then + ! Convergence has been reached + res = L + return + else + ! Convergence has not been reached, procedure restarted once + L = 1e-9 + m = m+1 + nlim = 200 + end if + end do + + if (m > 1) then + print*,'WARNING: convergence has not been reached in Obukhov length iteration' + print*,'Input: ', L_in, du, db_in, zsl, z0m, z0h + !stop + res = 1e-9 + return + end if + +end function calc_obuk_dirichlet + +pure function fx(zsl, L, du, db, z0h, z0m) result(res) + implicit none + real, intent(in) :: zsl, L, du, db, z0h, z0m + real :: res, fkar + fkar = 0.4 + + res = zsl/L - fkar * zsl * db * fh(zsl, z0h, L) / (du * fm(zsl, z0m, L))**2 +end function fx + +pure function fm(zsl, z0m, L) result(res) + use modglobal, only : fkar + use modsurface, only : psim + implicit none + real, intent(in) :: zsl, z0m, L + real :: res + + res = fkar / (log(zsl/z0m) - psim(zsl/L) + psim(z0m/L)) +end function fm + +pure function fh(zsl, z0h, L) result(res) + use modglobal, only : fkar + use modsurface, only : psih + implicit none + real, intent(in) :: zsl, z0h, L + real :: res + + res = fkar / (log(zsl/z0h) - psih(zsl/L) + psih(z0h/L)) +end function fh + +! +! Convert soil hydraulic head to soil water content, using van Genuchten parameterisation. +! +pure function psi_to_theta(theta_res, theta_sat, vg_a, vg_n, vg_m, psi) result(res) + implicit none + real, intent(in) :: theta_res, theta_sat, vg_a, vg_n, vg_m, psi + real :: res + + res = theta_res + (theta_sat - theta_res) * (1. / (1.+ abs(vg_a * psi)**vg_n))**vg_m +end function psi_to_theta + +! +! Convert soil water content to hydraulic head, using van Genuchten parameterisation. +! +pure function theta_to_psi(theta_res, theta_sat, vg_a, vg_n, vg_m, theta) result(res) + implicit none + real, intent(in) :: theta_res, theta_sat, vg_a, vg_n, vg_m, theta + real :: res + + res = -(vg_a**(-vg_n) * (-1. + ((theta_res - theta_sat)/(theta_res - theta))**(1./vg_m)))**(1./vg_n) +end function theta_to_psi + +! +! Calculate hydraulic diffusivity using van Genuchten parameterisation. +! +pure function calc_diffusivity_vg( & + theta_norm, vg_a, vg_l, vg_m, lambda_sat, theta_sat, theta_res) result(res) + implicit none + real, intent(in) :: theta_norm, vg_a, vg_l, vg_m, lambda_sat, theta_sat, theta_res + real :: res + + res = (1.-vg_m)*lambda_sat / (vg_a * vg_m * (theta_sat-theta_res)) * theta_norm**(vg_l-(1./vg_m)) * & + ( (1.-theta_norm**(1./vg_m))**(-vg_m) + (1.-theta_norm**(1./vg_m))**vg_m - 2. ) +end function calc_diffusivity_vg + +! +! Calculate hydraulic conductivity using van Genuchten parameterisation. +! +pure function calc_conductivity_vg(theta_norm, vg_l, vg_m, gamma_sat) result(res) + implicit none + real, intent(in) :: theta_norm, vg_l, vg_m, gamma_sat + real :: res + + res = gamma_sat * theta_norm**vg_l * ( 1.- (1.-theta_norm**(1./vg_m))**vg_m )**2. + +end function calc_conductivity_vg + +! +! Check NetCDF calls +! +subroutine check(status) + integer, intent (in) :: status + if(status /= nf90_noerr) then + print *,'NetCDF error: ', trim(nf90_strerror(status)) + stop + end if +end subroutine check + +end module modlsm diff --git a/src/modlsmcrosssection.f90 b/src/modlsmcrosssection.f90 index 45af5b8f..3c386659 100644 --- a/src/modlsmcrosssection.f90 +++ b/src/modlsmcrosssection.f90 @@ -37,7 +37,8 @@ module modlsmcrosssection PUBLIC :: initlsmcrosssection, lsmcrosssection,exitlsmcrosssection save !NetCDF variables - integer,parameter :: nvar = 2,nvar3=12 + integer,parameter :: nvar=2 + integer :: nvar3 integer :: ncid1 = 0 integer :: ncid2 = 0 integer :: ncid3 = 0 @@ -45,37 +46,34 @@ module modlsmcrosssection integer :: nrec2 = 0 integer :: nrec3 = 0 integer :: crossheight -! integer :: nxy = 0 -! integer :: cross -! integer :: nrc character(4) :: cheight character(80) :: fname1 = 'lsmcrossxz.xxxxyxxx.xxx.nc' character(80) :: fname2 = 'lsmcrossxy.xxxx.xxxxyxxx.xxx.nc' character(80) :: fname3 = 'surfcross.xxxxyxxx.xxx.nc' - character(80),dimension(nvar,4) :: ncname1 - character(80),dimension(1,4) :: tncname1 - character(80),dimension(nvar,4) :: ncname2 - character(80),dimension(1,4) :: tncname2 - character(80),dimension(nvar3,4) :: ncname3 - character(80),dimension(1,4) :: tncname3 - + character(80), dimension(nvar,4) :: ncname1 + character(80), dimension(1,4) :: tncname1 + character(80), dimension(nvar,4) :: ncname2 + character(80), dimension(1,4) :: tncname2 + character(80), allocatable, dimension(:,:) :: ncname3 + character(80), dimension(1,4) :: tncname3 real :: dtav - integer(kind=longint) :: idtav,tnext + integer(kind=longint) :: idtav, tnext logical :: lcross = .false. !< switch for doing the lsmcrosssection (on/off) integer :: crossplane = 2 !< Location of the xz lsmcrosssection contains !> Initializing lsmcrosssection. Read out the namelist, initializing the variables subroutine initlsmcrosssection - use modmpi, only :myid,mpierr,comm3d,mpi_logical,mpi_integer,cmyid,D_MPI_BCAST - use modglobal,only :imax,jmax,ifnamopt,fname_options,dtmax,dtav_glob,ladaptive,j1,dt_lim,cexpnr,tres,btime,checknamelisterror,& - output_prefix - use modstat_nc,only : lnetcdf,open_nc, define_nc,ncinfo,nctiminfo,writestat_dims_nc + use modmpi, only : myid,mpierr,comm3d,cmyid,D_MPI_BCAST + use modglobal, only : imax,jmax,ifnamopt,fname_options,dtmax,dtav_glob,ladaptive,j1,dt_lim,cexpnr,tres,btime,checknamelisterror,& + output_prefix + use modstat_nc, only : lnetcdf,open_nc, define_nc,ncinfo,nctiminfo,writestat_dims_nc use modsurfdata, only : isurf - implicit none + use modlsm, only : lags + implicit none - integer :: ierr + integer :: ierr, ii namelist/NAMLSMCROSSSECTION/ & lcross, dtav, crossheight, crossplane @@ -93,9 +91,9 @@ subroutine initlsmcrosssection close(ifnamopt) end if - if (lcross .and. (isurf .ne. 1)) then + if (lcross .and. .not. (isurf == 1 .or. isurf == 11)) then lcross = .FALSE. - write (6,*) "Ignoring lcross, lsmcrossection currently implemented only for isurf==1." + write (6,*) "Ignoring lcross, lsmcrossection currently implemented only for isurf==1 or 11." endif call D_MPI_BCAST(dtav ,1,0,comm3d,mpierr) @@ -103,7 +101,6 @@ subroutine initlsmcrosssection call D_MPI_BCAST(crossheight,1,0,comm3d,mpierr) call D_MPI_BCAST(crossplane ,1,0,comm3d,mpierr) - idtav = dtav/tres tnext = idtav+btime if(.not.(lcross)) return @@ -143,28 +140,69 @@ subroutine initlsmcrosssection call define_nc( ncid2, NVar, ncname2) end if ! -! !Surface values +! ! Surface values fname3(11:18) = cmyid fname3(20:22) = cexpnr - call nctiminfo(tncname3(1,:)) - call ncinfo(ncname3( 1,:),'Qnet','Net radiation','W/m^2','tt0t') - call ncinfo(ncname3( 2,:),'H','Sensible heat flux','W/m^2','tt0t') - call ncinfo(ncname3( 3,:),'LE','Latent heat flux','W/m^2','tt0t') - call ncinfo(ncname3( 4,:),'G0','Ground heat flux','W/m^2','tt0t') - call ncinfo(ncname3( 5,:),'tskin','Skin temperature','K','tt0t') - call ncinfo(ncname3( 6,:),'tendskin','Skin tendency','W/m^2','tt0t') - call ncinfo(ncname3( 7,:),'rs','Surface resistance','s/m','tt0t') - call ncinfo(ncname3( 8,:),'ra','Aerodynamic resistance','s/m','tt0t') - call ncinfo(ncname3( 9,:),'cliq','Fraction of vegetated surface covered with liquid water','-','tt0t') - call ncinfo(ncname3(10,:),'Wl','Liquid water reservoir','m','tt0t') - call ncinfo(ncname3(11,:),'rssoil','Soil evaporation resistance','s/m','tt0t') - call ncinfo(ncname3(12,:),'rsveg','Vegitation resistance','s/m','tt0t') - call open_nc(trim(output_prefix)//fname3, ncid3,nrec3,n1=imax,n2=jmax) - if (nrec3==0) then - call define_nc( ncid3, 1, tncname3) - call writestat_dims_nc(ncid3) + if (isurf == 1) then + nvar3 = 12 + else if (isurf == 11) then + nvar3 = 16 + if (lags) nvar3 = nvar3 + 2 + end if + + allocate(ncname3(nvar3,4)) + + if (isurf == 1) then + call nctiminfo(tncname3(1,:)) + call ncinfo(ncname3( 1,:),'Qnet','Net radiation','W/m^2','tt0t') + call ncinfo(ncname3( 2,:),'H','Sensible heat flux','W/m^2','tt0t') + call ncinfo(ncname3( 3,:),'LE','Latent heat flux','W/m^2','tt0t') + call ncinfo(ncname3( 4,:),'G0','Ground heat flux','W/m^2','tt0t') + call ncinfo(ncname3( 5,:),'tskin','Skin temperature','K','tt0t') + call ncinfo(ncname3( 6,:),'tendskin','Skin tendency','W/m^2','tt0t') + call ncinfo(ncname3( 7,:),'rs','Surface resistance','s/m','tt0t') + call ncinfo(ncname3( 8,:),'ra','Aerodynamic resistance','s/m','tt0t') + call ncinfo(ncname3( 9,:),'cliq','Fraction of vegetated surface covered with liquid water','-','tt0t') + call ncinfo(ncname3(10,:),'Wl','Liquid water reservoir','m','tt0t') + call ncinfo(ncname3(11,:),'rssoil','Soil evaporation resistance','s/m','tt0t') + call ncinfo(ncname3(12,:),'rsveg','Vegetation resistance','s/m','tt0t') + call open_nc(trim(output_prefix)//fname3, ncid3,nrec3,n1=imax,n2=jmax) + if (nrec3==0) then + call define_nc(ncid3, 1, tncname3) + call writestat_dims_nc(ncid3) + end if + call define_nc(ncid3, nvar3, ncname3) + else if (isurf == 11) then + call nctiminfo(tncname3(1,:)) + call ncinfo(ncname3( 1,:),'H', 'Sensible heat flux', 'W/m^2', 'tt0t') + call ncinfo(ncname3( 2,:),'LE', 'Latent heat flux', 'W/m^2', 'tt0t') + call ncinfo(ncname3( 3,:),'G0', 'Ground heat flux', 'W/m^2', 'tt0t') + call ncinfo(ncname3( 4,:),'tskin', 'Skin temperature', 'K', 'tt0t') + call ncinfo(ncname3( 5,:),'obuk', 'Obukhov length', 'm', 'tt0t') + call ncinfo(ncname3( 6,:),'ustar', 'Friction velocity', 'm/s^-1', 'tt0t') + call ncinfo(ncname3( 7,:),'cliq', 'Fraction of vegetated surface covered with liquid water', '-', 'tt0t') + call ncinfo(ncname3( 8,:),'wl', 'Liquid water reservoir', 'm', 'tt0t') + call ncinfo(ncname3( 9,:),'ra', 'Aerodynamic resistance', 's/m', 'tt0t') + call ncinfo(ncname3(10,:),'rssoil', 'Soil evaporation resistance', 's/m', 'tt0t') + call ncinfo(ncname3(11,:),'rsveg', 'Vegetation resistance', 's/m', 'tt0t') + call ncinfo(ncname3(12,:),'f1', 'f1(SWD) function vegetation resistance', 's/m', 'tt0t') + call ncinfo(ncname3(13,:),'f2_lv', 'f2(theta) function low vegetation resistance', 's/m', 'tt0t') + call ncinfo(ncname3(14,:),'f2_hv', 'f2(theta) function high vegetation resistance', 's/m', 'tt0t') + call ncinfo(ncname3(15,:),'f2_b', 'f2(theta) function soil resistance', 's/m', 'tt0t') + call ncinfo(ncname3(16,:),'f3', 'f3(VPD) function vegetation resistance', 's/m', 'tt0t') + if (lags) then + call ncinfo(ncname3(17,:),'an_co2', 'Net CO2 assimilation', 'ppb m s-1', 'tt0t') + call ncinfo(ncname3(18,:),'resp_co2', 'CO2 respiration soil', 'ppb m s-1', 'tt0t') + end if + + call open_nc(trim(output_prefix)//fname3, ncid3,nrec3,n1=imax,n2=jmax) + + if (nrec3==0) then + call define_nc(ncid3, 1, tncname3) + call writestat_dims_nc(ncid3) + end if + call define_nc( ncid3, nvar3, ncname3) end if - call define_nc( ncid3, NVar3, ncname3) end if @@ -230,14 +268,10 @@ subroutine wrthorz use modstat_nc, only : lnetcdf, writestat_nc implicit none - ! LOCAL integer i,j real, allocatable :: vars(:,:,:) - - - write(cheight,'(i4.4)') crossheight open(ifoutput,file='movh_tsoil.'//cexpnr,position='append',action='write') write(ifoutput,'(es12.5)') ((tsoil(i,j,crossheight),i=2,i1),j=2,j1) @@ -247,7 +281,6 @@ subroutine wrthorz write(ifoutput,'(es12.5)') ((phiw(i,j,crossheight),i=2,i1),j=2,j1) close(ifoutput) - if (lnetcdf) then allocate(vars(1:imax,1:jmax,2)) vars(:,:,1) = tsoil(2:i1,2:j1,crossheight) @@ -257,24 +290,21 @@ subroutine wrthorz deallocate(vars) end if - end subroutine wrthorz + !> Do the xy lsmcrosssections and dump them to file subroutine wrtsurf use modglobal, only : imax,jmax,i1,j1,cexpnr,ifoutput,rtimee use modsurfdata, only : Qnet, H, LE, G0, rs, ra, tskin, tendskin, & - cliq,rsveg,rssoil,Wl + cliq, rsveg, rssoil, Wl, isurf, obl, ustar + use modlsm, only : f1, f2_lv, f2_hv, f2b, f3, lags, an_co2, resp_co2 use modstat_nc, only : lnetcdf, writestat_nc implicit none - ! LOCAL integer i,j real, allocatable :: vars(:,:,:) - - - open(ifoutput,file='movh_qnet.'//cexpnr,position='append',action='write') write(ifoutput,'(es12.5)') ((qnet(i,j),i=2,i1),j=2,j1) close(ifoutput) @@ -323,23 +353,48 @@ subroutine wrtsurf write(ifoutput,'(es12.5)') ((wl(i,j),i=2,i1),j=2,j1) close(ifoutput) - if (lnetcdf) then allocate(vars(1:imax,1:jmax,nvar3)) - vars(:,:,1) = qnet(2:i1,2:j1) - vars(:,:,2) = h(2:i1,2:j1) - vars(:,:,3) = le(2:i1,2:j1) - vars(:,:,4) = g0(2:i1,2:j1) - vars(:,:,5) = tskin(2:i1,2:j1) - vars(:,:,6) = tendskin(2:i1,2:j1) - vars(:,:,7) = rs(2:i1,2:j1) - vars(:,:,8) = ra(2:i1,2:j1) - vars(:,:,9) = cliq(2:i1,2:j1) - vars(:,:,10) = Wl(2:i1,2:j1) - vars(:,:,11) = rssoil(2:i1,2:j1) - vars(:,:,12) = rsveg(2:i1,2:j1) - call writestat_nc(ncid3,1,tncname3,(/rtimee/),nrec3,.true.) - call writestat_nc(ncid3,nvar3,ncname3(1:nvar3,:),vars,nrec3,imax,jmax) + + if (isurf == 1) then + vars(:,:,1) = qnet(2:i1,2:j1) + vars(:,:,2) = h(2:i1,2:j1) + vars(:,:,3) = le(2:i1,2:j1) + vars(:,:,4) = g0(2:i1,2:j1) + vars(:,:,5) = tskin(2:i1,2:j1) + vars(:,:,6) = tendskin(2:i1,2:j1) + vars(:,:,7) = rs(2:i1,2:j1) + vars(:,:,8) = ra(2:i1,2:j1) + vars(:,:,9) = cliq(2:i1,2:j1) + vars(:,:,10) = Wl(2:i1,2:j1) + vars(:,:,11) = rssoil(2:i1,2:j1) + vars(:,:,12) = rsveg(2:i1,2:j1) + else if (isurf == 11) then + vars(:,:, 1) = H(2:i1,2:j1) + vars(:,:, 2) = LE(2:i1,2:j1) + vars(:,:, 3) = G0(2:i1,2:j1) + vars(:,:, 4) = tskin(2:i1,2:j1) + vars(:,:, 5) = obl(2:i1,2:j1) + vars(:,:, 6) = ustar(2:i1,2:j1) + vars(:,:, 7) = cliq(2:i1,2:j1) + vars(:,:, 8) = Wl(2:i1,2:j1) + vars(:,:, 9) = ra(2:i1,2:j1) + vars(:,:,10) = rssoil(2:i1,2:j1) + vars(:,:,11) = rsveg(2:i1,2:j1) + vars(:,:,12) = f1(2:i1,2:j1) + vars(:,:,13) = f2_lv(2:i1,2:j1) + vars(:,:,14) = f2_hv(2:i1,2:j1) + vars(:,:,15) = f2b(2:i1,2:j1) + vars(:,:,16) = f3(2:i1,2:j1) + if (lags) then + vars(:,:,17) = an_co2(2:i1,2:j1) + vars(:,:,18) = resp_co2(2:i1,2:j1) + endif + end if + + call writestat_nc(ncid3, 1, tncname3, (/rtimee/), nrec3, .true.) + call writestat_nc(ncid3, nvar3, ncname3(1:nvar3,:), vars, nrec3, imax, jmax) + deallocate(vars) end if @@ -352,10 +407,12 @@ subroutine exitlsmcrosssection implicit none if(lcross .and. lnetcdf) then - if (myid==0) then - call exitstat_nc(ncid1) - end if - call exitstat_nc(ncid2) + if (myid==0) then + call exitstat_nc(ncid1) + end if + call exitstat_nc(ncid2) + call exitstat_nc(ncid3) + deallocate(ncname3) end if end subroutine exitlsmcrosssection diff --git a/src/modlsmstat.f90 b/src/modlsmstat.f90 index 17e2eff7..74f15e5f 100644 --- a/src/modlsmstat.f90 +++ b/src/modlsmstat.f90 @@ -66,15 +66,16 @@ subroutine initlsmstat use modstat_nc, only : lnetcdf,define_nc,ncinfo use modgenstat, only : idtav_prof=>idtav, itimeav_prof=>itimeav,ncid_prof=>ncid use modsurfdata,only : ksoilmax,isurf + use modlsm, only : kmax_soil implicit none - integer ierr + integer ierr, kdim_soil namelist/NAMLSMSTAT/ & dtav,timeav,lstat dtav=dtav_glob;timeav=timeav_glob lstat = .false. - if (isurf /=1) return + if (.not. (isurf ==1 .or. isurf==11)) return if(myid==0)then open(ifnamopt,file=fname_options,status='old',iostat=ierr) read (ifnamopt,NAMLSMSTAT,iostat=ierr) @@ -104,17 +105,23 @@ subroutine initlsmstat stop 'dtav should be a integer multiple of dtmax' end if - allocate(phiwav(ksoilmax)) - allocate(tsoilav(ksoilmax)) - allocate(lambdaav(ksoilmax)) - allocate(lambdasav(ksoilmax)) - allocate(gammasav(ksoilmax)) + if (isurf == 1) then + kdim_soil = ksoilmax + else if (isurf == 11) then + kdim_soil = kmax_soil + end if + + allocate(phiwav(kdim_soil)) + allocate(tsoilav(kdim_soil)) + allocate(lambdaav(kdim_soil)) + allocate(lambdasav(kdim_soil)) + allocate(gammasav(kdim_soil)) - allocate(phiwmn(ksoilmax)) - allocate(tsoilmn(ksoilmax)) - allocate(lambdamn(ksoilmax)) - allocate(lambdasmn(ksoilmax)) - allocate(gammasmn(ksoilmax)) + allocate(phiwmn(kdim_soil)) + allocate(tsoilmn(kdim_soil)) + allocate(lambdamn(kdim_soil)) + allocate(lambdasmn(kdim_soil)) + allocate(gammasmn(kdim_soil)) phiwmn = 0.0 tsoilmn = 0.0 @@ -126,6 +133,7 @@ subroutine initlsmstat open (ifoutput,file='lsmstat.'//cexpnr,status='replace') close (ifoutput) end if + if (lnetcdf) then idtav = idtav_prof itimeav = itimeav_prof @@ -173,21 +181,29 @@ subroutine do_lsmstat use modmpi, only : slabsum use modglobal, only : ijtot,i1,j1,i2,j2 - use modsurfdata, only : ksoilmax,tsoil,phiw,lambda,lambdas,gammas + use modsurfdata, only : isurf,ksoilmax,tsoil,phiw,lambda,lambdas,gammas + use modlsm, only : kmax_soil implicit none + integer kdim_soil + if (isurf == 1) then + kdim_soil = ksoilmax + else if (isurf == 11) then + kdim_soil = kmax_soil + end if + tsoilav = 0. phiwav = 0. lambdaav = 0. lambdasav = 0. gammasav = 0. - call slabsum(tsoilav ,1,ksoilmax,tsoil ,1,i2,1,j2,1,ksoilmax,2,i1,2,j1,1,ksoilmax) - call slabsum(phiwav ,1,ksoilmax,phiw ,1,i2,1,j2,1,ksoilmax,2,i1,2,j1,1,ksoilmax) - call slabsum(lambdaav ,1,ksoilmax,lambda ,1,i2,1,j2,1,ksoilmax,2,i1,2,j1,1,ksoilmax) - call slabsum(lambdasav ,1,ksoilmax,lambdas ,1,i2,1,j2,1,ksoilmax,2,i1,2,j1,1,ksoilmax) - call slabsum(gammasav ,1,ksoilmax,gammas ,1,i2,1,j2,1,ksoilmax,2,i1,2,j1,1,ksoilmax) + call slabsum(tsoilav ,1,kdim_soil,tsoil ,1,i2,1,j2,1,kdim_soil,2,i1,2,j1,1,kdim_soil) + call slabsum(phiwav ,1,kdim_soil,phiw ,1,i2,1,j2,1,kdim_soil,2,i1,2,j1,1,kdim_soil) + call slabsum(lambdaav ,1,kdim_soil,lambda ,1,i2,1,j2,1,kdim_soil,2,i1,2,j1,1,kdim_soil) + call slabsum(lambdasav ,1,kdim_soil,lambdas ,1,i2,1,j2,1,kdim_soil,2,i1,2,j1,1,kdim_soil) + call slabsum(gammasav ,1,kdim_soil,gammas ,1,i2,1,j2,1,kdim_soil,2,i1,2,j1,1,kdim_soil) ! ADD SLAB AVERAGES TO TIME MEAN phiwmn = phiwmn + phiwav/ijtot @@ -205,11 +221,20 @@ subroutine writelsmstat use modglobal, only : cexpnr,ifoutput,rtimee use modstat_nc, only: lnetcdf, writestat_nc use modgenstat, only: ncid_prof=>ncid,nrec_prof=>nrec - use modsurfdata,only : ksoilmax,zsoilc + use modsurfdata,only : isurf, ksoilmax, zsoilc + use modlsm, only : kmax_soil, z_soil + implicit none - real,dimension(ksoilmax,nvar) :: vars - integer nsecs, nhrs, nminut,k + real, allocatable :: vars(:,:) + integer nsecs, nhrs, nminut,k, kdim_soil + + if (isurf == 1) then + kdim_soil = ksoilmax + else if (isurf == 11) then + kdim_soil = kmax_soil + end if + allocate(vars(kdim_soil, nvar)) nsecs = nint(rtimee) nhrs = int(nsecs/3600) @@ -236,15 +261,27 @@ subroutine writelsmstat '#--------------------------------------------------------------------------' & ,'#LEV HEIGHT T_SOIL SOIL MOIST HEAT COND. MOIST DIFF. MOIST COND.' & ,'# (M) (K) (M^3/M^3) (W/M/K) (M^2/S) (M/S) ' - do k=1,ksoilmax - write(ifoutput,'(I3,F8.4,F10.4,4E13.4)') & - k, zsoilc(k),& - tsoilmn(k),& - phiwmn(k),& - lambdamn(k),& - lambdasmn(k),& - gammasmn(k) - end do + if (isurf == 1) then + do k=1,kdim_soil + write(ifoutput,'(I3,F8.4,F10.4,4E13.4)') & + k, zsoilc(k),& + tsoilmn(k),& + phiwmn(k),& + lambdamn(k),& + lambdasmn(k),& + gammasmn(k) + end do + else if (isurf == 11) then + do k=1,kdim_soil + write(ifoutput,'(I3,F8.4,F10.4,4E13.4)') & + k, z_soil(k),& + tsoilmn(k),& + phiwmn(k),& + lambdamn(k),& + lambdasmn(k),& + gammasmn(k) + end do + end if close (ifoutput) if (lnetcdf) then vars(:, 1) = tsoilmn @@ -252,7 +289,7 @@ subroutine writelsmstat vars(:, 3) = lambdamn vars(:, 4) = lambdasmn vars(:, 5) = gammasmn - call writestat_nc(ncid_prof,nvar,ncname,vars(1:ksoilmax,:),nrec_prof,ksoilmax) + call writestat_nc(ncid_prof,nvar,ncname,vars(1:kdim_soil,:),nrec_prof,kdim_soil) end if end if ! end if(myid==0) @@ -262,6 +299,8 @@ subroutine writelsmstat lambdasmn = 0.0 gammasmn = 0.0 + deallocate(vars) + end subroutine writelsmstat diff --git a/src/modmpi.f90 b/src/modmpi.f90 index 5564689d..504ba9ac 100644 --- a/src/modmpi.f90 +++ b/src/modmpi.f90 @@ -99,6 +99,7 @@ module modmpi procedure :: D_MPI_BCAST_INT32_R2 procedure :: D_MPI_BCAST_LOGICAL_R1 procedure :: D_MPI_BCAST_STRING + procedure :: D_MPI_BCAST_STRING_R1 end interface interface D_MPI_ALLREDUCE procedure :: D_MPI_ALLREDUCE_REAL32_S @@ -778,5 +779,4 @@ subroutine gatherrow(l,g,imax,jmax,itot) end subroutine gatherrow - end module diff --git a/src/modmpiinterface.f90 b/src/modmpiinterface.f90 index 16a43db0..f9b1f52d 100644 --- a/src/modmpiinterface.f90 +++ b/src/modmpiinterface.f90 @@ -195,6 +195,14 @@ subroutine D_MPI_BCAST_STRING(buffer, count, root, comm, ierror) call MPI_BCAST(buffer, count, MPI_CHARACTER, root, comm, ierror) if (ierror /= MPI_SUCCESS) call abort end subroutine D_MPI_BCAST_STRING + subroutine D_MPI_BCAST_STRING_R1(buffer, count, root, comm, ierror) + implicit none + character(len = *),contiguous, intent(inout):: buffer(:) + integer :: count, root, ierror + type(MPI_COMM) :: comm + call MPI_BCAST(buffer, count, MPI_CHARACTER, root, comm, ierror) + if (ierror /= MPI_SUCCESS) call abort + end subroutine D_MPI_BCAST_STRING_R1 !>D_MPI_ALLREDUCE subroutine D_MPI_ALLREDUCE_REAL32_S(sendbuf, recvbuf, count, op, comm, ierror) diff --git a/src/modnudgeboundary.f90 b/src/modnudgeboundary.f90 new file mode 100644 index 00000000..9f6f40ca --- /dev/null +++ b/src/modnudgeboundary.f90 @@ -0,0 +1,480 @@ +!> \file modrelaxboundary.f90 +!> +!! Nudge the lateral boundaries +!> +!! \author Bart van Stratum, KNMI +! +! This file is part of DALES. +! +! DALES is free software; you can redistribute it and/or modify +! it under the terms of the GNU General Public License as published by +! the Free Software Foundation; either version 3 of the License, or +! (at your option) any later version. +! +! DALES is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU General Public License for more details. +! +! You should have received a copy of the GNU General Public License +! along with this program. If not, see . +! +! Copyright 1993-2009 Delft University of Technology, Wageningen University, Utrecht University, KNMI +! + +module modnudgeboundary +implicit none + +public :: initnudgeboundary, nudgeboundary, exitnudgeboundary +save + logical :: lnudge_boundary = .false. ! Switch boundary nudging of thermodynamics + logical :: lnudge_boundary_sv = .false. ! Switch boundary nudging of scalars + logical :: lperturb_boundary = .false. ! Switch perturbation of thl near boundary + logical :: lnudge_w = .true. ! Nudge w to zero or not + + real, dimension(:,:), allocatable :: nudge_factor ! Nudging factor (0-1) near lateral boundaries + real, dimension(:,:), allocatable :: perturb_factor ! Perturbing factor (0-1) near lateral boundaries + + real, dimension(:,:,:,:), allocatable :: lbc_u, lbc_v, lbc_thl, lbc_qt ! Input for nudging to external field + real, dimension(:,:,:,:,:), allocatable :: lbc_sv ! Input for nudging scalars to external field + + ! Boundary nudging settings: + real :: nudge_offset=0, nudge_width=0, nudge_radius=0, nudge_tau=-1 + + ! Inflow boundary perturbations settings: + real :: perturb_offset=0, perturb_width=0, perturb_radius=0, perturb_ampl=0, perturb_zmax=100 + integer :: perturb_blocksize=1, kmax_perturb=0 + + ! Misc + real :: dt_input_lbc=-1 + integer :: lbc_index=1 + + real :: dt_input_lbc_sv=-1 + integer :: lbc_index_sv=1 + + +contains + + function corner_factor(x, y, x_center, y_center, radius, width) result(f) + + implicit none + real, intent(in) :: x, y, x_center, y_center, radius, width + real :: D, f + + D = sqrt((x-x_center)**2 + (y-y_center)**2) - radius + f = exp(-0.5*(D/width)**2) + + end function + + + subroutine calc_weighting_factor(factor, offset, width, radius) + + use modglobal, only : xsize, ysize, j1, i1, dx, dy + use modmpi, only : myidx, myidy, nprocx, nprocy + implicit none + + real, dimension(2:i1,2:j1), intent(inout) :: factor + real, intent(in) :: offset, width, radius + + real :: dc, x, y + integer :: i, j + + ! Total size of corners + dc = offset + radius + + ! Calculate weighting factor + do j=2, j1 + do i=2,i1 + ! Location in domain, accounting for MPI + x = myidx * (xsize / nprocx) + (i-1.5)*dx + y = myidy * (ysize / nprocy) + (j-1.5)*dy + + ! Smooth corners of nudging zone + if (y < dc .and. x < dc) then ! SW-corner + factor(i,j) = corner_factor(x, y, dc, dc, radius, width) + + else if (y < dc .and. x > xsize-dc) then ! SE-corner + factor(i,j) = corner_factor(x, y, xsize-dc, dc, radius, width) + + else if (y > ysize-dc .and. x < dc) then ! NW-corner + factor(i,j) = corner_factor(x, y, dc, ysize-dc, radius, width) + + else if (y > ysize-dc .and. x > xsize-dc) then ! NE-corner + factor(i,j) = corner_factor(x, y, xsize-dc, ysize-dc, radius, width) + + else + factor(i,j) = min( 1., exp(-0.5*((x- offset )/width)**2) + & + & exp(-0.5*((x-(xsize-offset))/width)**2) + & + & exp(-0.5*((y- offset )/width)**2) + & + & exp(-0.5*((y-(ysize-offset))/width)**2) ) + end if + end do + end do + + end subroutine calc_weighting_factor + + + subroutine initnudgeboundary + + use modmpi, only : myid, mpierr, comm3d, D_MPI_BCAST + use modglobal, only : ifnamopt, fname_options, imax, jmax, dx, dy, i1, j1, k1, ih, jh, lwarmstart, kmax, zf, checknamelisterror, nsv + use modboundary, only : boundary + use modemisdata, only : svskip + + implicit none + + integer :: ierr, k + + ! Read namelist settings + namelist /NAMNUDGEBOUNDARY/ lnudge_boundary, lnudge_boundary_sv, lperturb_boundary, lnudge_w, & + & nudge_offset, nudge_width, nudge_radius, nudge_tau, & + & perturb_offset, perturb_width, perturb_radius, perturb_ampl, perturb_zmax, & + & dt_input_lbc, dt_input_lbc_sv, perturb_blocksize + + if (myid==0) then + open(ifnamopt, file=fname_options, status='old', iostat=ierr) + read (ifnamopt, NAMNUDGEBOUNDARY, iostat=ierr) + call checknamelisterror(ierr, ifnamopt, 'NAMNUDGEBOUNDARY') + write(6, NAMNUDGEBOUNDARY) + close(ifnamopt) + end if + + call D_MPI_BCAST(lnudge_boundary, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lnudge_boundary_sv,1, 0, comm3d, mpierr) + call D_MPI_BCAST(lperturb_boundary, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(lnudge_w, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(perturb_blocksize, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(nudge_offset, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(nudge_width, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(nudge_radius, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(nudge_tau, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(perturb_offset, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(perturb_width, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(perturb_radius, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(perturb_ampl, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(perturb_zmax, 1, 0, comm3d, mpierr) + + call D_MPI_BCAST(dt_input_lbc, 1, 0, comm3d, mpierr) + call D_MPI_BCAST(dt_input_lbc_sv, 1, 0, comm3d, mpierr) + + if (lnudge_boundary) then + if (myid==0) then + ! Require offset + 2 standard deviations of the nudging profile to fit inside one tile + if (imax * dx < (nudge_offset+2*nudge_width) .or. jmax * dy < (nudge_offset+2*nudge_width) ) then + STOP "Tile size is too small compared to boundary nudging range." + end if + end if + + ! Init and calculate nudge and perturb factors + allocate( nudge_factor(2:i1, 2:j1) ) + + ! Two time steps (last dim) are kept in memory, + ! and are linearly interpolated in time + allocate( lbc_u (2-ih:i1+ih, 2-jh:j1+jh, k1, 2) ) + allocate( lbc_v (2-ih:i1+ih, 2-jh:j1+jh, k1, 2) ) + allocate( lbc_thl(2-ih:i1+ih, 2-jh:j1+jh, k1, 2) ) + allocate( lbc_qt (2-ih:i1+ih, 2-jh:j1+jh, k1, 2) ) + if (lnudge_boundary_sv) allocate( lbc_sv (2-ih:i1+ih, 2-jh:j1+jh, k1, 1+svskip:nsv, 2) ) + + ! Read the first two input times + call read_new_LBCs(0.) + call read_new_LBCs(dt_input_lbc) + lbc_index = 1 + + if (lnudge_boundary_sv) then + ! Read the first two input times for scalars + call read_new_LBCs_sv(0.) + call read_new_LBCs_sv(dt_input_lbc_sv) + lbc_index_sv = 1 + end if + + ! Hack - read full initial 3D field + if (.not. lwarmstart) call read_initial_fields + + ! Make sure the ghost cells are set correctly.. + call boundary + + ! Calculate the nudging factor + call calc_weighting_factor(nudge_factor, nudge_offset, nudge_width, nudge_radius) + + if (lperturb_boundary) then + allocate( perturb_factor(2:i1, 2:j1) ) + + call calc_weighting_factor(perturb_factor, perturb_offset, perturb_width, perturb_radius) + + ! Find maximum grid level to which the perturbations are applied + do k=1,kmax + if (zf(k) > perturb_zmax) then + kmax_perturb = k-1 + exit + end if + end do + end if ! lperturb_boundary + + end if ! lnudge_boundary + + end subroutine initnudgeboundary + + + subroutine read_initial_fields + + ! BvS - this should really go somewhere else, probably modstartup... + use modfields, only : u0, v0, um, vm, thlm, thl0, qtm, qt0, sv0, svm + use modsurfdata, only : tskin + use modglobal, only : i1, j1, iexpnr, kmax, nsv + use modmpi, only : myidx, myidy + use modemisdata, only : svskip + + implicit none + + integer :: isv + + character(80) :: input_file = 'lbc000h00m_x___y___.___' + character(80) :: input_file_sv = 'lbcsv000h00m_x___y___.___' + + write(input_file(13:15), '(i3.3)') myidx + write(input_file(17:19), '(i3.3)') myidy + write(input_file(21:23), '(i3.3)') iexpnr + + print*,'Reading initial field: ', input_file + + open(666, file=input_file, form='unformatted', status='unknown', action='read', access='stream') + read(666) u0 (2:i1,2:j1,1:kmax) + read(666) v0 (2:i1,2:j1,1:kmax) + read(666) thl0 (2:i1,2:j1,1:kmax) + read(666) qt0 (2:i1,2:j1,1:kmax) + read(666) tskin(2:i1,2:j1 ) + close(666) + + um (2:i1,2:j1,1:kmax) = u0 (2:i1,2:j1,1:kmax) + vm (2:i1,2:j1,1:kmax) = v0 (2:i1,2:j1,1:kmax) + thlm (2:i1,2:j1,1:kmax) = thl0 (2:i1,2:j1,1:kmax) + qtm (2:i1,2:j1,1:kmax) = qt0 (2:i1,2:j1,1:kmax) + + if (lnudge_boundary_sv) then + + write(input_file_sv(15:17), '(i3.3)') myidx + write(input_file_sv(19:21), '(i3.3)') myidy + write(input_file_sv(23:25), '(i3.3)') iexpnr + + print*,'Reading initial field: ', input_file_sv + + open(777, file=input_file_sv, form='unformatted', status='unknown', action='read', access='stream') + do isv = svskip+1,nsv + read(777) sv0(2:i1,2:j1,1:kmax,isv) + end do + close(777) + + svm (2:i1,2:j1,1:kmax,svskip+1:nsv) = sv0(2:i1,2:j1,1:kmax,svskip+1:nsv) + endif + + end subroutine read_initial_fields + + + subroutine read_new_LBCs(time) + + use modglobal, only : i1, j1, iexpnr, kmax + use modmpi, only : myidx, myidy, nprocx, nprocy + + implicit none + real, intent(in) :: time !< Input: time to read (seconds) + integer :: ihour, imin + character(80) :: input_file = 'lbc___h__m_x___y___.___' + + ! Only the MPI tasks at the domain edges read the LBCs: + if (myidx == 0 .or. myidx == nprocx-1 .or. myidy == 0 .or. myidy == nprocy-1) then + + ! File name to read + ihour = floor(time/3600) + imin = floor((time-ihour*3600)/3600.*60.) + write(input_file( 4: 6), '(i3.3)') ihour + write(input_file( 8: 9), '(i2.2)') imin + write(input_file(13:15), '(i3.3)') myidx + write(input_file(17:19), '(i3.3)') myidy + write(input_file(21:23), '(i3.3)') iexpnr + + print*,'Processing LBC: ', input_file + + ! Copy old second time to new first time + lbc_u (:,:,:,1) = lbc_u (:,:,:,2) + lbc_v (:,:,:,1) = lbc_v (:,:,:,2) + lbc_thl(:,:,:,1) = lbc_thl(:,:,:,2) + lbc_qt (:,:,:,1) = lbc_qt (:,:,:,2) + + ! Read new LBC for next time + open(666, file=input_file, form='unformatted', status='unknown', action='read', access='stream') + read(666) lbc_u (2:i1,2:j1,1:kmax,2) + read(666) lbc_v (2:i1,2:j1,1:kmax,2) + read(666) lbc_thl(2:i1,2:j1,1:kmax,2) + read(666) lbc_qt (2:i1,2:j1,1:kmax,2) + close(666) + + end if + + end subroutine read_new_LBCs + + subroutine read_new_LBCs_sv(time) + + use modglobal, only : i1, j1, iexpnr, kmax, nsv + use modmpi, only : myidx, myidy, nprocx, nprocy + use modemisdata, only : svskip + implicit none + real, intent(in) :: time !< Input: time to read (seconds) + integer :: ihour, imin, isv + + character(80) :: input_file = 'lbcsv___h__m_x___y___.___' + + ! Only the MPI tasks at the domain edges read the LBCs: + if (myidx == 0 .or. myidx == nprocx-1 .or. myidy == 0 .or. myidy == nprocy-1) then + + ! File name to read + ihour = floor(time/3600) + imin = floor((time-ihour*3600)/3600.*60.) + write(input_file( 6: 8), '(i3.3)') ihour + write(input_file(10:11), '(i2.2)') imin + write(input_file(15:17), '(i3.3)') myidx + write(input_file(19:21), '(i3.3)') myidy + write(input_file(23:25), '(i3.3)') iexpnr + + print*,'Processing LBC for scalars: ', input_file + + ! Copy old second time to new first time + lbc_sv(:,:,:,:,1) = lbc_sv(:,:,:,:,2) + + ! Read new LBC for next time + open(777, file=input_file, form='unformatted', status='unknown', action='read', access='stream') + do isv = svskip+1,nsv + read(777) lbc_sv (2:i1,2:j1,1:kmax,isv,2) + end do + close(777) + + end if + + end subroutine read_new_LBCs_sv + + subroutine nudgeboundary + + use modglobal, only : i1, j1, imax, jmax, kmax, rdt, cu, cv, eps1, rtimee, nsv + use modfields, only : u0, up, v0, vp, w0, wp, thl0, thlp, qt0, qtp, sv0, svp + use modmpi, only : myidx, myidy, nprocx, nprocy + use modemisdata, only : svskip + + implicit none + + integer :: i, j, k, blocki, blockj, subi, subj, isv + real :: tau_i, perturbation, t0, t1, tfac, tfac_sv, rnd + real :: lbc_u_int, lbc_v_int, lbc_w_int, lbc_t_int, lbc_q_int, lbc_sv_int + + if (lnudge_boundary) then + + if (nudge_tau <= eps1) then + tau_i = 1. / rdt ! Nudge on time scale equal to current time step + else + tau_i = 1. / nudge_tau ! Nudge on specified time scale + end if + + ! Read new LBC (if required) + if (rtimee > lbc_index*dt_input_lbc) then + lbc_index = lbc_index + 1 + call read_new_LBCs(lbc_index*dt_input_lbc) + end if + + ! Read new LBC for scalars (if required) + if (lnudge_boundary_sv) then + if (rtimee > lbc_index_sv*dt_input_lbc_sv) then + lbc_index_sv = lbc_index_sv + 1 + call read_new_LBCs_sv(lbc_index_sv*dt_input_lbc_sv) + end if + end if + + ! Calculate time interpolation factor + t0 = (lbc_index - 1) * dt_input_lbc ! Time of previous boundary + t1 = (lbc_index ) * dt_input_lbc ! Time of next boundary + tfac = 1.-(rtimee - t0) / (t1 - t0) ! Interpolation factor + + if (lnudge_boundary_sv) then + t0 = (lbc_index_sv - 1) * dt_input_lbc_sv ! Time of previous boundary + t1 = (lbc_index_sv ) * dt_input_lbc_sv ! Time of next boundary + tfac_sv = 1.-(rtimee - t0) / (t1 - t0) ! Interpolation factor + end if + + if (myidx == 0 .or. myidx == nprocx-1 .or. myidy ==0 .or. myidy == nprocy-1) then + do k=1,kmax + do j=2,j1 + do i=2,i1 + + ! Interpolate LBC in time + lbc_u_int = tfac * lbc_u (i,j,k,1) + (1.-tfac) * lbc_u (i,j,k,2) + lbc_v_int = tfac * lbc_v (i,j,k,1) + (1.-tfac) * lbc_v (i,j,k,2) + lbc_t_int = tfac * lbc_thl(i,j,k,1) + (1.-tfac) * lbc_thl(i,j,k,2) + lbc_q_int = tfac * lbc_qt (i,j,k,1) + (1.-tfac) * lbc_qt (i,j,k,2) + lbc_w_int = 0. + + if (lnudge_boundary_sv) then + do isv = 1+svskip, nsv + lbc_sv_int = tfac_sv * lbc_sv (i,j,k,isv,1) + (1.-tfac_sv) * lbc_sv (i,j,k,isv,2) + svp(i,j,k,isv) = svp(i,j,k,isv) + nudge_factor(i,j) * tau_i * (lbc_sv_int - sv0(i,j,k,isv)) + end do + end if + + ! Nudge the boundaries + up(i,j,k) = up(i,j,k) + nudge_factor(i,j) * tau_i * (lbc_u_int - (u0(i,j,k)+cu)) + vp(i,j,k) = vp(i,j,k) + nudge_factor(i,j) * tau_i * (lbc_v_int - (v0(i,j,k)+cv)) + thlp(i,j,k) = thlp(i,j,k) + nudge_factor(i,j) * tau_i * (lbc_t_int - thl0(i,j,k)) + qtp(i,j,k) = qtp(i,j,k) + nudge_factor(i,j) * tau_i * (lbc_q_int - qt0(i,j,k) ) + + if (lnudge_w) then + wp(i,j,k) = wp(i,j,k) + nudge_factor(i,j) * tau_i * (lbc_w_int - w0(i,j,k)) + end if + + end do + end do + end do + end if + + + ! BvS; quick-and-dirty test with perturbing the inflow boundary. + if (lperturb_boundary) then + + do k=1,kmax_perturb + + do blockj=0, jmax/perturb_blocksize-1 + do blocki=0, imax/perturb_blocksize-1 + call random_number(rnd) + perturbation = perturb_ampl*(rnd-0.5) + + do subj=0, perturb_blocksize-1 + do subi=0, perturb_blocksize-1 + i = blocki*perturb_blocksize + subi + 2 + j = blockj*perturb_blocksize + subj + 2 + + thlp(i,j,k) = thlp(i,j,k) + perturb_factor(i,j) * perturbation / rdt + end do + end do + + end do + end do + + end do + end if + + end if ! lnudge_boundary + + end subroutine nudgeboundary + + + subroutine exitnudgeboundary + + implicit none + if (lnudge_boundary) then + deallocate( nudge_factor, lbc_u, lbc_v, lbc_thl, lbc_qt ) + if (lnudge_boundary_sv) deallocate( lbc_sv ) + if (lperturb_boundary) deallocate( perturb_factor ) + end if + + end subroutine exitnudgeboundary + +end module modnudgeboundary diff --git a/src/modstartup.f90 b/src/modstartup.f90 index 5e0304ed..a428c308 100644 --- a/src/modstartup.f90 +++ b/src/modstartup.f90 @@ -74,6 +74,9 @@ subroutine startup(path) use modforces, only : lforce_user use modsurfdata, only : z0,ustin,wtsurf,wqsurf,wsvsurf,ps,thls,isurf use modsurface, only : initsurface + use moddatetime, only : initdatetime + use modemission, only : initemission + use modlsm, only : initlsm, kmax_soil use modfields, only : initfields use modpois, only : initpois use modradiation, only : initradiation @@ -103,7 +106,7 @@ subroutine startup(path) krandumin, krandumax, randu,& nprocx,nprocy,loutdirs namelist/DOMAIN/ & - itot,jtot,kmax,& + itot,jtot,kmax,kmax_soil,& xsize,ysize,& xlat,xlon,xyear,xday,xtime,ksp namelist/PHYSICS/ & @@ -186,6 +189,7 @@ subroutine startup(path) call D_MPI_BCAST(itot ,1,0,commwrld,mpierr) call D_MPI_BCAST(jtot ,1,0,commwrld,mpierr) call D_MPI_BCAST(kmax ,1,0,commwrld,mpierr) + call D_MPI_BCAST(kmax_soil ,1,0,commwrld,mpierr) call D_MPI_BCAST(xsize ,1,0,commwrld,mpierr) call D_MPI_BCAST(ysize ,1,0,commwrld,mpierr) call D_MPI_BCAST(xlat ,1,0,commwrld,mpierr) @@ -280,6 +284,9 @@ subroutine startup(path) call initradiation call initchem call initsurface + call initdatetime + call initemission + call initlsm call initsubgrid call initmicrophysics @@ -373,6 +380,7 @@ subroutine checkinitvalues case(3:4) if (wtsurf <-1e10) stop 'wtsurf not set' if (wqsurf <-1e10) stop 'wqsurf not set' + case(11) case default stop 'isurf out of range/not set' end select @@ -408,6 +416,7 @@ subroutine readinitfiles thls,tskin,tskinm,tsoil,tsoilm,phiw,phiwm,Wl,Wlm,thvs,qts,isurf,svs,obl,oblav,& thvs_patch,lhetero,qskin use modsurface, only : surface,qtsurf,dthldz,ps + use modlsm, only : init_lsm_tiles use modboundary, only : boundary use modmpi, only : slabsum,myid,comm3d,mpierr,D_MPI_BCAST use modthermodynamics, only : thermodynamics,calc_halflev @@ -613,10 +622,16 @@ subroutine readinitfiles case(2) tskin = thls case(3,4) - thls = thlprof(1) - qts = qtprof(1) + thls = thlprof(1) + qts = qtprof(1) tskin = thls qskin = qts + case(11) + thls = thlprof(1) + qts = qtprof(1) + tskin = thls + qskin = qts + call init_lsm_tiles case(10) call initsurf_user end select @@ -869,6 +884,7 @@ subroutine readrestartfiles tres,ifinput,nsv,dt,output_prefix use modmpi, only : myid, cmyid use modsubgriddata, only : ekm,ekh + use modlsm, only : kmax_soil, tile_lv, tile_hv, tile_bs, tile_ws, tile_aq character(50) :: name @@ -977,7 +993,38 @@ subroutine readrestartfiles end if read(ifinput) timee close(ifinput) + + else if (isurf == 11) then + name(5:5) = 'l' + write(6,*) 'loading ',name + open(unit=ifinput,file=name,form='unformatted') + read(ifinput) (((tsoil(i,j,k), i=1,i2), j=1,j2), k=1,kmax_soil) + read(ifinput) (((phiw (i,j,k), i=1,i2), j=1,j2), k=1,kmax_soil) + read(ifinput) ((tskin (i,j), i=1,i2), j=1,j2) + read(ifinput) ((Wl (i,j), i=1,i2), j=1,j2) + + read(ifinput) ((tile_lv%thlskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_hv%thlskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_bs%thlskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_ws%thlskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_aq%thlskin(i,j), i=1,i2), j=1,j2) + + read(ifinput) ((tile_lv%qtskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_hv%qtskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_bs%qtskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_ws%qtskin(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_aq%qtskin(i,j), i=1,i2), j=1,j2) + + read(ifinput) ((tile_lv%obuk(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_hv%obuk(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_bs%obuk(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_ws%obuk(i,j), i=1,i2), j=1,j2) + read(ifinput) ((tile_aq%obuk(i,j), i=1,i2), j=1,j2) + + read(ifinput) timee + close(ifinput) end if + end subroutine readrestartfiles !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -1019,13 +1066,13 @@ subroutine do_writerestartfiles use modmpi, only : cmyid,myid use modsubgriddata, only : ekm,ekh + use modlsm, only : kmax_soil, tile_lv, tile_hv, tile_bs, tile_ws, tile_aq implicit none integer imin,ihour integer i,j,k,n character(50) name,linkname - ihour = floor(rtimee/3600) imin = floor((rtimee-ihour * 3600) /3600. * 60.) name = 'initdXXXhXXmXXXXXXXX.XXX' @@ -1137,8 +1184,39 @@ subroutine do_writerestartfiles linkname = name linkname(6:11) = "latest" call system("ln -s -f "//name //" "//trim(output_prefix)//linkname) - end if + else if (isurf == 11) then + name(5:5)='l' + open (ifoutput,file=name,form='unformatted') + write(ifoutput) (((tsoil(i,j,k), i=1,i2), j=1,j2), k=1,kmax_soil) + write(ifoutput) (((phiw (i,j,k), i=1,i2), j=1,j2), k=1,kmax_soil) + write(ifoutput) ((tskin (i,j), i=1,i2), j=1,j2) + write(ifoutput) ((Wl (i,j), i=1,i2), j=1,j2) + + ! Sub-grid tiles + write(ifoutput) ((tile_lv%thlskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_hv%thlskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_bs%thlskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_ws%thlskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_aq%thlskin(i,j), i=1,i2), j=1,j2) + + write(ifoutput) ((tile_lv%qtskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_hv%qtskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_bs%qtskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_ws%qtskin(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_aq%qtskin(i,j), i=1,i2), j=1,j2) + + write(ifoutput) ((tile_lv%obuk(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_hv%obuk(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_bs%obuk(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_ws%obuk(i,j), i=1,i2), j=1,j2) + write(ifoutput) ((tile_aq%obuk(i,j), i=1,i2), j=1,j2) + write(ifoutput) timee + close (ifoutput) + linkname = name + linkname(6:11) = "latest" + call system("ln -s -f "//name //" "//trim(output_prefix)//linkname) + end if if (myid==0) then write(*,'(A,F15.7,A,I4)') 'dump at time = ',rtimee,' unit = ',ifoutput @@ -1177,15 +1255,19 @@ subroutine exitmodules use modradiation, only : exitradiation use modsubgrid, only : exitsubgrid use modsurface, only : exitsurface + use modlsm, only : exitlsm use modthermodynamics, only : exitthermodynamics + use modemission, only : exitemission call exittimedep call exitthermodynamics call exitsurface + call exitlsm call exitsubgrid call exitradiation call exitpois call exitmicrophysics + call exitemission call exitboundary call exitfields call exitglobal diff --git a/src/modstat_nc.f90 b/src/modstat_nc.f90 index f37fde6b..a2640b24 100644 --- a/src/modstat_nc.f90 +++ b/src/modstat_nc.f90 @@ -342,6 +342,7 @@ end subroutine exitstat_nc subroutine writestat_dims_nc(ncid, ncoarse) use modglobal, only : dx,dy,zf,zh,jmax,imax use modsurfdata, only : zsoilc,isurf + use modlsm, only : z_soil use modmpi, only : myidx,myidy implicit none integer, intent(in) :: ncid @@ -374,10 +375,15 @@ subroutine writestat_dims_nc(ncid, ncoarse) iret = nf90_inq_varid(ncid, 'zm', VarID) if (iret==0) iret=nf90_inquire_dimension(ncid, zmID, len=length) if (iret==0) iret = nf90_put_var(ncid, varID, zh(1:length),(/1/)) + if (isurf==1) then iret = nf90_inq_varid(ncid, 'zts', VarID) if (iret==0) iret = nf90_inquire_dimension(ncid, ztsID, len=length) if (iret==0) iret = nf90_put_var(ncid, varID, zsoilc(1:length),(/1/)) + else if (isurf==11) then + iret = nf90_inq_varid(ncid, 'zts', VarID) + if (iret==0) iret = nf90_inquire_dimension(ncid, ztsID, len=length) + if (iret==0) iret = nf90_put_var(ncid, varID, z_soil(1:length),(/1/)) end if end subroutine writestat_dims_nc @@ -385,6 +391,7 @@ end subroutine writestat_dims_nc subroutine writestat_dims_q_nc(ncid,k1,k2) use modglobal, only : dx,dy,zf,zh,jmax,imax use modsurfdata, only : zsoilc,isurf + use modlsm, only : z_soil use modmpi, only : myidx,myidy implicit none integer, intent(in) :: ncid,k1,k2 @@ -413,6 +420,10 @@ subroutine writestat_dims_q_nc(ncid,k1,k2) iret = nf90_inq_varid(ncid, 'zts', VarID) if (iret==0) iret = nf90_inquire_dimension(ncid, ztsID, len=length) if (iret==0) iret = nf90_put_var(ncid, varID, zsoilc(1:length),(/1/)) + else if (isurf==11) then + iret = nf90_inq_varid(ncid, 'zts', VarID) + if (iret==0) iret = nf90_inquire_dimension(ncid, ztsID, len=length) + if (iret==0) iret = nf90_put_var(ncid, varID, z_soil(1:length),(/1/)) end if iret = nf90_inq_varid(ncid, 'zq', VarID) diff --git a/src/modsurface.f90 b/src/modsurface.f90 index 5c5bae13..10d81e9a 100644 --- a/src/modsurface.f90 +++ b/src/modsurface.f90 @@ -79,9 +79,10 @@ subroutine initsurface integer :: i,j,k, landindex, ierr, defined_landtypes, landtype_0 = -1 integer :: tempx,tempy - character(len=1500) :: readbuffer + character(len=1500) :: readbuffer + namelist/NAMSURFACE/ & !< Soil related variables - isurf,tsoilav, tsoildeepav, phiwav, rootfav, & + isurf, tsoilav, tsoildeepav, phiwav, rootfav, & ! Land surface related variables lmostlocal, lsmoothflux, lneutral, z0mav, z0hav, rsisurf2, Cskinav, lambdaskinav, albedoav, Qnetav, cvegav, Wlav, & ! Jarvis-Steward related variables @@ -543,7 +544,7 @@ subroutine initsurface enddo enddo end select - else + else ! not lhetero: if((z0mav == -1 .and. z0hav == -1) .and. (z0 .ne. -1)) then z0mav = z0 z0hav = z0 @@ -613,8 +614,8 @@ subroutine initsurface end if allocate(rs(i2,j2)) + allocate(ra(i2,j2)) if(isurf <= 2) then - allocate(ra(i2,j2)) ! CvH set initial values for rs and ra to be able to compute qskin ra = 50. @@ -700,7 +701,7 @@ subroutine initsurface if (lsplitleaf) then allocate(PARdirField (2:i1,2:j1)) allocate(PARdifField (2:i1,2:j1)) - endif + endif endif return end subroutine initsurface @@ -731,6 +732,11 @@ subroutine surface patchx = 0 patchy = 0 + if (isurf==11) then + ! New LSM, handled by modlsm + return + end if + if (isurf==10) then call surf_user return @@ -874,11 +880,12 @@ subroutine surface phimzf = phim(zf(1)/obl(i,j)) phihzf = phih(zf(1)/obl(i,j)) - + dudz (i,j) = ustar(i,j) * phimzf / (fkar*zf(1))*(upcu/horv) dvdz (i,j) = ustar(i,j) * phimzf / (fkar*zf(1))*(vpcv/horv) dthldz(i,j) = - thlflux(i,j) / ustar(i,j) * phihzf / (fkar*zf(1)) dqtdz (i,j) = - qtflux(i,j) / ustar(i,j) * phihzf / (fkar*zf(1)) + end do end do @@ -907,7 +914,7 @@ subroutine surface phimzf = phim(zf(1)/obl(i,j)) phihzf = phih(zf(1)/obl(i,j)) - + upcu = 0.5 * (u0(i,j,1) + u0(i+1,j,1)) + cu vpcv = 0.5 * (v0(i,j,1) + v0(i,j+1,1)) + cv horv = sqrt(upcu ** 2. + vpcv ** 2.) @@ -990,10 +997,10 @@ subroutine surface svflux(i,j,n) = wsvsurf(n) enddo endif - + phimzf = phim(zf(1)/obl(i,j)) phihzf = phih(zf(1)/obl(i,j)) - + dudz (i,j) = ustar(i,j) * phimzf / (fkar*zf(1))*(upcu/horv) dvdz (i,j) = ustar(i,j) * phimzf / (fkar*zf(1))*(vpcv/horv) dthldz(i,j) = - thlflux(i,j) / ustar(i,j) * phihzf / (fkar*zf(1)) @@ -1164,7 +1171,7 @@ subroutine getobl if(Rib > 0) L = 0.01 if(Rib < 0) L = -0.01 end if - + do while (.true.) iter = iter + 1 Lold = L @@ -1303,7 +1310,7 @@ subroutine getobl if(Rib > 0) L = 0.01 if(Rib < 0) L = -0.01 end if - + do while (.true.) iter = iter + 1 Lold = L @@ -1337,7 +1344,7 @@ subroutine getobl end subroutine getobl - function psim(zeta) + pure function psim(zeta) implicit none real :: psim @@ -1357,7 +1364,7 @@ function psim(zeta) return end function psim - function psih(zeta) + pure function psih(zeta) implicit none @@ -1380,10 +1387,10 @@ end function psih ! stability function Phi for momentum. ! Many functional forms of Phi have been suggested, see e.g. Optis 2015 - ! Phi and Psi above are related by an integral and should in principle match, + ! Phi and Psi above are related by an integral and should in principle match, ! currently they do not. ! FJ 2018: For very stable situations, zeta > 1 add cap to phi - the linear expression is valid only for zeta < 1 - function phim(zeta) + pure function phim(zeta) implicit none real :: phim real, intent(in) :: zeta @@ -1400,8 +1407,8 @@ function phim(zeta) return end function phim - ! stability function Phi for heat. - function phih(zeta) + ! stability function Phi for heat. + pure function phih(zeta) implicit none real :: phih real, intent(in) :: zeta @@ -1418,7 +1425,7 @@ function phih(zeta) return end function phih - + function E1(x) implicit none real :: E1 @@ -1430,7 +1437,7 @@ function E1(x) do k=1,99 !E1sum = E1sum + (-1.0) ** (k + 0.0) * x ** (k + 0.0) / ( (k + 0.0) * factorial(k) ) E1sum = E1sum + (-1.0 * x) ** k / ( k * factorial(k) ) ! FJ changed this for compilation with cray fortran - + end do E1 = -0.57721566490153286060 - log(x) - E1sum @@ -1669,7 +1676,7 @@ subroutine do_lsm real :: Ag, PARdir, PARdif !Variables for 2leaf AGS real :: MW_Air = 28.97 real :: MW_CO2 = 44 - + real :: sinbeta, kdrbl, kdf, kdr, ref, ref_dir real :: iLAI, fSL real :: PARdfU, PARdfD, PARdfT, PARdrU, PARdrD, PARdrT, dirPAR, difPAR @@ -1961,7 +1968,7 @@ subroutine do_lsm gc_inf = LAI(i,j) * sum(weight_g * gnet) else !lsplitleaf - + ! Calculate upscaling from leaf to canopy: net flow CO2 into the plant (An) AGSa1 = 1.0 / (1 - f0) Dstar = D0 / (AGSa1 * (f0 - fmin)) diff --git a/src/modsurfdata.f90 b/src/modsurfdata.f90 index f9f0218c..3fb08b72 100644 --- a/src/modsurfdata.f90 +++ b/src/modsurfdata.f90 @@ -26,8 +26,6 @@ ! Copyright 1993-2009 Delft University of Technology, Wageningen University, Utrecht University, KNMI ! - - module modsurfdata ! implicit none @@ -38,13 +36,13 @@ module modsurfdata ! Soil properties ! Domain-uniform properties - integer, parameter :: ksoilmax = 4 !< Number of soil layers [-] + integer, parameter :: ksoilmax = 4 !< Number of soil layers [-] real :: lambdasat !< heat conductivity saturated soil [W/m/K] real :: Ke !< Kersten number [-] real, allocatable :: zsoil (:) !< Height of bottom soil layer from surface [m] - real, allocatable :: zsoilc (:) !< Height of center soil layer from surface [m] + real, allocatable :: zsoilc (:) !< Height of center soil layer from surface [m] real, allocatable :: dzsoil (:) !< Depth of soil layer [m] real, allocatable :: dzsoilh(:) !< Depth of soil layer between center of layers [m] diff --git a/src/modtimestat.f90 b/src/modtimestat.f90 index d10215ea..d28bfe18 100644 --- a/src/modtimestat.f90 +++ b/src/modtimestat.f90 @@ -65,7 +65,7 @@ module modtimestat real :: qlint, qtint, qrint logical:: store_zi = .false. - !Variables for heterogeneity + ! Variables for heterogeneity real, allocatable :: u0av_patch (:,:) ! patch averaged um at full level real, allocatable :: v0av_patch (:,:) ! patch averaged vm at full level real, allocatable :: w0av_patch (:,:) ! patch averaged wm at full level @@ -74,11 +74,13 @@ module modtimestat real,allocatable, dimension(:,:) :: cc_patch, qlint_patch, qlintmax_patch, qlintmax_patchl, tke_tot_patch real,allocatable, dimension(:,:) :: wmax_patch, wmax_patchl, qlmax_patch, qlmax_patchl, ztopmax_patch, ztopmax_patchl real,allocatable, dimension(:,:) :: ust_patch, qst_patch, tst_patch, wthls_patch, wqls_patch, wthvs_patch - !In combination with isurf = 1 + + ! In combination with isurf = 1 real,allocatable, dimension(:,:) :: Qnet_patch, H_patch, LE_patch, G0_patch, tendskin_patch,rs_patch,ra_patch real,allocatable, dimension(:,:) :: cliq_patch, wl_patch, rsveg_patch, rssoil_patch, tskin_patch, obl_patch real,allocatable, dimension(:,:) :: zi_patch,ziold_patch,we_patch, zi_field + contains !> Initializing Timestat. Read out the namelist, initializing the variables subroutine inittimestat @@ -88,11 +90,13 @@ subroutine inittimestat use modfields, only : thlprof,qtprof,svprof use modsurfdata, only : isurf, lhetero, xpatches, ypatches use modstat_nc, only : lnetcdf, open_nc, define_nc, ncinfo, nctiminfo + use modlsm, only : lags implicit none integer :: ierr,k,location = 1 real :: gradient = 0.0 real, allocatable,dimension(:) :: profile - integer :: i,j + integer :: i,j,vi + character(len=1000) :: line namelist/NAMTIMESTAT/ & !< namelist dtav,ltimestat,blh_thres,iblh_meth,iblh_var,blh_nsamp !! namelist contents @@ -228,8 +232,11 @@ subroutine inittimestat endif if (lnetcdf) then - if(isurf == 1) then + if (isurf == 1) then nvar = 34 + else if (isurf == 11) then + nvar = 78 + if (lags) nvar = nvar + 2 else nvar = 23 end if @@ -273,7 +280,137 @@ subroutine inittimestat call ncinfo(ncname(32,:),'Wl','Liquid water reservoir','m','time') call ncinfo(ncname(33,:),'rssoil','Soil evaporation resistance','s/m','time') call ncinfo(ncname(34,:),'rsveg','Vegitation resistance','s/m','time') + else if (isurf==11) then + vi = 24 + call ncinfo(ncname(vi,:),'Qnet','Net radiation','W/m^2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'H','Sensible heat flux','W/m^2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'LE','Latent heat flux','W/m^2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'G','Ground heat flux','W/m^2','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'obuk_lv','Obukhov length low veg','m','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'obuk_hv','Obukhov length high veg','m','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'obuk_bs','Obukhov length bare soil','m','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'obuk_aq','Obukhov length water','m','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'ustar_lv','Friction velocity low veg','m s-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'ustar_hv','Friction velocity high veg','m s-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'ustar_bs','Friction velocity bare soil','m s-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'ustar_aq','Friction velocity water','m s-1','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'ra_lv','Aerodynamic resistance low veg','s m-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'ra_hv','Aerodynamic resistance high veg','s m-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'ra_bs','Aerodynamic resistance bare soil','s m-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'ra_aq','Aerodynamic resistance water','s m-1','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'f1','Reduction canopy resistance f(swd)','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'f2_lv','Reduction canopy resistance low veg f(theta)','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'f2_hv','Reduction canopy resistance low veg f(theta)','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'f3','Reduction canopy resistance f(VPD)','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'f2b','Reduction soil resistance f(theta)','-','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'rs_lv','Canopy resistance low veg','s m-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'rs_hv','Canopy resistance low veg','s m-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'rs_bs','Soil resistance','s m-1','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'c_lv','Tile fraction low vegetation','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'c_hv','Tile fraction high vegetation','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'c_bs','Tile fraction bare soil','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'c_ws','Tile fraction wet skin','-','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'c_aq','Tile fraction water','-','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'wl','Liquid water reservoir','m','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'H_lv','Sensible heat flux low vegetation','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'H_hv','Sensible heat flux high vegetation','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'H_bs','Sensible heat flux bare soil','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'H_ws','Sensible heat flux wet skin','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'H_aq','Sensible heat flux water','W m-2','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'LE_lv','Latent heat flux low vegetation','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'LE_hv','Latent heat flux high vegetation','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'LE_bs','Latent heat flux bare soil','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'LE_ws','Latent heat flux wet skin','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'LE_aq','Latent heat flux water','W m-2','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'G_lv','Soil heat flux low vegetation','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'G_hv','Soil heat flux high vegetation','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'G_bs','Soil heat flux bare soil','W m-2','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'G_ws','Soil heat flux wet skin','W m-2','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'thlskin_lv','Skin potential temperature low vegetation','K','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'thlskin_hv','Skin potential temperature high vegetation','K','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'thlskin_bs','Skin potential temperature bare soil','K','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'thlskin_ws','Skin potential temperature wet skin','K','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'thlskin_aq','Skin potential temperature water','K','time') + vi = vi+1 + + call ncinfo(ncname(vi,:),'qtskin_lv','Skin specific humidity low vegetation','kg kg-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'qtskin_hv','Skin specific humidity high vegetation','kg kg-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'qtskin_bs','Skin specific humidity bare soil','kg kg-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'qtskin_ws','Skin specific humidity wet skin','kg kg-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'qtskin_ws','Skin specific humidity water','kg kg-1','time') + vi = vi+1 + + if (lags) then + call ncinfo(ncname(vi,:),'an_co2','Net CO2 assimilation','ppb m s-1','time') + vi = vi+1 + call ncinfo(ncname(vi,:),'resp_co2','CO2 respiration soil','ppb m s-1','time') + vi = vi+1 + end if end if + call open_nc(fname, ncid,nrec) if(nrec==0) call define_nc( ncid, NVar, ncname) end if @@ -346,10 +483,13 @@ subroutine timestat use modsurfdata,only : wtsurf, wqsurf, isurf,ustar,thlflux,qtflux,z0,oblav,qts,thls,& Qnet, H, LE, G0, rs, ra, tskin, tendskin, & cliq,rsveg,rssoil,Wl, & - lhetero, xpatches, ypatches, qts_patch, wt_patch, wq_patch, thls_patch,obl,z0mav_patch, wco2av, Anav, Respav,gcco2av + lhetero, xpatches, ypatches, qts_patch, wt_patch, wq_patch, & + thls_patch,obl,z0mav_patch, wco2av, Anav, Respav,gcco2av use modsurface, only : patchxnr,patchynr + use modlsm, only : tile_lv, tile_hv, tile_bs, tile_ws, tile_aq, f1, f2_lv, f2_hv, f3, f2b, lags, an_co2, resp_co2 use modmpi, only : mpi_sum,mpi_max,mpi_min,comm3d,mpierr,myid, D_MPI_ALLREDUCE - use modstat_nc, only : lnetcdf, writestat_nc,nc_fillvalue + use modstat_nc, only : lnetcdf, writestat_nc,nc_fillvalue + use modraddata, only : swd, swu, lwd, lwu implicit none real :: zbaseavl, ztopavl, ztopmaxl, ztop,zbaseminl @@ -365,7 +505,22 @@ subroutine timestat ! lsm variables real :: Qnetavl, Havl, LEavl, G0avl, tendskinavl, rsavl, raavl, tskinavl,Wlavl,cliqavl,rsvegavl,rssoilavl real :: Qnetav, Hav, LEav, G0av, tendskinav, rsav, raav, tskinav,Wlav,cliqav,rsvegav,rssoilav - integer:: i, j, k + + ! LSM tiled variables + real :: obuk_lv_av, obuk_hv_av, obuk_bs_av, obuk_aq_av + real :: ustar_lv_av, ustar_hv_av, ustar_bs_av, ustar_aq_av + real :: ra_lv_av, ra_hv_av, ra_bs_av, ra_aq_av + real :: f1_av, f2_lv_av, f2_hv_av, f3_av, f2b_av + real :: rs_lv_av, rs_hv_av, rs_bs_av + real :: c_lv_av, c_hv_av, c_bs_av, c_ws_av, c_aq_av + real :: H_lv_av, H_hv_av, H_bs_av, H_ws_av, H_aq_av + real :: LE_lv_av, LE_hv_av, LE_bs_av, LE_ws_av, LE_aq_av + real :: G_lv_av, G_hv_av, G_bs_av, G_ws_av + real :: thlskin_lv_av, thlskin_hv_av, thlskin_bs_av, thlskin_ws_av, thlskin_aq_av + real :: qtskin_lv_av, qtskin_hv_av, qtskin_bs_av, qtskin_ws_av, qtskin_aq_av + real :: an_co2_av, resp_co2_av + + integer:: i, j, k, vi ! heterogeneity variables integer:: patchx, patchy @@ -777,6 +932,85 @@ subroutine timestat rssoil_patch = patchsum_1level(rssoil (2:i1, 2:j1)) * (xpatches*ypatches/ijtot) tskin_patch = patchsum_1level(tskin (2:i1, 2:j1)) * (xpatches*ypatches/ijtot) endif + + else if(isurf == 11) then + + Qnet(:,:) = swd(i,j,1) + swu(i,j,1) + lwd(i,j,1) + lwu(i,j,1) + + Qnetav = mean_2d(Qnet) + Hav = mean_2d(H) + LEav = mean_2d(LE) + G0av = mean_2d(G0) + oblav = mean_2d(obl) + + ! Tiled variables + obuk_lv_av = mean_2d(tile_lv%obuk) + obuk_hv_av = mean_2d(tile_hv%obuk) + obuk_bs_av = mean_2d(tile_bs%obuk) + obuk_aq_av = mean_2d(tile_aq%obuk) + + ustar_lv_av = mean_2d(tile_lv%ustar) + ustar_hv_av = mean_2d(tile_hv%ustar) + ustar_bs_av = mean_2d(tile_bs%ustar) + ustar_aq_av = mean_2d(tile_aq%ustar) + + ra_lv_av = mean_2d(tile_lv%ra) + ra_hv_av = mean_2d(tile_hv%ra) + ra_bs_av = mean_2d(tile_bs%ra) + ra_aq_av = mean_2d(tile_aq%ra) + + f1_av = mean_2d(f1) + f2_lv_av = mean_2d(f2_lv) + f2_hv_av = mean_2d(f2_hv) + f3_av = mean_2d(f3) + f2b_av = mean_2d(f2b) + + rs_lv_av = mean_2d(tile_lv%rs) + rs_hv_av = mean_2d(tile_hv%rs) + rs_bs_av = mean_2d(tile_bs%rs) + + c_lv_av = mean_2d(tile_lv%frac) + c_hv_av = mean_2d(tile_hv%frac) + c_bs_av = mean_2d(tile_bs%frac) + c_ws_av = mean_2d(tile_ws%frac) + c_aq_av = mean_2d(tile_aq%frac) + + wlav = mean_2d(wl) + + H_lv_av = mean_2d(tile_lv%H) + H_hv_av = mean_2d(tile_hv%H) + H_bs_av = mean_2d(tile_bs%H) + H_ws_av = mean_2d(tile_ws%H) + H_aq_av = mean_2d(tile_aq%H) + + LE_lv_av = mean_2d(tile_lv%LE) + LE_hv_av = mean_2d(tile_hv%LE) + LE_bs_av = mean_2d(tile_bs%LE) + LE_ws_av = mean_2d(tile_ws%LE) + LE_aq_av = mean_2d(tile_aq%LE) + + G_lv_av = mean_2d(tile_lv%G) + G_hv_av = mean_2d(tile_hv%G) + G_bs_av = mean_2d(tile_bs%G) + G_ws_av = mean_2d(tile_ws%G) + + thlskin_lv_av = mean_2d(tile_lv%thlskin) + thlskin_hv_av = mean_2d(tile_hv%thlskin) + thlskin_bs_av = mean_2d(tile_bs%thlskin) + thlskin_ws_av = mean_2d(tile_ws%thlskin) + thlskin_aq_av = mean_2d(tile_aq%thlskin) + + qtskin_lv_av = mean_2d(tile_lv%qtskin) + qtskin_hv_av = mean_2d(tile_hv%qtskin) + qtskin_bs_av = mean_2d(tile_bs%qtskin) + qtskin_ws_av = mean_2d(tile_ws%qtskin) + qtskin_aq_av = mean_2d(tile_aq%qtskin) + + if (lags) then + an_co2_av = mean_2d(an_co2) + resp_co2_av = mean_2d(resp_co2) + endif + end if ! 9.8 write the results to output file @@ -838,6 +1072,7 @@ subroutine timestat gcco2av close(ifoutput) end if + if (lnetcdf) then vars( 1) = rtimee vars( 2) = cc @@ -879,6 +1114,79 @@ subroutine timestat vars(32) = wlav vars(33) = rssoilav vars(34) = rsvegav + else if (isurf == 11) then + vi = 24 + vars(vi) = Qnetav; vi = vi+1 + vars(vi) = Hav; vi = vi+1 + vars(vi) = LEav; vi = vi+1 + vars(vi) = G0av; vi = vi+1 + + vars(vi) = obuk_lv_av; vi = vi+1 + vars(vi) = obuk_hv_av; vi = vi+1 + vars(vi) = obuk_bs_av; vi = vi+1 + vars(vi) = obuk_aq_av; vi = vi+1 + + vars(vi) = ustar_lv_av; vi = vi+1 + vars(vi) = ustar_hv_av; vi = vi+1 + vars(vi) = ustar_bs_av; vi = vi+1 + vars(vi) = ustar_aq_av; vi = vi+1 + + vars(vi) = ra_lv_av; vi = vi+1 + vars(vi) = ra_hv_av; vi = vi+1 + vars(vi) = ra_bs_av; vi = vi+1 + vars(vi) = ra_aq_av; vi = vi+1 + + vars(vi) = f1_av; vi = vi+1 + vars(vi) = f2_lv_av; vi = vi+1 + vars(vi) = f2_hv_av; vi = vi+1 + vars(vi) = f3_av; vi = vi+1 + vars(vi) = f2b_av; vi = vi+1 + + vars(vi) = rs_lv_av; vi = vi+1 + vars(vi) = rs_hv_av; vi = vi+1 + vars(vi) = rs_bs_av; vi = vi+1 + + vars(vi) = c_lv_av; vi = vi+1 + vars(vi) = c_hv_av; vi = vi+1 + vars(vi) = c_bs_av; vi = vi+1 + vars(vi) = c_ws_av; vi = vi+1 + vars(vi) = c_aq_av; vi = vi+1 + + vars(vi) = wlav; vi = vi+1 + + vars(vi) = H_lv_av; vi = vi+1 + vars(vi) = H_hv_av; vi = vi+1 + vars(vi) = H_bs_av; vi = vi+1 + vars(vi) = H_ws_av; vi = vi+1 + vars(vi) = H_aq_av; vi = vi+1 + + vars(vi) = LE_lv_av; vi = vi+1 + vars(vi) = LE_hv_av; vi = vi+1 + vars(vi) = LE_bs_av; vi = vi+1 + vars(vi) = LE_ws_av; vi = vi+1 + vars(vi) = LE_aq_av; vi = vi+1 + + vars(vi) = G_lv_av; vi = vi+1 + vars(vi) = G_hv_av; vi = vi+1 + vars(vi) = G_bs_av; vi = vi+1 + vars(vi) = G_ws_av; vi = vi+1 + + vars(vi) = thlskin_lv_av; vi = vi+1 + vars(vi) = thlskin_hv_av; vi = vi+1 + vars(vi) = thlskin_bs_av; vi = vi+1 + vars(vi) = thlskin_ws_av; vi = vi+1 + vars(vi) = thlskin_aq_av; vi = vi+1 + + vars(vi) = qtskin_lv_av; vi = vi+1 + vars(vi) = qtskin_hv_av; vi = vi+1 + vars(vi) = qtskin_bs_av; vi = vi+1 + vars(vi) = qtskin_ws_av; vi = vi+1 + vars(vi) = qtskin_aq_av; vi = vi+1 + + if (lags) then + vars(vi) = an_co2_av; vi = vi+1 + vars(vi) = resp_co2_av; vi = vi+1 + end if end if call writestat_nc(ncid,nvar,ncname,vars,nrec,.true.) @@ -952,6 +1260,19 @@ subroutine timestat end subroutine timestat + function mean_2d(var_2d) result(res) + use modglobal, only : i1, j1, ijtot + use modmpi, only : mpi_sum, comm3d, mpierr, d_mpi_allreduce + implicit none + real, intent(in) :: var_2d(:,:) + real :: res, var_sum_l, var_sum + + var_sum_l = sum(var_2d(2:i1,2:j1)) + call d_mpi_allreduce(var_sum_l, var_sum, 1, mpi_sum, comm3d, mpierr) + res = var_sum / ijtot + + end function mean_2d + !>Calculate the boundary layer height !! !! There are 3 available ways to calculate the boundary layer height: diff --git a/src/program.f90 b/src/program.f90 index 22a50ca0..e0e99164 100644 --- a/src/program.f90 +++ b/src/program.f90 @@ -107,7 +107,8 @@ program DALES use modboundary, only : boundary, grwdamp! JvdD ,tqaver use modthermodynamics, only : thermodynamics use modmicrophysics, only : microsources - use modsurface, only : surface + use modsurface, only : surface + use modlsm, only : lsm use modsubgrid, only : subgrid use modforces, only : forces, coriolis, lstend use modradiation, only : radiation @@ -147,12 +148,14 @@ program DALES !use modtilt, only : inittilt, tiltedgravity, tiltedboundary, exittilt !use modparticles, only : initparticles, particles, exitparticles use modnudge, only : initnudge, nudge, exitnudge + use modnudgeboundary, only : initnudgeboundary, nudgeboundary, exitnudgeboundary use modtestbed, only : testbednudge, exittestbed !use modprojection, only : initprojection, projection use modchem, only : initchem,twostep use modcanopy, only : initcanopy, canopy, exitcanopy use modadvection, only : advection - + use moddatetime, only : datetime + use modemission, only : emission implicit none @@ -186,6 +189,7 @@ program DALES call initlsmstat !call initparticles call initnudge + call initnudgeboundary call initbulkmicrostat call initbudget call initvarbudget @@ -208,6 +212,7 @@ program DALES call tstep_update call timedep call samptend(tend_start,firstterm=.true.) + call datetime !----------------------------------------------------- ! 3.1 RADIATION @@ -216,8 +221,9 @@ program DALES call samptend(tend_rad) !----------------------------------------------------- -! 3.2 THE SURFACE LAYER +! 3.2 THE SURFACE LAYER / LAND-SURFACE !----------------------------------------------------- + call lsm call surface !----------------------------------------------------- @@ -241,11 +247,13 @@ program DALES call samptend(tend_ls) call microsources !Drizzle etc. call samptend(tend_micro) + call emission !------------------------------------------------------ ! 3.4 EXECUTE ADD ONS !------------------------------------------------------ call nudge + call nudgeboundary call testbednudge ! call dospecs ! call tiltedgravity @@ -319,6 +327,7 @@ program DALES call exitlsmstat !call exitparticles call exitnudge + call exitnudgeboundary call exittestbed call exitsampling call exitquadrant