pyphyschemtools 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.
Files changed (80) hide show
  1. pyphyschemtools/Chem3D.py +831 -0
  2. pyphyschemtools/ML.py +42 -0
  3. pyphyschemtools/PeriodicTable.py +289 -0
  4. pyphyschemtools/__init__.py +43 -0
  5. pyphyschemtools/aithermo.py +350 -0
  6. pyphyschemtools/cheminformatics.py +230 -0
  7. pyphyschemtools/core.py +119 -0
  8. pyphyschemtools/icons-logos-banner/Logo_pyPhysChem_border.svg +1109 -0
  9. pyphyschemtools/icons-logos-banner/__init__.py +0 -0
  10. pyphyschemtools/icons-logos-banner/logo.png +0 -0
  11. pyphyschemtools/icons-logos-banner/tools4pyPC_banner.png +0 -0
  12. pyphyschemtools/icons-logos-banner/tools4pyPC_banner.svg +193 -0
  13. pyphyschemtools/kinetics.py +193 -0
  14. pyphyschemtools/resources/css/BrainHalfHalf-120x139.base64 +1 -0
  15. pyphyschemtools/resources/css/BrainHalfHalf-120x139.png +0 -0
  16. pyphyschemtools/resources/css/BrainHalfHalf.base64 +8231 -0
  17. pyphyschemtools/resources/css/BrainHalfHalf.png +0 -0
  18. pyphyschemtools/resources/css/BrainHalfHalf.svg +289 -0
  19. pyphyschemtools/resources/css/visualID.css +325 -0
  20. pyphyschemtools/resources/img/Tranformative_3.webp +0 -0
  21. pyphyschemtools/resources/img/Tranformative_3_banner.png +0 -0
  22. pyphyschemtools/resources/img/pyPhysChem_1.png +0 -0
  23. pyphyschemtools/resources/svg/BrainHalfHalf.png +0 -0
  24. pyphyschemtools/resources/svg/BrainHalfHalf.svg +289 -0
  25. pyphyschemtools/resources/svg/GitHub-Logo-C.png +0 -0
  26. pyphyschemtools/resources/svg/GitHub-Logo.png +0 -0
  27. pyphyschemtools/resources/svg/Logo-Universite-Toulouse-n-2023.png +0 -0
  28. pyphyschemtools/resources/svg/Logo_pyPhysChem_1-translucentBgd-woName.png +0 -0
  29. pyphyschemtools/resources/svg/Logo_pyPhysChem_1-translucentBgd.png +0 -0
  30. pyphyschemtools/resources/svg/Logo_pyPhysChem_1.png +0 -0
  31. pyphyschemtools/resources/svg/Logo_pyPhysChem_1.svg +622 -0
  32. pyphyschemtools/resources/svg/Logo_pyPhysChem_5.png +0 -0
  33. pyphyschemtools/resources/svg/Logo_pyPhysChem_5.svg +48 -0
  34. pyphyschemtools/resources/svg/Logo_pyPhysChem_border.svg +1109 -0
  35. pyphyschemtools/resources/svg/Python-logo-notext.svg +265 -0
  36. pyphyschemtools/resources/svg/Python_logo_and_wordmark.svg.png +0 -0
  37. pyphyschemtools/resources/svg/UT3_logoQ.jpg +0 -0
  38. pyphyschemtools/resources/svg/UT3_logoQ.png +0 -0
  39. pyphyschemtools/resources/svg/Universite-Toulouse-n-2023.svg +141 -0
  40. pyphyschemtools/resources/svg/X.png +0 -0
  41. pyphyschemtools/resources/svg/logoAnaconda.png +0 -0
  42. pyphyschemtools/resources/svg/logoAnaconda.webp +0 -0
  43. pyphyschemtools/resources/svg/logoCNRS.png +0 -0
  44. pyphyschemtools/resources/svg/logoDebut.svg +316 -0
  45. pyphyschemtools/resources/svg/logoEnd.svg +172 -0
  46. pyphyschemtools/resources/svg/logoFin.svg +172 -0
  47. pyphyschemtools/resources/svg/logoPPCL.svg +359 -0
  48. pyphyschemtools/resources/svg/logoPytChem.png +0 -0
  49. pyphyschemtools/resources/svg/logo_lpcno_300_dpi_notexttransparent.png +0 -0
  50. pyphyschemtools/resources/svg/logo_pyPhysChem.png +0 -0
  51. pyphyschemtools/resources/svg/logo_pyPhysChem_0.png +0 -0
  52. pyphyschemtools/resources/svg/logo_pyPhysChem_0.svg +390 -0
  53. pyphyschemtools/resources/svg/logopyPhyschem.png +0 -0
  54. pyphyschemtools/resources/svg/logopyPhyschem_2.webp +0 -0
  55. pyphyschemtools/resources/svg/logopyPhyschem_3.webp +0 -0
  56. pyphyschemtools/resources/svg/logopyPhyschem_4.webp +0 -0
  57. pyphyschemtools/resources/svg/logopyPhyschem_5.png +0 -0
  58. pyphyschemtools/resources/svg/logopyPhyschem_5.webp +0 -0
  59. pyphyschemtools/resources/svg/logopyPhyschem_6.webp +0 -0
  60. pyphyschemtools/resources/svg/logopyPhyschem_7.webp +0 -0
  61. pyphyschemtools/resources/svg/logos-Anaconda-pyPhysChem.png +0 -0
  62. pyphyschemtools/resources/svg/logos-Anaconda-pyPhysChem.svg +58 -0
  63. pyphyschemtools/resources/svg/pyPCBanner.svg +309 -0
  64. pyphyschemtools/resources/svg/pyPhysChem-GitHubSocialMediaTemplate.png +0 -0
  65. pyphyschemtools/resources/svg/pyPhysChem-GitHubSocialMediaTemplate.svg +295 -0
  66. pyphyschemtools/resources/svg/pyPhysChemBanner.png +0 -0
  67. pyphyschemtools/resources/svg/pyPhysChemBanner.svg +639 -0
  68. pyphyschemtools/resources/svg/qrcode-pyPhysChem.png +0 -0
  69. pyphyschemtools/resources/svg/repository-open-graph-template.png +0 -0
  70. pyphyschemtools/spectra.py +451 -0
  71. pyphyschemtools/survey.py +1048 -0
  72. pyphyschemtools/sympyUtilities.py +51 -0
  73. pyphyschemtools/tools4AS.py +960 -0
  74. pyphyschemtools/visualID.py +101 -0
  75. pyphyschemtools/visualID_Eng.py +175 -0
  76. pyphyschemtools-0.1.0.dist-info/METADATA +38 -0
  77. pyphyschemtools-0.1.0.dist-info/RECORD +80 -0
  78. pyphyschemtools-0.1.0.dist-info/WHEEL +5 -0
  79. pyphyschemtools-0.1.0.dist-info/licenses/LICENSE +674 -0
  80. pyphyschemtools-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,451 @@
