bem_boundary.f90 Source File


This file depends on

sourcefile~~bem_boundary.f90~~EfferentGraph sourcefile~bem_boundary.f90 bem_boundary.f90 sourcefile~bem_kinds.f90 bem_kinds.f90 sourcefile~bem_boundary.f90->sourcefile~bem_kinds.f90 sourcefile~bem_types.f90 bem_types.f90 sourcefile~bem_boundary.f90->sourcefile~bem_types.f90 sourcefile~bem_types.f90->sourcefile~bem_kinds.f90

Files dependent on this one

sourcefile~~bem_boundary.f90~~AfferentGraph sourcefile~bem_boundary.f90 bem_boundary.f90 sourcefile~bem_particle_stepper.f90 bem_particle_stepper.f90 sourcefile~bem_particle_stepper.f90->sourcefile~bem_boundary.f90 sourcefile~bem_photoelectron_injection.f90 bem_photoelectron_injection.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_boundary.f90 sourcefile~bem_injection.f90 bem_injection.f90 sourcefile~bem_injection.f90->sourcefile~bem_photoelectron_injection.f90 sourcefile~bem_simulator.f90 bem_simulator.f90 sourcefile~bem_simulator.f90->sourcefile~bem_particle_stepper.f90 sourcefile~bem_app_config_runtime.f90 bem_app_config_runtime.f90 sourcefile~bem_simulator.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_app_config.f90 bem_app_config.f90 sourcefile~bem_simulator.f90->sourcefile~bem_app_config.f90 sourcefile~bem_app_config_particle_runtime.f90 bem_app_config_particle_runtime.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_injection.f90 sourcefile~bem_simulator_charge.f90 bem_simulator_charge.f90 sourcefile~bem_simulator_charge.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_io.f90 bem_simulator_io.f90 sourcefile~bem_simulator_io.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_io.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_simulator_loop.f90 bem_simulator_loop.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_matching_plane_coupling.f90 bem_matching_plane_coupling.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_matching_plane_coupling.f90 sourcefile~bem_simulator_particles.f90 bem_simulator_particles.f90 sourcefile~bem_simulator_particles.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_stats.f90 bem_simulator_stats.f90 sourcefile~bem_simulator_stats.f90->sourcefile~bem_simulator.f90 sourcefile~main.f90 main.f90 sourcefile~main.f90->sourcefile~bem_simulator.f90 sourcefile~main.f90->sourcefile~bem_app_config.f90 sourcefile~bem_app_config_particle_runtime_batch.f90 bem_app_config_particle_runtime_batch.f90 sourcefile~bem_app_config_particle_runtime_batch.f90->sourcefile~bem_app_config_particle_runtime.f90 sourcefile~bem_app_config_particle_runtime_sampling.f90 bem_app_config_particle_runtime_sampling.f90 sourcefile~bem_app_config_particle_runtime_sampling.f90->sourcefile~bem_app_config_particle_runtime.f90 sourcefile~bem_app_config_runtime.f90->sourcefile~bem_app_config_particle_runtime.f90 sourcefile~bem_app_config.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_matching_plane_coupling.f90->sourcefile~bem_app_config.f90 sourcefile~zhao_atlas_main.f90 zhao_atlas_main.f90 sourcefile~zhao_atlas_main.f90->sourcefile~bem_app_config.f90 sourcefile~zhao_response_main.f90 zhao_response_main.f90 sourcefile~zhao_response_main.f90->sourcefile~bem_app_config.f90

Source Code

!> シミュレーションボックス境界(流出/反射/面内再配置反射/周期)を適用するモジュール。
module bem_boundary
  use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
  use bem_kinds, only: dp, i32
  use bem_types, only: sim_config, bc_open, bc_reflect, bc_periodic, bc_redistributed_reflect
  implicit none
  private

  integer(i32), parameter, public :: boundary_event_ok = 0_i32
  integer(i32), parameter, public :: boundary_event_invalid_geometry = 1_i32
  integer(i32), parameter :: boundary_face_all = 63_i32

  type, public :: boundary_event_type
    logical :: has_event = .false.
    real(dp) :: fraction = 0.0_dp
    integer(i32) :: face_mask = 0_i32
    integer(i32) :: face_bc(6) = -1_i32
  end type boundary_event_type

  public :: apply_box_boundary
  public :: find_first_boundary_event
  public :: apply_escape_reflect_periodic_event
