upstream_plume.F90 Source File


This file depends on

sourcefile~~upstream_plume.f90~~EfferentGraph sourcefile~upstream_plume.f90 upstream_plume.F90 sourcefile~rksuite_90.f90 rksuite_90.f90 sourcefile~upstream_plume.f90->sourcefile~rksuite_90.f90 sourcefile~boundary_types.f90 boundary_types.f90 sourcefile~upstream_plume.f90->sourcefile~boundary_types.f90 sourcefile~plume_boundary.f90 plume_boundary.F90 sourcefile~upstream_plume.f90->sourcefile~plume_boundary.f90 sourcefile~uniform_gradient_field.f90 uniform_gradient_field.f90 sourcefile~upstream_plume.f90->sourcefile~uniform_gradient_field.f90 sourcefile~plume_boundary.f90->sourcefile~boundary_types.f90

Files dependent on this one

sourcefile~~upstream_plume.f90~~AfferentGraph sourcefile~upstream_plume.f90 upstream_plume.F90 sourcefile~asymmetric_plume.f90 asymmetric_plume.F90 sourcefile~asymmetric_plume.f90->sourcefile~upstream_plume.f90 sourcefile~static_plume.f90 static_plume.F90 sourcefile~static_plume.f90->sourcefile~upstream_plume.f90 sourcefile~plume.f90 plume.F90 sourcefile~plume.f90->sourcefile~upstream_plume.f90

Contents

Source Code


Source Code

!
!  upstream_plume.F90
!  This file is part of ISOFT.
!  
!  Copyright 2017 Chris MacMackin <cmacmackin@gmail.com>
!  
!  This program 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.
!  
!  This program 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, write to the Free Software
!  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
!  MA 02110-1301, USA.
!  

#ifdef DEBUG
#define pure 
#define elemental 
#endif

module upstream_plume_mod
  !* Author: Christopher MacMackin
  !  Date: July 2017
  !  License: GPLv3
  !
  ! Provides a derived type which specifies the boundary conditions
  ! for a 1-D plume model. In order to avoid boundary layer effects,
  ! an ODE solver is used to integrate the plume a little way
  ! upstream, past the boundary layer.
  !
  use iso_fortran_env, only: r8 => real64
  use logger_mod, only: logger => master_logger
  use penf, only: str
  use factual_mod, only: scalar_field, vector_field, uniform_scalar_field, &
                         uniform_vector_field
  use uniform_gradient_field_mod, only: uniform_gradient_field
  use plume_boundary_mod, only: plume_boundary
  use boundary_types_mod, only: free_boundary, dirichlet, neumann
  use rksuite_90
  implicit none
  private

  type(uniform_scalar_field) :: dummy
  
  type, extends(plume_boundary), public :: upstream_plume_boundary
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! A type with procedures for getting the boundary conditions of
    ! the plume model. For given boundary conditions, it integrates
    ! the plume upstream slightly and then returns these when asked
    ! for boundary values. This allows boundary layers, which can
    ! cause numerical difficulties, to be avoided. Inflow boundaries
    ! must be Dirichlet, while outflow boundaries for velocity,
    ! salinity, and temperature are set to have a gradient of zero.
    !
    ! @Warning The `calculate` method __must__ be called prior to use.
    !
    private
    procedure(bound_vals), nopass, pointer :: get_boundaries => null()
      !! Calculate the "actual" boundary values, used to initiate the
      !! integration, for the specified time.
    real(r8) :: distance = 0.05_r8
      !! The distance upstream which the plume should be integrated
    real(r8) :: thickness = 0.1_r8
      !! The thickness of the plume at the inflowing boundary
    real(r8), dimension(:), allocatable :: velocity
      !! The velocity of the plume at the inflowing boundary
    real(r8) :: temperature = 0.0_r8
      !! The tempreature of the plume at the inflowing boundary
    real(r8) :: salinity = 1.0_r8
      !! The salinity of the plume at the inflowing boundary
    real(r8) :: boundary_time
      !! The time at which the boundaries were most recently
      !! calculated
    real(r8), dimension(:), allocatable :: thresholds
      !! Thresholds to use when calculating error in the integration.
  contains
    procedure :: thickness_bound_info => upstream_thickness_info
      !! Indicates the type and depth of the thickness boundary at
      !! different locations.
    procedure :: velocity_bound_info => upstream_info
      !! Indicates the type and depth of the thickness boundary at
      !! different locations.
    procedure :: temperature_bound_info => upstream_info
      !! Indicates the type and depth of the thickness boundary at
      !! different locations.
    procedure :: salinity_bound_info => upstream_info
      !! Indicates the type and depth of the thickness boundary at
      !! different locations.
    procedure :: thickness_bound => upstream_thickness_bound
      !! Produces a field containing the boundary conditions for plume
      !! thickness at the specified location.
    procedure :: velocity_bound => upstream_velocity_bound
      !! Produces a field containing the boundary conditions for plume
      !! velocity at the specified location.
    procedure :: temperature_bound => upstream_temperature_bound
      !! Produces a field containing the boundary conditions for plume
      !! temperature at the specified location.
    procedure :: salinity_bound => upstream_salinity_bound
      !! Produces a field containing the boundary conditions for plume
      !! salinity at the specified location.
    procedure :: calculate => upstream_calculate
      !! Calculates the upstreamed boundary conditions for the given
      !! time and ice thickness.
  end type upstream_plume_boundary

  abstract interface
    pure subroutine bound_vals(time, D, U, T, S)
      import :: r8
      real(r8), intent(in)                :: time
        !! The time at which the boundary values are being calculated
      real(r8), intent(out)               :: D
        !! Plume thickness boundary condition
      real(r8), dimension(:), allocatable, intent(out) :: U
        !! Plume velocity boundary condition
      real(r8), intent(out)               :: T
        !! Plume temperature boundary condition
      real(r8), intent(out)               :: S
        !! Plume salinity boundary condition
    end subroutine bound_vals

    subroutine non_diff(D, U, T, S, b, DU_x, DUU_x, DUT_x, DUS_x)
      import :: scalar_field
      import :: vector_field
      class(scalar_field), intent(in)  :: D
        !! The plume thickness
      class(vector_field), intent(in)  :: U
        !! The plume velocity
      class(scalar_field), intent(in)  :: T
        !! The plume temperature
      class(scalar_field), intent(in)  :: S
        !! The plume salinity
      class(scalar_field), intent(in)  :: b
        !! The debth of the base of the ice shelf
      class(scalar_field), intent(out) :: DU_x
        !! The derivative of the product DU
      class(vector_field), intent(out) :: DUU_x
        !! The derivative of the product DUU
      class(scalar_field), intent(out) :: DUT_x
        !! The derivative of the product DUT
      class(scalar_field), intent(out) :: DUS_x
        !! The derivative of the product DUS
    end subroutine non_diff
  end interface

  interface upstream_plume_boundary
    module procedure constructor
  end interface upstream_plume_boundary

