digichem-core 6.0.0rc1__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 (111) hide show
  1. digichem/__init__.py +75 -0
  2. digichem/basis.py +116 -0
  3. digichem/config/README +3 -0
  4. digichem/config/__init__.py +5 -0
  5. digichem/config/base.py +321 -0
  6. digichem/config/locations.py +14 -0
  7. digichem/config/parse.py +90 -0
  8. digichem/config/util.py +117 -0
  9. digichem/data/README +4 -0
  10. digichem/data/batoms/COPYING +18 -0
  11. digichem/data/batoms/LICENSE +674 -0
  12. digichem/data/batoms/README +2 -0
  13. digichem/data/batoms/__init__.py +0 -0
  14. digichem/data/batoms/batoms-renderer.py +351 -0
  15. digichem/data/config/digichem.yaml +714 -0
  16. digichem/data/functionals.csv +15 -0
  17. digichem/data/solvents.csv +185 -0
  18. digichem/data/tachyon/COPYING.md +5 -0
  19. digichem/data/tachyon/LICENSE +30 -0
  20. digichem/data/tachyon/tachyon_LINUXAMD64 +0 -0
  21. digichem/data/vmd/common.tcl +468 -0
  22. digichem/data/vmd/generate_combined_orbital_images.tcl +70 -0
  23. digichem/data/vmd/generate_density_images.tcl +45 -0
  24. digichem/data/vmd/generate_dipole_images.tcl +68 -0
  25. digichem/data/vmd/generate_orbital_images.tcl +57 -0
  26. digichem/data/vmd/generate_spin_images.tcl +66 -0
  27. digichem/data/vmd/generate_structure_images.tcl +40 -0
  28. digichem/datas.py +14 -0
  29. digichem/exception/__init__.py +7 -0
  30. digichem/exception/base.py +133 -0
  31. digichem/exception/uncatchable.py +63 -0
  32. digichem/file/__init__.py +1 -0
  33. digichem/file/base.py +364 -0
  34. digichem/file/cube.py +284 -0
  35. digichem/file/fchk.py +94 -0
  36. digichem/file/prattle.py +277 -0
  37. digichem/file/types.py +97 -0
  38. digichem/image/__init__.py +6 -0
  39. digichem/image/base.py +113 -0
  40. digichem/image/excited_states.py +335 -0
  41. digichem/image/graph.py +293 -0
  42. digichem/image/orbitals.py +239 -0
  43. digichem/image/render.py +617 -0
  44. digichem/image/spectroscopy.py +797 -0
  45. digichem/image/structure.py +115 -0
  46. digichem/image/vmd.py +826 -0
  47. digichem/input/__init__.py +3 -0
  48. digichem/input/base.py +78 -0
  49. digichem/input/digichem_input.py +500 -0
  50. digichem/input/gaussian.py +140 -0
  51. digichem/log.py +179 -0
  52. digichem/memory.py +166 -0
  53. digichem/misc/__init__.py +4 -0
  54. digichem/misc/argparse.py +44 -0
  55. digichem/misc/base.py +61 -0
  56. digichem/misc/io.py +239 -0
  57. digichem/misc/layered_dict.py +285 -0
  58. digichem/misc/text.py +139 -0
  59. digichem/misc/time.py +73 -0
  60. digichem/parse/__init__.py +13 -0
  61. digichem/parse/base.py +220 -0
  62. digichem/parse/cclib.py +138 -0
  63. digichem/parse/dump.py +253 -0
  64. digichem/parse/gaussian.py +130 -0
  65. digichem/parse/orca.py +96 -0
  66. digichem/parse/turbomole.py +201 -0
  67. digichem/parse/util.py +523 -0
  68. digichem/result/__init__.py +6 -0
  69. digichem/result/alignment/AA.py +114 -0
  70. digichem/result/alignment/AAA.py +61 -0
  71. digichem/result/alignment/FAP.py +148 -0
  72. digichem/result/alignment/__init__.py +3 -0
  73. digichem/result/alignment/base.py +310 -0
  74. digichem/result/angle.py +153 -0
  75. digichem/result/atom.py +742 -0
  76. digichem/result/base.py +258 -0
  77. digichem/result/dipole_moment.py +332 -0
  78. digichem/result/emission.py +402 -0
  79. digichem/result/energy.py +323 -0
  80. digichem/result/excited_state.py +821 -0
  81. digichem/result/ground_state.py +94 -0
  82. digichem/result/metadata.py +644 -0
  83. digichem/result/multi.py +98 -0
  84. digichem/result/nmr.py +1086 -0
  85. digichem/result/orbital.py +647 -0
  86. digichem/result/result.py +244 -0
  87. digichem/result/soc.py +272 -0
  88. digichem/result/spectroscopy.py +514 -0
  89. digichem/result/tdm.py +267 -0
  90. digichem/result/vibration.py +167 -0
  91. digichem/test/__init__.py +6 -0
  92. digichem/test/conftest.py +4 -0
  93. digichem/test/test_basis.py +71 -0
  94. digichem/test/test_calculate.py +30 -0
  95. digichem/test/test_config.py +78 -0
  96. digichem/test/test_cube.py +369 -0
  97. digichem/test/test_exception.py +16 -0
  98. digichem/test/test_file.py +104 -0
  99. digichem/test/test_image.py +337 -0
  100. digichem/test/test_input.py +64 -0
  101. digichem/test/test_parsing.py +79 -0
  102. digichem/test/test_prattle.py +36 -0
  103. digichem/test/test_result.py +489 -0
  104. digichem/test/test_translate.py +112 -0
  105. digichem/test/util.py +207 -0
  106. digichem/translate.py +591 -0
  107. digichem_core-6.0.0rc1.dist-info/METADATA +96 -0
  108. digichem_core-6.0.0rc1.dist-info/RECORD +111 -0
  109. digichem_core-6.0.0rc1.dist-info/WHEEL +4 -0
  110. digichem_core-6.0.0rc1.dist-info/licenses/COPYING.md +10 -0
  111. digichem_core-6.0.0rc1.dist-info/licenses/LICENSE +11 -0
