advance_cantera_chemistry Subroutine

public subroutine advance_cantera_chemistry(mesh, flow, params, species_Y, energy, step, chemistry_dt, max_dT_chem, max_dY_chem, max_rel_drho_chem, min_source_alpha, raw_max_dT_chem, raw_max_dY_chem, raw_max_rel_drho_chem)

Advance cell-local Cantera chemistry on owned cells.

This is an operator-split chemistry hook. Each owned cell is integrated as an adiabatic constant-pressure reactor for the accumulated chemistry interval using Cantera's ReactorNet path. The routine updates transported species mass fractions and the energy dependent state (T, sensible h, cp, lambda, rho_thermo) for owned cells, then exchanges the updated replicated/ghost values using the flow communicator. Radiation MPI remains untouched.

Arguments

Type IntentOptional Attributes Name
type(mesh_t), intent(in) :: mesh
type(flow_mpi_t), intent(inout) :: flow
type(case_params_t), intent(in) :: params
real(kind=rk), intent(inout) :: species_Y(:,:)
type(energy_fields_t), intent(inout) :: energy
integer, intent(in) :: step
real(kind=rk), intent(in), optional :: chemistry_dt
real(kind=rk), intent(out), optional :: max_dT_chem
real(kind=rk), intent(out), optional :: max_dY_chem
real(kind=rk), intent(out), optional :: max_rel_drho_chem
real(kind=rk), intent(out), optional :: min_source_alpha
real(kind=rk), intent(out), optional :: raw_max_dT_chem
real(kind=rk), intent(out), optional :: raw_max_dY_chem
real(kind=rk), intent(out), optional :: raw_max_rel_drho_chem

