pyphyschemtools 0.3.2__py3-none-any.whl → 0.3.4__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.
- pyphyschemtools/.ipynb_checkpoints/cheminformatics-checkpoint.py +230 -0
- pyphyschemtools/.ipynb_checkpoints/kinetics-checkpoint.py +272 -0
- pyphyschemtools/__init__.py +1 -1
- pyphyschemtools/kinetics.py +149 -70
- {pyphyschemtools-0.3.2.dist-info → pyphyschemtools-0.3.4.dist-info}/METADATA +1 -1
- {pyphyschemtools-0.3.2.dist-info → pyphyschemtools-0.3.4.dist-info}/RECORD +9 -8
- pyphyschemtools/.visualID_Eng.py.swp +0 -0
- {pyphyschemtools-0.3.2.dist-info → pyphyschemtools-0.3.4.dist-info}/WHEEL +0 -0
- {pyphyschemtools-0.3.2.dist-info → pyphyschemtools-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {pyphyschemtools-0.3.2.dist-info → pyphyschemtools-0.3.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
############################################################
|
|
2
|
+
# easy_rdkit
|
|
3
|
+
############################################################
|
|
4
|
+
from .visualID_Eng import fg, bg, hl
|
|
5
|
+
from .core import centerTitle, centertxt
|
|
6
|
+
|
|
7
|
+
import rdkit
|
|
8
|
+
from rdkit import Chem
|
|
9
|
+
from rdkit.Chem import AllChem, GetPeriodicTable, Draw, rdCoordGen
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from rdkit.Chem.Draw import rdMolDraw2D
|
|
12
|
+
from IPython.display import SVG
|
|
13
|
+
from PIL import Image
|
|
14
|
+
|
|
15
|
+
class easy_rdkit():
|
|
16
|
+
"""
|
|
17
|
+
A helper class to analyze and visualize molecules using RDKit.
|
|
18
|
+
Provides tools for Lewis structure analysis and advanced 2D drawing.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self,smiles, canonical=True):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the molecule object from a SMILES string.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
smiles (str): The SMILES representation of the molecule.
|
|
27
|
+
canonical (bool): If True, converts the SMILES to its canonical form
|
|
28
|
+
to ensure consistent atom numbering and uniqueness.
|
|
29
|
+
"""
|
|
30
|
+
from rdkit import Chem
|
|
31
|
+
|
|
32
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
33
|
+
if mol is None:
|
|
34
|
+
raise ValueError(f"Invalid SMILES string: {smiles}")
|
|
35
|
+
|
|
36
|
+
if canonical:
|
|
37
|
+
# Generate canonical isomeric SMILES
|
|
38
|
+
self.smiles = Chem.MolToSmiles(mol, isomericSmiles=True, canonical=True)
|
|
39
|
+
# Re-load the molecule from the canonical SMILES to sync atom indices
|
|
40
|
+
self.mol = Chem.MolFromSmiles(self.smiles)
|
|
41
|
+
else:
|
|
42
|
+
self.mol=mol
|
|
43
|
+
self.smiles = smiles
|
|
44
|
+
|
|
45
|
+
def analyze_lewis(self):
|
|
46
|
+
"""
|
|
47
|
+
Performs a Lewis structure analysis for each atom in the molecule.
|
|
48
|
+
Calculates valence electrons, lone pairs, formal charges, and octet rule compliance.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
pd.DataFrame: A table containing detailed Lewis electronic data per atom.
|
|
52
|
+
"""
|
|
53
|
+
if self.mol is None:
|
|
54
|
+
raise ValueError(f"Molécule invalide pour {self.smiles} (SMILES incorrect ?)")
|
|
55
|
+
|
|
56
|
+
pt = GetPeriodicTable()
|
|
57
|
+
rows = []
|
|
58
|
+
|
|
59
|
+
for atom in self.mol.GetAtoms():
|
|
60
|
+
Z = atom.GetAtomicNum()
|
|
61
|
+
valence_e = pt.GetNOuterElecs(Z)
|
|
62
|
+
bonding_e = atom.GetTotalValence()
|
|
63
|
+
formal_charge = atom.GetFormalCharge()
|
|
64
|
+
num_bonds = int(sum(bond.GetBondTypeAsDouble() for bond in atom.GetBonds()))
|
|
65
|
+
# hybridization = atom.GetHybridization()
|
|
66
|
+
nonbonding = valence_e - bonding_e - formal_charge
|
|
67
|
+
|
|
68
|
+
lone_pairs = max(0, nonbonding // 2)
|
|
69
|
+
|
|
70
|
+
if Z==1 or Z==2: # règle du duet
|
|
71
|
+
target = 2
|
|
72
|
+
else: # règle de l’octet
|
|
73
|
+
target = 8
|
|
74
|
+
|
|
75
|
+
missing_e = max(0, target/2 - (bonding_e + 2*lone_pairs))
|
|
76
|
+
vacancies = int(missing_e)
|
|
77
|
+
total_e = 2*(lone_pairs + bonding_e)
|
|
78
|
+
|
|
79
|
+
if total_e > 8:
|
|
80
|
+
octet_msg = "❌ hypercoordiné"
|
|
81
|
+
elif total_e < 8 and Z > 2:
|
|
82
|
+
octet_msg = "❌ électron-déficient"
|
|
83
|
+
elif total_e == 8:
|
|
84
|
+
octet_msg = "✅ octet"
|
|
85
|
+
elif total_e == 2 and (Z == 1 or Z == 2):
|
|
86
|
+
octet_msg = "✅ duet"
|
|
87
|
+
else:
|
|
88
|
+
octet_msg = "🤔"
|
|
89
|
+
rows.append({
|
|
90
|
+
"index atome": atom.GetIdx(),
|
|
91
|
+
"symbole": atom.GetSymbol(),
|
|
92
|
+
"e- valence": valence_e,
|
|
93
|
+
"e- liants": bonding_e,
|
|
94
|
+
"charge formelle": formal_charge,
|
|
95
|
+
"doublets non-liants (DNL)": lone_pairs,
|
|
96
|
+
"lacunes ([])": vacancies,
|
|
97
|
+
"nombre de liaisons": num_bonds,
|
|
98
|
+
"e- total (octet ?)": total_e,
|
|
99
|
+
"O/H/D ?": octet_msg
|
|
100
|
+
})
|
|
101
|
+
return pd.DataFrame(rows)
|
|
102
|
+
|
|
103
|
+
def show_mol(self,
|
|
104
|
+
size: tuple=(400,400),
|
|
105
|
+
show_Lewis: bool=False,
|
|
106
|
+
plot_conjugation: bool=False,
|
|
107
|
+
plot_aromatic: bool=False,
|
|
108
|
+
show_n: bool=False,
|
|
109
|
+
show_hybrid: bool=False,
|
|
110
|
+
show_H: bool=False,
|
|
111
|
+
rep3D: bool=False,
|
|
112
|
+
macrocycle: bool=False,
|
|
113
|
+
highlightAtoms: list=[],
|
|
114
|
+
legend: str=''
|
|
115
|
+
):
|
|
116
|
+
"""
|
|
117
|
+
Renders the molecule in 2D SVG format with optional property overlays.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
size (tuple): Drawing dimensions in pixels.
|
|
121
|
+
show_Lewis (bool): Annotates atoms with Lone Pairs and Vacancies.
|
|
122
|
+
plot_conjugation (bool): Highlights conjugated bonds in blue.
|
|
123
|
+
plot_aromatic (bool): Highlights aromatic rings in red.
|
|
124
|
+
show_n (bool): Displays atom indices.
|
|
125
|
+
show_hybrid (bool): Displays atom hybridization (sp3, sp2, etc.).
|
|
126
|
+
show_H (bool): Adds explicit Hydrogens to the drawing.
|
|
127
|
+
rep3D (bool): Computes a 3D-like conformation before drawing.
|
|
128
|
+
macrocycle (bool): Uses CoordGen for better rendering of large rings (e.g., Cyclodextrins).
|
|
129
|
+
highlightAtoms (list): List of indices to highlight.
|
|
130
|
+
legend (str): Title or legend text for the drawing.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def safe_add_hs():
|
|
134
|
+
try:
|
|
135
|
+
return Chem.AddHs(self.mol)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
print(f"[Warning] Impossible d'ajouter les H pour {self.smiles} ({e}), on garde la version brute.")
|
|
138
|
+
return mol
|
|
139
|
+
|
|
140
|
+
if show_H and not show_Lewis:
|
|
141
|
+
mol = Chem.AddHs(self.mol)
|
|
142
|
+
else:
|
|
143
|
+
mol = self.mol
|
|
144
|
+
if show_Lewis:
|
|
145
|
+
mol = safe_add_hs()
|
|
146
|
+
self.mol = mol
|
|
147
|
+
df = self.analyze_lewis()
|
|
148
|
+
lewis_info = {row["index atome"]: (row["doublets non-liants (DNL)"], row["lacunes ([])"])
|
|
149
|
+
for _, row in df.iterrows()}
|
|
150
|
+
else:
|
|
151
|
+
df = None
|
|
152
|
+
|
|
153
|
+
if rep3D:
|
|
154
|
+
mol = Chem.AddHs(self.mol)
|
|
155
|
+
self.mol = mol
|
|
156
|
+
AllChem.EmbedMolecule(mol)
|
|
157
|
+
|
|
158
|
+
if macrocycle:
|
|
159
|
+
rdCoordGen.AddCoords(self.mol)
|
|
160
|
+
|
|
161
|
+
d2d = rdMolDraw2D.MolDraw2DSVG(size[0],size[1])
|
|
162
|
+
|
|
163
|
+
atoms = list(mol.GetAtoms())
|
|
164
|
+
|
|
165
|
+
if plot_conjugation:
|
|
166
|
+
from collections import defaultdict
|
|
167
|
+
Chem.SetConjugation(mol)
|
|
168
|
+
colors = [(0.0, 0.0, 1.0, 0.4)]
|
|
169
|
+
athighlights = defaultdict(list)
|
|
170
|
+
arads = {}
|
|
171
|
+
bndhighlights = defaultdict(list)
|
|
172
|
+
for bond in mol.GetBonds():
|
|
173
|
+
aid1 = bond.GetBeginAtomIdx()
|
|
174
|
+
aid2 = bond.GetEndAtomIdx()
|
|
175
|
+
|
|
176
|
+
if bond.GetIsConjugated():
|
|
177
|
+
bid = mol.GetBondBetweenAtoms(aid1,aid2).GetIdx()
|
|
178
|
+
bndhighlights[bid].append(colors[0])
|
|
179
|
+
|
|
180
|
+
if plot_aromatic:
|
|
181
|
+
from collections import defaultdict
|
|
182
|
+
colors = [(1.0, 0.0, 0.0, 0.4)]
|
|
183
|
+
athighlights = defaultdict(list)
|
|
184
|
+
arads = {}
|
|
185
|
+
for a in atoms:
|
|
186
|
+
if a.GetIsAromatic():
|
|
187
|
+
aid = a.GetIdx()
|
|
188
|
+
athighlights[aid].append(colors[0])
|
|
189
|
+
arads[aid] = 0.3
|
|
190
|
+
|
|
191
|
+
bndhighlights = defaultdict(list)
|
|
192
|
+
for bond in mol.GetBonds():
|
|
193
|
+
aid1 = bond.GetBeginAtomIdx()
|
|
194
|
+
aid2 = bond.GetEndAtomIdx()
|
|
195
|
+
|
|
196
|
+
if bond.GetIsAromatic():
|
|
197
|
+
bid = mol.GetBondBetweenAtoms(aid1,aid2).GetIdx()
|
|
198
|
+
bndhighlights[bid].append(colors[0])
|
|
199
|
+
|
|
200
|
+
if show_hybrid or show_Lewis:
|
|
201
|
+
for i,atom in enumerate(atoms):
|
|
202
|
+
# print(i,atom.GetDegree(),atom.GetImplicitValence())
|
|
203
|
+
note_parts = []
|
|
204
|
+
if show_hybrid and(atom.GetValence(rdkit.Chem.rdchem.ValenceType.IMPLICIT) > 0 or atom.GetDegree() > 1):
|
|
205
|
+
note_parts.append(str(atom.GetHybridization()))
|
|
206
|
+
if show_Lewis and i in lewis_info:
|
|
207
|
+
lp, vac = lewis_info[i]
|
|
208
|
+
if lp > 0:
|
|
209
|
+
note_parts.append(f" {lp}DNL")
|
|
210
|
+
if vac > 0:
|
|
211
|
+
note_parts.append(f" {vac}[]")
|
|
212
|
+
if note_parts:
|
|
213
|
+
mol.GetAtomWithIdx(i).SetProp('atomNote',"".join(note_parts))
|
|
214
|
+
# print(f"Atom {i+1:3}: {atom.GetAtomicNum():3} {atom.GetSymbol():>2} {atom.GetHybridization()}")
|
|
215
|
+
if show_Lewis:
|
|
216
|
+
display(df)
|
|
217
|
+
|
|
218
|
+
if show_n:
|
|
219
|
+
d2d.drawOptions().addAtomIndices=show_n
|
|
220
|
+
|
|
221
|
+
if plot_aromatic or plot_conjugation:
|
|
222
|
+
d2d.DrawMoleculeWithHighlights(mol,legend,dict(athighlights),dict(bndhighlights),arads,{})
|
|
223
|
+
else:
|
|
224
|
+
d2d.DrawMolecule(mol,legend=legend, highlightAtoms=highlightAtoms)
|
|
225
|
+
|
|
226
|
+
d2d.FinishDrawing()
|
|
227
|
+
display(SVG(d2d.GetDrawingText()))
|
|
228
|
+
|
|
229
|
+
return
|
|
230
|
+
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from scipy.optimize import curve_fit
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
class KORD:
|
|
9
|
+
"""
|
|
10
|
+
Initialize the kinetic study with experimental data.
|
|
11
|
+
Reaction: alpha A = beta B
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
- t_exp (array-like): Time values.
|
|
15
|
+
- G_exp (array-like): Measured physical quantity (Absorbance, Conductivity, etc.).
|
|
16
|
+
|
|
17
|
+
Fixed Parameters:
|
|
18
|
+
- alpha (float): Stoichiometric coefficient for A reactant (smallest positive integer). Default is 1.0.
|
|
19
|
+
- beta (float): Stoichiometric coefficient for B product (smallest positive integer). Default is 1.0.
|
|
20
|
+
- A0 (float): Initial concentration. Note: G_theo is independent of A0 for Order 1. Default is 1.0.
|
|
21
|
+
|
|
22
|
+
Adjustable Variables (Initial Guesses):
|
|
23
|
+
- k_guess (float, optional): Initial estimate for the rate constant. Default is 0.01.
|
|
24
|
+
- G_0_guess (float, optional): Initial estimate for the initial measured value (G at t=0). if None, it will be initialized as G_exp[0]
|
|
25
|
+
- G_inf_guess (float, optional): Initial estimate for the final measured value (G at infinity). if None, it will be initialized as G_exp[-1]
|
|
26
|
+
|
|
27
|
+
Other Args:
|
|
28
|
+
- verbose (bool): If True, enables debug messages and detailed optimization logs.
|
|
29
|
+
- headers (tuple of strings): headers of the t_exp and G_exp arrays read in the excel file
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, t_exp, G_exp, headers, A0=1.0, alpha=1.0, beta=1.0, k_guess=0.01,
|
|
33
|
+
G_0_guess = None, G_inf_guess = None, verbose=False):
|
|
34
|
+
|
|
35
|
+
# Force conversion to float arrays to prevent 'object' dtype issues.
|
|
36
|
+
# This ensures [0, 0.5, ...] (objects) become [0.0, 0.5, ...] (floats),
|
|
37
|
+
# allowing NumPy mathematical functions (like np.exp) to operate correctly.
|
|
38
|
+
self.t_exp = np.array(t_exp, dtype=float)
|
|
39
|
+
self.G_exp = np.array(G_exp, dtype=float)
|
|
40
|
+
self.headers = headers
|
|
41
|
+
# Ensure fixed parameters are treated as native floats.
|
|
42
|
+
# This prevents errors during optimization if values are passed as strings or tuples.
|
|
43
|
+
self.A0 = float(A0)
|
|
44
|
+
self.alpha = float(alpha)
|
|
45
|
+
self.beta = float(beta)
|
|
46
|
+
|
|
47
|
+
self.k_guess = k_guess
|
|
48
|
+
self.G_0_guess = G_0_guess if G_0_guess is not None else G_exp[0]
|
|
49
|
+
self.G_inf_guess = G_inf_guess if G_inf_guess is not None else G_exp[-1]
|
|
50
|
+
self.results = {}
|
|
51
|
+
self.verbose = verbose
|
|
52
|
+
# Color mapping for orders
|
|
53
|
+
self.order_colors = {0: 'red', 1: 'green', 2: 'blue'}
|
|
54
|
+
self.ansi_colors = {0: "\033[91m", 1: "\033[92m", 2: "\033[94m"}
|
|
55
|
+
self.reset = "\033[0m"
|
|
56
|
+
# t_fin = self.A0 / (self.alpha * self.k_guess)
|
|
57
|
+
# print(f"{t_fin=}")
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def load_from_excel(file_path, exp_number, sheet_name=0, show_data=True):
|
|
61
|
+
"""
|
|
62
|
+
Static method to extract data from an Excel file.
|
|
63
|
+
Selects the pair of columns (t, G) corresponding to the experiment number.
|
|
64
|
+
Also loads parameters (A0, alpha, beta)
|
|
65
|
+
Format:
|
|
66
|
+
Row 1: Headers for t and G
|
|
67
|
+
Row 2: [A]0 value (in the G column)
|
|
68
|
+
Row 3: alpha value (in the G column)
|
|
69
|
+
Row 4: beta value (in the G column)
|
|
70
|
+
Row 5+: [t, G] data points
|
|
71
|
+
"""
|
|
72
|
+
# 1. Check if file exists
|
|
73
|
+
if not os.path.exists(file_path):
|
|
74
|
+
print(f"❌ Error: The file '{file_path}' was not found.")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
df = pd.read_excel(file_path, sheet_name=sheet_name)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(f"❌ Error while reading the Excel file: {e}")
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
idx_t, idx_G = 2*(exp_number-1), 2*(exp_number-1)+1
|
|
84
|
+
|
|
85
|
+
# --- Parameter Extraction (Now looking in the G column: idx_G) ---
|
|
86
|
+
def parse_param(val):
|
|
87
|
+
if isinstance(val, str):
|
|
88
|
+
match = re.search(r"(\d+\.?\d*)", val)
|
|
89
|
+
return float(match.group(1)) if match else 1.0
|
|
90
|
+
return float(val) if pd.notnull(val) else 1.0
|
|
91
|
+
|
|
92
|
+
total_cols = len(df.columns)
|
|
93
|
+
num_experiments = total_cols // 2
|
|
94
|
+
print(f"Experiments detected: {num_experiments}")
|
|
95
|
+
|
|
96
|
+
# Parameters are expected in rows 2, 3, and 4 of Excel (indices 0, 1, 2)
|
|
97
|
+
a0 = parse_param(df.iloc[0, idx_G])
|
|
98
|
+
alpha = parse_param(df.iloc[1, idx_G])
|
|
99
|
+
beta = parse_param(df.iloc[2, idx_G])
|
|
100
|
+
|
|
101
|
+
# --- Data Extraction (From index 3 onwards) ---
|
|
102
|
+
data = df.iloc[3:, [idx_t, idx_G]].dropna()
|
|
103
|
+
|
|
104
|
+
label_t = KORD._clean_pandas_suffix(df.columns[idx_t])
|
|
105
|
+
label_G = KORD._clean_pandas_suffix(df.columns[idx_G])
|
|
106
|
+
data.columns = [label_t, label_G]
|
|
107
|
+
|
|
108
|
+
print(f"✅ Loaded: {label_G} (Exp {exp_number})")
|
|
109
|
+
print(f" [Parameters from {label_G}] A0: {a0:.4e} mol.L-1 | alpha: {alpha} | beta: {beta}\n")
|
|
110
|
+
|
|
111
|
+
if show_data:
|
|
112
|
+
from IPython.display import display
|
|
113
|
+
display(data)
|
|
114
|
+
|
|
115
|
+
return data.iloc[:, 0].values, data.iloc[:, 1].values, (label_t, label_G), (a0, alpha, beta)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _clean_pandas_suffix(name):
|
|
119
|
+
"""
|
|
120
|
+
Safely removes the '.1', '.2' suffixes added by Pandas for duplicate names.
|
|
121
|
+
Only matches a dot followed by digits at the VERY END of the string.
|
|
122
|
+
Example: 'mol.L-1.1' -> 'mol.L-1'
|
|
123
|
+
"""
|
|
124
|
+
return re.sub(r'\.\d+$', '', str(name))
|
|
125
|
+
|
|
126
|
+
def G0_theo(self, t, k, G0, Ginf):
|
|
127
|
+
"""
|
|
128
|
+
Continuous linear model for optimization.
|
|
129
|
+
Allows A(t) to be negative so curve_fit can find the gradient.
|
|
130
|
+
"""
|
|
131
|
+
return G0 + (float(self.alpha) * k * t / float(self.A0)) * (Ginf - G0)
|
|
132
|
+
|
|
133
|
+
def G1_theo(self, t, k, G0, Ginf):
|
|
134
|
+
"""Model for Order 1 kinetics"""
|
|
135
|
+
return Ginf + np.exp(-self.alpha * k * t) * (G0 - Ginf)
|
|
136
|
+
|
|
137
|
+
def G2_theo(self, t, k, G0, Ginf):
|
|
138
|
+
"""Model for Order 2 kinetics"""
|
|
139
|
+
return Ginf - (Ginf - G0) / (1 + self.A0 * self.alpha * k * t)
|
|
140
|
+
|
|
141
|
+
def fit(self, k_guess, G_0_guess, G_inf_guess, order=1):
|
|
142
|
+
"""
|
|
143
|
+
Fits the chosen kinetic model to the experimental data, with order=order (Default: 1)
|
|
144
|
+
verbose=True: prints the initial guess vector p0
|
|
145
|
+
"""
|
|
146
|
+
models = {0: self.G0_theo, 1: self.G1_theo, 2: self.G2_theo}
|
|
147
|
+
func = models[order]
|
|
148
|
+
|
|
149
|
+
# Initial guess vector [k, G0, Ginf]
|
|
150
|
+
p0 = [self.k_guess, self.G_0_guess, self.G_inf_guess]
|
|
151
|
+
|
|
152
|
+
# 1. Inspection des types des paramètres de classe
|
|
153
|
+
# print(f"--- TYPE CHECK (Order {order}) ---")
|
|
154
|
+
# print(f"self.A0: {type(self.A0)} | Value: {self.A0}")
|
|
155
|
+
# print(f"self.alpha: {type(self.alpha)} | Value: {self.alpha}")
|
|
156
|
+
# print("----------------------------------")
|
|
157
|
+
# print(f"p0 types: {[type(x) for x in p0]}")
|
|
158
|
+
# print(f"t_exp type: {type(self.t_exp)} | dtype: {self.t_exp.dtype}")
|
|
159
|
+
# print(f"G_exp type: {type(self.G_exp)} | dtype: {self.G_exp.dtype}")
|
|
160
|
+
# print("----------------------------------")
|
|
161
|
+
# print(self.t_exp)
|
|
162
|
+
# print("----------------------------------")
|
|
163
|
+
|
|
164
|
+
if self.verbose:
|
|
165
|
+
c = self.ansi_colors[order]
|
|
166
|
+
print(f"{c}--- DEBUG INITIAL GUESS (Order {order}) ---{self.reset}")
|
|
167
|
+
print(f" GUESS: k: {p0[0]:.2e} | G0: {p0[1]:.4f} | Ginf: {p0[2]:.4f}")
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
popt, _ = curve_fit(func, self.t_exp, self.G_exp, p0=p0)
|
|
171
|
+
k_opt, G0_opt, Ginf_opt = popt
|
|
172
|
+
G_theo = func(self.t_exp, *popt)
|
|
173
|
+
|
|
174
|
+
rmsd = np.sqrt(np.mean((self.G_exp - G_theo)**2))
|
|
175
|
+
|
|
176
|
+
# t1/2 calculation
|
|
177
|
+
if order == 0: t_half = self.A0 / (2 * self.alpha * k_opt)
|
|
178
|
+
elif order == 1: t_half = np.log(2) / (self.alpha * k_opt)
|
|
179
|
+
else: t_half = 1 / (self.A0 * self.alpha * k_opt)
|
|
180
|
+
|
|
181
|
+
if self.verbose:
|
|
182
|
+
# Aligned exactly with the GUESS print for easy comparison
|
|
183
|
+
print(f" OPTIM: k: {k_opt:.2e} | G0: {G0_opt:.4f} | Ginf: {Ginf_opt:.4f}")
|
|
184
|
+
print(f" ✅ RMSD: {rmsd:.2e}")
|
|
185
|
+
|
|
186
|
+
self.results[order] = {
|
|
187
|
+
'k': k_opt, 'G0': G0_opt, 'Ginf': Ginf_opt,
|
|
188
|
+
'rmsd': rmsd, 't_half': t_half, 'G_theo': G_theo
|
|
189
|
+
}
|
|
190
|
+
return self.results[order]
|
|
191
|
+
except Exception as e:
|
|
192
|
+
print(f"Could not fit order {order}: {e}")
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
def plot_all_fits(self):
|
|
196
|
+
"""Plots experimental data and all three kinetic models for visual comparison."""
|
|
197
|
+
plt.figure(figsize=(10, 6))
|
|
198
|
+
ax1 = plt.gca()
|
|
199
|
+
|
|
200
|
+
ax1.scatter(self.t_exp, self.G_exp, label="Experimental", color='black', s=35, alpha=0.5)
|
|
201
|
+
|
|
202
|
+
t_smooth = np.linspace(self.t_exp.min(), self.t_exp.max(), 500)
|
|
203
|
+
models = {0: self.G0_theo, 1: self.G1_theo, 2: self.G2_theo}
|
|
204
|
+
|
|
205
|
+
for order in [0, 1, 2]:
|
|
206
|
+
if order not in self.results:
|
|
207
|
+
self.fit(self.k_guess, self.G_0_guess, self.G_inf_guess, order)
|
|
208
|
+
|
|
209
|
+
res = self.results[order]
|
|
210
|
+
|
|
211
|
+
G_smooth = models[order](t_smooth, res['k'], res['G0'], res['Ginf'])
|
|
212
|
+
# if order == 0:
|
|
213
|
+
# t_fin = self.A0 / (self.alpha * res['k'])
|
|
214
|
+
# print(f"{t_fin=}")
|
|
215
|
+
|
|
216
|
+
ax1.plot(t_smooth, G_smooth,
|
|
217
|
+
label=f"Order {order} (RMSD: {res['rmsd']:.2e})",
|
|
218
|
+
color=self.order_colors[order], lw=2)
|
|
219
|
+
|
|
220
|
+
# 3. Add horizontal lines for the BEST model
|
|
221
|
+
best_order = self.get_best_order(verbose=False)
|
|
222
|
+
best_res = self.results[best_order]
|
|
223
|
+
best_color = self.order_colors[best_order]
|
|
224
|
+
|
|
225
|
+
ax1.axhline(best_res['G0'], color=best_color, linestyle='--', alpha=0.6)
|
|
226
|
+
ax1.axhline(best_res['Ginf'], color=best_color, linestyle='--', alpha=0.6)
|
|
227
|
+
|
|
228
|
+
# 4. Add the second axis for the fitted values
|
|
229
|
+
ax2 = ax1.twinx()
|
|
230
|
+
ax2.set_ylim(ax1.get_ylim()) # Keep scales aligned
|
|
231
|
+
ax2.set_yticks([best_res['G0'], best_res['Ginf']])
|
|
232
|
+
ax2.set_yticklabels([f"G0_fit={best_res['G0']:.3f}", f"Ginf_fit={best_res['Ginf']:.3f}"])
|
|
233
|
+
ax2.tick_params(axis='y', labelcolor=best_color)
|
|
234
|
+
|
|
235
|
+
ax1.set_xlabel("Time")
|
|
236
|
+
ax1.set_ylabel("Quantity G")
|
|
237
|
+
ax1.set_title(f"KORD Kinetic Models Comparison (0, 1, 2). Label exp = {self.headers[1]}")
|
|
238
|
+
ax1.legend()
|
|
239
|
+
# ax1.grid(True, linestyle=':', alpha=0.6)
|
|
240
|
+
plt.show()
|
|
241
|
+
|
|
242
|
+
def get_best_order(self, verbose=True):
|
|
243
|
+
"""Determines and prints the best model based on the lowest RMSD."""
|
|
244
|
+
for i in [0, 1, 2]:
|
|
245
|
+
if i not in self.results: self.fit(self.k_guess, self.G_0_guess,
|
|
246
|
+
self.G_inf_guess, i)
|
|
247
|
+
|
|
248
|
+
best_order = min(self.results, key=lambda x: self.results[x]['rmsd'])
|
|
249
|
+
res = self.results[best_order]
|
|
250
|
+
|
|
251
|
+
if verbose:
|
|
252
|
+
# ANSI Escape sequences for color in terminal/notebook
|
|
253
|
+
reset = self.reset
|
|
254
|
+
color = self.ansi_colors[best_order]
|
|
255
|
+
|
|
256
|
+
print(f"--- {color}KORD CONCLUSION ---")
|
|
257
|
+
print(f"Best model: ORDER {best_order}")
|
|
258
|
+
print(f"Initial concentration: {self.A0:.3e} mol.L-1")
|
|
259
|
+
print(f"alpha: {self.alpha}")
|
|
260
|
+
print(f"beta: {self.beta}")
|
|
261
|
+
print()
|
|
262
|
+
print(f"RMSD: {res['rmsd']:.2e}")
|
|
263
|
+
print(f"k: {res['k']:.3e}")
|
|
264
|
+
print(f"t1/2: {res['t_half']:.3f}")
|
|
265
|
+
print()
|
|
266
|
+
print(f"G0_exp: {self.G_exp[0]:.3e}")
|
|
267
|
+
print(f"G0_fit: {res['G0']:.3e}")
|
|
268
|
+
print(f"Ginf_fit: {res['Ginf']:.3e}")
|
|
269
|
+
print(f"------------------------{reset}")
|
|
270
|
+
return
|
|
271
|
+
else:
|
|
272
|
+
return best_order
|
pyphyschemtools/__init__.py
CHANGED
pyphyschemtools/kinetics.py
CHANGED
|
@@ -6,78 +6,113 @@ import os
|
|
|
6
6
|
import re
|
|
7
7
|
|
|
8
8
|
class KORD:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
"""
|
|
10
|
+
Initialize the kinetic study with experimental data.
|
|
11
|
+
Reaction: alpha A = beta B
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
- t_exp (array-like): Time values.
|
|
15
|
+
- G_exp (array-like): Measured physical quantity (Absorbance, Conductivity, etc.).
|
|
16
|
+
|
|
17
|
+
Fixed Parameters:
|
|
18
|
+
- alpha (float): Stoichiometric coefficient for A reactant (smallest positive integer). Default is 1.0.
|
|
19
|
+
- beta (float): Stoichiometric coefficient for B product (smallest positive integer). Default is 1.0.
|
|
20
|
+
- A0 (float): Initial concentration. Note: G_theo is independent of A0 for Order 1. Default is 1.0.
|
|
21
|
+
|
|
22
|
+
Adjustable Variables (Initial Guesses):
|
|
23
|
+
- k_guess (float, optional): Initial estimate for the rate constant. Default is 0.01.
|
|
24
|
+
- G_0_guess (float, optional): Initial estimate for the initial measured value (G at t=0). if None, it will be initialized as G_exp[0]
|
|
25
|
+
- G_inf_guess (float, optional): Initial estimate for the final measured value (G at infinity). if None, it will be initialized as G_exp[-1]
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
Other Args:
|
|
28
|
+
- verbose (bool): If True, enables debug messages and detailed optimization logs.
|
|
29
|
+
- headers (tuple of strings): headers of the t_exp and G_exp arrays read in the excel file
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, t_exp, G_exp, headers, A0=1.0, alpha=1.0, beta=1.0, k_guess=0.01,
|
|
33
|
+
G_0_guess = None, G_inf_guess = None, verbose=False):
|
|
34
|
+
|
|
35
|
+
# Force conversion to float arrays to prevent 'object' dtype issues.
|
|
36
|
+
# This ensures [0, 0.5, ...] (objects) become [0.0, 0.5, ...] (floats),
|
|
37
|
+
# allowing NumPy mathematical functions (like np.exp) to operate correctly.
|
|
38
|
+
self.t_exp = np.array(t_exp, dtype=float)
|
|
39
|
+
self.G_exp = np.array(G_exp, dtype=float)
|
|
23
40
|
self.headers = headers
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
# Ensure fixed parameters are treated as native floats.
|
|
42
|
+
# This prevents errors during optimization if values are passed as strings or tuples.
|
|
43
|
+
self.A0 = float(A0)
|
|
44
|
+
self.alpha = float(alpha)
|
|
45
|
+
self.beta = float(beta)
|
|
46
|
+
|
|
26
47
|
self.k_guess = k_guess
|
|
48
|
+
self.G_0_guess = G_0_guess if G_0_guess is not None else G_exp[0]
|
|
49
|
+
self.G_inf_guess = G_inf_guess if G_inf_guess is not None else G_exp[-1]
|
|
27
50
|
self.results = {}
|
|
28
51
|
self.verbose = verbose
|
|
29
52
|
# Color mapping for orders
|
|
30
53
|
self.order_colors = {0: 'red', 1: 'green', 2: 'blue'}
|
|
31
54
|
self.ansi_colors = {0: "\033[91m", 1: "\033[92m", 2: "\033[94m"}
|
|
32
55
|
self.reset = "\033[0m"
|
|
56
|
+
# t_fin = self.A0 / (self.alpha * self.k_guess)
|
|
57
|
+
# print(f"{t_fin=}")
|
|
33
58
|
|
|
34
59
|
@staticmethod
|
|
35
60
|
def load_from_excel(file_path, exp_number, sheet_name=0, show_data=True):
|
|
36
61
|
"""
|
|
37
62
|
Static method to extract data from an Excel file.
|
|
38
63
|
Selects the pair of columns (t, G) corresponding to the experiment number.
|
|
64
|
+
Also loads parameters (A0, alpha, beta)
|
|
65
|
+
Format:
|
|
66
|
+
Row 1: Headers for t and G
|
|
67
|
+
Row 2: [A]0 value (in the G column)
|
|
68
|
+
Row 3: alpha value (in the G column)
|
|
69
|
+
Row 4: beta value (in the G column)
|
|
70
|
+
Row 5+: [t, G] data points
|
|
39
71
|
"""
|
|
40
72
|
# 1. Check if file exists
|
|
41
73
|
if not os.path.exists(file_path):
|
|
42
74
|
print(f"❌ Error: The file '{file_path}' was not found.")
|
|
43
|
-
return None
|
|
75
|
+
return None
|
|
44
76
|
|
|
45
77
|
try:
|
|
46
78
|
df = pd.read_excel(file_path, sheet_name=sheet_name)
|
|
47
79
|
except Exception as e:
|
|
48
80
|
print(f"❌ Error while reading the Excel file: {e}")
|
|
49
|
-
return None
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
idx_t, idx_G = 2*(exp_number-1), 2*(exp_number-1)+1
|
|
50
84
|
|
|
85
|
+
# --- Parameter Extraction (Now looking in the G column: idx_G) ---
|
|
86
|
+
def parse_param(val):
|
|
87
|
+
if isinstance(val, str):
|
|
88
|
+
match = re.search(r"(\d+\.?\d*)", val)
|
|
89
|
+
return float(match.group(1)) if match else 1.0
|
|
90
|
+
return float(val) if pd.notnull(val) else 1.0
|
|
91
|
+
|
|
51
92
|
total_cols = len(df.columns)
|
|
52
93
|
num_experiments = total_cols // 2
|
|
53
|
-
|
|
54
94
|
print(f"Experiments detected: {num_experiments}")
|
|
55
95
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
print(f"-----------------------\n")
|
|
64
|
-
|
|
65
|
-
if exp_number < 1 or exp_number > num_experiments:
|
|
66
|
-
print(f"⚠️ Error: Experiment {exp_number} does not exist (Choice: 1 to {num_experiments}).")
|
|
67
|
-
return None, None, None, None
|
|
96
|
+
# Parameters are expected in rows 2, 3, and 4 of Excel (indices 0, 1, 2)
|
|
97
|
+
a0 = parse_param(df.iloc[0, idx_G])
|
|
98
|
+
alpha = parse_param(df.iloc[1, idx_G])
|
|
99
|
+
beta = parse_param(df.iloc[2, idx_G])
|
|
100
|
+
|
|
101
|
+
# --- Data Extraction (From index 3 onwards) ---
|
|
102
|
+
data = df.iloc[3:, [idx_t, idx_G]].dropna()
|
|
68
103
|
|
|
69
|
-
# Position-based extraction
|
|
70
|
-
idx_t, idx_G = 2*(exp_number-1), 2*(exp_number-1)+1
|
|
71
104
|
label_t = KORD._clean_pandas_suffix(df.columns[idx_t])
|
|
72
105
|
label_G = KORD._clean_pandas_suffix(df.columns[idx_G])
|
|
73
|
-
|
|
74
|
-
data = df.iloc[:, [idx_t, idx_G]].dropna()
|
|
75
106
|
data.columns = [label_t, label_G]
|
|
76
|
-
|
|
107
|
+
|
|
108
|
+
print(f"✅ Loaded: {label_G} (Exp {exp_number})")
|
|
109
|
+
print(f" [Parameters from {label_G}] A0: {a0:.4e} mol.L-1 | alpha: {alpha} | beta: {beta}\n")
|
|
77
110
|
|
|
78
|
-
if show_data:
|
|
111
|
+
if show_data:
|
|
112
|
+
from IPython.display import display
|
|
113
|
+
display(data)
|
|
79
114
|
|
|
80
|
-
return data.iloc[:, 0].values, data.iloc[:, 1].values, (label_t, label_G)
|
|
115
|
+
return data.iloc[:, 0].values, data.iloc[:, 1].values, (label_t, label_G), (a0, alpha, beta)
|
|
81
116
|
|
|
82
117
|
@staticmethod
|
|
83
118
|
def _clean_pandas_suffix(name):
|
|
@@ -89,21 +124,21 @@ class KORD:
|
|
|
89
124
|
return re.sub(r'\.\d+$', '', str(name))
|
|
90
125
|
|
|
91
126
|
def G0_theo(self, t, k, G0, Ginf):
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
127
|
+
"""
|
|
128
|
+
Continuous linear model for optimization.
|
|
129
|
+
Allows A(t) to be negative so curve_fit can find the gradient.
|
|
130
|
+
"""
|
|
131
|
+
return G0 + (float(self.alpha) * k * t / float(self.A0)) * (Ginf - G0)
|
|
97
132
|
|
|
98
133
|
def G1_theo(self, t, k, G0, Ginf):
|
|
99
|
-
"""Model for Order 1 kinetics
|
|
134
|
+
"""Model for Order 1 kinetics"""
|
|
100
135
|
return Ginf + np.exp(-self.alpha * k * t) * (G0 - Ginf)
|
|
101
136
|
|
|
102
137
|
def G2_theo(self, t, k, G0, Ginf):
|
|
103
|
-
"""Model for Order 2 kinetics
|
|
138
|
+
"""Model for Order 2 kinetics"""
|
|
104
139
|
return Ginf - (Ginf - G0) / (1 + self.A0 * self.alpha * k * t)
|
|
105
140
|
|
|
106
|
-
def fit(self, order=1
|
|
141
|
+
def fit(self, k_guess, G_0_guess, G_inf_guess, order=1):
|
|
107
142
|
"""
|
|
108
143
|
Fits the chosen kinetic model to the experimental data, with order=order (Default: 1)
|
|
109
144
|
verbose=True: prints the initial guess vector p0
|
|
@@ -111,10 +146,21 @@ class KORD:
|
|
|
111
146
|
models = {0: self.G0_theo, 1: self.G1_theo, 2: self.G2_theo}
|
|
112
147
|
func = models[order]
|
|
113
148
|
|
|
114
|
-
# Initial guess [k, G0, Ginf]
|
|
115
|
-
current_k = k_guess if k_guess is not None else self.k_guess
|
|
116
149
|
# Initial guess vector [k, G0, Ginf]
|
|
117
|
-
p0 = [
|
|
150
|
+
p0 = [self.k_guess, self.G_0_guess, self.G_inf_guess]
|
|
151
|
+
|
|
152
|
+
# 1. Inspection des types des paramètres de classe
|
|
153
|
+
# print(f"--- TYPE CHECK (Order {order}) ---")
|
|
154
|
+
# print(f"self.A0: {type(self.A0)} | Value: {self.A0}")
|
|
155
|
+
# print(f"self.alpha: {type(self.alpha)} | Value: {self.alpha}")
|
|
156
|
+
# print("----------------------------------")
|
|
157
|
+
# print(f"p0 types: {[type(x) for x in p0]}")
|
|
158
|
+
# print(f"t_exp type: {type(self.t_exp)} | dtype: {self.t_exp.dtype}")
|
|
159
|
+
# print(f"G_exp type: {type(self.G_exp)} | dtype: {self.G_exp.dtype}")
|
|
160
|
+
# print("----------------------------------")
|
|
161
|
+
# print(self.t_exp)
|
|
162
|
+
# print("----------------------------------")
|
|
163
|
+
|
|
118
164
|
if self.verbose:
|
|
119
165
|
c = self.ansi_colors[order]
|
|
120
166
|
print(f"{c}--- DEBUG INITIAL GUESS (Order {order}) ---{self.reset}")
|
|
@@ -124,6 +170,7 @@ class KORD:
|
|
|
124
170
|
popt, _ = curve_fit(func, self.t_exp, self.G_exp, p0=p0)
|
|
125
171
|
k_opt, G0_opt, Ginf_opt = popt
|
|
126
172
|
G_theo = func(self.t_exp, *popt)
|
|
173
|
+
|
|
127
174
|
rmsd = np.sqrt(np.mean((self.G_exp - G_theo)**2))
|
|
128
175
|
|
|
129
176
|
# t1/2 calculation
|
|
@@ -148,46 +195,78 @@ class KORD:
|
|
|
148
195
|
def plot_all_fits(self):
|
|
149
196
|
"""Plots experimental data and all three kinetic models for visual comparison."""
|
|
150
197
|
plt.figure(figsize=(10, 6))
|
|
151
|
-
|
|
198
|
+
ax1 = plt.gca()
|
|
199
|
+
|
|
200
|
+
ax1.scatter(self.t_exp, self.G_exp, label="Experimental", color='black', s=35, alpha=0.5)
|
|
152
201
|
|
|
153
202
|
t_smooth = np.linspace(self.t_exp.min(), self.t_exp.max(), 500)
|
|
154
203
|
models = {0: self.G0_theo, 1: self.G1_theo, 2: self.G2_theo}
|
|
155
|
-
|
|
204
|
+
|
|
156
205
|
for order in [0, 1, 2]:
|
|
157
206
|
if order not in self.results:
|
|
158
|
-
self.fit(order)
|
|
207
|
+
self.fit(self.k_guess, self.G_0_guess, self.G_inf_guess, order)
|
|
159
208
|
|
|
160
209
|
res = self.results[order]
|
|
161
210
|
|
|
162
211
|
G_smooth = models[order](t_smooth, res['k'], res['G0'], res['Ginf'])
|
|
212
|
+
# if order == 0:
|
|
213
|
+
# t_fin = self.A0 / (self.alpha * res['k'])
|
|
214
|
+
# print(f"{t_fin=}")
|
|
163
215
|
|
|
164
|
-
|
|
216
|
+
ax1.plot(t_smooth, G_smooth,
|
|
165
217
|
label=f"Order {order} (RMSD: {res['rmsd']:.2e})",
|
|
166
218
|
color=self.order_colors[order], lw=2)
|
|
219
|
+
|
|
220
|
+
# 3. Add horizontal lines for the BEST model
|
|
221
|
+
best_order = self.get_best_order(verbose=False)
|
|
222
|
+
best_res = self.results[best_order]
|
|
223
|
+
best_color = self.order_colors[best_order]
|
|
224
|
+
|
|
225
|
+
ax1.axhline(best_res['G0'], color=best_color, linestyle='--', alpha=0.6)
|
|
226
|
+
ax1.axhline(best_res['Ginf'], color=best_color, linestyle='--', alpha=0.6)
|
|
227
|
+
|
|
228
|
+
# 4. Add the second axis for the fitted values
|
|
229
|
+
ax2 = ax1.twinx()
|
|
230
|
+
ax2.set_ylim(ax1.get_ylim()) # Keep scales aligned
|
|
231
|
+
ax2.set_yticks([best_res['G0'], best_res['Ginf']])
|
|
232
|
+
ax2.set_yticklabels([f"G0_fit={best_res['G0']:.3f}", f"Ginf_fit={best_res['Ginf']:.3f}"])
|
|
233
|
+
ax2.tick_params(axis='y', labelcolor=best_color)
|
|
167
234
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
235
|
+
ax1.set_xlabel("Time")
|
|
236
|
+
ax1.set_ylabel("Quantity G")
|
|
237
|
+
ax1.set_title(f"KORD Kinetic Models Comparison (0, 1, 2). Label exp = {self.headers[1]}")
|
|
238
|
+
ax1.legend()
|
|
239
|
+
# ax1.grid(True, linestyle=':', alpha=0.6)
|
|
173
240
|
plt.show()
|
|
174
241
|
|
|
175
|
-
def get_best_order(self):
|
|
242
|
+
def get_best_order(self, verbose=True):
|
|
176
243
|
"""Determines and prints the best model based on the lowest RMSD."""
|
|
177
244
|
for i in [0, 1, 2]:
|
|
178
|
-
if i not in self.results: self.fit(
|
|
245
|
+
if i not in self.results: self.fit(self.k_guess, self.G_0_guess,
|
|
246
|
+
self.G_inf_guess, i)
|
|
179
247
|
|
|
180
248
|
best_order = min(self.results, key=lambda x: self.results[x]['rmsd'])
|
|
181
249
|
res = self.results[best_order]
|
|
182
250
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
251
|
+
if verbose:
|
|
252
|
+
# ANSI Escape sequences for color in terminal/notebook
|
|
253
|
+
reset = self.reset
|
|
254
|
+
color = self.ansi_colors[best_order]
|
|
255
|
+
|
|
256
|
+
print(f"--- {color}KORD CONCLUSION ---")
|
|
257
|
+
print(f"Best model: ORDER {best_order}")
|
|
258
|
+
print(f"Initial concentration: {self.A0:.3e} mol.L-1")
|
|
259
|
+
print(f"alpha: {self.alpha}")
|
|
260
|
+
print(f"beta: {self.beta}")
|
|
261
|
+
print()
|
|
262
|
+
print(f"RMSD: {res['rmsd']:.2e}")
|
|
263
|
+
print(f"k: {res['k']:.3e}")
|
|
264
|
+
print(f"t1/2: {res['t_half']:.3f}")
|
|
265
|
+
print()
|
|
266
|
+
print(f"G0_exp: {self.G_exp[0]:.3e}")
|
|
267
|
+
print(f"G0_fit: {res['G0']:.3e}")
|
|
268
|
+
print(f"Ginf_fit: {res['Ginf']:.3e}")
|
|
269
|
+
print(f"------------------------{reset}")
|
|
270
|
+
return
|
|
271
|
+
else:
|
|
272
|
+
return best_order
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyphyschemtools
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: A comprehensive Python toolbox for physical chemistry and cheminformatics
|
|
5
5
|
Author-email: "Romuald POTEAU, LPCNO" <romuald.poteau@utoulouse.fr>
|
|
6
6
|
Project-URL: Repository, https://github.com/rpoteau/pyphyschemtools
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
pyphyschemtools/.readthedocs.yaml,sha256=ZTw2bOyF9p3JpeF8Ux0fwhYWO6KHCsroNEOvnXxbYGM,469
|
|
2
|
-
pyphyschemtools/.visualID_Eng.py.swp,sha256=e47tjB8u-H3ZCAru897onACs2xbR2XhrLBMLhqrrzS8,20480
|
|
3
2
|
pyphyschemtools/Chem3D.py,sha256=NuhoLvWpeATO88UklXg-3XqPpIiHqvP__b2tLHrypL8,34718
|
|
4
3
|
pyphyschemtools/ML.py,sha256=kR_5vm5TOOjVef8uXCW57y7685ts6K6OkRMBYKP_cYw,1599
|
|
5
4
|
pyphyschemtools/PeriodicTable.py,sha256=LfLSFOzRkirREQlwfeSR3TyvgHyjGiltIZXNmvBkbhQ,13526
|
|
6
|
-
pyphyschemtools/__init__.py,sha256=
|
|
5
|
+
pyphyschemtools/__init__.py,sha256=CqTWs1geGKDrA9V_CnzNFTukpwQ8-z9qQl7FwluLrqo,1442
|
|
7
6
|
pyphyschemtools/aithermo.py,sha256=kF8wtuYIJzkUKM2AGubmn9haAJKz-XaBskZ7HjivJeY,14984
|
|
8
7
|
pyphyschemtools/cheminformatics.py,sha256=Qps_JSYWOzZQcXwKElI1iWGjWAPDgwmtDKuJwONsKmI,8977
|
|
9
8
|
pyphyschemtools/core.py,sha256=5fRu83b125w2p_m2H521fLjktyswZHJXNKww1wfBwbU,4847
|
|
10
|
-
pyphyschemtools/kinetics.py,sha256=
|
|
9
|
+
pyphyschemtools/kinetics.py,sha256=IVZSY38ai9yO8GyMVXpywfzKMZ6K8Q3n-2UiElOoCyA,11737
|
|
11
10
|
pyphyschemtools/spectra.py,sha256=eCN9X6pK5k4KMcfWUskedWYwtxbrmJH_BvFXU1GZfVo,21702
|
|
12
11
|
pyphyschemtools/survey.py,sha256=YjZhhb8GFVNXoXSCxgGdZFqmCtNCx7O_uiFVCcGBYYo,24268
|
|
13
12
|
pyphyschemtools/sympyUtilities.py,sha256=LgLloh9dD9Mkff2WNoSnrJa3hxK0axOnK-4GS9wPtT0,1545
|
|
@@ -17,7 +16,9 @@ pyphyschemtools/visualID_Eng.py,sha256=W-rYHg4g090JUpTJxtbBZVZ2lXShq8f0ALeiNjFqW
|
|
|
17
16
|
pyphyschemtools/.ipynb_checkpoints/Chem3D-checkpoint.py,sha256=NuhoLvWpeATO88UklXg-3XqPpIiHqvP__b2tLHrypL8,34718
|
|
18
17
|
pyphyschemtools/.ipynb_checkpoints/PeriodicTable-checkpoint.py,sha256=LfLSFOzRkirREQlwfeSR3TyvgHyjGiltIZXNmvBkbhQ,13526
|
|
19
18
|
pyphyschemtools/.ipynb_checkpoints/aithermo-checkpoint.py,sha256=kF8wtuYIJzkUKM2AGubmn9haAJKz-XaBskZ7HjivJeY,14984
|
|
19
|
+
pyphyschemtools/.ipynb_checkpoints/cheminformatics-checkpoint.py,sha256=Qps_JSYWOzZQcXwKElI1iWGjWAPDgwmtDKuJwONsKmI,8977
|
|
20
20
|
pyphyschemtools/.ipynb_checkpoints/core-checkpoint.py,sha256=5fRu83b125w2p_m2H521fLjktyswZHJXNKww1wfBwbU,4847
|
|
21
|
+
pyphyschemtools/.ipynb_checkpoints/kinetics-checkpoint.py,sha256=IVZSY38ai9yO8GyMVXpywfzKMZ6K8Q3n-2UiElOoCyA,11737
|
|
21
22
|
pyphyschemtools/.ipynb_checkpoints/spectra-checkpoint.py,sha256=eCN9X6pK5k4KMcfWUskedWYwtxbrmJH_BvFXU1GZfVo,21702
|
|
22
23
|
pyphyschemtools/.ipynb_checkpoints/survey-checkpoint.py,sha256=Rcw0xb0_nwsxETleB1C2xjKmZfrUw4PXDm48CMSptHU,45816
|
|
23
24
|
pyphyschemtools/.ipynb_checkpoints/sympyUtilities-checkpoint.py,sha256=LgLloh9dD9Mkff2WNoSnrJa3hxK0axOnK-4GS9wPtT0,1545
|
|
@@ -86,8 +87,8 @@ pyphyschemtools/resources/svg/qrcode-pyPhysChem.png,sha256=rP7X-9eHL7HYj4ffmwBML
|
|
|
86
87
|
pyphyschemtools/resources/svg/repository-open-graph-template.png,sha256=UlnW5BMkLGOv6IAnEi7teDYS_5qeSLmpxRMT9r9m-5Q,51470
|
|
87
88
|
pyphyschemtools/resources/svg/tools4pyPC_banner.png,sha256=z7o_kBK0sIBsXHEJrT2GyLHu-0T0T3S8YkWcpxR2joA,89058
|
|
88
89
|
pyphyschemtools/resources/svg/tools4pyPC_banner.svg,sha256=BXxXHra9vwahaiet1IJW4q8QLA03crSeCIQYo30VpN8,651579
|
|
89
|
-
pyphyschemtools-0.3.
|
|
90
|
-
pyphyschemtools-0.3.
|
|
91
|
-
pyphyschemtools-0.3.
|
|
92
|
-
pyphyschemtools-0.3.
|
|
93
|
-
pyphyschemtools-0.3.
|
|
90
|
+
pyphyschemtools-0.3.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
91
|
+
pyphyschemtools-0.3.4.dist-info/METADATA,sha256=hb_ShyL8oPomJKdJODh18tFkUEnkjNkrn1M4PvRTE58,1328
|
|
92
|
+
pyphyschemtools-0.3.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
93
|
+
pyphyschemtools-0.3.4.dist-info/top_level.txt,sha256=N92w2qk4LQ42OSdzK1R2h_x1CyUFaFBOrOML2RnmFgE,16
|
|
94
|
+
pyphyschemtools-0.3.4.dist-info/RECORD,,
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|