Write aggregate species and enthalpy conservation trend diagnostics.
This diagnostics-only routine is a first-pass conservation tracker for the variable-density path. It reports global integrals, boundary-flux estimates, and step-to-step balance defects for:
Species boundary fluxes use stored face mass flux and owner-cell species values on physical boundary faces. The rho*h flux is advective only; the aggregate enthalpy defect therefore excludes reconstructed conductive boundary fluxes and should be interpreted as a trend diagnostic, not a complete energy closure proof.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(mesh_t), | intent(in) | :: | mesh | |||
| type(flow_mpi_t), | intent(in) | :: | flow | |||
| type(case_params_t), | intent(in) | :: | params | |||
| type(flow_fields_t), | intent(in) | :: | fields | |||
| type(species_fields_t), | intent(in) | :: | species | |||
| type(energy_fields_t), | intent(in) | :: | energy | |||
| type(transport_properties_t), | intent(in) | :: | transport | |||
| integer, | intent(in) | :: | step | |||
| real(kind=rk), | intent(in) | :: | time |
subroutine write_species_energy_conservation_diagnostics(mesh, flow, params, fields, species, energy, transport, step, time) type(mesh_t), intent(in) :: mesh type(flow_mpi_t), intent(in) :: flow type(case_params_t), intent(in) :: params type(flow_fields_t), intent(in) :: fields type(species_fields_t), intent(in) :: species type(energy_fields_t), intent(in) :: energy type(transport_properties_t), intent(in) :: transport integer, intent(in) :: step real(rk), intent(in) :: time integer :: c, f, k, owner, nb, ierr, unit_id, nsp logical, save :: aggregate_initialized = .false. logical, save :: species_initialized = .false. logical, save :: previous_initialized = .false. character(len=1024) :: filename character(len=64) :: label real(rk) :: vol, rho_c, y_sum, h_c, t_c, mflux real(rk) :: dt_since_previous, denom real(rk) :: local_volume, volume_total real(rk) :: local_mass, total_mass real(rk) :: local_boundary_mass_flux, net_boundary_mass_flux real(rk) :: local_species_mass_sum, species_mass_sum real(rk) :: local_boundary_species_flux_sum, net_boundary_species_flux_sum real(rk) :: local_y_sum_min, y_sum_min real(rk) :: local_y_sum_max, y_sum_max real(rk) :: local_y_sum_int, y_sum_int real(rk) :: local_y_sum_l2_num, y_sum_l2_num real(rk) :: species_sum_mean, species_sum_l2 real(rk) :: local_y_sum_minus_one_abs_max, y_sum_minus_one_abs_max real(rk) :: local_y_sum_minus_one_l2_num, y_sum_minus_one_l2_num real(rk) :: species_sum_minus_one_l2 real(rk) :: local_rho_h_integral, rho_h_integral real(rk) :: local_boundary_rho_h_advective_flux, net_boundary_rho_h_advective_flux real(rk) :: local_qrad_integral, qrad_integral real(rk) :: local_rho_species_hdiff_integral, rho_species_hdiff_integral real(rk) :: local_h_min, h_min real(rk) :: local_h_max, h_max real(rk) :: local_t_min, t_min real(rk) :: local_t_max, t_max real(rk) :: delta_mass, mass_rate, mass_balance_defect, rel_mass_balance_defect real(rk) :: delta_rho_h, rho_h_rate, rho_h_advective_balance_defect_no_conduction real(rk), save :: previous_time = 0.0_rk real(rk), save :: previous_mass = 0.0_rk real(rk), save :: previous_rho_h = 0.0_rk real(rk), allocatable, save :: previous_species_mass(:) real(rk), allocatable :: local_species_mass(:), species_mass(:) real(rk), allocatable :: local_boundary_species_flux(:), boundary_species_flux(:) real(rk), allocatable :: species_delta_mass(:), species_mass_rate(:) real(rk), allocatable :: species_balance_defect(:), species_relative_balance_defect(:) real(rk) :: send_val, recv_val if (.not. params%write_diagnostics) return nsp = 0 if (params%enable_species) nsp = max(0, species%nspecies) allocate(local_species_mass(max(1, nsp))) allocate(species_mass(max(1, nsp))) allocate(local_boundary_species_flux(max(1, nsp))) allocate(boundary_species_flux(max(1, nsp))) allocate(species_delta_mass(max(1, nsp))) allocate(species_mass_rate(max(1, nsp))) allocate(species_balance_defect(max(1, nsp))) allocate(species_relative_balance_defect(max(1, nsp))) local_species_mass = 0.0_rk species_mass = 0.0_rk local_boundary_species_flux = 0.0_rk boundary_species_flux = 0.0_rk species_delta_mass = 0.0_rk species_mass_rate = 0.0_rk species_balance_defect = 0.0_rk species_relative_balance_defect = 0.0_rk local_volume = 0.0_rk local_mass = 0.0_rk local_boundary_mass_flux = 0.0_rk local_species_mass_sum = 0.0_rk local_boundary_species_flux_sum = 0.0_rk local_y_sum_min = huge(1.0_rk) local_y_sum_max = -huge(1.0_rk) local_y_sum_int = 0.0_rk local_y_sum_l2_num = 0.0_rk local_y_sum_minus_one_abs_max = 0.0_rk local_y_sum_minus_one_l2_num = 0.0_rk local_rho_h_integral = 0.0_rk local_boundary_rho_h_advective_flux = 0.0_rk local_qrad_integral = 0.0_rk local_rho_species_hdiff_integral = 0.0_rk local_h_min = huge(1.0_rk) local_h_max = -huge(1.0_rk) local_t_min = huge(1.0_rk) local_t_max = -huge(1.0_rk) do c = 1, mesh%ncells if (.not. flow%owned(c)) cycle vol = mesh%cells(c)%volume if (vol <= 0.0_rk) cycle rho_c = params%rho if (allocated(transport%rho)) then if (size(transport%rho) >= c) rho_c = transport%rho(c) end if local_volume = local_volume + vol local_mass = local_mass + rho_c * vol y_sum = 0.0_rk if (nsp > 0 .and. allocated(species%Y)) then if (size(species%Y, 2) >= c) then do k = 1, nsp if (size(species%Y, 1) >= k) then local_species_mass(k) = local_species_mass(k) + rho_c * species%Y(k, c) * vol y_sum = y_sum + species%Y(k, c) end if end do end if end if if (nsp > 0) then local_species_mass_sum = local_species_mass_sum + rho_c * y_sum * vol local_y_sum_min = min(local_y_sum_min, y_sum) local_y_sum_max = max(local_y_sum_max, y_sum) local_y_sum_int = local_y_sum_int + y_sum * vol local_y_sum_l2_num = local_y_sum_l2_num + y_sum * y_sum * vol local_y_sum_minus_one_abs_max = max(local_y_sum_minus_one_abs_max, abs(y_sum - 1.0_rk)) local_y_sum_minus_one_l2_num = local_y_sum_minus_one_l2_num + (y_sum - 1.0_rk)**2 * vol end if if (params%enable_energy .and. allocated(energy%h)) then if (size(energy%h) >= c) then h_c = energy%h(c) local_rho_h_integral = local_rho_h_integral + rho_c * h_c * vol local_h_min = min(local_h_min, h_c) local_h_max = max(local_h_max, h_c) end if end if if (params%enable_energy .and. allocated(energy%T)) then if (size(energy%T) >= c) then t_c = energy%T(c) local_t_min = min(local_t_min, t_c) local_t_max = max(local_t_max, t_c) end if end if if (params%enable_energy .and. allocated(energy%qrad)) then if (size(energy%qrad) >= c) local_qrad_integral = local_qrad_integral + energy%qrad(c) * vol end if if (params%enable_energy .and. allocated(energy%species_enthalpy_diffusion)) then if (size(energy%species_enthalpy_diffusion) >= c) then local_rho_species_hdiff_integral = local_rho_species_hdiff_integral + & rho_c * energy%species_enthalpy_diffusion(c) * vol end if end if end do if (allocated(fields%mass_flux)) then do f = 1, mesh%nfaces owner = mesh%faces(f)%owner nb = mesh%faces(f)%neighbor if (nb <= 0) nb = mesh%faces(f)%periodic_neighbor if (nb <= 0 .and. owner >= 1 .and. owner <= mesh%ncells) then if (.not. flow%owned(owner)) cycle mflux = fields%mass_flux(f) local_boundary_mass_flux = local_boundary_mass_flux + mflux if (nsp > 0 .and. allocated(species%Y)) then if (size(species%Y, 2) >= owner) then do k = 1, nsp if (size(species%Y, 1) >= k) then local_boundary_species_flux(k) = local_boundary_species_flux(k) + mflux * species%Y(k, owner) local_boundary_species_flux_sum = local_boundary_species_flux_sum + mflux * species%Y(k, owner) end if end do end if end if if (params%enable_energy .and. allocated(energy%h)) then if (size(energy%h) >= owner) then local_boundary_rho_h_advective_flux = local_boundary_rho_h_advective_flux + mflux * energy%h(owner) end if end if end if end do end if call reduce_sum(local_volume, volume_total, flow) call reduce_sum(local_mass, total_mass, flow) call reduce_sum(local_boundary_mass_flux, net_boundary_mass_flux, flow) call reduce_sum(local_species_mass_sum, species_mass_sum, flow) call reduce_sum(local_boundary_species_flux_sum, net_boundary_species_flux_sum, flow) call reduce_sum(local_y_sum_int, y_sum_int, flow) call reduce_sum(local_y_sum_l2_num, y_sum_l2_num, flow) call reduce_sum(local_y_sum_minus_one_l2_num, y_sum_minus_one_l2_num, flow) call reduce_sum(local_rho_h_integral, rho_h_integral, flow) call reduce_sum(local_boundary_rho_h_advective_flux, net_boundary_rho_h_advective_flux, flow) call reduce_sum(local_qrad_integral, qrad_integral, flow) call reduce_sum(local_rho_species_hdiff_integral, rho_species_hdiff_integral, flow) if (nsp > 0) then send_val = local_y_sum_min call MPI_Allreduce(send_val, recv_val, 1, MPI_DOUBLE_PRECISION, MPI_MIN, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing species sum min') y_sum_min = recv_val send_val = local_y_sum_max call MPI_Allreduce(send_val, recv_val, 1, MPI_DOUBLE_PRECISION, MPI_MAX, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing species sum max') y_sum_max = recv_val send_val = local_y_sum_minus_one_abs_max call MPI_Allreduce(send_val, recv_val, 1, MPI_DOUBLE_PRECISION, MPI_MAX, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing species sum minus one max') y_sum_minus_one_abs_max = recv_val else y_sum_min = 0.0_rk y_sum_max = 0.0_rk y_sum_minus_one_abs_max = 0.0_rk end if if (params%enable_energy .and. allocated(energy%h)) then send_val = local_h_min call MPI_Allreduce(send_val, recv_val, 1, MPI_DOUBLE_PRECISION, MPI_MIN, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing h min') h_min = recv_val send_val = local_h_max call MPI_Allreduce(send_val, recv_val, 1, MPI_DOUBLE_PRECISION, MPI_MAX, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing h max') h_max = recv_val else h_min = 0.0_rk h_max = 0.0_rk end if if (params%enable_energy .and. allocated(energy%T)) then send_val = local_t_min call MPI_Allreduce(send_val, recv_val, 1, MPI_DOUBLE_PRECISION, MPI_MIN, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing T min') t_min = recv_val send_val = local_t_max call MPI_Allreduce(send_val, recv_val, 1, MPI_DOUBLE_PRECISION, MPI_MAX, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing T max') t_max = recv_val else t_min = 0.0_rk t_max = 0.0_rk end if if (nsp > 0) then call MPI_Allreduce(local_species_mass, species_mass, nsp, MPI_DOUBLE_PRECISION, MPI_SUM, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing species mass integrals') call MPI_Allreduce(local_boundary_species_flux, boundary_species_flux, nsp, MPI_DOUBLE_PRECISION, MPI_SUM, flow%comm, ierr) if (ierr /= MPI_SUCCESS) call fatal_error('output', 'MPI failure reducing species boundary fluxes') end if if (volume_total > 0.0_rk) then species_sum_mean = y_sum_int / volume_total species_sum_l2 = sqrt(max(0.0_rk, y_sum_l2_num / volume_total)) species_sum_minus_one_l2 = sqrt(max(0.0_rk, y_sum_minus_one_l2_num / volume_total)) else species_sum_mean = 0.0_rk species_sum_l2 = 0.0_rk species_sum_minus_one_l2 = 0.0_rk end if if (previous_initialized) then dt_since_previous = time - previous_time else dt_since_previous = 0.0_rk end if if (previous_initialized .and. dt_since_previous > tiny(1.0_rk)) then delta_mass = total_mass - previous_mass mass_rate = delta_mass / dt_since_previous mass_balance_defect = mass_rate + net_boundary_mass_flux denom = max(abs(net_boundary_mass_flux), abs(mass_rate), tiny(1.0_rk)) rel_mass_balance_defect = abs(mass_balance_defect) / denom delta_rho_h = rho_h_integral - previous_rho_h rho_h_rate = delta_rho_h / dt_since_previous rho_h_advective_balance_defect_no_conduction = rho_h_rate + net_boundary_rho_h_advective_flux - & qrad_integral - rho_species_hdiff_integral if (nsp > 0) then if (.not. allocated(previous_species_mass)) allocate(previous_species_mass(nsp)) if (size(previous_species_mass) /= nsp) then deallocate(previous_species_mass) allocate(previous_species_mass(nsp)) previous_species_mass = species_mass end if do k = 1, nsp species_delta_mass(k) = species_mass(k) - previous_species_mass(k) species_mass_rate(k) = species_delta_mass(k) / dt_since_previous species_balance_defect(k) = species_mass_rate(k) + boundary_species_flux(k) denom = max(abs(species_mass_rate(k)), abs(boundary_species_flux(k)), tiny(1.0_rk)) species_relative_balance_defect(k) = abs(species_balance_defect(k)) / denom end do end if else delta_mass = 0.0_rk mass_rate = 0.0_rk mass_balance_defect = 0.0_rk rel_mass_balance_defect = 0.0_rk delta_rho_h = 0.0_rk rho_h_rate = 0.0_rk rho_h_advective_balance_defect_no_conduction = 0.0_rk end if if (flow%rank == 0) then call execute_command_line('mkdir -p ' // trim(params%output_dir) // '/diagnostics') filename = trim(params%output_dir) // '/diagnostics/species_energy_conservation.csv' if (.not. aggregate_initialized) then open(newunit=unit_id, file=trim(filename), status='replace', action='write') aggregate_initialized = .true. write(unit_id,'(a)') 'step,time,volume_total,total_mass,net_boundary_mass_flux,' // & 'delta_time_since_previous,delta_mass_since_previous,mass_rate_since_previous,' // & 'mass_balance_defect_since_previous,relative_mass_balance_defect_since_previous,' // & 'transported_species_mass_sum,net_boundary_species_mass_flux_sum,' // & 'species_sum_min,species_sum_max,species_sum_mean,species_sum_l2,' // & 'species_sum_minus_one_abs_max,species_sum_minus_one_l2,' // & 'rho_h_integral,net_boundary_rho_h_advective_flux,' // & 'qrad_integral,rho_species_enthalpy_diffusion_integral,' // & 'delta_rho_h_since_previous,rho_h_rate_since_previous,' // & 'rho_h_advective_balance_defect_no_conduction,h_min,h_max,T_min,T_max' else open(newunit=unit_id, file=trim(filename), status='unknown', position='append', action='write') end if call write_csv_value_i(unit_id, step) call write_csv_value_r(unit_id, time) call write_csv_value_r(unit_id, volume_total) call write_csv_value_r(unit_id, total_mass) call write_csv_value_r(unit_id, net_boundary_mass_flux) call write_csv_value_r(unit_id, dt_since_previous) call write_csv_value_r(unit_id, delta_mass) call write_csv_value_r(unit_id, mass_rate) call write_csv_value_r(unit_id, mass_balance_defect) call write_csv_value_r(unit_id, rel_mass_balance_defect) call write_csv_value_r(unit_id, species_mass_sum) call write_csv_value_r(unit_id, net_boundary_species_flux_sum) call write_csv_value_r(unit_id, y_sum_min) call write_csv_value_r(unit_id, y_sum_max) call write_csv_value_r(unit_id, species_sum_mean) call write_csv_value_r(unit_id, species_sum_l2) call write_csv_value_r(unit_id, y_sum_minus_one_abs_max) call write_csv_value_r(unit_id, species_sum_minus_one_l2) call write_csv_value_r(unit_id, rho_h_integral) call write_csv_value_r(unit_id, net_boundary_rho_h_advective_flux) call write_csv_value_r(unit_id, qrad_integral) call write_csv_value_r(unit_id, rho_species_hdiff_integral) call write_csv_value_r(unit_id, delta_rho_h) call write_csv_value_r(unit_id, rho_h_rate) call write_csv_value_r(unit_id, rho_h_advective_balance_defect_no_conduction) call write_csv_value_r(unit_id, h_min) call write_csv_value_r(unit_id, h_max) call write_csv_value_r(unit_id, t_min) call write_csv_value_r(unit_id, t_max) write(unit_id,*) close(unit_id) filename = trim(params%output_dir) // '/diagnostics/species_integrals.csv' if (nsp > 0) then if (.not. species_initialized) then open(newunit=unit_id, file=trim(filename), status='replace', action='write') species_initialized = .true. write(unit_id,'(a)', advance='no') 'step,time' do k = 1, nsp write(label,'("Y",i0)') k call write_csv_header(unit_id, trim(label)//'_mass_integral') call write_csv_header(unit_id, trim(label)//'_net_boundary_mass_flux') call write_csv_header(unit_id, trim(label)//'_delta_mass_since_previous') call write_csv_header(unit_id, trim(label)//'_mass_rate_since_previous') call write_csv_header(unit_id, trim(label)//'_balance_defect_since_previous') call write_csv_header(unit_id, trim(label)//'_relative_balance_defect_since_previous') end do write(unit_id,*) else open(newunit=unit_id, file=trim(filename), status='unknown', position='append', action='write') end if call write_csv_value_i(unit_id, step) call write_csv_value_r(unit_id, time) do k = 1, nsp call write_csv_value_r(unit_id, species_mass(k)) call write_csv_value_r(unit_id, boundary_species_flux(k)) call write_csv_value_r(unit_id, species_delta_mass(k)) call write_csv_value_r(unit_id, species_mass_rate(k)) call write_csv_value_r(unit_id, species_balance_defect(k)) call write_csv_value_r(unit_id, species_relative_balance_defect(k)) end do write(unit_id,*) close(unit_id) end if end if previous_time = time previous_mass = total_mass previous_rho_h = rho_h_integral previous_initialized = .true. if (nsp > 0) then if (.not. allocated(previous_species_mass)) allocate(previous_species_mass(nsp)) if (size(previous_species_mass) /= nsp) then deallocate(previous_species_mass) allocate(previous_species_mass(nsp)) end if previous_species_mass = species_mass(1:nsp) end if deallocate(local_species_mass) deallocate(species_mass) deallocate(local_boundary_species_flux) deallocate(boundary_species_flux) deallocate(species_delta_mass) deallocate(species_mass_rate) deallocate(species_balance_defect) deallocate(species_relative_balance_defect) end subroutine write_species_energy_conservation_diagnostics