@@ -0,0 +1,402 @@
1
+ import math
2
+ from scipy.constants import epsilon_0, Planck, c
3
+
4
+ from digichem.exception.base import Digichem_exception, Result_unavailable_error
5
+ from digichem.result.excited_state import Excited_state
6
+ from digichem.result import Result_object
7
+
8
+ # Dirac constant.
9
+ h_bar = Planck / (math.pi *2)
10
+
11
+
12
+ class Emissions(Result_object):
13
+ """
14
+ Class that holds all emissions from a result.
15
+ """
16
+
17
+ def __init__(self, adiabatic = None, vertical = None):
18
+ """
19
+ """
20
+ # For now, we have no way of knowing which state is being optimised.
21
+ # As such, we assume it's the lowest of each mult (because this is most
22
+ # common thanks to kasha's rule). This being the case, each emission is
23
+ # stored in a dict, which each key is the multiplicity. Each value is not
24
+ # a list (because we only have one emission for each mult), but the
25
+ # emission object itself.
26
+ # TODO: Improve once we can detect which state is being optimised.
27
+ self.adiabatic = adiabatic if adiabatic is not None else {}
28
+ self.vertical = vertical if vertical is not None else {}
29
+
30
+ def dump(self, digichem_options):
31
+ # Several dumpers (JSON, DB backends based on JSON etc) don't support non-string keys...
32
+ return {
33
+ "adiabatic": {str(key):value.dump(digichem_options) for key,value in self.adiabatic.items()},
34
+ "vertical": {str(key):value.dump(digichem_options) for key,value in self.vertical.items()}
35
+ }
36
+
37
+ @classmethod
38
+ def from_dump(self, data, result_set, options):
39
+ """
40
+ Get a list of instances of this class from its dumped representation.
41
+
42
+ :param data: The data to parse.
43
+ :param result_set: The partially constructed result set which is being populated.
44
+ """
45
+ emissions = self()
46
+
47
+ for transition_type in ("adiabatic", "vertical"):
48
+ if transition_type not in data:
49
+ continue
50
+
51
+ setattr(emissions, transition_type,
52
+ {mult: Relaxed_excited_state.from_dump(state_data, result_set, options, transition_type = transition_type) for mult, state_data in data[transition_type].items()}
53
+ )
54
+
55
+ return emissions
56
+
57
+
58
+ class Relaxed_excited_state(Excited_state):
59
+ """
60
+ Class for representing an emission energy from an excited state to a ground state.
61
+
62
+ Note that while emission and absorption can be approximated as the reverse of each other, emission and absorption strictly have different energies.
63
+ Excited states, as represented by the Excited_state class, are for absorption energies.
64
+ This class is for emission energies, which are typically lower in energy (because the excited state has relaxed).
65
+ """
66
+
67
+ def __init__(self,
68
+ level,
69
+ multiplicity,
70
+ multiplicity_level,
71
+ ground_multiplicity,
72
+ excited_energy,
73
+ ground_energy,
74
+ oscillator_strength,
75
+ transition_type,
76
+ # TODO: Add Support.
77
+ transitions = None,
78
+ # TODO: Add Support.
79
+ symmetry = None,
80
+ transition_dipole_moment = None,
81
+ ):
82
+ """
83
+ """
84
+ self.ground_energy = ground_energy
85
+ self.excited_energy = excited_energy
86
+ self.ground_multiplicity = ground_multiplicity
87
+ # The emission type (as a string), either fluorescence or phosphorescence.
88
+ if ground_multiplicity == multiplicity:
89
+ self.emission_type = "fluorescence"
90
+
91
+ else:
92
+ self.emission_type = "phosphorescence"
93
+
94
+ # Either adiabatic or vertical.
95
+ self.transition_type = transition_type
96
+
97
+ super().__init__(level, multiplicity, multiplicity_level, symmetry, excited_energy - ground_energy, oscillator_strength, transitions if transitions is not None else [], transition_dipole_moment)
98
+
99
+
100
+ @classmethod
101
+ def from_results(self,
102
+ ground_state_result,
103
+ excited_state_result,
104
+ transition_type,
105
+ excited_state = None,
106
+ # For now we assume this is the lowest possible excited state (may change in future).
107
+ level = 1,
108
+ multiplicity_level = 1,
109
+ ):
110
+ """
111
+ Constructor for Relaxed_excited_state objects.
112
+
113
+ :param ground_state_result: A Result_set object representing the ground state.
114
+ :param excited_state_result: A Result_set object representing the excited state.
115
+ :param transition_type: A string describing the type of transition, either 'adiabatic' (GS and ES relaxed) or 'vertical' (ES relaxed, GS @ ES geom).
116
+ :param excited_state: An optional Excited_state object. This is required (for example) in time dependent DFT where the total energy of excited_state_result is the ground state energy (at the excited state geometry).
117
+ :param level: The level (ordered index) of this excited state, this has no effect if excited_state is not None (in which case it is taken from the given excited state).
118
+ :param multiplicity_level: The ordered index of this excited state out of states with the same multiplicity, this has no effect if excited_state is not None (in which case it is taken from the given excited state).
119
+ """
120
+ if excited_state is not None:
121
+ # If we have an excited state we can inherit certain properties from it.
122
+ level = excited_state.level
123
+ multiplicity_level = excited_state.multiplicity_level
124
+ oscillator_strength = excited_state.oscillator_strength
125
+ transition_dipole_moment = excited_state.transition_dipole_moment
126
+ else:
127
+ # We don't have a concept of oscillator strength (yet?).
128
+ oscillator_strength = None
129
+ transition_dipole_moment = None
130
+
131
+ # The total energy of the excited state in this transition.
132
+ # Start with our excited_state_result energy.
133
+ excited_energy = excited_state_result.energies.final
134
+
135
+ # Add the excited state energy (if we have it).
136
+ if excited_state is not None:
137
+ excited_energy += excited_state.energy
138
+
139
+ # The total energy of the ground state in this transition.
140
+ ground_energy = ground_state_result.energies.final
141
+
142
+ # The multiplicity (as a number) of the excited state in this emission transition.
143
+ if excited_state is not None:
144
+ excited_multiplicity = excited_state.multiplicity
145
+ else:
146
+ excited_multiplicity = excited_state_result.ground_state.multiplicity
147
+
148
+ # The multiplicity (as a number) of the ground state in this emission transition.
149
+ ground_multiplicity = ground_state_result.ground_state.multiplicity
150
+
151
+ return self(
152
+ level = level,
153
+ multiplicity = excited_multiplicity,
154
+ multiplicity_level = multiplicity_level,
155
+ ground_multiplicity = ground_multiplicity,
156
+ # TODO: Add Support.
157
+ symmetry = None,
158
+ excited_energy = excited_energy,
159
+ ground_energy = ground_energy,
160
+ oscillator_strength = oscillator_strength,
161
+ # TODO: Add Support.
162
+ transitions = None,
163
+ transition_dipole_moment = transition_dipole_moment,
164
+ transition_type = transition_type,
165
+ )
166
+
167
+ @property
168
+ def excited_multiplicity(self):
169
+ """
170
+ This is an alias of multiplicity
171
+ """
172
+ return self.multiplicity
173
+
174
+ @classmethod
175
+ def guess_from_results(self, *results):
176
+ """
177
+ Try and find emission energies from a number of calculation results.
178
+
179
+ Attempts will be made to calculate both adiabatic and vertical emission energies based on the properties of the excited states in results.
180
+
181
+ :param results: A number of result sets.
182
+ :returns: A tuple of dictionaries of the form (vertical, adiabatic), where the key of each dict is the multiplicity of the corresponding emission.
183
+ """
184
+ # The two types of emission we consider.
185
+ # Each is a dictionary where the keys are multiplicities and the items are the corresponding emission.
186
+ adiabatic = {}
187
+ vertical = {}
188
+
189
+ # We're only interested in optimisations (by definition emission needs to be from relaxed geom).
190
+ opt_results = [result for result in results if "Optimisation" in result.metadata.calculations]
191
+
192
+ for excited_state_result in reversed(opt_results):
193
+ # See if we can use this calc type as an excited state.
194
+ # The easiest situation is if we have excited states in the calc (and it is an opt).
195
+ if len(excited_state_result.excited_states) > 0:
196
+ # This will do fine for vertical.
197
+ vertical.update(self.for_each_multiplicity(excited_state_result, excited_state_result, "vertical"))
198
+
199
+ # For adiabatic, we need the earliest suitable optimisation from our list that doesn't have excited states.
200
+ try:
201
+ ground_state_result = [result for result in opt_results if len(result.excited_states) == 0][0]
202
+ adiabatic.update(self.for_each_multiplicity(ground_state_result, excited_state_result, "adiabatic"))
203
+ except IndexError:
204
+ # No suitable calcs.
205
+ pass
206
+
207
+ else:
208
+ # We have no explicit excited states, but so long as we are an opt we can still find an emission if we have calcs of different mult.
209
+ # First look for adiabatic.
210
+ try:
211
+ # Try and find a suitable ground.
212
+ ground_state_result = [result for result in opt_results if result.ground_state.multiplicity != excited_state_result.ground_state.multiplicity][0]
213
+
214
+ # Get 'emission' object.
215
+ emission = self.from_results(ground_state_result, excited_state_result, "adiabatic")
216
+
217
+ # If the energy is negative we will ignore.
218
+ if emission.energy >= 0:
219
+ adiabatic[excited_state_result.ground_state.multiplicity] = emission
220
+ except IndexError:
221
+ # No good.
222
+ pass
223
+
224
+ # And now vertical (we need a single point at different geom.
225
+ try:
226
+ # Try and find a suitable ground.
227
+ ground_state_result = [result for result in results if "Single Point" in result.metadata.calculations and result.ground_state.multiplicity != excited_state_result.ground_state.multiplicity][0]
228
+
229
+ # Get 'emission' object.
230
+ emission = self.from_results(ground_state_result, excited_state_result, "vertical")
231
+
232
+ # If the energy is negative we will ignore.
233
+ if emission.energy >= 0:
234
+ vertical[excited_state_result.ground_state.multiplicity] = emission
235
+ except IndexError:
236
+ # No good.
237
+ pass
238
+
239
+ return (vertical, adiabatic)
240
+
241
+
242
+ @classmethod
243
+ def for_each_multiplicity(self, ground_state_result, excited_state_result, transition_type):
244
+ """
245
+ Return one emission object for each multiplicity in an excited state result.
246
+
247
+ This method is useful because frequently only the lowest excited state of each mult is considered for emission, so there is one emission type per multiplicity.
248
+ """
249
+ return {multiplicity: self.from_results(ground_state_result, excited_state_result, transition_type, states[0]) for multiplicity, states in excited_state_result.excited_states.group().items()}
250
+
251
+ # TODO: This might not be used anywhere?
252
+ @classmethod
253
+ def determine_from_results(self, main_result, *, ground_state_result = None, excited_state_result, transition_type = None, excited_state = None):
254
+ """
255
+ Try and determine some emission energies from a number of result sets.
256
+
257
+ :param main_result: A Result_set object containing main calculation results.
258
+ :param ground_state_result: A Result_set object representing the ground state.
259
+ :param excited_state_result: A Result_set object representing the excited state.
260
+ :param transition_type: A string describing the type of transition, either 'adiabatic' (GS and ES relaxed) or 'vertical' (ES relaxed, GS @ ES geom).
261
+ :param excited_state: An optional Excited_state object. This is required (for example) in time dependent DFT where the total energy of excited_state_result is the ground state energy (at the excited state geometry). If excited_state is not an Excited_state object (and isn't None), it is passed as criteria to Excited_state_list.get_state().
262
+ """
263
+ # Decide on what to use for our ground state if not given explicitly.
264
+ if ground_state_result is None:
265
+ # If a transition type wasn't given, we can try and guess.
266
+ if transition_type is None:
267
+ # We don't know what type of emission we've been asked for.
268
+ # If the main_result is an opt, we'll assume we're adiabatic and use that as the ground state.
269
+ # Otherwise, we'll assume vertical and use the excited state.
270
+ if "Optimisation" in main_result.metadata.calculations:
271
+ transition_type = "adiabatic"
272
+ else:
273
+ transition_type = "vertical"
274
+
275
+ # Distinguish between the two types of excited states
276
+ if len(excited_state_result.excited_states) > 0:
277
+ # Emission from TD style excited states.
278
+
279
+ if transition_type == "vertical":
280
+ # For vertical emission, the excited state result is also the ground state result.
281
+ ground_state_result = excited_state_result
282
+
283
+ elif transition_type == "adiabatic":
284
+ # For adiabatic we need an optimised ground state geometry.
285
+ if "Optimisation" in main_result.metadata.calculations:
286
+ ground_state_result = main_result
287
+
288
+ else:
289
+ # No explicit ground given and out main result is not an Opt, so it probably isn't suitable.
290
+ raise Digichem_exception("Unable to determine ground state in adiabatic emission; no explicit ground state given and this calculation is not an optimisation")
291
+
292
+ else:
293
+ # Emission from unrestricted triplet calc.
294
+ # This method requires another calc type to compare to.
295
+ if transition_type == "vertical":
296
+ # For vertical, we want a singlet calc at the same geometry as the triplet calc.
297
+ if 'Single Point' in main_result.metadata.calculations:
298
+ ground_state_result = main_result
299
+ else:
300
+ raise Digichem_exception("Unable to determine ground state in vertical emission; no explicit ground state given and this calculation is not a single point")
301
+
302
+ elif transition_type == "adiabatic":
303
+ # For adiabatic, we need an opt.
304
+ if "Optimisation" in main_result.metadata.calculations:
305
+ ground_state_result = main_result
306
+ else:
307
+ # No explicit ground given and out main result is not an Opt, so it probably isn't suitable.
308
+ raise Digichem_exception("Unable to determine ground state in adiabatic emission; no explicit ground state given and this calculation is not an optimisation")
309
+
310
+ # Now decide which excited state to use.
311
+ excited_state = excited_state if excited_state is None or isinstance(excited_state, Excited_state) else excited_state_result.excited_states.get_state(excited_state)
312
+
313
+ # No excited state given and generating emission from TD style excited states, get all possible.
314
+ if len(excited_state_result.excited_states) > 0 and excited_state is None:
315
+ return self.for_each_multiplicity(ground_state_result, excited_state_result, transition_type)
316
+
317
+ else:
318
+ # Get emission.
319
+ emission = self.from_results(
320
+ ground_state_result,
321
+ excited_state_result,
322
+ transition_type,
323
+ excited_state)
324
+
325
+ # Return as single dict.
326
+ return {emission.multiplicity: emission}
327
+
328
+
329
+
330
+ @property
331
+ def emission_rate(self):
332
+ """
333
+ The rate of emission (k(F) or k(Phos) etc) from this excited state.
334
+
335
+ Calculated according to Shizu, K., Kaji, H. Commun Chem 5, 53 (2022).
336
+ """
337
+ try:
338
+ return ((4 * self.joules **3) / (3 * epsilon_0 * h_bar **4 * c **3) ) * self.transition_dipole_moment.coulomb_meters **2
339
+ except Exception:
340
+ if self.transition_dipole_moment is None:
341
+ raise Result_unavailable_error("emission_rate", "there is no transition dipole moment associated with this emission") from None
342
+
343
+ else:
344
+ raise
345
+
346
+ def dump(self, digichem_options):
347
+ """
348
+ Get a representation of this result object in primitive format.
349
+ """
350
+
351
+ dump_dict = super().dump(digichem_options)
352
+ dump_dict['emission_type'] = self.emission_type
353
+ dump_dict['ground_multiplicity'] = self.ground_multiplicity
354
+ dump_dict['excited_energy'] = {
355
+ "value": float(self.excited_energy),
356
+ "units": "eV"
357
+ }
358
+ dump_dict['ground_energy'] = {
359
+ "value": float(self.ground_energy),
360
+ "units": "eV"
361
+ }
362
+
363
+
364
+ try:
365
+ dump_dict["emission_rate"] = {
366
+ "value": float(self.emission_rate),
367
+ "units": "s^-1"
368
+ }
369
+
370
+ except Result_unavailable_error:
371
+ pass
372
+
373
+ return dump_dict
374
+
375
+ @classmethod
376
+ def from_dump(self, data, result_set, options, transition_type):
377
+ """
378
+ Get an instance of this class from its dumped representation.
379
+
380
+ :param data: The data to parse.
381
+ :param result_set: The partially constructed result set which is being populated.
382
+ """
383
+ # Get our tdm (if available).
384
+ if 'tdm' in data and data['tdm'] is not None:
385
+ tdm = result_set.transition_dipole_moments[data['index']-1]
386
+
387
+ else:
388
+ tdm = None
389
+
390
+ return self(
391
+ level = data['index'],
392
+ multiplicity = data['multiplicity'],
393
+ multiplicity_level = data['multiplicity_index'],
394
+ ground_multiplicity = data['ground_multiplicity'],
395
+ excited_energy = data['excited_energy']['value'],
396
+ ground_energy = data['ground_energy']['value'],
397
+ oscillator_strength = data['oscillator_strength'],
398
+ transition_type = transition_type,
399
+ transition_dipole_moment = tdm,
400
+ )
401
+
402
+