Chapter 4#
Multiple Mechanisms in the Same Fortran Host Application#
In real atmospheric models, different parts of the model may require different
chemical mechanisms. For example, an urban chemistry scheme might co-exist with
a remote-troposphere scheme, or a gas-phase solver might run alongside an aerosol
chemistry solver. MUSICA supports this naturally: you can create as many
independent micm_t solver instances as you need, each with its own mechanism,
and advance all of them inside the same Fortran host application.
In this example, we create two independent solvers:
Solver 1 loads the six-species analytical mechanism from
configs/v0/analytical. This mechanism has six species (A,B,C,D,E,F), two Arrhenius reactions, and two user-defined rate-constant reactions.Solver 2 loads the three-species analytical mechanism from
configs/v1/analytical/config.json. This mechanism has three species (A,B,C) and two Arrhenius reactions.
Both solvers share the same physical conditions (temperature and pressure) but maintain completely independent state vectors. Both are advanced through the same time step in the same simulation loop.
Save the following to test_multiple_mechanisms.F90:
! Copyright (C) 2023-2026 University Corporation for Atmospheric Research ! SPDX-License-Identifier: Apache-2.0 ! ! Tutorial Chapter 4: Create solvers for multiple mechanisms that run in the same host application ! ! This program demonstrates how two independent MICM solver instances, each ! configured for a different chemical mechanism, can coexist and run within ! the same Fortran host application. Each solver has its own state, its own ! species set, and its own reaction set. They are advanced through time inside ! the same simulation loop, exactly as they would be in a real atmospheric model ! that couples different chemistry modules. ! ! Mechanism 1 – configs/v0/analytical ! Six species (A, B, C, D, E, F), two Arrhenius reactions, and two ! user-defined-rate reactions. ! ! Mechanism 2 – configs/v1/analytical/config.json ! Three species (A, B, C) and two pure Arrhenius reactions. ! ! Both solvers use the standard-ordered Rosenbrock method. program test_multiple_mechanisms use, intrinsic :: iso_c_binding use iso_fortran_env, only: real64 use musica_util, only: assert, error_t, string_t, mapping_t use musica_micm, only: micm_t, solver_stats_t use musica_micm, only: RosenbrockStandardOrder use musica_state, only: conditions_t, state_t #define ASSERT( expr ) call assert( expr, __FILE__, __LINE__ ) #define ASSERT_EQ( a, b ) call assert( a == b, __FILE__, __LINE__ ) implicit none call multiple_mechanisms_example() contains !> Runs two independent MICM solvers for different mechanisms in one application subroutine multiple_mechanisms_example() ! --- Mechanism 1 variables -------------------------------------------------- character(len=256) :: config_path_1 type(micm_t), pointer :: micm_1 type(state_t), pointer :: state_1 integer :: num_species_1 ! --- Mechanism 2 variables -------------------------------------------------- character(len=256) :: config_path_2 type(micm_t), pointer :: micm_2 type(state_t), pointer :: state_2 integer :: num_species_2 ! --- Shared simulation variables -------------------------------------------- real(real64), parameter :: GAS_CONSTANT = 8.31446261815324_real64 ! J mol-1 K-1 real(real64) :: time_step real(real64) :: temperature, pressure, air_density type(string_t) :: solver_state_str type(solver_stats_t) :: solver_stats type(error_t) :: error integer :: i, num_grid_cells num_grid_cells = 1 time_step = 200.0_real64 ! seconds temperature = 272.5_real64 ! K pressure = 101253.3_real64 ! Pa air_density = pressure / (GAS_CONSTANT * temperature) ! ========================================================================= ! Step 1 – Create Solver 1 (six-species analytical mechanism) ! ========================================================================= config_path_1 = "configs/v0/analytical" write(*,*) "Creating Solver 1 from: ", trim(config_path_1) micm_1 => micm_t(config_path_1, RosenbrockStandardOrder, error) ASSERT( error%is_success() ) state_1 => micm_1%get_state(num_grid_cells, error) ASSERT( error%is_success() ) ! Report the species in Mechanism 1 num_species_1 = state_1%species_ordering%size() write(*,'(A,I0,A)') " Mechanism 1 has ", num_species_1, " species:" do i = 1, num_species_1 write(*,'(A,A,A,I0)') " ", trim(state_1%species_ordering%name(i)), & " index=", state_1%species_ordering%index(i) end do ! ========================================================================= ! Step 2 – Create Solver 2 (three-species v1 analytical mechanism) ! ========================================================================= config_path_2 = "configs/v1/analytical/config.json" write(*,*) "Creating Solver 2 from: ", trim(config_path_2) micm_2 => micm_t(config_path_2, RosenbrockStandardOrder, error) ASSERT( error%is_success() ) state_2 => micm_2%get_state(num_grid_cells, error) ASSERT( error%is_success() ) ! Report the species in Mechanism 2 num_species_2 = state_2%species_ordering%size() write(*,'(A,I0,A)') " Mechanism 2 has ", num_species_2, " species:" do i = 1, num_species_2 write(*,'(A,A,A,I0)') " ", trim(state_2%species_ordering%name(i)), & " index=", state_2%species_ordering%index(i) end do ! The two mechanisms are independent: they may share species names but each ! solver manages its own internal state. ASSERT( num_species_1 /= num_species_2 ) ! ========================================================================= ! Step 3 – Set initial conditions for both states ! ========================================================================= ! State 1 conditions state_1%conditions(1)%temperature = temperature state_1%conditions(1)%pressure = pressure state_1%conditions(1)%air_density = air_density associate( var_stride => state_1%species_strides%variable ) do i = 1, num_species_1 state_1%concentrations(i * var_stride) = 1.0_real64 end do end associate ! State 1 user-defined rate parameters (two reactions in v0/analytical) associate( var_stride => state_1%rate_parameters_strides%variable ) state_1%rate_parameters(1) = 0.001_real64 state_1%rate_parameters(1 + var_stride) = 0.002_real64 end associate ! State 2 conditions state_2%conditions(1)%temperature = temperature state_2%conditions(1)%pressure = pressure state_2%conditions(1)%air_density = air_density associate( var_stride => state_2%species_strides%variable ) do i = 1, num_species_2 state_2%concentrations(i * var_stride) = 1.0_real64 end do end associate ! ========================================================================= ! Step 4 – Advance both solvers for one time step in the same loop ! ========================================================================= write(*,*) "" write(*,*) "Advancing both mechanisms by one time step..." call micm_1%solve(time_step, state_1, solver_state_str, solver_stats, error) ASSERT( error%is_success() ) write(*,*) " Solver 1 result: ", trim(solver_state_str%value_) call micm_2%solve(time_step, state_2, solver_state_str, solver_stats, error) ASSERT( error%is_success() ) write(*,*) " Solver 2 result: ", trim(solver_state_str%value_) ! ========================================================================= ! Step 5 – Print final concentrations for both mechanisms ! ========================================================================= write(*,*) "" write(*,*) "Final concentrations – Mechanism 1:" associate( var_stride => state_1%species_strides%variable ) do i = 1, num_species_1 write(*,'(A,A,A,F12.6)') " ", & trim(state_1%species_ordering%name(i)), " = ", & state_1%concentrations(i * var_stride) end do end associate write(*,*) "" write(*,*) "Final concentrations – Mechanism 2:" associate( var_stride => state_2%species_strides%variable ) do i = 1, num_species_2 write(*,'(A,A,A,F12.6)') " ", & trim(state_2%species_ordering%name(i)), " = ", & state_2%concentrations(i * var_stride) end do end associate write(*,*) "" write(*,*) "Both mechanisms solved successfully in the same host application!" ! ========================================================================= ! Clean up ! ========================================================================= deallocate(state_1) deallocate(micm_1) deallocate(state_2) deallocate(micm_2) end subroutine multiple_mechanisms_example end program test_multiple_mechanisms
Key concepts illustrated by this example:
Independent solver instances
Each micm_t pointer is a self-contained solver. Creating a second solver does
not affect the first:
micm_1 => micm_t(config_path_1, RosenbrockStandardOrder, error)
micm_2 => micm_t(config_path_2, RosenbrockStandardOrder, error)
Independent state objects
Each solver creates its own state_t object that holds concentrations and
environmental conditions for that mechanism only:
state_1 => micm_1%get_state(num_grid_cells, error)
state_2 => micm_2%get_state(num_grid_cells, error)
Because state_1 and state_2 were created from different solvers, they may
have different numbers of species, different internal strides, and different
user-defined rate-parameter arrays.
Simultaneous time-stepping
Within the same simulation loop, both mechanisms are advanced by calling each
solver’s solve method:
call micm_1%solve(time_step, state_1, solver_state_str, solver_stats, error)
call micm_2%solve(time_step, state_2, solver_state_str, solver_stats, error)
The two calls are completely independent: they can even be re-ordered or parallelized without affecting the results of either mechanism.
Memory management
Each pointer must be deallocated independently when it is no longer needed:
deallocate(state_1)
deallocate(micm_1)
deallocate(state_2)
deallocate(micm_2)
To build and run the example, follow the same build instructions as in Chapter 1. The compilation command would be:
gfortran -o test_multiple_mechanisms test_multiple_mechanisms.F90 \
-I<MUSICA_DIR>/include -L<MUSICA_DIR>/lib64 -lmusica-fortran -lmusica -lstdc++ -lyaml-cpp
Run it with:
$ ./test_multiple_mechanisms
Creating Solver 1 from: configs/v0/analytical
Mechanism 1 has 6 species:
F index=6
E index=4
B index=2
C index=5
D index=3
A index=1
Creating Solver 2 from: configs/v1/analytical/config.json
Mechanism 2 has 3 species:
B index=2
C index=3
A index=1
Advancing both mechanisms by one time step...
Solver 1 result: Converged
Solver 2 result: Converged
Final concentrations – Mechanism 1:
F = 0.382238
E = 1.467040
B = 0.818730
C = 0.818730
D = 1.150722
A = 1.362541
Final concentrations – Mechanism 2:
B = 0.449202
C = 0.809056
A = 1.741742
Both mechanisms solved successfully in the same host application!
The final concentrations differ because the two mechanisms have different species, reactions, and rate expressions.