├── pykpp ├── morpho │ ├── __init__.py │ └── jtable.py ├── __main__.py ├── tuv │ └── __init__.py ├── tests │ ├── __init__.py │ ├── __main__.py │ ├── test_simple.py │ └── test_updaters.py ├── models │ ├── prep │ │ ├── __init__.py │ │ ├── sbox2kpp.py │ │ ├── am32kpp.py │ │ ├── chimere2kpp.py │ │ └── cmaq2chimere.py │ ├── simple_organic.eqn │ ├── small_strato.eqn │ ├── small_strato.kpp │ ├── cbm4.kpp │ ├── chimere1.def │ ├── UFAuxMech09.eqn │ ├── geossuper_chem.kpp │ ├── geoschem_v11.def │ ├── MCM_J.def │ ├── melchior2_soa_cpx.eqn │ ├── atoms │ ├── melchior1_soa_cpx.eqn │ ├── cbm4.eqn │ ├── mcm_ch4.kpp │ └── melchior2.eqn ├── funcs │ ├── __init__.py │ ├── camx.py │ ├── racm.py │ ├── kpp.py │ ├── cmaq.py │ ├── chimere.py │ ├── am3.py │ ├── mozart4.py │ ├── mcm.py │ └── geoschem.py ├── __init__.py ├── plot.py ├── main.py └── stdfuncs.py ├── scripts └── pykpprun.py ├── examples ├── knote_ae_2015 │ ├── physical.pdf │ ├── chemical_nox.pdf │ ├── chemical_ozone.pdf │ ├── summerenv.tsv │ ├── README.txt │ ├── mean_emis.csv │ └── plot_knoteae2015.py ├── README.txt ├── hysplit_traj │ ├── trajdump.txt │ └── plot_hysplit.py ├── plot_acp2006.py ├── plot_henderson2011.py └── plot_zombies.py ├── docs ├── requirements.txt ├── Makefile ├── make.bat └── source │ ├── index.rst │ └── conf.py ├── .gitignore ├── tox.ini ├── .github └── workflows │ ├── docs.yml │ └── python-publish.yml ├── setup.py └── README.md /pykpp/morpho/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['jtable'] -------------------------------------------------------------------------------- /scripts/pykpprun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pykpp.main import main 3 | 4 | main(globals = globals()) -------------------------------------------------------------------------------- /pykpp/__main__.py: -------------------------------------------------------------------------------- 1 | from .main import main 2 | 3 | if __name__ == '__main__': 4 | main(globals=globals()) 5 | -------------------------------------------------------------------------------- /pykpp/tuv/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['tuv4pt1', 'tuv5pt0'] 2 | 3 | from . import tuv4pt1 4 | from . import tuv5pt0 5 | -------------------------------------------------------------------------------- /examples/knote_ae_2015/physical.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barronh/pykpp/HEAD/examples/knote_ae_2015/physical.pdf -------------------------------------------------------------------------------- /examples/knote_ae_2015/chemical_nox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barronh/pykpp/HEAD/examples/knote_ae_2015/chemical_nox.pdf -------------------------------------------------------------------------------- /examples/knote_ae_2015/chemical_ozone.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barronh/pykpp/HEAD/examples/knote_ae_2015/chemical_ozone.pdf -------------------------------------------------------------------------------- /pykpp/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['test_simple', 'test_updaters'] 2 | 3 | from . import test_simple 4 | from . import test_updaters 5 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | ipython 2 | notebook 3 | requests 4 | numpy 5 | scipy 6 | matplotlib 7 | pandas 8 | sphinx==7.0.1 9 | sphinx-rtd-theme 10 | sphinx-gallery<=0.13 11 | sphinx_design 12 | sphinx-copybutton 13 | pydata-sphinx-theme<0.9.0 14 | myst_nb 15 | -------------------------------------------------------------------------------- /pykpp/models/prep/__init__.py: -------------------------------------------------------------------------------- 1 | def help(): 2 | """ 3 | This folder keeps mechanism translators that should operate 4 | as stand-alone scripts. They are kept here as a central 5 | repository 6 | """ 7 | 8 | import chimere2kpp 9 | import geoschem2kpp 10 | import cmaq2kpp 11 | import sbox2kpp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/* 3 | dist/* 4 | .DS_Store 5 | *.dat 6 | *.ncf 7 | # *.txt 8 | src/pykpp/test/*/ 9 | *.tsv 10 | *.csv 11 | *.png 12 | docs/build/ 13 | docs/source/api/ 14 | docs/source/auto_examples/ 15 | MANIFEST 16 | MANIFEST.in 17 | pykpp.egg-info 18 | .ipynb_checkpoints 19 | .tox 20 | .coverage 21 | -------------------------------------------------------------------------------- /pykpp/funcs/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | 'am3', 'geoschem', 'geoschemkpp', 'kpp', 'camx', 'cmaq', 'mozart4', 'racm', 3 | 'chimere' 4 | ] 5 | from . import am3 6 | from . import geoschem 7 | from . import geoschemkpp 8 | from . import kpp 9 | from . import cmaq 10 | from . import camx 11 | from . import mozart4 12 | from . import racm 13 | from . import chimere 14 | -------------------------------------------------------------------------------- /pykpp/models/simple_organic.eqn: -------------------------------------------------------------------------------- 1 | #EQUATIONS 2 | < 1> RH + OH = RO2 { + H2O} :26.3e-12; 3 | < 2> RO2 + NO = NO2 + RCHO + HO2 : 7.7e-12; 4 | < 3> HO2 + NO = NO2 + OH : 8.1e-12; 5 | < 4> OH + NO2 = HNO3 : 1.1e-11; 6 | < 5> HO2 + HO2 = H2O2 { + O2} : 2.9e-12; 7 | < 6> RO2 + HO2 = ROOH { + O2} : 5.2e-12; 8 | < 7> NO2 = NO + O3 : 0.015; 9 | < 8> O3 + NO = NO2 : 1.9e-14; 10 | -------------------------------------------------------------------------------- /pykpp/models/small_strato.eqn: -------------------------------------------------------------------------------- 1 | #EQUATIONS 2 | O2 = 2O : (2.643E-10) * SUN*SUN*SUN; 3 | O + O2 = O3 : (8.018E-17); 4 | O3 = O + O2 : (6.120E-04) * SUN; 5 | O + O3 = 2O2 : (1.576E-15); 6 | O3 = O1D + O2 : (1.070E-03) * SUN*SUN; 7 | O1D = O : (7.110E-11) * M; 8 | O1D + O3 = 2O2 : (1.200E-10); 9 | NO + O3 = NO2 + O2 : (6.062E-15); 10 | NO2 + O = NO + O2 : (1.069E-11); 11 | NO2 = NO + O : (1.289E-02) * SUN; 12 | 13 | -------------------------------------------------------------------------------- /pykpp/tests/__main__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | if __name__ == '__main__': 4 | from optparse import OptionParser 5 | from .test import testit 6 | parser = OptionParser() 7 | parser.set_usage("""Usage: python -m pykpp.test [-v] model1,model2""") 8 | 9 | parser.add_option( 10 | "-v", "--verbose", dest="verbose", action="store_true", default=False, 11 | help="Show extended output" 12 | ) 13 | options, args = parser.parse_args() 14 | testit(*args, verbose=options.verbose) 15 | -------------------------------------------------------------------------------- /pykpp/models/small_strato.kpp: -------------------------------------------------------------------------------- 1 | #INLINE PY_UTIL 2 | add_world_updater(func_updater(Update_M, incr = 1)) 3 | add_world_updater(func_updater(Update_SUN, incr = 1)) 4 | #ENDINLINE 5 | 6 | #INLINE PY_INIT 7 | TSTART = (12*3600.); 8 | TEND = TSTART + (3*24*3600.); 9 | DT = 30.; 10 | MONITOR_DT = 3600.; 11 | SUN = 1.; 12 | TEMP = 270.; 13 | M = 8.120E+16 ; 14 | #ENDINLINE 15 | 16 | #INITVALUES 17 | CFACTOR=1.; 18 | O1D = 9.906E+01 ; 19 | O = 6.624E+08 ; 20 | O3 = 5.326E+11 ; 21 | NO = 8.725E+08 ; 22 | NO2 = 2.240E+08 ; 23 | O2 = 1.697E+16 ; 24 | 25 | #include small_strato.eqn 26 | -------------------------------------------------------------------------------- /examples/knote_ae_2015/summerenv.tsv: -------------------------------------------------------------------------------- 1 | time PBLH TEMP 2 | 0.00 250. 288.15 3 | 3600 250. 288.15 4 | 7200 250. 288.15 5 | 10800 250. 288.15 6 | 14400 250. 288.15 7 | 18000 300. 288.16 8 | 21600 550. 289 9 | 25200 850. 292.75 10 | 28800 1120 295.93 11 | 32400 1300 298.52 12 | 36000 1400 300.55 13 | 39600 1475 302 14 | 43200 1500 302.86 15 | 46800 1500 303.15 16 | 50400 1500 302.86 17 | 54000 1500 302 18 | 57600 1500 300.55 19 | 61200 1500 298.53 20 | 64800 1500 295.93 21 | 68400 1500 292.75 22 | 72000 1500 289 23 | 75600 250. 288.15 24 | 79200 250. 288.15 25 | 82800 250. 288.15 26 | 86400 250. 288.15 -------------------------------------------------------------------------------- /pykpp/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.0' 2 | __all__ = ['mech', 'parse', 'plot', 'stdfuncs', 'main', 'funcs'] 3 | import warnings 4 | import sys 5 | from . import mech 6 | from . import parse 7 | from . import plot 8 | from . import funcs 9 | from . import stdfuncs 10 | from . import main 11 | 12 | warn = warnings.warn 13 | 14 | 15 | def clean_showwarning( 16 | message, category, filename, lineno, file=None, line=None 17 | ): 18 | print( 19 | '**PYKPP:%s:%s:%s:\n %s' 20 | % ((filename), lineno, category.__name__, message), file=sys.stderr 21 | ) 22 | return 23 | 24 | 25 | warnings.showwarning = clean_showwarning 26 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # content of: tox.ini , put in same dir as setup.py 2 | [tox] 3 | envlist = py39,py312,py313 4 | 5 | [flake8] 6 | exclude = .ipynb_checkpoints 7 | ignore=E501,W291 8 | 9 | [testenv] 10 | # install pytest in the virtualenv where commands will be executed 11 | deps = 12 | flake8 13 | pytest 14 | coverage 15 | numpy 16 | pandas 17 | scipy 18 | matplotlib 19 | pyparsing 20 | tabulate 21 | 22 | setenv = 23 | OPENBLAS_NUM_THREADS=1 24 | MKL_NUM_THREADS=1 25 | 26 | commands = 27 | # NOTE: you can run any command line tool here - not just tests 28 | # flake8 -j1 pykpp 29 | coverage run -m pytest pykpp 30 | coverage report -im 31 | -------------------------------------------------------------------------------- /pykpp/models/cbm4.kpp: -------------------------------------------------------------------------------- 1 | #INITVALUES 2 | P = 101325. 3 | TEMP = 288.15 4 | CFACTOR = P / (R / centi**3) / TEMP * Avogadro * 1e-9; 5 | NO = 50.0; 6 | NO2 = 20.0; 7 | HONO = 1.0; 8 | O3 = 100.0; 9 | HCHO = 10.0; 10 | ALD2 = 10; 11 | PAN = 1.0; 12 | PAR = 50.0; 13 | OLE = 10.0; 14 | ETH = 10.0; 15 | TOL = 10.0; 16 | XYL = 10.0; 17 | ISOP = 10.0; 18 | CO = 300.0; 19 | H2O = 1.25E+8; 20 | 21 | #include cbm4.eqn 22 | 23 | #INLINE PY_UTIL 24 | add_world_updater(func_updater(Update_M, incr = 300)) 25 | add_world_updater(func_updater(Update_SUN, incr = 300)) 26 | #ENDINLINE 27 | 28 | #INLINE PY_INIT 29 | TSTART = 12 * 3600. 30 | TEND = TSTART + 5 * 3600. * 24 31 | DT = 600. 32 | StartDate = 'datetime(2013, 6, 1)' 33 | Latitude_Degrees = 35. 34 | Longitude_Degrees = 0.00E+00 35 | #ENDINLINE 36 | #MONITOR O3; NO; -------------------------------------------------------------------------------- /pykpp/models/chimere1.def: -------------------------------------------------------------------------------- 1 | 2 | #INITVALUES 3 | TEMP = 298. 4 | P = 101325. 5 | CFACTOR = P * Avogadro / R / TEMP * centi **3 * micro {ppm-to-molecules/cm3} 6 | ALL_SPEC=1e-5; 7 | M = 1e6 8 | TOL = 195.81; 9 | OXYL = 195.81; 10 | NO = 77.75; 11 | NO2 = 25.06; 12 | SO2 = 48.36; 13 | O3 = 0.1; 14 | NH3 = 0.1; 15 | H2O = 6.11 * 10**((7.5 * 11.85)/(11.85+237.3)) * 50. / 100 * 100 / P * 1e6 16 | 17 | #INLINE PY_UTIL 18 | add_world_updater(func_updater(Update_M, incr = 300)) 19 | add_world_updater(func_updater(Update_THETA, incr = 300)) 20 | #ENDINLINE 21 | #INLINE PY_INIT 22 | TSTART = 5.5 * 3600. 23 | TEND = TSTART + 3600. * 8. 24 | DT = 120. 25 | StartDate = 'datetime(2012, 5, 18)' 26 | Latitude_Degrees = 2.50E+01 27 | Longitude_Degrees = 0.00E+00 28 | #ENDINLINE 29 | 30 | #MONITOR O3; NO2; NO; HNO3; 31 | 32 | #include melchior1.eqn 33 | #include melchior1_soa_cpx.eqn 34 | -------------------------------------------------------------------------------- /pykpp/funcs/camx.py: -------------------------------------------------------------------------------- 1 | __all__ = ['CAMX_4', 'CAMX_6'] 2 | 3 | from numpy import exp, log 4 | 5 | TEMP = M = 0 6 | 7 | 8 | def update_func_world(mech, world): 9 | """ 10 | Function to update globals for user defined functions 11 | """ 12 | global M, TEMP 13 | globals().update(world) 14 | 15 | 16 | def CAMX_4(A0, Ea0, B0, Tr0, A1, Ea1, B1, Tr1, F, n): 17 | k0 = A0 * (TEMP/Tr0)**(B0) * exp(-Ea0/TEMP) 18 | kinf = A1 * (TEMP/Tr1)**(B1) * exp(-Ea1/TEMP) 19 | k0M = k0*M 20 | G = (1+(log(k0M/kinf)/n)**2)**(-1) 21 | k = (k0M / (1 + k0M/kinf)) * F**(G) 22 | return k 23 | 24 | 25 | def CAMX_6(A0, Ea0, B0, Tr0, A2, Ea2, B2, Tr2, A3, Ea3, B3, Tr3): 26 | k0 = A0 * (TEMP/Tr0)**(B0) * exp(-Ea0/TEMP) 27 | k2 = A2 * (TEMP/Tr2)**(B2) * exp(-Ea2/TEMP) 28 | k3 = A3 * (TEMP/Tr3)**(B3) * exp(-Ea3/TEMP) 29 | k = k0 + k3*M/(1+k3*M/k2) 30 | return k 31 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: [workflow_dispatch] 3 | permissions: 4 | contents: write 5 | jobs: 6 | docs: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-python@v3 11 | - name: Install dependencies 12 | run: | 13 | pip install -r docs/requirements.txt 14 | - name: Install pykpp 15 | run: | 16 | pip install -e . --no-deps --force-reinstall 17 | - name: Sphinx build 18 | run: | 19 | cd docs 20 | make html 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@v3 23 | if: ${{ github.ref == 'refs/heads/main' }} 24 | with: 25 | publish_branch: gh-pages 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | publish_dir: docs/build/html 28 | force_orphan: true 29 | -------------------------------------------------------------------------------- /pykpp/models/UFAuxMech09.eqn: -------------------------------------------------------------------------------- 1 | // Chamber wall mechanism 2 | #EQUATIONS 3 | H2O2 = : 1.1E-3 ; 4 | O3 = : 1.91E-6 ; 5 | SO2 = : 9.72E-6 ; 6 | N2O5 = 2.0 WHNO3 : 4.2E-5; 7 | N2O5 + WHNO3 = 2.0 NO + NO2 : 9.0E-22*exp(2000.0/TEMP) ; 8 | HNO3 = WHNO3 : 8.2E-5 ; 9 | HNO3 + WH2O = WHNO3 : 2.5E-21 ; 10 | WHNO3 = NO2 : 8.0e-4 * TUV_J5pt0('NO2 -> NO + O(3P)', THETA); 11 | NO + WH2O = WHNO3 : 6.77E-24 ; 12 | NO2 + WH2O = WHNO3 : 6.77E-24 ; 13 | NO + NO2 + WHNO3 = 2.0 HONO + 1.0 NO2 : 5.0E-30; 14 | NO + WHNO3 = 1.5 HONO + 0.5 NO2 : 6.09E-17 ; 15 | NO2 + WHNO3 = 1.5 HONO + 0.5 NO2 : 3.38E-18 ; 16 | NO2 = HONO : 1e-3 * TUV_J5pt0('NO2 -> NO + O(3P)', THETA); 17 | HONO + WH2O = WHONO : 4.5E-21 ; 18 | WHONO = HONO : 3.3E-3 ; 19 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | clean: 18 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 19 | rm -rf build source/api source/auto_examples 20 | 21 | # Catch-all target: route all unknown targets to Sphinx using the new 22 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 23 | %: Makefile api 24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 25 | 26 | api: 27 | sphinx-apidoc -f --maxdepth=1 -o ./source/api ../pykpp 28 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /examples/README.txt: -------------------------------------------------------------------------------- 1 | pykpp Example Gallery 2 | ===================== 3 | 4 | This gallery houses examples on how to use pykpp to do various 5 | analyses. This includes downloading and visualizing the data. 6 | 7 | Some examples require some features of pykpp that are not required 8 | for minimal functionality. To install all the necessary libaries 9 | for any example, run the command below (use --user if not an admin): 10 | 11 | .. code-block:: bash 12 | 13 | python -m pip install --user https://github.com/barronh/pykpp/archive/main.zip 14 | 15 | In notebooks (e.g., on Google Colab), this can be done live with 16 | the command below (may require kernel restart): 17 | 18 | .. code-block:: python 19 | 20 | %pip install --user https://github.com/barronh/pykpp/archive/main.zip 21 | 22 | To run an example in a notebook: 23 | 24 | 1. Copy the command above into a new cell and run it. 25 | 2. Click on example below to see code. 26 | 3. Copy the code from the example into a new cell and run it. 27 | 28 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pyrkpp documentation main file, created by 2 | 3 | pykpp User's Guide 4 | =================== 5 | 6 | Python interface similar to the Kinetic Preprocessor (KPP) 7 | 8 | The key value of `pyrkpp` is to make simple simulations easy to run in common 9 | cloud environments like Google Colab, AWS SageMaker, Binder, or even 10 | Jupyter-lite. 11 | 12 | Getting Started 13 | --------------- 14 | 15 | The best way to get started is to install (see below) and then explore the 16 | examples gallery. 17 | 18 | 19 | Installation 20 | ------------ 21 | 22 | You can get the latest version with this command. 23 | 24 | .. code-block:: 25 | 26 | pip install https://github.com/barronh/pykpp/archive/main.zip 27 | 28 | 29 | Issues 30 | ------ 31 | 32 | If you're having any problems, open an issue on github. 33 | 34 | https://github.com/barronh/pykpp/issues 35 | 36 | 37 | Quick Links 38 | ----------- 39 | 40 | * :doc:`auto_examples/index` 41 | * :doc:`api/pykpp` 42 | 43 | .. toctree:: 44 | :maxdepth: 2 45 | :hidden: 46 | :caption: Table of Contents 47 | 48 | self 49 | auto_examples/index 50 | api/pykpp 51 | -------------------------------------------------------------------------------- /pykpp/models/geossuper_chem.kpp: -------------------------------------------------------------------------------- 1 | #INITVALUES 2 | P = 3.01E+02 * 100 3 | TEMP = 2.35E+02 4 | CFACTOR = P * Avogadro / R / TEMP * centi **3 * micro {ppm-to-molecules/cm3} 5 | ACET = 1.49E-03 6 | ALD2 = 1.17E-04 7 | ALK4 = 7.70E-05 8 | C2H6 = 8.30E-04 9 | C3H8 = 2.81E-04 10 | CH2O = 4.03E-04 11 | CH4 = 1.78E+00 12 | CO = 1.07E-01 13 | EOH = 2.08E-04 14 | H2O = 2.87E+02 15 | H2O2 = 2.08E-04 16 | HNO3 = 1.19E-04 17 | HNO4 = 6.78E-05 18 | HO2 = 1.16E-05 19 | MAP = 2.25E-04 20 | MEK = 9.50E-05 21 | MNO3 = 2.37E-06 22 | MOH = 2.90E-03 23 | MP = 2.33E-04 24 | MPN = 3.25E-05 25 | N2O = 3.30E-01 26 | NO = 3.38E-04 27 | NO2 = 1.33E-04 28 | O3 = 7.05E-02 29 | OCS = 4.49E-04 30 | OH = 5.62E-07 31 | PAN = 3.73E-04 32 | PPN = 3.73E-05 33 | PRPE = 1.50E-06 34 | R4N2 = 7.86E-06 35 | SO2 = 2.06E-05 36 | 37 | 38 | #INLINE PY_INIT 39 | add_world_updater(func_updater(Update_M, incr = 300.)) 40 | add_world_updater(func_updater(Update_THETA, incr = 300.)) 41 | TSTART = 43200 42 | TEND = 43200 + 10 * 24 * 3600. 43 | DT = 3600. 44 | StartDate = 'datetime(2000, 7, 1)' 45 | Latitude_Degrees = 4.50E+01 46 | Longitude_Degrees = 0.00E+00 47 | 48 | #ENDINLINE 49 | 50 | #include geossuper_chem.eqn 51 | 52 | #MONITOR O3; NO2; H2O; N2; O2; -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /pykpp/models/geoschem_v11.def: -------------------------------------------------------------------------------- 1 | #INITVALUES 2 | P = 3.01E+02 * 100 3 | TEMP = 2.35E+02 4 | ALL_SPEC = 1e-20 5 | CFACTOR = P * Avogadro / R / TEMP * centi **3 * micro {ppm-to-molecules/cm3} 6 | ACET = 1.49E-03 7 | ALD2 = 1.17E-04 8 | ALK4 = 7.70E-05 9 | C2H6 = 8.30E-04 10 | C3H8 = 2.81E-04 11 | CH2O = 4.03E-04 12 | CH4 = 1.78E+00 13 | CO = 1.07E-01 14 | EOH = 2.08E-04 15 | H2O = 2.87E+02 16 | H2O2 = 2.08E-04 17 | HNO3 = 1.19E-04 18 | HNO4 = 6.78E-05 19 | HO2 = 1.16E-05 20 | MAP = 2.25E-04 21 | MEK = 9.50E-05 22 | MNO3 = 2.37E-06 23 | MOH = 2.90E-03 24 | MP = 2.33E-04 25 | MPN = 3.25E-05 26 | N2O = 3.30E-01 27 | NO = 3.38E-04 28 | NO2 = 1.33E-04 29 | O3 = 7.05E-02 30 | OCS = 4.49E-04 31 | OH = 5.62E-07 32 | PAN = 3.73E-04 33 | PPN = 3.73E-05 34 | PRPE = 1.50E-06 35 | R4N2 = 7.86E-06 36 | SO2 = 2.06E-05 37 | 38 | 39 | #INLINE PY_INIT 40 | add_world_updater(func_updater(Update_M, incr = 300.)) 41 | add_world_updater(func_updater(Update_THETA, incr = 300.)) 42 | TSTART = 43200 43 | TEND = 43200 + 10 * 24 * 3600. 44 | DT = 300. 45 | MONITOR_DT = 3600. 46 | StartDate = 'datetime(2000, 7, 1)' 47 | Latitude_Degrees = 4.50E+01 48 | Longitude_Degrees = 0.00E+00 49 | atol = ones(NSPCS, dtype = 'd')*1e-3 50 | atol[ind_O1D] *= 1e5 51 | atol[ind_ClO] *= 1e5 52 | atol[ind_Cl] *= 1e5 53 | #ENDINLINE 54 | 55 | #include geoschem_standard_v11.eqn 56 | 57 | #MONITOR O3; NO2; H2O; N2; O2; ClO; -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | try: 3 | from setuptools import setup 4 | except ImportError: 5 | from distutils.core import setup 6 | 7 | version = '0.0.0' 8 | with open('pykpp/__init__.py', 'r') as initf: 9 | while True: 10 | _l = initf.readline() 11 | if _l.startswith('__version__'): 12 | version = eval(_l.split('=')[1].strip()) 13 | break 14 | 15 | setup( 16 | name='pykpp', 17 | version=version, 18 | author='Barron Henderson', 19 | author_email='barronh@gmail.com', 20 | maintainer='Barron Henderson', 21 | maintainer_email='barronh@gmail.com', 22 | url='https://github.com/barronh/pykpp/', 23 | download_url='https://github.com/barronh/pykpp/archive/main.zip', 24 | long_description=( 25 | "pykpp is a KPP-like chemical mechanism parser that produces a box" 26 | + " model solvable by SciPy's odeint solver" 27 | ), 28 | packages=[ 29 | 'pykpp', 'pykpp.tuv', 'pykpp.morpho', 'pykpp.models', 'pykpp.tests', 30 | 'pykpp.funcs' 31 | ], 32 | package_data={'pykpp.models': ['*.eqn', '*.txt', '*.kpp', '*.def']}, 33 | scripts=['scripts/pykpprun.py'], 34 | entry_points={ 35 | 'console_scripts': [ 36 | 'pykpp = pykpp.__main__:main' 37 | ] 38 | }, 39 | install_requires=['numpy', 'scipy', 'pyparsing', 'matplotlib', 'pandas'] 40 | ) 41 | -------------------------------------------------------------------------------- /examples/knote_ae_2015/README.txt: -------------------------------------------------------------------------------- 1 | Typical Summer Day 2 | ------------------ 3 | 4 | This example application is based on the Knote et al. (2015) simulation of a typical summer day with CB05. The example here uses the "Summer" PBL and temperature profile. At present, the example uses the a relatively "clean" initial environment (NOx(t=0) = 1ppb). 5 | 6 | Where possible, the inputs are as described in the publication, but has been adapted for pykpp. The model uses initial conditions, temperature, PBL and deposition rates as described in the publications. The emissions were produced from the NEI using a similar process as described in the paper, but may be somewhat different. 7 | 8 | Figures showing the inputs and outputs are distributed with the example and will be remade if you run the example (python summerclean.py). physical.pdf shows the input diurnal profiles of temperature, pressure, NOx emissions, and photolysis. chemical_ozone.pdf shows the output diurnal profiles of ozone, hydroxyl radical and hydroperoxy radical concentrations. chemical_nox.pdf shows the output diurnal profiles of NO, NO2 and the sum. 9 | 10 | [1] Knote, C., Tuccella, P., Curci, G., Emmons, L., Orlando, J. J., Madronich, S., Baró, R., Jiménez-Guerrero, P., Luecken, D., Hogrefe, C., Forkel, R., Werhahn, J., Hirtl, M., Pérez, J. L., San José, R., Giordano, L., Brunner, D., Yahya, K., Zhang, Y., Influence of the choice of gas-phase mechanism on predictions of key gaseous pollutants during the AQMEII phase-2 intercomparison, Atmospheric Environment, Volume 115, August 2015, Pages 553-568, ISSN 1352-2310, http://dx.doi.org/10.1016/j.atmosenv.2014.11.066. 11 | -------------------------------------------------------------------------------- /pykpp/models/MCM_J.def: -------------------------------------------------------------------------------- 1 | translation = {1:compile('TUV_J(2,THETA)', '', 'eval'), 2 | 2:compile('TUV_J(3,THETA)', '', 'eval'), 3 | 3:compile('TUV_J(5,THETA)', '', 'eval'), 4 | 4:compile('TUV_J(6,THETA)', '', 'eval'), 5 | 5:compile('TUV_J(7,THETA)', '', 'eval'), 6 | 6:compile('TUV_J(8,THETA)', '', 'eval'), 7 | 7:compile('TUV_J(12,THETA)', '', 'eval'), 8 | 8:compile('TUV_J(13,THETA)', '', 'eval'), 9 | 11:compile('TUV_J(17,THETA)', '', 'eval'), 10 | 12:compile('TUV_J(18,THETA)', '', 'eval'), 11 | 13:compile('TUV_J(19,THETA)', '', 'eval'), 12 | 14:compile('TUV_J(22,THETA)', '', 'eval'), 13 | 15:compile('TUV_J(22,THETA)', '', 'eval'), #Mapped to 22 14 | 16:compile('TUV_J(22,THETA)', '', 'eval'), #Mapped to 22. Needs scaling factor 15 | 17:compile('TUV_J(22,THETA)', '', 'eval'), #Mapped to 22 16 | 21:compile('TUV_J(26,THETA)', '', 'eval'), 17 | 18:compile('.5*TUV_J(25,THETA)', '', 'eval'), #Assume pathway splits 50% 18 | 19:compile('.5*TUV_J(25,THETA)', '', 'eval'), 19 | 22:compile('TUV_J(28,THETA)', '', 'eval'), 20 | 23:compile('.5*TUV_J(27,THETA)', '', 'eval'), 21 | 24:compile('.5*TUV_J(27,THETA)', '', 'eval'), #Assume pathway splits 50% 22 | 31:0 #No longer a part of TUV 23 | 32:compile('TUV_J(45,THETA)', '', 'eval'), 24 | 33:compile('TUV_J(44,THETA)', '', 'eval'), 25 | 34:compile('TUV_J(46,THETA)', '', 'eval'), 26 | 35:compile('TUV_J(47,THETA)', '', 'eval'), 27 | 41:compile('TUV_J(31,THETA)', '', 'eval'), 28 | 51:compile('TUV_J(34,THETA)', '', 'eval'), 29 | 52:compile('TUV_J(35,THETA)', '', 'eval'), 30 | 53:compile('TUV_J(35,THETA)', '', 'eval'), 31 | 54:compile('TUV_J(36,THETA)', '', 'eval'), 32 | 55:compile('TUV_J(39,THETA)', '', 'eval'), 33 | 56:compile('TUV_J(38,THETA)', '', 'eval'), 34 | 57:0} #No longer part of TUV 35 | def J(idx): 36 | return eval(translation[idx], None, None) 37 | -------------------------------------------------------------------------------- /pykpp/models/melchior2_soa_cpx.eqn: -------------------------------------------------------------------------------- 1 | #EQUATIONS 2 | <1> APINEN + NO3 = CH3CHO + CH3COE + oRN1 + 0.8 BiBmP : (1.19e-12) * exp(-(-490) / TEMP) {k(T)=Aexp(-B/T),A=1.19e-12,B=-490}; 3 | <2> BPINEN + NO3 = CH3CHO + CH3COE + oRN1 + 0.8 BiBmP : (2.51e-12) {k=2.51e-12}; 4 | <3> OCIMEN + NO3 = CH3CHO + CH3COE + oRN1 + 0.7 BiA0D + 0.075 BiA1D : 4.30e-9 / TEMP {k(1/T)=A/T,A=4.30e-9}; 5 | <4> APINEN + OH = 0.8 CH3CHO + 0.8 CH3COE + obio+0.30 BiA0D + 0.17 BiA1D + 0.10 BiA2D : (1.21e-11) * exp(-(-444) / TEMP) {k(T)=Aexp(-B/T),A=1.21e-11,B=-444}; 6 | <5> BPINEN + OH = 0.8 CH3CHO + 0.8 CH3COE + obio+0.07 BiA0D + 0.08 BiA1D + 0.06 BiA2D : (2.38e-11) * exp(-(-357) / TEMP) {k(T)=Aexp(-B/T),A=2.38e-11,B=-357}; 7 | <6> OCIMEN + OH = 0.8 CH3CHO + 0.8 CH3COE + obio+0.70 BiA0D + 0.075 BiA1D : 5.1e-8 / TEMP {k(1/T)=A/T,A=5.1e-8}; 8 | <7> HUMULE + OH = 0.8 CH3CHO + 0.8 CH3COE + obio+0.74 BiBmP + 0.26 BiBIP : (2.93e-10) {k=2.93e-10}; 9 | <8> C5H8 + OH = 0.232 ISOPA1 + 0.0288 ISOPA2 + 0.32 MAC + 0.42 MVK + 0.74 HCHO + obio : (2.55e-11) * exp(-(-410) / TEMP) {k(T)=Aexp(-B/T),A=2.55e-11,B=-410}; 10 | <9> APINEN + O3 = 1.27 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.62 oRO2 + 0.42 HCHO + 0.85 OH + 0.1 HO2 + 0.18 BiA0D + 0.16 BiA1D + 0.05 BiA2D : (1.01e-15) * exp(-(732) / TEMP) {k(T)=Aexp(-B/T),A=1.01e-15,B=732}; 11 | <10> BPINEN + O3 = 1.27 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.62 oRO2 + 0.42 HCHO + 0.85 OH + 0.1 HO2 + 0.09 BiA0D + 0.13 BiA1D + 0.04 BiA2D : (1.5e-17) {k=1.5e-17}; 12 | <11> OCIMEN + O3 = 1.27 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.62 oRO2 + 0.42 HCHO + 0.85 OH + 0.1 HO2 + 0.50 BiA0D + 0.055 BiA1D : 7.5e-14 / TEMP {k(1/T)=A/T,A=7.5e-14}; 13 | <12> LIMONE + O3 = 1.27 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.62 oRO2 + 0.42 HCHO + 0.85 OH + 0.1 HO2 + 0.09 BiA0D + 0.10 BiA1D : (2e-16) {k=2e-16}; 14 | <13> TOL + OH = OH + 0.004 AnA0D + 0.001 AnA1D + 0.084 AnBmP + 0.013 AnBIP : (1.81e-12) * exp(-(-355) / TEMP) {k(T)=Aexp(-B/T),A=1.81e-12,B=-355}; 15 | <14> TMB + OH = OH + 0.002 AnA0D + 0.002 AnA1D + 0.001 AnA2D + 0.088 AnBmP + 0.006 AnBIP : 9.80e-9 / TEMP {k(1/T)=A/T,A=9.80e-9}; 16 | <15> NC4H10 + OH = 0.9 CH3COE + 0.1 CH3CHO + 0.1 CH3COO + 0.9 oRO2 + 0.07 AnBmP: (1.36e-12) * exp(-(-190) / TEMP) * (300. / TEMP)**(-2) {k(T)=Aexp(-B/T)(300/T)**N,A=1.36e-12,B=-190,N=-2}; 17 | <16> OXYL + OH = MEMALD + MGLYOX + oRO2 : (1.37e-11) {k=1.37e-11}; 18 | -------------------------------------------------------------------------------- /pykpp/models/atoms: -------------------------------------------------------------------------------- 1 | #ATOMS 2 | H { 1 Hydrogen }; 3 | He { 2 Helium }; 4 | Li { 3 Litium }; 5 | Be { 4 }; 6 | B { 5 }; 7 | C { 6 }; 8 | N { 7 }; 9 | O { 8 }; 10 | F { 9 }; 11 | Ne { 10 }; 12 | Na { 11 }; 13 | Mg { 12 }; 14 | Al { 13 }; 15 | Si { 14 }; 16 | P { 15 }; 17 | S { 16 }; 18 | Cl { 17 }; 19 | Ar { 18 }; 20 | K { 19 }; 21 | Ca { 20 }; 22 | Sc { 21 }; 23 | Ti { 22 }; 24 | V { 23 }; 25 | Cr { 24 }; 26 | Mn { 25 }; 27 | Fe { 26 }; 28 | Co { 27 }; 29 | Ni { 28 }; 30 | Cu { 29 }; 31 | Zn { 30 }; 32 | Ga { 31 }; 33 | Ge { 32 }; 34 | As { 33 }; 35 | Se { 34 }; 36 | Br { 35 }; 37 | Kr { 36 }; 38 | Rb { 37 }; 39 | Sr { 38 }; 40 | Y { 39 }; 41 | Zr { 40 }; 42 | Nb { 41 }; 43 | Mu { 42 }; 44 | Tc { 43 }; 45 | Ru { 44 }; 46 | Rh { 45 }; 47 | Pd { 46 }; 48 | Ag { 47 }; 49 | Cd { 48 }; 50 | In { 49 }; 51 | Sn { 50 }; 52 | Sb { 51 }; 53 | Te { 52 }; 54 | I { 53 }; 55 | Xe { 54 }; 56 | Cs { 55 }; 57 | Ba { 56 }; 58 | La { 57 }; 59 | Ce { 58 }; 60 | Pr { 59 }; 61 | Nd { 60 }; 62 | Pm { 61 }; 63 | Sm { 62 }; 64 | Eu { 63 }; 65 | Gd { 64 }; 66 | Tb { 65 }; 67 | Dy { 66 }; 68 | Ho { 67 }; 69 | Er { 68 }; 70 | Tm { 69 }; 71 | Yb { 70 }; 72 | Lu { 71 }; 73 | Hf { 72 }; 74 | Ta { 73 }; 75 | W { 74 }; 76 | Re { 75 }; 77 | Os { 76 }; 78 | Ir { 77 }; 79 | Pt { 78 }; 80 | Au { 79 }; 81 | Hg { 80 }; 82 | Tl { 81 }; 83 | Pb { 82 }; 84 | Bi { 83 }; 85 | Po { 84 }; 86 | At { 85 }; 87 | Rn { 86 }; 88 | Fr { 87 }; 89 | Ra { 88 }; 90 | Ac { 89 }; 91 | Th { 90 }; 92 | Pa { 91 }; 93 | U { 92 }; 94 | Np { 93 }; 95 | Pu { 94 }; 96 | Am { 95 }; 97 | Cm { 96 }; 98 | Bk { 97 }; 99 | Cf { 98 }; 100 | Es { 99 }; 101 | Fm {100 }; 102 | Md {101 }; 103 | No {102 }; 104 | Lr {103 }; 105 | Unq {104 }; 106 | Unp {105 }; 107 | Unh {106 }; 108 | -------------------------------------------------------------------------------- /pykpp/funcs/racm.py: -------------------------------------------------------------------------------- 1 | __all__ = ['RACM_TROE', 'RACM_TROE_EQUIL', 'RACM_THERMAL', 'RACM_THERMAL_T2'] 2 | from numpy import exp, log10 3 | 4 | TEMP = M = 0 5 | 6 | 7 | def update_func_world(mech, world): 8 | """ 9 | Function to update globals for user defined functions 10 | """ 11 | globals().update(world) 12 | 13 | 14 | def RACM_TROE(A0, B0, A1, B1): 15 | """ 16 | RACM_TROE equation as defined in the RACM SBOX model 17 | 18 | K0 = (A0 * (TEMP/300.0)**(-B0)) 19 | K1 = (A1 * (TEMP/300.0)**(-B1)) 20 | K0 = K0 * M 21 | K1 = K0 / K1 22 | 23 | Returns (K0 / (1.0 + K1))* \ 24 | CF**(1.0 / (1.0 / N + (log10(K1))**2)) 25 | """ 26 | # REAL A0, B0, A1, B1, C1 27 | # REAL(kind=dp) K0, K1 28 | # REAL(kind=dp), PARAMETER :: CF = 0.6_dp, N = 1._dp 29 | CF = 0.6 30 | N = 1. 31 | K0 = (A0 * (TEMP/300.0)**(-B0)) 32 | K1 = (A1 * (TEMP/300.0)**(-B1)) 33 | K0 = K0 * M 34 | K1 = K0 / K1 35 | return (K0 / (1.0 + K1)) * \ 36 | CF**(1.0 / (1.0 / N + (log10(K1))**2)) 37 | 38 | 39 | def RACM_TROE_EQUIL(A0, B0, A1, B1, A2, C2): 40 | """ 41 | RACM Troe equilibrium equation as defined in the RACM SBOX model 42 | 43 | #REAL A0, B0, A1, B1, A2, C2 44 | return RACM_TROE( A0, B0, A1, B1) * (1./A2 * exp(-C2 / TEMP)) 45 | """ 46 | # REAL A0, B0, A1, B1, A2, C2 47 | return RACM_TROE(A0, B0, A1, B1) * (1./A2 * exp(-C2 / TEMP)) 48 | 49 | 50 | def RACM_THERMAL(A0, B0): 51 | """ 52 | RACM Thermal equation as defined in the RACM SBOX model 53 | 54 | #REAL A0, B0 55 | # RACM2 reaction rates have the form K = A * EXP(-B / T) 56 | # 57 | # Translation adds a 0 C 58 | return (A0 * exp(-B0 / TEMP)) 59 | """ 60 | # REAL A0, B0 61 | # RACM2 reaction rates have the form K = A * EXP(-B / T) 62 | # 63 | # Translation adds a 0 C 64 | return (A0 * exp(-B0 / TEMP)) 65 | 66 | 67 | def RACM_THERMAL_T2(A0, B0): 68 | """ 69 | RACM Thermal T2 equation as defined in the RACM SBOX model 70 | 71 | # REAL A0, B0 72 | # REAL, PARAMETER :: C0 = 0. 73 | # 74 | # Translation adds a 0 C 75 | return (A0)*TEMP**2*exp(-(B0)/TEMP) 76 | """ 77 | # REAL A0, B0 78 | # REAL, PARAMETER :: C0 = 0. 79 | # 80 | # Translation adds a 0 C 81 | return (A0)*TEMP**2*exp(-(B0)/TEMP) 82 | -------------------------------------------------------------------------------- /pykpp/plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import matplotlib.pyplot as plt 3 | import pandas as pd 4 | from warnings import warn 5 | 6 | 7 | def plot_from_file(path, **kwds): 8 | """ 9 | See plot for more details. 10 | 11 | Note that kwds that are explicit in plot 12 | will be removed from kwds before plot 13 | evaluates these keywords 14 | """ 15 | data = pd.read_csv(path, sep=',') 16 | data = dict([(k, data[k]) for k in data.dtype.names if k != 'CFACTOR']) 17 | return plot(None, world=dict(history=data), **kwds) 18 | 19 | 20 | def plot(mech, world, fig=None, ax=None, ax_props=None, path=None, **kwds): 21 | """ 22 | mech - Not used unless now kwds are provided 23 | world - dictionary to find data in 24 | fig - optional figure to append 25 | ax - axes bounding box or axes object to be added to fig 26 | ax_props - properties of the axes 27 | kwds - each named keyword is a set of options to plot; 28 | if kwds == {}; then kwds = dict([(k, {}) for i, k in mech.monitor]) 29 | """ 30 | if ax is None: 31 | ax = (0.1, 0.3, 0.8, 0.6) 32 | if ax_props is None: 33 | ax_props = dict(xlabel='hour', ylabel='unknown') 34 | if isinstance(ax, plt.matplotlib.axes.Axes): 35 | fig = ax.figure 36 | elif fig is None: 37 | fig = plt.figure() 38 | 39 | ax = fig.add_axes(ax) 40 | plt.setp(ax, **ax_props) 41 | t = world['history']['t'] / 3600. 42 | if kwds == {}: 43 | kwds = dict([(k, {}) for i, k in mech.monitor]) 44 | 45 | for varkey, varprop in kwds.items(): 46 | CFACTOR = varprop.get( 47 | 'CFACTOR', 48 | world['history'].get('CFACTOR', world.get('CFACTOR', 1)) 49 | ) 50 | if 'CFACTOR' in varprop: 51 | del varprop['CFACTOR'] 52 | varexpr = varprop.get('expr', varkey) 53 | if 'expr' in varprop: 54 | del varprop['expr'] 55 | varprop.setdefault('label', varkey) 56 | try: 57 | var = eval(varexpr, None, world['history']) / CFACTOR 58 | ax.plot(t, var, **varprop) 59 | except Exception: 60 | warn('Skipping %s' % varexpr) 61 | 62 | handles = [_l for _l in ax.lines if _l.get_label()[:1] != '_'] 63 | labels = [_l.get_label() for _l in handles] 64 | fig.legend( 65 | handles, labels, loc='lower center', bbox_to_anchor=(0.5, 0), 66 | ncol=min(3, len(kwds)) 67 | ) 68 | if path is not None: 69 | fig.savefig(path) 70 | return fig 71 | -------------------------------------------------------------------------------- /pykpp/models/melchior1_soa_cpx.eqn: -------------------------------------------------------------------------------- 1 | #EQUATIONS 2 | <1> APINEN + NO3 = APINO3 + 0.8 BiBmP : (1.19e-12) * exp(-(-490) / TEMP) {k(T)=Aexp(-B/T),A=1.19e-12,B=-490}; 3 | <2> BPINEN + NO3 = APINO3 + 0.8 BiBmP : (2.51e-12) {k=2.51e-12}; 4 | <3> OCIMEN + NO3 = APINO3 + 0.7 BiA0D + 0.075 BiA1D : 4.3e-9 / TEMP {k(1/T)=A/T,A=4.3e-9}; 5 | <4> APINEN + OH = APIOH + 0.30 BiA0D + 0.17 BiA1D + 0.10 BiA2D : (1.21e-11) * exp(-(-444) / TEMP) {k(T)=Aexp(-B/T),A=1.21e-11,B=-444}; 6 | <5> BPINEN + OH = APIOH + 0.07 BiA0D + 0.08 BiA1D + 0.06 BiA2D : (2.38e-11) * exp(-(-357) / TEMP) {k(T)=Aexp(-B/T),A=2.38e-11,B=-357}; 7 | <6> OCIMEN + OH = APIOH + 0.70 BiA0D + 0.075 BiA1D : 5.1e-8 / TEMP {k(1/T)=A/T,A=5.1e-8}; 8 | <7> HUMULE + OH = APIOH + 0.74 BiBmP + 0.26 BiBIP : (2.93e-10) {k=2.93e-10}; 9 | <8> APINEN + O3 = 0.65 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.2 C2H5O2 + 0.42 CH3CHX + 0.85 OH + 0.1 HO2 + 0.18 BiA0D + 0.16 BiA1D + 0.05 BiA2D : (1.01e-15) * exp(-(732) / TEMP) {k(T)=Aexp(-B/T),A=1.01e-15,B=732}; 10 | <9> BPINEN + O3 = 0.65 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.2 C2H5O2 + 0.42 CH3CHX + 0.85 OH + 0.1 HO2 + 0.09 BiA0D + 0.13 BiA1D + 0.04 BiA2D : (1.5e-17) {k=1.5e-17}; 11 | <10> OCIMEN + O3 = 0.65 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.2 C2H5O2 + 0.42 CH3CHX + 0.85 OH + 0.1 HO2 + 0.5 BiA0D + 0.055 BiA1D : 7.5e-14 / TEMP {k(1/T)=A/T,A=7.5e-14}; 12 | <11> LIMONE + O3 = 0.65 CH3CHO + 0.53 CH3COE + 0.14 CO + 0.2 C2H5O2 + 0.42 CH3CHX + 0.85 OH + 0.1 HO2 + 0.09 BiA0D + 0.10 BiA1D : (2e-16) {k=2e-16}; 13 | <12> C5H8 + OH = 0.232 ISOPA1 + 0.0288 ISOPA2 + RO2IP1 : (2.55e-11) * exp(-(-410) / TEMP) {k(T)=Aexp(-B/T),A=2.55e-11,B=-410}; 14 | <13> TOL + OH = OH + 0.004 AnA0D + 0.001 AnA1D + 0.084 AnBmP + 0.013 AnBIP : (1.81e-12) * exp(-(-355) / TEMP) {k(T)=Aexp(-B/T),A=1.81e-12,B=-355}; 15 | <14> TMB + OH = OH + 0.002 AnA0D + 0.002 AnA1D + 0.001 AnA2D + 0.088 AnBmP + 0.006 AnBIP : 9.80e-9 / TEMP {k(1/T)=A/T,A=9.80e-9}; 16 | <15> NC4H10 + OH = SECC4H + 0.07 AnBmP: (1.36e-12) * exp(-(-190) / TEMP) * (300. / TEMP)**(-2) {k(T)=Aexp(-B/T)(300/T)**N,A=1.36e-12,B=-190,N=-2}; 17 | <16> OXYL + OH = OXYL1 : (1.37e-11) {k=1.37e-11}; 18 | <17> AnA2D = X : (1e-18) {k=1e-18}; 19 | <18> AnA1D = X : (1e-18) {k=1e-18}; 20 | <19> AnA0D = X : (1e-18) {k=1e-18}; 21 | <20> AnBmP = X : (1e-18) {k=1e-18}; 22 | <21> AnBIP = X : (1e-18) {k=1e-18}; 23 | <22> BiA2D = X : (1e-18) {k=1e-18}; 24 | <23> BiA1D = X : (1e-18) {k=1e-18}; 25 | <24> BiA0D = X : (1e-18) {k=1e-18}; 26 | <25> BiBmP = X : (1e-18) {k=1e-18}; 27 | <26> ISOPA1 = X : (1e-18) {k=1e-18}; 28 | <27> ISOPA2 = X : (1e-18) {k=1e-18}; 29 | <28> AnA2DAQ = X : (1e-18) {k=1e-18}; 30 | <29> AnA1DAQ = X : (1e-18) {k=1e-18}; 31 | <30> AnA0DAQ = X : (1e-18) {k=1e-18}; 32 | <31> AnBmPAQ = X : (1e-18) {k=1e-18}; 33 | <32> AnBIPAQ = X : (1e-18) {k=1e-18}; 34 | <33> BiA2DAQ = X : (1e-18) {k=1e-18}; 35 | <34> BiA1DAQ = X : (1e-18) {k=1e-18}; 36 | <35> BiA0DAQ = X : (1e-18) {k=1e-18}; 37 | <36> BiBmPAQ = X : (1e-18) {k=1e-18}; 38 | <37> ISOPA1AQ = X : (1e-18) {k=1e-18}; 39 | <38> ISOPA2AQ = X : (1e-18) {k=1e-18}; 40 | -------------------------------------------------------------------------------- /pykpp/funcs/kpp.py: -------------------------------------------------------------------------------- 1 | __all__ = ['ARR', 'ARR2', 'EP2', 'EP3', 'FALL', 'DP3', 'k_3rd', 'k_arr'] 2 | from numpy import exp, log10 3 | 4 | TEMP = M = 0 5 | 6 | 7 | def update_func_world(mech, world): 8 | """ 9 | Function to update globals for user defined functions 10 | """ 11 | globals().update(world) 12 | 13 | 14 | def ARR(A0, B0, C0): 15 | """ 16 | A0, B0 and C0 - numeric values used to 17 | calculate a reaction rate (1/s) based on 18 | the Arrhenius equation in the following 19 | form: 20 | 21 | A0 * exp(-B0/TEMP) * (TEMP / 300.)**(C0) 22 | 23 | Returns a rate in per time 24 | """ 25 | out = A0 * exp(-B0 / TEMP) * (TEMP / 300.0)**(C0) 26 | return out 27 | 28 | 29 | def ARR2(A0, B0): 30 | """ 31 | A0 and B0 - numeric values used to 32 | calculate a reaction rate (1/s) based on 33 | the Arrhenius equation in the following 34 | form: 35 | 36 | A0 * exp(B0/TEMP) 37 | 38 | Returns a rate in per time 39 | 40 | Note: ARR2 sign of B0 is different than ARR 41 | """ 42 | out = A0 * exp(B0 / TEMP) 43 | return out 44 | 45 | 46 | def EP2(A0, C0, A2, C2, A3, C3): 47 | """ 48 | A0, C0, A2, C2, A3, and C3 - numeric 49 | values used to calculate 3 rates (K0, K2, 50 | and K3), each of the form A * exp(-C / TEMP), 51 | to return a rate of the following form: 52 | 53 | K0 + K3 * M * 1e6 / (1. + K3 * M * 1e6 / K2) 54 | 55 | Returns a rate in per time 56 | """ 57 | K0 = A0 * exp(-C0 / TEMP) 58 | K2 = A2 * exp(-C2 / TEMP) 59 | K3 = A3 * exp(-C3 / TEMP) 60 | K3 = K3 * M * 1.0E6 61 | return K0 + K3 / (1.0 + K3 / K2) 62 | 63 | 64 | def EP3(A1, C1, A2, C2): 65 | """ 66 | A1, C1, A2, and C2 - numeric 67 | values used to calculate 3 rates (K0, K2, 68 | and K3), each of the form A * exp(-C / TEMP), 69 | to return a rate of the following form: 70 | 71 | K1 + K2 * M * 1e6 72 | 73 | Returns a rate in per time 74 | """ 75 | K1 = A1 * exp(-C1 / TEMP) 76 | K2 = A2 * exp(-C2 / TEMP) 77 | return K1 + K2 * (1.0E6 * M) 78 | 79 | 80 | def FALL(A0, B0, C0, A1, B1, C1, CF): 81 | """ 82 | Troe fall off equation 83 | 84 | A0, B0, C0, A1, B1, C1 - numeric values 85 | to calculate 2 reaction rates (K0, K1) using ARR 86 | function; returns a rate in the following form 87 | 88 | K0M = K0 * M * 1e6 89 | KR = K0 / K1 90 | 91 | Returns (K0M / (1.0 + KR))* CF**(1.0 / (1.0 + (log10(KR))**2)) 92 | """ 93 | 94 | K0 = ARR(A0, B0, C0) 95 | K1 = ARR(A1, B1, C1) 96 | K0 = K0 * M * 1.0E6 97 | K1 = K0 / K1 98 | return (K0 / (1.0 + K1)) * CF**(1.0 / (1.0 + (log10(K1))**2)) 99 | 100 | 101 | def k_3rd(temp, cair, k0_300K, n, kinf_300K, m, fc): 102 | """ 103 | """ 104 | zt_help = 300. / temp 105 | k0_T = k0_300K * zt_help**(n) * cair # k_0 at current T 106 | kinf_T = kinf_300K * zt_help**(m) # k_inf at current T 107 | k_ratio = k0_T / kinf_T 108 | return k0_T / (1. + k_ratio) * fc**(1. / (1. + log10(k_ratio)**2)) 109 | 110 | 111 | DP3 = EP3 112 | 113 | 114 | def k_arr(k_298, tdep, temp): 115 | """ 116 | """ 117 | return k_298 * exp(tdep * (1. / temp - 3.3540E-3)) # 1/298.15=3.3540e-3 118 | -------------------------------------------------------------------------------- /pykpp/morpho/jtable.py: -------------------------------------------------------------------------------- 1 | import os 2 | from numpy import * 3 | from datetime import datetime 4 | 5 | months = dict(MY = 5, 6 | JN = 6, 7 | JL = 7, 8 | AU = 8, 9 | ST = 9, 10 | OC = 10,) 11 | 12 | def read_jtable(path, seconds_since_start = True): 13 | fname = os.path.basename(path) 14 | month = months[fname[:2]] 15 | day = int(fname[2:4]) 16 | year = int(fname[4:6]) 17 | if year < 80: 18 | year += 2000 19 | 20 | lines = file(path).readlines() 21 | location = 'header' 22 | keys = [] 23 | units = [] 24 | temp = '' 25 | values = [] 26 | times = [] 27 | for line in lines: 28 | if location == 'header': 29 | if line.strip() == '<': 30 | location = 'meta' 31 | elif location == 'meta': 32 | if line.strip() == '>': 33 | location = 'data' 34 | else: 35 | key, unit = map(str.strip, line.split(';')[0].split(',')) 36 | keys.append(key) 37 | units.append(unit) 38 | elif location == 'data': 39 | if line.strip() in ('{', '}'): 40 | continue 41 | else: 42 | temp += line 43 | if '}' in line: 44 | temp = temp.replace('}', '').replace('{', '').strip() 45 | vals = temp.split() 46 | timestr = vals.pop(0) 47 | time = datetime(year, month, day, int(timestr[:2]), int(timestr[2:4])) 48 | vals = map(float, vals) 49 | vals = [time] + vals 50 | times.append(time) 51 | values.append(vals) 52 | temp = '' 53 | names = keys 54 | formats = ['f'] * len(vals) 55 | if not seconds_since_start: 56 | formats[0] = 'object' 57 | output = zeros(len(values), dtype = dtype(dict(names = names, formats = formats))) 58 | start_time = values[0][keys.index('TIME')] 59 | for ri, row in enumerate(values): 60 | for i, (key, unit, val) in enumerate(zip(keys, units, row)): 61 | factor = {'PER MIN': 60., '1000*PER MIN': 60. / 1000, 'HHMM': 1}[unit] 62 | if factor != 1: 63 | val *= factor 64 | if seconds_since_start: 65 | if key == 'TIME': 66 | val = (val - start_time).total_seconds() 67 | output[key][ri] = val 68 | return output 69 | 70 | def get_jtable_funcs(path): 71 | """ 72 | Creates two functions 73 | Update_JTABLE(mech, world) - prepares JTABLE values for a 74 | time (t) in world 75 | JTABLE(key) - Returns jvalue in 1/s for time at last Update_JTABLE 76 | """ 77 | 78 | def Update_JTABLE(mech, world): 79 | global joutput 80 | global jtimes 81 | global jvalues 82 | try: 83 | len(joutput) 84 | except: 85 | joutput = read_jtable(path, seconds_since_start = True) 86 | jtimes = joutput['TIME'] 87 | t = world['t'] 88 | jvalues = dict([(k, interp(t, jtimes, joutput[k])) for k in joutput.dtype.names]) 89 | 90 | def JTABLE(key): 91 | global jvalues 92 | out = jvalues[key] 93 | return out 94 | return Update_JTABLE, JTABLE -------------------------------------------------------------------------------- /pykpp/tests/test_simple.py: -------------------------------------------------------------------------------- 1 | __doc__ = """ 2 | This test is a simple example from Seinfeld and Pandis ACP Second edition 3 | on pg 240.[1]. 4 | 5 | we extract a basic chemical mechanism with 1 primary organic oxidation 6 | (R1). Under high NOx conditions, the primary organic oxidation produces 7 | 2 peroxy radical NO oxidations (R2,R3) to produce more radicals (i.e., 8 | HO2 or OH). The produced NO2 can photolyze (R4) to reproduce NO and an 9 | odd oxygen (O3P) that is assumed to instantaneously produce ozone. Under 10 | high-NOx conditions, the ozone can be lost by oxidizing NO (R5). Finally, 11 | radicals can be removed lost by producing nitric acid (R6) or peroxides 12 | (R7,R8). Lastly, we add an artificial source of HOx (here defined as HO2 13 | + OH) (R9). 14 | 15 | [Seinfeld and Pandis Pg 240](https://books.google.com/books?id=YH2K9eWsZOcC&lpg=PT220&vq=RH%20PHOx&pg=PT220#v=onepage&f=false) 16 | """ 17 | 18 | mechstr = """ 19 | #EQUATIONS 20 | {R1} RH + OH = RO2 + H2O : 26.3e-12; 21 | {R2} RO2 + NO = NO2 + RCHO + HO2 : 7.7e-12; 22 | {R3} HO2 + NO = NO2 + OH : 8.1e-12; 23 | {R4} NO2 = NO + O3 : jno2 ; 24 | {R5} O3 + NO = NO2 + O2 : 1.9e-14 ; 25 | {R6} OH + NO2 = HNO3 : kohno2 ; 26 | {R7} HO2 + HO2 = H2O2 + O2 : 2.9e-12 ; 27 | {R8} RO2 + HO2 = ROOH + O2 : 5.2e-12 ; 28 | {R9} EMISSION = OH : PHOx; 29 | 30 | #INLINE PY_INIT 31 | t=TSTART=6*3600 32 | TEND=10*3600+TSTART 33 | P = 99600. 34 | TEMP = 298.15 35 | DT = 60. 36 | MONITOR_DT = 3600. 37 | StartDate = 'datetime(2010, 7, 14)' 38 | Latitude_Degrees = 40. 39 | Longitude_Degrees = 0.00E+00 40 | 41 | jno2 = .015; 42 | kohno2 = 1.1e-11; 43 | #ENDINLINE 44 | #MONITOR O3; RH; NO; NO2; OH; HO2; 45 | #INTEGRATOR odeint; 46 | 47 | #INITVALUES 48 | CFACTOR = P * Avogadro / R / TEMP * centi **3 * nano {ppb-to-molecules/cm3} 49 | ALL_SPEC=1e-32*CFACTOR; 50 | M=1e9 51 | TOTALNOx=10. 52 | RH = 200. 53 | O2=.21*M 54 | N2=.79*M 55 | H2O=0.01*M 56 | O3=30. 57 | NO = 0.1 * TOTALNOx 58 | NO2 = 0.9 * TOTALNOx 59 | PHOx = .1e-3 * CFACTOR 60 | {B = 210.; what to do about B?} 61 | """ 62 | 63 | 64 | def test_simple(): 65 | from warnings import warn 66 | import numpy as np 67 | import io 68 | import pandas as pd 69 | from ..mech import Mech 70 | 71 | mechdefn = io.StringIO(mechstr) 72 | mech1 = Mech(mechdefn, incr=3600, monitor_incr=3600, verbose=0) 73 | mech1.run(verbose=1, debug=False, solver='odeint') 74 | ode_df = mech1.get_output() 75 | mechdefn = io.StringIO(mechstr) 76 | mech2 = Mech(mechdefn, incr=3600, monitor_incr=3600, verbose=0) 77 | mech2.run( 78 | verbose=1, debug=False, solver='lsoda', max_order_ns=2, max_order_s=2 79 | ) 80 | lsoda_df = mech2.get_output() 81 | mechdefn = io.StringIO(mechstr) 82 | mech3 = Mech(mechdefn, incr=3600, monitor_incr=3600, verbose=0) 83 | mech3.run(verbose=1, debug=False, solver='vode') 84 | vode_df = mech3.get_output() 85 | for ok in ['t', 'O3', 'NO2', 'RH']: 86 | sumdf = pd.DataFrame(dict( 87 | ode=ode_df[ok], lsoda=lsoda_df[ok], vode=vode_df[ok] 88 | )) 89 | sumdf['del'] = sumdf.max(axis=1) - sumdf.min(axis=1) 90 | sumdf['del%'] = sumdf['del'] / sumdf.mean(axis=1) * 100 91 | warn(f'\nKey: {ok}\n' + sumdf.iloc[::60].to_markdown()) 92 | assert np.allclose(ode_df['O3'], lsoda_df['O3'], rtol=5) 93 | assert np.allclose(ode_df['O3'], vode_df['O3'], rtol=5) 94 | -------------------------------------------------------------------------------- /pykpp/models/prep/sbox2kpp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import re 4 | multispace = re.compile('[ ]{2,100}', re.M) 5 | continuation = re.compile('\n&', re.M) 6 | comment = re.compile('![^\n]*\n', re.M) 7 | thermal = re.compile('\nTHERMAL A-FACT ([\d.E\-+]+) E/R ([\d.E\-+]+)', re.M) 8 | special = re.compile('\nSPECIAL RWK\(\#\) = (.*?)(?=\n)', re.M) 9 | thermalt2 = re.compile('\nTHERMAL-T2 A-FACT ([\d.E\-+]+) E/R ([\d.E\-+]+)', re.M) 10 | photolysis = re.compile(r'\nPHOTOLYSIS', re.M) 11 | stoic = re.compile(r'(?<=[=+}])\s*(?:([\d.]+)\s+)?([A-Z]\S+)[ ]*(?=[^:+*]+:)') 12 | troe = re.compile(r'\nTROE KO ([\d.Ee\-]+) N ([\d.Ee\-]+) KINF ([\d.Ee\-]+) M ([\d.Ee\-]+)', re.M) 13 | troeequil = re.compile(r'\nTROE-EQUIL KO ([\d.Ee\-]+) N ([\d.Ee\-]+) KINF ([\d.Ee\-]+) M ([\d.Ee\-]+)\n A-FACT ([\d.Ee\-]+) B ([\d.Ee\-]+)', re.M) 14 | rxnn = re.compile('(^\d{3})\s', re.M) 15 | constant_spcs = "O2 M N2 H2 H2O".split() 16 | 17 | constant_spc_re = re.compile(r'((?:(?:\+\s*)?\b(?:' + '|'.join(constant_spcs) + r')\b(?:\s*\+\s*)?)+)', re.M) 18 | constant_spc_rct = re.compile(r'\{((?:(?:\+\s*)?\b(?:' + '|'.join(constant_spcs) + r')\b(?:\s*\+\s*)?)+)\}.*?=.*?\n', re.M) 19 | 20 | 21 | def stoic_repl(matchobj): 22 | stoic, spc = matchobj.groups() 23 | if stoic is None: 24 | return ' %s + ' % spc 25 | else: 26 | return ' %s %s + ' % (stoic, spc) 27 | 28 | def sbox2kpp(text): 29 | text = rxnn.sub(r'<\1> ', text) 30 | text = comment.sub('', text) 31 | text = continuation.sub(r' ', text) 32 | text = re.sub(r'->', '= ', text) 33 | text = multispace.sub(' ', text) 34 | text = thermal.sub(r' : \1 * exp(-(\2) / TEMP );', text) 35 | text = thermalt2.sub(r' : \1 * TEMP**2 * exp(-(\2) / TEMP);', text) 36 | text = photolysis.sub(r' : TUV_J(, THETA);', text) 37 | text = troe.sub(r' : RACM_TROE(\1, \2, \3, \4);', text) 38 | text = troeequil.sub(r' : RACM_TROE_EQUIL(\1, \2, \3, \4, \5, \6);', text) 39 | text = special.sub(r' : \1;', text) 40 | new_text = text 41 | old_text = '' 42 | while old_text != new_text: 43 | old_text = new_text 44 | new_text = stoic.sub(stoic_repl, old_text) 45 | 46 | text = re.sub(r'\+\s+(?==)', '', new_text) 47 | text = re.sub(r'\+\s+(?=:)', '', text) 48 | text = re.sub(r'=\s+:', '= DUMMY :', text) 49 | text = re.sub(r'\n\n', '\n', text) 50 | text = re.sub(r'} 1.000', '} ', text) 51 | text = re.sub(r' \* exp\(-\(0\.0\) / TEMP \)', '', text) 52 | text = re.sub(r'\n\s*\n', r'\n', text) 53 | def scaleconstants(matcho): 54 | groups = [[vs.strip() for vs in v.split('+') if vs.strip() != ''] for v in matcho.groups() if v is not None] 55 | outtext = matcho.group() 56 | 57 | for group in groups: 58 | outtext = outtext.replace(':', ': ' + ' * '.join(group) + ' *') 59 | return outtext 60 | 61 | text = constant_spc_re.sub(r'{\1}', text) 62 | text = constant_spc_rct.sub(scaleconstants, text) 63 | text = text.replace('= :', '= DUMMY :') 64 | return text 65 | 66 | def print_usage(): 67 | print( """ 68 | 69 | Usage: %s inputpath 70 | 71 | Converts SBOX (RACM2) formatted reactions to KPP formatted 72 | reactions. Output is sent to stdout. Photolysis reactions 73 | must be updated manually after the conversion. 74 | 75 | Example: 76 | %s RACM2_sbox > RACM2_sbox.kpp 77 | 78 | """ % (sys.argv[0], sys.argv[0])) 79 | 80 | if __name__ == '__main__': 81 | import sys 82 | if len(sys.argv) != 2: 83 | print_usage() 84 | exit() 85 | try: 86 | text = file(sys.argv[1], 'r').read() 87 | text = sbox2kpp(text) 88 | print(text) 89 | except Exception, e: 90 | print_usage() 91 | raise e -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | from sphinx_gallery.sorting import ExplicitOrder 16 | # from sphinx_gallery.sorting import ExampleTitleSortKey 17 | from sphinx_gallery.sorting import FileNameSortKey 18 | pykpproot = os.path.abspath('../../') 19 | sys.path.insert(0, pykpproot) 20 | 21 | 22 | with open('../../pykpp/__init__.py', 'r') as initf: 23 | for _l in initf.readlines(): 24 | if _l.startswith('__version__ = '): 25 | release = _l.split(' = ')[-1][1:-1] 26 | break 27 | else: 28 | release = '0.0.0' 29 | 30 | # -- Project information ----------------------------------------------------- 31 | 32 | project = 'pykpp' 33 | copyright = '2023, Barron H. Henderson' 34 | author = 'Barron H. Henderson' 35 | 36 | 37 | # -- General configuration --------------------------------------------------- 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.autosummary', 45 | 'sphinx.ext.githubpages', 46 | 'sphinx.ext.intersphinx', 47 | 'sphinx.ext.mathjax', 48 | 'sphinx.ext.viewcode', 49 | 'IPython.sphinxext.ipython_directive', 50 | 'IPython.sphinxext.ipython_console_highlighting', 51 | 'matplotlib.sphinxext.plot_directive', 52 | 'sphinx_copybutton', 53 | 'sphinx_design', 54 | #'sphinx_rtd_theme', 55 | 'myst_nb', 56 | 'sphinx_gallery.gen_gallery', 57 | 'sphinx.ext.napoleon', 58 | ] 59 | 60 | sphinx_gallery_conf = { 61 | 'examples_dirs': '../../examples', 62 | 'gallery_dirs': 'auto_examples', 63 | 'subsection_order': ExplicitOrder([ 64 | '../../examples', 65 | '../../examples/knote_ae_2015', 66 | ]), 67 | 'within_subsection_order': FileNameSortKey, 68 | } 69 | 70 | # Generate the API documentation when building 71 | autoclass_content = 'both' 72 | autosummary_generate = True 73 | autosummary_imported_members = True 74 | 75 | html_sidebars = { 76 | 'userguide': ['searchbox.html', 'sidebar-nav-bs.html'], 77 | 'API': ['searchbox.html', 'sidebar-nav-bs.html'], 78 | 'examples': ['searchbox.html', 'sidebar-nav-bs.html'], 79 | 'notebook-gallery': ['searchbox.html', 'sidebar-nav-bs.html'], 80 | } 81 | 82 | # Add any paths that contain templates here, relative to this directory. 83 | templates_path = ['_templates'] 84 | 85 | # List of patterns, relative to source directory, that match files and 86 | # directories to ignore when looking for source files. 87 | # This pattern also affects html_static_path and html_extra_path. 88 | exclude_patterns = ['_build', '**.ipynb_checkpoints'] 89 | 90 | 91 | # -- Options for HTML output ------------------------------------------------- 92 | 93 | # The theme to use for HTML and HTML Help pages. See the documentation for 94 | # a list of builtin themes. 95 | # 96 | # html_theme = 'sphinx_rtd_theme' 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | html_static_path = ['_static'] 102 | -------------------------------------------------------------------------------- /pykpp/models/prep/am32kpp.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import re 4 | intxt = file(sys.argv[1], 'r').read() 5 | 6 | counter = 0 7 | commentonly = re.compile('(=\s*{[^}]+}\s*):') 8 | intertspcs = r'(?:\b(?:M|H2|CO2|H2O|hv)\b)' 9 | stoic = r'(?:\d*\.?\d+\*)' 10 | first = '(?:(?<=)\s*' + stoic + '?' + intertspcs + '\s*\+?)' 11 | second = r'(?:\+[ ]*' + stoic + '?' + intertspcs + ')' 12 | inert = re.compile('((?:(?:' + first + '|' + second + ')\s*)+)', flags = re.M) 13 | def makedble(ra_in): 14 | ra_in = ra_in.strip() 15 | if 'd' in ra_in or 'D' in ra_in or ra_in == '': 16 | ra_out = ra_in 17 | elif 'e' in ra_in: 18 | ra_out = ra_in.replace('e', 'd') 19 | elif 'E' in ra_in: 20 | ra_out = ra_in.replace('E', 'D') 21 | elif '.' in ra_in: 22 | ra_out = ra_in + 'd0' 23 | else: 24 | ra_out = ra_in + '.d0' 25 | return ra_out 26 | 27 | def rateform(matcho): 28 | global counter 29 | gd = matcho.groupdict() 30 | kwds = {} 31 | kwds.update(gd) 32 | if kwds['more'] is None: 33 | kwds['more'] = '' 34 | if kwds['rateargs'] is None: 35 | kwds['rateargs'] = '' 36 | rateargs = kwds['rateargs'].strip() 37 | rateargs = [makedble(ra) for ra in rateargs.split(',')] 38 | kwds['rateargs'] = rateargs 39 | label = gd['label'] 40 | ratefunc = kwds['ratefunc'] = 'am3_' + {None: 'standard'}.get(label, label) 41 | nargs = len(rateargs) 42 | minarg = dict(am3_standard = 3).get(ratefunc, nargs) 43 | rateargs = rateargs + ['0.0d0'] * (minarg - nargs) 44 | kwds['rateargs'] = ', '.join(rateargs) 45 | counter += 1 46 | kwds['counter'] = counter 47 | reaction = (kwds['reaction'] + kwds['more']).replace('\t', ' ') 48 | reactants, products = reaction.split('->') 49 | reactants = inert.sub(r'{\1}', reactants) 50 | products = inert.sub(r'{\1}', products) 51 | reaction = reactants + '->' + products 52 | kwds['factor'] = '' 53 | kwds['reaction'] = reaction 54 | 55 | out = '%(reaction)s : %(factor)s%(ratefunc)s(%(rateargs)s);\n' % kwds 56 | out = out.replace('*', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ').replace(' ', ' ') 57 | if kwds['comment'] == '*': 58 | out = '{active?}' + out 59 | return '\n' + out 60 | 61 | 62 | species_g = re.search(r'^\s*Explicit\s*?\n(?P.+)^\s*End Explicit\s*Implicit\s*\n(?P.+)^\s*End Implicit', intxt, re.M + re.DOTALL).groupdict() 63 | species_txt = '#DEFVAR\n' + ' = IGNORE;\n'.join((species_g['explicit'] + '\n'+ species_g['implicit']).strip().replace('\n', ',').replace(' ', '').replace(',,', ',').replace(',,', ',').replace(',,', ',').split(',')) + ' = IGNORE;\n' 64 | photolysis_txt = re.search(r'^\s*Photolysis\s*\n(?P.+)^\s*End Photolysis', intxt, re.M + re.DOTALL).groupdict()['photolysis'] 65 | kinetic_txt = re.search(r'^\s*Reactions\s*\n(?P.+)^\s*End Reactions', intxt, re.M + re.DOTALL).groupdict()['kinetic'] 66 | 67 | n = -1 68 | while n != 0: 69 | photolysis_txt, n = re.subn(r'(?:^|\n)\s*(?:\[(\S+)\])?\s*([0-9a-zA-Z* >+-.\t]+)', r'\n{\1}: \2: TUV_J(\1, THETA, 1);', photolysis_txt, flags = re.M) 70 | photolysis_txt = inert.sub(r'{\1}', photolysis_txt) 71 | 72 | n = -1 73 | while n != 0: 74 | kinetic_txt, n = re.subn(r'(?:^|\n)(?P\*)?\s*(?:\[(?P