run_multi_molecule_calculations Subroutine

public subroutine run_multi_molecule_calculations(resources, mqc_config)

Uses

  • proc~~run_multi_molecule_calculations~~UsesGraph proc~run_multi_molecule_calculations run_multi_molecule_calculations module~mqc_config_adapter mqc_config_adapter proc~run_multi_molecule_calculations->module~mqc_config_adapter module~mqc_config_parser mqc_config_parser proc~run_multi_molecule_calculations->module~mqc_config_parser module~mqc_error mqc_error proc~run_multi_molecule_calculations->module~mqc_error module~mqc_io_helpers mqc_io_helpers proc~run_multi_molecule_calculations->module~mqc_io_helpers module~mqc_json mqc_json proc~run_multi_molecule_calculations->module~mqc_json module~mqc_config_adapter->module~mqc_config_parser module~mqc_config_adapter->module~mqc_error module~mqc_calculation_keywords mqc_calculation_keywords module~mqc_config_adapter->module~mqc_calculation_keywords module~mqc_elements mqc_elements module~mqc_config_adapter->module~mqc_elements module~mqc_method_config mqc_method_config module~mqc_config_adapter->module~mqc_method_config module~mqc_physical_fragment mqc_physical_fragment module~mqc_config_adapter->module~mqc_physical_fragment pic_logger pic_logger module~mqc_config_adapter->pic_logger pic_types pic_types module~mqc_config_adapter->pic_types module~mqc_config_parser->module~mqc_error module~mqc_calc_types mqc_calc_types module~mqc_config_parser->module~mqc_calc_types module~mqc_calculation_defaults mqc_calculation_defaults module~mqc_config_parser->module~mqc_calculation_defaults module~mqc_geometry mqc_geometry module~mqc_config_parser->module~mqc_geometry module~mqc_method_types mqc_method_types module~mqc_config_parser->module~mqc_method_types module~mqc_config_parser->module~mqc_physical_fragment module~mqc_config_parser->pic_types pic_io pic_io module~mqc_json->pic_io module~mqc_json->pic_logger module~mqc_calc_types->pic_types module~mqc_calculation_defaults->pic_types module~mqc_calculation_keywords->module~mqc_calculation_defaults module~mqc_calculation_keywords->pic_types module~mqc_elements->pic_types pic_ascii pic_ascii module~mqc_elements->pic_ascii module~mqc_geometry->pic_types module~mqc_method_config->module~mqc_method_types module~mqc_method_config->pic_types module~mqc_method_types->pic_types module~mqc_physical_fragment->module~mqc_error module~mqc_physical_fragment->module~mqc_elements module~mqc_physical_fragment->module~mqc_geometry module~mqc_physical_fragment->pic_types module~mqc_cgto mqc_cgto module~mqc_physical_fragment->module~mqc_cgto module~mqc_physical_constants mqc_physical_constants module~mqc_physical_fragment->module~mqc_physical_constants module~mqc_xyz_reader mqc_xyz_reader module~mqc_physical_fragment->module~mqc_xyz_reader module~mqc_cgto->pic_types module~mqc_physical_constants->pic_types module~mqc_xyz_reader->module~mqc_error module~mqc_xyz_reader->module~mqc_geometry module~mqc_xyz_reader->pic_types

Run calculations for multiple molecules with MPI parallelization Each molecule is independent, so assign one molecule per rank

Arguments

Type IntentOptional Attributes Name
type(resources_t), intent(in) :: resources
type(mqc_config_t), intent(in) :: mqc_config

Calls

