! Conformal Cubic Atmospheric Model ! Copyright 2015 Commonwealth Scientific Industrial Research Organisation (CSIRO) ! This file is part of the Conformal Cubic Atmospheric Model (CCAM) ! ! CCAM 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. ! ! CCAM 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 CCAM. If not, see . !------------------------------------------------------------------------------ ! ! THIS FILE CONTAINS MISC SUBROUTINES ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This function converts between two sets of units ! Subroutine calrescale(inunit,outunit,x) Implicit None Character(len=*), intent(in) :: inunit,outunit Real, intent(inout) :: x real, dimension(1) :: xd Character*80 baseunit baseunit='' xd(1)=x If (inunit/=outunit) Then Call baserescale(inunit,xd,1,baseunit,1) Call baserescale(outunit,xd,-1,baseunit,1) End If x=xd(1) Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! The function rescales to a base value for calrescale ! Subroutine baserescale(inunit,x,inverse,baseunit,sz) Implicit None Integer, intent(in) :: inverse,sz Character(len=*), intent(in) :: inunit Character(len=*), intent(inout) :: baseunit Real, dimension(sz), intent(inout) :: x Character*80 actunit actunit='' Select Case(inunit) Case('minute') actunit='hour' If (inverse.EQ.1) Then x=x*60. Else x=x/60. End If Case('minutes') actunit='hour' If (inverse.EQ.1) Then x=x*60. Else x=x/60. End If Case('hour') actunit='hour' ! No change Case('hours') actunit='hour' ! No change Case('hrs') actunit='hour' ! No change Case('Pa') actunit='Pa' ! No change Case('hPa') actunit='Pa' If (inverse.EQ.1) Then x=x*100. Else x=x/100. End If Case('meters') actunit='meters' ! No change Case('none') actunit='none' ! No change Case('') actunit='none' ! No change Case('g/g') actunit='ratio' ! No change Case('kg/kg') actunit='ratio' ! No change Case('W/m^2') actunit='W/m^2' ! No change Case('W/m2') actunit='W/m^2' ! No change Case('%') actunit='ratio' If (inverse.EQ.1) Then x=x*100. Else x=x/100. End If Case('ratio') actunit='ratio' ! No change Case('K') actunit='K' ! No change Case('Kelvin') actunit='K' ! No change Case('C') actunit='K' If (inverse.EQ.1) Then x=x+273.16 Else x=x-273.16 End If Case('gpm') actunit='gpm' ! No change Case('m2/s2') actunit='gpm' If (inverse.EQ.1) Then x=x/0.98 Else x=x*0.98 End If Case('kg/m^2/s') actunit='mm/hr' If (inverse.EQ.1) Then x=x*3600. Else x=x/3600. End If Case('mm/day') actunit='mm/hr' If (inverse.EQ.1) Then x=x/24. Else x=x*24. End If Case('mm/hr') actunit='mm/hr' ! No change Case DEFAULT Write(6,*) "ERROR: Unknown unit ",trim(inunit)," for conversion." Write(6,*) " Please contact MJT and get him to fix this." call finishbanner Stop -1 End Select If (inverse.EQ.1) Then baseunit=actunit Else If ((baseunit.NE.actunit).AND.(actunit.NE.'none')) Then Write(6,*) "ERROR: Mismatched units ",trim(baseunit)," and ",trim(actunit) call finishbanner Stop -1 End If End If Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine cleans a string from 0's to spaces (32's) ! Subroutine stringclean(instr) Implicit None Character(len=*), intent(inout) :: instr Integer i,iend iend=Len(instr) Do i=1,iend If (Ichar(instr(i:i)).EQ.0) Then instr(i:i)=Char(32) End If End Do Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine rescales a 1D field ! Subroutine fieldrescale(inunit,outunit,f,asize) Implicit None Character(len=*), intent(in) :: inunit,outunit Character*80 baseunit Integer, intent(in) :: asize Real, dimension(1:asize), intent(inout) :: f Integer i ! Not a simple multiplication factor. Need to rescale point-by-point. If (inunit/=outunit) Then baseunit='' Call baserescale(inunit,f,1,baseunit,asize) Call baserescale(outunit,f,-1,baseunit,asize) End If Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine rescales a 4D field ! Subroutine arrfieldrescale(inunit,outunit,f,asize) Implicit None Character(len=*), intent(in) :: inunit,outunit Integer, dimension(1:4), intent(in) :: asize Real, dimension(1:asize(1),1:asize(2),1:asize(3),1:asize(4)), intent(inout) :: f real, dimension(1:asize(1)*asize(2)*asize(3)*asize(4)) :: dum Integer i i=asize(1)*asize(2)*asize(3)*asize(4) dum(:)=reshape(f,(/i/)) Call fieldrescale(inunit,outunit,dum(:),i) f(:,:,:,:)=reshape(dum(:),asize(:)) Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine advances the date by a specified number of minutes ! Subroutine advdate(indate,outdate,ot) Implicit None Integer, intent(in) :: ot Integer, dimension(1:6), intent(in) :: indate Integer, dimension(1:6), intent(out) :: outdate Integer i,maxday outdate=indate outdate(5)=outdate(5)+ot i=Int(Real(outdate(5))/60.) outdate(5)=mod(outdate(5),60) outdate(4)=outdate(4)+i i=Int(Real(outdate(4))/24.) outdate(4)=mod(outdate(4),24) maxday=0 outdate(3)=outdate(3)+i Do While(outdate(3)>maxday) Select Case(outdate(2)) Case(1) maxday=31 Case(2) maxday=28 If (Mod(outdate(1),4)==0) maxday=29 if (mod(outdate(1),100)==0) maxday=28 if (mod(outdate(1),400)==0) maxday=29 Case(3) maxday=31 Case(4) maxday=30 Case(5) maxday=31 Case(6) maxday=30 Case(7) maxday=31 Case(8) maxday=31 Case(9) maxday=30 Case(10) maxday=31 Case(11) maxday=30 Case(12) maxday=31 Case DEFAULT Write(6,*) "ERROR: Internal error in advdate" call finishbanner Stop -1 End Select If (outdate(3)>maxday) Then outdate(3)=outdate(3)-maxday outdate(2)=outdate(2)+1 maxday=0 If (outdate(2)>12) Then outdate(1)=outdate(1)+1 outdate(2)=1 End If End If End Do Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine interpolates field from sigma levels to meters ! Subroutine convertlvl(arrdata,inlvl,outlvl,arrsize) Implicit None Integer, dimension(3), intent(in) :: arrsize Real, dimension(arrsize(1),arrsize(2),arrsize(3)), intent(in) :: inlvl Real, dimension(arrsize(3)), intent(in) :: outlvl Real, dimension(arrsize(1),arrsize(2),arrsize(3)), intent(inout) :: arrdata Real, dimension(arrsize(3)) :: tempdata Integer i,j,k Do j=1,arrsize(2) Do i=1,arrsize(1) ! Interpolate between levels Do k=1,arrsize(3) Call lineintp(arrdata(i,j,:),inlvl(i,j,:),outlvl(k),tempdata(k),arrsize(3)) End Do arrdata(i,j,:)=tempdata End Do End Do Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine integrates temperature to determine height of each ! sigma level in meters. Currently, we assume the hydrostatic approximation. ! Subroutine calsigmalevel(slvl,olvl,arrsize,tempdata) Implicit None Integer, dimension(1:3), intent(in) :: arrsize Real, dimension(arrsize(3)), intent(in) :: slvl Real, dimension(arrsize(1),arrsize(2),arrsize(3)), intent(out) :: olvl Real, dimension(arrsize(1),arrsize(2),arrsize(3)), intent(in) :: tempdata Real, dimension(arrsize(3)) :: ilvl Integer i,j,k olvl=0. ilvl=Log(slvl) Do k=2,arrsize(3) olvl(:,:,k)=olvl(:,:,k-1)+0.5*(tempdata(:,:,k-1)+tempdata(:,:,k))*(ilvl(k)-ilvl(k-1)) End Do olvl=(-287./9.81)*olvl Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine linearly interpolates along an array ! Subroutine lineintp(dataval,posin,posout,oval,num) Implicit None Integer, intent(in) :: num Real, intent(in) :: posout Real, intent(out) :: oval Real, dimension(num), intent(in) :: dataval,posin Integer apos(1),bpos(1) Real m If (posoutMaxval(posin)) Then Write(6,*) "ERROR: Must extrapolate above highest value" call finishbanner Stop -1 end if ! interpolate bpos=Minloc(posin,posin>=posout) apos=Maxloc(posin,posin<=posout) If (apos(1)==bpos(1)) Then oval=dataval(apos(1)) Else m=(dataval(bpos(1))-dataval(apos(1)))/(posin(bpos(1))-posin(apos(1))) oval=m*(posout-posin(apos(1)))+dataval(apos(1)) End If Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine is a bilinear interpolator ! Real function ipol(dat,serlon,serlat) Implicit None Real, intent(in), dimension(2,2) :: dat Real, intent(in) :: serlon,serlat Real b,c,d ! Very basic for now. Later replace with a bicubic. ! Current : z = a + b x + c y + d x y d=dat(2,2)-dat(1,2)-dat(2,1)+dat(1,1) b=dat(2,1)-dat(1,1) c=dat(1,2)-dat(1,1) ipol=b*serlon+c*serlat+d*serlon*serlat+dat(1,1) Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This function converts a string to a real (with error checking) ! Real function sr(instr) Implicit None Character(len=*), intent(in) :: instr Integer ierr Read(instr,*,iostat=ierr) sr if (ierr/=0) then Write(6,*) "ERROR: String "//trim(instr)//" is not a number." call finishbanner Stop -1 end if Return End !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine calculates the saturation vapor pressure ! subroutine getqsat(qsat,temp,ps) implicit none real, intent(in) :: temp,ps real, intent(out) :: qsat real, dimension(0:220), save :: table real esatf,tdiff logical, save :: first=.true. if (first) then table(0:4)= (/ 1.e-9, 1.e-9, 2.e-9, 3.e-9, 4.e-9 /) !-146C table(5:9)= (/ 6.e-9, 9.e-9, 13.e-9, 18.e-9, 26.e-9 /) !-141C table(10:14)= (/ 36.e-9, 51.e-9, 71.e-9, 99.e-9, 136.e-9 /) !-136C table(15:19)= (/ 0.000000188, 0.000000258, 0.000000352, 0.000000479, 0.000000648 /) !-131C table(20:24)= (/ 0.000000874, 0.000001173, 0.000001569, 0.000002090, 0.000002774 /) !-126C table(25:29)= (/ 0.000003667, 0.000004831, 0.000006340, 0.000008292, 0.00001081 /) !-121C table(30:34)= (/ 0.00001404, 0.00001817, 0.00002345, 0.00003016, 0.00003866 /) !-116C table(35:39)= (/ 0.00004942, 0.00006297, 0.00008001, 0.0001014, 0.0001280 /) !-111C table(40:44)= (/ 0.0001613, 0.0002026, 0.0002538, 0.0003170, 0.0003951 /) !-106C table(45:49)= (/ 0.0004910, 0.0006087, 0.0007528, 0.0009287, 0.001143 /) !-101C table(50:55)= (/ .001403, .001719, .002101, .002561, .003117, .003784 /) !-95C table(56:63)= (/ .004584, .005542, .006685, .008049, .009672,.01160,.01388,.01658 /) !-87C table(64:72)= (/ .01977, .02353, .02796,.03316,.03925,.04638,.05472,.06444,.07577 /) !-78C table(73:81)= (/ .08894, .1042, .1220, .1425, .1662, .1936, .2252, .2615, .3032 /) !-69C table(82:90)= (/ .3511, .4060, .4688, .5406, .6225, .7159, .8223, .9432, 1.080 /) !-60C table(91:99)= (/ 1.236, 1.413, 1.612, 1.838, 2.092, 2.380, 2.703, 3.067, 3.476 /) !-51C table(100:107)=(/ 3.935,4.449, 5.026, 5.671, 6.393, 7.198, 8.097, 9.098 /) !-43C table(108:116)=(/ 10.21, 11.45, 12.83, 14.36, 16.06, 17.94, 20.02, 22.33, 24.88 /) !-34C table(117:126)=(/ 27.69, 30.79, 34.21, 37.98, 42.13, 46.69,51.70,57.20,63.23,69.85 /) !-24C table(127:134)=(/ 77.09, 85.02, 93.70, 103.20, 114.66, 127.20, 140.81, 155.67 /) !-16C table(135:142)=(/ 171.69, 189.03, 207.76, 227.96 , 249.67, 272.98, 298.00, 324.78 /) !-8C table(143:150)=(/ 353.41, 383.98, 416.48, 451.05, 487.69, 526.51, 567.52, 610.78 /) !0C table(151:158)=(/ 656.62, 705.47, 757.53, 812.94, 871.92, 934.65, 1001.3, 1072.2 /) !8C table(159:166)=(/ 1147.4, 1227.2, 1311.9, 1401.7, 1496.9, 1597.7, 1704.4, 1817.3 /) !16C table(167:174)=(/ 1936.7, 2063.0, 2196.4, 2337.3, 2486.1, 2643.0, 2808.6, 2983.1 /) !24C table(175:182)=(/ 3167.1, 3360.8, 3564.9, 3779.6, 4005.5, 4243.0, 4492.7, 4755.1 /) !32C table(183:190)=(/ 5030.7, 5320.0, 5623.6, 5942.2, 6276.2, 6626.4, 6993.4, 7377.7 /) !40C table(191:197)=(/ 7780.2, 8201.5, 8642.3, 9103.4, 9585.5, 10089.0, 10616.0 /) !47C table(198:204)=(/ 11166.0, 11740.0, 12340.0, 12965.0, 13617.0, 14298.0, 15007.0 /) !54C table(205:211)=(/ 15746.0, 16516.0, 17318.0, 18153.0, 19022.0, 19926.0, 20867.0 /) !61C table(212:218)=(/ 21845.0, 22861.0, 23918.0, 25016.0, 26156.0, 27340.0, 28570.0 /) !68C table(219:220)=(/ 29845.0, 31169.0 /) first=.false. end if tdiff=min(max( temp-123.16, 0.), 219.) esatf=(1.-(tdiff-aint(tdiff)))*table(int(tdiff))+ (tdiff-aint(tdiff))*table(int(tdiff)+1) qsat=.622*esatf/(ps-esatf) return end subroutine getqsat !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! This subroutine finds the nearest valid value (i.e., for filling) ! subroutine findnear(pxy,i,j,sermsk,rlld,ecodim) implicit none integer, dimension(2), intent(out) :: pxy integer, dimension(2), intent(in) :: ecodim integer, intent(in) ::i,j integer ii,jj real minval,dismsk real, dimension(ecodim(1),ecodim(2),2), intent(in) :: rlld logical, dimension(ecodim(1),ecodim(2)), intent(in) :: sermsk minval=9.E9 pxy(1)=1 pxy(2)=1 do jj=1,ecodim(2) do ii=1,ecodim(1) if (sermsk(ii,jj)) then dismsk=abs(rlld(i,j,1)-rlld(ii,jj,1)) if (dismsk>180.) dismsk=abs(360.-dismsk) dismsk=dismsk**2+(rlld(i,j,2)-rlld(ii,jj,2))**2 if (dismsk0)then av(1) = a_unpack(i+1,j,n) av(2) = a_unpack(i-1,j,n) av(3) = a_unpack(i,j+1,n) av(4) = a_unpack(i,j-1,n) a_io_unpack(i,j,n) = sum(av,mask)/real(neighb) land_b(i,j,n) = .true. finish_mask(i,j,n) = .true. else iminb=min(i,iminb) imaxb=max(i,imaxb) jminb=min(j,jminb) jmaxb=max(j,jmaxb) endif endif end do end do imin(n)=iminb imax(n)=imaxb jmin(n)=jminb jmax(n)=jmaxb end do end do do n = 0,5 a_io(n*ik*ik+1:(n+1)*ik*ik,ii) = reshape( a_io_unpack(1:ik,1:ik,n), (/ ik*ik /) ) end do end do ! ii loop return end