init_particle_batch_from_config Subroutine

public subroutine init_particle_batch_from_config(cfg, batch_idx, pcls, state, mesh, photo_emission_dq, mpi_rank, mpi_size, mpi)

指定バッチ番号に対応する粒子バッチを生成する。

Arguments

Type IntentOptional Attributes Name
type(app_config), intent(in) :: cfg

粒子種とシミュレーション条件を含むアプリ設定。

integer(kind=i32), intent(in) :: batch_idx

生成対象のバッチ番号(1始まり)。

type(particles_soa), intent(out) :: pcls

生成したバッチ粒子群。

type(injection_state), intent(inout), optional :: state

reservoir_face 注入の残差状態(必要時のみ)。

type(mesh_type), intent(in), optional :: mesh

現在バッチ開始時点の電荷分布メッシュ(電位補正時に必要)。

real(kind=dp), intent(out), optional :: photo_emission_dq(:)
integer(kind=i32), intent(in), optional :: mpi_rank
integer(kind=i32), intent(in), optional :: mpi_size
type(mpi_context), intent(in), optional :: mpi

Calls

proc~~init_particle_batch_from_config~~CallsGraph proc~init_particle_batch_from_config init_particle_batch_from_config proc~apply_normal_speed_override apply_normal_speed_override proc~init_particle_batch_from_config->proc~apply_normal_speed_override proc~compute_macro_particles_for_species compute_macro_particles_for_species proc~init_particle_batch_from_config->proc~compute_macro_particles_for_species proc~init_particles init_particles proc~init_particle_batch_from_config->proc~init_particles proc~lower_ascii lower_ascii proc~init_particle_batch_from_config->proc~lower_ascii proc~mpi_split_count mpi_split_count proc~init_particle_batch_from_config->proc~mpi_split_count proc~reservoir_face_velocity_correction reservoir_face_velocity_correction proc~init_particle_batch_from_config->proc~reservoir_face_velocity_correction proc~resolve_parallel_rank_size resolve_parallel_rank_size proc~init_particle_batch_from_config->proc~resolve_parallel_rank_size proc~resolve_reservoir_target_weight resolve_reservoir_target_weight proc~init_particle_batch_from_config->proc~resolve_reservoir_target_weight proc~resolve_sheath_injection_context resolve_sheath_injection_context proc~init_particle_batch_from_config->proc~resolve_sheath_injection_context proc~sample_photo_species_state sample_photo_species_state proc~init_particle_batch_from_config->proc~sample_photo_species_state proc~sample_species_state sample_species_state proc~init_particle_batch_from_config->proc~sample_species_state proc~species_number_density_m3~2 species_number_density_m3 proc~init_particle_batch_from_config->proc~species_number_density_m3~2 proc~species_temperature_k~2 species_temperature_k proc~init_particle_batch_from_config->proc~species_temperature_k~2 proc~compute_macro_particles_for_species->proc~species_number_density_m3~2 proc~compute_macro_particles_for_species->proc~species_temperature_k~2 proc~compute_macro_particles_for_batch compute_macro_particles_for_batch proc~compute_macro_particles_for_species->proc~compute_macro_particles_for_batch proc~reservoir_face_velocity_correction->proc~lower_ascii proc~compute_face_average_potential compute_face_average_potential proc~reservoir_face_velocity_correction->proc~compute_face_average_potential proc~mpi_get_rank_size mpi_get_rank_size proc~resolve_parallel_rank_size->proc~mpi_get_rank_size proc~compute_face_area_from_bounds compute_face_area_from_bounds proc~resolve_reservoir_target_weight->proc~compute_face_area_from_bounds proc~compute_inflow_flux_from_drifting_maxwellian compute_inflow_flux_from_drifting_maxwellian proc~resolve_reservoir_target_weight->proc~compute_inflow_flux_from_drifting_maxwellian proc~resolve_inward_normal resolve_inward_normal proc~resolve_reservoir_target_weight->proc~resolve_inward_normal proc~resolve_sheath_injection_context->proc~lower_ascii proc~build_zhao_params build_zhao_params proc~resolve_sheath_injection_context->proc~build_zhao_params proc~detect_sheath_species detect_sheath_species proc~resolve_sheath_injection_context->proc~detect_sheath_species proc~resolve_sheath_reference_plane resolve_sheath_reference_plane proc~resolve_sheath_injection_context->proc~resolve_sheath_reference_plane proc~resolve_species_drift_speed resolve_species_drift_speed proc~resolve_sheath_injection_context->proc~resolve_species_drift_speed proc~sample_zhao_reservoir_state sample_zhao_reservoir_state proc~resolve_sheath_injection_context->proc~sample_zhao_reservoir_state proc~solve_no_photo_floating_potential solve_no_photo_floating_potential proc~resolve_sheath_injection_context->proc~solve_no_photo_floating_potential proc~solve_zhao_unknowns solve_zhao_unknowns proc~resolve_sheath_injection_context->proc~solve_zhao_unknowns proc~to_sheath_model_species to_sheath_model_species proc~resolve_sheath_injection_context->proc~to_sheath_model_species proc~zhao_electron_vmin_normal zhao_electron_vmin_normal proc~resolve_sheath_injection_context->proc~zhao_electron_vmin_normal proc~zhao_photo_emit_current_density zhao_photo_emit_current_density proc~resolve_sheath_injection_context->proc~zhao_photo_emit_current_density proc~zhao_photo_vmin_normal zhao_photo_vmin_normal proc~resolve_sheath_injection_context->proc~zhao_photo_vmin_normal proc~sample_photo_species_state->proc~species_temperature_k~2 proc~sample_photo_raycast_particles sample_photo_raycast_particles proc~sample_photo_species_state->proc~sample_photo_raycast_particles proc~sample_species_state->proc~lower_ascii proc~sample_species_state->proc~species_temperature_k~2 proc~sample_reservoir_face_particles sample_reservoir_face_particles proc~sample_species_state->proc~sample_reservoir_face_particles proc~sample_shifted_maxwell_velocities sample_shifted_maxwell_velocities proc~sample_species_state->proc~sample_shifted_maxwell_velocities proc~sample_uniform_positions sample_uniform_positions proc~sample_species_state->proc~sample_uniform_positions proc~electric_potential_at electric_potential_at proc~compute_face_average_potential->proc~electric_potential_at proc~resolve_face_sampling_geometry resolve_face_sampling_geometry proc~compute_face_average_potential->proc~resolve_face_sampling_geometry proc~compute_macro_particles_for_batch->proc~compute_face_area_from_bounds proc~compute_macro_particles_for_batch->proc~compute_inflow_flux_from_drifting_maxwellian proc~detect_sheath_species->proc~lower_ascii proc~resolve_inward_normal->proc~lower_ascii proc~resolve_sheath_reference_plane->proc~resolve_inward_normal proc~resolve_inject_face resolve_inject_face proc~resolve_sheath_reference_plane->proc~resolve_inject_face proc~resolve_species_drift_speed->proc~lower_ascii proc~sample_photo_raycast_particles->proc~lower_ascii proc~sample_photo_raycast_particles->proc~compute_face_area_from_bounds proc~apply_box_boundary apply_box_boundary proc~sample_photo_raycast_particles->proc~apply_box_boundary proc~find_first_hit find_first_hit proc~sample_photo_raycast_particles->proc~find_first_hit proc~sample_reservoir_face_particles->proc~sample_shifted_maxwell_velocities proc~sample_zhao_reservoir_state->proc~resolve_inject_face proc~sample_zhao_state_at_z sample_zhao_state_at_z proc~sample_zhao_reservoir_state->proc~sample_zhao_state_at_z proc~solve_no_photo_floating_potential->proc~compute_inflow_flux_from_drifting_maxwellian proc~no_photo_current_balance no_photo_current_balance proc~solve_no_photo_floating_potential->proc~no_photo_current_balance proc~temperature_ev_from_species temperature_ev_from_species proc~solve_no_photo_floating_potential->proc~temperature_ev_from_species proc~solve_zhao_branch_a solve_zhao_branch_a proc~solve_zhao_unknowns->proc~solve_zhao_branch_a proc~solve_zhao_branch_b solve_zhao_branch_b proc~solve_zhao_unknowns->proc~solve_zhao_branch_b proc~solve_zhao_branch_c solve_zhao_branch_c proc~solve_zhao_unknowns->proc~solve_zhao_branch_c proc~try_solve_zhao_branch_a try_solve_zhao_branch_a proc~solve_zhao_unknowns->proc~try_solve_zhao_branch_a proc~try_solve_zhao_branch_b try_solve_zhao_branch_b proc~solve_zhao_unknowns->proc~try_solve_zhao_branch_b proc~try_solve_zhao_branch_c try_solve_zhao_branch_c proc~solve_zhao_unknowns->proc~try_solve_zhao_branch_c proc~species_number_density_m3 species_number_density_m3 proc~to_sheath_model_species->proc~species_number_density_m3 proc~species_temperature_k species_temperature_k proc~to_sheath_model_species->proc~species_temperature_k proc~find_first_hit_base find_first_hit_base proc~find_first_hit->proc~find_first_hit_base proc~find_first_hit_periodic2 find_first_hit_periodic2 proc~find_first_hit->proc~find_first_hit_periodic2 proc~resolve_periodic2_collision_config resolve_periodic2_collision_config proc~find_first_hit->proc~resolve_periodic2_collision_config proc~no_photo_current_balance->proc~compute_inflow_flux_from_drifting_maxwellian proc~resolve_face_sampling_geometry->proc~lower_ascii proc~resolve_inject_face->proc~lower_ascii proc~evaluate_zhao_state_from_phi_hat evaluate_zhao_state_from_phi_hat proc~sample_zhao_state_at_z->proc~evaluate_zhao_state_from_phi_hat proc~sample_monotonic_phi_hat_at_z sample_monotonic_phi_hat_at_z proc~sample_zhao_state_at_z->proc~sample_monotonic_phi_hat_at_z proc~sample_type_a_phi_hat_at_z sample_type_a_phi_hat_at_z proc~sample_zhao_state_at_z->proc~sample_type_a_phi_hat_at_z proc~solve_zhao_branch_a->proc~try_solve_zhao_branch_a proc~solve_zhao_branch_b->proc~try_solve_zhao_branch_b proc~solve_zhao_branch_c->proc~try_solve_zhao_branch_c proc~solve_nonlinear_system solve_nonlinear_system proc~try_solve_zhao_branch_a->proc~solve_nonlinear_system proc~try_solve_zhao_branch_b->proc~solve_nonlinear_system proc~try_solve_zhao_branch_c->proc~solve_nonlinear_system proc~evaluate_zhao_density_hat evaluate_zhao_density_hat proc~evaluate_zhao_state_from_phi_hat->proc~evaluate_zhao_density_hat proc~find_first_hit_base_grid find_first_hit_base_grid proc~find_first_hit_base->proc~find_first_hit_base_grid proc~find_first_hit_base_linear find_first_hit_base_linear proc~find_first_hit_base->proc~find_first_hit_base_linear proc~initialize_hit initialize_hit proc~find_first_hit_base->proc~initialize_hit proc~resolve_box_filter_args resolve_box_filter_args proc~find_first_hit_base->proc~resolve_box_filter_args proc~find_first_hit_periodic2->proc~find_first_hit_base proc~find_first_hit_periodic2->proc~resolve_periodic2_collision_config proc~compute_periodic_shift_bounds compute_periodic_shift_bounds proc~find_first_hit_periodic2->proc~compute_periodic_shift_bounds proc~find_first_hit_periodic2->proc~initialize_hit proc~point_inside_box_periodic2 point_inside_box_periodic2 proc~find_first_hit_periodic2->proc~point_inside_box_periodic2 proc~prefer_periodic_candidate prefer_periodic_candidate proc~find_first_hit_periodic2->proc~prefer_periodic_candidate proc~find_first_hit_periodic2->proc~resolve_box_filter_args proc~wrap_periodic2_point wrap_periodic2_point proc~find_first_hit_periodic2->proc~wrap_periodic2_point proc~resolve_periodic2_collision_config->proc~lower_ascii proc~evaluate_zhao_rho_hat evaluate_zhao_rho_hat proc~sample_monotonic_phi_hat_at_z->proc~evaluate_zhao_rho_hat proc~interpolate_profile_value interpolate_profile_value proc~sample_monotonic_phi_hat_at_z->proc~interpolate_profile_value proc~build_type_a_branch_from_minimum build_type_a_branch_from_minimum proc~sample_type_a_phi_hat_at_z->proc~build_type_a_branch_from_minimum proc~sample_type_a_phi_hat_at_z->proc~interpolate_profile_value proc~try_newton_solve try_newton_solve proc~solve_nonlinear_system->proc~try_newton_solve proc~build_type_a_branch_from_minimum->proc~evaluate_zhao_rho_hat proc~evaluate_zhao_rho_hat->proc~evaluate_zhao_density_hat proc~bbox_inside_box bbox_inside_box proc~find_first_hit_base_grid->proc~bbox_inside_box proc~cell_id cell_id proc~find_first_hit_base_grid->proc~cell_id proc~coord_to_cell coord_to_cell proc~find_first_hit_base_grid->proc~coord_to_cell proc~point_inside_box point_inside_box proc~find_first_hit_base_grid->proc~point_inside_box proc~segment_aabb_intersection_t segment_aabb_intersection_t proc~find_first_hit_base_grid->proc~segment_aabb_intersection_t proc~segment_bbox_overlap_precomputed segment_bbox_overlap_precomputed proc~find_first_hit_base_grid->proc~segment_bbox_overlap_precomputed proc~segment_triangle_intersect segment_triangle_intersect proc~find_first_hit_base_grid->proc~segment_triangle_intersect proc~find_first_hit_base_linear->proc~bbox_inside_box proc~find_first_hit_base_linear->proc~point_inside_box proc~find_first_hit_base_linear->proc~segment_bbox_overlap_precomputed proc~find_first_hit_base_linear->proc~segment_triangle_intersect proc~numerical_jacobian numerical_jacobian proc~try_newton_solve->proc~numerical_jacobian proc~residual_norm residual_norm proc~try_newton_solve->proc~residual_norm proc~solve_small_linear_system solve_small_linear_system proc~try_newton_solve->proc~solve_small_linear_system proc~cross cross proc~segment_triangle_intersect->proc~cross