contains

  pure function constructor(bound_calculator, distance, thresholds) &
                                                       result(this)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Constructs a boundary condition object which integrates an IVP
    ! from actual boundary values to calculate the staet of the plume
    ! a little upstream. This can be used to avoid boundary layers.
    !
    procedure(bound_vals)                        :: bound_calculator
      !! Calculates the "actual" inflow boundary conditions, used to
      !! initiate the integration to find the values to use in the
      !! simulation.
    real(r8), intent(in)                         :: distance
      !! The distance upstream which the plume should be integrated.
    real(r8), dimension(:), optional, intent(in) :: thresholds
      !! The thresholds to use when evaluating the error of the
      !! integration. This is done according to the formula
      !! `abs(e) / max(magnitude_y, THRESHOLDS) <= TOLERANCE`.
    type(upstream_plume_boundary) :: this
    this%get_boundaries => bound_calculator
    this%distance = distance
    if (present(thresholds)) this%thresholds = thresholds
  end function constructor

  subroutine upstream_thickness_info(this, location, bound_type, bound_depth)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Indicates that the lower boundary is Dirichlet and the upper
    ! boundary is free.
    !
    class(upstream_plume_boundary), intent(in) :: this
    integer, intent(in)                                :: location
      !! Which boundary information is to be provided for.  The
      !! boundary will be the one normal to dimension of number
      !! `abs(boundary)`. If the argument is negative, then the lower
      !! boundary is returned. If positive, then the upper boundary is
      !! returned.
    integer, intent(out)                               :: bound_type
      !! An integer representing what sort of boundary condition is
      !! used. The integer value corresponding to each boundary type is
      !! specified in the [[boundary_types_mod]].
    integer, intent(out)                               :: bound_depth
      !! The number of layers of data-points needed to specify the
      !! boundary condition.
    select case(location)
    case(-1)
      bound_type = dirichlet
      bound_depth = 1
    case default
      bound_type = free_boundary
      bound_depth = 0
    end select
  end subroutine upstream_thickness_info

  subroutine upstream_info(this, location, bound_type, bound_depth)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Indicates that the lower boundary is Dirichlet and the upper
    ! boundary is Neumann.
    !
    class(upstream_plume_boundary), intent(in) :: this
    integer, intent(in)                        :: location
      !! Which boundary information is to be provided for.  The
      !! boundary will be the one normal to dimension of number
      !! `abs(boundary)`. If the argument is negative, then the lower
      !! boundary is returned. If positive, then the upper boundary is
      !! returned.
    integer, intent(out)                       :: bound_type
      !! An integer representing what sort of boundary condition is
      !! used. The integer value corresponding to each boundary type is
      !! specified in the [[boundary_types_mod]].
    integer, intent(out)                       :: bound_depth
      !! The number of layers of data-points needed to specify the
      !! boundary condition.
    select case(location)
    case(-1)
      bound_type = dirichlet
      bound_depth = 1
    case(1)
      bound_type = neumann
      bound_depth = 1
    case default
      bound_type = free_boundary
      bound_depth = 0
    end select
  end subroutine upstream_info

  function upstream_thickness_bound(this, location) result(bound)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Returns a field containing the thickness boundary values for the
    ! specified boundary location.
    !
    class(upstream_plume_boundary), intent(in) :: this
    integer, intent(in)                        :: location
      !! Which boundary information is to be provided for.  The
      !! boundary will be the one normal to dimension of number
      !! `abs(boundary)`. If the argument is negative, then the lower
      !! boundary is returned. If positive, then the upper boundary is
      !! returned.
    class(scalar_field), pointer               :: bound
    call dummy%allocate_scalar_field(bound)
    select case(location)
    case(-1)
      bound = uniform_scalar_field(this%thickness)
    case default
      bound = uniform_scalar_field(0._r8)
    end select    
    call bound%set_temp() ! Shouldn't need to call this, but for some
                          ! reason being set as non-temporary when
                          ! assignment subroutine returns.
  end function upstream_thickness_bound

  function upstream_velocity_bound(this, location) result(bound)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Returns a field containing the velocity boundary values for
    ! the specified boundary location.
    !
    class(upstream_plume_boundary), intent(in) :: this
    integer, intent(in)                                :: location
      !! Which boundary information is to be provided for.  The
      !! boundary will be the one normal to dimension of number
      !! `abs(boundary)`. If the argument is negative, then the lower
      !! boundary is returned. If positive, then the upper boundary is
      !! returned.
    class(vector_field), pointer                       :: bound
    call dummy%allocate_vector_field(bound)
    select case(location)
    case(-1)
      bound = uniform_vector_field(this%velocity)
    case(1)
      bound = uniform_vector_field([0._r8, 0._r8])
    case default
      bound = uniform_vector_field([0._r8, 0._r8])
    end select
    call bound%set_temp() ! Shouldn't need to call this, but for some
                          ! reason being set as non-temporary when
                          ! assignment subroutine returns.
  end function upstream_velocity_bound

  function upstream_temperature_bound(this, location) result(bound)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Returns a field containing the temperature boundary values for
    ! the specified boundary location.
    !
    class(upstream_plume_boundary), intent(in) :: this
    integer, intent(in)                        :: location
      !! Which boundary information is to be provided for.  The
      !! boundary will be the one normal to dimension of number
      !! `abs(boundary)`. If the argument is negative, then the lower
      !! boundary is returned. If positive, then the upper boundary is
      !! returned.
    class(scalar_field), pointer               :: bound
    call dummy%allocate_scalar_field(bound)
    select case(location)
    case(-1)
      bound = uniform_scalar_field(this%temperature)
    case(1)
      bound = uniform_scalar_field(0._r8)
    case default
      bound = uniform_scalar_field(0._r8)
    end select
    call bound%set_temp() ! Shouldn't need to call this, but for some
                          ! reason being set as non-temporary when
                          ! assignment subroutine returns.
  end function upstream_temperature_bound

  function upstream_salinity_bound(this, location) result(bound)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Returns a field containing the salinity boundary values for
    ! the specified boundary location.
    !
    class(upstream_plume_boundary), intent(in) :: this
    integer, intent(in)                        :: location
      !! Which boundary information is to be provided for.  The
      !! boundary will be the one normal to dimension of number
      !! `abs(boundary)`. If the argument is negative, then the lower
      !! boundary is returned. If positive, then the upper boundary is
      !! returned.
    class(scalar_field), pointer               :: bound
    call dummy%allocate_scalar_field(bound)
    select case(location)
    case(-1)
      bound = uniform_scalar_field(this%salinity)
    case(1)
      bound = uniform_scalar_field(0._r8)
    case default
      bound = uniform_scalar_field(0._r8)
    end select
    call bound%set_temp() ! Shouldn't need to call this, but for some
                          ! reason being set as non-temporary when
                          ! assignment subroutine returns.
  end function upstream_salinity_bound

  subroutine upstream_calculate(this, t, func, b)
    !* Author: Chris MacMackin
    !  Date: July 2017
    !
    ! Calculates the boundary values to use at the current time with
    ! the current ice thickness.
    !
    class(upstream_plume_boundary), intent(inout) :: this
    real(r8), intent(in)                          :: t
      !! The time at which to calculate the boundary values.
    procedure(non_diff)                           :: func
      !! A function which returns the non-diffusive, non-inertial
      !! components of the ODEs describing the plume.
    class(scalar_field), intent(in)               :: b
      !! The depth of the ice shelf base.

    integer :: n
    real(r8) :: D_0, U_0, S_0, T_0, DU_0, DUS_0, DUT_0
    real(r8), dimension(:), allocatable :: Uvec_0, DUvecU_0
    class(scalar_field), pointer :: b_x
    real(r8), dimension(:), allocatable :: y0, ygot, yderiv_got
    real(r8) :: xgot
    integer :: flag
    type(rk_comm_real_1d) :: comm

    call b%guard_temp()
    call this%get_boundaries(t, D_0, Uvec_0, T_0, S_0)

    U_0 = Uvec_0(1)
    DU_0 = D_0*U_0
    DUvecU_0 = DU_0*Uvec_0
    DUT_0 = DU_0*T_0
    DUS_0 = DU_0*S_0
    y0 = [DU_0, DUvecU_0, DUT_0, DUS_0]
    n = size(y0)
    allocate(ygot(n), yderiv_got(n))
    if (.not. allocated(this%thresholds)) then
      allocate(this%thresholds(n))
      this%thresholds = 1._r8
    end if
    b_x => b%d_dx(1)
    call b_x%guard_temp()

    call setup(comm, 0._r8, y0, this%distance, 1e-6_r8, this%thresholds, &
               method='h', h_start=2e-3_r8*this%distance)
    call range_integrate(comm, integrand, this%distance, xgot, ygot, &
                         yderiv_got, flag)
    if (flag /= 1) then
      call logger%error('upstream_plume_boundary%calculate',            &
                        'Could only integrate plume to x = '//str(xgot))
    end if
    !this%thresholds = abs(ygot) + 1e-10
    this%thickness = ygot(1)**2/ygot(2)
    this%velocity = ygot(2:n-2)/ygot(1)
    this%temperature = ygot(n-1)/ygot(1)
    this%salinity = ygot(n)/ygot(1)
    call logger%trivia('upstream_plume_boundary%calculate',             &
         'Calculated boundary values D='//str(this%thickness)//', U='// &
         str(this%velocity)//', T='//str(this%temperature)//', S='//    &
         str(this%salinity))
    if (this%thickness < 0._r8) error stop ('Negative plume thickness found')
    call collect_garbage(comm)
    call b%clean_temp(); call b_x%clean_temp()

  contains

    function integrand(x, y) result(f)
      real(r8), intent(in)               :: x
        !! The independent variable
      real(r8), dimension(:), intent(in) :: y
        !! The dependent variable
      real(r8), dimension(size(y))       :: f
        !! The derivatives

      type(uniform_scalar_field)   :: D, T, S, DU_x, DUT_x, DUS_x
      type(uniform_vector_field)   :: Uvec, DUU_x
      type(uniform_gradient_field) :: b_loc
      D = uniform_scalar_field(y(1)**2/y(2))
      Uvec = uniform_vector_field(y(2:n-2)/y(1))
      T = uniform_scalar_field(y(n-1)/y(1))
      S = uniform_scalar_field(y(n)/y(1))
      b_loc = uniform_gradient_field(b%interpolate([x]), [b_x%interpolate([x])])
      call func(D, Uvec, T, S, b_loc, DU_x, DUU_x, DUT_x, DUS_x)
      f(1) = DU_x%get_value()
      f(2:n-2) = DUU_x%get_value()
      f(n-1) = DUT_x%get_value()
      f(n) = DUS_x%get_value()
    end function integrand
    
  end subroutine upstream_calculate

end module upstream_plume_mod