bem_periodic_zero_mode_plan.f90 Source File


This file depends on

sourcefile~~bem_periodic_zero_mode_plan.f90~~EfferentGraph sourcefile~bem_periodic_zero_mode_plan.f90 bem_periodic_zero_mode_plan.f90 sourcefile~bem_constants.f90 bem_constants.f90 sourcefile~bem_periodic_zero_mode_plan.f90->sourcefile~bem_constants.f90 sourcefile~bem_kinds.f90 bem_kinds.f90 sourcefile~bem_periodic_zero_mode_plan.f90->sourcefile~bem_kinds.f90 sourcefile~bem_sorting.f90 bem_sorting.f90 sourcefile~bem_periodic_zero_mode_plan.f90->sourcefile~bem_sorting.f90 sourcefile~bem_types.f90 bem_types.f90 sourcefile~bem_periodic_zero_mode_plan.f90->sourcefile~bem_types.f90 sourcefile~bem_constants.f90->sourcefile~bem_kinds.f90 sourcefile~bem_sorting.f90->sourcefile~bem_kinds.f90 sourcefile~bem_types.f90->sourcefile~bem_kinds.f90 sourcefile~bem_outflow_spectrum.f90 bem_outflow_spectrum.f90 sourcefile~bem_types.f90->sourcefile~bem_outflow_spectrum.f90 sourcefile~bem_outflow_spectrum.f90->sourcefile~bem_kinds.f90

Files dependent on this one

sourcefile~~bem_periodic_zero_mode_plan.f90~~AfferentGraph sourcefile~bem_periodic_zero_mode_plan.f90 bem_periodic_zero_mode_plan.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90 bem_coulomb_fmm_plan_ops.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_periodic_zero_mode_plan.f90 sourcefile~bem_coulomb_fmm_types.f90 bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_basis.f90 bem_coulomb_fmm_basis.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_basis.f90 sourcefile~bem_coulomb_fmm_periodic.f90 bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_periodic_ewald.f90 bem_coulomb_fmm_periodic_ewald.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_periodic_ewald.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90 bem_coulomb_fmm_periodic_root_ops.f90 sourcefile~bem_coulomb_fmm_plan_ops.f90->sourcefile~bem_coulomb_fmm_periodic_root_ops.f90 sourcefile~bem_coulomb_fmm_tree_utils.f90 bem_coulomb_fmm_tree_utils.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_zero_mode_plan.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_zero_mode_plan.f90 sourcefile~bem_electrostatic_snapshot.f90 bem_electrostatic_snapshot.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_periodic_zero_mode_plan.f90 sourcefile~bem_periodic_zero_mode_eval.f90 bem_periodic_zero_mode_eval.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_periodic_zero_mode_eval.f90 sourcefile~bem_field_solver.f90 bem_field_solver.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_periodic_zero_mode_c.f90 bem_periodic_zero_mode_c.f90 sourcefile~bem_periodic_zero_mode_c.f90->sourcefile~bem_periodic_zero_mode_plan.f90 sourcefile~bem_periodic_zero_mode_c.f90->sourcefile~bem_periodic_zero_mode_eval.f90 sourcefile~bem_periodic_zero_mode_eval.f90->sourcefile~bem_periodic_zero_mode_plan.f90 sourcefile~bem_simulator_loop.f90 bem_simulator_loop.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_periodic_zero_mode_plan.f90 sourcefile~bem_output_writer.f90 bem_output_writer.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_simulator.f90 bem_simulator.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_simulator.f90 sourcefile~bem_app_config_runtime.f90 bem_app_config_runtime.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_periodic_checkpoint.f90 bem_periodic_checkpoint.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_periodic_checkpoint.f90 sourcefile~bem_run_outputs.f90 bem_run_outputs.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_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_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_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_eval_ops.f90 bem_coulomb_fmm_eval_ops.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_periodic_zero_mode_eval.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_basis.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_periodic.f90 sourcefile~bem_coulomb_fmm_eval_ops.f90->sourcefile~bem_coulomb_fmm_tree_utils.f90 sourcefile~bem_coulomb_fmm_periodic.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_periodic_cell_cache.f90 bem_coulomb_fmm_periodic_cell_cache.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_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_root_ops.f90->sourcefile~bem_coulomb_fmm_periodic_cell_cache.f90 sourcefile~bem_coulomb_fmm_periodic_root_ops.f90->sourcefile~bem_coulomb_fmm_periodic_ewald.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_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->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_simulator.f90->sourcefile~bem_app_config_runtime.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~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_coulomb_fmm_core_eval.f90 bem_coulomb_fmm_core_eval.f90 sourcefile~bem_coulomb_fmm_core_eval.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_coulomb_fmm_core_eval.f90->sourcefile~bem_coulomb_fmm_eval_ops.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->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_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_output_writer_summary.f90 bem_output_writer_summary.f90 sourcefile~bem_output_writer_summary.f90->sourcefile~bem_output_writer.f90 sourcefile~bem_output_writer_summary.f90->sourcefile~bem_field_solver.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_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_field_solver_tree.f90 bem_field_solver_tree.f90 sourcefile~bem_field_solver_tree.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_zhao_outflow_refresh.f90->sourcefile~bem_app_config.f90