Source Code

   subroutine advance_cantera_chemistry(mesh, flow, params, species_Y, energy, step, chemistry_dt, &
                                        max_dT_chem, max_dY_chem, max_rel_drho_chem, min_source_alpha, &
                                        raw_max_dT_chem, raw_max_dY_chem, raw_max_rel_drho_chem)
      type(mesh_t), intent(in) :: mesh
      type(flow_mpi_t), intent(inout) :: flow
      type(case_params_t), intent(in) :: params
      real(rk), intent(inout) :: species_Y(:,:)
      type(energy_fields_t), intent(inout) :: energy
      integer, intent(in) :: step
      real(rk), intent(in), optional :: chemistry_dt
      real(rk), intent(out), optional :: max_dT_chem, max_dY_chem, max_rel_drho_chem, min_source_alpha
      real(rk), intent(out), optional :: raw_max_dT_chem, raw_max_dY_chem, raw_max_rel_drho_chem

      integer :: nloc, nactive, global_active, i, c, k, n_len, ierr, j, n_sub
      integer :: nactive_screen_species, active_screen_species(max_species)
      integer :: skipped_temperature_local, skipped_named_species_local, skipped_legacy_mass_fraction_local
      integer, allocatable :: active_cell(:)
      real(rk) :: sum_Y, dt_local, reactive_indicator, dt_sub
      real(rk) :: dT_abs, dY_abs, rel_drho, alpha, alpha_base
      real(rk) :: max_dT_local, max_dY_local, max_rel_drho_local, min_alpha_local
      real(rk) :: raw_max_dT_local, raw_max_dY_local, raw_max_rel_drho_local
      real(rk) :: qdot_max_local, qdot_integral_local
      real(rk) :: reduce_in(3), reduce_out(3), raw_reduce_in(3), raw_reduce_out(3), alpha_reduce
      logical :: use_named_species_screen, use_source_limiter
      real(rk) :: t_total_start, t_reactor_start, total_time_local, reactor_time_local
      real(rk), allocatable :: T_local(:), P_local(:), Y_local(:,:)
      real(rk), allocatable :: T_before(:), rho_before(:), Y_before(:,:)
      real(rk), allocatable :: h_local(:), cp_local(:), lambda_local(:), rho_local(:), alpha_local(:), qdot_local(:)
      character(kind=c_char), allocatable :: c_names_flat(:)
      real(rk), allocatable :: prev_chem_dT_dt(:)
      real(rk) :: dT_max_est

      ! Load-balancing packed dynamic buffers
      integer :: nfields, r
      real(rk) :: qdot_val, T_prev, rho_prev, dY_max_val
      integer, allocatable :: pack_cell(:), all_active_cells(:)
      real(rk), allocatable :: pack_data(:,:), all_data_new(:,:)
      integer, allocatable :: active_counts(:), active_displs(:)
      integer, allocatable :: active_matrix_counts(:), active_matrix_displs(:)

      n_sub = 1
      if (present(max_dT_chem)) max_dT_chem = zero
      if (present(max_dY_chem)) max_dY_chem = zero
      if (present(max_rel_drho_chem)) max_rel_drho_chem = zero
      if (present(min_source_alpha)) min_source_alpha = 1.0_rk
      if (present(raw_max_dT_chem)) raw_max_dT_chem = zero
      if (present(raw_max_dY_chem)) raw_max_dY_chem = zero
      if (present(raw_max_rel_drho_chem)) raw_max_rel_drho_chem = zero

      if (.not. params%enable_reactions) return
      if (params%chemistry_update_interval <= 0) return

      t_total_start = real(MPI_Wtime(), rk)
      reactor_time_local = zero
      total_time_local = zero

      if (.not. params%enable_cantera_thermo) then
         call fatal_error('chemistry', 'Cantera chemistry requires enable_cantera_thermo=.true.')
      end if
      if (.not. energy%initialized) then
         call fatal_error('chemistry', 'energy must be initialized before chemistry')
      end if
      if (params%nspecies <= 0) then
         call fatal_error('chemistry', 'Cantera chemistry requires nspecies > 0 after mechanism discovery')
      end if
      if (size(species_Y,1) < params%nspecies .or. size(species_Y,2) < mesh%ncells) then
         call fatal_error('chemistry', 'species_Y has incompatible shape')
      end if

      call resolve_chemistry_active_species(params, nactive_screen_species, active_screen_species)
      use_named_species_screen = params%chemistry_active_species_threshold > zero .and. &
                                 nactive_screen_species > 0

      if (present(chemistry_dt)) then
         dt_local = chemistry_dt
      else if (step <= 1) then
         dt_local = params%dt
      else
         dt_local = params%dt * real(params%chemistry_update_interval, rk)
      end if
      if (dt_local <= zero) return

      if (allocated(energy%chem_dT_dt)) then
         allocate(prev_chem_dT_dt(size(energy%chem_dT_dt)))
         prev_chem_dT_dt = energy%chem_dT_dt
      end if
      if (allocated(energy%chem_heat_release_rate)) energy%chem_heat_release_rate = zero
      if (allocated(energy%chem_dT_dt)) energy%chem_dT_dt = zero
      if (allocated(energy%chem_dY_max_dt)) energy%chem_dY_max_dt = zero
      if (allocated(energy%chem_rel_rho_change_dt)) energy%chem_rel_rho_change_dt = zero
      if (allocated(energy%chem_source_alpha)) energy%chem_source_alpha = one
      if (allocated(energy%chem_active)) energy%chem_active = zero

      if (params%enable_chemistry_load_balancing) then
         nloc = mesh%ncells
         allocate(active_cell(nloc))
         nactive = 0
         skipped_temperature_local = 0
         skipped_named_species_local = 0
         skipped_legacy_mass_fraction_local = 0
         
         global_active = 0
         do c = 1, mesh%ncells
            if (params%chemistry_temperature_cutoff > zero) then
               if (energy%T(c) < params%chemistry_temperature_cutoff) then
                  skipped_temperature_local = skipped_temperature_local + 1
                  cycle
               end if
            end if
            if (use_named_species_screen) then
               reactive_indicator = zero
               do k = 1, nactive_screen_species
                  reactive_indicator = max(reactive_indicator, &
                                           max(zero, species_Y(active_screen_species(k), c)))
               end do
               if (reactive_indicator < params%chemistry_active_species_threshold) then
                  skipped_named_species_local = skipped_named_species_local + 1
                  cycle
               end if
            end if
            if (params%chemistry_min_reactive_mass_fraction > zero) then
               reactive_indicator = maxval(max(zero, species_Y(1:params%nspecies, c)))
               if (reactive_indicator < params%chemistry_min_reactive_mass_fraction) then
                  skipped_legacy_mass_fraction_local = skipped_legacy_mass_fraction_local + 1
                  cycle
               end if
            end if
            
            global_active = global_active + 1
            if (mod(global_active - 1, flow%nprocs) == flow%rank) then
               nactive = nactive + 1
               active_cell(nactive) = c
            end if
         end do
      else
         nloc = max(0, flow%last_cell - flow%first_cell + 1)
         allocate(active_cell(max(1, nloc)))
         nactive = 0
         skipped_temperature_local = 0
         skipped_named_species_local = 0
         skipped_legacy_mass_fraction_local = 0
         if (nloc > 0) then
            do i = 1, nloc
               c = flow%first_cell + i - 1
               if (params%chemistry_temperature_cutoff > zero) then
                  if (energy%T(c) < params%chemistry_temperature_cutoff) then
                     skipped_temperature_local = skipped_temperature_local + 1
                     cycle
                  end if
               end if
               if (use_named_species_screen) then
                  reactive_indicator = zero
                  do k = 1, nactive_screen_species
                     reactive_indicator = max(reactive_indicator, &
                                              max(zero, species_Y(active_screen_species(k), c)))
                  end do
                  if (reactive_indicator < params%chemistry_active_species_threshold) then
                     skipped_named_species_local = skipped_named_species_local + 1
                     cycle
                  end if
               end if
               if (params%chemistry_min_reactive_mass_fraction > zero) then
                  reactive_indicator = maxval(max(zero, species_Y(1:params%nspecies, c)))
                  if (reactive_indicator < params%chemistry_min_reactive_mass_fraction) then
                     skipped_legacy_mass_fraction_local = skipped_legacy_mass_fraction_local + 1
                     cycle
                  end if
               end if
               nactive = nactive + 1
               active_cell(nactive) = c
            end do
         end if
         
         call MPI_Allreduce(nactive, global_active, 1, MPI_INTEGER, MPI_SUM, flow%comm, ierr)
         if (ierr /= MPI_SUCCESS) call fatal_error('chemistry', 'MPI_Allreduce failed for active chemistry count')
      end if

      if (global_active <= 0) then
         total_time_local = real(MPI_Wtime(), rk) - t_total_start
         call accumulate_chemistry_screening_stats(nloc, nactive, skipped_temperature_local, &
                                                   skipped_named_species_local, skipped_legacy_mass_fraction_local, &
                                                   total_time_local, reactor_time_local)
         call write_chemistry_diagnostics_row(flow, params, step, dt_local, nloc, nactive, nloc - nactive, &
                                              total_time_local, reactor_time_local, zero, zero, zero, 1.0_rk, &
                                              zero, zero, zero, zero, zero)
         deallocate(active_cell)
         return
      end if

      if (nactive > 0) then
         allocate(T_local(nactive), P_local(nactive), Y_local(params%nspecies, nactive))
         allocate(T_before(nactive), rho_before(nactive), Y_before(params%nspecies, nactive))
         allocate(h_local(nactive), cp_local(nactive), lambda_local(nactive), rho_local(nactive), alpha_local(nactive), qdot_local(nactive))
         alpha_local = 1.0_rk
         P_local = params%background_press

         do i = 1, nactive
            c = active_cell(i)
            T_local(i) = energy%T(c)
            sum_Y = zero
            do k = 1, params%nspecies
               Y_local(k, i) = max(zero, species_Y(k, c))
               sum_Y = sum_Y + Y_local(k, i)
            end do
            if (sum_Y > tiny_safe) then
               Y_local(:, i) = Y_local(:, i) / sum_Y
            else
               Y_local(:, i) = zero
               Y_local(1, i) = 1.0_rk
            end if
            T_before(i) = T_local(i)
            rho_before(i) = max(energy%rho_thermo(c), tiny_safe)
            Y_before(:, i) = Y_local(:, i)
         end do

         call build_c_species_names(params, c_names_flat, n_len)
         call profiler_start('Chemistry_Cantera_ReactorNet')
         t_reactor_start = real(MPI_Wtime(), rk)
         if (params%enable_chemistry_subcycling) then
            dT_max_est = zero
            if (allocated(prev_chem_dT_dt)) then
               do i = 1, nactive
                  c = active_cell(i)
                  dT_max_est = max(dT_max_est, abs(prev_chem_dT_dt(c)) * dt_local)
               end do
            end if

            n_sub = max(1, min(params%max_chemistry_subcycles, &
                               ceiling(dT_max_est / max(params%subcycling_dT_threshold, tiny_safe))))

            dt_sub = dt_local / real(n_sub, rk)
            do j = 1, n_sub
               call cantera_integrate_const_p_chemistry_c(nactive, dt_sub, T_local, P_local, params%nspecies, &
                                                          Y_local, h_local, cp_local, lambda_local, rho_local, &
                                                          params%energy_reference_T, c_names_flat, n_len, &
                                                          params%chemistry_rtol, params%chemistry_atol, &
                                                          params%chemistry_max_steps, merge(1, 0, params%chemistry_energy_enabled))
            end do
         else
            call cantera_integrate_const_p_chemistry_c(nactive, dt_local, T_local, P_local, params%nspecies, &
                                                       Y_local, h_local, cp_local, lambda_local, rho_local, &
                                                       params%energy_reference_T, c_names_flat, n_len, &
                                                       params%chemistry_rtol, params%chemistry_atol, &
                                                       params%chemistry_max_steps, merge(1, 0, params%chemistry_energy_enabled))
         end if
         reactor_time_local = real(MPI_Wtime(), rk) - t_reactor_start
         call profiler_stop('Chemistry_Cantera_ReactorNet')

         raw_max_dT_local = zero
         raw_max_dY_local = zero
         raw_max_rel_drho_local = zero
         do i = 1, nactive
            raw_max_dT_local = max(raw_max_dT_local, abs(T_local(i) - T_before(i)))
            raw_max_dY_local = max(raw_max_dY_local, maxval(abs(Y_local(:, i) - Y_before(:, i))))
            raw_max_rel_drho_local = max(raw_max_rel_drho_local, &
                                         abs(rho_local(i) - rho_before(i)) / max(rho_before(i), tiny_safe))
         end do

         use_source_limiter = params%chemistry_limit_source_update
         alpha_base = min(1.0_rk, max(zero, params%chemistry_source_relaxation))
         min_alpha_local = 1.0_rk
         qdot_max_local = zero
         qdot_integral_local = zero
         if (use_source_limiter) then
            do i = 1, nactive
               alpha = alpha_base
               dT_abs = abs(T_local(i) - T_before(i))
               if (params%chemistry_max_dT_per_step > zero .and. dT_abs > params%chemistry_max_dT_per_step) then
                  alpha = min(alpha, params%chemistry_max_dT_per_step / max(dT_abs, tiny_safe))
               end if
               dY_abs = maxval(abs(Y_local(:, i) - Y_before(:, i)))
               if (params%chemistry_max_dY_per_step > zero .and. dY_abs > params%chemistry_max_dY_per_step) then
                  alpha = min(alpha, params%chemistry_max_dY_per_step / max(dY_abs, tiny_safe))
               end if
               rel_drho = abs(rho_local(i) - rho_before(i)) / max(rho_before(i), tiny_safe)
               if (params%chemistry_max_rel_rho_change_per_step > zero .and. &
                   rel_drho > params%chemistry_max_rel_rho_change_per_step) then
                  alpha = min(alpha, params%chemistry_max_rel_rho_change_per_step / max(rel_drho, tiny_safe))
               end if
               alpha = max(zero, min(1.0_rk, alpha))
               T_local(i) = T_before(i) + alpha * (T_local(i) - T_before(i))
               Y_local(:, i) = Y_before(:, i) + alpha * (Y_local(:, i) - Y_before(:, i))
               Y_local(:, i) = max(zero, Y_local(:, i))
               sum_Y = sum(Y_local(:, i))
               if (sum_Y > tiny_safe) then
                  Y_local(:, i) = Y_local(:, i) / sum_Y
               else
                  Y_local(:, i) = zero
                  Y_local(1, i) = 1.0_rk
               end if
               alpha_local(i) = alpha
               min_alpha_local = min(min_alpha_local, alpha)
            end do
            call cantera_update_thermo_c(nactive, T_local, P_local, params%nspecies, Y_local, &
                                         h_local, cp_local, lambda_local, rho_local, &
                                         params%energy_reference_T, c_names_flat, n_len)
         end if

         call cantera_heat_release_rate_c(nactive, T_local, P_local, params%nspecies, Y_local, qdot_local, &
                                          c_names_flat, n_len)

         qdot_max_local = zero
         qdot_integral_local = zero
         do i = 1, nactive
            c = active_cell(i)
            qdot_max_local = max(qdot_max_local, qdot_local(i))
            qdot_integral_local = qdot_integral_local + qdot_local(i) * mesh%cells(c)%volume
         end do

         max_dT_local = zero
         max_dY_local = zero
         max_rel_drho_local = zero
         do i = 1, nactive
            max_dT_local = max(max_dT_local, abs(T_local(i) - T_before(i)))
            max_dY_local = max(max_dY_local, maxval(abs(Y_local(:, i) - Y_before(:, i))))
            max_rel_drho_local = max(max_rel_drho_local, abs(rho_local(i) - rho_before(i)) / max(rho_before(i), tiny_safe))
         end do

       if (params%enable_chemistry_load_balancing) then
          nfields = 10 + params%nspecies
          allocate(pack_cell(max(1, nactive)))
          allocate(pack_data(nfields, max(1, nactive)))
          
          do i = 1, nactive
             c = active_cell(i)
             pack_cell(i) = c
             pack_data(1, i) = T_local(i)
             pack_data(2, i) = h_local(i)
             pack_data(3, i) = cp_local(i)
             pack_data(4, i) = lambda_local(i)
             pack_data(5, i) = rho_local(i)
             pack_data(6, i) = alpha_local(i)
             pack_data(7, i) = qdot_local(i)
             pack_data(8, i) = T_before(i)
             pack_data(9, i) = rho_before(i)
             pack_data(10, i) = maxval(abs(Y_local(:, i) - Y_before(:, i)))
             pack_data(11:10+params%nspecies, i) = Y_local(1:params%nspecies, i)
          end do
          
          allocate(active_counts(flow%nprocs))
          allocate(active_displs(flow%nprocs))
          allocate(active_matrix_counts(flow%nprocs))
          allocate(active_matrix_displs(flow%nprocs))
          
          call MPI_Allgather(nactive, 1, MPI_INTEGER, &
                             active_counts, 1, MPI_INTEGER, flow%comm, ierr)
          if (ierr /= MPI_SUCCESS) call fatal_error('chemistry', 'MPI_Allgather active counts failed')
          
          active_displs(1) = 0
          active_matrix_counts(1) = active_counts(1) * nfields
          active_matrix_displs(1) = 0
          
          do r = 2, flow%nprocs
             active_displs(r) = active_displs(r - 1) + active_counts(r - 1)
             active_matrix_counts(r) = active_counts(r) * nfields
             active_matrix_displs(r) = active_matrix_displs(r - 1) + active_matrix_counts(r - 1)
          end do
          
          global_active = sum(active_counts)
          
          allocate(all_active_cells(max(1, global_active)))
          allocate(all_data_new(nfields, max(1, global_active)))
          
          ! Gather active cell indices
          call MPI_Allgatherv(pack_cell, nactive, MPI_INTEGER, &
                              all_active_cells, active_counts, active_displs, MPI_INTEGER, flow%comm, ierr)
          if (ierr /= MPI_SUCCESS) call fatal_error('chemistry', 'MPI_Allgatherv active cells failed')
          
          ! Gather packed data
          call MPI_Allgatherv(pack_data, nactive * nfields, MPI_DOUBLE_PRECISION, &
                              all_data_new, active_matrix_counts, active_matrix_displs, MPI_DOUBLE_PRECISION, flow%comm, ierr)
          if (ierr /= MPI_SUCCESS) call fatal_error('chemistry', 'MPI_Allgatherv active data failed')
          
          ! Unpack gathered data directly into global replicated arrays
          do i = 1, global_active
             c = all_active_cells(i)
             
             energy%T(c) = all_data_new(1, i)
             energy%h(c) = all_data_new(2, i)
             energy%cp(c) = all_data_new(3, i)
             energy%lambda(c) = all_data_new(4, i)
             energy%rho_thermo(c) = all_data_new(5, i)
             
             alpha = all_data_new(6, i)
             qdot_val = all_data_new(7, i)
             T_prev = all_data_new(8, i)
             rho_prev = all_data_new(9, i)
             dY_max_val = all_data_new(10, i)
             
             species_Y(1:params%nspecies, c) = all_data_new(11:10+params%nspecies, i)
             
             if (allocated(energy%chem_active)) energy%chem_active(c) = one
             if (allocated(energy%chem_source_alpha)) energy%chem_source_alpha(c) = alpha
             if (allocated(energy%chem_dT_dt)) energy%chem_dT_dt(c) = (energy%T(c) - T_prev) / max(dt_local, tiny_safe)
             if (allocated(energy%chem_dY_max_dt)) energy%chem_dY_max_dt(c) = dY_max_val / max(dt_local, tiny_safe)
             if (allocated(energy%chem_rel_rho_change_dt)) energy%chem_rel_rho_change_dt(c) = &
                (abs(energy%rho_thermo(c) - rho_prev) / max(rho_prev, tiny_safe)) / max(dt_local, tiny_safe)
             if (allocated(energy%chem_heat_release_rate)) energy%chem_heat_release_rate(c) = qdot_val
          end do
          
          ! Enforce mass fraction normalization
          do i = 1, global_active
             c = all_active_cells(i)
             species_Y(1:params%nspecies, c) = max(zero, species_Y(1:params%nspecies, c))
             sum_Y = sum(species_Y(1:params%nspecies, c))
             if (sum_Y > tiny_safe) then
                species_Y(1:params%nspecies, c) = species_Y(1:params%nspecies, c) / sum_Y
             else
                species_Y(:, c) = zero
                species_Y(1, c) = 1.0_rk
             end if
          end do
          
          deallocate(pack_cell, pack_data)
          deallocate(active_counts, active_displs, active_matrix_counts, active_matrix_displs)
          deallocate(all_active_cells, all_data_new)
       else
          do i = 1, nactive
             c = active_cell(i)
             energy%T(c) = T_local(i)
             energy%h(c) = h_local(i)
             energy%cp(c) = cp_local(i)
             energy%lambda(c) = lambda_local(i)
             energy%rho_thermo(c) = rho_local(i)
             do k = 1, params%nspecies
                species_Y(k, c) = max(zero, Y_local(k, i))
             end do
             sum_Y = sum(species_Y(1:params%nspecies, c))
             if (sum_Y > tiny_safe) then
                species_Y(1:params%nspecies, c) = species_Y(1:params%nspecies, c) / sum_Y
             else
                species_Y(:, c) = zero
                species_Y(1, c) = 1.0_rk
             end if
             if (allocated(energy%chem_active)) energy%chem_active(c) = one
             if (allocated(energy%chem_source_alpha)) energy%chem_source_alpha(c) = alpha_local(i)
             if (allocated(energy%chem_dT_dt)) energy%chem_dT_dt(c) = (T_local(i) - T_before(i)) / max(dt_local, tiny_safe)
             if (allocated(energy%chem_dY_max_dt)) energy%chem_dY_max_dt(c) = &
                maxval(abs(Y_local(:, i) - Y_before(:, i))) / max(dt_local, tiny_safe)
             if (allocated(energy%chem_rel_rho_change_dt)) energy%chem_rel_rho_change_dt(c) = &
                (abs(rho_local(i) - rho_before(i)) / max(rho_before(i), tiny_safe)) / max(dt_local, tiny_safe)
             if (allocated(energy%chem_heat_release_rate)) energy%chem_heat_release_rate(c) = qdot_local(i)
          end do
       end if
    end if
 
    if (.not. allocated(T_local)) then
       max_dT_local = zero
       max_dY_local = zero
       max_rel_drho_local = zero
       raw_max_dT_local = zero
       raw_max_dY_local = zero
       raw_max_rel_drho_local = zero
       min_alpha_local = 1.0_rk
       qdot_max_local = zero
       qdot_integral_local = zero
    end if
    reduce_in = [max_dT_local, max_dY_local, max_rel_drho_local]
    raw_reduce_in = [raw_max_dT_local, raw_max_dY_local, raw_max_rel_drho_local]
    call MPI_Allreduce(raw_reduce_in, raw_reduce_out, 3, MPI_DOUBLE_PRECISION, MPI_MAX, flow%comm, ierr)
    if (ierr /= MPI_SUCCESS) call fatal_error('chemistry', 'MPI_Allreduce failed for raw chemistry source diagnostics')
    call MPI_Allreduce(reduce_in, reduce_out, 3, MPI_DOUBLE_PRECISION, MPI_MAX, flow%comm, ierr)
    if (ierr /= MPI_SUCCESS) call fatal_error('chemistry', 'MPI_Allreduce failed for chemistry source diagnostics')
    call MPI_Allreduce(min_alpha_local, alpha_reduce, 1, MPI_DOUBLE_PRECISION, MPI_MIN, flow%comm, ierr)
    if (ierr /= MPI_SUCCESS) call fatal_error('chemistry', 'MPI_Allreduce failed for chemistry source limiter alpha')
    if (present(max_dT_chem)) max_dT_chem = reduce_out(1)
    if (present(max_dY_chem)) max_dY_chem = reduce_out(2)
    if (present(max_rel_drho_chem)) max_rel_drho_chem = reduce_out(3)
    if (present(min_source_alpha)) min_source_alpha = alpha_reduce
    if (present(raw_max_dT_chem)) raw_max_dT_chem = raw_reduce_out(1)
    if (present(raw_max_dY_chem)) raw_max_dY_chem = raw_reduce_out(2)
    if (present(raw_max_rel_drho_chem)) raw_max_rel_drho_chem = raw_reduce_out(3)
 
    if (.not. params%enable_chemistry_load_balancing) then
       call flow_exchange_cell_matrix(flow, species_Y)
       call flow_exchange_cell_scalars(flow, energy%T, energy%h, energy%cp, energy%lambda)
       call flow_exchange_cell_scalar(flow, energy%rho_thermo)
    end if

      total_time_local = real(MPI_Wtime(), rk) - t_total_start
      call accumulate_chemistry_screening_stats(nloc, nactive, skipped_temperature_local, &
                                                skipped_named_species_local, skipped_legacy_mass_fraction_local, &
                                                total_time_local, reactor_time_local, nactive * n_sub)
      call write_chemistry_diagnostics_row(flow, params, step, dt_local, nloc, nactive, nloc - nactive, &
                                           total_time_local, reactor_time_local, &
                                           max_dT_local, max_dY_local, max_rel_drho_local, min_alpha_local, &
                                           raw_max_dT_local, raw_max_dY_local, raw_max_rel_drho_local, &
                                           qdot_max_local, qdot_integral_local)

      if (allocated(T_local)) deallocate(T_local, P_local, Y_local, T_before, Y_before, rho_before, &
                                          h_local, cp_local, lambda_local, rho_local, alpha_local, qdot_local)
      if (allocated(c_names_flat)) deallocate(c_names_flat)
      deallocate(active_cell)
   end subroutine advance_cantera_chemistry