!> 境界要素に蓄積した電荷から観測点の電場を評価する場計算モジュール。
module bem_field
  use bem_kinds, only: dp, i32
  use bem_constants, only: k_coulomb
  use bem_types, only: mesh_type
  implicit none
contains

  !> 全要素電荷を点電荷近似で総和し、softening付きで観測点 `r` の電場ベクトルを返す。
  !! @param[in] mesh 要素重心 `centers` と要素電荷 `q_elem` を保持したメッシュ情報。
  !! @param[in] r 電場を評価する観測点座標 `(x,y,z)` [m]。
  !! @param[in] softening 特異点回避のために距離2乗へ加える softening 長さ [m]。
  !! @param[out] e 観測点 `r` における電場ベクトル `(Ex,Ey,Ez)` [V/m]。
  subroutine electric_field_at(mesh, r, softening, e)
    type(mesh_type), intent(in) :: mesh
    real(dp), intent(in) :: r(3)
    real(dp), intent(in) :: softening
    real(dp), intent(out) :: e(3)

    integer(i32) :: i
    real(dp) :: soft2, r2, inv_r3, ex, ey, ez, min_dist2
    real(dp) :: rx, ry, rz, dx, dy, dz, qi

    ex = 0.0d0
    ey = 0.0d0
    ez = 0.0d0
    soft2 = softening*softening
    min_dist2 = tiny(1.0d0)
    rx = r(1)
    ry = r(2)
    rz = r(3)

    !$omp simd reduction(+:ex,ey,ez) private(dx,dy,dz,r2,inv_r3,qi)
    do i = 1, mesh%nelem
      dx = rx - mesh%center_x(i)
      dy = ry - mesh%center_y(i)
      dz = rz - mesh%center_z(i)
      r2 = dx*dx + dy*dy + dz*dz + soft2
      if (r2 <= min_dist2) cycle
      inv_r3 = 1.0d0/(sqrt(r2)*r2)
      qi = mesh%q_elem(i)
      ex = ex + qi*inv_r3*dx
      ey = ey + qi*inv_r3*dy
      ez = ez + qi*inv_r3*dz
    end do

    e(1) = k_coulomb*ex
    e(2) = k_coulomb*ey
    e(3) = k_coulomb*ez
  end subroutine electric_field_at

  !> 全要素電荷を点電荷近似で総和し、softening付きで観測点 `r` の電位を返す。
  !! @param[in] mesh 要素重心 `centers` と要素電荷 `q_elem` を保持したメッシュ情報。
  !! @param[in] r 電位を評価する観測点座標 `(x,y,z)` [m]。
  !! @param[in] softening 特異点回避のために距離2乗へ加える softening 長さ [m]。
  !! @param[out] phi 観測点 `r` における電位 [V]。
  subroutine electric_potential_at(mesh, r, softening, phi)
    type(mesh_type), intent(in) :: mesh
    real(dp), intent(in) :: r(3)
    real(dp), intent(in) :: softening
    real(dp), intent(out) :: phi

    integer(i32) :: i
    real(dp) :: soft2, r2, inv_r, phi_sum, min_dist2
    real(dp) :: rx, ry, rz, dx, dy, dz

    phi_sum = 0.0d0
    soft2 = softening*softening
    min_dist2 = tiny(1.0d0)
    rx = r(1)
    ry = r(2)
    rz = r(3)

    !$omp simd reduction(+:phi_sum) private(dx,dy,dz,r2,inv_r)
    do i = 1, mesh%nelem
      dx = rx - mesh%center_x(i)
      dy = ry - mesh%center_y(i)
      dz = rz - mesh%center_z(i)
      r2 = dx*dx + dy*dy + dz*dz + soft2
      if (r2 <= min_dist2) cycle
      inv_r = 1.0d0/sqrt(r2)
      phi_sum = phi_sum + mesh%q_elem(i)*inv_r
    end do

    phi = k_coulomb*phi_sum
  end subroutine electric_potential_at

end module bem_field
