bem_periodic_shell_mean.f90 Source File


This file depends on

sourcefile~~bem_periodic_shell_mean.f90~~EfferentGraph sourcefile~bem_periodic_shell_mean.f90 bem_periodic_shell_mean.f90 sourcefile~bem_kinds.f90 bem_kinds.f90 sourcefile~bem_periodic_shell_mean.f90->sourcefile~bem_kinds.f90

Files dependent on this one

sourcefile~~bem_periodic_shell_mean.f90~~AfferentGraph sourcefile~bem_periodic_shell_mean.f90 bem_periodic_shell_mean.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90 bem_coulomb_fmm_eval_ops.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_periodic_shell_mean.f90 sourcefile~bem_coulomb_fmm_types.f90 bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_basis.f90 bem_coulomb_fmm_basis.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_basis.f90 sourcefile~bem_coulomb_fmm_periodic.f90 bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_tree_utils.f90 bem_coulomb_fmm_tree_utils.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_tree_utils.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90 bem_coulomb_fmm_periodic_root_ops.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90->sourcefile~bem_periodic_shell_mean.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90->sourcefile~bem_coulomb_fmm_basis.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90->sourcefile~bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_periodic_cell_cache.f90 bem_coulomb_fmm_periodic_cell_cache.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90->sourcefile~bem_coulomb_fmm_periodic_cell_cache.f90 sourcefile~bem_coulomb_fmm_periodic_ewald.f90 bem_coulomb_fmm_periodic_ewald.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90->sourcefile~bem_coulomb_fmm_periodic_ewald.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90 bem_coulomb_fmm_plan_ops.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_periodic_shell_mean.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_periodic_root_ops.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_basis.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_periodic_ewald.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_tree_utils.f90 sourcefile~bem_coulomb_fmm_state_ops.f90 bem_coulomb_fmm_state_ops.f90 sourcefile~bem_coulomb_fmm_state_ops.f90->sourcefile~bem_periodic_shell_mean.f90 sourcefile~bem_coulomb_fmm_state_ops.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_state_ops.f90->sourcefile~bem_coulomb_fmm_tree_utils.f90 sourcefile~bem_coulomb_fmm_types.f90->sourcefile~bem_periodic_shell_mean.f90 sourcefile~bem_coulomb_fmm_basis.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_core.f90 bem_coulomb_fmm_core.f90 sourcefile~bem_coulomb_fmm_core.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_core_build.f90 bem_coulomb_fmm_core_build.f90 sourcefile~bem_coulomb_fmm_core_build.f90->sourcefile~bem_coulomb_fmm_plan_ops.f90 sourcefile~bem_coulomb_fmm_core_build.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_coulomb_fmm_core_eval.f90 bem_coulomb_fmm_core_eval.f90 sourcefile~bem_coulomb_fmm_core_eval.f90->sourcefile~bem_coulomb_fmm_eval_ops.f90 sourcefile~bem_coulomb_fmm_core_eval.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_coulomb_fmm_core_state.f90 bem_coulomb_fmm_core_state.f90 sourcefile~bem_coulomb_fmm_core_state.f90->sourcefile~bem_coulomb_fmm_state_ops.f90 sourcefile~bem_coulomb_fmm_core_state.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_coulomb_fmm_periodic.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_periodic_cell_cache.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_periodic_ewald.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_periodic_ewald.f90->sourcefile~bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_tree_utils.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_tree_utils.f90->sourcefile~bem_coulomb_fmm_periodic.f90 sourcefile~bem_field_kernel_c.f90 bem_field_kernel_c.f90 sourcefile~bem_field_kernel_c.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_field_solver.f90 bem_field_solver.f90 sourcefile~bem_field_solver.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_field_solver_config.f90 bem_field_solver_config.f90 sourcefile~bem_field_solver_config.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_field_solver_config.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_field_solver_eval.f90 bem_field_solver_eval.f90 sourcefile~bem_field_solver_eval.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_field_solver_eval.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_field_solver_fmm.f90 bem_field_solver_fmm.f90 sourcefile~bem_field_solver_fmm.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_field_solver_fmm.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_electrostatic_snapshot.f90 bem_electrostatic_snapshot.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_field_solver_tree.f90 bem_field_solver_tree.f90 sourcefile~bem_field_solver_tree.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_output_writer_summary.f90 bem_output_writer_summary.f90 sourcefile~bem_output_writer_summary.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_output_writer.f90 bem_output_writer.f90 sourcefile~bem_output_writer_summary.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_app_config_particle_runtime.f90 bem_app_config_particle_runtime.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_app_config_potential_runtime.f90 bem_app_config_potential_runtime.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_app_config_potential_runtime.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_electrostatic_snapshot_eval.f90 bem_electrostatic_snapshot_eval.f90 sourcefile~bem_electrostatic_snapshot_eval.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_electrostatic_snapshot_upper_fourier.f90 bem_electrostatic_snapshot_upper_fourier.f90 sourcefile~bem_electrostatic_snapshot_upper_fourier.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_output_writer.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_particle_stepper.f90 bem_particle_stepper.f90 sourcefile~bem_particle_stepper.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_simulator.f90 bem_simulator.f90 sourcefile~bem_simulator.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_simulator.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_simulator.f90->sourcefile~bem_particle_stepper.f90 sourcefile~bem_surface_models.f90 bem_surface_models.f90 sourcefile~bem_simulator.f90->sourcefile~bem_surface_models.f90 sourcefile~bem_app_config_runtime.f90 bem_app_config_runtime.f90 sourcefile~bem_simulator.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_run_outputs.f90 bem_run_outputs.f90 sourcefile~bem_simulator.f90->sourcefile~bem_run_outputs.f90 sourcefile~bem_app_config.f90 bem_app_config.f90 sourcefile~bem_simulator.f90->sourcefile~bem_app_config.f90 sourcefile~bem_surface_models.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~main.f90 main.f90 sourcefile~main.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~main.f90->sourcefile~bem_output_writer.f90 sourcefile~main.f90->sourcefile~bem_simulator.f90 sourcefile~bem_periodic_checkpoint.f90 bem_periodic_checkpoint.f90 sourcefile~main.f90->sourcefile~bem_periodic_checkpoint.f90 sourcefile~main.f90->sourcefile~bem_run_outputs.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_particle_runtime_slots.f90 bem_app_config_particle_runtime_slots.f90 sourcefile~bem_app_config_particle_runtime_slots.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_runtime.f90->sourcefile~bem_app_config_potential_runtime.f90 sourcefile~bem_output_writer_files.f90 bem_output_writer_files.f90 sourcefile~bem_output_writer_files.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_output_writer_history.f90 bem_output_writer_history.f90 sourcefile~bem_output_writer_history.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_periodic_checkpoint.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_run_outputs.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_run_outputs.f90->sourcefile~bem_periodic_checkpoint.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_output_writer.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_output_writer.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_periodic_checkpoint.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_run_outputs.f90 sourcefile~bem_zhao_outflow_refresh.f90 bem_zhao_outflow_refresh.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_zhao_outflow_refresh.f90 sourcefile~bem_simulator_particles.f90 bem_simulator_particles.f90 sourcefile~bem_simulator_particles.f90->sourcefile~bem_particle_stepper.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~bem_surface_models_conductor.f90 bem_surface_models_conductor.f90 sourcefile~bem_surface_models_conductor.f90->sourcefile~bem_surface_models.f90 sourcefile~bem_app_config.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_zhao_outflow_refresh.f90->sourcefile~bem_app_config.f90

