modelbound_lbc_data_procedures.f90 Source File


Source Code

!-----------------------------------------------------------------------------------------------------------------------------------
! This file is part of ReMKiT1D.
!
! ReMKiT1D 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.
!
! ReMKiT1D 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 ReMKiT1D. If not, see <https://www.gnu.org/licenses/>. 
!
! Copyright 2023 United Kingdom Atomic Energy Authority (stefan.mijin@ukaea.uk)
!-----------------------------------------------------------------------------------------------------------------------------------
submodule (modelbound_lbc_data_class) modelbound_lbc_data_procedures
!! author: Stefan Mijin 
!! 
!! Contains module procedures associated with the modelbound logical boundary condition data class

implicit none

!-----------------------------------------------------------------------------------------------------------------------------------
contains
!-----------------------------------------------------------------------------------------------------------------------------------
module subroutine initMBLBC(this,vSpaceObj,distExtDerivation,distExtReqVarIndices,&
                                ionCurrentVarIndex,isActive,totalCurrentVarIndex,bisTol,isLeftBoundary) 
    !! Varlike modelbound data initialization routine

    class(ModelboundLBCData)        ,intent(inout)  :: this
    type(VSpace)                    ,intent(in)     :: vSpaceObj !! Velocity space object used to get grid data
    class(MatDerivation)            ,intent(in)     :: distExtDerivation !! Extrapolated distribution derivation object
    integer(ik) ,dimension(:)       ,intent(in)     :: distExtReqVarIndices !! Required variable indices for the distribution derivation 
    integer(ik)                     ,intent(in)     :: ionCurrentVarIndex !! Scalar variable index representing ion current at boundary
    logical                         ,intent(in)     :: isActive !! True if the modelbound data should be updated (use to avoid updates on processors with no boundary)
    integer(ik) ,optional           ,intent(in)     :: totalCurrentVarIndex !! Scalar variable index representing total current through boundary. Defaults to 0 current.
    real(rk)    ,optional           ,intent(in)     :: bisTol !! Bisection tolerance for false position method used to calculate cut-off velocity. Defaults to 1e-12
    logical     ,optional           ,intent(in)     :: isLeftBoundary !! True if boundary this data refers to is the left boundary. Defaults to false. 

    if (assertions) then 

            call assertPure(vSpaceObj%isDefined(),"Undefined VSpace object passed to initMBLBC")
            call assertPure(distExtDerivation%isDefined(),"Undefined distribution derivation passed to init MBLBC")

    end if

    this%isActive = isActive

    if (isActive) then 
        allocate(this%fextDeriv,source=distExtDerivation)
        this%fextReqIndices = distExtReqVarIndices
        this%ionCurrentVarIndex = ionCurrentVarIndex
        if (present(totalCurrentVarIndex)) this%totalCurrentVarIndex = totalCurrentVarIndex
        
        this%bisTol = real(1d-12,kind=rk)
        if (present(bisTol)) this%bisTol = bisTol

        this%isLeftBoundary = .false.
        if (present(isLeftBoundary)) this%isLeftBoundary = isLeftBoundary

        this%vGridCopy = vSpaceObj%getVGrid()
        this%dvCopy = vSpaceObj%getVCellWidths()

        allocate(this%vBoundaries(1:size(this%vGridCopy)+1))
        this%vBoundaries(1:size(this%vGridCopy)) = this%vGridCopy - this%dvCopy/2
        this%vBoundaries(size(this%vGridCopy)+1) = this%vGridCopy(size(this%vGridCopy)) + this%dvCopy(size(this%vGridCopy))/2

        this%maxL = vSpaceObj%getNumH() - 1 !Assuming no m>0 harmonics 

        allocate(this%fext(this%maxL+1,size(this%dvCopy)))
        this%fext = 0 

        allocate(this%fixedPLL(0:this%maxL,0:this%maxL))
        this%fixedPLL = fixedPll(this%maxL)
    end if

    call this%makeDefined()