proc~~run_multi_molecule_calculations~~CallsGraph proc~run_multi_molecule_calculations run_multi_molecule_calculations abort_comm abort_comm proc~run_multi_molecule_calculations->abort_comm barrier barrier proc~run_multi_molecule_calculations->barrier error error proc~run_multi_molecule_calculations->error info info proc~run_multi_molecule_calculations->info proc~config_to_driver config_to_driver proc~run_multi_molecule_calculations->proc~config_to_driver proc~config_to_system_geometry config_to_system_geometry proc~run_multi_molecule_calculations->proc~config_to_system_geometry proc~error_add_context error_t%error_add_context proc~run_multi_molecule_calculations->proc~error_add_context proc~error_get_full_trace error_t%error_get_full_trace proc~run_multi_molecule_calculations->proc~error_get_full_trace proc~error_has_error error_t%error_has_error proc~run_multi_molecule_calculations->proc~error_has_error proc~get_output_json_filename get_output_json_filename proc~run_multi_molecule_calculations->proc~get_output_json_filename proc~merge_multi_molecule_json merge_multi_molecule_json proc~run_multi_molecule_calculations->proc~merge_multi_molecule_json proc~run_calculation run_calculation proc~run_multi_molecule_calculations->proc~run_calculation proc~set_molecule_suffix set_molecule_suffix proc~run_multi_molecule_calculations->proc~set_molecule_suffix proc~system_destroy system_geometry_t%system_destroy proc~run_multi_molecule_calculations->proc~system_destroy to_char to_char proc~run_multi_molecule_calculations->to_char verbose verbose proc~run_multi_molecule_calculations->verbose proc~xtb_configure xtb_config_t%xtb_configure proc~config_to_driver->proc~xtb_configure proc~config_to_system_geometry->proc~error_add_context proc~config_to_system_geometry->proc~error_has_error proc~error_set error_t%error_set proc~config_to_system_geometry->proc~error_set proc~geometry_to_system_fragmented geometry_to_system_fragmented proc~config_to_system_geometry->proc~geometry_to_system_fragmented proc~geometry_to_system_unfragmented geometry_to_system_unfragmented proc~config_to_system_geometry->proc~geometry_to_system_unfragmented proc~molecule_to_system_geometry molecule_to_system_geometry proc~config_to_system_geometry->proc~molecule_to_system_geometry proc~error_get_full_trace->proc~error_has_error proc~merge_multi_molecule_json->error proc~merge_multi_molecule_json->info proc~merge_multi_molecule_json->to_char proc~read_json_content read_json_content proc~merge_multi_molecule_json->proc~read_json_content proc~run_calculation->info proc~run_calculation->to_char omp_set_num_threads omp_set_num_threads proc~run_calculation->omp_set_num_threads proc~config_log_settings method_config_t%config_log_settings proc~run_calculation->proc~config_log_settings proc~json_output_data_destroy json_output_data_t%json_output_data_destroy proc~run_calculation->proc~json_output_data_destroy proc~run_fragmented_calculation run_fragmented_calculation proc~run_calculation->proc~run_fragmented_calculation proc~run_unfragmented_calculation run_unfragmented_calculation proc~run_calculation->proc~run_unfragmented_calculation proc~write_json_output write_json_output proc~run_calculation->proc~write_json_output warning warning proc~run_calculation->warning proc~config_log_settings->info proc~xtb_get_solvation_info xtb_config_t%xtb_get_solvation_info proc~config_log_settings->proc~xtb_get_solvation_info proc~initialize_fragmented_system initialize_fragmented_system proc~geometry_to_system_fragmented->proc~initialize_fragmented_system proc~element_symbol_to_number element_symbol_to_number proc~geometry_to_system_unfragmented->proc~element_symbol_to_number proc~to_bohr to_bohr proc~geometry_to_system_unfragmented->proc~to_bohr proc~json_output_data_reset json_output_data_t%json_output_data_reset proc~json_output_data_destroy->proc~json_output_data_reset proc~molecule_to_system_geometry->proc~error_set proc~molecule_to_system_geometry->proc~geometry_to_system_unfragmented proc~molecule_to_system_geometry->proc~initialize_fragmented_system proc~read_json_content->error proc~get_molecule_name get_molecule_name proc~read_json_content->proc~get_molecule_name proc~run_fragmented_calculation->abort_comm proc~run_fragmented_calculation->error proc~run_fragmented_calculation->info proc~run_fragmented_calculation->proc~error_has_error proc~run_fragmented_calculation->to_char allgather allgather proc~run_fragmented_calculation->allgather bcast bcast proc~run_fragmented_calculation->bcast destroy destroy proc~run_fragmented_calculation->destroy init init proc~run_fragmented_calculation->init proc~apply_distance_screening apply_distance_screening proc~run_fragmented_calculation->proc~apply_distance_screening proc~binomial binomial proc~run_fragmented_calculation->proc~binomial proc~combine combine proc~run_fragmented_calculation->proc~combine proc~create_monomer_list create_monomer_list proc~run_fragmented_calculation->proc~create_monomer_list proc~error_get_message error_t%error_get_message proc~run_fragmented_calculation->proc~error_get_message proc~generate_fragment_list generate_fragment_list proc~run_fragmented_calculation->proc~generate_fragment_list proc~get_nfrags get_nfrags proc~run_fragmented_calculation->proc~get_nfrags proc~gmbe_enumerate_pie_terms gmbe_enumerate_pie_terms proc~run_fragmented_calculation->proc~gmbe_enumerate_pie_terms proc~sort_fragments_by_size sort_fragments_by_size proc~run_fragmented_calculation->proc~sort_fragments_by_size run_distributed run_distributed proc~run_fragmented_calculation->run_distributed run_serial run_serial proc~run_fragmented_calculation->run_serial proc~run_unfragmented_calculation->info proc~run_unfragmented_calculation->to_char proc~run_unfragmented_calculation->verbose interface~distributed_unfragmented_hessian distributed_unfragmented_hessian proc~run_unfragmented_calculation->interface~distributed_unfragmented_hessian interface~unfragmented_calculation unfragmented_calculation proc~run_unfragmented_calculation->interface~unfragmented_calculation proc~calc_type_to_string calc_type_to_string proc~run_unfragmented_calculation->proc~calc_type_to_string proc~write_json_output->error proc~write_gmbe_pie_json_impl write_gmbe_pie_json_impl proc~write_json_output->proc~write_gmbe_pie_json_impl proc~write_mbe_breakdown_json_impl write_mbe_breakdown_json_impl proc~write_json_output->proc~write_mbe_breakdown_json_impl proc~write_unfragmented_json_impl write_unfragmented_json_impl proc~write_json_output->proc~write_unfragmented_json_impl proc~write_vibrational_json_impl write_vibrational_json_impl proc~write_json_output->proc~write_vibrational_json_impl proc~distributed_unfragmented_hessian distributed_unfragmented_hessian interface~distributed_unfragmented_hessian->proc~distributed_unfragmented_hessian proc~unfragmented_calculation unfragmented_calculation interface~unfragmented_calculation->proc~unfragmented_calculation proc~apply_distance_screening->info proc~apply_distance_screening->to_char proc~fragment_should_be_screened fragment_should_be_screened proc~apply_distance_screening->proc~fragment_should_be_screened proc~combine_util combine_util proc~combine->proc~combine_util to_lower to_lower proc~element_symbol_to_number->to_lower to_upper to_upper proc~element_symbol_to_number->to_upper proc~generate_fragment_list->proc~combine proc~get_nfrags->proc~binomial proc~gmbe_enumerate_pie_terms->info proc~gmbe_enumerate_pie_terms->proc~error_has_error proc~gmbe_enumerate_pie_terms->to_char proc~gmbe_enumerate_pie_terms->proc~error_set atom_list atom_list proc~gmbe_enumerate_pie_terms->atom_list proc~compute_polymer_atoms compute_polymer_atoms proc~gmbe_enumerate_pie_terms->proc~compute_polymer_atoms proc~dfs_pie_accumulate dfs_pie_accumulate proc~gmbe_enumerate_pie_terms->proc~dfs_pie_accumulate proc~initialize_fragmented_system->proc~error_add_context proc~initialize_fragmented_system->proc~error_has_error proc~initialize_fragmented_system->proc~element_symbol_to_number proc~initialize_fragmented_system->proc~to_bohr proc~check_fragment_overlap check_fragment_overlap proc~initialize_fragmented_system->proc~check_fragment_overlap proc~sort_fragments_by_size->info sort_index sort_index proc~sort_fragments_by_size->sort_index proc~write_gmbe_pie_json_impl->error proc~write_gmbe_pie_json_impl->info proc~write_gmbe_pie_json_impl->proc~get_output_json_filename proc~write_gmbe_pie_json_impl->destroy add add proc~write_gmbe_pie_json_impl->add create_array create_array proc~write_gmbe_pie_json_impl->create_array create_object create_object proc~write_gmbe_pie_json_impl->create_object initialize initialize proc~write_gmbe_pie_json_impl->initialize proc~get_basename get_basename proc~write_gmbe_pie_json_impl->proc~get_basename proc~write_mbe_breakdown_json_impl->error proc~write_mbe_breakdown_json_impl->info proc~write_mbe_breakdown_json_impl->proc~get_output_json_filename proc~write_mbe_breakdown_json_impl->warning proc~write_mbe_breakdown_json_impl->destroy proc~write_mbe_breakdown_json_impl->add proc~write_mbe_breakdown_json_impl->create_array proc~write_mbe_breakdown_json_impl->create_object proc~write_mbe_breakdown_json_impl->initialize proc~write_mbe_breakdown_json_impl->proc~get_basename proc~get_frag_level_name get_frag_level_name proc~write_mbe_breakdown_json_impl->proc~get_frag_level_name proc~write_unfragmented_json_impl->error proc~write_unfragmented_json_impl->info proc~write_unfragmented_json_impl->proc~get_output_json_filename proc~write_unfragmented_json_impl->destroy proc~write_unfragmented_json_impl->add proc~write_unfragmented_json_impl->create_object proc~write_unfragmented_json_impl->initialize proc~write_unfragmented_json_impl->proc~get_basename proc~write_vibrational_json_impl->error proc~write_vibrational_json_impl->info proc~write_vibrational_json_impl->proc~get_output_json_filename proc~write_vibrational_json_impl->destroy proc~write_vibrational_json_impl->add proc~write_vibrational_json_impl->create_object proc~write_vibrational_json_impl->initialize proc~write_vibrational_json_impl->proc~get_basename proc~xtb_get_solvation_info->to_char proc~xtb_has_solvation xtb_config_t%xtb_has_solvation proc~xtb_get_solvation_info->proc~xtb_has_solvation proc~check_fragment_overlap->to_char proc~check_fragment_overlap->proc~error_set proc~combine_util->proc~combine_util proc~dfs_pie_accumulate->proc~error_has_error proc~dfs_pie_accumulate->proc~dfs_pie_accumulate new_clique new_clique proc~dfs_pie_accumulate->new_clique proc~atom_sets_equal atom_sets_equal proc~dfs_pie_accumulate->proc~atom_sets_equal proc~grow_pie_storage grow_pie_storage proc~dfs_pie_accumulate->proc~grow_pie_storage proc~intersect_atom_lists intersect_atom_lists proc~dfs_pie_accumulate->proc~intersect_atom_lists test_intersect test_intersect proc~dfs_pie_accumulate->test_intersect proc~distributed_unfragmented_hessian->barrier interface~hessian_coordinator hessian_coordinator proc~distributed_unfragmented_hessian->interface~hessian_coordinator interface~hessian_worker hessian_worker proc~distributed_unfragmented_hessian->interface~hessian_worker proc~calculate_monomer_distance calculate_monomer_distance proc~fragment_should_be_screened->proc~calculate_monomer_distance proc~get_next_combination get_next_combination proc~fragment_should_be_screened->proc~get_next_combination proc~unfragmented_calculation->error proc~unfragmented_calculation->info proc~unfragmented_calculation->proc~error_get_full_trace proc~unfragmented_calculation->proc~error_has_error proc~unfragmented_calculation->to_char proc~unfragmented_calculation->proc~error_get_message cart_disp cart_disp proc~unfragmented_calculation->cart_disp configuration configuration proc~unfragmented_calculation->configuration eigenvalues eigenvalues proc~unfragmented_calculation->eigenvalues fc_mdyne fc_mdyne proc~unfragmented_calculation->fc_mdyne force_constants force_constants proc~unfragmented_calculation->force_constants frequencies frequencies proc~unfragmented_calculation->frequencies interface~do_fragment_work do_fragment_work proc~unfragmented_calculation->interface~do_fragment_work ir_intensities ir_intensities proc~unfragmented_calculation->ir_intensities proc~check_duplicate_atoms check_duplicate_atoms proc~unfragmented_calculation->proc~check_duplicate_atoms proc~compute_thermochemistry compute_thermochemistry proc~unfragmented_calculation->proc~compute_thermochemistry proc~compute_vibrational_analysis compute_vibrational_analysis proc~unfragmented_calculation->proc~compute_vibrational_analysis proc~compute_vibrational_frequencies compute_vibrational_frequencies proc~unfragmented_calculation->proc~compute_vibrational_frequencies proc~energy_total energy_t%energy_total proc~unfragmented_calculation->proc~energy_total proc~fragment_compute_nelec physical_fragment_t%fragment_compute_nelec proc~unfragmented_calculation->proc~fragment_compute_nelec proc~print_vibrational_analysis print_vibrational_analysis proc~unfragmented_calculation->proc~print_vibrational_analysis proc~result_destroy calculation_result_t%result_destroy proc~unfragmented_calculation->proc~result_destroy projected_hessian projected_hessian proc~unfragmented_calculation->projected_hessian reduced_masses reduced_masses proc~unfragmented_calculation->reduced_masses

