program main ! Stand-alone test program for implicit stationary wave solver ! Based on wave enrgy balance for directionally spread waves, single representative frequency ! 4-sweep implicit method ! This test program is for structured 2D grids such as XBeach, but subroutines find_upwind_neighbours and ! solve_energy_balance2Dstat are for unstructured grids. At the main level arrays are two-dimensional in ! space but at lower level they are 1D. ! ! (c) 2020 Dano Roelvink, IHE Delft ! ! Local arrays real*8, dimension(:,:), allocatable :: x,y ! x,y coordinates of grid real*8, dimension(:,:), allocatable :: zb,hh ! bed level, water depth real*8, dimension(:,:), allocatable :: dhdx,dhdy ! water depth gradients real*8, dimension(:,:), allocatable :: kwav,nwav ! wave number, ratio Cg/C real*8, dimension(:,:), allocatable :: C,Cg ! wave celerity, group velocity real*8, dimension(:,:), allocatable :: fw ! friction coefficient real*8, dimension(:,:), allocatable :: H ! rms wave height real*8, dimension(:,:), allocatable :: Dw,Df ! dissipation due to breaking, bed friction real*8, dimension(:,:), allocatable :: thetam ! mean wave direction real*8, dimension(:,:), allocatable :: uorb ! orbital velocity integer, dimension(:,:), allocatable :: kp ! surrounding points for each grid point (unstructured) logical, dimension(:,:), allocatable :: inner ! mask for inner points (not on any boundary) integer, dimension(:), allocatable :: neumannconnected ! neumann boundary indices connected to inner points integer, dimension(:), allocatable :: seapts ! grid numbers on incoming wave boundary integer*4 :: noseapts,noneumannpts ! number of sea points, neumann boundary points real*8, dimension(:), allocatable :: theta,sinth,costh ! wave angles,sine and cosine of wave angles real*8, dimension(:), allocatable :: dist,ee0 ! input directional distribution, wave energy distribution at sea integer, dimension(:,:,:), allocatable :: prev ! two upwind grid points per grid point and wave direction real*8, dimension(:,:,:), allocatable :: w ! weights of upwind grid points, 2 per grid point and per wave direction real*8, dimension(:,:), allocatable :: ds ! distance to interpolated upwind point, per grid point and direction real*8, dimension(:,:,:), allocatable :: ctheta ! refraction speed, per grid point and direction ! Local input variables integer :: nx,ny ! number of grid cells in two directions integer :: m,n ! number of grid points in two directions real*8 :: dx,dy ! x and y grid sizes (only for regular grid) integer :: testcase=1 ! test case number real*8 :: Tp ! peak wave period real*8 :: Hrms ! incident rms wave height real*8 :: dir0 ! incident mean direction (deg, cartesian definition) real*8 :: thetamean ! incident mean direction (rad, cartesian definition) integer :: ms ! power in cos^ms distribution function real*8 :: zs0 ! mean water level real*8 :: fw0 ! uniform wave friction factor real*8 :: fwcutoff ! depth below which to apply space-varying fw real*8 :: alfa,gamma ! coefficients in Baldock breaking dissipation model character*232 :: gridfile ! name of gridfile (Delft3D .grd format) character*232 :: depfile ! name of bathymetry file (Delft3D .dep format) character*232 :: fwfile ! name of bed friction factor file (Delft3D .dep format) real*8 :: dt ! time step (no limitation) ! Local constants real*8 :: rho ! water density real*8 :: g ! acceleration of gravity real*8 :: sigm ! angular frequency real*8 :: t0,t1,t2,t3,t4 ! timers pi=4d0*atan(1.d0) g=9.81d0 rho=1025.d0 alfa=1.d0 gamma=0.75d0 dt=1000.d0 hmin=0.1d0 ! Case-specific inputs if (testcase==1) then gridfile='buck008.grd' depfile='buck008.dep' fwfile='fw008.dep' fwcutoff=5.d0 fw0=0.01 call read_d3d_grid_dims(gridfile,m,n) nx=m-1 ny=n-1 mn=m*n dir0=-100 Hrms=1.67 Tp=12 zs0=0.d0 ms=10 else nx=200 ny=200 dx=5.d0 dy=5.d0 m=nx+1 n=ny+1 mn=m*n slope=0.02d0 h0=20.d0 dir0=45.d0 Hrms=2.d0 Tp=8.d0 zs0=0.d0 fw0=0.01d0 ms=10 endif ! Definition of directional grid thetamean=dir0 ntheta=18 allocate(theta(ntheta)) allocate(sinth(ntheta)) allocate(costh(ntheta)) allocate(dist(ntheta)) allocate(ee0(ntheta)) thetamin=thetamean-90.d0 thetamax=thetamean+90.d0 dtheta=(thetamax-thetamin)/dble(ntheta) do itheta=1,ntheta theta(itheta)=thetamin+.5d0*dtheta+(itheta-1)*dtheta enddo theta=theta*pi/180.d0 dtheta=dtheta*pi/180.d0 thetamean=thetamean*pi/180.d0 ! Offshore wave energy distribution E0=0.125d0*rho*g*Hrms**2 dist=(cos(theta-thetamean))**ms; where (abs(theta-thetamean)>pi/2.d0) dist=0.d0 ee0=dist/sum(dist)*E0/dtheta ! Allocation of spatial arrays allocate(x(m,n)) allocate(y(m,n)) allocate(zb(m,n)) allocate(hh(m,n)) allocate(inner(m,n)) allocate(dhdx(m,n)) allocate(dhdy(m,n)) allocate(kwav(m,n)) allocate(nwav(m,n)) allocate(C(m,n)) allocate(Cg(m,n)) allocate(fw(m,n)) allocate(H(m,n)) allocate(Dw(m,n)) allocate(Df(m,n)) allocate(thetam(m,n)) allocate(uorb(m,n)) allocate(ctheta(m,n,ntheta)) allocate(kp(mn,9)) allocate(w(mn,ntheta,2)) allocate(prev(mn,ntheta,2)) allocate(ds(mn,ntheta)) allocate(neumannconnected(mn)) allocate(seapts(n)) ! Case-dependent definition of grid-based variables if (testcase==1) then call read_d3d_grid(gridfile,x,y,m,n) call read_d3d_dep(depfile,m,n,zb) call read_d3d_dep(fwfile,m,n,fw) where(zs0-zb>fwcutoff) fw=fw0 hh=zs0-zb else x0=nx/2*dx y0=ny/2*dy do j=1,n do i=1,m x(i,j)=(i-1)*dx y(i,j)=(j-1)*dy hh(i,j)=zs0-max(-h0+(x(i,j))*slope,-h0) enddo enddo fw=fw0 endif hh=max(hh,hmin) ! Definition of inner points (not on boundary) inner=.false. inner(2:nx,2:ny)=.true. ! Definition of Neumann boundary points, according to XBeach definition neumannconnected=0 do i=2,m neumannconnected(i+m)=i enddo do i=2,m neumannconnected((n-2)*m+i)=(n-1)*m+i enddo ! Definition of sea boundary points, according to XBeach definition noseapts=n do j=1,n seapts(j)=(j-1)*m+1 enddo ! Initialization of reference tables kp=0 w=0.d0 prev=0 ds=0.d0 call timer(t0) ! Define surrounding grid points for each grid point call create_surrounding_points(m,n,kp) call timer(t1) write(*,*)'create_surrounding_points: ',t1-t0,' seconds' ! Find upwind neighbours for each grid point and wave direction call find_upwind_neighbours(x,y,mn,theta,ntheta,kp,w,prev,ds) call timer(t2) write(*,*)'find_upwind_neighbours: ',t2-t1,' seconds' ! Compute water depth gradients call slopes(x,y,hh,m,n,dhdx,dhdy) ! Compute celerities and refraction speed call disper_approx(hh,Tp,kwav,nwav,C,Cg,mn) sinth=sin(theta) costh=cos(theta) sigm=2.d0*pi/Tp do itheta=1,ntheta ctheta(:,:,itheta)= sigm/sinh(2.d0*kwav*hh)*(dhdx*sinth(itheta)-dhdy*costh(itheta)) enddo ! Solve the directional wave energy balance on an unstructured grid call solve_energy_balance2Dstat (x,y,mn,w,ds,inner,prev,seapts,noseapts,neumannconnected, & ee0,theta,ntheta,thetamean, & hh,kwav,cg,ctheta,fw,Tp,dt,rho,alfa,gamma, & H,Dw,Df,thetam,uorb) call timer(t3) write(*,*)'computation: ',t3-t2,' seconds' ! Simple output as ASCII blocks call writeblock('x.txt',x,m,n) call writeblock('y.txt',y,m,n) call writeblock('zb.txt',zb,m,n) call writeblock('hh.txt',hh,m,n) call writeblock('cg.txt',cg,m,n) call writeblock('H.txt',H,m,n) call writeblock('thetam.txt',thetam*180.d0/pi,m,n) call writeblock('Dw.txt',Dw,m,n) call writeblock('Df.txt',Df,m,n) call timer(t4) write(*,*)'output: ',t4-t3,' seconds' write(*,*)'total: ',t4-t0,' seconds' pause end program subroutine create_surrounding_points(m,n,kp) integer,intent(in) :: m,n integer, dimension(m*n,9),intent(out) :: kp integer, dimension(9) :: ii,jj,dk integer :: i,j,k ii=(/-1,-1, 0, 1, 1, 1, 0,-1,-1/) jj=(/ 0,-1,-1,-1, 0, 1, 1, 1, 0/) dk=ii+jj*m do j=2,n-1 do i=2,m-1 k=i+(j-1)*m kp(k,:)=k+dk enddo enddo end subroutine create_surrounding_points subroutine find_upwind_neighbours(x,y,mn,theta,ntheta,kp,w,prev,ds) integer, intent(in) :: mn,ntheta ! length of x,y; length of theta real*8, dimension(mn), intent(in) :: x,y ! x, y coordinates of grid integer, dimension(mn,9), intent(in) :: kp ! grid indices of surrounfing points per grid point real*8, dimension(ntheta), intent(in) :: theta ! array of wave angles real*8, dimension(mn,ntheta,2), intent(out) :: w ! per grid point and direction, weight of upwind points integer, dimension(mn,ntheta,2), intent(out) :: prev ! per grid point and direction, indices of upwind points real*8, dimension(mn,ntheta), intent(out) :: ds ! upwind distance to intersection point for each direction real*8, dimension(2) :: xsect,ysect,ww real*8 :: pi,dss,xi,yi integer :: ind1,ind2 integer :: ip integer :: k, itheta ! find upwind neighbours for each cell in an unstructured grid x,y (1d ! vectors) given vector of directions theta pi=4d0*atan(1.d0) np=size(kp,dim=2) do k=1,mn if (kp(k,1)/=0) then do itheta=1,ntheta do ip=1,np-1 ind1=kp(k,ip) ind2=kp(k,ip+1) xsect=(/x(ind1),x(ind2)/) ysect=(/y(ind1),y(ind2)/) call intersect_angle(x(k),y(k),theta(itheta)+pi,xsect,ysect,ww,dss,xi,yi) if (dss/=0) then w(k,itheta,1)=ww(1) w(k,itheta,2)=ww(2) ds(k,itheta)=dss prev(k,itheta,1)=kp(k,ip) prev(k,itheta,2)=kp(k,ip+1) exit endif enddo if (dss==0) then prev(k,itheta,1)=1 prev(k,itheta,2)=1 w(k,itheta,1)=0 w(k,itheta,2)=0 endif enddo endif enddo end subroutine find_upwind_neighbours subroutine intersect_angle(x0,y0,phi,x,y,W,ds,xi,yi) real*8, intent(in) :: x0,y0,phi real*8, dimension(2),intent(in) :: x,y real*8, dimension(2),intent(out) :: W real*8, intent(out) :: ds,xi,yi real*8 :: eps,m,a,b,n,L,d1,d2 eps=1d-3 if (abs(x(2)-x(1))>eps) then m=(y(2)-y(1))/(x(2)-x(1)) a=y(1)-m*x(1) n=tan(phi) b=y0-n*x0 xi=(b-a)/(m-n) yi=a+m*xi else yi=(x(1)-x0)*tan(phi)+y0 xi=x(1) endif L=hypot(x(2)-x(1),y(2)-y(1)) d1=hypot(xi-x(1),yi-y(1)) d2=hypot(xi-x(2),yi-y(2)) ds=hypot(xi-x0,yi-y0) err=hypot((x0-xi)+ds*cos(phi),(y0-yi)+ds*sin(phi)) if (abs(L-d1-d2)1.1*hmin) then if (.not. ok(k)) then ! Only perform computations on wet inner points that are not yet converged (ok) do itheta=1,ntheta k1=prev(k,itheta,1) k2=prev(k,itheta,2) eeprev(itheta)=w(k,itheta,1)*ee(k1,itheta)+w(k,itheta,2)*ee(k2,itheta) cgprev(itheta)=w(k,itheta,1)*cg(k1)+w(k,itheta,2)*cg(k2) enddo do itheta=2,ntheta-1 A(k,itheta)=-ctheta(k,itheta-1)/2/dtheta B(k,itheta)=1/dt+cg(k)/ds(k,itheta)+DoverE(k) C(k,itheta)=ctheta(k,itheta+1)/2/dtheta R(k,itheta)=ee(k,itheta)/dt+cgprev(itheta)*eeprev(itheta)/ds(k,itheta) enddo if (ctheta(k,1)<0) then A(k,1)=0.d0 B(k,1)=1/dt-ctheta(k,1)/dtheta+cg(k)/ds(k,1)+DoverE(k) C(k,1)=ctheta(k,2)/dtheta R(k,1)=ee(k,1)/dt+cgprev(itheta)*eeprev(1)/ds(k,1) else A(k,1)=0.d0 B(k,1)=1.d0/dt C(k,1)=0.d0 R(k,1)=0.d0 endif if (ctheta(k,ntheta)>0) then A(k,ntheta)=-ctheta(k,ntheta-1)/dtheta B(k,ntheta)=1/dt+ctheta(k,ntheta)/dtheta+cg(k)/ds(k,ntheta)+DoverE(k) C(k,ntheta)=0 R(k,ntheta)=ee(k,ntheta)/dt+cgprev(itheta)*eeprev(ntheta)/ds(k,ntheta) else A(k,ntheta)=0.d0 B(k,ntheta)=1.d0/dt C(k,ntheta)=0.d0 R(k,ntheta)=0.d0 endif ! Solve tridiagonal system per point call solve_tridiag(A(k,:),B(k,:),C(k,:),R(k,:),ee(k,:),ntheta) ee(k,:)=max(ee(k,:),0.d0) E(k)=sum(ee(k,:))*dtheta H(k)=sqrt(8*E(k)/rho/g) H(k)=min(H(k),gamma*hh(k)) E(k)=0.125d0*rho*g*H(k)**2 call baldock(g,rho,alfa,gamma,kwav(k),hh(k),H(k),T,1,Dw(k)) uorb(k)=pi*H(k)/T/sinh(kwav(k)*hh(k)) Df(k)=0.28d0*rho*fw(k)*uorb(k)**3 DoverE(k)=(1.d0-fac)*DoverE(k)+fac*(Dw(k)+Df(k))/max(E(k),1.d-6) do itheta=1,ntheta B(k,itheta)=1/dt+cg(k)/ds(k,itheta)+DoverE(k) enddo ! Solve tridiagonal system per point call solve_tridiag(A(k,:),B(k,:),C(k,:),R(k,:),ee(k,:),ntheta) ee(k,:)=max(ee(k,:),0.d0) endif else ee(k,:)=0 endif if (neumannconnected(k)/=0) then ee(neumannconnected(k),:)=ee(k,:) endif endif enddo do k=1,mn ! Compute directionally integrated paraneters ee(k,:)=max(ee(k,:),0.d0) E(k)=sum(ee(k,:))*dtheta H(k)=sqrt(8*E(k)/rho/g) call baldock(g,rho,alfa,gamma,kwav(k),hh(k),H(k),T,1,Dw(k)) uorb(k)=pi*H(k)/T/sinh(kwav(k)*hh(k)) Df(k)=0.28d0*rho*fw(k)*uorb(k)**3 DoverE(k)=(1.d0-fac)*DoverE(k)+fac*(Dw(k)+Df(k))/max(E(k),1.d-6) thetam(k)=atan2(sum(ee(k,:)*sin(theta)),sum(ee(k,:)*cos(theta))) enddo if (sweep==4) then ! Check convergence after all 4 sweeps do k=1,mn dee(k,:)=ee(k,:)-eeold(k,:) diff(k)=maxval(abs(dee(k,:))) if (diff(k)/eemax