Source Code

  subroutine init_particle_batch_from_config(cfg, batch_idx, pcls, state, mesh, photo_emission_dq, mpi_rank, mpi_size, mpi)
    type(app_config), intent(in) :: cfg
    integer(i32), intent(in) :: batch_idx
    type(particles_soa), intent(out) :: pcls
    type(injection_state), intent(inout), optional :: state
    type(mesh_type), intent(in), optional :: mesh
    real(dp), intent(out), optional :: photo_emission_dq(:)
    integer(i32), intent(in), optional :: mpi_rank, mpi_size
    type(mpi_context), intent(in), optional :: mpi

    integer(i32) :: s, i, batch_n, max_rank, out_idx, local_rank, n_ranks, global_count
    integer(i32), allocatable :: counts_max(:), counts_actual(:), species_cursor(:), species_id(:), emit_elem_species(:, :)
    real(dp), allocatable :: vmin_normal(:), barrier_normal(:), effective_density_m3(:), w_effective(:), &
                             effective_temperature_k(:), effective_drift_velocity(:, :), &
                             photo_emit_current_density(:), photo_vmin_normal(:), photo_normal_drift_speed(:)
    logical, allocatable :: apply_barrier_energy_shift(:)
    real(dp), allocatable :: x_species(:, :, :), v_species(:, :, :), w_species(:, :)
    real(dp), allocatable :: x(:, :), v(:, :), q(:), m(:), w(:)
    type(sheath_injection_context) :: sheath_ctx

    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_parallel_rank_size(local_rank, n_ranks, mpi_rank, mpi_size, mpi, 'init_particle_batch_from_config')
    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.'
    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

    allocate (counts_max(cfg%n_particle_species), counts_actual(cfg%n_particle_species))
    allocate (vmin_normal(cfg%n_particle_species), barrier_normal(cfg%n_particle_species))
    allocate (effective_density_m3(cfg%n_particle_species), w_effective(cfg%n_particle_species))
    allocate (effective_temperature_k(cfg%n_particle_species), effective_drift_velocity(3, cfg%n_particle_species))
    allocate (photo_emit_current_density(cfg%n_particle_species), photo_vmin_normal(cfg%n_particle_species))
    allocate (photo_normal_drift_speed(cfg%n_particle_species), apply_barrier_energy_shift(cfg%n_particle_species))
    counts_max = 0_i32
    counts_actual = 0_i32
    vmin_normal = 0.0d0
    barrier_normal = 0.0d0
    effective_density_m3 = 0.0d0
    w_effective = 0.0d0
    effective_temperature_k = 0.0d0
    effective_drift_velocity = 0.0d0
    photo_emit_current_density = 0.0d0
    photo_vmin_normal = 0.0d0
    photo_normal_drift_speed = 0.0d0
    apply_barrier_energy_shift = .true.
    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')
        w_effective(s) = cfg%particle_species(s)%w_particle
        effective_temperature_k(s) = species_temperature_k(cfg%particle_species(s))
        effective_drift_velocity(:, s) = cfg%particle_species(s)%drift_velocity
      case ('reservoir_face')
        effective_density_m3(s) = species_number_density_m3(cfg%particle_species(s))
        w_effective(s) = cfg%particle_species(s)%w_particle
        effective_temperature_k(s) = species_temperature_k(cfg%particle_species(s))
        effective_drift_velocity(:, s) = cfg%particle_species(s)%drift_velocity
      case ('photo_raycast')
        photo_emit_current_density(s) = cfg%particle_species(s)%emit_current_density_a_m2
        photo_normal_drift_speed(s) = cfg%particle_species(s)%normal_drift_speed
      end select
    end do

    call resolve_sheath_injection_context(cfg, sheath_ctx)
    if (sheath_ctx%enabled) then
      s = int(sheath_ctx%electron_species)
      effective_density_m3(s) = sheath_ctx%electron_number_density_m3
      vmin_normal(s) = max(vmin_normal(s), sheath_ctx%electron_vmin_normal)
      apply_barrier_energy_shift(s) = .false.

      if (cfg%particle_species(s)%has_target_macro_particles_per_batch .and. &
          cfg%particle_species(s)%target_macro_particles_per_batch > 0_i32) then
        call resolve_reservoir_target_weight( &
          cfg%sim, cfg%particle_species(s), effective_density_m3(s), vmin_normal(s), &
          effective_temperature_k(s), effective_drift_velocity(:, s), &
          cfg%particle_species(s)%target_macro_particles_per_batch, w_effective(s) &
          )
      end if

      if (sheath_ctx%has_local_reservoir_profile) then
        s = int(sheath_ctx%ion_species)
        effective_density_m3(s) = sheath_ctx%ion_number_density_m3
        effective_temperature_k(s) = 0.0d0
        call apply_normal_speed_override( &
          cfg%particle_species(s)%drift_velocity, sheath_ctx%reference_inward_normal, &
          sheath_ctx%ion_normal_speed_mps, effective_drift_velocity(:, s) &
          )

        if (cfg%particle_species(s)%has_target_macro_particles_per_batch .and. &
            cfg%particle_species(s)%target_macro_particles_per_batch > 0_i32) then
          call resolve_reservoir_target_weight( &
            cfg%sim, cfg%particle_species(s), effective_density_m3(s), vmin_normal(s), &
            effective_temperature_k(s), effective_drift_velocity(:, s), &
            cfg%particle_species(s)%target_macro_particles_per_batch, w_effective(s) &
            )
        end if
      end if

      if (sheath_ctx%has_photo_species) then
        s = int(sheath_ctx%photo_species)
        photo_emit_current_density(s) = sheath_ctx%photo_emit_current_density_a_m2
        photo_vmin_normal(s) = sheath_ctx%photo_vmin_normal
        photo_normal_drift_speed(s) = 0.0d0
      end if

      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') cycle
        if (.not. cfg%particle_species(s)%has_target_macro_particles_per_batch) cycle
        if (cfg%particle_species(s)%target_macro_particles_per_batch == -1_i32) then
          w_effective(s) = w_effective(1)
        end if
      end do
    end if
    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
        counts_max(s) = mpi_split_count(global_count, local_rank, n_ranks)
      case ('reservoir_face')
        if (.not. present(state)) then
          error stop 'reservoir_face requires injection_state in init_particle_batch_from_config.'
        end if
        call reservoir_face_velocity_correction( &
          cfg%sim, cfg%particle_species(s), vmin_normal(s), barrier_normal(s), mesh &
          )
        call compute_macro_particles_for_species( &
          cfg%sim, cfg%particle_species(s), state%macro_residual(s), counts_max(s), vmin_normal=vmin_normal(s), &
          batch_duration_scale=1.0d0/real(n_ranks, dp), number_density_override=effective_density_m3(s), &
          temperature_k_override=effective_temperature_k(s), drift_velocity_override=effective_drift_velocity(:, s), &
          w_particle_override=w_effective(s) &
          )
      case ('photo_raycast')
        global_count = cfg%particle_species(s)%rays_per_batch
        counts_max(s) = mpi_split_count(global_count, local_rank, n_ranks)
      case default
        error stop 'Unknown particles.species.source_mode.'
      end select
    end do
    max_rank = max(1_i32, maxval(counts_max))
    allocate (x_species(3, max_rank, cfg%n_particle_species))
    allocate (v_species(3, max_rank, cfg%n_particle_species))
    allocate (w_species(max_rank, cfg%n_particle_species))
    allocate (emit_elem_species(max_rank, cfg%n_particle_species))
    x_species = 0.0d0
    v_species = 0.0d0
    w_species = 0.0d0
    emit_elem_species = -1_i32
    do s = 1, cfg%n_particle_species
      if (counts_max(s) <= 0_i32) cycle
      select case (trim(lower_ascii(cfg%particle_species(s)%source_mode)))
      case ('volume_seed', 'reservoir_face')
        call sample_species_state( &
          cfg%sim, cfg%particle_species(s), counts_max(s), &
          x_species(:, 1:counts_max(s), s), v_species(:, 1:counts_max(s), s), &
          barrier_normal_energy=barrier_normal(s), vmin_normal=vmin_normal(s), &
          apply_barrier_energy_shift=apply_barrier_energy_shift(s), &
          temperature_k_override=effective_temperature_k(s), drift_velocity_override=effective_drift_velocity(:, s) &
          )
        counts_actual(s) = counts_max(s)
        w_species(1:counts_actual(s), s) = w_effective(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.0d0) then
          counts_actual(s) = 0_i32
          cycle
        end if
        call sample_photo_species_state( &
          cfg%sim, cfg%particle_species(s), mesh, counts_max(s), x_species(:, 1:counts_max(s), s), &
          v_species(:, 1:counts_max(s), s), w_species(1:counts_max(s), s), counts_actual(s), &
          emit_elem_idx=emit_elem_species(1:counts_max(s), s), &
          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), vmin_normal=photo_vmin_normal(s) &
          )
        if (present(photo_emission_dq) .and. cfg%particle_species(s)%deposit_opposite_charge_on_emit) then
          do i = 1, counts_actual(s)
            if (emit_elem_species(i, s) < 1_i32 .or. emit_elem_species(i, s) > size(photo_emission_dq)) then
              error stop 'photo_raycast emitted invalid elem_idx.'
            end if
            photo_emission_dq(emit_elem_species(i, s)) = photo_emission_dq(emit_elem_species(i, s)) - &
                                                         cfg%particle_species(s)%q_particle*w_species(i, s)
          end do
        end if
      case default
        error stop 'Unknown particles.species.source_mode.'
      end select
    end do

    batch_n = sum(counts_actual)
    allocate (species_id(batch_n))
    out_idx = 0_i32
    do i = 1, max_rank
      do s = 1, cfg%n_particle_species
        if (i > counts_actual(s)) cycle
        out_idx = out_idx + 1_i32
        species_id(out_idx) = s
      end do
    end do

    allocate (x(3, batch_n), v(3, batch_n), q(batch_n), m(batch_n), w(batch_n))
    allocate (species_cursor(cfg%n_particle_species))
    species_cursor = 0_i32
    do i = 1, batch_n
      s = species_id(i)
      species_cursor(s) = species_cursor(s) + 1_i32
      x(:, i) = x_species(:, species_cursor(s), s)
      v(:, i) = v_species(:, species_cursor(s), s)
      q(i) = cfg%particle_species(s)%q_particle
      m(i) = cfg%particle_species(s)%m_particle
      w(i) = w_species(species_cursor(s), s)
    end do

    call init_particles(pcls, x, v, q, m, w)
  end subroutine init_particle_batch_from_config