Called by

proc~~run_multi_molecule_calculations~~CalledByGraph proc~run_multi_molecule_calculations run_multi_molecule_calculations program~main main program~main->proc~run_multi_molecule_calculations

Variables

Type Visibility Attributes Name Initial
integer, private :: color
type(driver_config_t), private :: config
type(error_t), private :: error
logical, private :: has_fragmented_molecules
integer, private :: imol
character(len=256), private, allocatable :: individual_json_files(:)
character(len=:), private, allocatable :: mol_name
type(resources_t), private :: mol_resources
integer, private :: molecules_processed
integer, private :: my_rank
integer, private :: num_ranks
type(system_geometry_t), private :: sys_geom

Source Code

   subroutine run_multi_molecule_calculations(resources, mqc_config)
      !! Run calculations for multiple molecules with MPI parallelization
      !! Each molecule is independent, so assign one molecule per rank
      use mqc_config_parser, only: mqc_config_t
      use mqc_config_adapter, only: config_to_system_geometry
      use mqc_error, only: error_t
      use mqc_io_helpers, only: set_molecule_suffix, get_output_json_filename
      use mqc_json, only: merge_multi_molecule_json

      type(resources_t), intent(in) :: resources
      type(mqc_config_t), intent(in) :: mqc_config

      type(driver_config_t) :: config
      type(system_geometry_t) :: sys_geom
      type(resources_t) :: mol_resources
      type(error_t) :: error
      integer :: imol, my_rank, num_ranks, color
      integer :: molecules_processed
      character(len=:), allocatable :: mol_name
      logical :: has_fragmented_molecules
      character(len=256), allocatable :: individual_json_files(:)

      my_rank = resources%mpi_comms%world_comm%rank()
      num_ranks = resources%mpi_comms%world_comm%size()

      ! Allocate array to track individual JSON files for merging
      allocate (individual_json_files(mqc_config%nmol))

      ! Check if any molecules have fragments (nlevel > 0)
      has_fragmented_molecules = .false.
      do imol = 1, mqc_config%nmol
         if (mqc_config%molecules(imol)%nfrag > 0) then
            has_fragmented_molecules = .true.
            exit
         end if
      end do

      if (my_rank == 0) then
         call logger%info(" ")
         call logger%info("============================================")
         call logger%info("Multi-molecule mode: "//to_char(mqc_config%nmol)//" molecules")
         call logger%info("MPI ranks: "//to_char(num_ranks))
         if (has_fragmented_molecules) then
            call logger%info("Mode: Sequential execution (fragmented molecules detected)")
            call logger%info("  Each molecule will use all "//to_char(num_ranks)//" rank(s) for its calculation")
         else if (num_ranks == 1) then
            call logger%info("Mode: Sequential execution (single rank)")
         else if (num_ranks > mqc_config%nmol) then
            call logger%info("Mode: Parallel execution (one molecule per rank)")
            call logger%info("Note: More ranks than molecules - ranks "//to_char(mqc_config%nmol)// &
                             " to "//to_char(num_ranks - 1)//" will be idle")
         else
            call logger%info("Mode: Parallel execution (one molecule per rank)")
         end if
         call logger%info("============================================")
         call logger%info(" ")
      end if

      ! Determine execution mode:
      ! 1. Sequential: Single rank OR fragmented molecules (each molecule needs all ranks)
      ! 2. Parallel: Multiple ranks AND unfragmented molecules (distribute molecules across ranks)
      molecules_processed = 0

      if (num_ranks == 1 .or. has_fragmented_molecules) then
         ! Sequential mode: process all molecules one after another
         ! Each molecule uses all available ranks for its calculation
         do imol = 1, mqc_config%nmol
            ! Determine molecule name for logging
            if (allocated(mqc_config%molecules(imol)%name)) then
               mol_name = mqc_config%molecules(imol)%name
            else
               mol_name = "molecule_"//to_char(imol)
            end if

            if (my_rank == 0) then
               call logger%info(" ")
               call logger%info("--------------------------------------------")
               call logger%info("Processing molecule "//to_char(imol)//"/"//to_char(mqc_config%nmol)//": "//mol_name)
               call logger%info("--------------------------------------------")
            end if

            ! Convert to driver configuration for this molecule
            call config_to_driver(mqc_config, config, molecule_index=imol)

            ! Convert geometry for this molecule
            call config_to_system_geometry(mqc_config, sys_geom, error, molecule_index=imol)
            if (error%has_error()) then
               call error%add_context("mqc_driver:run_multi_molecule_calculation")
               if (my_rank == 0) then
                  call logger%error("Error converting geometry for "//mol_name//": "//error%get_full_trace())
               end if
               call abort_comm(resources%mpi_comms%world_comm, 1)
            end if

            ! Set output filename suffix for this molecule
            call set_molecule_suffix("_"//trim(mol_name))

            ! Run calculation for this molecule
            call run_calculation(resources, config, sys_geom, mqc_config%molecules(imol)%bonds)

            ! Track the JSON filename for later merging
            individual_json_files(imol) = get_output_json_filename()

            ! Clean up for this molecule
            call sys_geom%destroy()

            if (my_rank == 0) then
               call logger%info("Completed molecule "//to_char(imol)//"/"//to_char(mqc_config%nmol)//": "//mol_name)
            end if
            molecules_processed = molecules_processed + 1
         end do
      else
         ! Multiple ranks: distribute molecules across ranks in round-robin fashion
         molecules_processed = 0
         do imol = 1, mqc_config%nmol
            ! This rank processes molecules where (imol - 1) mod num_ranks == my_rank
            if (mod(imol - 1, num_ranks) == my_rank) then
               ! Determine molecule name for logging
               if (allocated(mqc_config%molecules(imol)%name)) then
                  mol_name = mqc_config%molecules(imol)%name
               else
                  mol_name = "molecule_"//to_char(imol)
               end if

               call logger%info(" ")
               call logger%info("--------------------------------------------")
               call logger%info("Rank "//to_char(my_rank)//": Processing molecule "//to_char(imol)// &
                                "/"//to_char(mqc_config%nmol)//": "//mol_name)
               call logger%info("--------------------------------------------")

               ! Convert to driver configuration for this molecule
               call config_to_driver(mqc_config, config, molecule_index=imol)

               ! Convert geometry for this molecule
               call config_to_system_geometry(mqc_config, sys_geom, error, molecule_index=imol)
               if (error%has_error()) then
                  call error%add_context("mqc_driver:run_multi_molecule_calculation")
  call logger%error("Rank "//to_char(my_rank)//": Error converting geometry for "//mol_name//": "//error%get_full_trace())
                  call abort_comm(resources%mpi_comms%world_comm, 1)
               end if

               ! Set output filename suffix for this molecule
               call set_molecule_suffix("_"//trim(mol_name))

               ! Run calculation for this molecule (all ranks write JSON in parallel mode)
               call run_calculation(resources, config, sys_geom, mqc_config%molecules(imol)%bonds, &
                                    all_ranks_write_json=.true.)

               ! Track the JSON filename for later merging
               individual_json_files(imol) = get_output_json_filename()

               ! Clean up for this molecule
               call sys_geom%destroy()

               call logger%info("Rank "//to_char(my_rank)//": Completed molecule "//to_char(imol)// &
                                "/"//to_char(mqc_config%nmol)//": "//mol_name)

               molecules_processed = molecules_processed + 1
            end if
         end do

         if (molecules_processed == 0) then
            ! Idle rank - no molecules assigned
            call logger%verbose("Rank "//to_char(my_rank)//": No molecules assigned (idle)")
         end if
      end if

      ! Synchronize all ranks
      call resources%mpi_comms%world_comm%barrier()

      ! In parallel execution, rank 0 needs to reconstruct all JSON filenames for merging
      ! since each rank only populated its own entry
      if (my_rank == 0 .and. num_ranks > 1 .and. .not. has_fragmented_molecules) then
         ! Rank 0 constructs filenames for all molecules
         do imol = 1, mqc_config%nmol
            ! Get molecule name
            if (allocated(mqc_config%molecules(imol)%name)) then
               mol_name = mqc_config%molecules(imol)%name
            else
               mol_name = "molecule_"//to_char(imol)
            end if
            ! Construct JSON filename pattern: output_<basename>_<molname>.json
            ! This mirrors what get_output_json_filename() returns after set_molecule_suffix()
            call set_molecule_suffix("_"//trim(mol_name))
            individual_json_files(imol) = get_output_json_filename()
         end do
      end if

      ! Merge individual JSON files into one combined file (rank 0 only)
      if (my_rank == 0) then
         call merge_multi_molecule_json(individual_json_files, mqc_config%nmol)
      end if

      if (my_rank == 0) then
         call logger%info(" ")
         call logger%info("============================================")
         call logger%info("All "//to_char(mqc_config%nmol)//" molecules completed")
         if (has_fragmented_molecules) then
            call logger%info("Execution: Sequential (each molecule used all ranks)")
         else if (num_ranks == 1) then
            call logger%info("Execution: Sequential (single rank)")
         else if (num_ranks > mqc_config%nmol) then
           call logger%info("Execution: Parallel (active ranks: "//to_char(mqc_config%nmol)//"/"//to_char(num_ranks)//")")
         else
            call logger%info("Execution: Parallel (all ranks active)")
         end if
         call logger%info("============================================")
      end if

   end subroutine run_multi_molecule_calculations