Source Code

module bem_periodic_zero_mode_plan
  use bem_kinds, only: dp, i32
  use bem_constants, only: eps0
  use bem_types, only: mesh_type
  use bem_sorting, only: sort_real_ascending
  use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
  implicit none
  private

  integer(i32), parameter, public :: periodic_zero_mode_ok = 0_i32
  integer(i32), parameter, public :: periodic_zero_mode_invalid = 1_i32

  type :: panel_height_projection_type
    real(dp) :: z(3) = [0.0_dp, 0.0_dp, 0.0_dp]
    integer(i32) :: break_index(3) = [0_i32, 0_i32, 0_i32]
    logical :: horizontal = .false.
  end type panel_height_projection_type

  type, public :: periodic_zero_mode_plan_type
    integer(i32) :: nelem = 0_i32
    integer(i32) :: nbreak = 0_i32
    real(dp) :: area_xy = 0.0_dp
    !> Geometric part of the height-coincidence tolerance (zero_mode_heights_coincide).
    real(dp) :: height_tolerance = 0.0_dp
    real(dp), allocatable :: break_z(:)
    type(panel_height_projection_type), allocatable :: panel(:)
  end type periodic_zero_mode_plan_type

  type, public :: periodic_zero_mode_state_type
    real(dp) :: e_bottom = 0.0_dp
    real(dp) :: z_gauge = 0.0_dp
    real(dp) :: phi_gauge = 0.0_dp
    real(dp) :: total_charge = 0.0_dp
    !> Charge primitive at z_gauge; every evaluation subtracts it, so refresh computes it once.
    real(dp) :: gauge_primitive = 0.0_dp
    !> 区間 k の累積電荷多項式の係数。k>=2 は左端 break_z(k-1)、k=1 は break_z(1) を原点とする局所座標
    !! t=z-原点 の多項式で、絶対 z の冪の相殺を避ける(zero_mode_interval_origin)。
    real(dp), allocatable :: cumulative_charge_coeff(:, :)
    real(dp), allocatable :: primitive_at_break(:)
    real(dp), allocatable :: sheet_charge(:)
  end type periodic_zero_mode_state_type

  public :: build_periodic_zero_mode_plan
  public :: build_periodic_zero_mode_height_plan
  public :: refresh_periodic_zero_mode_state
  public :: symmetric_vacuum_bottom_field
  public :: zero_mode_interval_origin
  public :: integrate_local_polynomial
  public :: zero_mode_heights_coincide
  public :: locate_zero_mode_interval
  public :: zero_mode_charge_primitive