end subroutine initMBLBC
!-----------------------------------------------------------------------------------------------------------------------------------
module subroutine updateDataLBC(this,hostModel,inputVars,updatePriority) 
    !!  Update modelbound data based on input variable container

    class(ModelboundLBCData)              ,intent(inout) :: this 
    class(ModelSurrogate)                 ,intent(in)    :: hostModel !! Host model - unused
    class(VariableContainer)              ,intent(in)    :: inputVars !! Variable container used to calculate modelbound data
    integer(ik) ,optional                 ,intent(in)    :: updatePriority !! Priority for this update call (determines which variables are updated) - unused here

    real(rk) :: ionCurrent, totCurrent ,boundaryWe ,boundaryDens ,boundaryFlux ,boundaryHeatflux

    real(rk) ,allocatable ,dimension(:) :: f0, f1 

    if (assertions) then 
        call assert(this%isDefined(),"Attempted to updated undefined logical boundary modelbound data")
        call assert(inputVars%isDefined(),"Attempted to updated logical boundary modelbound data using undefined inputVars")
    end if

    if (this%isActive) then 
        this%fext = this%fextDeriv%calculate(inputVars%variables,this%fextReqIndices)

        ionCurrent = inputVars%variables(this%ionCurrentVarIndex)%entry(1)
        totCurrent = 0 

        if (allocated(this%totalCurrentVarIndex)) &
        totCurrent = inputVars%variables(this%totalCurrentVarIndex)%entry(1)

        call this%calculateCutOff(ionCurrent,totCurrent)
        call this%calculatePll()

        f0 = this%boundaryHarmonic(0)
        f1 = this%boundaryHarmonic(1)
        boundaryDens = this%interpMom(0,f0)
        boundaryWe = this%interpMom(2,f0)
        boundaryFlux = this%interpMom(1,f1)/real(3,kind=rk)
        boundaryHeatflux = this%interpMom(3,f1)/real(3,kind=rk)
        this%shTemp = 2 * boundaryWe/(3*boundaryDens) - elMass * boundaryFlux**2/boundaryDens**2

        this%gamma = boundaryHeatflux/(boundaryFlux*this%shTemp)
        this%potential = this%coVel**2/this%shTemp
    end if

end subroutine updateDataLBC
!-----------------------------------------------------------------------------------------------------------------------------------
module subroutine copyDataLBC(this,name,container) 
    !! Copy named modelbound data to passed container.

    class(ModelboundLBCData)              ,intent(in)    :: this 
    character(*)                          ,intent(in)    :: name !! Name of data
    real(rk) ,allocatable ,dimension(..)  ,intent(inout) :: container !! Container to copy into 

    if (assertions) call assert(this%isDefined(),"Attempted to copy data from undefined logical boundary modelbound data object")

    select rank (container)
    rank (1)
        if (allocated(container)) then 
            if (ubound(container,1) /= 1 .or. &
               lbound(container,1) /= 1) then 
                deallocate(container)
                allocate(container(1))
            end if
        else
            allocate(container(1))
        end if

        select case (name)
        case ("gamma")
            container = this%gamma
        case ("potential")
            container = this%potential
        case ("coVel")
            container = this%coVel
        case ("shTemp")
            container = this%shTemp
        case default
            error stop "unregistered name requested from ModelboundLBCData object"
        end select
    rank default 
        error stop "container passed to copyDataLBC is not rank 1"
    end select

end subroutine copyDataLBC
!-----------------------------------------------------------------------------------------------------------------------------------
module function getDataDimLBC(this,name) result(dim)
    !! Get data dimensionality - will return 0 for scalars

    class(ModelboundLBCData)              ,intent(in)    :: this 
    character(*)                          ,intent(in)    :: name !! Name of data
    integer(ik)                                          :: dim

    if (assertions) &
    call assert(this%isDefined(),"Attempted to get data dimensionality from undefined logical boundary modelbound data object")

    select case (name)
    case ("gamma")
        dim = 0
    case ("potential")
        dim = 0
    case ("coVel")
        dim = 0
    case ("shTemp")
        dim = 0
    case default
        error stop "unregistered name requested from ModelboundLBCData object"
    end select

end function getDataDimLBC
!-----------------------------------------------------------------------------------------------------------------------------------
function fixedPll(maxL) result(res)
    !! Calculate fixed component of Pll tensor 

    integer(ik)           ,intent(in)     :: maxL
    real(rk) ,allocatable ,dimension(:,:) :: legPl 
    real(rk) ,allocatable ,dimension(:,:) :: res 

    integer(ik) :: i ,j
    
    allocate(legPl(1,0:maxL))
    legPl = allPl([real(0,kind=rk)],maxL)

    allocate(res(0:maxL,0:maxL))
     
    res = 0 
    do i = 0,maxL 

        if (mod(i,2)==1) then

            do j = 0,maxL 

                if (mod(j,2)==1 .and. i == j) res(j,i) = real(1,kind=rk) 

                if (mod(j,2)==0) then 

                    if (j==0) then 
                        res(j,i) = - i * legPl(1,i-1)
                    else
                        res(j,i) = j * legPl(1,j-1)*legPl(1,i) - i * legPl(1,j)*legPl(1,i-1)
                    end if

                    res(j,i) = res(j,i) * real(2*j+1,kind=rk)/(real((j-i)*(i+j+1),kind=rk))
                end if 
            end do 

        end if

    end do

