.. _User defined rate constants: User-Defined Rate Constants ########################### This tutorial extends the :ref:`Rate constants` tutorial. That one showed how to use every type of rate constant except the user-defined ones. The difference is that a user-defined rate constant must be updated by the user, whereas the other rate constants update in response to a changing state. Internal to MICM, user-defined rate constants provide the ability to represent processes like emissions, loss, and photolysis that have rate constants that are calculated externally from MICM. These can be static and the same for each time step, or dynamically updated during the simulation. To show how this works, we'll add one photolysis reaction, one first-order loss reaction, and one emission .. math:: A &\longrightarrow B, &k_{1, \mathrm{arrhenius}} \\ B &\longrightarrow C (\mathrm{alkoxy\ products}) + D (\mathrm{nitrate\ products}), &k_{2, \mathrm{branched}} \\ C &\longrightarrow E, &k_{3, \mathrm{surface}} \\ D &\longrightarrow 2F, &k_{4, \mathrm{ternary\ chemical\ activation}} \\ 2E &\longrightarrow G, &k_{5, \mathrm{troe}} \\ F &\longrightarrow G, &k_{6, \mathrm{tunneling}} \\ C &\longrightarrow G, &k_{7, \mathrm{photolysis}} \\ &\longrightarrow A, &k_{8, \mathrm{emission}} \\ B &\longrightarrow, &k_{9, \mathrm{loss}} \\ If you're looking for a copy and paste, choose the appropriate tab below and be on your way! Otherwise, stick around for a line by line explanation. .. tab-set:: .. tab-item:: Build the Mechanism with the API .. literalinclude:: ../../../test/tutorial/test_rate_constants_user_defined_by_hand.cpp :language: cpp .. tab-item:: OpenAtmos Configuration reading .. raw:: html
.. literalinclude:: ../../../test/tutorial/test_rate_constants_user_defined_with_config.cpp :language: cpp Line-by-line explanation ------------------------ Adding the custom rate constant is quite simple. Include the header file: .. code-block:: diff #include #include #include #include #include #include + #include #include Then setup the reaction which will use this rate constant: .. tab-set:: .. tab-item:: Build the Mechanism with the API .. code-block:: diff Process r7 = Process::Create() .SetReactants({ f }) .SetProducts({ Yields(g, 1) }) .SetRateConstant(TunnelingRateConstant({ .A_ = 1.2, .B_ = 2.3, .C_ = 302.3 })) .SetPhase(gas_phase); + Process r8 = Process::Create() + .SetReactants({ c }) + .SetProducts({ Yields(g, 1) }) + .SetRateConstant(UserDefinedRateConstant({.label_="my rate"})) + .SetPhase(gas_phase); + Process r9 = Process::Create() + .SetProducts({ Yields(a, 1) }) + .SetRateConstant(UserDefinedRateConstant({.label_="my emission rate"})) + .SetPhase(gas_phase); + Process r10 = Process::Create() + .SetReactants({ b }) + .SetRateConstant(UserDefinedRateConstant({.label_="my loss rate"})) + .SetPhase(gas_phase); auto chemical_system = System(micm::SystemParameters{ .gas_phase_ = gas_phase }); - auto reactions = std::vector{ r1, r2, r3, r4, r5, r6, r7 }; + auto reactions = std::vector{ r1, r2, r3, r4, r5, r6, r7, r8, r9, r10 }; .. tab-item:: OpenAtmos Configuration reading In this case, you only need to add the configuration to the reactions.json file in the configuration directory. .. code-block:: diff + { + "type": "PHOTOLYSIS", + "reactants": { + "C": {} + }, + "products": { + "G": {} + }, + "MUSICA name": "my photolysis rate" + }, + { + "type": "FIRST_ORDER_LOSS", + "species": "B", + "MUSICA name": "my loss rate" + }, + { + "type": "EMISSION", + "species": "A", + "MUSICA name": "my emission rate" + } Finally, set and upate the rate constants as needed: .. tab-set:: .. tab-item:: Build the Mechanism with the API .. code-block:: diff + double photo_rate = 1e-10; + double emission_rate = 1e-20; + double loss = emission_rate * 1e-3; + // these rates are constant through the simulation + state.SetCustomRateParameter("my emission rate", emission_rate); + state.SetCustomRateParameter("my loss rate", loss); // solve for ten iterations for (int i = 0; i < 10; ++i) { // Depending on how stiff the system is // the solver integration step may not be able to solve for the full time step // so we need to track how much time the solver was able to integrate for and continue // solving until we finish double elapsed_solve_time = 0; + state.SetCustomRateParameter("my photolysis rate", photo_rate); while (elapsed_solve_time < time_step) { auto result = solver.Solve(time_step - elapsed_solve_time, state); elapsed_solve_time = result.final_time_; state.variables_[0] = result.result_.AsVector(); } print_state(time_step * (i + 1), state); + photo_rate *= 1.5; } .. tab-item:: OpenAtmos Configuration reading In this case, you only need to add the configuration to the reactions.json file in the configuration directory. When reading in from a configuration file, the loss, emissions, and photolysis rates are prefixed with ``LOSS.``, ``EMIS.``, and ``PHOTO.``. This differs slightly from defining the API by hand. .. code-block:: diff + double photo_rate = 1e-10; + double emission_rate = 1e-20; + double loss = emission_rate * 1e-3; + // these rates are constant through the simulation + state.SetCustomRateParameter("EMIS.my emission rate", emission_rate); + state.SetCustomRateParameter("LOSS.my loss rate", loss); // solve for ten iterations for (int i = 0; i < 10; ++i) { // Depending on how stiff the system is // the solver integration step may not be able to solve for the full time step // so we need to track how much time the solver was able to integrate for and continue // solving until we finish double elapsed_solve_time = 0; + state.SetCustomRateParameter("PHOTO.my photolysis rate", photo_rate); while (elapsed_solve_time < time_step) { auto result = solver.Solve(time_step - elapsed_solve_time, state); elapsed_solve_time = result.final_time_; state.variables_[0] = result.result_.AsVector(); } print_state(time_step * (i + 1), state); + photo_rate *= 1.5; } And this is final output. Notice that the concentration of G ends up much higher than in the :ref:`Rate constants` tutorial's result. .. csv-table:: The Change of Concentration with Time :header: "time", "A", "B", "C", "D", "E", "F", "G" :widths: 10, 15, 15, 15, 15, 15, 15, 15 "0", "1.00e+00", "0.00e+00", "0.00e+00", "0.00e+00", "0.00e+00", "0.00e+00", "0.00e+00" "500", "3.18e-09", "3.66e-09", "9.83e-01", "3.88e-14", "1.41e-03", "2.02e-13", "7.92e-03" "1000", "1.14e-14", "1.31e-14", "9.66e-01", "1.39e-19", "1.40e-03", "7.24e-19", "1.64e-02" "1500", "7.27e-20", "6.40e-20", "9.49e-01", "6.53e-25", "1.39e-03", "3.19e-24", "2.48e-02" "2000", "3.17e-20", "1.70e-20", "9.33e-01", "1.55e-25", "1.38e-03", "5.92e-25", "3.30e-02" "2500", "3.17e-20", "1.70e-20", "9.17e-01", "1.55e-25", "1.37e-03", "5.92e-25", "4.11e-02" "3000", "3.17e-20", "1.70e-20", "9.01e-01", "1.55e-25", "1.36e-03", "5.92e-25", "4.90e-02" "3500", "3.17e-20", "1.70e-20", "8.85e-01", "1.55e-25", "1.34e-03", "5.92e-25", "5.68e-02" "4000", "3.17e-20", "1.70e-20", "8.70e-01", "1.55e-25", "1.33e-03", "5.92e-25", "6.44e-02" "4500", "3.17e-20", "1.70e-20", "8.55e-01", "1.55e-25", "1.32e-03", "5.92e-25", "7.20e-02" "5000", "3.17e-20", "1.70e-20", "8.40e-01", "1.55e-25", "1.31e-03", "5.92e-25", "7.94e-02"