Source Code

!> periodic2 の有限画像殻の面平均を、無限面電荷の平均場から差し引いた滑らかな残り。
!!
!! 有限画像殻(中心セルと周囲 N 層の画像)の電位をセル `C` 上で面平均すると、無限面電荷の平均場
!! `k0(h) = -2*pi*|h|/A` と、滑らかな残り `s(h)` の和になる。`s` は 1/r を画像ブロック
!! `[c-(N+1/2)L, c+(N+1/2)L]^2` 上で積分した閉じた式から `k0` を引いたもので、`h=0` でも解析的である。
!! cached_kneq0 の遠方補正は面平均 0 の残差だけを当てはめ、`s` は実行時に高さの表から引く。
!! 単位は FMM core と同じ raw(1/r、Coulomb 定数なし)。
module bem_periodic_shell_mean
  use bem_kinds, only: dp, i32
  implicit none
  private

  real(dp), parameter :: pi = acos(-1.0_dp)
  ! Dunavant の 5 次の三角形積分則(重心座標の 1 成分目、重み)。
  real(dp), parameter :: dunavant_a(7) = [ &
                         1.0_dp/3.0_dp, 0.059715871789770_dp, 0.470142064105115_dp, 0.470142064105115_dp, &
                         0.797426985353087_dp, 0.101286507323456_dp, 0.101286507323456_dp]
  real(dp), parameter :: dunavant_b(7) = [ &
                         1.0_dp/3.0_dp, 0.470142064105115_dp, 0.059715871789770_dp, 0.470142064105115_dp, &
                         0.101286507323456_dp, 0.797426985353087_dp, 0.101286507323456_dp]
  real(dp), parameter :: dunavant_w(7) = [ &
                         0.225_dp, 0.132394152788506_dp, 0.132394152788506_dp, 0.132394152788506_dp, &
                         0.125939180544827_dp, 0.125939180544827_dp, 0.125939180544827_dp]

  !> 線源ごとの `s` の高さ表。高さの格子は等間隔。
  type, public :: periodic_shell_mean_plan_type
    logical :: ready = .false.
    integer(i32) :: nz = 0_i32
    integer(i32) :: periodic_axes(2) = 0_i32
    integer(i32) :: free_axis = 0_i32
    real(dp) :: z0 = 0.0_dp
    real(dp) :: dz = 0.0_dp
    !> 平均をとるセルの下端と周期長、画像の層数、softening の二乗。
    real(dp) :: cell_lo(2) = 0.0_dp
    real(dp) :: cell_len(2) = 0.0_dp
    integer(i32) :: image_layers = 0_i32
    real(dp) :: soft2 = 0.0_dp
    !> `(nz, nsrc)`。値と高さ微分。
    real(dp), allocatable :: value(:, :)
    real(dp), allocatable :: slope(:, :)
  end type periodic_shell_mean_plan_type

  !> 電荷を重ねた `S(z) = sum_i q_i s_i(z)` と `dS/dz` の格子値。
  type, public :: periodic_shell_mean_state_type
    real(dp), allocatable :: value(:)
    real(dp), allocatable :: slope(:)
  end type periodic_shell_mean_state_type

  public :: periodic_shell_mean_excess
  public :: periodic_shell_mean_point_excess
  public :: build_periodic_shell_mean_plan
  public :: refresh_periodic_shell_mean_state
  public :: eval_periodic_shell_mean
  public :: reset_periodic_shell_mean_plan
  public :: reset_periodic_shell_mean_state

