Skip to main content

ChemPy 是一个 Python 包,可用于解决化学问题。

项目描述

构建状态 PyPI 版本 蟒蛇版本 执照 空速速度 覆盖范围 开源软件期刊 DOI <nav class="contents" id="contents" role="doc-toc">

内容

</nav>

关于 ChemPy

ChemPy 是一个Python包,可用于化学(主要是物理/无机/分析化学)。目前包括:

  • 化学动力学的数值积分例程(ODE 求解器前端)

  • 集成速率表达式(和方便的拟合例程)

  • 平衡求解器(包括多相系统)

  • 物理化学关系:

    • Debye-Hückel 表达式

    • Arrhenius & Eyring 方程

    • 爱因斯坦-斯莫鲁霍夫斯基方程

  • 属性(文献中的纯 python 实现)

    • 作为温度函数的水密度

    • 作为温度和压力函数的水介电常数

    • 作为温度函数的水扩散率

    • 作为温度函数的水粘度

    • 硫酸浓度与温度和重量分数 H₂SO₄ 的函数关系

    • 更多内容……(欢迎投稿!)

文档

最简单的入门方法是查看本 README 中的示例,以及 jupyter notebooks。此外,这里还有最新稳定版本的自动生成的 API 文档 (这里是开发版本的 API 文档)。

安装

安装 ChemPy 及其(可选)依赖项的最简单方法是使用 conda 包管理器

$ conda install -c bjodah chempy pytest
$ pytest -rs -W ignore::chempy.ChemPyDeprecationWarning --pyargs chempy

目前 conda 包只为 Linux 提供。在 Windows 和 OS X 上,您需要使用pip代替:

$ python3 -m pip install chempy pytest
$ python3 -m pytest -rs -W ignore::chempy.ChemPyDeprecationWarning --pyargs chempy

除了 SciPy 中的那些(用于求解非线性方程组和常微分方程组)之外,由于缺少一些可选的后端,将会跳过一些测试。

如果您仍在使用 Python 2,您可以使用 ChemPy 的长期支持 0.6.x 分支,它将继续接收错误修复:

$ python2 -m pip install "chempy<0.7"

可选依赖项

如果您使用conda安装 ChemPy,则可以跳过本节。但是,如果您使用pip,则默认安装是通过以下方式实现的:

$ python3 -m pip install --user --upgrade chempy pytest
$ python3 -m pytest -rs --pyargs chempy

如果你有 root 权限,你可以跳过--user标志。您可能有兴趣使用其他后端(除了 SciPy 提供的后端)来解决 ODE 系统和非线性优化问题:

$ python3 -m pip install chempy[all]

请注意,此选项将安装以下库(其中一些需要您的系统上存在其他库):

如果您想查看需要在基于 Debian 的系统上安装哪些软件包,您可以查看此 Dockerfile

使用 Docker

如果您安装了Docker,则可以使用它来托管 jupyter notebook 服务器:

$ ./scripts/host-jupyter-using-docker.sh . 8888

第一次运行该命令时,将下载一些依赖项。安装完成后,会出现一个可见的链接,您可以在浏览器中打开该链接。您还可以使用相同的 docker-image 运行测试套件:

$ ./scripts/host-jupyter-using-docker.sh . 0

会有一些跳过的测试(由于默认情况下没有安装一些依赖项)和相当多的警告。

例子

请参阅examples/中的演示脚本,以及一些渲染的 jupyter notebooks。您还可以浏览文档以获取更多示例。您将在下面找到一些代码片段:

解析公式

>>> from chempy import Substance
>>> ferricyanide = Substance.from_formula('Fe(CN)6-3')
>>> ferricyanide.composition == {0: -3, 26: 1, 6: 6, 7: 6}  # 0 for charge
True
>>> print(ferricyanide.unicode_name)
Fe(CN)₆³⁻
>>> print(ferricyanide.latex_name + ", " + ferricyanide.html_name)
Fe(CN)_{6}^{3-}, Fe(CN)<sub>6</sub><sup>3-</sup>
>>> print('%.3f' % ferricyanide.mass)
211.955

如您所见,在组合中,原子序数(和 0 表示电荷)用作键,每种计数成为各自的值。

化学反应的平衡化学计量

>>> from chempy import balance_stoichiometry  # Main reaction in NASA's booster rockets:
>>> reac, prod = balance_stoichiometry({'NH4ClO4', 'Al'}, {'Al2O3', 'HCl', 'H2O', 'N2'})
>>> from pprint import pprint
>>> pprint(dict(reac))
{'Al': 10, 'NH4ClO4': 6}
>>> pprint(dict(prod))
{'Al2O3': 5, 'H2O': 9, 'HCl': 6, 'N2': 3}
>>> from chempy import mass_fractions
>>> for fractions in map(mass_fractions, [reac, prod]):
...     pprint({k: '{0:.3g} wt%'.format(v*100) for k, v in fractions.items()})
...
{'Al': '27.7 wt%', 'NH4ClO4': '72.3 wt%'}
{'Al2O3': '52.3 wt%', 'H2O': '16.6 wt%', 'HCl': '22.4 wt%', 'N2': '8.62 wt%'}

