bem_app_config_particle_runtime_batch.f90 Source File


This file depends on

sourcefile~~bem_app_config_particle_runtime_batch.f90~~EfferentGraph sourcefile~bem_app_config_particle_runtime_batch.f90 bem_app_config_particle_runtime_batch.f90 sourcefile~bem_app_config_particle_runtime.f90 bem_app_config_particle_runtime.f90 sourcefile~bem_app_config_particle_runtime_batch.f90->sourcefile~bem_app_config_particle_runtime.f90 sourcefile~bem_particles.f90 bem_particles.f90 sourcefile~bem_app_config_particle_runtime_batch.f90->sourcefile~bem_particles.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_types.f90 bem_app_config_types.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_app_config_types.f90 sourcefile~bem_collision.f90 bem_collision.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_collision.f90 sourcefile~bem_config_helpers.f90 bem_config_helpers.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_config_helpers.f90 sourcefile~bem_constants.f90 bem_constants.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_constants.f90 sourcefile~bem_electrostatic_snapshot.f90 bem_electrostatic_snapshot.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_external_boundary_contract.f90 bem_external_boundary_contract.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_external_boundary_contract.f90 sourcefile~bem_injection.f90 bem_injection.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_injection.f90 sourcefile~bem_kinds.f90 bem_kinds.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_kinds.f90 sourcefile~bem_mpi.f90 bem_mpi.F90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_mpi.f90 sourcefile~bem_string_utils.f90 bem_string_utils.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_types.f90 bem_types.f90 sourcefile~bem_app_config_particle_runtime.f90->sourcefile~bem_types.f90 sourcefile~bem_particles.f90->sourcefile~bem_kinds.f90 sourcefile~bem_particles.f90->sourcefile~bem_types.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_app_config_types.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_config_helpers.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_constants.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_electrostatic_snapshot.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_kinds.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_app_config_potential_runtime.f90->sourcefile~bem_types.f90 sourcefile~bem_app_config_types.f90->sourcefile~bem_kinds.f90 sourcefile~bem_app_config_types.f90->sourcefile~bem_types.f90 sourcefile~bem_physics_config_types.f90 bem_physics_config_types.f90 sourcefile~bem_app_config_types.f90->sourcefile~bem_physics_config_types.f90 sourcefile~bem_collision.f90->sourcefile~bem_kinds.f90 sourcefile~bem_collision.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_collision.f90->sourcefile~bem_types.f90 sourcefile~bem_config_helpers.f90->sourcefile~bem_app_config_types.f90 sourcefile~bem_config_helpers.f90->sourcefile~bem_kinds.f90 sourcefile~bem_config_helpers.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_config_helpers.f90->sourcefile~bem_types.f90 sourcefile~bem_constants.f90->sourcefile~bem_kinds.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_constants.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_kinds.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_types.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_reference.f90 bem_coulomb_fmm_periodic_nonzero_reference.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_coulomb_fmm_periodic_nonzero_reference.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_upper_vacuum.f90 bem_coulomb_fmm_periodic_nonzero_upper_vacuum.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_coulomb_fmm_periodic_nonzero_upper_vacuum.f90 sourcefile~bem_field_solver.f90 bem_field_solver.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_field_solver.f90 sourcefile~bem_panel_geometry.f90 bem_panel_geometry.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_panel_kernel.f90 bem_panel_kernel.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_panel_kernel.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_periodic_zero_mode_plan.f90 bem_periodic_zero_mode_plan.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_periodic_zero_mode_plan.f90 sourcefile~bem_electrostatic_snapshot.f90->sourcefile~bem_physics_config_types.f90 sourcefile~bem_external_boundary_contract.f90->sourcefile~bem_kinds.f90 sourcefile~bem_external_boundary_contract.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_injection_flux.f90 bem_injection_flux.f90 sourcefile~bem_injection.f90->sourcefile~bem_injection_flux.f90 sourcefile~bem_injection_geometry.f90 bem_injection_geometry.f90 sourcefile~bem_injection.f90->sourcefile~bem_injection_geometry.f90 sourcefile~bem_injection_random.f90 bem_injection_random.f90 sourcefile~bem_injection.f90->sourcefile~bem_injection_random.f90 sourcefile~bem_injection_velocity_grid.f90 bem_injection_velocity_grid.f90 sourcefile~bem_injection.f90->sourcefile~bem_injection_velocity_grid.f90 sourcefile~bem_photoelectron_injection.f90 bem_photoelectron_injection.f90 sourcefile~bem_injection.f90->sourcefile~bem_photoelectron_injection.f90 sourcefile~bem_mpi.f90->sourcefile~bem_kinds.f90 sourcefile~bem_types.f90->sourcefile~bem_kinds.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_reference.f90->sourcefile~bem_constants.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_reference.f90->sourcefile~bem_kinds.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_reference.f90->sourcefile~bem_types.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_reference.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_triangle_quadrature.f90 bem_triangle_quadrature.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_reference.f90->sourcefile~bem_triangle_quadrature.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_upper_vacuum.f90->sourcefile~bem_constants.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_upper_vacuum.f90->sourcefile~bem_kinds.f90 sourcefile~bem_coulomb_fmm_periodic_nonzero_upper_vacuum.f90->sourcefile~bem_types.f90 sourcefile~bem_field_solver.f90->sourcefile~bem_constants.f90 sourcefile~bem_field_solver.f90->sourcefile~bem_kinds.f90 sourcefile~bem_field_solver.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_field_solver.f90->sourcefile~bem_types.f90 sourcefile~bem_field_solver.f90->sourcefile~bem_physics_config_types.f90 sourcefile~bem_coulomb_fmm_core.f90 bem_coulomb_fmm_core.f90 sourcefile~bem_field_solver.f90->sourcefile~bem_coulomb_fmm_core.f90 sourcefile~bem_injection_flux.f90->sourcefile~bem_constants.f90 sourcefile~bem_injection_flux.f90->sourcefile~bem_kinds.f90 sourcefile~bem_injection_flux.f90->sourcefile~bem_injection_geometry.f90 sourcefile~bem_injection_flux.f90->sourcefile~bem_injection_random.f90 sourcefile~bem_injection_geometry.f90->sourcefile~bem_kinds.f90 sourcefile~bem_injection_geometry.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_injection_random.f90->sourcefile~bem_particles.f90 sourcefile~bem_injection_random.f90->sourcefile~bem_constants.f90 sourcefile~bem_injection_random.f90->sourcefile~bem_kinds.f90 sourcefile~bem_injection_random.f90->sourcefile~bem_types.f90 sourcefile~bem_injection_velocity_grid.f90->sourcefile~bem_kinds.f90 sourcefile~bem_injection_velocity_grid.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_injection_velocity_grid.f90->sourcefile~bem_injection_geometry.f90 sourcefile~bem_panel_geometry.f90->sourcefile~bem_kinds.f90 sourcefile~bem_panel_kernel.f90->sourcefile~bem_constants.f90 sourcefile~bem_panel_kernel.f90->sourcefile~bem_kinds.f90 sourcefile~bem_panel_kernel.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_panel_self_terms.f90 bem_panel_self_terms.f90 sourcefile~bem_panel_kernel.f90->sourcefile~bem_panel_self_terms.f90 sourcefile~bem_periodic_zero_mode_eval.f90->sourcefile~bem_constants.f90 sourcefile~bem_periodic_zero_mode_eval.f90->sourcefile~bem_kinds.f90 sourcefile~bem_periodic_zero_mode_eval.f90->sourcefile~bem_periodic_zero_mode_plan.f90 sourcefile~bem_periodic_zero_mode_plan.f90->sourcefile~bem_constants.f90 sourcefile~bem_periodic_zero_mode_plan.f90->sourcefile~bem_kinds.f90 sourcefile~bem_periodic_zero_mode_plan.f90->sourcefile~bem_types.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_collision.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_constants.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_kinds.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_types.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_injection_flux.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_injection_geometry.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_injection_random.f90 sourcefile~bem_boundary.f90 bem_boundary.f90 sourcefile~bem_photoelectron_injection.f90->sourcefile~bem_boundary.f90 sourcefile~bem_physics_config_types.f90->sourcefile~bem_kinds.f90 sourcefile~bem_physics_config_types.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_physics_config_types.f90->sourcefile~bem_types.f90 sourcefile~bem_boundary.f90->sourcefile~bem_kinds.f90 sourcefile~bem_boundary.f90->sourcefile~bem_types.f90 sourcefile~bem_coulomb_fmm_core.f90->sourcefile~bem_kinds.f90 sourcefile~bem_coulomb_fmm_types.f90 bem_coulomb_fmm_types.f90 sourcefile~bem_coulomb_fmm_core.f90->sourcefile~bem_coulomb_fmm_types.f90 sourcefile~bem_panel_self_terms.f90->sourcefile~bem_kinds.f90 sourcefile~bem_panel_self_terms.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_triangle_quadrature.f90->sourcefile~bem_constants.f90 sourcefile~bem_triangle_quadrature.f90->sourcefile~bem_kinds.f90 sourcefile~bem_triangle_quadrature.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_coulomb_fmm_types.f90->sourcefile~bem_kinds.f90 sourcefile~bem_coulomb_fmm_types.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_coulomb_fmm_types.f90->sourcefile~bem_periodic_zero_mode_plan.f90

