!> Coulomb FMM 電場評価。 module bem_coulomb_fmm_eval_ops use bem_kinds, only: dp, i32 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_m2l_root_oracle use bem_coulomb_fmm_periodic_ewald, only: add_periodic2_exact_ewald_correction_all_sources, & add_periodic2_exact_ewald_potential_correction_all_sources 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 implicit none private public :: core_eval_points_impl public :: core_eval_point_impl public :: core_eval_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 !> 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 !> 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) logical :: use_periodic_root_oracle_fallback 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 use_periodic_root_oracle_fallback = use_periodic2_m2l_root_oracle(plan) 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, use_periodic_root_oracle_fallback, 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) 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) logical :: use_periodic_root_oracle_fallback 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 use_periodic_root_oracle_fallback = use_periodic2_m2l_root_oracle(plan) 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, use_periodic_root_oracle_fallback, 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) end subroutine core_eval_potential_point_xyz_impl subroutine apply_direct_fallback_field(plan, state, rt, soft2, use_periodic_root_oracle_fallback, 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 logical, intent(in) :: use_periodic_root_oracle_fallback real(dp), intent(inout) :: ex, ey, ez real(dp) :: evec(3) call eval_direct_all_sources_scalar(plan, state, rt(1), rt(2), rt(3), soft2, ex, ey, ez) if (use_periodic_root_oracle_fallback) then evec = [ex, ey, ez] call add_periodic2_exact_ewald_correction_all_sources(plan, state, rt, evec) ex = evec(1) ey = evec(2) ez = evec(3) end if end subroutine apply_direct_fallback_field subroutine apply_direct_fallback_potential(plan, state, rt, soft2, use_periodic_root_oracle_fallback, 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 logical, intent(in) :: use_periodic_root_oracle_fallback real(dp), intent(inout) :: phi call eval_direct_all_sources_potential_scalar(plan, state, rt(1), rt(2), rt(3), soft2, phi) if (use_periodic_root_oracle_fallback) then call add_periodic2_exact_ewald_potential_correction_all_sources(plan, state, rt, phi) end if 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 pure 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%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 pure 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%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%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%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 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