1
+ ############################################################
2
+ # Absorption spectra
3
+ ############################################################
4
+ from .visualID_Eng import fg, bg, hl
5
+ from .core import centerTitle, centertxt
6
+
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ import scipy.constants as sc
10
+
11
+ class SpectrumSimulator:
12
+
13
+ def __init__(self, sigma_ev=0.3, plotWH=(12,8), \
14
+ fontSize_axisText=14, fontSize_axisLabels=14, fontSize_legends=12,
15
+ fontsize_peaks=12,
16
+ colorS='#3e89be',colorVT='#469cd6'
17
+ ):
18
+ """
19
+ Initializes the spectrum simulator
20
+
21
+ Args:
22
+ - sigma_ev (float): Gaussian half-width at half-maximum in electron-volts (eV).
23
+ Default is 0.3 eV (GaussView default is 0.4 eV).
24
+ - plotWH (tuple(int,int)): Width and Height of the matplotlib figures in inches. Default is (12,8).
25
+ - colorS: color of the simulated spectrum (default ='#3e89be')
26
+ - colorVT: color of the vertical transition line (default = '#469cd6')
27
+
28
+ Returns:
29
+ None: This method initializes the instance attributes.
30
+ Calculates:
31
+ - sigmanm = half-width of the Gaussian band, in nm
32
+ """
33
+ self.sigma_ev = sigma_ev
34
+ # Conversion constante eV -> nm sigma
35
+ self.ev2nm_const = (sc.h * sc.c) * 1e9 / sc.e
36
+ self.sigmanm = self.ev2nm_const / self.sigma_ev
37
+ self.plotW = plotWH[0]
38
+ self.plotH = plotWH[1]
39
+ self.colorS = colorS
40
+ self.colorVT = colorVT
41
+ self.fig = None
42
+ self.graph = None
43
+ self.fontSize_axisText = fontSize_axisText
44
+ self.fontSize_axisLabels = fontSize_axisLabels
45
+ self.fontSize_legends = fontSize_legends
46
+ self.fontsize_peaks = fontsize_peaks
47
+
48
+ print(f"sigma = {sigma_ev} eV -> sigmanm = {self.sigmanm:.1f} nm")
49
+
50
+ def _initializePlot(self):
51
+ fig, graph = plt.subplots(figsize=(self.plotW,self.plotH))
52
+ plt.subplots_adjust(wspace=0)
53
+ plt.xticks(fontsize=self.fontSize_axisText,fontweight='bold')
54
+ plt.yticks(fontsize=self.fontSize_axisText,fontweight='bold')
55
+ return fig, graph
56
+
57
+ def _calc_epsiG(self,lambdaX,lambdai,fi):
58
+ '''
59
+ calculates a Gaussian band shape around a vertical transition
60
+ input:
61
+ - lambdaX = wavelength variable, in nm
62
+ - lambdai = vertical excitation wavelength for i_th state, in nm
63
+ - fi = oscillator strength for state i (dimensionless)
64
+ output :
65
+ molar absorption coefficient, in L mol-1 cm-1
66
+ '''
67
+ import scipy.constants as sc
68
+ import numpy as np
69
+ c = sc.c*1e2 #cm-1
70
+ NA = sc.N_A #mol-1
71
+ me = sc.m_e*1000 #g
72
+ e = sc.e*sc.c*10 #elementary charge in esu
73
+ pf = np.sqrt(np.pi)*e**2*NA/(1000*np.log(10)*c**2*me)
74
+ nubarX = 1e7 / lambdaX # nm to cm-1
75
+ nubari = 1e7 / lambdai
76
+ sigmabar = 1e7 / self.sigmanm
77
+ epsi = pf * (fi / sigmabar) * np.exp(-((nubarX - nubari)/sigmabar)**2)
78
+ return epsi
79
+
80
+ def _Absorbance(self,eps,opl,cc):
81
+ '''
82
+ Calculates the Absorbance with the Beer-Lambert law
83
+ input:
84
+ - eps = molar absorption coefficient, in L mol-1 cm-1
85
+ - opl = optical path length, in cm
86
+ - cc = concentration of the attenuating species, in mol.L-1
87
+ output :
88
+ Absorbance, A (dimensionless)
89
+ '''
90
+ return eps*opl*cc
91
+
92
+ def _sumStatesWithGf(self,wavel,wavelTAB,feTAB):
93
+ '''
94
+ '''
95
+ import numpy as np
96
+ sumInt = np.zeros(len(wavel))
97
+ for l in wavel:
98
+ for i in range(len(wavelTAB)):
99
+ sumInt[np.argwhere(l==wavel)[0][0]] += self._calc_epsiG(l,wavelTAB[i],feTAB[i])
100
+ return sumInt
101
+
102
+ def _FindPeaks(self,sumInt,height,prom=1):
103
+ '''
104
+ Finds local maxima within the spectrum based on height and prominence.
105
+
106
+ Prominence is crucial when switching between linear and logarithmic scales:
107
+ - In Linear mode: A large prominence (e.g., 1 to 1000) filters out noise.
108
+ - In Log mode: Data is compressed into a range of ~0 to 5. A large
109
+ prominence will 'hide' real peaks. A smaller value (0.01 to 0.1)
110
+ is required to detect shoulders and overlapping bands.
111
+
112
+ Input:
113
+ - sumInt: Array of intensities (Epsilon or Absorbance).
114
+ - height: Minimum height a peak must reach to be considered.
115
+ - prom: Required vertical distance between the peak and its lowest contour line.
116
+
117
+ Returns:
118
+ - PeakIndex: Indices of the detected peaks in the wavelength array.
119
+ - PeakHeight: The intensity values at these peak positions.
120
+ '''
121
+ from scipy.signal import find_peaks
122
+ peaks = find_peaks(sumInt, height = height, threshold = None, distance = 1, prominence=prom)
123
+ PeakIndex = peaks[0]
124
+ # Check if 'peak_heights' exists in the properties dictionary
125
+ if 'peak_heights' in peaks[1]:
126
+ PeakHeight = peaks[1]['peak_heights']
127
+ else:
128
+ # If height=None, we extract values manually from the input data
129
+ PeakHeight = sumInt[PeakIndex]
130
+ return PeakIndex,PeakHeight
131
+
132
+ def _FindShoulders(self, data, tP):
133
+ '''
134
+ ###not working
135
+ Detects shoulders using the second derivative.
136
+ A shoulder appears as a peak in the negative second derivative.
137
+
138
+ Note on scales:
139
+ - If ylog is True: data should be log10(sumInt) and tP should be log10(tP).
140
+ The second derivative on log data is much more sensitive to subtle
141
+ inflection points in weak transitions (like n -> pi*).
142
+ - If ylog is False: data is linear sumInt and tP is linear.
143
+
144
+ Returns:
145
+ - shoulder_idx (ndarray): Array of indices where shoulders were found.
146
+ - shoulder_heights (ndarray): The intensity values at these positions
147
+ extracted from the input data.
148
+ '''
149
+ import numpy as np
150
+ # Calculate the second derivative (rate of change of the slope)
151
+ d2 = np.gradient(np.gradient(data))
152
+
153
+ # We search for peaks in the opposite of the second derivative (-d2).
154
+ # A local maximum in -d2 corresponds to a point of maximum curvature
155
+ # (inflection), which identifies a shoulder.
156
+ # We use a very low prominence threshold to capture subtle inflections.
157
+ shoulder_idx, _ = self._FindPeaks(-d2, height=None, prom=0.0001)
158
+ shoulder_heights = data[shoulder_idx]
159
+ print(shoulder_idx, shoulder_heights )
160
+
161
+ return shoulder_idx, shoulder_heights
162
+
163
+ def _pickPeak(self,wavel,peaksIndex,peaksH,color,\
164
+ shift=500,height=500,posAnnotation=200, ylog=False):
165
+ '''
166
+ Annotates peaks with a small vertical tick and the wavelength value.
167
+ Adjusts offsets based on whether the plot is in log10 scale or linear.
168
+ In log mode, peaksH must already be log10 values.
169
+ '''
170
+ s=shift
171
+ h=height
172
+ a=posAnnotation
173
+
174
+
175
+ for i in range(len(peaksIndex)):
176
+ x = wavel[peaksIndex[i]]
177
+ y = peaksH[i]
178
+ if ylog:
179
+ # In log scale, we use multipliers to keep the same visual distance
180
+ # 1.1 means "10% above the peak"
181
+ # Adjust these factors based on your preference
182
+ y_s = y * 1.1
183
+ y_h = y * 1.3
184
+ y_a = y * 1.5
185
+ self.graph.vlines(x, y_s, y_h, colors=color, linestyles='solid')
186
+ self.graph.annotate(f"{x:.1f}",xy=(x,y),xytext=(x,y_a),rotation=90,size=self.fontsize_peaks,ha='center',va='bottom', color=color)
187
+ else:
188
+ # Classic linear offsets
189
+ self.graph.vlines(x, y+s, y+s+h, colors=color, linestyles='solid')
190
+ self.graph.annotate(f"{x:.1f}",xy=(x,y),xytext=(x,y+s+h+a),rotation=90,size=self.fontsize_peaks,ha='center',va='bottom',color=color)
191
+ return
192
+
193
+ def _setup_axes(self, lambdamin, lambdamax, ymax, ylabel="Absorbance"):
194
+ self.graph.set_xlabel('wavelength / nm', size=self.fontSize_axisLabels, fontweight='bold', color='#2f6b91')
195
+ self.graph.set_ylabel(ylabel, size=self.fontSize_axisLabels, fontweight='bold', color='#2f6b91')
196
+ self.graph.set_xlim(lambdamin, lambdamax)
197
+ self.graph.set_ylim(0, ymax)
198
+ self.graph.tick_params(axis='both', labelsize=self.fontSize_axisText,labelcolor='black')
199
+ for tick in self.graph.xaxis.get_majorticklabels(): tick.set_fontweight('bold') #it is both powerful
200
+ # (you can specify the type of a specific tick) and annoying
201
+ for tick in self.graph.yaxis.get_majorticklabels(): tick.set_fontweight('bold')
202
+
203
+ def plotTDDFTSpectrum(self,wavel,sumInt,wavelTAB,feTAB,tP,ylog,labelSpectrum,colorS='#0000ff',colorT='#0000cf'):
204
+
205
+ '''
206
+ Called by plotEps_lambda_TDDFT. Plots a single simulated UV-Vis spectrum, i.e. after
207
+ gaussian broadening, together with the TDDFT vertical transitions (i.e. plotted as lines)
208
+
209
+ input:
210
+ - wavel = array of gaussian-broadened wavelengths, in nm
211
+ - sumInt = corresponding molar absorptiopn coefficients, in L. mol-1 cm-1
212
+ - wavelTAB = wavelength of TDDFT, e.g. discretized, transitions
213
+ - ylog = log plot of epsilon
214
+ - tP: threshold for finding the peaks
215
+ - feTAB = TDDFT oscillator strength for each transition of wavelTAB
216
+ - labelSpectrum = title for the spectrum
217
+ '''
218
+
219
+ # # --- DEBUG START ---
220
+ # if ylog:
221
+ # print(f"\n--- DEBUG LOG MODE ---")
222
+ # print(f"Max sumInt (linear): {np.max(sumInt):.2f}")
223
+ # print(f"Max sumInt (log10): {np.log10(max(np.max(sumInt), 1e-5)):.2f}")
224
+ # # --- DEBUG END ---
225
+ if ylog:
226
+ # Apply safety floor to the entire array
227
+ self.graph.set_yscale('log')
228
+ ymin_val = 1.0 # Epsilon = 1
229
+ else:
230
+ self.graph.set_yscale('linear')
231
+ ymin_val = 0
232
+
233
+ # vertical lines
234
+ for i in range(len(wavelTAB)):
235
+ val_eps = self._calc_epsiG(wavelTAB[i],wavelTAB[i],feTAB[i])
236
+ self.graph.vlines(x=wavelTAB[i], ymin=ymin_val, ymax=max(val_eps, ymin_val), colors=colorT)
237
+
238
+ self.graph.plot(wavel,sumInt,linewidth=3,linestyle='-',color=colorS,label=labelSpectrum)
239
+
240
+ self.graph.legend(fontsize=self.fontSize_legends)
241
+ if ylog:
242
+ # Use log-transformed data and log-transformed threshold
243
+ # Clipping tP to 1e-5 ensures we don't take log of 0 or negative
244
+ tPlog = np.log10(max(tP, 1e-5))
245
+ # prom=0.05 allows detection of peaks that are close in log-magnitude
246
+ peaks, peaksH_log = self._FindPeaks(np.log10(np.clip(sumInt, 1e-5, None)), tPlog, prom=0.05)
247
+ peaksH = 10**peaksH_log
248
+ # shoulders, shouldersH_log = self._FindShoulders(np.log10(np.clip(sumInt, 1e-5, None)), tPlog)
249
+ # all_idx = np.concatenate((peaks, shoulders))
250
+ # allH_log = np.concatenate((peaksH_log, shouldersH_log))
251
+ # allH = 10**allH_log
252
+ else:
253
+ peaks, peaksH = self._FindPeaks(sumInt,tP)
254
+ # shoulders, shouldersH = self._FindShoulders(wavel, sumInt, tP)
255
+ # all_idx = np.concatenate((peaks, shoulders))
256
+ # allH = np.concatenate((peaksH, shouldersH))
257
+ self._pickPeak(wavel,peaks,peaksH,colorS,500,500,200,ylog)
258
+
259
+
260
+ def plotEps_lambda_TDDFT(self,datFile,lambdamin=200,lambdamax=800,\
261
+ epsMax=None, titles=None, tP = 10, \
262
+ ylog=False,\
263
+ filename=None):
264
+ '''
265
+ Plots a TDDFT VUV simulated spectrum (vertical transitions and transitions summed with gaussian functions)
266
+ between lambdamin and lambdamax (sum of states done in the range [lambdamin-50, lambdamlax+50] nm)
267
+ input:
268
+ - datFile: list of pathway/names to "XXX_ExcStab.dat" files generated by 'GParser Gaussian.log -S'
269
+ - lambdamin, lambdamax: plot range
270
+ - epsMax: y axis graph limit
271
+ - titles: list of titles (1 per spectrum plot)
272
+ - tP: threshold for finding the peaks (default = 10 L. mol-1 cm-1)
273
+ - ylog: y logarithmic axis (default: False).
274
+ - save: saves in a png file (300 dpi) if True (default = False)
275
+ - filename: saves figure in a 300 dpi png file if not None (default), with filename=full pathway
276
+ '''
277
+ if self.fig is not None:
278
+ graph = self.graph
279
+ fig = self.fig
280
+ lambdamin = self.lambdamin
281
+ lambdamax = self.lambdamax
282
+ epsMax = self.epsMax
283
+ else:
284
+ fig, graph = self._initializePlot()
285
+
286
+ graph.set_prop_cycle(None)
287
+
288
+ if self.fig is None:
289
+ self.fig = fig
290
+ self.graph = graph
291
+ self.lambdamin = lambdamin
292
+ self.lambdamax = lambdamax
293
+ self.epsMax = epsMax
294
+
295
+ graph.set_xlabel('wavelength / nm',size=self.fontSize_axisLabels,fontweight='bold',color='#2f6b91')
296
+
297
+ graph.set_xlim(lambdamin,lambdamax)
298
+
299
+ import matplotlib.ticker as ticker
300
+ graph.xaxis.set_major_locator(ticker.MultipleLocator(50)) # sets a tick for every integer multiple of the base (here 250) within the view interval
301
+
302
+ istate,state,wavel,fe,SSq = np.genfromtxt(datFile,skip_header=1,dtype="<U20,<U20,float,float,<U20",unpack=True)
303
+ wavel = np.array(wavel)
304
+ fe = np.array(fe)
305
+ if wavel.size == 1:
306
+ wavel = np.array([wavel])
307
+ fe = np.array([fe])
308
+ wvl = np.arange(lambdamin-50,lambdamax+50,1)
309
+ sumInt = self._sumStatesWithGf(wvl,wavel,fe)
310
+ self.plotTDDFTSpectrum(wvl,sumInt,wavel,fe,tP,ylog,titles,self.colorS,self.colorVT)
311
+ if ylog:
312
+ graph.set_ylabel('log(molar absorption coefficient / L mol$^{-1}$ cm$^{-1})$',size=self.fontSize_axisLabels,fontweight='bold',color='#2f6b91')
313
+ graph.set_ylim(1, epsMax * 5 if epsMax else None)
314
+ else:
315
+ graph.set_yscale('linear')
316
+ graph.set_ylabel('molar absorption coefficient / L mol$^{-1}$ cm$^{-1}$',size=self.fontSize_axisLabels,fontweight='bold',color='#2f6b91')
317
+ graph.set_ylim(0, epsMax if epsMax else np.max(sumInt)*1.18)
318
+ if filename is not None: self.fig.savefig(filename, dpi=300, bbox_inches='tight')
319
+ plt.show()
320
+
321
+ peaksI, peaksH = self._FindPeaks(sumInt,tP)
322
+ print(f"{bg.LIGHTREDB}{titles}{bg.OFF}")
323
+ for i in range(len(peaksI)):
324
+ print(f"peak {i:3}. {wvl[peaksI[i]]:4} nm. epsilon_max = {peaksH[i]:.1f} L mol-1 cm-1")
325
+ if ylog:
326
+ print()
327
+ # prom=0.05 allows detection of peaks that are close in log-magnitude
328
+ peaksI, peaksH = self._FindPeaks(np.log10(np.clip(sumInt, 1e-5, None)), np.log10(max(tP, 1e-5)), prom=0.05)
329
+ for i in range(len(peaksI)):
330
+ print(f"peak {i:3}. {wvl[peaksI[i]]:4} nm. log10(epsilon_max) = {peaksH[i]:.1f}")
331
+
332
+ def plotAbs_lambda_TDDFT(self, datFiles=None, C0=1e-5, lambdamin=200, lambdamax=800, Amax=2.0,\
333
+ titles=None, linestyles=[], annotateP=[], tP = 0.1,\
334
+ resetColors=False,\
335
+ filename=None):
336
+ '''
337
+ Plots a simulated TDDFT VUV absorbance spectrum (transitions summed with gaussian functions)
338
+ between lambdamin and lambdamax (sum of states done in the range [lambdamin-50, lambdamlax+50] nm)
339
+ input:
340
+ - datFiles: list of pathway/name to files generated by 'GParser Gaussian.log -S'
341
+ - C0: list of concentrations needed to calculate A = epsilon x l x c (in mol.L-1)
342
+ - lambdamin, lambdamax: plot range (x axis)
343
+ - Amax: y axis graph limit
344
+ - titles: list of titles (1 per spectrum plot)
345
+ - linestyles: list of line styles(default = "-", i.e. a continuous line)
346
+ - annotateP: list of Boolean (annotate lambda max True or False. Default = True)
347
+ - tP: threshold for finding the peaks (default = 0.1)
348
+ - resetColors (bool): If True, resets the matplotlib color cycle
349
+ to the first color. This allows different series
350
+ (e.g., gas phase vs. solvent) to share the same
351
+ color coding for each molecule across multiple calls. Default: False
352
+ - save: saves in a png file (300 dpi) if True (default = False)
353
+ - filename: saves figure in a 300 dpi png file if not None (default), with filename=full pathway
354
+ '''
355
+
356
+ if self.fig is None:
357
+ fig, graph = self._initializePlot()
358
+ self.fig = fig
359
+ self.graph = graph
360
+ self.lambdamin = lambdamin
361
+ self.lambdamax = lambdamax
362
+ self.Amax = Amax
363
+ else:
364
+ graph = self.graph
365
+ fig = self.fig
366
+ lambdamin = self.lambdamin
367
+ lambdamax = self.lambdamax
368
+ Amax = self.Amax
369
+ if resetColors: graph.set_prop_cycle(None)
370
+
371
+ if linestyles == []: linestyles = len(datFiles)*['-']
372
+ if annotateP == []: annotateP = len(datFiles)*[True]
373
+
374
+ self._setup_axes(lambdamin, lambdamax, self.Amax, ylabel="Absorbance")
375
+
376
+ wvl = np.arange(lambdamin-50,lambdamax+50,1)
377
+ for f in range(len(datFiles)):
378
+ istate,state,wavel,fe,SSq = np.genfromtxt(datFiles[f],skip_header=1,dtype="<U20,<U20,float,float,<U20",unpack=True)
379
+ sumInt = self._sumStatesWithGf(wvl,wavel,fe)
380
+ Abs = self._Absorbance(sumInt,1,C0[f])
381
+ plot=self.graph.plot(wvl,Abs,linewidth=3,linestyle=linestyles[f],label=f"{titles[f]}. TDDFT ($C_0$={C0[f]} mol/L)")
382
+ peaksI, peaksH = self._FindPeaks(Abs,tP,0.01)
383
+ if (annotateP[f]): self._pickPeak(wvl,peaksI,peaksH,plot[0].get_color(),0.01,0.04,0.02)
384
+ print(f"{bg.LIGHTREDB}TDDFT. {titles[f]}{bg.OFF}")
385
+ for i in range(len(peaksI)):
386
+ print(f"peak {i:3}. {wvl[peaksI[i]]:4} nm. A = {peaksH[i]:.2f}")
387
+
388
+ self.graph.legend(fontsize=self.fontSize_legends)
389
+
390
+ if filename is not None: self.fig.savefig(filename, dpi=300, bbox_inches='tight')
391
+
392
+ return
393
+
394
+ def plotAbs_lambda_exp(self, csvFiles, C0, lambdamin=200, lambdamax=800,\
395
+ Amax=2.0, titles=None, linestyles=[], annotateP=[], tP = 0.1,\
396
+ filename=None):
397
+ '''
398
+ Plots an experimental VUV absorbance spectrum read from a csv file between lambdamin and lambdamax
399
+ input:
400
+ - superpose: False = plots a new graph, otherwise the plot is superposed to a previously created one
401
+ (probably with plotAbs_lambda_TDDFT())
402
+ - csvfiles: list of pathway/name to experimental csvFiles (see examples for the format)
403
+ - C0: list of experimental concentrations, i.e. for each sample
404
+ - lambdamin, lambdamax: plot range (x axis)
405
+ - Amax: graph limit (y axis)
406
+ - titles: list of titles (1 per spectrum plot)
407
+ - linestyles: list of line styles(default = "--", i.e. a dashed line)
408
+ - annotateP: list of Boolean (annotate lambda max True or False. Default = True)
409
+ - tP: threshold for finding the peaks (default = 0.1)
410
+ - save: saves in a png file (300 dpi) if True (default = False)
411
+ - filename: saves figure in a 300 dpi png file if not None (default), with filename=full pathway
412
+ '''
413
+ if linestyles == []: linestyles = len(csvFiles)*['--']
414
+ if annotateP == []: annotateP = len(csvFiles)*[True]
415
+
416
+ if self.fig is not None:
417
+ graph = self.graph
418
+ fig = self.fig
419
+ lambdamin = self.lambdamin
420
+ lambdamax = self.lambdamax
421
+ Amax = self.Amax
422
+ else:
423
+ fig, graph = self._initializePlot()
424
+
425
+ graph.set_prop_cycle(None)
426
+
427
+ if self.fig is None:
428
+ self.graph = graph
429
+ self.fig = fig
430
+ self.lambdamin = lambdamin
431
+ self.lambdamax = lambdamax
432
+ self.Amax = Amax
433
+
434
+ self._setup_axes(lambdamin, lambdamax, self.Amax, ylabel="Absorbance")
435
+
436
+ for f in range(len(csvFiles)):
437
+ wavel,Abs = np.genfromtxt(csvFiles[f],skip_header=1,unpack=True,delimiter=";")
438
+ wavel *= 1e9
439
+ plot=graph.plot(wavel,Abs,linewidth=3,linestyle=linestyles[f],label=f"{titles[f]}. exp ($C_0$={C0[f]} mol/L)")
440
+ peaksI, peaksH = self._FindPeaks(Abs,tP,0.01)
441
+ if (annotateP[f]): self._pickPeak(wavel,peaksI,peaksH,plot[0].get_color(),0.01,0.04,0.02)
442
+ print(f"{bg.LIGHTREDB}exp. {titles[f]}{bg.OFF}")
443
+ for i in range(len(peaksI)):
444
+ print(f"peak {i:3}. {wavel[peaksI[i]]:4} nm. A = {peaksH[i]:.2f}")
445
+
446
+ graph.legend(fontsize=self.fontSize_legends)
447
+
448
+ if filename is not None: self.fig.savefig(filename, dpi=300, bbox_inches='tight')
449
+
450
+ return
451
+