aerosolprops 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aerosolprops/__init__.py +56 -0
- aerosolprops/boiling_point.py +637 -0
- aerosolprops/fragmentation.py +170 -0
- aerosolprops/methods/__init__.py +10 -0
- aerosolprops/methods/evaporation.py +469 -0
- aerosolprops/methods/myrdal_yalkowsky.py +463 -0
- aerosolprops/methods/nannoolal_bp.py +732 -0
- aerosolprops/methods/nannoolal_vp.py +810 -0
- aerosolprops/methods/simpol1.py +373 -0
- aerosolprops/structure.py +198 -0
- aerosolprops/topology.py +146 -0
- aerosolprops/vapour_pressure.py +2208 -0
- aerosolprops-0.1.0.dist-info/METADATA +125 -0
- aerosolprops-0.1.0.dist-info/RECORD +17 -0
- aerosolprops-0.1.0.dist-info/WHEEL +5 -0
- aerosolprops-0.1.0.dist-info/licenses/LICENSE +21 -0
- aerosolprops-0.1.0.dist-info/top_level.txt +1 -0
aerosolprops/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Structure-derived pure-component properties for atmospheric organic
|
|
3
|
+
aerosol.
|
|
4
|
+
|
|
5
|
+
Successor to the pure-component property methods of UManSysProp
|
|
6
|
+
(Topping et al., Geosci. Model Dev. 9, 899-911, 2016), rebuilt on RDKit as
|
|
7
|
+
the structure engine and following the conventions of the ``thermo``
|
|
8
|
+
package family: strict SI units (Pa, K, mol), estimators as pure functions
|
|
9
|
+
of group counts where possible, and failures returned as ``None``.
|
|
10
|
+
|
|
11
|
+
Method coverage for v0.1:
|
|
12
|
+
|
|
13
|
+
- SIMPOL.1 vapour pressure (Pankow & Asher, ACP 8, 2773, 2008) —
|
|
14
|
+
implemented (see :class:`aerosolprops.vapour_pressure.SIMPOL1`)
|
|
15
|
+
- EVAPORATION vapour pressure (Compernolle et al., ACP 11, 9431, 2011) —
|
|
16
|
+
implemented (see :class:`aerosolprops.vapour_pressure.EVAPORATION`)
|
|
17
|
+
- Nannoolal Part 1 normal boiling point (Nannoolal et al., FPE 226, 45,
|
|
18
|
+
2004), including the SOA extension groups of Compernolle et al. (ACP 10,
|
|
19
|
+
6271, 2010) — implemented (see
|
|
20
|
+
:class:`aerosolprops.boiling_point.NannoolalBP`)
|
|
21
|
+
- Nannoolal Part 3 vapour pressure (Nannoolal et al., FPE 269, 117, 2008) —
|
|
22
|
+
implemented (see :class:`aerosolprops.vapour_pressure.NannoolalVP`)
|
|
23
|
+
- Myrdal-Yalkowsky vapour pressure (Myrdal & Yalkowsky, IECR 36, 2494,
|
|
24
|
+
1997) with the hydrogen-bonding extension of Compernolle et al. (2010) —
|
|
25
|
+
implemented (see :class:`aerosolprops.vapour_pressure.MyrdalYalkowsky`)
|
|
26
|
+
|
|
27
|
+
Boiling-point inputs may instead come from ``thermo``'s built-in Joback
|
|
28
|
+
method or from experimental data; aerosolprops does not reimplement Joback.
|
|
29
|
+
|
|
30
|
+
RDKit is a required dependency but is imported lazily: importing this
|
|
31
|
+
package pulls in neither RDKit nor ``thermo``'s group-contribution
|
|
32
|
+
machinery. Estimators consume plain group-count dictionaries — the same
|
|
33
|
+
interface ``thermo`` uses for its own RDKit-optional group-contribution
|
|
34
|
+
methods — so pieces remain extractable upstream.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
__version__ = "0.1.0.dev0"
|
|
38
|
+
|
|
39
|
+
from . import (
|
|
40
|
+
boiling_point,
|
|
41
|
+
fragmentation,
|
|
42
|
+
methods,
|
|
43
|
+
structure,
|
|
44
|
+
topology,
|
|
45
|
+
vapour_pressure,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"__version__",
|
|
50
|
+
"boiling_point",
|
|
51
|
+
"fragmentation",
|
|
52
|
+
"methods",
|
|
53
|
+
"structure",
|
|
54
|
+
"topology",
|
|
55
|
+
"vapour_pressure",
|
|
56
|
+
]
|
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Normal boiling point estimation (object-oriented wrapper layer).
|
|
3
|
+
|
|
4
|
+
Nannoolal Part 1 (Nannoolal, Rarey, Ramjugernath & Cordes, Fluid Phase
|
|
5
|
+
Equilibria 226, 45-63, 2004) via the priority-claim fragmenter of
|
|
6
|
+
:mod:`aerosolprops.fragmentation` and the pure coefficient catalogue and
|
|
7
|
+
estimator of :mod:`aerosolprops.methods.nannoolal_bp`.
|
|
8
|
+
|
|
9
|
+
This module owns everything RDKit: the SMARTS catalogue (authored
|
|
10
|
+
clean-room from the paper's group *definitions*; no third-party pattern
|
|
11
|
+
files consulted), the claimed-atom sets of every group, and the
|
|
12
|
+
second-order corrections 118-133 that no single SMARTS pattern can
|
|
13
|
+
express (ring sizes, ortho/meta/para pairs, steric bond counts,
|
|
14
|
+
molecular hydrogen count). Functional groups claim every atom of the
|
|
15
|
+
functional pattern except their flanking carbon skeleton (paper Table
|
|
16
|
+
17d: the ester group covers exactly the C(=O)O atoms); the skeleton
|
|
17
|
+
groups claim single carbons; overlaps are adjudicated by the paper's
|
|
18
|
+
priority numbers (Table 1, footnote a: lower number wins).
|
|
19
|
+
|
|
20
|
+
The aerosolprops catalogue parameterises the C/N/O/S/F/Cl/Br/I subset of
|
|
21
|
+
the paper's groups. Molecules containing other elements the paper
|
|
22
|
+
covers (Si, Ge, Sn, P, As, Se, B, Al - groups 70-93, 113, 116, 117)
|
|
23
|
+
fail cleanly with a status naming the missing family: atmospheric SOA
|
|
24
|
+
chemistry is outside their scope and transcribing them without test
|
|
25
|
+
compounds would be guesswork.
|
|
26
|
+
|
|
27
|
+
Examples
|
|
28
|
+
--------
|
|
29
|
+
Paper Table 17a (3,3,4,4-tetramethylhexane; experimental Tb 444.0 K):
|
|
30
|
+
|
|
31
|
+
>>> nnp = NannoolalBP(smi="CCC(C)(C)C(C)(C)CC")
|
|
32
|
+
>>> (nnp.success, nnp.status, nnp.n, nnp.counts)
|
|
33
|
+
(True, 'OK', 10, {1: 6, 4: 2, 6: 2, 133: 1})
|
|
34
|
+
>>> round(nnp.boiling_point(), 1)
|
|
35
|
+
429.5
|
|
36
|
+
|
|
37
|
+
Ethanol (paper Table 1, group 36 "OH short chain (<C5)"; experimental
|
|
38
|
+
Tb 351.5 K):
|
|
39
|
+
|
|
40
|
+
>>> nnp = NannoolalBP(smi="CCO")
|
|
41
|
+
>>> (nnp.success, nnp.counts, nnp.n)
|
|
42
|
+
(True, {1: 1, 7: 1, 36: 1}, 3)
|
|
43
|
+
>>> round(nnp.boiling_point(), 1)
|
|
44
|
+
333.1
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from .fragmentation import PriorityFragmenter
|
|
48
|
+
from .methods.nannoolal_bp import (
|
|
49
|
+
NANNOOLAL_BP_GROUPS,
|
|
50
|
+
nannoolal_bp_boiling_point,
|
|
51
|
+
nannoolal_bp_gi,
|
|
52
|
+
)
|
|
53
|
+
from .structure import load_rdkit, mol_from_smiles, normalize_mol
|
|
54
|
+
from .topology import ring_arc_distance
|
|
55
|
+
|
|
56
|
+
__all__ = ["NannoolalBP", "nannoolal_fragmentation"]
|
|
57
|
+
|
|
58
|
+
#: SMARTS catalogue: group ID -> (pattern, claim). The pattern is
|
|
59
|
+
#: authored from the paper's group definition; ``claim`` lists the
|
|
60
|
+
#: pattern-atom indices the group instance owns (its anchor first is not
|
|
61
|
+
#: required - the engine only needs the set). Every non-hydrogen atom
|
|
62
|
+
#: must end up claimed by exactly one instance.
|
|
63
|
+
NANNOOLAL_BP_SMARTS = {
|
|
64
|
+
# --- Carbon skeleton, chain (paper Table 1) ----------------------
|
|
65
|
+
# CH3 by neighbour type; (e) = N/O/F/Cl, (a) = aromatic atom of any
|
|
66
|
+
# element. 2 < 3 < 1 by priority; a methyl on an aromatic N or O is
|
|
67
|
+
# both (e) and (a) and goes to the lower number, 2.
|
|
68
|
+
1: ("[CH3;!R;!$([CH3]~[a]);!$([CH3]~[#7,#8,#9,#17])]", (0,)),
|
|
69
|
+
2: ("[CH3;!R;$([CH3]~[#7,#8,#9,#17])]", (0,)),
|
|
70
|
+
3: ("[CH3;!R;$([CH3]~[a])]", (0,)),
|
|
71
|
+
# Chain sp3 carbons of any hydrogen count; the qualified variants 7
|
|
72
|
+
# (electronegative neighbour) and 8 (aromatic carbon neighbour) have
|
|
73
|
+
# lower priority numbers and claim first.
|
|
74
|
+
4: ("[CH2;!R]", (0,)),
|
|
75
|
+
5: ("[CH;!R]", (0,)),
|
|
76
|
+
6: ("[C;H0;!R]", (0,)),
|
|
77
|
+
7: ("[C;!R;$([C]~[#7,#8,#9,#17])]", (0,)),
|
|
78
|
+
8: ("[C;!R;$([C]~[c])]", (0,)),
|
|
79
|
+
# Ring sp3 carbons; 12/13/14 (heteroatom or aromatic neighbours)
|
|
80
|
+
# have lower priority numbers than the plain 9/10/11.
|
|
81
|
+
9: ("[CH2;R]", (0,)),
|
|
82
|
+
10: ("[CH;R]", (0,)),
|
|
83
|
+
11: ("[C;H0;R]", (0,)),
|
|
84
|
+
# 12: ring C with an external (non-ring-bond) N/O, or any F/Cl.
|
|
85
|
+
12: ("[$([C;R]-!@[#7,#8]),$([C;R]~[#9,#17])]", (0,)),
|
|
86
|
+
# 13: ring C with the N/O inside the same ring (ring bond).
|
|
87
|
+
13: ("[$([C;R]-@[#7,#8])]", (0,)),
|
|
88
|
+
14: ("[C;R;$([C]~[c])]", (0,)),
|
|
89
|
+
# Aromatic carbons. 18 (fused, three aromatic neighbours) beats 16
|
|
90
|
+
# and 17 by priority; 17/16 split on electronegative neighbours.
|
|
91
|
+
15: ("[cH]", (0,)),
|
|
92
|
+
16: ("[cH0;!$([cH0]~[#7,#8,#9,#17])]", (0,)),
|
|
93
|
+
17: ("[cH0;$([cH0]~[#7,#8,#9,#17])]", (0,)),
|
|
94
|
+
18: ("[cH0](:[c])(:[c])~[c]", (0,)),
|
|
95
|
+
# --- Halogens ----------------------------------------------------
|
|
96
|
+
# Fluorine: the specific families (20-23, 102) have lower priority
|
|
97
|
+
# numbers than the generic 19, so the patterns can stay positive -
|
|
98
|
+
# whatever no specific family claims falls to 19 (paper: "F
|
|
99
|
+
# connected to C" as the remainder class). The neighbour-hydrogen
|
|
100
|
+
# conditions follow Table 1: H2 carbon "a" variants take exactly one
|
|
101
|
+
# further halogen, H1/H0 "b" variants take at least two/one.
|
|
102
|
+
19: ("[F][C]", (0,)),
|
|
103
|
+
20: ("[F;$([F][C]=[C])]", (0,)),
|
|
104
|
+
21: ("[F;$([F][C;H0]~[F])]", (0,)),
|
|
105
|
+
102: ("[F;$([F][C;H0]~[Cl])]", (0,)),
|
|
106
|
+
22: ("[F;$([F][C;H1]([F,Cl])[!F,!Cl])]", (0,)),
|
|
107
|
+
23: ("[F;$([F][C;H1]([F,Cl])[F,Cl])]", (0,)),
|
|
108
|
+
# Chlorine: 27 (two or more co-halogens) then 26 (one) then 25
|
|
109
|
+
# (none), descending priority; aromatic and vinyl handled first.
|
|
110
|
+
25: ("[Cl][C]", (0,)),
|
|
111
|
+
26: ("[Cl;$([Cl][C]~[F,Cl])]", (0,)),
|
|
112
|
+
27: ("[Cl;$([Cl][C]([F,Cl])[F,Cl])]", (0,)),
|
|
113
|
+
28: ("[Cl][c]", (0,)),
|
|
114
|
+
29: ("[Cl;$([Cl][C]=[C])]", (0,)),
|
|
115
|
+
30: ("[Br][C]", (0,)),
|
|
116
|
+
31: ("[Br][c]", (0,)),
|
|
117
|
+
32: ("[I][#6]", (0,)),
|
|
118
|
+
# --- Oxygen ------------------------------------------------------
|
|
119
|
+
# Alcohols 33/34/35 (carbon neighbour count; paper Table 1); 36 is
|
|
120
|
+
# algorithmic (molecules with fewer than five C: post-processed from
|
|
121
|
+
# 33/34/35); 37 is the phenol O. 44-52 carbonyl families claim the
|
|
122
|
+
# C, the Oxo O and the O/N single-bonded heteroatom, but never the
|
|
123
|
+
# flanking carbon skeleton (paper Table 17d atom lists).
|
|
124
|
+
33: ("[OX2H;$([OX2H][C;H0])]", (0,)),
|
|
125
|
+
34: ("[OX2H;$([OX2H][C;H1]([#6])[#6])]", (0,)),
|
|
126
|
+
35: ("[OX2H;$([OX2H][C;H2][#6])]", (0,)),
|
|
127
|
+
# 36 claims any remaining alcohol O (e.g. methanol, whose carbon has
|
|
128
|
+
# no C neighbour at all); the <C5 reclassification below moves every
|
|
129
|
+
# 33/34/35/36 instance into 36 for short-chain molecules (paper
|
|
130
|
+
# Table 1, note ruling: the short-chain gate precedes the neighbour
|
|
131
|
+
# count - di-isopropanolamine, Table 17b, keeps 34 at six C).
|
|
132
|
+
36: ("[OX2H;$([OX2H][C])]", (0,)),
|
|
133
|
+
37: ("[OX2H;$([OX2H][c])]", (0,)),
|
|
134
|
+
# 38: plain ether O (two C neighbours, any ring/aromatic status);
|
|
135
|
+
# furan-type O is aromatic ([o], group 65) and never matches [OX2].
|
|
136
|
+
38: ("[OX2;$([OX2]([#6])[#6])]", (0,)),
|
|
137
|
+
# 39: epoxide - the 3-ring O plus both its ring carbons (priority
|
|
138
|
+
# beats the plain ether 38).
|
|
139
|
+
39: ("[OX2;r3]([C;r3])[C;r3]", (0, 1, 2)),
|
|
140
|
+
44: ("[CX3](=[OX1])([OX2H1])[#6]", (0, 1, 2)),
|
|
141
|
+
# 45/47 split acyclic/cyclic on the ester single-bond O; 47 demands
|
|
142
|
+
# a non-carbonyl C neighbour so cyclic carbonates stay whole for 103.
|
|
143
|
+
45: ("[CX3](=[OX1])([OX2;!R][#6])[#6]", (0, 1, 2)),
|
|
144
|
+
47: ("[CX3](=[OX1])([OX2;R;$([OX2]~[#6;!$([#6]=[OX1])])])[#6]",
|
|
145
|
+
(0, 1, 2)),
|
|
146
|
+
46: ("[CX3;H1](=[OX1])([OX2][#6])", (0, 1, 2)),
|
|
147
|
+
48: ("[NX3;H0]([#6])([#6])[CX3](=[OX1])", (0, 3, 4)),
|
|
148
|
+
49: ("[NX3;H1]([#6])[CX3](=[OX1])", (0, 2, 3)),
|
|
149
|
+
50: ("[NX3;H2][CX3](=[OX1])", (0, 1, 2)),
|
|
150
|
+
99: ("[CX3](=[OX1])([NX3])[OX2]", (0, 1, 2, 3)),
|
|
151
|
+
100: ("[CX3](=[OX1])([NX3])[NX3]", (0, 1, 2, 3)),
|
|
152
|
+
51: ("[CX3](=[OX1])([C])[C]", (0, 1)),
|
|
153
|
+
92: ("[CX3](=[OX1])([c])[#6]", (0, 1)),
|
|
154
|
+
52: ("[CX3;H1](=[OX1])([C])", (0, 1)),
|
|
155
|
+
90: ("[CX3;H1](=[OX1])([c])", (0, 1)),
|
|
156
|
+
94: ("[#6][OX2][OX2][#6]", (1, 2)),
|
|
157
|
+
75: ("[N;X2]([OX2H1])=[#6]", (0, 1)),
|
|
158
|
+
77: ("[CX3](=[OX1])([Cl])[#6]", (0, 1)),
|
|
159
|
+
76: ("[CX3](=[OX1])([OX2;!R][CX3](=[OX1]))", (0, 1, 2, 3, 4)),
|
|
160
|
+
96: ("[CX3](=[OX1])([OX2;R][CX3](=[OX1]))", (0, 1, 2, 3, 4)),
|
|
161
|
+
79: ("[CX3](=[OX1])([OX2;!R][#6])([OX2][#6])", (0, 1, 2, 4)),
|
|
162
|
+
103: ("[CX3](=[OX1])([OX2;R])([OX2;R])", (0, 1, 2, 3)),
|
|
163
|
+
72: ("[#6][OX2][N+](=[OX1])([OX1-])", (1, 2, 3, 4)),
|
|
164
|
+
74: ("[#6][OX2][N;X2](=[OX1])", (1, 2, 3)),
|
|
165
|
+
80: ("[#6][N;X2]=[CX2]=[OX1]", (1, 2, 3)),
|
|
166
|
+
65: ("[o](:[c]):[c]", (0,)),
|
|
167
|
+
115: ("[o;r5]~[n;r5]", (0, 1)),
|
|
168
|
+
# Compernolle (2010) Table 3 extensions; the paper has no priority
|
|
169
|
+
# numbers for them, so aerosolprops assigns engine-local ordering (a
|
|
170
|
+
# separate constant below) - 302 before 301 because the peroxy-acid
|
|
171
|
+
# OH-O also matches the hydroperoxide pattern. Nitro/PAN nitrogen is
|
|
172
|
+
# written in the charge-separated form aerosolprops' normaliser emits.
|
|
173
|
+
301: ("[OX2H1][OX2][#6]", (0, 1)),
|
|
174
|
+
302: ("[OX2H1][OX2][CX3](=[OX1])", (0, 1, 2, 3)),
|
|
175
|
+
303: ("[#6][CX3](=[OX1])([OX2][OX2][N+](=[OX1])([OX1-]))",
|
|
176
|
+
(1, 2, 3, 4, 5, 6, 7)),
|
|
177
|
+
# --- Nitrogen ----------------------------------------------------
|
|
178
|
+
# Amines split on hydrogen count; secondary amines split on ring
|
|
179
|
+
# membership (42 acyclic / 97 cyclic). The Part 1 Table 1 text for
|
|
180
|
+
# 97 ("at least one aromatic neighbour") contradicts its own example
|
|
181
|
+
# molecules (morpholine, pyrrolidine); the Part 3 revision (FPE 354
|
|
182
|
+
# (2008) Table 1) defines 97 explicitly as "in a ring (cyclic
|
|
183
|
+
# secondary amine)" with the same examples, so the cyclic reading is
|
|
184
|
+
# adopted. 66/67 are the "free electron pair" (H0) aromatic N only -
|
|
185
|
+
# a pyrrole-type ring NH is 97, and an NH bonded to a ring N (e.g.
|
|
186
|
+
# pyrazole) matches no group and refuses. 106 claims whole imidazole
|
|
187
|
+
# rings first.
|
|
188
|
+
40: ("[NX3;H2][#6]", (0,)),
|
|
189
|
+
41: ("[NX3;H2][c]", (0,)),
|
|
190
|
+
42: ("[#7X3;H1;!R]([#6])[#6]", (0,)),
|
|
191
|
+
97: ("[#7X3;H1;R]([#6;R])[#6;R]", (0,)),
|
|
192
|
+
43: ("[NX3;H0]([#6])([#6])[#6]", (0,)),
|
|
193
|
+
101: ("[N+;X4;H0]([#6])([#6])([#6])[#6]", (0,)),
|
|
194
|
+
66: ("[n;r5;H0]", (0,)),
|
|
195
|
+
67: ("[n;r6;H0]", (0,)),
|
|
196
|
+
57: ("[#6][CX2]#[NX1]", (1, 2)),
|
|
197
|
+
108: ("[#16][CX2]#[NX1]", (1, 2)),
|
|
198
|
+
111: ("[#7][CX2]#[NX1]", (1, 2)),
|
|
199
|
+
91: ("[NX2]=[#6]", (0,)),
|
|
200
|
+
# 68/69 split on the nitro-bearing carbon: aliphatic ([C]) vs
|
|
201
|
+
# aromatic ([c]); a shared [#6] context would let the lower-priority
|
|
202
|
+
# number 68 steal aromatic nitros from 69.
|
|
203
|
+
68: ("[C][N+](=[OX1])([OX1-])", (1, 2, 3)),
|
|
204
|
+
69: ("[c][N+](=[OX1])([OX1-])", (1, 2, 3)),
|
|
205
|
+
106: ("[n;r5]1[c;r5][n;r5][c;r5][c;r5]1", (0, 1, 2, 3, 4)),
|
|
206
|
+
# --- Sulfur ------------------------------------------------------
|
|
207
|
+
53: ("[SX2H1][#6]", (0,)),
|
|
208
|
+
54: ("[SX2]([#6])[#6]", (0,)),
|
|
209
|
+
55: ("[#6][SX2][SX2][#6]", (1, 2)),
|
|
210
|
+
56: ("[s]", (0,)),
|
|
211
|
+
82: ("[SX4](=[OX1])(=[OX1])([#6])[#6]", (0, 1, 2)),
|
|
212
|
+
104: ("[SX4](=[OX1])(=[OX1])([OX2][#6])([OX2][#6])", (0, 1, 2, 3, 5)),
|
|
213
|
+
105: ("[SX4](=[OX1])(=[OX1])([NX3])[#6]", (0, 1, 2)),
|
|
214
|
+
# Sulfoxide: RDKit's normaliser renders S=O sulfoxides charge-
|
|
215
|
+
# separated (CS(C)=O -> C[S+](C)[O-]); sulfones keep their double
|
|
216
|
+
# bonds (82 above), so 107 is written against the charge form only.
|
|
217
|
+
107: ("[S+;X3]([OX1-])([#6])[#6]", (0, 1)),
|
|
218
|
+
# --- Unsaturated carbon (bond-anchored; claims every sp2/sp C of
|
|
219
|
+
# the unit). The C=C families are written so the substitution
|
|
220
|
+
# condition sits on the sp2 carbon itself (59/60: the OR recursion
|
|
221
|
+
# checks the atom's own substituent or its double-bond partner's),
|
|
222
|
+
# which resolves the paper's cinnamic-alcohol example in favour of
|
|
223
|
+
# 59, not 60.
|
|
224
|
+
87: ("[C]=[C]=[C]", (0, 1, 2)),
|
|
225
|
+
88: ("[C;R]=[C;R][C;R]=[C;R]", (0, 1, 2, 3)),
|
|
226
|
+
89: ("[C;!R]=[C;!R][C;!R]=[C;!R]", (0, 1, 2, 3)),
|
|
227
|
+
95: ("[C;X2]#[C;X2][C;X2]#[C;X2]", (0, 1, 2, 3)),
|
|
228
|
+
64: ("[CH1]#[C]", (0, 1)),
|
|
229
|
+
63: ("[C]#[C]", (0, 1)),
|
|
230
|
+
61: ("[CH2]=[C]", (0, 1)),
|
|
231
|
+
60: ("[$([C]~[#7,#8,#9,#17]),$([C]=[C]~[#7,#8,#9,#17]);!R]"
|
|
232
|
+
"=[C;!R]", (0, 1)),
|
|
233
|
+
59: ("[$([C]~[c]),$([C]=[C]~[c]);!R]=[C;!R]", (0, 1)),
|
|
234
|
+
58: ("[C;!R;X2,X3]=[C;!R;X2,X3]", (0, 1)),
|
|
235
|
+
62: ("[C;R]=[C;R]", (0, 1)),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#: Engine-local claiming order for the extension groups 301-303 (the
|
|
239
|
+
#: paper and Compernolle (2010) give no priority numbers for them).
|
|
240
|
+
#: Values are chosen below every competing native group; only the
|
|
241
|
+
#: 302-before-301 relation is load-bearing (a peroxy acid otherwise
|
|
242
|
+
#: matches the hydroperoxide pattern first).
|
|
243
|
+
NANNOOLAL_BP_EXTENSION_PRIORITIES = {303: 1, 302: 2, 301: 3}
|
|
244
|
+
|
|
245
|
+
#: Elements the aerosolprops catalogue parameterises (paper groups outside
|
|
246
|
+
#: this set - Si, Ge, Sn, P, As, Se, B, Al families 70-93, 113, 116,
|
|
247
|
+
#: 117 - are deliberately not transcribed; see module docstring).
|
|
248
|
+
NANNOOLAL_BP_ELEMENTS = frozenset("C N O S F Cl Br I".split())
|
|
249
|
+
|
|
250
|
+
#: Paper group 109 ("CO connected to N", example "methyl thioacetate"
|
|
251
|
+
#: which contains no nitrogen) has no implementable definition and is
|
|
252
|
+
#: excluded from the catalogue; real amides are covered by 48-50, 99,
|
|
253
|
+
#: 100. Unclaimed C(=O)-N fragments of exotic acyl species therefore
|
|
254
|
+
#: fail the atom balance cleanly.
|
|
255
|
+
_ENGINE = None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _nannoolal_engine():
|
|
259
|
+
"""Build (once) the priority fragmenter for the Nannoolal catalogue."""
|
|
260
|
+
global _ENGINE
|
|
261
|
+
if _ENGINE is None:
|
|
262
|
+
catalogue = {}
|
|
263
|
+
for gid, (smarts, claim) in NANNOOLAL_BP_SMARTS.items():
|
|
264
|
+
priority = NANNOOLAL_BP_GROUPS[gid][2]
|
|
265
|
+
if priority is None:
|
|
266
|
+
priority = NANNOOLAL_BP_EXTENSION_PRIORITIES[gid]
|
|
267
|
+
catalogue[gid] = {
|
|
268
|
+
"smarts": smarts, "priority": priority, "claim": claim,
|
|
269
|
+
}
|
|
270
|
+
_ENGINE = PriorityFragmenter(catalogue)
|
|
271
|
+
return _ENGINE
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
#: Carbonyl groups whose acyl neighbours decide corrections 118-120.
|
|
275
|
+
_HO_CONJUGATION_GIDS = (44, 45, 46, 47, 51, 52, 90, 92)
|
|
276
|
+
_HALO_CONJUGATION_GIDS = (44, 45, 46, 47, 48, 49, 50, 51, 52, 77, 90, 92)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _carbonyl_carbon(mol, claim):
|
|
280
|
+
"""Index of the C=O carbon inside a carbonyl group's claim tuple."""
|
|
281
|
+
for idx in claim:
|
|
282
|
+
atom = mol.GetAtomWithIdx(idx)
|
|
283
|
+
if atom.GetSymbol() != "C":
|
|
284
|
+
continue
|
|
285
|
+
for bond in atom.GetBonds():
|
|
286
|
+
other = bond.GetOtherAtom(atom)
|
|
287
|
+
if other.GetSymbol() == "O" and bond.GetBondTypeAsDouble() == 2.0:
|
|
288
|
+
return idx
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _halogen_count(atom):
|
|
293
|
+
"""Number of directly bonded fluorine/chlorine atoms."""
|
|
294
|
+
return sum(
|
|
295
|
+
1 for nb in atom.GetNeighbors() if nb.GetSymbol() in ("F", "Cl")
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _carbon_neighbours(atom):
|
|
300
|
+
return [nb for nb in atom.GetNeighbors() if nb.GetSymbol() == "C"]
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _is_sp3(atom):
|
|
304
|
+
"""True when every bond of `atom` is a single non-aromatic bond."""
|
|
305
|
+
return not atom.GetIsAromatic() and all(
|
|
306
|
+
bond.GetBondTypeAsDouble() == 1.0 for bond in atom.GetBonds()
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _carbonyl_corrections(mol, assignments, corrections):
|
|
311
|
+
"""Corrections 118-120: conjugated and halo-substituted carbonyls.
|
|
312
|
+
|
|
313
|
+
118 - a carbonyl (acid, ester, lactone, ketone, aldehyde) whose acyl
|
|
314
|
+
carbon neighbour is sp2/aromatic (paper Table 11); aryl
|
|
315
|
+
ketones and benzaldehyde always carry it by definition.
|
|
316
|
+
119 - a carbonyl with one acyl neighbour carrying two or more F/Cl.
|
|
317
|
+
120 - a carbonyl with two or more such neighbours (per paper Table
|
|
318
|
+
17c, 119 and 120 are mutually exclusive).
|
|
319
|
+
"""
|
|
320
|
+
for gid in _HO_CONJUGATION_GIDS:
|
|
321
|
+
for claim in assignments.get(gid, ()):
|
|
322
|
+
cx = _carbonyl_carbon(mol, claim)
|
|
323
|
+
if cx is None:
|
|
324
|
+
continue
|
|
325
|
+
for nb in mol.GetAtomWithIdx(cx).GetNeighbors():
|
|
326
|
+
if nb.GetIdx() in claim and nb.GetSymbol() == "O":
|
|
327
|
+
continue
|
|
328
|
+
if nb.GetIsAromatic() or any(
|
|
329
|
+
b.GetBondTypeAsDouble() == 2.0 for b in nb.GetBonds()
|
|
330
|
+
):
|
|
331
|
+
corrections[118] = corrections.get(118, 0) + 1
|
|
332
|
+
break
|
|
333
|
+
for gid in _HALO_CONJUGATION_GIDS:
|
|
334
|
+
for claim in assignments.get(gid, ()):
|
|
335
|
+
cx = _carbonyl_carbon(mol, claim)
|
|
336
|
+
if cx is None:
|
|
337
|
+
continue
|
|
338
|
+
halo_carbons = sum(
|
|
339
|
+
1
|
|
340
|
+
for nb in mol.GetAtomWithIdx(cx).GetNeighbors()
|
|
341
|
+
if nb.GetSymbol() == "C" and _halogen_count(nb) >= 2
|
|
342
|
+
)
|
|
343
|
+
if halo_carbons >= 2:
|
|
344
|
+
corrections[120] = corrections.get(120, 0) + 1
|
|
345
|
+
elif halo_carbons == 1:
|
|
346
|
+
corrections[119] = corrections.get(119, 0) + 1
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _halogen_carbon_corrections(mol, corrections):
|
|
350
|
+
"""Corrections 121/122: heavily halogenated carbon atoms.
|
|
351
|
+
|
|
352
|
+
121 - a carbon with three or more F/Cl neighbours (per carbon).
|
|
353
|
+
122 - a carbon with exactly two F/Cl and two carbon neighbours.
|
|
354
|
+
"""
|
|
355
|
+
for atom in mol.GetAtoms():
|
|
356
|
+
if atom.GetSymbol() != "C":
|
|
357
|
+
continue
|
|
358
|
+
halo = _halogen_count(atom)
|
|
359
|
+
if halo >= 3:
|
|
360
|
+
corrections[121] = corrections.get(121, 0) + 1
|
|
361
|
+
elif halo == 2 and len(_carbon_neighbours(atom)) == 2:
|
|
362
|
+
corrections[122] = corrections.get(122, 0) + 1
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _steric_corrections(mol, corrections):
|
|
366
|
+
"""Corrections 130-133: crowded quaternary carbons and C-C bonds.
|
|
367
|
+
|
|
368
|
+
130 - a carbon with four carbon neighbours at least one of which is
|
|
369
|
+
aromatic or double-bonded (tert-butylbenzene crowding; the
|
|
370
|
+
bond from an sp3 carbon to a ring atom is single, so the
|
|
371
|
+
condition sits on the neighbour, not the bond).
|
|
372
|
+
131/132/133 - per single C(sp3)-C(sp3) bond, by the number of
|
|
373
|
+
further carbon neighbours of each endpoint: 2-2, 3-2, 3-3
|
|
374
|
+
(paper Table 2 names C2C-CC2 / C3C-CC2 / C3C-CC3). Ring bonds
|
|
375
|
+
qualify: the paper's own example for 132 (ethyl bornyl ether)
|
|
376
|
+
only reaches 3+2 on the bornane bridge bond C7-C4, and the
|
|
377
|
+
131 example (bicyclohexyl) is the acyclic analogue of decalin's
|
|
378
|
+
fused (2,2) bond. Ordinary small-ring crowding never reaches
|
|
379
|
+
(2,2) per bond (cyclohexane bonds are 1-1), so 125/126 stay
|
|
380
|
+
the sole small-ring corrections.
|
|
381
|
+
"""
|
|
382
|
+
for atom in mol.GetAtoms():
|
|
383
|
+
if atom.GetSymbol() != "C":
|
|
384
|
+
continue
|
|
385
|
+
nbs = _carbon_neighbours(atom)
|
|
386
|
+
if len(nbs) != 4:
|
|
387
|
+
continue
|
|
388
|
+
if any(
|
|
389
|
+
nb.GetIsAromatic()
|
|
390
|
+
or any(b.GetBondTypeAsDouble() == 2.0 for b in nb.GetBonds())
|
|
391
|
+
for nb in nbs
|
|
392
|
+
):
|
|
393
|
+
corrections[130] = corrections.get(130, 0) + 1
|
|
394
|
+
seen_bonds = set()
|
|
395
|
+
for bond in mol.GetBonds():
|
|
396
|
+
a1, a2 = bond.GetBeginAtom(), bond.GetEndAtom()
|
|
397
|
+
key = tuple(sorted((a1.GetIdx(), a2.GetIdx())))
|
|
398
|
+
if key in seen_bonds:
|
|
399
|
+
continue
|
|
400
|
+
seen_bonds.add(key)
|
|
401
|
+
if (
|
|
402
|
+
a1.GetSymbol() != "C"
|
|
403
|
+
or a2.GetSymbol() != "C"
|
|
404
|
+
or bond.GetBondTypeAsDouble() != 1.0
|
|
405
|
+
or not (_is_sp3(a1) and _is_sp3(a2))
|
|
406
|
+
):
|
|
407
|
+
continue
|
|
408
|
+
d1 = sum(
|
|
409
|
+
1
|
|
410
|
+
for nb in a1.GetNeighbors()
|
|
411
|
+
if nb.GetSymbol() == "C" and nb.GetIdx() != a2.GetIdx()
|
|
412
|
+
)
|
|
413
|
+
d2 = sum(
|
|
414
|
+
1
|
|
415
|
+
for nb in a2.GetNeighbors()
|
|
416
|
+
if nb.GetSymbol() == "C" and nb.GetIdx() != a1.GetIdx()
|
|
417
|
+
)
|
|
418
|
+
pair = tuple(sorted((d1, d2)))
|
|
419
|
+
gid = {(2, 2): 131, (2, 3): 132, (3, 3): 133}.get(pair)
|
|
420
|
+
if gid is not None:
|
|
421
|
+
corrections[gid] = corrections.get(gid, 0) + 1
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _ring_corrections(mol, corrections):
|
|
425
|
+
"""Corrections 125-129: small rings and aromatic substitution patterns.
|
|
426
|
+
|
|
427
|
+
125/126 - one per SSSR ring of size 3-4 / 5 that is not fully
|
|
428
|
+
aromatic (cyclopropane/butane/pentane skeleton strain;
|
|
429
|
+
furan-style aromatic rings are excluded).
|
|
430
|
+
127/128/129 - ortho/meta/para: once per molecule, by the closest
|
|
431
|
+
pair of substituted, non-fused carbons of any aromatic
|
|
432
|
+
ring (ring-arc distance 1/2/3). Fused ring atoms carry
|
|
433
|
+
no substituent (naphthalene gets no o/m/p correction);
|
|
434
|
+
1,2,4-trimethylbenzene counts as ortho only.
|
|
435
|
+
"""
|
|
436
|
+
loaded = load_rdkit()
|
|
437
|
+
if loaded is None:
|
|
438
|
+
return
|
|
439
|
+
rings = [list(ring) for ring in loaded[0].GetSymmSSSR(mol)]
|
|
440
|
+
for ring in rings:
|
|
441
|
+
size = len(ring)
|
|
442
|
+
if size not in (3, 4, 5):
|
|
443
|
+
continue
|
|
444
|
+
if all(mol.GetAtomWithIdx(i).GetIsAromatic() for i in ring):
|
|
445
|
+
continue
|
|
446
|
+
gid = 125 if size <= 4 else 126
|
|
447
|
+
corrections[gid] = corrections.get(gid, 0) + 1
|
|
448
|
+
ring_count = {}
|
|
449
|
+
for ring in rings:
|
|
450
|
+
for idx in ring:
|
|
451
|
+
ring_count[idx] = ring_count.get(idx, 0) + 1
|
|
452
|
+
closest = None
|
|
453
|
+
for ring in rings:
|
|
454
|
+
if not all(mol.GetAtomWithIdx(i).GetIsAromatic() for i in ring):
|
|
455
|
+
continue
|
|
456
|
+
members = set(ring)
|
|
457
|
+
substituted = [
|
|
458
|
+
idx
|
|
459
|
+
for idx in ring
|
|
460
|
+
if ring_count[idx] == 1
|
|
461
|
+
and any(
|
|
462
|
+
nb.GetIdx() not in members
|
|
463
|
+
for nb in mol.GetAtomWithIdx(idx).GetNeighbors()
|
|
464
|
+
)
|
|
465
|
+
]
|
|
466
|
+
for pos, i1 in enumerate(substituted):
|
|
467
|
+
for i2 in substituted[pos + 1:]:
|
|
468
|
+
d = ring_arc_distance(mol, i1, i2)
|
|
469
|
+
if d in (1, 2, 3) and (closest is None or d < closest):
|
|
470
|
+
closest = d
|
|
471
|
+
if closest is not None:
|
|
472
|
+
corrections[{1: 127, 2: 128, 3: 129}[closest]] = 1
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _hydrogen_count_corrections(mol, corrections):
|
|
476
|
+
"""Corrections 123/124: molecules with zero/one hydrogen atom."""
|
|
477
|
+
n_h = sum(atom.GetTotalNumHs() for atom in mol.GetAtoms())
|
|
478
|
+
if n_h == 0:
|
|
479
|
+
corrections[123] = 1
|
|
480
|
+
elif n_h == 1:
|
|
481
|
+
corrections[124] = 1
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def nannoolal_fragmentation(mol):
|
|
485
|
+
"""Fragment `mol` into Nannoolal Part 1 groups (paper Tables 1-2).
|
|
486
|
+
|
|
487
|
+
Parameters
|
|
488
|
+
----------
|
|
489
|
+
mol : Mol
|
|
490
|
+
Normalised RDKit molecule.
|
|
491
|
+
|
|
492
|
+
Returns
|
|
493
|
+
-------
|
|
494
|
+
counts : dict
|
|
495
|
+
Group ID -> count, main groups plus second-order corrections
|
|
496
|
+
118-133 plus extension groups 301-303.
|
|
497
|
+
n : int
|
|
498
|
+
Number of non-hydrogen atoms (paper Eq. (1) normalisation; note
|
|
499
|
+
the paper counts *every* non-H atom including sulfur - this is
|
|
500
|
+
a deliberate difference from UManSysProp, which omits S).
|
|
501
|
+
success : bool
|
|
502
|
+
status : str
|
|
503
|
+
``"OK"``, or the reason the molecule cannot be parameterised
|
|
504
|
+
(unsupported element, unclaimed atom, failed parse).
|
|
505
|
+
|
|
506
|
+
Examples
|
|
507
|
+
--------
|
|
508
|
+
Paper Table 17b (di-isopropanolamine):
|
|
509
|
+
|
|
510
|
+
>>> counts, n, ok, status = nannoolal_fragmentation(
|
|
511
|
+
... mol_from_smiles("CC(O)CNCC(O)C"))
|
|
512
|
+
>>> (ok, status, n, counts)
|
|
513
|
+
(True, 'OK', 9, {1: 2, 7: 4, 34: 2, 42: 1})
|
|
514
|
+
"""
|
|
515
|
+
if mol is None:
|
|
516
|
+
return {}, 0, False, "Failed to construct mol"
|
|
517
|
+
unsupported = sorted(
|
|
518
|
+
{
|
|
519
|
+
atom.GetSymbol()
|
|
520
|
+
for atom in mol.GetAtoms()
|
|
521
|
+
if atom.GetSymbol() not in NANNOOLAL_BP_ELEMENTS
|
|
522
|
+
}
|
|
523
|
+
)
|
|
524
|
+
if unsupported:
|
|
525
|
+
return {}, mol.GetNumAtoms(), False, (
|
|
526
|
+
"unsupported element(s) %s: outside the C/N/O/S/F/Cl/Br/I "
|
|
527
|
+
"families aerosolprops parameterises" % ", ".join(unsupported)
|
|
528
|
+
)
|
|
529
|
+
counts, assignments, success, status = _nannoolal_engine().fragment(mol)
|
|
530
|
+
n = mol.GetNumAtoms()
|
|
531
|
+
counts = dict(sorted(counts.items()))
|
|
532
|
+
if not success:
|
|
533
|
+
return counts, n, False, status
|
|
534
|
+
|
|
535
|
+
# Group 36: paper Table 1 reclassifies every alcohol OH of a
|
|
536
|
+
# molecule with fewer than five carbon atoms; Table 17b keeps 34 for
|
|
537
|
+
# the six-carbon di-isopropanolamine. The claimed atoms are
|
|
538
|
+
# unchanged.
|
|
539
|
+
n_carbon = sum(
|
|
540
|
+
1 for atom in mol.GetAtoms() if atom.GetSymbol() == "C"
|
|
541
|
+
)
|
|
542
|
+
if n_carbon < 5:
|
|
543
|
+
n_oh = sum(counts.pop(gid, 0) for gid in (33, 34, 35, 36))
|
|
544
|
+
if n_oh:
|
|
545
|
+
counts[36] = n_oh
|
|
546
|
+
|
|
547
|
+
corrections = {}
|
|
548
|
+
_carbonyl_corrections(mol, assignments, corrections)
|
|
549
|
+
_halogen_carbon_corrections(mol, corrections)
|
|
550
|
+
_steric_corrections(mol, corrections)
|
|
551
|
+
_ring_corrections(mol, corrections)
|
|
552
|
+
_hydrogen_count_corrections(mol, corrections)
|
|
553
|
+
counts.update(corrections)
|
|
554
|
+
return counts, n, True, "OK"
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class NannoolalBP:
|
|
558
|
+
"""Normal boiling point of pure components (Nannoolal Part 1, 2004).
|
|
559
|
+
|
|
560
|
+
Wraps :func:`nannoolal_fragmentation` and the pure estimators of
|
|
561
|
+
:mod:`aerosolprops.methods.nannoolal_bp` in the thermo ``Joback``
|
|
562
|
+
idiom: fragmentation runs once in the constructor, results are
|
|
563
|
+
stored on the instance, and the estimation method returns ``None``
|
|
564
|
+
if fragmentation failed or a required group interaction has no
|
|
565
|
+
published value (paper Table 5 rows 153/211) - never zero.
|
|
566
|
+
|
|
567
|
+
Parameters
|
|
568
|
+
----------
|
|
569
|
+
mol : Mol, optional
|
|
570
|
+
Normalisation is applied internally; pass any parsed RDKit
|
|
571
|
+
molecule.
|
|
572
|
+
smi : str, optional
|
|
573
|
+
SMILES string, used when `mol` is not given.
|
|
574
|
+
|
|
575
|
+
Attributes
|
|
576
|
+
----------
|
|
577
|
+
counts : dict
|
|
578
|
+
Non-zero group counts (empty on failure); IDs follow paper
|
|
579
|
+
Tables 1-2, extensions 301-303 follow Compernolle (2010).
|
|
580
|
+
n : int
|
|
581
|
+
Number of non-hydrogen atoms (0 on failure).
|
|
582
|
+
success : bool
|
|
583
|
+
status : str
|
|
584
|
+
``"OK"`` on success, otherwise the failure reason.
|
|
585
|
+
gi_status : str
|
|
586
|
+
Status of the group-interaction term: ``"OK"`` or
|
|
587
|
+
``"no-GI (extension groups present)"`` when extension groups
|
|
588
|
+
force the reduced-confidence branch (Compernolle 2010: no
|
|
589
|
+
interaction parameters derivable).
|
|
590
|
+
|
|
591
|
+
Examples
|
|
592
|
+
--------
|
|
593
|
+
Paper Table 17c (hexafluoroacetone; experimental Tb 245.9 K):
|
|
594
|
+
|
|
595
|
+
>>> nnp = NannoolalBP(smi="FC(F)(F)C(=O)C(F)(F)F")
|
|
596
|
+
>>> (nnp.success, nnp.status, nnp.n)
|
|
597
|
+
(True, 'OK', 10)
|
|
598
|
+
>>> sorted(nnp.counts.items())
|
|
599
|
+
[(7, 2), (21, 6), (51, 1), (120, 1), (121, 2), (123, 1)]
|
|
600
|
+
>>> round(nnp.boiling_point(), 1)
|
|
601
|
+
246.3
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
def __init__(self, mol=None, smi=None):
|
|
605
|
+
if mol is None:
|
|
606
|
+
mol = None if smi is None else mol_from_smiles(smi)
|
|
607
|
+
else:
|
|
608
|
+
mol = normalize_mol(mol)
|
|
609
|
+
if mol is None:
|
|
610
|
+
self.counts, self.n = {}, 0
|
|
611
|
+
self.success, self.status = False, "Failed to construct mol"
|
|
612
|
+
else:
|
|
613
|
+
self.counts, self.n, self.success, self.status = (
|
|
614
|
+
nannoolal_fragmentation(mol)
|
|
615
|
+
)
|
|
616
|
+
self.gi_status = None
|
|
617
|
+
|
|
618
|
+
def boiling_point(self):
|
|
619
|
+
"""Normal boiling point [K], or ``None`` on any failure.
|
|
620
|
+
|
|
621
|
+
Refuses (returns ``None`` and records the reason in
|
|
622
|
+
``self.status``) when the molecule needs an interaction the
|
|
623
|
+
paper prints without a value. Molecules with Compernolle
|
|
624
|
+
extension groups are estimated without the GI term and flagged
|
|
625
|
+
in ``self.gi_status`` instead.
|
|
626
|
+
"""
|
|
627
|
+
if not self.success:
|
|
628
|
+
return None
|
|
629
|
+
gi, self.gi_status = nannoolal_bp_gi(self.counts, self.n)
|
|
630
|
+
tb = nannoolal_bp_boiling_point(self.counts, self.n)
|
|
631
|
+
if tb is None:
|
|
632
|
+
self.status = (
|
|
633
|
+
"refused: missing interaction parameter"
|
|
634
|
+
if gi is None
|
|
635
|
+
else "refused: invalid group counts"
|
|
636
|
+
)
|
|
637
|
+
return tb
|