contains

  pure real(dp) function symmetric_vacuum_bottom_field(plan, charge) result(field)
    type(periodic_zero_mode_plan_type), intent(in) :: plan
    real(dp), intent(in) :: charge(:)

    field = -sum(charge)/(2.0_dp*eps0*plan%area_xy)
  end function symmetric_vacuum_bottom_field

  subroutine build_periodic_zero_mode_plan(mesh, area_xy, plan, status, message)
    type(mesh_type), intent(in) :: mesh
    real(dp), intent(in) :: area_xy
    type(periodic_zero_mode_plan_type), intent(out) :: plan
    integer(i32), intent(out) :: status
    character(len=*), intent(out) :: message
    real(dp), allocatable :: heights(:, :)
    integer(i32) :: elem

    status = periodic_zero_mode_invalid
    message = ''
    if (mesh%nelem <= 0_i32) then
      message = 'periodic zero-mode mesh must contain at least one element'
      return
    end if

    allocate (heights(3, mesh%nelem))
    do elem = 1, mesh%nelem
      heights(:, elem) = [mesh%v0(3, elem), mesh%v1(3, elem), mesh%v2(3, elem)]
    end do
    call build_periodic_zero_mode_height_plan(heights, area_xy, plan, status, message)
  end subroutine build_periodic_zero_mode_plan

  subroutine build_periodic_zero_mode_height_plan(source_heights, area_xy, plan, status, message)
    real(dp), intent(in) :: source_heights(:, :)
    real(dp), intent(in) :: area_xy
    type(periodic_zero_mode_plan_type), intent(out) :: plan
    integer(i32), intent(out) :: status
    character(len=*), intent(out) :: message
    real(dp), allocatable :: heights(:), unique_heights(:)
    real(dp) :: tolerance
    integer(i32) :: elem, vertex, count, nelem

    status = periodic_zero_mode_invalid
    message = ''
    if (.not. ieee_is_finite(area_xy) .or. area_xy <= 0.0_dp) then
      message = 'periodic zero-mode area_xy must be finite and positive'
      return
    end if
    if (size(source_heights, 1) /= 3 .or. size(source_heights, 2) <= 0) then
      message = 'periodic zero-mode heights must have shape (3, nsource)'
      return
    end if
    if (any(.not. ieee_is_finite(source_heights))) then
      message = 'periodic zero-mode panel heights must be finite'
      return
    end if
    nelem = int(size(source_heights, 2), i32)
    allocate (heights(3*nelem))
    do elem = 1_i32, nelem
      heights(3*elem - 2:3*elem) = source_heights(:, elem)
    end do
    call sort_real_ascending(heights)
    ! Geometric scale of the problem: the slab height and the lateral cell size. Neither depends on
    ! the absolute z origin or on a 1 m unit; sqrt(area_xy) keeps a flat slab from treating
    ! rounding-level tilts (eps times the lateral coordinates) as inclined panels.
    tolerance = 128.0_dp*epsilon(1.0_dp)*max(heights(size(heights)) - heights(1), sqrt(area_xy))
    allocate (unique_heights(size(heights)))
    count = 1_i32
    unique_heights(1) = heights(1)
    do vertex = 2, size(heights)
      if (.not. zero_mode_heights_coincide(heights(vertex), unique_heights(count), tolerance)) then
        count = count + 1_i32
        unique_heights(count) = heights(vertex)
      end if
    end do

    plan%nelem = nelem
    plan%nbreak = count
    plan%area_xy = area_xy
    plan%height_tolerance = tolerance
    allocate (plan%break_z(count), plan%panel(nelem))
    plan%break_z = unique_heights(1:count)
    do elem = 1, nelem
      plan%panel(elem)%z = source_heights(:, elem)
      call sort_three(plan%panel(elem)%z)
      plan%panel(elem)%horizontal = &
        zero_mode_heights_coincide(plan%panel(elem)%z(3), plan%panel(elem)%z(1), tolerance)
      do vertex = 1, 3
        plan%panel(elem)%break_index(vertex) = nearest_break(plan%break_z, plan%panel(elem)%z(vertex))
      end do
    end do
    status = periodic_zero_mode_ok
  end subroutine build_periodic_zero_mode_height_plan

  subroutine refresh_periodic_zero_mode_state(plan, charge, e_bottom, z_gauge, phi_gauge, state)
    type(periodic_zero_mode_plan_type), intent(in) :: plan
    real(dp), intent(in) :: charge(:)
    real(dp), intent(in) :: e_bottom, z_gauge, phi_gauge
    !> Arrays already sized for this plan are reused; every component is overwritten.
    type(periodic_zero_mode_state_type), intent(inout) :: state
    real(dp) :: z0, z1, z2, denominator, offset, shift
    integer(i32) :: elem, interval, ninterval, i0, i1, i2, gauge_interval, gauge_breakpoint

    if (size(charge) /= plan%nelem) error stop 'periodic zero-mode charge size mismatch.'
    if (any(.not. ieee_is_finite(charge))) error stop 'periodic zero-mode charges must be finite.'
    ninterval = plan%nbreak + 1_i32
    if (allocated(state%cumulative_charge_coeff)) then
      if (size(state%cumulative_charge_coeff, 2) /= ninterval) deallocate (state%cumulative_charge_coeff)
    end if
    if (.not. allocated(state%cumulative_charge_coeff)) allocate (state%cumulative_charge_coeff(3, ninterval))
    call size_break_array(state%primitive_at_break, plan%nbreak)
    call size_break_array(state%sheet_charge, plan%nbreak)
    ! The per-interval differences accumulate in cumulative_charge_coeff and become prefix sums in place.
    state%cumulative_charge_coeff = 0.0_dp
    state%sheet_charge = 0.0_dp
    state%e_bottom = e_bottom
    state%z_gauge = z_gauge
    state%phi_gauge = phi_gauge
    state%total_charge = sum(charge)

    associate (difference => state%cumulative_charge_coeff)
      ! 傾斜 panel の寄与は、加える区間と外す区間それぞれの局所原点で表す。係数は panel 自身の高さ幅だけで
      ! 決まり、絶対 z に依らない(平行移動で値が変わらない)。
      do elem = 1, plan%nelem
        z0 = plan%panel(elem)%z(1)
        z1 = plan%panel(elem)%z(2)
        z2 = plan%panel(elem)%z(3)
        i0 = plan%panel(elem)%break_index(1)
        i1 = plan%panel(elem)%break_index(2)
        i2 = plan%panel(elem)%break_index(3)
        if (plan%panel(elem)%horizontal) then
          state%sheet_charge(i0) = state%sheet_charge(i0) + charge(elem)
        else
          if (i1 > i0) then
            ! [z0, z1]: q*(z-z0)^2/denominator。原点 a では z-z0 = t+offset, offset=a-z0。
            denominator = (z1 - z0)*(z2 - z0)
            offset = plan%break_z(i0) - z0
            difference(:, i0 + 1_i32) = difference(:, i0 + 1_i32) + &
                                        charge(elem)*[offset*offset, 2.0_dp*offset, 1.0_dp]/denominator
            offset = plan%break_z(i1) - z0
            difference(:, i1 + 1_i32) = difference(:, i1 + 1_i32) - &
                                        charge(elem)*[offset*offset, 2.0_dp*offset, 1.0_dp]/denominator
          end if
          if (i2 > i1) then
            ! [z1, z2]: q*(1-(z2-z)^2/denominator)。原点 a では z2-z = offset-t, offset=z2-a。
            denominator = (z2 - z0)*(z2 - z1)
            offset = z2 - plan%break_z(i1)
            difference(:, i1 + 1_i32) = difference(:, i1 + 1_i32) + charge(elem)*[ &
                                        1.0_dp - offset*offset/denominator, 2.0_dp*offset/denominator, &
                                        -1.0_dp/denominator]
            offset = z2 - plan%break_z(i2)
            difference(:, i2 + 1_i32) = difference(:, i2 + 1_i32) - charge(elem)*[ &
                                        1.0_dp - offset*offset/denominator, 2.0_dp*offset/denominator, &
                                        -1.0_dp/denominator]
          end if
        end if
        difference(1, i2 + 1_i32) = difference(1, i2 + 1_i32) + charge(elem)
      end do

      ! 前の区間の多項式を次の区間の原点へ移してから差分を足す。移す量は隣接 break の間隔だけ。
      do interval = 2, ninterval
        shift = zero_mode_interval_origin(plan%break_z, interval) - &
                zero_mode_interval_origin(plan%break_z, interval - 1_i32)
        difference(:, interval) = shift_polynomial(difference(:, interval - 1), shift) + difference(:, interval)
      end do
    end associate
    ! Outside the source slab the integrated charge is known exactly. Do not
    ! extrapolate cancellation residue from the inclined-panel polynomials.
    state%cumulative_charge_coeff(:, 1) = 0.0_dp
    state%cumulative_charge_coeff(:, ninterval) = [state%total_charge, 0.0_dp, 0.0_dp]
    state%primitive_at_break(1) = 0.0_dp
    do interval = 1, plan%nbreak - 1
      state%primitive_at_break(interval + 1) = state%primitive_at_break(interval) + &
                                               integrate_local_polynomial( &
                                               state%cumulative_charge_coeff(:, interval + 1), &
                                               plan%break_z(interval + 1) - plan%break_z(interval) &
                                               )
    end do
    call locate_zero_mode_interval(plan%break_z, plan%height_tolerance, z_gauge, gauge_interval, gauge_breakpoint)
    state%gauge_primitive = zero_mode_charge_primitive(plan, state, z_gauge, gauge_interval)
  end subroutine refresh_periodic_zero_mode_state

  subroutine size_break_array(values, nbreak)
    real(dp), allocatable, intent(inout) :: values(:)
    integer(i32), intent(in) :: nbreak

    if (allocated(values)) then
      if (size(values) /= nbreak) deallocate (values)
    end if
    if (.not. allocated(values)) allocate (values(nbreak))
  end subroutine size_break_array

  pure real(dp) function zero_mode_charge_primitive(plan, state, z, interval) result(primitive)
    type(periodic_zero_mode_plan_type), intent(in) :: plan
    type(periodic_zero_mode_state_type), intent(in) :: state
    real(dp), intent(in) :: z
    integer(i32), intent(in) :: interval

    ! 区間 1 は最下 break から、それ以外は区間の左端 break(局所原点)から積分する。
    primitive = integrate_local_polynomial( &
                state%cumulative_charge_coeff(:, interval), z - zero_mode_interval_origin(plan%break_z, interval) &
                )
    if (interval > 1_i32) primitive = primitive + state%primitive_at_break(interval - 1_i32)
  end function zero_mode_charge_primitive

  !> A target on a breakpoint (same coincidence rule as the plan's breakpoint merge) takes the
  !! requested sheet trace; any other target lies strictly inside one interval.
  pure subroutine locate_zero_mode_interval(break_z, tolerance, z, interval, breakpoint)
    real(dp), intent(in) :: break_z(:), tolerance, z
    integer(i32), intent(out) :: interval, breakpoint
    integer(i32) :: low, high, middle

    low = 1_i32
    high = size(break_z)
    breakpoint = 0_i32
    do while (low <= high)
      middle = (low + high)/2_i32
      if (zero_mode_heights_coincide(z, break_z(middle), tolerance)) then
        breakpoint = middle
        interval = middle + 1_i32
        return
      else if (z < break_z(middle)) then
        high = middle - 1_i32
      else
        low = middle + 1_i32
      end if
    end do
    interval = low
  end subroutine locate_zero_mode_interval

  !> Two heights are one breakpoint when they differ by at most the plan's geometric tolerance or by
  !! the few ULPs that binary64 cannot resolve at their magnitude. Only the second term sees the
  !! absolute coordinate, so a representable tilt (tens of ULPs) far from the origin stays inclined.
  pure logical function zero_mode_heights_coincide(a, b, geometric_tolerance) result(coincide)
    real(dp), intent(in) :: a, b, geometric_tolerance

    coincide = abs(a - b) <= max(geometric_tolerance, 8.0_dp*spacing(max(abs(a), abs(b))))
  end function zero_mode_heights_coincide

  !> 区間 interval の累積電荷多項式の原点。区間 1 は最下 break、それ以外は区間の左端 break。
  pure real(dp) function zero_mode_interval_origin(break_z, interval) result(origin)
    real(dp), intent(in) :: break_z(:)
    integer(i32), intent(in) :: interval

    origin = break_z(max(interval - 1_i32, 1_i32))
  end function zero_mode_interval_origin

  !> 局所座標の多項式を原点から t=0..t_upper まで積分する。
  pure real(dp) function integrate_local_polynomial(coefficient, t_upper) result(integral)
    real(dp), intent(in) :: coefficient(3), t_upper

    integral = t_upper*(coefficient(1) + t_upper*(0.5_dp*coefficient(2) + t_upper*coefficient(3)/3.0_dp))
  end function integrate_local_polynomial

  !> c0+c1*t+c2*t^2 を原点を shift だけ進めた座標 t'=t-shift の係数へ書き換える。
  pure function shift_polynomial(coefficient, shift) result(shifted)
    real(dp), intent(in) :: coefficient(3), shift
    real(dp) :: shifted(3)

    shifted(1) = coefficient(1) + shift*(coefficient(2) + shift*coefficient(3))
    shifted(2) = coefficient(2) + 2.0_dp*shift*coefficient(3)
    shifted(3) = coefficient(3)
  end function shift_polynomial

  pure integer(i32) function nearest_break(break_z, value) result(index)
    real(dp), intent(in) :: break_z(:), value
    integer(i32) :: low, high, middle

    ! break_z is sorted and nonempty; only the two neighbors can be nearest.
    low = 1_i32
    high = size(break_z)
    do while (low < high)
      middle = low + (high - low)/2_i32
      if (break_z(middle) < value) then
        low = middle + 1_i32
      else
        high = middle
      end if
    end do
    index = low
    if (index > 1_i32) then
      ! Preserve the previous linear scan's preference for the lower index on ties.
      if (abs(break_z(index - 1_i32) - value) <= abs(break_z(index) - value)) index = index - 1_i32
    end if
  end function nearest_break

  pure subroutine sort_three(values)
    real(dp), intent(inout) :: values(3)
    real(dp) :: temporary

    if (values(1) > values(2)) then
      temporary = values(1); values(1) = values(2); values(2) = temporary
    end if
    if (values(2) > values(3)) then
      temporary = values(2); values(2) = values(3); values(3) = temporary
    end if
    if (values(1) > values(2)) then
      temporary = values(1); values(1) = values(2); values(2) = temporary
    end if
  end subroutine sort_three

end module bem_periodic_zero_mode_plan