contains

  !> 単位電荷 1 個の `s(h)` と `ds/dh`。
  !! @param[in] h 評価点の高さ − 線源の高さ。
  !! @param[in] x1,x2,y1,y2 線源から見た画像ブロックの範囲(x1<0<x2、y1<0<y2)。
  !! @param[in] area セルの面積。
  !! @param[in] soft2 softening の二乗(P0 panel では 0)。
  pure subroutine periodic_shell_mean_excess(h, x1, x2, y1, y2, area, soft2, s, dsdh)
    real(dp), intent(in) :: h, x1, x2, y1, y2, area, soft2
    real(dp), intent(out) :: s, dsdh
    real(dp) :: h_eff, log_part, atan_part, smooth

    h_eff = sqrt(h*h + soft2)
    log_part = corner_log(x2, y2, h_eff) - corner_log(x1, y2, h_eff) - corner_log(x2, y1, h_eff) + &
               corner_log(x1, y1, h_eff)
    atan_part = corner_atan(x2, y2, h_eff) + corner_atan(x1, y2, h_eff) + corner_atan(x2, y1, h_eff) + &
                corner_atan(x1, y1, h_eff)
    ! smooth = mu(h_eff) + 2*pi*h_eff/A。四隅の atan(xy/(h r)) の和を 2*pi - sum atan(h r/|xy|) に直し、
    ! |h| の折れ目を解析的に打ち消している。
    smooth = (log_part + h_eff*atan_part)/area
    s = smooth + 2.0_dp*pi*(abs(h) - h_eff)/area
    if (h_eff > 0.0_dp) then
      dsdh = (h/h_eff)*atan_part/area + 2.0_dp*pi*(sign(1.0_dp, h) - h/h_eff)/area
    else
      dsdh = 0.0_dp
    end if
  end subroutine periodic_shell_mean_excess

  !> 点線源 1 個の `s` と `ds/dh`。平均をとるセルと画像の層数は plan の値を使う。
  pure subroutine periodic_shell_mean_point_excess(plan, source, target_z, s, dsdh)
    type(periodic_shell_mean_plan_type), intent(in) :: plan
    real(dp), intent(in) :: source(3), target_z
    real(dp), intent(out) :: s, dsdh
    real(dp) :: lo(2), hi(2), xs, ys

    lo = plan%cell_lo - real(plan%image_layers, dp)*plan%cell_len
    hi = plan%cell_lo + real(plan%image_layers + 1_i32, dp)*plan%cell_len
    xs = source(plan%periodic_axes(1))
    ys = source(plan%periodic_axes(2))
    call periodic_shell_mean_excess( &
      target_z - source(plan%free_axis), lo(1) - xs, hi(1) - xs, lo(2) - ys, hi(2) - ys, &
      plan%cell_len(1)*plan%cell_len(2), plan%soft2, s, dsdh &
      )
  end subroutine periodic_shell_mean_point_excess

  !> 線源ごとの `s` を高さの格子に表として持つ。
  !! @param[in] periodic_axes 周期軸、free_axis 非周期軸。
  !! @param[in] cell_center 平均をとるセルの周期軸方向の中心。線源から画像ブロックの端まで約 L 離すため線源の中心に置く。
  !! @param[in] cell_len 周期長、image_layers 画像の層数、softening P0 panel では 0。
  !! @param[in] z_min,z_max 評価点がとる高さの範囲、spacing 格子間隔の上限。
  !! @param[in] vertices `(3,3,nsrc)` 三角形の頂点。panel でなければ 3 頂点とも同じ点を渡す。
  subroutine build_periodic_shell_mean_plan( &
    periodic_axes, free_axis, cell_center, cell_len, image_layers, softening, z_min, z_max, spacing, vertices, plan &
    )
    integer(i32), intent(in) :: periodic_axes(2), free_axis, image_layers
    real(dp), intent(in) :: cell_center(2), cell_len(2), softening, z_min, z_max, spacing
    real(dp), intent(in) :: vertices(:, :, :)
    type(periodic_shell_mean_plan_type), intent(inout) :: plan
    integer(i32) :: nsrc, k, i, q, nq
    real(dp) :: point(3), weight, s, dsdh, z
    logical :: is_point

    call reset_periodic_shell_mean_plan(plan)
    if (z_max < z_min .or. spacing <= 0.0_dp) error stop 'periodic shell-mean plan has an invalid height range.'
    nsrc = int(size(vertices, 3), i32)
    plan%periodic_axes = periodic_axes
    plan%free_axis = free_axis
    plan%cell_lo = cell_center - 0.5_dp*cell_len
    plan%cell_len = cell_len
    plan%image_layers = image_layers
    plan%soft2 = softening*softening
    plan%nz = max(2_i32, int(ceiling((z_max - z_min)/spacing), i32) + 1_i32)
    plan%z0 = z_min
    plan%dz = max(z_max - z_min, tiny(1.0_dp))/real(plan%nz - 1_i32, dp)
    allocate (plan%value(plan%nz, nsrc), plan%slope(plan%nz, nsrc))
    plan%value = 0.0_dp
    plan%slope = 0.0_dp
    !$omp parallel do default(shared) private(i, k, q, nq, point, weight, s, dsdh, z, is_point) schedule(static)
    do i = 1_i32, nsrc
      is_point = all(vertices(:, 2, i) == vertices(:, 1, i)) .and. all(vertices(:, 3, i) == vertices(:, 1, i))
      nq = merge(1_i32, 7_i32, is_point)
      do q = 1_i32, nq
        if (is_point) then
          point = vertices(:, 1, i)
          weight = 1.0_dp
        else
          point = dunavant_a(q)*vertices(:, 1, i) + dunavant_b(q)*vertices(:, 2, i) + &
                  (1.0_dp - dunavant_a(q) - dunavant_b(q))*vertices(:, 3, i)
          weight = dunavant_w(q)
        end if
        do k = 1_i32, plan%nz
          z = plan%z0 + real(k - 1_i32, dp)*plan%dz
          call periodic_shell_mean_point_excess(plan, point, z, s, dsdh)
          plan%value(k, i) = plan%value(k, i) + weight*s
          plan%slope(k, i) = plan%slope(k, i) + weight*dsdh
        end do
      end do
    end do
    !$omp end parallel do
    plan%ready = .true.
  end subroutine build_periodic_shell_mean_plan

  !> 線源電荷から `S` と `dS/dz` の格子値を作る。
  subroutine refresh_periodic_shell_mean_state(plan, charge, state)
    type(periodic_shell_mean_plan_type), intent(in) :: plan
    real(dp), intent(in) :: charge(:)
    type(periodic_shell_mean_state_type), intent(inout) :: state

    if (.not. plan%ready) return
    if (size(charge) /= size(plan%value, 2)) error stop 'periodic shell-mean charge size mismatch.'
    if (.not. allocated(state%value)) allocate (state%value(plan%nz), state%slope(plan%nz))
    if (size(state%value) /= plan%nz) then
      deallocate (state%value, state%slope)
      allocate (state%value(plan%nz), state%slope(plan%nz))
    end if
    state%value = matmul(plan%value, charge)
    state%slope = matmul(plan%slope, charge)
  end subroutine refresh_periodic_shell_mean_state

  !> 3 次 Hermite 補間した `S(z)` と、同じ多項式の微分 `dS/dz`。電場は電位の勾配と一致する。
  pure subroutine eval_periodic_shell_mean(plan, state, z, value, slope)
    type(periodic_shell_mean_plan_type), intent(in) :: plan
    type(periodic_shell_mean_state_type), intent(in) :: state
    real(dp), intent(in) :: z
    real(dp), intent(out) :: value, slope
    integer(i32) :: k
    real(dp) :: t, t2, t3, h00, h10, h01, h11, d00, d10, d01, d11

    value = 0.0_dp
    slope = 0.0_dp
    if (.not. plan%ready .or. .not. allocated(state%value)) return
    t = (z - plan%z0)/plan%dz
    k = min(max(int(floor(t), i32), 0_i32), plan%nz - 2_i32)
    t = min(max(t - real(k, dp), 0.0_dp), 1.0_dp)
    k = k + 1_i32
    t2 = t*t
    t3 = t2*t
    h00 = 2.0_dp*t3 - 3.0_dp*t2 + 1.0_dp
    h10 = t3 - 2.0_dp*t2 + t
    h01 = -2.0_dp*t3 + 3.0_dp*t2
    h11 = t3 - t2
    d00 = 6.0_dp*t2 - 6.0_dp*t
    d10 = 3.0_dp*t2 - 4.0_dp*t + 1.0_dp
    d01 = -6.0_dp*t2 + 6.0_dp*t
    d11 = 3.0_dp*t2 - 2.0_dp*t
    value = h00*state%value(k) + h10*plan%dz*state%slope(k) + h01*state%value(k + 1_i32) + &
            h11*plan%dz*state%slope(k + 1_i32)
    slope = (d00*state%value(k) + d01*state%value(k + 1_i32))/plan%dz + d10*state%slope(k) + &
            d11*state%slope(k + 1_i32)
  end subroutine eval_periodic_shell_mean

  subroutine reset_periodic_shell_mean_plan(plan)
    type(periodic_shell_mean_plan_type), intent(inout) :: plan

    if (allocated(plan%value)) deallocate (plan%value)
    if (allocated(plan%slope)) deallocate (plan%slope)
    plan%ready = .false.
    plan%nz = 0_i32
    plan%z0 = 0.0_dp
    plan%dz = 0.0_dp
  end subroutine reset_periodic_shell_mean_plan

  subroutine reset_periodic_shell_mean_state(state)
    type(periodic_shell_mean_state_type), intent(inout) :: state

    if (allocated(state%value)) deallocate (state%value)
    if (allocated(state%slope)) deallocate (state%slope)
  end subroutine reset_periodic_shell_mean_state

  !> 四隅の対数項 x*log(y+r) + y*log(x+r)。負の引数では a+r = (r^2-a^2)/(r-a) で桁落ちを避ける。
  pure real(dp) function corner_log(x, y, h) result(value)
    real(dp), intent(in) :: x, y, h
    real(dp) :: r

    r = sqrt(x*x + y*y + h*h)
    value = x*log_a_plus_r(y, r, x*x + h*h) + y*log_a_plus_r(x, r, y*y + h*h)
  end function corner_log

  pure real(dp) function log_a_plus_r(a, r, rest) result(value)
    real(dp), intent(in) :: a, r, rest

    if (a >= 0.0_dp) then
      value = log(a + r)
    else
      value = log(rest) - log(r - a)
    end if
  end function log_a_plus_r

  !> 四隅の atan(h r/|x y|)。h -> 0 で 0 になり、|h| の折れ目を消した形。
  pure real(dp) function corner_atan(x, y, h) result(value)
    real(dp), intent(in) :: x, y, h

    value = atan(h*sqrt(x*x + y*y + h*h)/abs(x*y))
  end function corner_atan

end module bem_periodic_shell_mean