end function fixedPll
!-----------------------------------------------------------------------------------------------------------------------------------
module subroutine calculatePll(this,rowsToUpdate)
    !! Calculate full Pll tensor based on modelbound data 

    class(ModelboundLBCData)              ,intent(inout)    :: this 
    integer(ik) ,optional ,dimension(:)   ,intent(in)       :: rowsToUpdate

    integer(ik) :: maxL ,numV ,i ,j ,k

    real(rk) ,allocatable ,dimension(:) :: xmin

    real(rk) ,allocatable ,dimension(:,:) :: legPl

    integer(ik) ,allocatable ,dimension(:) :: lRows
    if (assertions) call assert(this%isDefined(),"calculatePll called on undefined modelbound LBC data")

    maxL = size(this%fixedPLL,1)-1
    numV = size(this%vGridCopy)

    if (.not. allocated(this%bufferPLL)) then 
        allocate(this%bufferPLL(numV,0:maxL,0:maxL))
    end if
    xmin = - this%coVel/this%vGridCopy
    xmin(this%coCell) = - this%coVel/this%interpCoord(2)

    xmin = max(xmin,real(-1,kind=rk))
    if (present(rowsToUpdate)) then 
        lRows = rowsToUpdate
    else
        lRows = [(i,i=0,maxL)]
    end if
    allocate(legPl(numV,0:maxL))
    legPl = allPl(xmin,maxL)

    do i = 0,maxL
        do j = 1,size(lRows) 
            this%bufferPLL(:,lRows(j),i) = 0

            if (i==lRows(j)) then 
                this%bufferPLL(1:this%coCell-1,lRows(j),i) = real(1,kind=rk)
                this%bufferPLL(this%coCell:numV,lRows(j),i) = real(0.5d0,kind=rk) - xmin(this%coCell:numV)&
                                                                                   *legPl(this%coCell:numV,lRows(j))**2/2

                do k = 1,i-1
                    this%bufferPLL(this%coCell:numV,lRows(j),i) = this%bufferPLL(this%coCell:numV,lRows(j),i) &
                                                - legPl(this%coCell:numV,k)*(xmin(this%coCell:numV)*legPl(this%coCell:numV,k) &
                                                                            - legPl(this%coCell:numV,k+1))
                                                                            
                end do

                this%bufferPLL(:,lRows(j),i) = this%bufferPLL(:,lRows(j),i) * (-1) ** i
            else

                this%bufferPLL(this%coCell:numV,lRows(j),i) = - xmin(this%coCell:numV) * legPl(this%coCell:numV,i) &
                                                      * legPl(this%coCell:numV,lRows(j)) 

                if (i > 0) this%bufferPLL(this%coCell:numV,lRows(j),i) = this%bufferPLL(this%coCell:numV,lRows(j),i) &
                                                                - i*legPl(this%coCell:numV,lRows(j))*legPl(this%coCell:numV,i-1)&
                                                                /real(lRows(j)-i,kind=rk)

                if (lRows(j) > 0) this%bufferPLL(this%coCell:numV,lRows(j),i) = this%bufferPLL(this%coCell:numV,lRows(j),i) &
                                                                 + lRows(j)*legPl(this%coCell:numV,lRows(j)-1)&
                                                                 *legPl(this%coCell:numV,i) &
                                                                 /real(lRows(j)-i,kind=rk)

                this%bufferPLL(this%coCell:numV,lRows(j),i) = this%bufferPLL(this%coCell:numV,lRows(j),i) &
                                                       * (-1)**i * real(2*lRows(j)+1,kind=rk)/real(2*(i+lRows(j)+1),kind=rk)
            end if

            this%bufferPLL(:,lRows(j),i) = this%bufferPLL(:,lRows(j),i) + this%fixedPLL(lRows(j),i)
            if (this%isLeftBoundary) this%bufferPLL(:,lRows(j),i) = this%bufferPLL(:,lRows(j),i) * (-1)**(i+lRows(j))

        end do 

    end do

