SpanLib - Fortran 90 library source
module spanlib
contains
subroutine sl_pca(ff, nkeep, xeof, pc, ev, weights, useteof)
implicit none
real, intent(in) :: ff(:,:)
integer, intent(in) :: nkeep
real, intent(out),optional :: pc(size(ff,2),nkeep), &
& xeof(size(ff,1),nkeep), ev(nkeep)
real, intent(in), optional :: weights(:)
integer, intent(in), optional :: useteof
integer :: ii,ij,nn,ns,nt
real, allocatable :: cov(:,:)
real, allocatable :: wff(:,:), ww(:), zeof(:,:), zff(:,:)
real, allocatable :: eig(:)
integer :: zuseteof, znkeepmax, i,j
ns = size(ff,1)
nt = size(ff,2)
znkeepmax = 100
if(nkeep>znkeepmax)then
print*,'[pca] You want to keep a number of PCs '//&
& 'greater than ',znkeepmax
return
end if
if(.not.present(xeof).and..not.present(pc)&
&.and..not.present(ev))then
print*,'[pca] Nothing to do. Quit.'
return
end if
zuseteof = -1
if(present(useteof))zuseteof = useteof
if(zuseteof<=0)then
if(ns>nt)then
zuseteof=1
else
zuseteof=0
endif
endif
znkeepmax=100
if(zuseteof)then
if(nkeep>znkeepmax)then
print*,'[pca] You want to keep a number of PCs '//&
&'greater than the number of EOF:',nt
return
end if
else
if(nkeep>znkeepmax)then
print*,'[pca] You want to keep a number of PCs '//&
&'greater than the number of EOF:',ns
return
end if
end if
allocate(zff(ns,nt))
zff = ff - spread(sum(ff,dim=2)/real(nt), ncopies=nt, dim=2)
allocate(ww(ns))
allocate(wff(ns,nt))
ww = 1.
if(present(weights))then
ww(:) = weights * real(ns) / sum(weights)
where(ww==0.)
ww = 1.
end where
do i = 1, nt
wff(:,i) = zff(:,i) * sqrt(ww)
end do
else
wff = zff
end if
if(zuseteof==1)then
allocate(cov(nt,nt))
allocate(eig(nt))
do i=1,nt
do j=1,i
cov(i,j) = dot_product(wff(:,i),wff(:,j))
cov(j,i) = cov(i,j)
end do
end do
cov = cov / float(ns)
deallocate(wff)
call sl_diasym(cov,eig)
if(present(pc).or.present(xeof))then
allocate(zeof(ns,nkeep))
call sgemm('N','N',ns,nkeep,nt,1.,zff,ns, &
& cov(:,nt:nt-nkeep+1:-1),nt,0.,zeof,ns)
deallocate(cov)
do i = 1, nkeep
zeof(:,i) = zeof(:,i) / &
& sqrt(dot_product(ww(:), zeof(:,i)**2))
end do
if(.not.present(pc))then
deallocate(ww)
end if
else
deallocate(cov)
end if
if(present(ev))then
ev = eig(nt:nt-nkeep+1:-1)
end if
else
allocate(cov(ns,ns))
allocate(eig(ns))
cov = 0.
do i=1,ns
do j=1,i
cov(i,j) = dot_product(wff(i,:), wff(j,:))
cov(j,i) = cov(i,j)
end do
end do
cov = cov / float(nt)
deallocate(wff)
call sl_diasym(cov,eig)
if(present(xeof).or.present(pc))then
allocate(zeof(ns,nkeep))
do i = 1, nkeep
zeof(:,i) = cov(:,ns-i+1) / sqrt(ww(:))
end do
end if
deallocate(cov)
if(present(ev))then
ev = eig(ns:ns-nkeep+1:-1)
end if
end if
if(present(xeof))then
xeof = zeof
if(.not.present(pc)) deallocate(zeof)
end if
if(present(pc))then
if(present(weights))then
do i=1, nt
zff(:,i) = zff(:,i) * ww(:)
end do
end if
call sgemm('T','N',nt,nkeep,ns,1.,zff,ns, &
& zeof,ns,0.,pc,nt)
do i = 1, nkeep
pc(:,i) = pc(:,i) / dot_product(zeof(:,i)**2, ww(:))
end do
end if
end subroutine sl_pca
subroutine sl_pcarec(xeof, pc, ffrec, istart, iend)
implicit none
real, intent(in) :: xeof(:,:), pc(:,:)
real, intent(out) :: ffrec(size(xeof,1),size(pc,1))
integer,intent(in), optional :: istart, iend
integer :: nkept, itmp, zistart, ziend, nt, ns, i
real, allocatable :: zpc(:,:)
nkept = size(xeof,2)
zistart=1
if(present(istart))zistart=istart
if(present(iend))then
ziend=iend
else
ziend=nkept
end if
if(zistart.lt.1.or.zistart.gt.nkept)then
zistart=1
print*,'[pcarec] istart lower than 1 => set to 1'
end if
if(ziend.lt.1.or.ziend.gt.nkept)then
ziend=nkept
print*,'[pcarec] iend greater than the number '//&
&'of avalaible modes => reduced to ',ziend
end if
if(zistart>ziend)then
itmp=ziend
ziend=zistart
zistart=itmp
print*,'[pcarec] istart > iend => inversion'
end if
ns = size(xeof,1)
nt = size(pc,1)
ffrec = 0.
if(nt<ns) then
do i = 1, nt
ffrec(:, i) = ffrec(:, i) + &
& matmul(xeof(:, zistart:ziend), pc(i, zistart:ziend))
end do
else
allocate(zpc(ziend-zistart+1, nt))
zpc = transpose(pc(:, zistart:ziend))
do i = 1, ns
ffrec(i, :) = ffrec(i, :) + &
& matmul(xeof(i, zistart:ziend), zpc)
end do
end if
end subroutine sl_pcarec
subroutine sl_mssa(ff, nwindow, nkeep, steof, stpc, ev)
implicit none
real, intent(in) :: ff(:,:)
integer,intent(in) :: nwindow, nkeep
real, intent(out), optional :: steof(size(ff,1)*nwindow, nkeep), &
& stpc(size(ff,2)-nwindow+1, nkeep), ev(nkeep)
real, allocatable :: cov(:,:), eig(:), zff(:,:), zsteof(:,:), wpc(:)
real :: wsteof
integer :: nchan, nsteof, nt, znkeepmax
integer :: iw, iw1, iw2, i1, i2, im, ic1, ic2
nchan = size(ff,1)
nsteof = nchan * nwindow
nt = size(ff,2)
znkeepmax = 100
if(nkeep>znkeepmax)then
print*,'[pca] You want to keep a number of PCs '//&
& 'greater than ',znkeepmax
return
else if(nkeep>nsteof) then
print*,'[pca] You want to keep a number of PCs greater '// &
& 'than the number of ST-EOFs:',nsteof
return
end if
allocate(zff(nchan, nt))
zff = ff - spread(sum(ff,dim=2)/real(nt), ncopies=nt, dim=2)
allocate(cov(nsteof, nsteof))
do ic1 = 1, nchan
do ic2 = 1, nchan
do iw2 = 1, nwindow
do iw1 = 1, iw2
i1 = (ic1-1) * nwindow + iw1
i2 = (ic2-1) * nwindow + iw2
iw = iw2 - iw1 + 1
cov(i1,i2) = &
& dot_product(zff(ic1, 1 : nt-iw+1), &
& zff(ic2, iw : nt )) / &
& real(nt-iw+1)
cov(i2,i1) = cov(i1,i2)
end do
end do
end do
end do
allocate(eig(nsteof))
call sl_diasym(cov,eig)
if(present(steof).or.present(stpc))then
allocate(zsteof(nsteof, nkeep))
zsteof = cov(:, nsteof : nsteof-nkeep+1 : -1)
deallocate(cov)
if(present(steof))then
steof = zsteof
deallocate(zsteof)
end if
end if
if(present(ev))then
ev = eig(nsteof : nsteof-nkeep+1 : -1)
end if
deallocate(eig)
if(present(stpc))then
allocate(wpc(nt-nwindow+1))
stpc = 0.
do im = 1, nkeep
do iw = 1, nwindow
call sgemm('T','N', nt-nwindow+1, 1, nchan, 1.,&
& zff(:,iw:iw+nt-nwindow), nchan, &
& steof(iw:iw+(nchan-1)*nwindow:nwindow, im),nchan,&
& 0., wpc, nt-nwindow+1)
stpc(:, im) = stpc(:, im) + wpc
end do
stpc(:, im) = stpc(:, im) / sum(steof(:,im)**2)
end do
end if
end subroutine sl_mssa
subroutine sl_mssarec(steof, stpc, nwindow, ffrec, istart, iend)
implicit none
real, intent(in) :: steof(:,:), stpc(:,:)
real, intent(out) :: ffrec(size(steof, 1)/nwindow,&
& size(stpc, 1)+nwindow-1)
integer,intent(in) :: nwindow
integer,intent(in), optional :: istart, iend
integer :: ntpc, nchan, nt, ic, im, iw, nkept, &
& itmp, zistart, ziend
real, allocatable :: reof(:), epc(:,:)
ntpc = size(stpc, 1)
nt = ntpc+nwindow-1
nchan = size(steof, 1)/nwindow
nkept = size(steof, 2)
allocate(reof(nwindow))
allocate(epc(nwindow, ntpc-nwindow+1))
ffrec = 0.
if(present(istart))zistart=istart
if(present(iend))then
ziend=iend
else
ziend=nkept
end if
zistart = 1
if(zistart.lt.1.or.zistart.gt.nkept)then
zistart = 1
print*,'[mssarec] istart lower than 1 => set to 1'
end if
if(ziend.lt.1.or.ziend.gt.nkept)then
ziend = nkept
print*,'[mssarec] iend greater than the number of '// &
& 'avalaible modes => reduced to',iend
end if
if(zistart>ziend)then
itmp = ziend
ziend = zistart
zistart = itmp
print*,'[mssarec] istart > iend => inversion'
end if
ffrec = 0.
do im = zistart, ziend
do iw = 1, nwindow
epc(iw,:) = stpc(iw : iw+ntpc-nwindow, im)
end do
do ic = 1, nchan
reof = steof(nwindow+(ic-1)*nwindow : 1+(ic-1)*nwindow : -1, im)
ffrec(ic, nwindow : ntpc) = ffrec(ic, nwindow : ntpc) + &
& matmul(reof, epc) / real(nwindow)
do iw = 1, nwindow-1
ffrec(ic, iw) = ffrec(ic, iw) + &
& dot_product(reof(nwindow-iw+1:nwindow), &
& stpc(1:iw, im) ) / real(iw)
ffrec(ic, nt-iw+1) = ffrec(ic, nt-iw+1) + &
& dot_product(reof(1:iw), &
& stpc(ntpc-iw+1:ntpc, im) ) / real(iw)
end do
end do
end do
end subroutine sl_mssarec
subroutine sl_phasecomp(ffrec,np,phases,weights,offset,firstphase)
implicit none
integer, intent(in) :: np
real, intent(in) :: ffrec(:,:)
real, intent(in), optional :: weights(:)
real, intent(in), optional :: offset, firstphase
real, intent(out) :: phases(size(ffrec, 1),np)
real, allocatable :: xeof(:,:), pc(:,:)
real :: dpc(size(ffrec,2)), amp(size(ffrec,2))
integer :: nt, iphase
real :: angles(np), projection(size(ffrec,2))
real :: pi, deltarad, pcos, psin, zoffset, zfirstphase
logical :: select_amplitude(size(ffrec,2)), &
& select_phase(size(ffrec,2))
integer :: itime(size(ffrec,2)), nsel, i, ns
integer, allocatable :: isel(:)
nt = size(ffrec,2)
pi = acos(-1.)
itime = (/ (i, i=1, nt) /)
ns = size(ffrec, 1)
if(present(offset))then
zoffset=offset
else
zoffset=0.
end if
allocate(pc(nt,1))
call sl_pca(ffrec, 1, pc=pc, weights=weights)
pc = pc * sqrt(real(nt)/sum(pc**2))
dpc = 0.5 * (eoshift(pc(:,1), 1, pc(nt,1)) - &
& eoshift(pc(:,1), -1, pc(1,1)))
dpc((/1,nt/)) = dpc((/1,nt/)) * 2.
dpc = dpc * sqrt(real(nt)/sum(dpc**2))
amp = sqrt(pc(:,1)**2 + dpc**2)
deltarad = 2 * pi / real(np)
if(present(firstphase))then
zfirstphase = modulo(firstphase * 2 * pi / 360., 2 * pi)
else
zfirstphase = 0.
end if
angles = (/ (real(iphase), iphase=0,np-1) /) * deltarad + &
& zfirstphase
phases = 0.
select_amplitude = amp >= zoffset
do iphase = 1, np
pcos = cos(angles(iphase))
psin = sin(angles(iphase))
projection = (pc(:,1)*pcos+dpc*psin) / amp
select_phase = ( projection >= cos(0.5*deltarad) ) &
& .and. select_amplitude
if(any(select_phase))then
nsel = count(select_phase)
allocate(isel(nsel))
isel = pack(itime, select_phase)
phases(:,iphase) = sum(ffrec(:,isel), dim=2) / real(nsel)
deallocate(isel)
end if
end do
end subroutine sl_phasecomp
subroutine sl_diasym(a,eig)
implicit none
real, intent(inout) :: a(:,:)
real, intent(out) :: eig(size(a,1))
integer :: n
integer :: lwork,inf
real, allocatable :: work(:)
n=size(a,1)
lwork=1+ 6*N + 2*N**2
allocate(work(lwork))
call ssyev('V','U',n,a,n,eig,work,lwork,inf)
end subroutine sl_diasym
end module spanlib