contains

  !> 閉じたbox内の始点から候補終点へ向かう線分について、最初のbox面交差を返す。
  subroutine find_first_boundary_event(cfg, x0, x1, event, status)
    type(sim_config), intent(in) :: cfg
    real(dp), intent(in) :: x0(3), x1(3)
    type(boundary_event_type), intent(out) :: event
    integer(i32), intent(out) :: status

    real(dp) :: delta(3), candidate_fraction(3), first_fraction, tie_tolerance
    integer(i32) :: axis, candidate_face(3), n_candidate

    event = boundary_event_type()
    status = boundary_event_ok
    if (.not. all(ieee_is_finite(x0)) .or. .not. all(ieee_is_finite(x1))) then
      status = boundary_event_invalid_geometry
      return
    end if
    if (.not. cfg%use_box) return
    if (.not. valid_event_box(cfg)) then
      status = boundary_event_invalid_geometry
      return
    end if
    if (any(x0 < cfg%box_min) .or. any(x0 > cfg%box_max)) then
      status = boundary_event_invalid_geometry
      return
    end if

    delta = x1 - x0
    if (.not. all(ieee_is_finite(delta))) then
      status = boundary_event_invalid_geometry
      return
    end if

    event%face_bc = boundary_face_conditions(cfg)
    candidate_fraction = 0.0_dp
    candidate_face = 0_i32
    n_candidate = 0_i32
    do axis = 1_i32, 3_i32
      if (delta(axis) > 0.0_dp .and. x1(axis) >= cfg%box_max(axis)) then
        n_candidate = n_candidate + 1_i32
        candidate_fraction(n_candidate) = (cfg%box_max(axis) - x0(axis))/delta(axis)
        candidate_face(n_candidate) = 2_i32*axis
      else if (delta(axis) < 0.0_dp .and. x1(axis) <= cfg%box_min(axis)) then
        n_candidate = n_candidate + 1_i32
        candidate_fraction(n_candidate) = (cfg%box_min(axis) - x0(axis))/delta(axis)
        candidate_face(n_candidate) = 2_i32*axis - 1_i32
      end if
    end do
    if (n_candidate == 0_i32) return
    if (.not. all(ieee_is_finite(candidate_fraction(1:n_candidate))) .or. &
        any(candidate_fraction(1:n_candidate) < 0.0_dp) .or. &
        any(candidate_fraction(1:n_candidate) > 1.0_dp)) then
      event = boundary_event_type()
      status = boundary_event_invalid_geometry
      return
    end if

    first_fraction = minval(candidate_fraction(1:n_candidate))
    if (first_fraction == 0.0_dp) first_fraction = 0.0_dp
    tie_tolerance = 64.0_dp*epsilon(1.0_dp)*max(1.0_dp, abs(first_fraction))
    event%has_event = .true.
    event%fraction = first_fraction
    do axis = 1_i32, n_candidate
      if (abs(candidate_fraction(axis) - first_fraction) <= tie_tolerance) then
        event%face_mask = ior(event%face_mask, shiftl(1_i32, candidate_face(axis) - 1_i32))
      end if
    end do
  end subroutine find_first_boundary_event

  !> 最初のbox eventへescape/reflect/redistributed-reflect/periodicを軸順序に依存せず適用する。
  subroutine apply_escape_reflect_periodic_event( &
    cfg, event, x, v, alive, escaped, status, redistribution_uniform &
    )
    type(sim_config), intent(in) :: cfg
    type(boundary_event_type), intent(in) :: event
    real(dp), intent(inout) :: x(3), v(3)
    logical, intent(inout) :: alive
    logical, intent(out) :: escaped
    integer(i32), intent(out) :: status
    real(dp), intent(in), optional :: redistribution_uniform(3)

    real(dp) :: x_work(3), v_work(3)
    integer(i32) :: axis, low_face, high_face, low_bc, high_bc
    logical :: alive_work, has_open, has_redistributed_reflect

    escaped = .false.
    status = boundary_event_ok
    if (.not. cfg%use_box .or. .not. valid_event_box(cfg)) then
      status = boundary_event_invalid_geometry
      return
    end if
    if (trim(cfg%open_boundary_model) /= 'escape') then
      status = boundary_event_invalid_geometry
      return
    end if
    if (.not. all(ieee_is_finite(x)) .or. .not. all(ieee_is_finite(v))) then
      status = boundary_event_invalid_geometry
      return
    end if
    if (.not. event%has_event) then
      if (event%face_mask /= 0_i32 .or. event%fraction /= 0.0_dp) status = boundary_event_invalid_geometry
      return
    end if
    if (.not. ieee_is_finite(event%fraction) .or. event%fraction < 0.0_dp .or. event%fraction > 1.0_dp) then
      status = boundary_event_invalid_geometry
      return
    end if
    if (event%face_mask <= 0_i32 .or. iand(event%face_mask, boundary_face_all) /= event%face_mask) then
      status = boundary_event_invalid_geometry
      return
    end if
    if (any(event%face_bc /= boundary_face_conditions(cfg))) then
      status = boundary_event_invalid_geometry
      return
    end if
    do axis = 1_i32, 3_i32
      low_face = 2_i32*axis - 1_i32
      high_face = 2_i32*axis
      if (btest(event%face_mask, low_face - 1_i32) .and. btest(event%face_mask, high_face - 1_i32)) then
        status = boundary_event_invalid_geometry
        return
      end if
    end do

    x_work = x
    v_work = v
    alive_work = alive
    has_open = .false.
    has_redistributed_reflect = .false.
    do axis = 1_i32, 3_i32
      low_face = 2_i32*axis - 1_i32
      high_face = 2_i32*axis
      low_bc = event%face_bc(low_face)
      high_bc = event%face_bc(high_face)
      if (btest(event%face_mask, low_face - 1_i32)) has_open = has_open .or. low_bc == bc_open
      if (btest(event%face_mask, high_face - 1_i32)) has_open = has_open .or. high_bc == bc_open
      if (btest(event%face_mask, low_face - 1_i32)) then
        has_redistributed_reflect = has_redistributed_reflect .or. low_bc == bc_redistributed_reflect
      end if
      if (btest(event%face_mask, high_face - 1_i32)) then
        has_redistributed_reflect = has_redistributed_reflect .or. high_bc == bc_redistributed_reflect
      end if
    end do
    if (has_redistributed_reflect .and. .not. has_open) then
      if (.not. present(redistribution_uniform)) then
        status = boundary_event_invalid_geometry
        return
      end if
      if (.not. valid_redistribution_uniform(redistribution_uniform)) then
        status = boundary_event_invalid_geometry
        return
      end if
    end if

    if (has_open) then
      alive_work = .false.
      escaped = .true.
    else
      do axis = 1_i32, 3_i32
        low_face = 2_i32*axis - 1_i32
        high_face = 2_i32*axis
        low_bc = event%face_bc(low_face)
        high_bc = event%face_bc(high_face)
        if (btest(event%face_mask, low_face - 1_i32)) then
          call apply_surviving_event_axis( &
            axis, low_bc, .false., cfg%box_min(axis), cfg%box_max(axis), x_work, v_work, status &
            )
        else if (btest(event%face_mask, high_face - 1_i32)) then
          call apply_surviving_event_axis( &
            axis, high_bc, .true., cfg%box_min(axis), cfg%box_max(axis), x_work, v_work, status &
            )
        end if
        if (status /= boundary_event_ok) return
      end do
      if (has_redistributed_reflect) then
        call redistribute_event_position(cfg, event%face_mask, redistribution_uniform, x_work)
      end if
    end if
    if (.not. all(ieee_is_finite(x_work)) .or. .not. all(ieee_is_finite(v_work))) then
      escaped = .false.
      status = boundary_event_invalid_geometry
      return
    end if

    x = x_work
    v = v_work
    alive = alive_work
  end subroutine apply_escape_reflect_periodic_event

  !> 反射または周期境界の単一軸作用をwork stateへ適用する。
  subroutine apply_surviving_event_axis(axis, bc, high_side, box_min, box_max, x, v, status)
    integer(i32), intent(in) :: axis, bc
    logical, intent(in) :: high_side
    real(dp), intent(in) :: box_min, box_max
    real(dp), intent(inout) :: x(3), v(3)
    integer(i32), intent(inout) :: status
    real(dp) :: inset

    inset = boundary_inset(box_min, box_max)

    select case (bc)
    case (bc_reflect, bc_redistributed_reflect)
      if (high_side) then
        x(axis) = box_max - inset
      else
        x(axis) = box_min + inset
      end if
      v(axis) = -v(axis)
    case (bc_periodic)
      if (high_side) then
        x(axis) = box_min + inset
      else
        x(axis) = box_max - inset
      end if
    case default
      status = boundary_event_invalid_geometry
    end select
  end subroutine apply_surviving_event_axis

  !> Event APIで扱える有限かつ正幅のbox設定かを返す。
  logical function valid_event_box(cfg) result(valid)
    type(sim_config), intent(in) :: cfg
    real(dp) :: inset
    integer(i32) :: axis

    valid = .false.
    if (.not. all(ieee_is_finite(cfg%box_min)) .or. .not. all(ieee_is_finite(cfg%box_max))) return
    if (any(cfg%box_max <= cfg%box_min)) return
    if (.not. all(valid_boundary_condition(cfg%bc_low)) .or. &
        .not. all(valid_boundary_condition(cfg%bc_high))) return
    do axis = 1_i32, 3_i32
      inset = boundary_inset(cfg%box_min(axis), cfg%box_max(axis))
      if (.not. ieee_is_finite(inset) .or. inset <= 0.0_dp) return
      if (cfg%box_min(axis) + inset >= cfg%box_max(axis) - inset) return
    end do
    valid = .true.
  end function valid_event_box

  !> 境界作用後の座標をnormal範囲かつevent toleranceより十分内側へ置く。
  pure real(dp) function boundary_inset(box_min, box_max) result(inset)
    real(dp), intent(in) :: box_min, box_max
    real(dp) :: span, scale

    span = box_max - box_min
    scale = max(abs(box_min), abs(box_max), span, tiny(1.0_dp))
    inset = max(64.0_dp*epsilon(1.0_dp)*scale, spacing(scale))
    inset = min(0.25_dp*span, inset)
  end function boundary_inset

  !> 境界条件enum配列の各値が既知かを返す。
  elemental logical function valid_boundary_condition(bc) result(valid)
    integer(i32), intent(in) :: bc

    valid = bc == bc_open .or. bc == bc_reflect .or. bc == bc_periodic .or. bc == bc_redistributed_reflect
  end function valid_boundary_condition

  !> 再配置反射に供給された各軸の一様乱数が閉区間 `[0,1]` にあるかを返す。
  pure logical function valid_redistribution_uniform(uniform) result(valid)
    real(dp), intent(in) :: uniform(3)

    valid = all(ieee_is_finite(uniform)) .and. all(uniform >= 0.0_dp) .and. all(uniform <= 1.0_dp)
  end function valid_redistribution_uniform

  !> 同時eventの法線軸を固定し、それ以外の座標をbox面内で一様再配置する。
  pure subroutine redistribute_event_position(cfg, face_mask, uniform, x)
    type(sim_config), intent(in) :: cfg
    integer(i32), intent(in) :: face_mask
    real(dp), intent(in) :: uniform(3)
    real(dp), intent(inout) :: x(3)
    integer(i32) :: axis
    real(dp) :: inset

    do axis = 1_i32, 3_i32
      if (btest(face_mask, 2_i32*axis - 2_i32) .or. btest(face_mask, 2_i32*axis - 1_i32)) cycle
      inset = boundary_inset(cfg%box_min(axis), cfg%box_max(axis))
      x(axis) = cfg%box_min(axis) + inset + &
                uniform(axis)*(cfg%box_max(axis) - cfg%box_min(axis) - 2.0_dp*inset)
    end do
  end subroutine redistribute_event_position

  !> face bit順にbox設定の境界条件を並べる。
  pure function boundary_face_conditions(cfg) result(face_bc)
    type(sim_config), intent(in) :: cfg
    integer(i32) :: face_bc(6)
    integer(i32) :: axis

    do axis = 1_i32, 3_i32
      face_bc(2_i32*axis - 1_i32) = cfg%bc_low(axis)
      face_bc(2_i32*axis) = cfg%bc_high(axis)
    end do
  end function boundary_face_conditions

  !> 1ステップの更新候補位置にボックス境界条件を適用し、生存/流出状態と位置速度を更新する。
  !! @param[in] cfg 境界条件とボックス範囲を含むシミュレーション設定。
  !! @param[inout] x 境界適用対象の粒子位置 `(x,y,z)` [m]。
  !! @param[inout] v 境界適用対象の粒子速度 `(vx,vy,vz)` [m/s]。
  !! @param[inout] alive 粒子生存フラグ(流出時は `.false.` へ更新)。
  !! @param[out] escaped_boundary この呼び出しで境界流出が発生したか。
  !! @param[in] redistribution_uniform 面内再配置反射に使う各軸の一様乱数 `[0,1]`。
  subroutine apply_box_boundary( &
    cfg, x, v, alive, escaped_boundary, q_particle, m_particle, phi_boundary, potential_axis, potential_high_side, &
    redistribution_uniform &
    )
    type(sim_config), intent(in) :: cfg
    real(dp), intent(inout) :: x(3)
    real(dp), intent(inout) :: v(3)
    logical, intent(inout) :: alive
    logical, intent(out) :: escaped_boundary
    real(dp), intent(in), optional :: q_particle
    real(dp), intent(in), optional :: m_particle
    real(dp), intent(in), optional :: phi_boundary
    integer(i32), intent(in), optional :: potential_axis
    logical, intent(in), optional :: potential_high_side
    real(dp), intent(in), optional :: redistribution_uniform(3)

    integer(i32) :: axis
    integer(i32) :: bc
    real(dp) :: span, eps

    escaped_boundary = .false.
    if (.not. cfg%use_box) return
    if (.not. alive) return

    eps = 1.0d-12
    if (present(potential_axis) .and. present(potential_high_side)) then
      axis = potential_axis
      if (axis >= 1_i32 .and. axis <= 3_i32) then
        span = cfg%box_max(axis) - cfg%box_min(axis)
        if (span <= 0.0_dp) then
          alive = .false.
          escaped_boundary = .true.
          return
        end if
        if ((.not. potential_high_side) .and. x(axis) < cfg%box_min(axis)) then
          call apply_one_side_boundary( &
            cfg, axis, cfg%bc_low(axis), cfg%box_min(axis), cfg%box_max(axis), span, eps, x, v, alive, &
            escaped_boundary, q_particle, m_particle, phi_boundary, potential_axis, potential_high_side, &
            redistribution_uniform &
            )
        else if (potential_high_side .and. x(axis) > cfg%box_max(axis)) then
          call apply_one_side_boundary( &
            cfg, axis, cfg%bc_high(axis), cfg%box_min(axis), cfg%box_max(axis), span, eps, x, v, alive, &
            escaped_boundary, q_particle, m_particle, phi_boundary, potential_axis, potential_high_side, &
            redistribution_uniform &
            )
        end if
        if (.not. alive) return
      end if
    end if

    do axis = 1, 3
      span = cfg%box_max(axis) - cfg%box_min(axis)
      if (span <= 0.0_dp) then
        alive = .false.
        escaped_boundary = .true.
        return
      end if

      do while (x(axis) < cfg%box_min(axis))
        bc = cfg%bc_low(axis)
        call apply_one_side_boundary( &
          cfg, axis, bc, cfg%box_min(axis), cfg%box_max(axis), span, eps, x, v, alive, escaped_boundary, &
          q_particle, m_particle, phi_boundary, potential_axis, potential_high_side, redistribution_uniform &
          )
        if ((.not. alive) .or. (.not. escaped_boundary .and. x(axis) >= cfg%box_min(axis))) exit
      end do
      if (.not. alive) return

      do while (x(axis) > cfg%box_max(axis))
        bc = cfg%bc_high(axis)
        call apply_one_side_boundary( &
          cfg, axis, bc, cfg%box_min(axis), cfg%box_max(axis), span, eps, x, v, alive, escaped_boundary, &
          q_particle, m_particle, phi_boundary, potential_axis, potential_high_side, redistribution_uniform &
          )
        if ((.not. alive) .or. (.not. escaped_boundary .and. x(axis) <= cfg%box_max(axis))) exit
      end do
      if (.not. alive) return
    end do
  end subroutine apply_box_boundary

  !> 単一軸・単一側面の境界条件を適用する内部ヘルパ。
  !! @param[in] axis 境界判定軸(1:x, 2:y, 3:z)。
  !! @param[in] bc 適用する境界条件種別(open/reflect/redistributed-reflect/periodic)。
  !! @param[in] box_min 判定軸の下限座標 [m]。
  !! @param[in] box_max 判定軸の上限座標 [m]。
  !! @param[in] span 判定軸のボックス幅 `box_max - box_min` [m]。
  !! @param[in] eps 反射/周期後に境界内へ押し戻す微小量 [m]。
  !! @param[inout] x 粒子位置 `(x,y,z)` [m]。
  !! @param[inout] v 粒子速度 `(vx,vy,vz)` [m/s]。
  !! @param[inout] alive 粒子生存フラグ。
  !! @param[inout] escaped_boundary 境界流出フラグ。
  subroutine apply_one_side_boundary( &
    cfg, axis, bc, box_min, box_max, span, eps, x, v, alive, escaped_boundary, q_particle, m_particle, phi_boundary, &
    potential_axis, potential_high_side, redistribution_uniform &
    )
    type(sim_config), intent(in) :: cfg
    integer(i32), intent(in) :: axis, bc
    real(dp), intent(in) :: box_min, box_max, span, eps
    real(dp), intent(inout) :: x(3), v(3)
    logical, intent(inout) :: alive
    logical, intent(inout) :: escaped_boundary
    real(dp), intent(in), optional :: q_particle
    real(dp), intent(in), optional :: m_particle
    real(dp), intent(in), optional :: phi_boundary
    integer(i32), intent(in), optional :: potential_axis
    logical, intent(in), optional :: potential_high_side
    real(dp), intent(in), optional :: redistribution_uniform(3)

    select case (bc)
    case (bc_open)
      if (open_boundary_should_reflect( &
          cfg, axis, x(axis) > box_max, v, q_particle, m_particle, phi_boundary, potential_axis, potential_high_side &
          )) then
        call reflect_one_side(axis, box_min, box_max, eps, x, v)
        escaped_boundary = .false.
      else
        alive = .false.
        escaped_boundary = .true.
      end if
    case (bc_reflect)
      call reflect_one_side(axis, box_min, box_max, eps, x, v)
      escaped_boundary = .false.
    case (bc_redistributed_reflect)
      if (.not. present(redistribution_uniform)) then
        alive = .false.
        escaped_boundary = .true.
      else if (.not. valid_redistribution_uniform(redistribution_uniform)) then
        alive = .false.
        escaped_boundary = .true.
      else
        call reflect_one_side(axis, box_min, box_max, eps, x, v)
        call redistribute_one_face_position(cfg, axis, redistribution_uniform, x)
        escaped_boundary = .false.
      end if
    case (bc_periodic)
      x(axis) = modulo(x(axis) - box_min, span) + box_min
      x(axis) = min(box_max - eps, max(box_min + eps, x(axis)))
      escaped_boundary = .false.
    case default
      alive = .false.
      escaped_boundary = .true.
    end select
  end subroutine apply_one_side_boundary

  !> legacy単一面APIで、反射軸以外の二座標をbox面内へ一様再配置する。
  pure subroutine redistribute_one_face_position(cfg, normal_axis, uniform, x)
    type(sim_config), intent(in) :: cfg
    integer(i32), intent(in) :: normal_axis
    real(dp), intent(in) :: uniform(3)
    real(dp), intent(inout) :: x(3)
    integer(i32) :: axis
    real(dp) :: inset

    do axis = 1_i32, 3_i32
      if (axis == normal_axis) cycle
      inset = boundary_inset(cfg%box_min(axis), cfg%box_max(axis))
      x(axis) = cfg%box_min(axis) + inset + &
                uniform(axis)*(cfg%box_max(axis) - cfg%box_min(axis) - 2.0_dp*inset)
    end do
  end subroutine redistribute_one_face_position

  !> 開境界の電位障壁モデルで反射すべきかを判定する。
  logical function open_boundary_should_reflect( &
    cfg, axis, is_high_side, v, q_particle, m_particle, phi_boundary, potential_axis, potential_high_side &
    ) result(reflect)
    type(sim_config), intent(in) :: cfg
    integer(i32), intent(in) :: axis
    logical, intent(in) :: is_high_side
    real(dp), intent(in) :: v(3)
    real(dp), intent(in), optional :: q_particle
    real(dp), intent(in), optional :: m_particle
    real(dp), intent(in), optional :: phi_boundary
    integer(i32), intent(in), optional :: potential_axis
    logical, intent(in), optional :: potential_high_side

    real(dp) :: outward_v, kinetic_normal, potential_barrier

    reflect = .false.
    if (trim(cfg%open_boundary_model) /= 'potential_barrier') return
    if (.not. present(q_particle) .or. .not. present(m_particle) .or. .not. present(phi_boundary)) return
    if (present(potential_axis)) then
      if (potential_axis /= axis) return
    end if
    if (present(potential_high_side)) then
      if (potential_high_side .neqv. is_high_side) return
    end if
    if (m_particle <= 0.0d0) return

    if (is_high_side) then
      outward_v = v(axis)
    else
      outward_v = -v(axis)
    end if
    if (outward_v <= 0.0d0) return

    kinetic_normal = 0.5d0*m_particle*outward_v*outward_v
    potential_barrier = q_particle*(cfg%phi_infty - phi_boundary)
    reflect = potential_barrier > 0.0d0 .and. kinetic_normal < potential_barrier
  end function open_boundary_should_reflect

  !> 反射境界と同じ鏡像位置・法線速度反転を適用する。
  subroutine reflect_one_side(axis, box_min, box_max, eps, x, v)
    integer(i32), intent(in) :: axis
    real(dp), intent(in) :: box_min, box_max, eps
    real(dp), intent(inout) :: x(3), v(3)

    if (x(axis) < box_min) then
      x(axis) = box_min + (box_min - x(axis))
    else if (x(axis) > box_max) then
      x(axis) = box_max - (x(axis) - box_max)
    end if
    x(axis) = min(box_max - eps, max(box_min + eps, x(axis)))
    v(axis) = -v(axis)
  end subroutine reflect_one_side

end module bem_boundary