end subroutine calculatePll
!-----------------------------------------------------------------------------------------------------------------------------------
module subroutine calculateInterps(this)
    !! Calculate interpolation quantities based on modelbound data 

    class(ModelboundLBCData)              ,intent(inout)    :: this 

    if (assertions) call assert(this%isDefined(),"calculateInterps called on undefined modelbound LBC data")

    this%interpCoord(1) = (this%vBoundaries(this%coCell-1)+this%coVel)/2 !Left value
    this%interpCoord(2) = (this%vBoundaries(this%coCell+1)+this%coVel)/2 !Right value

    this%vInterp(1) = real(1,kind=rk) - (this%interpCoord(1)-this%vGridCopy(this%coCell-1))/this%dvCopy(this%coCell-1)
    this%vInterp(2) = real(1,kind=rk) - (this%interpCoord(2)-this%vGridCopy(this%coCell))/this%dvCopy(this%coCell)

    this%interpdv(1) = this%coVel - this%vBoundaries(this%coCell-1)
    this%interpdv(2) = this%vBoundaries(this%coCell+1) - this%coVel 

end subroutine calculateInterps
!-----------------------------------------------------------------------------------------------------------------------------------
module function interpMom(this,order,f) result(res)
    !! Calculate moment of single local harmonic with interpolated grid points near cut-off

    class(ModelboundLBCData)              ,intent(inout)    :: this 
    integer(ik)                           ,intent(in)       :: order 
    real(rk) ,dimension(:)                ,intent(in)       :: f

    real(rk) :: res
    
    if (assertions) call assert(this%isDefined(),"interpMom called on undefined modelbound LBC data")

    res = 0 

    res = res + dot_product(f(1:this%coCell-2),this%vGridCopy(1:this%coCell-2)**(order+2)*this%dvCopy(1:this%coCell-2))

    res = res + f(this%coCell-1) * this%interpCoord(1)**2*this%interpdv(1)*this%vGridCopy(this%coCell-1)**order

    res = res + f(this%coCell) * this%interpCoord(2)**2*this%interpdv(2)*this%vGridCopy(this%coCell)**order

    res = res + dot_product(f(this%coCell+1:size(f)),this%vGridCopy(this%coCell+1:size(f))**(order+2)&
                *this%dvCopy(this%coCell+1:size(f)))

    res = res * 4*pi

end function interpMom
!-----------------------------------------------------------------------------------------------------------------------------------
module function boundaryHarmonic(this,lNum) result(res)
    !! Calculate lNum harmonic at boundary (assuming m=0) using Pll tensor and interpolation

    class(ModelboundLBCData)              ,intent(inout)    :: this 
    integer(ik)                           ,intent(in)       :: lNum 

    real(rk) ,allocatable ,dimension(:) :: res

    integer(ik) :: i

    if (assertions) call assert(this%isDefined(),"boundaryHarmonic called on undefined modelbound LBC data")

    allocate(res(size(this%vGridCopy)))

    res = 0 

    do i = 1,this%coCell-2
        res(i) = dot_product(this%bufferPLL(i,lNum,:),this%fext(:,i))
    end do

    !Left interpolated cell
    res(this%coCell-1) = dot_product(this%bufferPLL(this%coCell-1,lNum,:),this%vInterp(1) * this%fext(:,this%coCell-1) &
                                                                    + (real(1,kind=rk)-this%vInterp(1))*this%fext(:,this%coCell))
    
    !Right interpolated cell
    if (this%coCell == size(res)) then 
        res(this%coCell) = dot_product(this%bufferPLL(this%coCell,lNum,:),this%vInterp(2) * this%fext(:,this%coCell))

    else
        res(this%coCell) = dot_product(this%bufferPLL(this%coCell,lNum,:),this%vInterp(2) * this%fext(:,this%coCell) &
        + (real(1,kind=rk)-this%vInterp(2))*this%fext(:,this%coCell+1))
    end if

    do i = this%coCell+1,size(res)
        res(i) = dot_product(this%bufferPLL(i,lNum,:),this%fext(:,i))
    end do
                                
