logo Netzschleuder network catalog, repository, and centrifuge

History of mcm_atmospheric

The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.

[entries] Add the date when each entry was added, and remove icon_hash by Tiago de Paula Peixoto beff7c9
The dates are those of the commits that first added the entries' files
(2020-08-11 for the entries of the initial commit); renamed entries keep
the date of their first name.

Changed: none
diff --git a/entries/mcm_atmospheric/__init__.py b/entries/mcm_atmospheric/__init__.py
index e332e8c..f13bab1 100644
--- a/entries/mcm_atmospheric/__init__.py
+++ b/entries/mcm_atmospheric/__init__.py
@@ -58,7 +58,7 @@ citation = [('M. E. Jenkin, S. M. Saunders, M. J. Pilling, "The tropospheric deg
              'https://doi.org/10.1016/j.atmosenv.2008.07.034'),
             ('M. E. Jenkin, M. A. H. Khan, D. E. Shallcross, R. Bergström, D. Simpson, K. L. C. Murphy, A. R. Rickard, "The CRI v2.2 reduced degradation scheme for isoprene." Atmospheric Environment 212, 172-182 (2019)',
              'https://doi.org/10.1016/j.atmosenv.2019.05.055')]
-icon_hash = None
+date_added = '2026-10-31'
 upstream_license = None
 upstream_prefix = 'https://mcm.york.ac.uk/static/MCM/download'
 SPECIES = "mcm_3-3-1.tar.gz:mcm_3-3-1_unix/mcm_3-3-1_species_complete.tsv"
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) by Tiago de Paula Peixoto bc3dbbd
The lists of properties at the end of the descriptions marked edge
properties with "Edge" but left node properties unmarked, or grouped
them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property
was often unclear. Every item now starts with (Node), (Edge) or (Graph),
followed by the property names, a colon and the explanation, one item
per property or group of properties; qualifications (kinds of nodes,
networks) follow the names in parentheses. Properties that were only
named got a short explanation, and a few items that named the wrong
kind of property were corrected.
diff --git a/entries/mcm_atmospheric/__init__.py b/entries/mcm_atmospheric/__init__.py
index f4110c8..e332e8c 100644
--- a/entries/mcm_atmospheric/__init__.py
+++ b/entries/mcm_atmospheric/__init__.py
@@ -35,12 +35,12 @@ Only `v3.3.1` and `cri_v2_2` have the properties of the species (structures, for
 
 The networks have the following properties:
 
-- `is_reaction`: whether the node is a reaction (otherwise it is a species).
-- `name`: the name of the species in the MCM (e.g. `O3`, `CH3O2`, `APINENE`), or the equation of the reaction.
-- `rate`: the rate coefficient of the reaction, as an expression (in the syntax of the file: FORTRAN for KPP, FACSIMILE otherwise) of the temperature `TEMP`, of the concentrations of `M`, `O2`, `N2`, `H2O`, of named complex rate coefficients (e.g. `KMT01`, `KRO2NO`), of the sum of the peroxy radicals `RO2`, and, for photolysis, of the photolysis rates `J(...)` (empty for species).
-- `is_photolysis`: whether the reaction is a photolysis.
-- `smiles`, `inchi`, `inchi_key`, `formula`, `mass` (`v3.3.1`, `cri_v2_2`): the structure, formula and molar mass of the species (empty or NaN for reactions, and for the lumped species of CRI without a structure).
-- `is_excited`, `is_peroxy_radical` (`v3.3.1`, `cri_v2_2`): whether the species is an excited species, or a peroxy radical."""
+- (Node) `is_reaction`: whether the node is a reaction (otherwise it is a species).
+- (Node) `name`: the name of the species in the MCM (e.g. `O3`, `CH3O2`, `APINENE`), or the equation of the reaction.
+- (Node) `rate`: the rate coefficient of the reaction, as an expression (in the syntax of the file: FORTRAN for KPP, FACSIMILE otherwise) of the temperature `TEMP`, of the concentrations of `M`, `O2`, `N2`, `H2O`, of named complex rate coefficients (e.g. `KMT01`, `KRO2NO`), of the sum of the peroxy radicals `RO2`, and, for photolysis, of the photolysis rates `J(...)` (empty for species).
+- (Node) `is_photolysis`: whether the reaction is a photolysis.
+- (Node) `smiles`, `inchi`, `inchi_key`, `formula`, `mass` (`v3.3.1`, `cri_v2_2`): the structure, formula and molar mass of the species (empty or NaN for reactions, and for the lumped species of CRI without a structure).
+- (Node) `is_excited`, `is_peroxy_radical` (`v3.3.1`, `cri_v2_2`): whether the species is an excited species, or a peroxy radical."""
 tags = ['Chemical', 'Unweighted', 'Bipartite', 'Multigraph', 'Metadata']
 url = ['https://mcm.york.ac.uk/MCM', 'https://mcm.york.ac.uk/MCM/about/archive',
        'https://mcm.york.ac.uk/CRI', 'https://mcm.york.ac.uk/CRI/about/archive']
[entries] New entry: mcm_atmospheric by Tiago de Paula Peixoto 982ea10
diff --git a/entries/mcm_atmospheric/__init__.py b/entries/mcm_atmospheric/__init__.py
new file mode 100644
index 0000000..f4110c8
--- /dev/null
+++ b/entries/mcm_atmospheric/__init__.py
@@ -0,0 +1,202 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import csv
+import re
+from collections import defaultdict
+
+from .. import *
+
+title = "Master Chemical Mechanism and CRI mechanism of atmospheric chemistry (1999-2023)"
+description = r"""Networks of the chemical reactions of the Master Chemical Mechanism (MCM), a near-explicit mechanism of the gas-phase chemistry of the troposphere, which describes the degradation of 143 emitted volatile organic compounds (alkanes, alkenes, aromatics, isoprene, terpenes, oxygenated compounds, ...) and the inorganic chemistry of ozone, nitrogen oxides and sulfur, and is used in models of air quality. The reactions and their rate coefficients were derived by the MCM developers from measured kinetic data and from structure-activity relationships, following documented protocols.
+
+The networks are directed and bipartite: nodes are chemical species and reactions, and an edge goes from each reactant to the reaction, and from the reaction to each of its products. A species that appears twice among the reactants (e.g. `HO2 + HO2`) or the products of a reaction gives parallel edges. Reactions without products (e.g. a sink of a species) only have edges from their reactants; the pseudo-species of photons (`hv`) and of unspecified products (`PROD`) of the KPP format are left out (photolysis reactions are marked as such).
+
+There is one network for each version of the mechanism available from its website, from the files of the complete mechanism: `v3.3.1` (exported in September 2023, KPP format), `v3.2` (KPP format), `v3.1` (FACSIMILE box model, 2005), `v3.0` (FACSIMILE box model, 2003) and `v2.0` (FACSIMILE box model, 1999). The box models of the older versions contain the reactions of the mechanism (with some reactions written as several lines with partial rate coefficients, which are kept as separate reactions, and reversible reactions, which are split into their forward and backward reactions), together with the setup of the model, which is ignored.
+
+The Common Representative Intermediates mechanism (CRI), distributed from the same site, is a reduced mechanism derived from the MCM: the degradation of the emitted compounds is described with far fewer reactions and intermediate species, which are lumped into "representative intermediates" that conserve the ozone-forming potential of each compound (counted as its number of reactive bonds, `C-C` and `C-H`), and its parameters were optimized against the MCM in box-model simulations. CRI v2 was developed with MCM v3.1 as the reference. Its two versions available from the site are included, built in the same way, from the KPP files of the complete mechanism: `cri_v2_1` (CRI v2.1, exported in June 2018: the reactions of CRI v2, with the revisions of rate coefficients of the transition from MCM v3.1 to v3.2) and `cri_v2_2` (CRI v2.2, exported in September 2023: a substantially revised chemistry of isoprene, and the revisions of rate coefficients of the transition from MCM v3.2 to v3.3.1). The reduced variants of CRI with lumped emissions (R1-R5) are not among the files of the site and are not included.
+
+Only `v3.3.1` and `cri_v2_2` have the properties of the species (structures, formulas and masses).
+
+The networks have the following properties:
+
+- `is_reaction`: whether the node is a reaction (otherwise it is a species).
+- `name`: the name of the species in the MCM (e.g. `O3`, `CH3O2`, `APINENE`), or the equation of the reaction.
+- `rate`: the rate coefficient of the reaction, as an expression (in the syntax of the file: FORTRAN for KPP, FACSIMILE otherwise) of the temperature `TEMP`, of the concentrations of `M`, `O2`, `N2`, `H2O`, of named complex rate coefficients (e.g. `KMT01`, `KRO2NO`), of the sum of the peroxy radicals `RO2`, and, for photolysis, of the photolysis rates `J(...)` (empty for species).
+- `is_photolysis`: whether the reaction is a photolysis.
+- `smiles`, `inchi`, `inchi_key`, `formula`, `mass` (`v3.3.1`, `cri_v2_2`): the structure, formula and molar mass of the species (empty or NaN for reactions, and for the lumped species of CRI without a structure).
+- `is_excited`, `is_peroxy_radical` (`v3.3.1`, `cri_v2_2`): whether the species is an excited species, or a peroxy radical."""
+tags = ['Chemical', 'Unweighted', 'Bipartite', 'Multigraph', 'Metadata']
+url = ['https://mcm.york.ac.uk/MCM', 'https://mcm.york.ac.uk/MCM/about/archive',
+       'https://mcm.york.ac.uk/CRI', 'https://mcm.york.ac.uk/CRI/about/archive']
+citation = [('M. E. Jenkin, S. M. Saunders, M. J. Pilling, "The tropospheric degradation of volatile organic compounds: a protocol for mechanism development." Atmospheric Environment 31, 81-104 (1997)',
+             'https://doi.org/10.1016/S1352-2310(96)00105-7'),
+            ('S. M. Saunders, M. E. Jenkin, R. G. Derwent, M. J. Pilling, "Protocol for the development of the Master Chemical Mechanism, MCM v3 (Part A): tropospheric degradation of non-aromatic volatile organic compounds." Atmospheric Chemistry and Physics 3, 161-180 (2003)',
+             'https://doi.org/10.5194/acp-3-161-2003'),
+            ('M. E. Jenkin, S. M. Saunders, V. Wagner, M. J. Pilling, "Protocol for the development of the Master Chemical Mechanism, MCM v3 (Part B): tropospheric degradation of aromatic volatile organic compounds." Atmospheric Chemistry and Physics 3, 181-193 (2003)',
+             'https://doi.org/10.5194/acp-3-181-2003'),
+            ('M. E. Jenkin, J. C. Young, A. R. Rickard, "The MCM v3.3.1 degradation scheme for isoprene." Atmospheric Chemistry and Physics 15, 11433-11459 (2015)',
+             'https://doi.org/10.5194/acp-15-11433-2015'),
+            ('M. E. Jenkin, L. A. Watson, S. R. Utembe, D. E. Shallcross, "A Common Representative Intermediates (CRI) mechanism for VOC degradation. Part 1: Gas phase mechanism development." Atmospheric Environment 42, 7185-7195 (2008)',
+             'https://doi.org/10.1016/j.atmosenv.2008.07.028'),
+            ('L. A. Watson, D. E. Shallcross, S. R. Utembe, M. E. Jenkin, "A Common Representative Intermediates (CRI) mechanism for VOC degradation. Part 2: Gas phase mechanism reduction." Atmospheric Environment 42, 7196-7204 (2008)',
+             'https://doi.org/10.1016/j.atmosenv.2008.07.034'),
+            ('M. E. Jenkin, M. A. H. Khan, D. E. Shallcross, R. Bergström, D. Simpson, K. L. C. Murphy, A. R. Rickard, "The CRI v2.2 reduced degradation scheme for isoprene." Atmospheric Environment 212, 172-182 (2019)',
+             'https://doi.org/10.1016/j.atmosenv.2019.05.055')]
+icon_hash = None
+upstream_license = None
+upstream_prefix = 'https://mcm.york.ac.uk/static/MCM/download'
+SPECIES = "mcm_3-3-1.tar.gz:mcm_3-3-1_unix/mcm_3-3-1_species_complete.tsv"
+# (the tar.gz file of v3.0 is corrupted, the zip file is used)
+files = [(("mcm_3-3-1.tar.gz:mcm_3-3-1_unix/mcm_3-3-1_kpp_complete.eqn", SPECIES),
+          "v3.3.1", "kpp"),
+         ("mcm_3-2.tar.gz:./mcm_3-2_kpp_complete.kpp", "v3.2", "kpp"),
+         ("MCM31boxv2.tar.gz:MCM3-1boxv21.fac", "v3.1", "fac"),
+         ("MCM3box.zip:MCM/MCM3box.fac", "v3.0", "fac"),
+         ("mcm2box.tar.gz:MCM2box.fac", "v2.0", "fac")]
+# the CRI mechanism, from the same site
+cri_prefix = 'https://mcm.york.ac.uk/static/CRI/download'
+cri_files = [(("cri_2-2.tar.gz:cri_2-2_unix/cri_2-2_kpp_complete.eqn",
+               "cri_2-2.tar.gz:cri_2-2_unix/cri_2-2_species_complete.tsv"),
+              "cri_v2_2", "kpp"),
+             ("cri_2-1.tar.gz:./cri_2-1_kpp_complete.kpp", "cri_v2_1", "kpp")]
+mcm_files = files
+files = mcm_files + cri_files
+
+def fetch_upstream(force=False):
+    name = __name__.split(".")[-1]
+    fetch_upstream_files(name, cri_prefix, cri_files, force)
+    return fetch_upstream_files(name, upstream_prefix, mcm_files, force)
+
+def statements(text, start):
+    """The statements (up to ';') of the reactions, which start with `start`
+    at the beginning of a line ('%' for FACSIMILE, '<' or '{' for KPP)."""
+    lines = text.splitlines()
+    i = 0
+    while i < len(lines):
+        l = lines[i].strip()
+        if l[:1] in start:
+            s = l
+            while ";" not in s and i + 1 < len(lines):
+                i += 1
+                s += " " + lines[i].strip()
+            yield s.split(";")[0]
+        i += 1
+
+def side(x):
+    return [t.strip() for t in x.split("+") if t.strip()]
+
+def reactions(text, fmt):
+    "Yield (reactants, products, rate) of the reactions."
+    if fmt == "kpp":
+        for s in statements(text[text.index("#EQUATIONS"):], "<{"):
+            s = re.sub(r"^(<[^>]*>|\{[^}]*\})\s*", "", s)
+            if "=" not in s:
+                continue
+            eq, rate = s.split(":", 1)
+            lhs, rhs = eq.split("=")
+            yield side(lhs), side(rhs), rate.strip()
+    else:
+        for s in statements(text, "%"):
+            if ":" not in s or "=" not in s:
+                continue
+            rates, eq = s.split(":", 1)
+            rates = [r.strip() for r in rates.split("%") if r.strip()]
+            lhs, rhs = eq.split("=")
+            yield side(lhs), side(rhs), rates[0]
+            if len(rates) == 2:
+                # reversible reaction
+                yield side(rhs), side(lhs), rates[1]
+            else:
+                assert len(rates) == 1, s
+
+def to_float(x):
+    try:
+        return float(x)
+    except ValueError:
+        return float("nan")
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+    name = __name__.split(".")[-1]
+    for fnames, alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        if isinstance(fnames, str):
+            fnames = (fnames,)
+        with open_upstream_file(name, fnames[0], "r", encoding="latin1") as f:
+            text = f.read()
+        species = {}
+        if len(fnames) > 1:
+            with open_upstream_file(name, fnames[1], "r") as f:
+                lines = [l for l in f if not l.startswith("*")]
+            for row in csv.DictReader(lines, delimiter="\t"):
+                species[row["Name"]] = row
+
+        vidx = {}
+        vvals = defaultdict(list)
+        def add_species(s):
+            v = vidx.get(s)
+            if v is None:
+                v = vidx[s] = len(vidx)
+                vvals["is_reaction"].append(False)
+                vvals["name"].append(s)
+                vvals["rate"].append("")
+                vvals["is_photolysis"].append(False)
+                vvals["species"].append(species.get(s))
+            return v
+        edges = []
+        for lhs, rhs, rate in reactions(text, fmt):
+            photo = "hv" in lhs or rate.startswith("J(") or \
+                re.match(r"^J\s*[<(]", rate) is not None
+            lhs = [x for x in lhs if x != "hv"]
+            rhs = [x for x in rhs if x != "PROD"]
+            r = len(vidx)
+            vidx[("reaction", r)] = r
+            vvals["is_reaction"].append(True)
+            vvals["name"].append(" + ".join(lhs + (["hv"] if photo else [])) +
+                                 " = " + " + ".join(rhs))
+            vvals["rate"].append(rate)
+            vvals["is_photolysis"].append(photo)
+            vvals["species"].append(None)
+            for x in lhs:
+                edges.append((add_species(x), r))
+            for x in rhs:
+                edges.append((r, add_species(x)))
+
+        g = Graph(directed=True)
+        g.add_vertex(len(vidx))
+        g.add_edge_list(numpy.array(edges, dtype="int64").reshape(-1, 2))
+        g.vp.is_reaction = g.new_vp("bool", vals=vvals["is_reaction"])
+        g.vp.name = g.new_vp("string", vals=vvals["name"])
+        g.vp.rate = g.new_vp("string", vals=vvals["rate"])
+        g.vp.is_photolysis = g.new_vp("bool", vals=vvals["is_photolysis"])
+        if species:
+            sp = vvals["species"]
+            for p, col in [("smiles", "Smiles"), ("inchi", "Inchi"),
+                           ("inchi_key", "InchiKey"), ("formula", "Formula")]:
+                g.vp[p] = g.new_vp("string", vals=[x[col] if x else "" for x in sp])
+            g.vp.mass = g.new_vp("double", vals=[to_float(x["Mass"]) if x else float("nan")
+                                                 for x in sp])
+            g.vp.is_excited = g.new_vp("bool", vals=[x is not None and x["Excited"] == "true"
+                                                     for x in sp])
+            g.vp.is_peroxy_radical = g.new_vp("bool", vals=[x is not None and x["PeroxyRadical"] == "true"
+                                                            for x in sp])
+        yield alt, g