pyreactlab-core 0.1.8__tar.gz → 0.2.0__tar.gz
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.
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/PKG-INFO +1 -1
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyproject.toml +1 -1
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/__init__.py +11 -1
- pyreactlab_core-0.2.0/pyreactlab_core/configs/constants.py +95 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/configs/info.py +1 -1
- pyreactlab_core-0.2.0/pyreactlab_core/core/__init__.py +23 -0
- pyreactlab_core-0.2.0/pyreactlab_core/core/chem_react.py +421 -0
- pyreactlab_core-0.2.0/pyreactlab_core/core/chem_react_utils.py +181 -0
- pyreactlab_core-0.2.0/pyreactlab_core/core/reaction_component_mapper.py +206 -0
- pyreactlab_core-0.2.0/pyreactlab_core/core/reaction_network_analysis.py +291 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/models/reaction.py +24 -15
- pyreactlab_core-0.2.0/pyreactlab_core/utils/reaction_tools.py +153 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core.egg-info/PKG-INFO +1 -1
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core.egg-info/SOURCES.txt +4 -0
- pyreactlab_core-0.1.8/pyreactlab_core/configs/constants.py +0 -40
- pyreactlab_core-0.1.8/pyreactlab_core/core/__init__.py +0 -17
- pyreactlab_core-0.1.8/pyreactlab_core/core/chem_react.py +0 -1122
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/LICENSE +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/README.md +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/app.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/configs/__init__.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/docs/__init__.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/docs/chem_balance.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/docs/chem_utils.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/models/__init__.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/utils/__init__.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/utils/component_tools.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core/utils/tools.py +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core.egg-info/dependency_links.txt +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core.egg-info/requires.txt +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/pyreactlab_core.egg-info/top_level.txt +0 -0
- {pyreactlab_core-0.1.8 → pyreactlab_core-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyreactlab-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: pyreactlab-core is the core foundation of the PyReactLab ecosystem, offering shared data structures and algorithms for chemical reaction representation, stoichiometry, and reaction analysis.
|
|
5
5
|
Author-email: Sina Gilassi <sina.gilassi@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyreactlab-core"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "pyreactlab-core is the core foundation of the PyReactLab ecosystem, offering shared data structures and algorithms for chemical reaction representation, stoichiometry, and reaction analysis."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# config
|
|
1
|
+
# NOTE: config
|
|
2
2
|
from .configs.info import (
|
|
3
3
|
__version__,
|
|
4
4
|
__author__,
|
|
@@ -8,6 +8,13 @@ from .configs.info import (
|
|
|
8
8
|
__license__,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
# NOTE: configs
|
|
12
|
+
from .configs.constants import (
|
|
13
|
+
REACTION_SYMBOLIC_MODES,
|
|
14
|
+
ReactionMode
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# NOTE: app
|
|
11
18
|
from .app import (
|
|
12
19
|
rxn,
|
|
13
20
|
rxn_stoichiometry_matrix,
|
|
@@ -23,6 +30,9 @@ __all__ = [
|
|
|
23
30
|
"__description__",
|
|
24
31
|
"__email__",
|
|
25
32
|
"__license__",
|
|
33
|
+
# configs
|
|
34
|
+
"REACTION_SYMBOLIC_MODES",
|
|
35
|
+
"ReactionMode",
|
|
26
36
|
# app
|
|
27
37
|
"rxn",
|
|
28
38
|
"rxn_stoichiometry_matrix",
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# import libs
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
# SECTION: PyThermoDBLink/PyThermoDB
|
|
6
|
+
import math
|
|
7
|
+
DATASOURCE = "datasource"
|
|
8
|
+
EQUATIONSOURCE = "equationsource"
|
|
9
|
+
|
|
10
|
+
# NOTE: universal gas constant [J/mol.K]
|
|
11
|
+
R_CONST_J__molK = 8.314472
|
|
12
|
+
|
|
13
|
+
# NOTE: pi
|
|
14
|
+
PI_CONST = math.pi
|
|
15
|
+
|
|
16
|
+
# NOTE: STP condition
|
|
17
|
+
# pressure [Pa]
|
|
18
|
+
PRESSURE_STP_Pa = 101325
|
|
19
|
+
# temperature [K]
|
|
20
|
+
TEMPERATURE_STP_K = 273.15
|
|
21
|
+
# reference pressure [Pa]
|
|
22
|
+
PRESSURE_REF_Pa = 101325
|
|
23
|
+
# reference temperature [K]
|
|
24
|
+
TEMPERATURE_REF_K = 298.15
|
|
25
|
+
|
|
26
|
+
# SECTION: Periodic Table Elements
|
|
27
|
+
PERIODIC_TABLE_ELEMENTS = [
|
|
28
|
+
"H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne",
|
|
29
|
+
"Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar",
|
|
30
|
+
"K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni",
|
|
31
|
+
"Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr",
|
|
32
|
+
"Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd",
|
|
33
|
+
"Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe",
|
|
34
|
+
"Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd",
|
|
35
|
+
"Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu",
|
|
36
|
+
"Hf", "Ta", "W", "Re", "Os", "Ir", "Pt",
|
|
37
|
+
"Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn",
|
|
38
|
+
"Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu",
|
|
39
|
+
"Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr",
|
|
40
|
+
"Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn",
|
|
41
|
+
"Nh", "Fl", "Mc", "Lv", "Ts", "Og",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# SECTION: Reaction
|
|
45
|
+
REACTION_SYMBOLIC_MODES = {
|
|
46
|
+
# irreversible
|
|
47
|
+
"->": ("irreversible", "forward"),
|
|
48
|
+
"=>": ("irreversible", "forward"),
|
|
49
|
+
"→": ("irreversible", "forward"),
|
|
50
|
+
"⇒": ("irreversible", "forward"),
|
|
51
|
+
|
|
52
|
+
# backward irreversible
|
|
53
|
+
# ! not recommended for use in reaction expressions
|
|
54
|
+
"<-": ("irreversible", "backward"),
|
|
55
|
+
"←": ("irreversible", "backward"),
|
|
56
|
+
|
|
57
|
+
# reversible
|
|
58
|
+
"<=>": ("reversible", "forward"),
|
|
59
|
+
"<->": ("reversible", "forward"),
|
|
60
|
+
"⇌": ("reversible", "forward"),
|
|
61
|
+
"↔": ("reversible", "forward"),
|
|
62
|
+
|
|
63
|
+
# thermodynamic equilibrium
|
|
64
|
+
"=": ("equilibrium", "forward"),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# NOTE: irreversible reaction mode symbols
|
|
68
|
+
IRREVERSIBLE_REACTION_MODE_SYMBOLS = [
|
|
69
|
+
"->", "=>", "→", "⇒",
|
|
70
|
+
"<-", "←"
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# NOTE: reversible reaction mode symbols
|
|
74
|
+
REVERSIBLE_REACTION_MODE_SYMBOLS = [
|
|
75
|
+
"<=>", "<->", "⇌", "↔"
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# NOTE: equilibrium reaction mode symbols
|
|
79
|
+
EQUILIBRIUM_REACTION_MODE_SYMBOLS = [
|
|
80
|
+
"="
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
# NOTE: all reaction mode symbols
|
|
84
|
+
ALL_REACTION_MODE_SYMBOLS = (
|
|
85
|
+
IRREVERSIBLE_REACTION_MODE_SYMBOLS +
|
|
86
|
+
REVERSIBLE_REACTION_MODE_SYMBOLS +
|
|
87
|
+
EQUILIBRIUM_REACTION_MODE_SYMBOLS
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# NOTE: Reaction Mode
|
|
91
|
+
ReactionMode = Literal["<=>", "=>", "="]
|
|
92
|
+
# NOTE: Reaction Type
|
|
93
|
+
ReactionType = Literal["irreversible", "reversible", "equilibrium"]
|
|
94
|
+
# NOTE: Reaction Direction
|
|
95
|
+
ReactionDirection = Literal["forward", "backward"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
# NOTE: chem react
|
|
3
|
+
from .chem_react import (
|
|
4
|
+
ReactionMode,
|
|
5
|
+
PhaseRule,
|
|
6
|
+
Reactant,
|
|
7
|
+
Product,
|
|
8
|
+
ChemReact
|
|
9
|
+
)
|
|
10
|
+
from .chem_react_utils import ChemReactUtils
|
|
11
|
+
from .reaction_component_mapper import ReactionComponentMapper
|
|
12
|
+
from .reaction_network_analysis import ReactionNetworkAnalysis
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"ReactionMode",
|
|
16
|
+
"PhaseRule",
|
|
17
|
+
"Reactant",
|
|
18
|
+
"Product",
|
|
19
|
+
"ChemReact",
|
|
20
|
+
"ChemReactUtils",
|
|
21
|
+
"ReactionComponentMapper",
|
|
22
|
+
"ReactionNetworkAnalysis",
|
|
23
|
+
]
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# import libs
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict, Any, List, Optional, Literal, TypedDict
|
|
5
|
+
from pythermodb_settings.models import Component, ComponentKey
|
|
6
|
+
# locals
|
|
7
|
+
from ..configs.constants import (
|
|
8
|
+
R_CONST_J__molK,
|
|
9
|
+
PRESSURE_REF_Pa,
|
|
10
|
+
TEMPERATURE_REF_K,
|
|
11
|
+
ReactionMode,
|
|
12
|
+
)
|
|
13
|
+
from .chem_react_utils import ChemReactUtils
|
|
14
|
+
from .reaction_component_mapper import ReactionComponentMapper
|
|
15
|
+
from .reaction_network_analysis import ReactionNetworkAnalysis
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# NOTE: logger
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# NOTE: Phase Rule
|
|
22
|
+
PhaseRule = Literal["gas", "liquid", "aqueous", "solid"]
|
|
23
|
+
|
|
24
|
+
# SECTION: Models
|
|
25
|
+
# NOTE: reactants
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Reactant(TypedDict):
|
|
29
|
+
coefficient: float
|
|
30
|
+
molecule: str
|
|
31
|
+
state: str
|
|
32
|
+
molecule_state: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Product(TypedDict):
|
|
36
|
+
coefficient: float
|
|
37
|
+
molecule: str
|
|
38
|
+
state: str
|
|
39
|
+
molecule_state: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# SECTION: ChemReact class
|
|
43
|
+
class ChemReact(
|
|
44
|
+
ChemReactUtils,
|
|
45
|
+
ReactionComponentMapper,
|
|
46
|
+
ReactionNetworkAnalysis,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Chemical Reaction Utilities
|
|
50
|
+
|
|
51
|
+
The ChemReact class provides utilities for analyzing and processing chemical reactions in various phases and conditions. These reactions can be represented in different ways depending on the dominant factors influencing them:
|
|
52
|
+
|
|
53
|
+
- Use = → when thermodynamics dominates
|
|
54
|
+
- Use <=> → when kinetics + thermodynamics matter
|
|
55
|
+
- Use => → when kinetics only matter
|
|
56
|
+
- The class supports reactions involving components in gas, liquid, aqueous, and solid phases.
|
|
57
|
+
- It includes methods for analyzing reactions, counting carbon atoms, determining reaction phases, and more.
|
|
58
|
+
"""
|
|
59
|
+
# # NOTE: variables
|
|
60
|
+
# system inputs
|
|
61
|
+
_system_inputs = None
|
|
62
|
+
# universal gas constant [J/mol.K]
|
|
63
|
+
R = R_CONST_J__molK
|
|
64
|
+
# temperature [K]
|
|
65
|
+
T_Ref = TEMPERATURE_REF_K
|
|
66
|
+
# pressure [bar]
|
|
67
|
+
P_Ref = PRESSURE_REF_Pa/1e5
|
|
68
|
+
|
|
69
|
+
# available phases
|
|
70
|
+
available_phases = PhaseRule.__args__
|
|
71
|
+
|
|
72
|
+
# NOTE: id separator
|
|
73
|
+
# ! used to separate component name and state
|
|
74
|
+
_id_separator: str = '-'
|
|
75
|
+
|
|
76
|
+
# NOTE: component checker
|
|
77
|
+
_component_checker: bool = False
|
|
78
|
+
|
|
79
|
+
# NOTE: stoichiometry source
|
|
80
|
+
_stoichiometry_source: dict[str, Any] = {}
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
reaction_mode_symbol: ReactionMode,
|
|
85
|
+
components: Optional[List[Component]],
|
|
86
|
+
component_keys: Optional[List[ComponentKey]] = None
|
|
87
|
+
):
|
|
88
|
+
"""
|
|
89
|
+
Initialize the ChemReactUtils class.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
reaction_mode_symbol : ReactionMode, optional
|
|
94
|
+
The symbol used to separate reactants and products in a reaction equation.
|
|
95
|
+
components : Optional[List[Component]]
|
|
96
|
+
A list of Component objects involved in the reaction.
|
|
97
|
+
component_keys : List[ComponentKey], optional
|
|
98
|
+
The key used to identify components in the reaction.
|
|
99
|
+
|
|
100
|
+
Notes
|
|
101
|
+
-----
|
|
102
|
+
- Use "<=>" when kinetics + thermodynamics matter
|
|
103
|
+
- Use "=" when thermodynamics dominates
|
|
104
|
+
- Use "=>" when kinetics only matter
|
|
105
|
+
- If components is None, component IDs will be an empty list.
|
|
106
|
+
- Component IDs are generated by combining the formula and state of each component, separated by a hyphen such as "H2O-l" for liquid water.
|
|
107
|
+
"""
|
|
108
|
+
# SECTION: parent class initialization
|
|
109
|
+
# NOTE: initialize general reaction utility settings
|
|
110
|
+
ChemReactUtils.__init__(
|
|
111
|
+
self,
|
|
112
|
+
available_phases=PhaseRule.__args__,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# NOTE: set reaction mode symbol used by this reaction parser
|
|
116
|
+
self.reaction_mode_symbol = reaction_mode_symbol
|
|
117
|
+
# >> reaction type
|
|
118
|
+
self.reaction_type = self.get_reaction_type(reaction_mode_symbol)
|
|
119
|
+
|
|
120
|
+
# NOTE: initialize component mapping settings
|
|
121
|
+
ReactionComponentMapper.__init__(
|
|
122
|
+
self,
|
|
123
|
+
components=components,
|
|
124
|
+
component_keys=component_keys,
|
|
125
|
+
id_separator=self._id_separator,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def system_inputs(self) -> Dict[str, Any]:
|
|
130
|
+
"""Get the system inputs."""
|
|
131
|
+
# check
|
|
132
|
+
if self._system_inputs is None:
|
|
133
|
+
raise ValueError("System inputs are not set.")
|
|
134
|
+
return self._system_inputs
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def stoichiometry_source(self) -> Dict[str, Any]:
|
|
138
|
+
"""Get the stoichiometry source."""
|
|
139
|
+
# res
|
|
140
|
+
return self._stoichiometry_source
|
|
141
|
+
|
|
142
|
+
def analyze_reaction(
|
|
143
|
+
self,
|
|
144
|
+
reaction_pack: Dict[str, str],
|
|
145
|
+
phase_rule: Optional[str] = None
|
|
146
|
+
) -> Dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Analyze a chemical reaction and extract relevant information.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
reaction_pack : dict
|
|
153
|
+
A dictionary containing the reaction and its name.
|
|
154
|
+
phase_rule : str, optional
|
|
155
|
+
The phase of the reaction, which can be 'gas', 'liquid', 'aqueous', or 'solid'.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
dict
|
|
160
|
+
A dictionary containing the analyzed reaction data, including reactants,
|
|
161
|
+
products, reaction coefficient, and carbon count.
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
# NOTE: check reaction_pack
|
|
165
|
+
if not isinstance(reaction_pack, dict):
|
|
166
|
+
raise ValueError("reaction_pack must be a dictionary.")
|
|
167
|
+
|
|
168
|
+
if 'reaction' not in reaction_pack or 'name' not in reaction_pack:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
"reaction_pack must contain 'reaction' and 'name' keys.")
|
|
171
|
+
|
|
172
|
+
# NOTE: check phase
|
|
173
|
+
# set phase
|
|
174
|
+
phase_set = self.phase_rule_analysis(phase_rule)
|
|
175
|
+
|
|
176
|
+
# SECTION: extract data from reaction
|
|
177
|
+
reaction = reaction_pack['reaction']
|
|
178
|
+
name = reaction_pack['name']
|
|
179
|
+
|
|
180
|
+
# ! Split the reaction into left and right sides
|
|
181
|
+
sides = reaction.split(self.reaction_mode_symbol.strip())
|
|
182
|
+
|
|
183
|
+
# Define a regex pattern to match reactants/products
|
|
184
|
+
# pattern = r'(\d*)?(\w+)\((\w)\)'
|
|
185
|
+
# pattern = r'(\d*\.?\d+)?(\w+)\((\w)\)'
|
|
186
|
+
# pattern = r'(?:(\d*\.?\d+)\s*)?([A-Z][a-zA-Z0-9]*)\s*(?:\((\w)\))?'
|
|
187
|
+
# NOTE: multi-purpose pattern
|
|
188
|
+
pattern = r'(?:(\d*\.?\d+)\s*)?(e(?:\{-?1?\}|[+-])?|\[[^\]\s]+\](?:\d+)?(?:\{[^{}\s]+\})?|(?:(?:\((?!(?:g|l|s|aq)\))[A-Za-z0-9]+\)\d*)*[A-Z][A-Za-z0-9]*(?:\((?!(?:g|l|s|aq)\))[A-Za-z0-9]+\)\d*)*)(?:[·*](?:\d+)?(?:(?:\((?!(?:g|l|s|aq)\))[A-Za-z0-9]+\)\d*)*[A-Z][A-Za-z0-9]*(?:\((?!(?:g|l|s|aq)\))[A-Za-z0-9]+\)\d*)*))*(?:\{[^{}\s]+\})?)\s*(?:\((g|l|s|aq)\))?'
|
|
189
|
+
|
|
190
|
+
# SECTION: SECTION: Extract reactants and products
|
|
191
|
+
# Extract reactants
|
|
192
|
+
reactants_raw = re.findall(pattern, sides[0])
|
|
193
|
+
reactants: List[Reactant] = [
|
|
194
|
+
{
|
|
195
|
+
'coefficient': float(r[0]) if r[0] else float(1),
|
|
196
|
+
'molecule': r[1],
|
|
197
|
+
'state': r[2] if r[2] else phase_set,
|
|
198
|
+
'molecule_state': ''
|
|
199
|
+
} for r in reactants_raw
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
# NOTE: reactants full name
|
|
203
|
+
reactants_names = []
|
|
204
|
+
# loop over reactants
|
|
205
|
+
for i, item in enumerate(reactants):
|
|
206
|
+
# ! check phase_set and phase_rule
|
|
207
|
+
if phase_rule is None:
|
|
208
|
+
# check item state
|
|
209
|
+
if item['state'] == 'empty':
|
|
210
|
+
raise ValueError(
|
|
211
|
+
f"Phase rule is empty but reactant '{item['molecule']}' has state '{item['state']}'.")
|
|
212
|
+
else:
|
|
213
|
+
# check item state
|
|
214
|
+
if item['state'] != phase_set:
|
|
215
|
+
raise ValueError(
|
|
216
|
+
f"Phase rule is '{phase_set}' but reactant '{item['molecule']}' has state '{item['state']}'.")
|
|
217
|
+
|
|
218
|
+
# generate full name
|
|
219
|
+
full_name = item['molecule'] + "-" + item['state']
|
|
220
|
+
# append to list
|
|
221
|
+
reactants_names.append(full_name)
|
|
222
|
+
# update source
|
|
223
|
+
reactants[i]['molecule_state'] = full_name
|
|
224
|
+
|
|
225
|
+
# Extract products
|
|
226
|
+
products_raw = re.findall(pattern, sides[1])
|
|
227
|
+
products: List[Product] = [
|
|
228
|
+
{
|
|
229
|
+
'coefficient': float(p[0]) if p[0] else float(1),
|
|
230
|
+
'molecule': p[1],
|
|
231
|
+
'state': p[2] if p[2] else phase_set,
|
|
232
|
+
'molecule_state': ''
|
|
233
|
+
} for p in products_raw
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
# NOTE: products full name
|
|
237
|
+
products_names = []
|
|
238
|
+
# loop over products
|
|
239
|
+
for i, item in enumerate(products):
|
|
240
|
+
# ! check phase_set and phase_rule
|
|
241
|
+
if phase_rule is None:
|
|
242
|
+
# check item state
|
|
243
|
+
if item['state'] == 'empty':
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"Phase rule is empty but product '{item['molecule']}' has state '{item['state']}'.")
|
|
246
|
+
else:
|
|
247
|
+
# check item state
|
|
248
|
+
if item['state'] != phase_set:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Phase rule is '{phase_set}' but product '{item['molecule']}' has state '{item['state']}'.")
|
|
251
|
+
|
|
252
|
+
# generate full name
|
|
253
|
+
full_name = item['molecule'] + "-" + item['state']
|
|
254
|
+
# append to list
|
|
255
|
+
products_names.append(full_name)
|
|
256
|
+
# update source
|
|
257
|
+
products[i]['molecule_state'] = full_name
|
|
258
|
+
|
|
259
|
+
# SECTION: all components
|
|
260
|
+
all_components = reactants_names + products_names
|
|
261
|
+
# >> remove duplicates
|
|
262
|
+
all_components: List[str] = list(set(all_components))
|
|
263
|
+
|
|
264
|
+
# SECTION: reaction coefficient and stoichiometry
|
|
265
|
+
reaction_coefficients = 0
|
|
266
|
+
reaction_stoichiometry = {}
|
|
267
|
+
reaction_stoichiometry_matrix = []
|
|
268
|
+
|
|
269
|
+
# iterate over reactants and products to calculate reaction coefficients
|
|
270
|
+
# NOTE: reactants
|
|
271
|
+
for item in reactants:
|
|
272
|
+
reaction_coefficients += item['coefficient']
|
|
273
|
+
reaction_stoichiometry[
|
|
274
|
+
item['molecule_state']
|
|
275
|
+
] = -1 * item['coefficient']
|
|
276
|
+
# append to stoichiometric matrix
|
|
277
|
+
reaction_stoichiometry_matrix.append(
|
|
278
|
+
-1 * item['coefficient']
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# NOTE: products
|
|
282
|
+
for item in products:
|
|
283
|
+
reaction_coefficients -= item['coefficient']
|
|
284
|
+
reaction_stoichiometry[
|
|
285
|
+
item['molecule_state']
|
|
286
|
+
] = item['coefficient']
|
|
287
|
+
# append to stoichiometric matrix
|
|
288
|
+
reaction_stoichiometry_matrix.append(
|
|
289
|
+
item['coefficient']
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# SECTION: Carbon count for each component
|
|
293
|
+
carbon_count = {}
|
|
294
|
+
for r in reactants:
|
|
295
|
+
carbon_count[r['molecule_state']] = self.count_carbon(
|
|
296
|
+
r['molecule'],
|
|
297
|
+
r['coefficient']
|
|
298
|
+
)
|
|
299
|
+
for p in products:
|
|
300
|
+
carbon_count[p['molecule_state']] = self.count_carbon(
|
|
301
|
+
p['molecule'],
|
|
302
|
+
p['coefficient']
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# SECTION: reaction state
|
|
306
|
+
reaction_state = {}
|
|
307
|
+
for r in reactants:
|
|
308
|
+
# set
|
|
309
|
+
reaction_state[r['molecule_state']] = r['state']
|
|
310
|
+
for p in products:
|
|
311
|
+
# set
|
|
312
|
+
reaction_state[p['molecule_state']] = p['state']
|
|
313
|
+
|
|
314
|
+
# NOTE: reaction phase
|
|
315
|
+
# reaction
|
|
316
|
+
reaction_phase = self.determine_reaction_phase(
|
|
317
|
+
reaction_state
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# NOTE: unique states
|
|
321
|
+
state_count = self.count_reaction_states(
|
|
322
|
+
reaction_state
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# SECTION: Symbolic reaction without states
|
|
326
|
+
symbolic_reaction = ""
|
|
327
|
+
symbolic_unbalanced_reaction = ""
|
|
328
|
+
|
|
329
|
+
# reactants
|
|
330
|
+
for i, r in enumerate(reactants):
|
|
331
|
+
if i == 0:
|
|
332
|
+
if r['coefficient'] == 1:
|
|
333
|
+
symbolic_reaction += f"{r['molecule']}"
|
|
334
|
+
else:
|
|
335
|
+
symbolic_reaction += f"{r['coefficient']}{r['molecule']}"
|
|
336
|
+
# unbalanced
|
|
337
|
+
symbolic_unbalanced_reaction += f"{r['molecule']}"
|
|
338
|
+
else:
|
|
339
|
+
if r['coefficient'] == 1:
|
|
340
|
+
symbolic_reaction += f" + {r['molecule']}"
|
|
341
|
+
else:
|
|
342
|
+
symbolic_reaction += f" + {r['coefficient']}{r['molecule']}"
|
|
343
|
+
# unbalanced
|
|
344
|
+
symbolic_unbalanced_reaction += f" + {r['molecule']}"
|
|
345
|
+
# reaction mode symbol
|
|
346
|
+
symbolic_reaction += f" {self.reaction_mode_symbol} "
|
|
347
|
+
symbolic_unbalanced_reaction += f" {self.reaction_mode_symbol} "
|
|
348
|
+
|
|
349
|
+
# products
|
|
350
|
+
for i, p in enumerate(products):
|
|
351
|
+
if i == 0:
|
|
352
|
+
if p['coefficient'] == 1:
|
|
353
|
+
symbolic_reaction += f"{p['molecule']}"
|
|
354
|
+
else:
|
|
355
|
+
symbolic_reaction += f"{p['coefficient']}{p['molecule']}"
|
|
356
|
+
# unbalanced
|
|
357
|
+
symbolic_unbalanced_reaction += f"{p['molecule']}"
|
|
358
|
+
else:
|
|
359
|
+
if p['coefficient'] == 1:
|
|
360
|
+
symbolic_reaction += f" + {p['molecule']}"
|
|
361
|
+
else:
|
|
362
|
+
symbolic_reaction += f" + {p['coefficient']}{p['molecule']}"
|
|
363
|
+
# unbalanced
|
|
364
|
+
symbolic_unbalanced_reaction += f" + {p['molecule']}"
|
|
365
|
+
|
|
366
|
+
# SECTION: set id for each component
|
|
367
|
+
# NOTE: component ids
|
|
368
|
+
component_ids = {}
|
|
369
|
+
for i, r in enumerate(reactants):
|
|
370
|
+
component_ids[r['molecule_state']] = i+1
|
|
371
|
+
offset = len(reactants)
|
|
372
|
+
for i, p in enumerate(products):
|
|
373
|
+
component_ids[p['molecule_state']] = offset + i + 1
|
|
374
|
+
|
|
375
|
+
# SECTION: collect components
|
|
376
|
+
components = self.collect_components(
|
|
377
|
+
reactants,
|
|
378
|
+
products
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# SECTION: map components
|
|
382
|
+
map_components = self.map_components(
|
|
383
|
+
reactants,
|
|
384
|
+
products,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# SECTION: build stoichiometry source
|
|
388
|
+
stoichiometry_source = self.build_stoichiometry_source(
|
|
389
|
+
reaction_stoichiometry=reaction_stoichiometry
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# res
|
|
393
|
+
res = {
|
|
394
|
+
'name': name,
|
|
395
|
+
'reaction': reaction,
|
|
396
|
+
'reaction_mode_symbol': self.reaction_mode_symbol,
|
|
397
|
+
'reaction_type': self.reaction_type,
|
|
398
|
+
"component_ids": component_ids,
|
|
399
|
+
"all_components": all_components,
|
|
400
|
+
"symbolic_reaction": symbolic_reaction,
|
|
401
|
+
"symbolic_unbalanced_reaction": symbolic_unbalanced_reaction,
|
|
402
|
+
'reactants': reactants,
|
|
403
|
+
'reactants_names': reactants_names,
|
|
404
|
+
'products': products,
|
|
405
|
+
'products_names': products_names,
|
|
406
|
+
'reaction_coefficients': reaction_coefficients,
|
|
407
|
+
'reaction_stoichiometry': reaction_stoichiometry,
|
|
408
|
+
'reaction_stoichiometry_matrix': reaction_stoichiometry_matrix,
|
|
409
|
+
'reaction_stoichiometry_source': stoichiometry_source,
|
|
410
|
+
'carbon_count': carbon_count,
|
|
411
|
+
'reaction_state': reaction_state,
|
|
412
|
+
'reaction_phase': reaction_phase,
|
|
413
|
+
'state_count': state_count,
|
|
414
|
+
'components': components,
|
|
415
|
+
'map_components': map_components,
|
|
416
|
+
'component_checker': self._component_checker,
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return res
|
|
420
|
+
except Exception as e:
|
|
421
|
+
raise Exception(f"Error analyzing reaction: {e}")
|