end function boundaryHarmonic
!-----------------------------------------------------------------------------------------------------------------------------------
module subroutine calculateCutOff(this,ionCurrent,totalCurrent)
    !! Calculate cut-off cell and velocity based on modelbound data and currents

    class(ModelboundLBCData)              ,intent(inout)    :: this 
    real(rk)                              ,intent(in)       :: ionCurrent !! ion current into sheath 
    real(rk)                              ,intent(in)       :: totalCurrent !! total current into sheath to be matched

    integer(ik) :: i 

    real(rk) :: elFlux ,v1,v2,y1, y2
    if (assertions) call assert(this%isDefined(),"calculateCutOff called on undefined modelbound LBC data")

    do i = size(this%vGridCopy),2,-1
        this%coVel = this%vBoundaries(i)
        this%coCell = i
        call this%calculateInterps()
        call this%calculatePll([1])

        elFlux = this%interpMom(1,this%boundaryHarmonic(1))/real(3,kind=rk)
        if (abs(elFlux) > abs(ionCurrent - totalCurrent)) exit
    end do

    if (this%coCell == 2) error stop "calculateCutOff cannot match logical boundary condition fluxes"

    !Initialize false position method variables 

    v1 = this%coVel
    y1 = elFlux - ionCurrent + totalCurrent
    v2 = this%vBoundaries(this%coCell+1)
    this%coVel = v2
    call this%calculateInterps()
    call this%calculatePll([1])

    elFlux = this%interpMom(1,this%boundaryHarmonic(1))/real(3,kind=rk)

    y2 = elFlux - ionCurrent + totalCurrent

    do i = 1,30
        this%coVel = (v1*y2-v2*y1)/(y2-y1)
        call this%calculateInterps()
        call this%calculatePll([1])
        elFlux = this%interpMom(1,this%boundaryHarmonic(1))/real(3,kind=rk)
        if (abs(elFlux-ionCurrent+ totalCurrent)/abs(ionCurrent- totalCurrent) < this%bisTol) exit 

        if ((elFlux-ionCurrent+ totalCurrent) * y1 < 0 ) then 

            v2 = this%coVel
            y2 = elFlux-ionCurrent+ totalCurrent

        else
            v1 = this%coVel
            y1 = elFlux-ionCurrent+ totalCurrent
        end if

    end do

end subroutine 
!-----------------------------------------------------------------------------------------------------------------------------------
pure module function getCoCell(this) result(res)
    !! Getter for the cut-off cell coordinate

    class(ModelboundLBCData)              ,intent(in)    :: this
    integer(ik)                                          :: res

    if (assertions) call assertPure(this%isDefined(),"getCoCell called on undefined modelbound LBC data")

    res = this%coCell

end function getCoCell
!-----------------------------------------------------------------------------------------------------------------------------------
pure module function getPll(this,lNum) result(res)
    !! Getter for the cut-off (lNum,:,:) decomposition tensor

    class(ModelboundLBCData)             ,intent(in)    :: this
    integer(ik)                          ,intent(in)    :: lNum 
    real(rk) ,allocatable ,dimension(:,:)               :: res

    if (assertions) call assertPure(this%isDefined(),"getPll called on undefined modelbound LBC data")

    res = this%bufferPLL(:,lNum,:)

end function getPll
!-----------------------------------------------------------------------------------------------------------------------------------
pure module function getInterpCoords(this) result(res)
    !! Getter for the interpolated cut-off cell coordinates

    class(ModelboundLBCData)  ,intent(in)    :: this
    real(rk) ,dimension(2)                   :: res

    if (assertions) call assertPure(this%isDefined(),"getInterpCoords called on undefined modelbound LBC data")
    
    res = this%interpCoord

end function getInterpCoords
!-----------------------------------------------------------------------------------------------------------------------------------
pure module function getInterpWidths(this) result(res)
    !! Getter for the interpolated cut-off cell widths

    class(ModelboundLBCData)  ,intent(in)    :: this
    real(rk) ,dimension(2)                   :: res

    if (assertions) call assertPure(this%isDefined(),"getInterpWidths called on undefined modelbound LBC data")

    res = this%interpdv 
    
end function getInterpWidths
!-----------------------------------------------------------------------------------------------------------------------------------
pure module function getInterpCoeffs(this) result(res)
    !! Getter for the interpolation coefficients

    class(ModelboundLBCData)  ,intent(in)    :: this
    real(rk) ,dimension(2)                   :: res 

    if (assertions) call assertPure(this%isDefined(),"getInterpCoeffs called on undefined modelbound LBC data")

    res = this%vInterp

end function getInterpCoeffs
!-----------------------------------------------------------------------------------------------------------------------------------
end submodule modelbound_lbc_data_procedures
!-----------------------------------------------------------------------------------------------------------------------------------