Source Code

!> 粒子源計画に従うMPI粒子数配分、サンプリング、バッチ組み立て。
submodule(bem_app_config_particle_runtime) bem_app_config_particle_runtime_batch
  use bem_particles, only: allocate_particles
  implicit none

  ! Each species owns only its requested capacity; ray hits can fill fewer entries.
  type :: sampled_species_type
    real(dp), allocatable :: x(:, :), v(:, :), w(:)
    integer(i32), allocatable :: source_element(:)
  end type sampled_species_type
contains

  !> 指定バッチ番号に対応する粒子バッチを生成する。
  !! @param[in] cfg 粒子種とシミュレーション条件を含むアプリ設定。
  !! @param[in] batch_idx 生成対象のバッチ番号(1始まり)。
  !! @param[out] pcls 生成したバッチ粒子群。
  !! @param[inout] state reservoir_face 注入の残差状態(必要時のみ)。
  !! @param[in] mesh 現在バッチ開始時点の電荷分布メッシュ(電位補正時に必要)。
  !! @param[out] photo_emission_dq photo_raycast 放出起因の要素電荷差分 `photo_emission_dq(nelem)`(省略可)。
  !! @param[out] collision_failure_status 不完全な photo collision query の status(省略時は停止)。
  !! @param[out] collision_failure_species 不完全な照会を返した最小 species index。
  !! @param[out] collision_failure_ray 不完全な照会を返した最小 ray index。
  !! @param[out] collision_failure_bounce 不完全な照会を返した bounce index。
  !! @param[inout] snapshot refresh 済み静電 snapshot(注入電位補正の使用時に必要)。
  !! @param[in] source_plan run中に再利用する粒子source導出値(省略時は呼出し内で構築)。
  !! @param[out] photo_emission_dq_by_species species別のphoto放出反作用電荷 `photo_emission_dq_by_species(nelem, nspecies)`(省略可)。
  module procedure init_particle_batch_from_config

  integer(i32) :: s, i, face, local_rank, n_ranks, global_count
  integer(i32) :: source_begin, source_end, face_begin, face_end
  integer(i32) :: boundary_status
  integer(i32) :: photo_collision_status, photo_collision_ray, photo_collision_bounce
  integer(i32), allocatable :: counts_max(:), counts_actual(:), source_counts(:), global_counts(:), &
                               boundary_counts(:, :), boundary_global_counts(:, :)
  real(dp), allocatable :: vmin_normal(:), barrier_normal(:), boundary_vmin(:, :), boundary_barrier(:, :), &
                           batch_density_m3(:), batch_weight(:)
  logical :: use_collective_reservoir_count
  type(sampled_species_type), allocatable :: sampled(:)
  type(particle_source_plan_type), target :: generated_source_plan
  type(particle_source_plan_type), pointer :: active_source_plan
  type(external_boundary_contract_type) :: active_boundary_contract
  type(particle_species_spec) :: face_spec
  real(dp) :: correction_vmin_normal
  real(dp) :: kinetic_vmin_normal, kinetic_barrier_normal
  character(len=256) :: boundary_message

  if (present(collision_failure_status)) collision_failure_status = collision_query_ok
  if (present(collision_failure_species)) collision_failure_species = huge(0_i32)
  if (present(collision_failure_ray)) collision_failure_ray = huge(0_i32)
  if (present(collision_failure_bounce)) collision_failure_bounce = 0_i32
  if (cfg%sim%batch_count <= 0_i32) error stop 'sim.batch_count must be > 0.'
  if (batch_idx < 1_i32 .or. batch_idx > cfg%sim%batch_count) then
    error stop 'Requested batch index is out of range.'
  end if
  call resolve_external_boundary_contract( &
    cfg%sim%reservoir_potential_model, cfg%sim%open_boundary_model, &
    active_boundary_contract, boundary_status, boundary_message &
    )
  if (boundary_status /= external_boundary_ok) error stop trim(boundary_message)
  call resolve_parallel_rank_size(local_rank, n_ranks, mpi_rank, mpi_size, mpi, 'init_particle_batch_from_config')
  if (present(source_plan)) then
    active_source_plan => source_plan
  else
    call build_particle_source_plan( &
      cfg, generated_source_plan, mpi_rank=mpi_rank, mpi_size=mpi_size, mpi=mpi &
      )
    active_source_plan => generated_source_plan
  end if
  if (.not. active_source_plan%ready) error stop 'particle source plan is not initialized.'
  if (active_source_plan%nspecies /= cfg%n_particle_species) then
    error stop 'particle source plan species count does not match app config.'
  end if
  if (active_source_plan%mpi_rank /= local_rank .or. active_source_plan%mpi_size /= n_ranks .or. &
      active_source_plan%mpi_argument_present .neqv. present(mpi)) then
    error stop 'particle source plan MPI context does not match batch initialization.'
  end if
  use_collective_reservoir_count = active_source_plan%use_collective_reservoir_count
  if (present(state)) then
    if (.not. allocated(state%macro_residual)) error stop 'injection_state is not initialized.'
    if (size(state%macro_residual) < cfg%n_particle_species) error stop 'injection_state size mismatch.'
    if (any([(has_boundary_inflow(cfg%particle_species(s)), s=1, cfg%n_particle_species)])) then
      if (.not. allocated(state%boundary_macro_residual)) then
        error stop 'boundary_inflow requires boundary_macro_residual in injection_state.'
      end if
      if (size(state%boundary_macro_residual, 1) < 6 .or. &
          size(state%boundary_macro_residual, 2) < cfg%n_particle_species) then
        error stop 'injection_state boundary residual size mismatch.'
      end if
    end if
  end if
  if (present(photo_emission_dq)) then
    if (.not. present(mesh)) error stop 'photo_emission_dq requires mesh in init_particle_batch_from_config.'
    if (size(photo_emission_dq) /= mesh%nelem) error stop 'photo_emission_dq size mismatch.'
    photo_emission_dq = 0.0d0
  end if
  if (present(photo_emission_dq_by_species)) then
    if (.not. present(mesh)) then
      error stop 'photo_emission_dq_by_species requires mesh in init_particle_batch_from_config.'
    end if
    if (size(photo_emission_dq_by_species, 1) /= mesh%nelem .or. &
        size(photo_emission_dq_by_species, 2) /= cfg%n_particle_species) then
      error stop 'photo_emission_dq_by_species size mismatch.'
    end if
    photo_emission_dq_by_species = 0.0_dp
  end if

  allocate ( &
    counts_max(cfg%n_particle_species), counts_actual(cfg%n_particle_species), source_counts(cfg%n_particle_species), &
    global_counts(cfg%n_particle_species) &
    )
  allocate (boundary_counts(6, cfg%n_particle_species), boundary_global_counts(6, cfg%n_particle_species))
  allocate (vmin_normal(cfg%n_particle_species), barrier_normal(cfg%n_particle_species))
  allocate (boundary_vmin(6, cfg%n_particle_species), boundary_barrier(6, cfg%n_particle_species))
  allocate (batch_density_m3(cfg%n_particle_species), batch_weight(cfg%n_particle_species))
  counts_max = 0_i32
  counts_actual = 0_i32
  source_counts = 0_i32
  global_counts = 0_i32
  boundary_counts = 0_i32
  boundary_global_counts = 0_i32
  vmin_normal = 0.0d0
  barrier_normal = 0.0d0
  boundary_vmin = 0.0_dp
  boundary_barrier = 0.0_dp
  batch_density_m3 = active_source_plan%effective_density_m3
  batch_weight = active_source_plan%effective_weight
  associate ( &
    effective_particle_flux_m2_s => active_source_plan%effective_particle_flux_m2_s, &
    effective_temperature_k => active_source_plan%effective_temperature_k, &
    effective_drift_velocity => active_source_plan%effective_drift_velocity, &
    photo_emit_current_density => active_source_plan%photo_emit_current_density, &
    photo_normal_drift_speed => active_source_plan%photo_normal_drift_speed &
    )
  do s = 1, cfg%n_particle_species
    if (.not. cfg%particle_species(s)%enabled) cycle
    select case (trim(lower_ascii(cfg%particle_species(s)%source_mode)))
    case ('volume_seed')
      global_count = cfg%particle_species(s)%npcls_per_step
      source_counts(s) = mpi_split_count(global_count, local_rank, n_ranks)
    case ('reservoir_face', 'plane_source')
      if (.not. present(state)) then
        error stop 'flux-driven source requires injection_state in init_particle_batch_from_config.'
      end if
      if (trim(lower_ascii(cfg%particle_species(s)%source_mode)) == 'reservoir_face') then
        call reservoir_face_velocity_correction( &
          cfg, cfg%particle_species(s), correction_vmin_normal, barrier_normal(s), mesh, snapshot, &
          warn_face_variation=local_rank == 0_i32 .and. &
          (batch_idx == 1_i32 .or. batch_idx == cfg%sim%batch_count), &
          boundary_contract=active_boundary_contract &
          )
        vmin_normal(s) = max(vmin_normal(s), correction_vmin_normal)
        if (active_source_plan%kinetic_inflow_active(s) .and. &
            injection_face_index(cfg%particle_species(s)%inject_face) == active_source_plan%kinetic_inflow_face(s)) then
          call external_kinetic_face_velocity_correction( &
            cfg, cfg%particle_species(s), active_source_plan%kinetic_reservoir_potential_v(s), &
            active_source_plan%kinetic_access_potential_v(s), kinetic_vmin_normal, kinetic_barrier_normal, &
            mesh, snapshot, warn_face_variation=local_rank == 0_i32 .and. &
            (batch_idx == 1_i32 .or. batch_idx == cfg%sim%batch_count) &
            )
          vmin_normal(s) = max(vmin_normal(s), kinetic_vmin_normal)
          barrier_normal(s) = kinetic_barrier_normal
        end if
      end if
      if (.not. use_collective_reservoir_count .or. local_rank == 0_i32) then
        call compute_macro_particles_for_species( &
          cfg%sim, cfg%particle_species(s), state%macro_residual(s), global_counts(s), vmin_normal=vmin_normal(s), &
          number_density_override=batch_density_m3(s), particle_flux_override=effective_particle_flux_m2_s(s), &
          use_particle_flux_override=active_source_plan%number_flux_override_active(s), &
          temperature_k_override=effective_temperature_k(s), drift_velocity_override=effective_drift_velocity(:, s), &
          w_particle_override=batch_weight(s) &
          )
      end if
      if (.not. use_collective_reservoir_count) then
        source_counts(s) = mpi_split_count(global_counts(s), local_rank, n_ranks)
      end if
    case ('photo_raycast')
      global_count = cfg%particle_species(s)%rays_per_batch
      source_counts(s) = mpi_split_count(global_count, local_rank, n_ranks)
    case default
      error stop 'Unknown particles.species.source_mode.'
    end select
    if (has_boundary_inflow(cfg%particle_species(s))) then
      if (.not. present(state)) then
        error stop 'boundary_inflow requires injection_state in init_particle_batch_from_config.'
      end if
      do face = 1, 6
        if (.not. boundary_inflow_face_enabled(cfg%particle_species(s), face)) cycle
        call make_boundary_inflow_spec(cfg%sim, cfg%particle_species(s), face, face_spec)
        call reservoir_face_velocity_correction( &
          cfg, face_spec, correction_vmin_normal, boundary_barrier(face, s), mesh, snapshot, &
          warn_face_variation=local_rank == 0_i32 .and. &
          (batch_idx == 1_i32 .or. batch_idx == cfg%sim%batch_count), &
          boundary_contract=active_boundary_contract &
          )
        boundary_vmin(face, s) = correction_vmin_normal
        if (active_source_plan%kinetic_inflow_active(s) .and. face == active_source_plan%kinetic_inflow_face(s)) then
          call external_kinetic_face_velocity_correction( &
            cfg, face_spec, active_source_plan%kinetic_reservoir_potential_v(s), &
            active_source_plan%kinetic_access_potential_v(s), kinetic_vmin_normal, kinetic_barrier_normal, &
            mesh, snapshot, warn_face_variation=local_rank == 0_i32 .and. &
            (batch_idx == 1_i32 .or. batch_idx == cfg%sim%batch_count) &
            )
          boundary_vmin(face, s) = max(boundary_vmin(face, s), kinetic_vmin_normal)
          boundary_barrier(face, s) = kinetic_barrier_normal
        end if
        if (.not. use_collective_reservoir_count .or. local_rank == 0_i32) then
          call compute_macro_particles_for_species( &
            cfg%sim, face_spec, state%boundary_macro_residual(face, s), boundary_global_counts(face, s), &
            vmin_normal=boundary_vmin(face, s), number_density_override=batch_density_m3(s), &
            particle_flux_override=effective_particle_flux_m2_s(s), &
            use_particle_flux_override=active_source_plan%number_flux_override_active(s), &
            temperature_k_override=effective_temperature_k(s), &
            drift_velocity_override=effective_drift_velocity(:, s), w_particle_override=batch_weight(s) &
            )
        end if
        if (.not. use_collective_reservoir_count) then
          boundary_counts(face, s) = mpi_split_count(boundary_global_counts(face, s), local_rank, n_ranks)
        end if
      end do
    end if
  end do
  if (use_collective_reservoir_count) then
    call mpi_bcast_i32_array(mpi, global_counts, 0_i32)
    call mpi_bcast_real_dp_array(mpi, state%macro_residual, 0_i32)
    do s = 1, cfg%n_particle_species
      if (.not. cfg%particle_species(s)%enabled) cycle
      if (trim(lower_ascii(cfg%particle_species(s)%source_mode)) == 'reservoir_face' .or. &
          trim(lower_ascii(cfg%particle_species(s)%source_mode)) == 'plane_source') then
        source_counts(s) = mpi_split_count(global_counts(s), local_rank, n_ranks)
      end if
      if (.not. has_boundary_inflow(cfg%particle_species(s))) cycle
      call mpi_bcast_i32_array(mpi, boundary_global_counts(:, s), 0_i32)
      call mpi_bcast_real_dp_array(mpi, state%boundary_macro_residual(:, s), 0_i32)
      do face = 1, 6
        boundary_counts(face, s) = mpi_split_count(boundary_global_counts(face, s), local_rank, n_ranks)
      end do
    end do
  end if
  counts_max = source_counts + sum(boundary_counts, dim=1)
  allocate (sampled(cfg%n_particle_species))
  do s = 1, cfg%n_particle_species
    if (counts_max(s) <= 0_i32) cycle
    allocate (sampled(s)%x(3, counts_max(s)), sampled(s)%v(3, counts_max(s)), &
              sampled(s)%w(counts_max(s)), sampled(s)%source_element(counts_max(s)))
    sampled(s)%source_element = -1_i32
    associate (x_species => sampled(s)%x, v_species => sampled(s)%v, &
               w_species => sampled(s)%w, emit_elem_species => sampled(s)%source_element)
      source_begin = 1_i32
      source_end = source_counts(s)
      if (source_end >= source_begin) then
        select case (trim(lower_ascii(cfg%particle_species(s)%source_mode)))
        case ('volume_seed', 'reservoir_face', 'plane_source')
          call sample_species_state( &
            cfg%sim, cfg%particle_species(s), source_counts(s), &
            x_species(:, source_begin:source_end), v_species(:, source_begin:source_end), &
            barrier_normal_energy=barrier_normal(s), vmin_normal=vmin_normal(s), &
            temperature_k_override=effective_temperature_k(s), drift_velocity_override=effective_drift_velocity(:, s) &
            )
          counts_actual(s) = source_counts(s)
          w_species(source_begin:source_end) = batch_weight(s)
        case ('photo_raycast')
          if (.not. present(mesh)) then
            error stop 'photo_raycast requires mesh in init_particle_batch_from_config.'
          end if
          if (photo_emit_current_density(s) > 0.0_dp) then
            call sample_photo_species_state( &
              cfg%sim, cfg%particle_species(s), mesh, source_counts(s), x_species(:, source_begin:source_end), &
              v_species(:, source_begin:source_end), w_species(source_begin:source_end), counts_actual(s), &
              emit_elem_idx=emit_elem_species(source_begin:source_end), &
              global_rays_per_batch=cfg%particle_species(s)%rays_per_batch, &
              emit_current_density_override=photo_emit_current_density(s), &
              normal_drift_speed_override=photo_normal_drift_speed(s), &
              collision_failure_status=photo_collision_status, collision_failure_ray=photo_collision_ray, &
              collision_failure_bounce=photo_collision_bounce &
              )
            if (photo_collision_status /= collision_query_ok) then
              call finalize_particle_batch_collision_query( &
                photo_collision_status, batch_idx, s, photo_collision_ray, photo_collision_bounce, &
                collision_failure_status, collision_failure_species, collision_failure_ray, collision_failure_bounce &
                )
              return
            end if
            if ((present(photo_emission_dq) .or. present(photo_emission_dq_by_species)) .and. &
                cfg%particle_species(s)%deposit_opposite_charge_on_emit) then
              do i = 1, counts_actual(s)
                if (emit_elem_species(i) < 1_i32 .or. emit_elem_species(i) > mesh%nelem) then
                  error stop 'photo_raycast emitted invalid elem_idx.'
                end if
                if (present(photo_emission_dq)) then
                  photo_emission_dq(emit_elem_species(i)) = photo_emission_dq(emit_elem_species(i)) - &
                                                            cfg%particle_species(s)%q_particle*w_species(i)
                end if
                if (present(photo_emission_dq_by_species)) then
                  photo_emission_dq_by_species(emit_elem_species(i), s) = &
                    photo_emission_dq_by_species(emit_elem_species(i), s) - &
                    cfg%particle_species(s)%q_particle*w_species(i)
                end if
              end do
            end if
          end if
        case default
          error stop 'Unknown particles.species.source_mode.'
        end select
      end if
      do face = 1, 6
        if (boundary_counts(face, s) <= 0_i32) cycle
        face_begin = counts_actual(s) + 1_i32
        face_end = face_begin + boundary_counts(face, s) - 1_i32
        call make_boundary_inflow_spec(cfg%sim, cfg%particle_species(s), face, face_spec)
        call sample_species_state( &
          cfg%sim, face_spec, boundary_counts(face, s), x_species(:, face_begin:face_end), &
          v_species(:, face_begin:face_end), barrier_normal_energy=boundary_barrier(face, s), &
          vmin_normal=boundary_vmin(face, s), temperature_k_override=effective_temperature_k(s), &
          drift_velocity_override=effective_drift_velocity(:, s) &
          )
        w_species(face_begin:face_end) = batch_weight(s)
        counts_actual(s) = face_end
      end do
    end associate
  end do

  call assemble_particle_batch(cfg%particle_species, counts_actual, sampled, pcls)
  end associate
  end procedure init_particle_batch_from_config

  !> 種別のサンプルを従来のround-robin順で完成したSoAへ直接詰める。
  subroutine assemble_particle_batch(species, counts, sampled, pcls)
    type(particle_species_spec), intent(in) :: species(:)
    integer(i32), intent(in) :: counts(:)
    type(sampled_species_type), intent(in) :: sampled(:)
    type(particles_soa), intent(out) :: pcls
    integer(i32) :: i, s, out_idx

    call allocate_particles(pcls, sum(counts))
    out_idx = 0_i32
    do i = 1, maxval(counts)
      do s = 1, size(counts)
        if (i > counts(s)) cycle
        out_idx = out_idx + 1_i32
        pcls%x(:, out_idx) = sampled(s)%x(:, i)
        pcls%v(:, out_idx) = sampled(s)%v(:, i)
        pcls%q(out_idx) = species(s)%q_particle
        pcls%m(out_idx) = species(s)%m_particle
        pcls%w(out_idx) = sampled(s)%w(i)
        pcls%species_id(out_idx) = s
        pcls%source_element(out_idx) = sampled(s)%source_element(i)
      end do
    end do
  end subroutine assemble_particle_batch

  !> batch injection の不完全な photo collision query を返し、status 未要求なら serial に停止する。
  subroutine finalize_particle_batch_collision_query( &
    query_status, batch_idx, species_idx, query_ray, query_bounce, &
    collision_failure_status, collision_failure_species, collision_failure_ray, collision_failure_bounce &
    )
    integer(i32), intent(in) :: query_status, batch_idx, species_idx, query_ray, query_bounce
    integer(i32), intent(out), optional :: collision_failure_status, collision_failure_species
    integer(i32), intent(out), optional :: collision_failure_ray, collision_failure_bounce
    character(len=16) :: status_name
    character(len=256) :: failure_message

    if (present(collision_failure_status)) collision_failure_status = query_status
    if (present(collision_failure_species)) collision_failure_species = species_idx
    if (present(collision_failure_ray)) collision_failure_ray = query_ray
    if (present(collision_failure_bounce)) collision_failure_bounce = query_bounce
    if (present(collision_failure_status)) return

    select case (query_status)
    case (collision_query_image_limit)
      status_name = 'image_limit'
    case (collision_query_index_range)
      status_name = 'index_range'
    case (collision_query_invalid_segment)
      status_name = 'invalid_segment'
    case (collision_query_grid_stalled)
      status_name = 'grid_stalled'
    case default
      status_name = 'unknown'
    end select
    write (failure_message, '(a,i0,a,i0,a,i0,a,i0,a,a,a,i0)') &
      'photo_raycast collision query incomplete during batch preparation: batch=', batch_idx, &
      ' species=', species_idx, ' ray=', query_ray, ' bounce=', query_bounce, &
      ' status=', trim(status_name), ' code=', query_status
    write (error_unit, '(a)') trim(failure_message)
    flush (error_unit)
    error stop 1
  end subroutine finalize_particle_batch_collision_query

end submodule bem_app_config_particle_runtime_batch