!> Coulomb FMM 電場評価。 module bem_coulomb_fmm_eval_ops use bem_kinds, only: dp, i32 use bem_constants, only: k_coulomb use bem_panel_geometry, only: panel_geometry_type use bem_panel_kernel, only: panel_potential_field, panel_side_principal_value use bem_coulomb_fmm_types, only: fmm_plan_type, fmm_state_type use bem_coulomb_fmm_basis, only: build_axis_powers use bem_coulomb_fmm_periodic, only: wrap_periodic2_point, use_periodic2_cached_kneq0 use bem_coulomb_fmm_tree_utils, only: octant_index, active_tree_nnode, active_tree_child_count, active_tree_child_idx, & active_tree_child_octant, active_tree_node_center, active_tree_node_half_size use bem_periodic_zero_mode_eval, only: eval_periodic_zero_mode, zero_mode_trace_plus implicit none private public :: core_eval_points_impl public :: core_eval_direct_points_impl public :: core_eval_point_impl public :: core_eval_potential_points_impl public :: core_eval_direct_potential_points_impl public :: core_eval_potential_point_impl contains !> 複数の評価点で電場を計算する。 !! @param[in] plan 構築済みの FMM 計画。 !! @param[inout] state 評価に使う FMM state。 !! @param[in] target_pos 評価点位置 `(3,m)` [m]。 !! @param[out] e 電場ベクトル `(3,m)` [V/m]。 subroutine core_eval_points_impl(plan, state, target_pos, e) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(inout) :: state real(dp), intent(in) :: target_pos(:, :) real(dp), intent(out) :: e(:, :) integer(i32) :: i, ntarget if (size(target_pos, 1) /= 3) error stop 'FMM core expects target_pos(3,m).' if (size(e, 1) /= 3 .or. size(e, 2) /= size(target_pos, 2)) then error stop 'FMM eval_points expects e(3,m).' end if ntarget = int(size(target_pos, 2), i32) !$omp parallel do default(none) schedule(static) & !$omp shared(plan, state, target_pos, e, ntarget) private(i) do i = 1_i32, ntarget call core_eval_point_xyz_impl( & plan, state, target_pos(1, i), target_pos(2, i), target_pos(3, i), e(1, i), e(2, i), e(3, i) & ) end do !$omp end parallel do end subroutine core_eval_points_impl !> 非周期計画の全ソース直接和で複数点の電場を計算する。 subroutine core_eval_direct_points_impl(plan, state, target_pos, e) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: target_pos(:, :) real(dp), intent(out) :: e(:, :) integer(i32) :: i, ntarget real(dp) :: soft2 if (size(target_pos, 1) /= 3) error stop 'FMM core expects target_pos(3,m).' if (size(e, 1) /= 3 .or. size(e, 2) /= size(target_pos, 2)) then error stop 'FMM eval_direct_points expects e(3,m).' end if if (plan%options%use_periodic2) then error stop 'FMM exact-direct evaluation requires a non-periodic plan.' end if e = 0.0_dp if (.not. plan%built .or. .not. state%ready) return ntarget = int(size(target_pos, 2), i32) soft2 = plan%options%softening*plan%options%softening !$omp parallel do default(none) schedule(static) & !$omp shared(plan, state, target_pos, e, ntarget, soft2) private(i) do i = 1_i32, ntarget call eval_direct_all_sources_scalar( & plan, state, target_pos(1, i), target_pos(2, i), target_pos(3, i), soft2, e(1, i), e(2, i), e(3, i) & ) end do !$omp end parallel do end subroutine core_eval_direct_points_impl !> 1 点で電場を計算する。 !! @param[in] plan 構築済みの FMM 計画。 !! @param[inout] state 評価に使う FMM state。 !! @param[in] r 評価点位置 `(x,y,z)` [m]。 !! @param[out] e 電場ベクトル `(x,y,z)` [V/m]。 subroutine core_eval_point_impl(plan, state, r, e) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(inout) :: state real(dp), intent(in) :: r(3) real(dp), intent(out) :: e(3) call core_eval_point_xyz_impl(plan, state, r(1), r(2), r(3), e(1), e(2), e(3)) end subroutine core_eval_point_impl !> 複数の評価点で電位を計算する。 !! @param[in] plan 構築済みの FMM 計画。 !! @param[inout] state 評価に使う FMM state。 !! @param[in] target_pos 評価点位置 `(3,m)` [m]。 !! @param[out] phi 電位 `(m)` [V](`k_coulomb` は含まない)。 subroutine core_eval_potential_points_impl(plan, state, target_pos, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(inout) :: state real(dp), intent(in) :: target_pos(:, :) real(dp), intent(out) :: phi(:) integer(i32) :: i, ntarget if (size(target_pos, 1) /= 3) error stop 'FMM core expects target_pos(3,m).' if (size(phi) /= size(target_pos, 2)) then error stop 'FMM eval_potential_points expects phi(m).' end if ntarget = int(size(target_pos, 2), i32) !$omp parallel do default(none) schedule(static) & !$omp shared(plan, state, target_pos, phi, ntarget) private(i) do i = 1_i32, ntarget call core_eval_potential_point_xyz_impl( & plan, state, target_pos(1, i), target_pos(2, i), target_pos(3, i), phi(i) & ) end do !$omp end parallel do end subroutine core_eval_potential_points_impl !> 非周期計画の全ソース直接和で複数点の電位を計算する。 subroutine core_eval_direct_potential_points_impl(plan, state, target_pos, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: target_pos(:, :) real(dp), intent(out) :: phi(:) integer(i32) :: i, ntarget real(dp) :: soft2 if (size(target_pos, 1) /= 3) error stop 'FMM core expects target_pos(3,m).' if (size(phi) /= size(target_pos, 2)) then error stop 'FMM eval_direct_potential_points expects phi(m).' end if if (plan%options%use_periodic2) then error stop 'FMM exact-direct evaluation requires a non-periodic plan.' end if phi = 0.0_dp if (.not. plan%built .or. .not. state%ready) return ntarget = int(size(target_pos, 2), i32) soft2 = plan%options%softening*plan%options%softening !$omp parallel do default(none) schedule(static) & !$omp shared(plan, state, target_pos, phi, ntarget, soft2) private(i) do i = 1_i32, ntarget call eval_direct_all_sources_potential_scalar( & plan, state, target_pos(1, i), target_pos(2, i), target_pos(3, i), soft2, phi(i) & ) end do !$omp end parallel do end subroutine core_eval_direct_potential_points_impl !> 1 点で電位を計算する。 !! @param[in] plan 構築済みの FMM 計画。 !! @param[inout] state 評価に使う FMM state。 !! @param[in] r 評価点位置 `(x,y,z)` [m]。 !! @param[out] phi 電位 [V](`k_coulomb` は含まない)。 subroutine core_eval_potential_point_impl(plan, state, r, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(inout) :: state real(dp), intent(in) :: r(3) real(dp), intent(out) :: phi call core_eval_potential_point_xyz_impl(plan, state, r(1), r(2), r(3), phi) end subroutine core_eval_potential_point_impl !> 1 点評価の本体処理を行う。 !! @param[in] plan 構築済みの FMM 計画。 !! @param[inout] state 評価に使う FMM state。 !! @param[in] rx 評価点 x 座標 [m]。 !! @param[in] ry 評価点 y 座標 [m]。 !! @param[in] rz 評価点 z 座標 [m]。 !! @param[out] ex 電場 x 成分 [V/m]。 !! @param[out] ey 電場 y 成分 [V/m]。 !! @param[out] ez 電場 z 成分 [V/m]。 subroutine core_eval_point_xyz_impl(plan, state, rx, ry, rz, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(inout) :: state real(dp), intent(in) :: rx, ry, rz real(dp), intent(out) :: ex, ey, ez integer(i32) :: leaf_node, leaf_slot integer(i32) :: axes(2) real(dp) :: rt(3), soft2 ex = 0.0d0 ey = 0.0d0 ez = 0.0d0 if (.not. plan%built .or. .not. state%ready) return rt = [rx, ry, rz] if (plan%options%use_periodic2) call wrap_periodic2_point(plan, rt) soft2 = plan%options%softening*plan%options%softening leaf_node = locate_target_leaf(plan, rt) leaf_slot = 0_i32 if (leaf_node > 0_i32) leaf_slot = plan%leaf_slot_of_node(leaf_node) if (leaf_slot <= 0_i32) then call apply_direct_fallback_field(plan, state, rt, soft2, ex, ey, ez) return end if call accumulate_leaf_local_expansion(plan, state, leaf_node, rt, ex, ey, ez) axes = active_periodic_axes(plan) call accumulate_near_direct_field(plan, state, leaf_slot, rt, soft2, axes, ex, ey, ez) call subtract_cached_periodic_k0_field(plan, state, rt, ex, ey, ez) end subroutine core_eval_point_xyz_impl !> 1 点評価の電位本体処理を行う。 subroutine core_eval_potential_point_xyz_impl(plan, state, rx, ry, rz, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(inout) :: state real(dp), intent(in) :: rx, ry, rz real(dp), intent(out) :: phi integer(i32) :: leaf_node, leaf_slot integer(i32) :: axes(2) real(dp) :: rt(3), soft2 phi = 0.0d0 if (.not. plan%built .or. .not. state%ready) return rt = [rx, ry, rz] if (plan%options%use_periodic2) call wrap_periodic2_point(plan, rt) soft2 = plan%options%softening*plan%options%softening leaf_node = locate_target_leaf(plan, rt) leaf_slot = 0_i32 if (leaf_node > 0_i32) leaf_slot = plan%leaf_slot_of_node(leaf_node) if (leaf_slot <= 0_i32) then call apply_direct_fallback_potential(plan, state, rt, soft2, phi) return end if call accumulate_leaf_local_potential_expansion(plan, state, leaf_node, rt, phi) axes = active_periodic_axes(plan) call accumulate_near_direct_potential(plan, state, leaf_slot, rt, soft2, axes, phi) call subtract_cached_periodic_k0_potential(plan, state, rt, phi) end subroutine core_eval_potential_point_xyz_impl subroutine subtract_cached_periodic_k0_field(plan, state, rt, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: rt(3) real(dp), intent(inout) :: ex, ey, ez real(dp) :: potential_dummy, field_si, field_raw(3) if (.not. plan%periodic_k0_ready) return call eval_periodic_zero_mode( & plan%periodic_k0_plan, state%periodic_k0_state, rt(plan%periodic_k0_free_axis), & zero_mode_trace_plus, potential_dummy, field_si & ) field_raw = [ex, ey, ez] field_raw(plan%periodic_k0_free_axis) = field_raw(plan%periodic_k0_free_axis) - field_si/k_coulomb ex = field_raw(1) ey = field_raw(2) ez = field_raw(3) end subroutine subtract_cached_periodic_k0_field subroutine subtract_cached_periodic_k0_potential(plan, state, rt, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: rt(3) real(dp), intent(inout) :: phi real(dp) :: potential_si, field_dummy if (.not. plan%periodic_k0_ready) return call eval_periodic_zero_mode( & plan%periodic_k0_plan, state%periodic_k0_state, rt(plan%periodic_k0_free_axis), & zero_mode_trace_plus, potential_si, field_dummy & ) phi = phi - potential_si/k_coulomb end subroutine subtract_cached_periodic_k0_potential subroutine apply_direct_fallback_field(plan, state, rt, soft2, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: rt(3) real(dp), intent(in) :: soft2 real(dp), intent(inout) :: ex, ey, ez if (use_periodic2_cached_kneq0(plan)) then error stop 'cached_kneq0 evaluation requires targets inside the configured target box.' end if call eval_direct_all_sources_scalar(plan, state, rt(1), rt(2), rt(3), soft2, ex, ey, ez) end subroutine apply_direct_fallback_field subroutine apply_direct_fallback_potential(plan, state, rt, soft2, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: rt(3) real(dp), intent(in) :: soft2 real(dp), intent(inout) :: phi if (use_periodic2_cached_kneq0(plan)) then error stop 'cached_kneq0 potential evaluation requires targets inside the configured target box.' end if call eval_direct_all_sources_potential_scalar(plan, state, rt(1), rt(2), rt(3), soft2, phi) end subroutine apply_direct_fallback_potential subroutine accumulate_leaf_local_expansion(plan, state, leaf_node, rt, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: leaf_node real(dp), intent(in) :: rt(3) real(dp), intent(inout) :: ex, ey, ez integer(i32) :: order, term_idx real(dp) :: dr(3), monomial real(dp) :: xpow(0:max(0_i32, plan%options%order)), ypow(0:max(0_i32, plan%options%order)) real(dp) :: zpow(0:max(0_i32, plan%options%order)) order = active_local_expansion_order(plan) if (order <= 0_i32) return if (state%local_active(leaf_node) == 0_i32) return if (plan%eval_term_count <= 0_i32) return dr = rt - active_tree_node_center(plan, plan%target_tree_ready, leaf_node) call build_axis_powers(dr, order, xpow, ypow, zpow) do term_idx = 1_i32, plan%eval_term_count monomial = xpow(plan%eval_exp(1, term_idx))*ypow(plan%eval_exp(2, term_idx)) & *zpow(plan%eval_exp(3, term_idx))*plan%eval_inv_factorial(term_idx) ex = ex - state%local(plan%eval_deriv_idx(1, term_idx), leaf_node)*monomial ey = ey - state%local(plan%eval_deriv_idx(2, term_idx), leaf_node)*monomial ez = ez - state%local(plan%eval_deriv_idx(3, term_idx), leaf_node)*monomial end do end subroutine accumulate_leaf_local_expansion subroutine accumulate_leaf_local_potential_expansion(plan, state, leaf_node, rt, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: leaf_node real(dp), intent(in) :: rt(3) real(dp), intent(inout) :: phi integer(i32) :: order, alpha_idx real(dp) :: dr(3), monomial real(dp) :: xpow(0:max(0_i32, plan%options%order)), ypow(0:max(0_i32, plan%options%order)) real(dp) :: zpow(0:max(0_i32, plan%options%order)) order = active_local_expansion_order(plan) if (state%local_active(leaf_node) == 0_i32) return dr = rt - active_tree_node_center(plan, plan%target_tree_ready, leaf_node) call build_axis_powers(dr, order, xpow, ypow, zpow) do alpha_idx = 1_i32, plan%ncoef monomial = xpow(plan%alpha(1, alpha_idx))*ypow(plan%alpha(2, alpha_idx)) & *zpow(plan%alpha(3, alpha_idx))/plan%alpha_factorial(alpha_idx) phi = phi + state%local(alpha_idx, leaf_node)*monomial end do end subroutine accumulate_leaf_local_potential_expansion pure function active_local_expansion_order(plan) result(order) type(fmm_plan_type), intent(in) :: plan integer(i32) :: order order = plan%options%order end function active_local_expansion_order pure function active_periodic_axes(plan) result(axes) type(fmm_plan_type), intent(in) :: plan integer(i32) :: axes(2) axes = 0_i32 if (plan%options%use_periodic2) axes = plan%options%periodic_axes end function active_periodic_axes subroutine accumulate_near_direct_field(plan, state, leaf_slot, rt, soft2, axes, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: leaf_slot real(dp), intent(in) :: rt(3) real(dp), intent(in) :: soft2 integer(i32), intent(in) :: axes(2) real(dp), intent(inout) :: ex, ey, ez integer(i32) :: near_pos, idx, axis1, axis2 integer(i32) :: near_source_begin, near_source_end real(dp) :: shift1, shift2 if (leaf_slot <= 0_i32) return near_source_begin = plan%near_source_start(leaf_slot) near_source_end = plan%near_source_start(leaf_slot + 1_i32) - 1_i32 if (near_source_end < near_source_begin) return if (plan%panel_source) then do near_pos = near_source_begin, near_source_end idx = plan%near_source_idx(near_pos) shift1 = plan%near_source_shift1(near_pos) shift2 = plan%near_source_shift2(near_pos) call accumulate_panel_shifted_field(plan, state, idx, rt, axes, shift1, shift2, ex, ey, ez) end do return end if if (plan%options%use_periodic2) then axis1 = axes(1) axis2 = axes(2) do near_pos = near_source_begin, near_source_end idx = plan%near_source_idx(near_pos) shift1 = plan%near_source_shift1(near_pos) shift2 = plan%near_source_shift2(near_pos) call accumulate_point_charge_shifted_field( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), shift1, shift2, & axis1, axis2, rt(1), rt(2), rt(3), soft2, ex, ey, ez & ) end do else do near_pos = near_source_begin, near_source_end idx = plan%near_source_idx(near_pos) call accumulate_point_charge_field( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), & rt(1), rt(2), rt(3), soft2, ex, ey, ez & ) end do end if end subroutine accumulate_near_direct_field subroutine accumulate_near_direct_potential(plan, state, leaf_slot, rt, soft2, axes, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: leaf_slot real(dp), intent(in) :: rt(3) real(dp), intent(in) :: soft2 integer(i32), intent(in) :: axes(2) real(dp), intent(inout) :: phi integer(i32) :: near_pos, idx, axis1, axis2 integer(i32) :: near_source_begin, near_source_end real(dp) :: shift1, shift2 if (leaf_slot <= 0_i32) return near_source_begin = plan%near_source_start(leaf_slot) near_source_end = plan%near_source_start(leaf_slot + 1_i32) - 1_i32 if (near_source_end < near_source_begin) return if (plan%panel_source) then do near_pos = near_source_begin, near_source_end idx = plan%near_source_idx(near_pos) shift1 = plan%near_source_shift1(near_pos) shift2 = plan%near_source_shift2(near_pos) call accumulate_panel_shifted_potential(plan, state, idx, rt, axes, shift1, shift2, phi) end do return end if if (plan%options%use_periodic2) then axis1 = axes(1) axis2 = axes(2) do near_pos = near_source_begin, near_source_end idx = plan%near_source_idx(near_pos) shift1 = plan%near_source_shift1(near_pos) shift2 = plan%near_source_shift2(near_pos) call accumulate_point_charge_shifted_potential( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), shift1, shift2, & axis1, axis2, rt(1), rt(2), rt(3), soft2, phi & ) end do else do near_pos = near_source_begin, near_source_end idx = plan%near_source_idx(near_pos) call accumulate_point_charge_potential( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), & rt(1), rt(2), rt(3), soft2, phi & ) end do end if end subroutine accumulate_near_direct_potential subroutine eval_direct_all_sources_scalar(plan, state, tx, ty, tz, soft2, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: tx, ty, tz real(dp), intent(in) :: soft2 real(dp), intent(out) :: ex, ey, ez integer(i32) :: idx, axis1, axis2, nshift integer(i32) :: axes(2) ex = 0.0d0 ey = 0.0d0 ez = 0.0d0 axes = active_periodic_axes(plan) axis1 = axes(1) axis2 = axes(2) if (plan%panel_source) then if (plan%options%use_periodic2) then nshift = size(plan%shift_axis1) do idx = 1_i32, plan%nsrc call accumulate_panel_images_field(plan, state, idx, [tx, ty, tz], axes, nshift, ex, ey, ez) end do else do idx = 1_i32, plan%nsrc call accumulate_panel_shifted_field(plan, state, idx, [tx, ty, tz], axes, 0.0_dp, 0.0_dp, ex, ey, ez) end do end if return end if if (plan%options%use_periodic2) then nshift = size(plan%shift_axis1) do idx = 1_i32, plan%nsrc call accumulate_point_charge_images_field( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), & tx, ty, tz, soft2, axis1, axis2, plan%shift_axis1, plan%shift_axis2, nshift, ex, ey, ez & ) end do else do idx = 1_i32, plan%nsrc call accumulate_point_charge_field( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), & tx, ty, tz, soft2, ex, ey, ez & ) end do end if end subroutine eval_direct_all_sources_scalar subroutine eval_direct_all_sources_potential_scalar(plan, state, tx, ty, tz, soft2, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state real(dp), intent(in) :: tx, ty, tz real(dp), intent(in) :: soft2 real(dp), intent(out) :: phi integer(i32) :: idx, axis1, axis2, nshift integer(i32) :: axes(2) phi = 0.0d0 axes = active_periodic_axes(plan) axis1 = axes(1) axis2 = axes(2) if (plan%panel_source) then if (plan%options%use_periodic2) then nshift = size(plan%shift_axis1) do idx = 1_i32, plan%nsrc call accumulate_panel_images_potential(plan, state, idx, [tx, ty, tz], axes, nshift, phi) end do else do idx = 1_i32, plan%nsrc call accumulate_panel_shifted_potential(plan, state, idx, [tx, ty, tz], axes, 0.0_dp, 0.0_dp, phi) end do end if return end if if (plan%options%use_periodic2) then nshift = size(plan%shift_axis1) do idx = 1_i32, plan%nsrc call accumulate_point_charge_images_potential( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), & tx, ty, tz, soft2, axis1, axis2, plan%shift_axis1, plan%shift_axis2, nshift, phi & ) end do else do idx = 1_i32, plan%nsrc call accumulate_point_charge_potential( & state%src_q(idx), plan%src_pos(1, idx), plan%src_pos(2, idx), plan%src_pos(3, idx), & tx, ty, tz, soft2, phi & ) end do end if end subroutine eval_direct_all_sources_potential_scalar subroutine accumulate_panel_images_field(plan, state, idx, target, axes, nshift, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: idx, axes(2), nshift real(dp), intent(in) :: target(3) real(dp), intent(inout) :: ex, ey, ez integer(i32) :: image1, image2 do image1 = 1_i32, nshift do image2 = 1_i32, nshift call accumulate_panel_shifted_field( & plan, state, idx, target, axes, plan%shift_axis1(image1), plan%shift_axis2(image2), ex, ey, ez & ) end do end do end subroutine accumulate_panel_images_field subroutine accumulate_panel_images_potential(plan, state, idx, target, axes, nshift, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: idx, axes(2), nshift real(dp), intent(in) :: target(3) real(dp), intent(inout) :: phi integer(i32) :: image1, image2 do image1 = 1_i32, nshift do image2 = 1_i32, nshift call accumulate_panel_shifted_potential( & plan, state, idx, target, axes, plan%shift_axis1(image1), plan%shift_axis2(image2), phi & ) end do end do end subroutine accumulate_panel_images_potential subroutine accumulate_panel_shifted_field(plan, state, idx, target, axes, shift1, shift2, ex, ey, ez) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: idx, axes(2) real(dp), intent(in) :: target(3), shift1, shift2 real(dp), intent(inout) :: ex, ey, ez real(dp) :: potential, field(3) type(panel_geometry_type) :: geometry if (abs(state%src_q(idx)) <= tiny(1.0_dp)) return call shifted_panel_geometry(plan, idx, axes, shift1, shift2, geometry) call panel_potential_field(geometry, state%src_q(idx), target, panel_side_principal_value, potential, field) ex = ex + field(1)/k_coulomb ey = ey + field(2)/k_coulomb ez = ez + field(3)/k_coulomb end subroutine accumulate_panel_shifted_field subroutine accumulate_panel_shifted_potential(plan, state, idx, target, axes, shift1, shift2, phi) type(fmm_plan_type), intent(in) :: plan type(fmm_state_type), intent(in) :: state integer(i32), intent(in) :: idx, axes(2) real(dp), intent(in) :: target(3), shift1, shift2 real(dp), intent(inout) :: phi real(dp) :: potential, field(3) type(panel_geometry_type) :: geometry if (abs(state%src_q(idx)) <= tiny(1.0_dp)) return call shifted_panel_geometry(plan, idx, axes, shift1, shift2, geometry) call panel_potential_field(geometry, state%src_q(idx), target, panel_side_principal_value, potential, field) phi = phi + potential/k_coulomb end subroutine accumulate_panel_shifted_potential subroutine shifted_panel_geometry(plan, idx, axes, shift1, shift2, geometry) type(fmm_plan_type), intent(in) :: plan integer(i32), intent(in) :: idx, axes(2) real(dp), intent(in) :: shift1, shift2 type(panel_geometry_type), intent(out) :: geometry real(dp) :: shift(3) geometry = plan%panel_geometry(idx) shift = 0.0_dp if (axes(1) > 0_i32) shift(axes(1)) = shift(axes(1)) + shift1 if (axes(2) > 0_i32) shift(axes(2)) = shift(axes(2)) + shift2 geometry%vertex = geometry%vertex + spread(shift, 2, 3) geometry%centroid = geometry%centroid + shift end subroutine shifted_panel_geometry pure subroutine accumulate_point_charge_field(q, sx, sy, sz, tx, ty, tz, soft2, ex, ey, ez) real(dp), intent(in) :: q, sx, sy, sz, tx, ty, tz, soft2 real(dp), intent(inout) :: ex, ey, ez real(dp) :: dx, dy, dz, r2, inv_r3 if (abs(q) <= tiny(1.0d0)) return dx = tx - sx dy = ty - sy dz = tz - sz r2 = dx*dx + dy*dy + dz*dz + soft2 if (r2 <= tiny(1.0d0)) return inv_r3 = 1.0d0/(sqrt(r2)*r2) ex = ex + q*inv_r3*dx ey = ey + q*inv_r3*dy ez = ez + q*inv_r3*dz end subroutine accumulate_point_charge_field pure subroutine accumulate_point_charge_potential(q, sx, sy, sz, tx, ty, tz, soft2, phi) real(dp), intent(in) :: q, sx, sy, sz, tx, ty, tz, soft2 real(dp), intent(inout) :: phi real(dp) :: dx, dy, dz, r2 if (abs(q) <= tiny(1.0d0)) return dx = tx - sx dy = ty - sy dz = tz - sz r2 = dx*dx + dy*dy + dz*dz + soft2 if (r2 <= tiny(1.0d0)) return phi = phi + q/sqrt(r2) end subroutine accumulate_point_charge_potential pure subroutine accumulate_point_charge_images_field( & q, sx, sy, sz, tx, ty, tz, soft2, axis1, axis2, shift_axis1, shift_axis2, nshift, ex, ey, ez & ) real(dp), intent(in) :: q, sx, sy, sz, tx, ty, tz, soft2 integer(i32), intent(in) :: axis1, axis2, nshift real(dp), intent(in) :: shift_axis1(:), shift_axis2(:) real(dp), intent(inout) :: ex, ey, ez integer(i32) :: img_i, img_j real(dp) :: sx_img, sy_img, sz_img if (abs(q) <= tiny(1.0d0)) return do img_i = 1_i32, nshift do img_j = 1_i32, nshift sx_img = sx sy_img = sy sz_img = sz if (axis1 == 1_i32) sx_img = sx_img + shift_axis1(img_i) if (axis1 == 2_i32) sy_img = sy_img + shift_axis1(img_i) if (axis1 == 3_i32) sz_img = sz_img + shift_axis1(img_i) if (axis2 == 1_i32) sx_img = sx_img + shift_axis2(img_j) if (axis2 == 2_i32) sy_img = sy_img + shift_axis2(img_j) if (axis2 == 3_i32) sz_img = sz_img + shift_axis2(img_j) call accumulate_point_charge_field(q, sx_img, sy_img, sz_img, tx, ty, tz, soft2, ex, ey, ez) end do end do end subroutine accumulate_point_charge_images_field pure subroutine accumulate_point_charge_images_potential( & q, sx, sy, sz, tx, ty, tz, soft2, axis1, axis2, shift_axis1, shift_axis2, nshift, phi & ) real(dp), intent(in) :: q, sx, sy, sz, tx, ty, tz, soft2 integer(i32), intent(in) :: axis1, axis2, nshift real(dp), intent(in) :: shift_axis1(:), shift_axis2(:) real(dp), intent(inout) :: phi integer(i32) :: img_i, img_j real(dp) :: sx_img, sy_img, sz_img if (abs(q) <= tiny(1.0d0)) return do img_i = 1_i32, nshift do img_j = 1_i32, nshift sx_img = sx sy_img = sy sz_img = sz if (axis1 == 1_i32) sx_img = sx_img + shift_axis1(img_i) if (axis1 == 2_i32) sy_img = sy_img + shift_axis1(img_i) if (axis1 == 3_i32) sz_img = sz_img + shift_axis1(img_i) if (axis2 == 1_i32) sx_img = sx_img + shift_axis2(img_j) if (axis2 == 2_i32) sy_img = sy_img + shift_axis2(img_j) if (axis2 == 3_i32) sz_img = sz_img + shift_axis2(img_j) call accumulate_point_charge_potential(q, sx_img, sy_img, sz_img, tx, ty, tz, soft2, phi) end do end do end subroutine accumulate_point_charge_images_potential pure subroutine accumulate_point_charge_shifted_field( & q, sx, sy, sz, shift1, shift2, axis1, axis2, tx, ty, tz, soft2, ex, ey, ez & ) real(dp), intent(in) :: q, sx, sy, sz, shift1, shift2, tx, ty, tz, soft2 integer(i32), intent(in) :: axis1, axis2 real(dp), intent(inout) :: ex, ey, ez real(dp) :: sx_img, sy_img, sz_img sx_img = sx sy_img = sy sz_img = sz if (axis1 == 1_i32) sx_img = sx_img + shift1 if (axis1 == 2_i32) sy_img = sy_img + shift1 if (axis1 == 3_i32) sz_img = sz_img + shift1 if (axis2 == 1_i32) sx_img = sx_img + shift2 if (axis2 == 2_i32) sy_img = sy_img + shift2 if (axis2 == 3_i32) sz_img = sz_img + shift2 call accumulate_point_charge_field(q, sx_img, sy_img, sz_img, tx, ty, tz, soft2, ex, ey, ez) end subroutine accumulate_point_charge_shifted_field pure subroutine accumulate_point_charge_shifted_potential( & q, sx, sy, sz, shift1, shift2, axis1, axis2, tx, ty, tz, soft2, phi & ) real(dp), intent(in) :: q, sx, sy, sz, shift1, shift2, tx, ty, tz, soft2 integer(i32), intent(in) :: axis1, axis2 real(dp), intent(inout) :: phi real(dp) :: sx_img, sy_img, sz_img sx_img = sx sy_img = sy sz_img = sz if (axis1 == 1_i32) sx_img = sx_img + shift1 if (axis1 == 2_i32) sy_img = sy_img + shift1 if (axis1 == 3_i32) sz_img = sz_img + shift1 if (axis2 == 1_i32) sx_img = sx_img + shift2 if (axis2 == 2_i32) sy_img = sy_img + shift2 if (axis2 == 3_i32) sz_img = sz_img + shift2 call accumulate_point_charge_potential(q, sx_img, sy_img, sz_img, tx, ty, tz, soft2, phi) end subroutine accumulate_point_charge_shifted_potential integer(i32) function locate_target_leaf(plan, r) type(fmm_plan_type), intent(in) :: plan real(dp), intent(in) :: r(3) integer(i32) :: oct, child_k, child, cand, axis logical :: use_target_tree real(dp) :: best_dist2, dist2, dx, dy, dz, extent_eps real(dp) :: r_eval(3), root_center(3), root_half(3), node_center_curr(3), cand_center(3) locate_target_leaf = 0_i32 if (plan%nnode <= 0_i32) return use_target_tree = plan%target_tree_ready if (active_tree_nnode(plan, use_target_tree) <= 0_i32) return r_eval = r if (use_target_tree .and. plan%options%use_periodic2) call wrap_periodic2_point(plan, r_eval) if (use_target_tree .or. .not. plan%options%use_periodic2) then root_center = active_tree_node_center(plan, use_target_tree, 1_i32) root_half = active_tree_node_half_size(plan, use_target_tree, 1_i32) extent_eps = 1.0d-12*max(1.0d0, maxval(abs(root_center))) do axis = 1_i32, 3_i32 if (abs(r_eval(axis) - root_center(axis)) > root_half(axis) + extent_eps) return end do end if locate_target_leaf = 1_i32 do while (active_tree_child_count(plan, use_target_tree, locate_target_leaf) > 0_i32) node_center_curr = active_tree_node_center(plan, use_target_tree, locate_target_leaf) oct = octant_index(r_eval(1), r_eval(2), r_eval(3), node_center_curr) child = 0_i32 do child_k = 1_i32, active_tree_child_count(plan, use_target_tree, locate_target_leaf) if (active_tree_child_octant(plan, use_target_tree, child_k, locate_target_leaf) == oct) then child = active_tree_child_idx(plan, use_target_tree, child_k, locate_target_leaf) exit end if end do if (child <= 0_i32) then child = active_tree_child_idx(plan, use_target_tree, 1_i32, locate_target_leaf) best_dist2 = huge(1.0d0) do child_k = 1_i32, active_tree_child_count(plan, use_target_tree, locate_target_leaf) cand = active_tree_child_idx(plan, use_target_tree, child_k, locate_target_leaf) cand_center = active_tree_node_center(plan, use_target_tree, cand) dx = r_eval(1) - cand_center(1) dy = r_eval(2) - cand_center(2) dz = r_eval(3) - cand_center(3) dist2 = dx*dx + dy*dy + dz*dz if (dist2 < best_dist2) then best_dist2 = dist2 child = cand end if end do end if locate_target_leaf = child end do end function locate_target_leaf end module bem_coulomb_fmm_eval_ops