ChemPy 还可以平衡反应物种更复杂的反应,并且用其他术语而不是它们的分子式来更好地描述。一个愚蠢但具有说明性的示例是如何在没有任何部分使用的包装的情况下制作煎饼:

>>> substances = {s.name: s for s in [
...     Substance('pancake', composition=dict(eggs=1, spoons_of_flour=2, cups_of_milk=1)),
...     Substance('eggs_6pack', composition=dict(eggs=6)),
...     Substance('milk_carton', composition=dict(cups_of_milk=4)),
...     Substance('flour_bag', composition=dict(spoons_of_flour=60))
... ]}
>>> pprint([dict(_) for _ in balance_stoichiometry({'eggs_6pack', 'milk_carton', 'flour_bag'},
...                                                {'pancake'}, substances=substances)])
[{'eggs_6pack': 10, 'flour_bag': 2, 'milk_carton': 15}, {'pancake': 60}]

ChemPy 甚至可以处理具有线性依赖关系(欠定系统)的反应,例如:

>>> pprint([dict(_) for _ in balance_stoichiometry({'C', 'O2'}, {'CO2', 'CO'})])  # doctest: +SKIP
[{'C': x1 + 2, 'O2': x1 + 1}, {'CO': 2, 'CO2': x1}]

上面的x1对象是 SymPy 的Symbol的一个实例。如果我们希望获得具有最小(非零)整数系数的解决方案,我们可以通过underdetermined=None

>>> pprint([dict(_) for _ in balance_stoichiometry({'C', 'O2'}, {'CO2', 'CO'}, underdetermined=None)])
[{'C': 3, 'O2': 2}, {'CO': 2, 'CO2': 1}]

但是请注意,即使该解决方案在某种意义上是“规范的”,它也只是无限数量的解决方案之一(前面的x1可能是任何整数)。

平衡反应

>>> from chempy import Equilibrium
>>> from sympy import symbols
>>> K1, K2, Kw = symbols('K1 K2 Kw')
>>> e1 = Equilibrium({'MnO4-': 1, 'H+': 8, 'e-': 5}, {'Mn+2': 1, 'H2O': 4}, K1)
>>> e2 = Equilibrium({'O2': 1, 'H2O': 2, 'e-': 4}, {'OH-': 4}, K2)
>>> coeff = Equilibrium.eliminate([e1, e2], 'e-')
>>> coeff
[4, -5]
>>> redox = e1*coeff[0] + e2*coeff[1]
>>> print(redox)
32 H+ + 4 MnO4- + 20 OH- = 26 H2O + 4 Mn+2 + 5 O2; K1**4/K2**5
>>> autoprot = Equilibrium({'H2O': 1}, {'H+': 1, 'OH-': 1}, Kw)
>>> n = redox.cancel(autoprot)
>>> n
20
>>> redox2 = redox + n*autoprot
>>> print(redox2)
12 H+ + 4 MnO4- = 6 H2O + 4 Mn+2 + 5 O2; K1**4*Kw**20/K2**5

与单位合作

chempy.units模块中提供了可用于处理单位的函数和对象。以下是 ChemPy 如何检查单位一致性的示例:

>>> from chempy import Reaction
>>> r = Reaction.from_string("H2O -> H+ + OH-; 1e-4/M/s")
Traceback (most recent call last):
...
ValueError: Unable to convert between units of "1/M" and "dimensionless"
>>> r = Reaction.from_string("H2O -> H+ + OH-; 1e-4/s")
>>> from chempy.units import to_unitless, default_units as u
>>> to_unitless(r.param, 1/u.minute)
0.006

现在.units模块用一些小的添加和变通方法包装了数量包。但是,不能保证底层包在未来版本的 ChemPy 中不会发生变化(科学 Python 生态系统中有许多用于处理单元的包)。

化学平衡

如果我们想预测碳酸氢盐溶液的 pH 值,我们只需要 pKa 和 pKw 值:

>>> from collections import defaultdict
>>> from chempy.equilibria import EqSystem
>>> eqsys = EqSystem.from_string("""HCO3- = H+ + CO3-2; 10**-10.3
... H2CO3 = H+ + HCO3-; 10**-6.3
... H2O = H+ + OH-; 10**-14/55.4
... """)  # pKa1(H2CO3) = 6.3 (implicitly incl. CO2(aq)), pKa2=10.3 & pKw=14
>>> arr, info, sane = eqsys.root(defaultdict(float, {'H2O': 55.4, 'HCO3-': 1e-2}))
>>> conc = dict(zip(eqsys.substances, arr))
>>> from math import log10
>>> print("pH: %.2f" % -log10(conc['H+']))
pH: 8.30

这是氨的另一个例子:

