Skip to content

How-To: Integrating MKPP Generated Mechanisms into Host Models

This guide describes how to include MKPP generated chemical mechanism solvers into Fortran, C, and C++ host models (such as UFS, WRF-Chem, CESM, or standalone CCPP suites) with zero Python dependencies at host model compile or run time.


1. Ahead-Of-Time (AOT) Code Generation

During the preprocessing phase, run MKPP with --host-api to emit the AOT C++, C, and Fortran interface files:

python -m mkpp generate --mechanism example_mechanism.yaml --out-dir src/solvers --host-api

This generates: - mkpp.h (C11 ABI header with opaque context handles and status codes) - mkpp_mod.f90 (Fortran 2018 module wrapping iso_c_binding) - mkpp.hpp (Idiomatic C++17 RAII class wrapper mkpp::MechanismContext) - mkpp_c_api.cpp (C ABI implementation wrapping Kokkos solver views) - <mechanism_name>.hpp (Kokkos GPU/CPU ODE solver kernels)


2. CMake Integration

Host models link against the generated mechanism code using standard CMake targets exported by mkpp_host:

In your host model CMakeLists.txt:

cmake_minimum_required(VERSION 3.24)
project(my_host_model CXX Fortran C)

# Locate MKPP host API targets
find_package(MKPP REQUIRED COMPONENTS mkpp_host)

# Fortran host driver executable
add_executable(my_fortran_model main.f90)
target_link_libraries(my_fortran_model PRIVATE MKPP::mkpp_host)

# C host driver executable
add_executable(my_c_model main.c)
target_link_libraries(my_c_model PRIVATE MKPP::mkpp_host)

# C++ host driver executable
add_executable(my_cpp_model main.cpp)
target_link_libraries(my_cpp_model PRIVATE MKPP::mkpp_host)

3. Multi-Language Usage Examples

Fortran Host Model (main.f90)

program main
  use iso_c_binding
  use mkpp_mod
  implicit none

  integer, parameter :: num_cells = 100
  type(mkpp_handle_t) :: handle
  real(c_double), target :: conc(num_cells, MKPP_NUM_SPECIES)
  real(c_double), target :: temp(num_cells), pres(num_cells), rho(num_cells)
  real(c_double), target :: photo(num_cells, MKPP_NUM_PHOTOLYSIS)
  integer :: status, o3_idx

  ! 1. Create and initialize handle
  call mkpp_create_handle(handle, num_cells, status)
  call mkpp_initialize(handle, status)

  ! 2. Bind host model arrays zero-copy (Column-Major order)
  call mkpp_set_state_ptrs(handle, conc, temp, pres, rho, status)
  call mkpp_set_photolysis_ptrs(handle, photo, status)

  ! 3. Look up species index dynamically by name
  call mkpp_get_species_index(handle, "O3", o3_idx, status)

  ! 4. Step chemistry integration
  call mkpp_integrate(handle, 60.0d0, status)

  ! 5. Teardown context handle
  call mkpp_destroy_handle(handle, status)
end program main
  • Explanation: Fortran 2D column-major arrays conc(num_cells, num_species) pass directly to c_loc without copying memory, mapping natively to C++/Kokkos LayoutLeft unmanaged views.

C Host Model (main.c)

#include <stdio.h>
#include <stdlib.h>
#include <mkpp.h>

int main(void) {
    size_t num_cells = 100;
    mkpp_handle_t handle = NULL;

    // 1. Create and initialize handle
    if (mkpp_create_handle(&handle, num_cells) != MKPP_SUCCESS) {
        return 1;
    }
    if (mkpp_initialize(handle) != MKPP_SUCCESS) {
        return 1;
    }

    // 2. Allocate and bind host state buffers
    double* conc = (double*)calloc(num_cells * MKPP_NUM_SPECIES, sizeof(double));
    double* temp = (double*)malloc(num_cells * sizeof(double));
    double* pres = (double*)malloc(num_cells * sizeof(double));
    double* rho  = (double*)malloc(num_cells * sizeof(double));
    double* photo = (double*)calloc(num_cells * MKPP_NUM_PHOTOLYSIS, sizeof(double));

    mkpp_set_state_ptrs(handle, conc, temp, pres, rho);
    if (MKPP_NUM_PHOTOLYSIS > 0) {
        mkpp_set_photolysis_ptrs(handle, photo);
    }

    // 3. Step chemistry
    if (mkpp_integrate(handle, 60.0) != MKPP_SUCCESS) {
        char err[512];
        mkpp_get_last_error(handle, err, sizeof(err));
        printf("Integration error: %s\n", err);
    }

    // 4. Teardown
    mkpp_destroy_handle(handle);
    free(conc); free(temp); free(pres); free(rho); free(photo);
    return 0;
}

C++ Host Model (main.cpp)

#include <iostream>
#include <vector>
#include <mkpp.hpp>

int main() {
    std::size_t num_cells = 100;
    mkpp::MechanismContext ctx(num_cells);
    ctx.initialize();

    std::size_t num_species = ctx.getSpeciesCount();
    std::size_t num_photo = ctx.getPhotolysisCount();
    std::vector<double> conc(num_cells * num_species, 1.0e-9);
    std::vector<double> temp(num_cells, 298.15);
    std::vector<double> pres(num_cells, 101325.0);
    std::vector<double> rho(num_cells, 2.45e19);
    std::vector<double> photo(num_cells * num_photo, 1.0e-5);

    ctx.setStatePointers(conc.data(), temp.data(), pres.data(), rho.data());
    if (num_photo > 0) {
        ctx.setPhotolysisPointers(photo.data());
    }
    ctx.integrate(60.0);

    std::cout << "C++ integration completed successfully.\n";
    return 0;
}

4. Key Performance Guarantees

  • Zero Python Dependency: Host models compile and execute purely using standard C11, C++17, and Fortran 2018 toolchains.
  • Zero Heap Allocations in Integration Loop: Scratch solver memory is allocated once during mkpp_initialize().
  • Zero-Copy Memory Interoperability: Host model concentration and meteorological arrays bind unmanaged views directly across language boundaries.