>>> from chempy import Equilibrium
>>> from chempy.chemistry import Species
>>> water_autop = Equilibrium({'H2O'}, {'H+', 'OH-'}, 10**-14)  # unit "molar" assumed
>>> ammonia_prot = Equilibrium({'NH4+'}, {'NH3', 'H+'}, 10**-9.24)  # same here
>>> substances = [Species.from_formula(f) for f in 'H2O OH- H+ NH3 NH4+'.split()]
>>> eqsys = EqSystem([water_autop, ammonia_prot], substances)
>>> print('\n'.join(map(str, eqsys.rxns)))  # "rxns" short for "reactions"
H2O = H+ + OH-; 1e-14
NH4+ = H+ + NH3; 5.75e-10
>>> init_conc = defaultdict(float, {'H2O': 1, 'NH3': 0.1})
>>> x, sol, sane = eqsys.root(init_conc)
>>> assert sol['success'] and sane
>>> print(', '.join('%.2g' % v for v in x))
1, 0.0013, 7.6e-12, 0.099, 0.0013

概念

ChemPy 收集方程和效用函数,用于处理离子强度等概念:

>>> from chempy.electrolytes import ionic_strength
>>> ionic_strength({'Fe+3': 0.050, 'ClO4-': 0.150}) == .3
True

请注意 ChemPy 如何从物质名称中解析电荷。还有例如经验方程和方便类可用,例如:

>>> from chempy.henry import Henry
>>> kH_O2 = Henry(1.2e-3, 1800, ref='carpenter_1966')
>>> print('%.1e' % kH_O2(298.15))
1.2e-03

要获取有关例如此类的更多信息,您可以查看API 文档

化学动力学(常微分方程组)

对化学问题建模时的一项常见任务是研究系统的时间依赖性。这个研究分支被称为 化学动力学,ChemPy 有一些类和函数来处理这些问题:

>>> from chempy import ReactionSystem  # The rate constants below are arbitrary
>>> rsys = ReactionSystem.from_string("""2 Fe+2 + H2O2 -> 2 Fe+3 + 2 OH-; 42
...     2 Fe+3 + H2O2 -> 2 Fe+2 + O2 + 2 H+; 17
...     H+ + OH- -> H2O; 1e10
...     H2O -> H+ + OH-; 1e-4""")  # "[H2O]" = 1.0 (actually 55.4 at RT)
>>> from chempy.kinetics.ode import get_odesys
>>> odesys, extra = get_odesys(rsys)
>>> from collections import defaultdict
>>> import numpy as np
>>> tout = sorted(np.concatenate((np.linspace(0, 23), np.logspace(-8, 1))))
>>> c0 = defaultdict(float, {'Fe+2': 0.05, 'H2O2': 0.1, 'H2O': 1.0, 'H+': 1e-2, 'OH-': 1e-12})
>>> result = odesys.integrate(tout, c0, atol=1e-12, rtol=1e-14)
>>> import matplotlib.pyplot as plt
>>> fig, axes = plt.subplots(1, 2, figsize=(12, 5))
>>> for ax in axes:
...     _ = result.plot(names=[k for k in rsys.substances if k != 'H2O'], ax=ax)
...     _ = ax.legend(loc='best', prop={'size': 9})
...     _ = ax.set_xlabel('Time')
...     _ = ax.set_ylabel('Concentration')
>>> _ = axes[1].set_ylim([1e-13, 1e-1])
>>> _ = axes[1].set_xscale('log')
>>> _ = axes[1].set_yscale('log')
>>> _ = fig.tight_layout()
>>> _ = fig.savefig('examples/kinetics.png', dpi=72)
https://raw.githubusercontent.com/bjodah/chempy/master/examples/kinetics.png

特性

科学的一项基本任务是仔细收集有关我们周围世界的数据。ChemPy 包含越来越多的与化学相关的科学文献中的参数化集合。以下是您如何使用这些配方之一:

>>> from chempy import Substance
>>> from chempy.properties.water_density_tanaka_2001 import water_density as rho
>>> from chempy.units import to_unitless, default_units as u
>>> water = Substance.from_formula('H2O')
>>> for T_C in (15, 25, 35):
...     concentration_H2O = rho(T=(273.15 + T_C)*u.kelvin, units=u)/water.molar_mass(units=u)
...     print('[H2O] = %.2f M (at %d °C)' % (to_unitless(concentration_H2O, u.molar), T_C))
...
[H2O] = 55.46 M (at 15 °C)
[H2O] = 55.35 M (at 25 °C)
[H2O] = 55.18 M (at 35 °C)

使用 binder 运行 notebook

仅使用网络浏览器(和互联网连接)就可以在这里浏览笔记本:(由 mybinder 背后的人提供)

粘合剂

引用

如果您在学术工作中使用 ChemPy,您可以引用以下经过同行评审的出版物:

开源软件期刊 DOI

根据您使用的底层求解器,您还应该引用相应的论文(您可以查看 JOSS 文章中的参考文献列表)。如果除了论文之外,您还需要参考 ChemPy 的特定点版本(例如可重复性),您可以从 zendodo 存档中获取每个版本的 DOI:

泽诺多 DOI

许可

源代码是开源的,并在非常宽松的 “简化(2 条款)BSD 许可”下发布。有关详细信息,请参阅许可证

也可以看看

贡献

欢迎贡献者在https://github.com/bjodah/chempy提出改进建议 (请参阅此处的更多详细信息)。

作者

Björn I. Dahlgren,联系人:
  • gmail地址:bjodah

项目详情