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
digichem/translate.py ADDED
@@ -0,0 +1,591 @@
1
+ """This module contains translation tables between various different, but equivalent, terms expected by different calculation programs."""
2
+
3
+ import csv
4
+
5
+ from configurables.misc import is_int
6
+
7
+ import digichem.log
8
+ from digichem.datas import get_resource
9
+
10
+ # Hidden imports.
11
+ #import basis_set_exchange as bse
12
+
13
+
14
+ # Load functional conversion table from file.
15
+ functional_db = {}
16
+ with open(get_resource('data/functionals.csv')) as csv_file:
17
+ _reader = csv.reader(csv_file)
18
+ headers = None
19
+ for line_num, line in enumerate(_reader):
20
+ if line_num == 0:
21
+ headers = line
22
+
23
+ else:
24
+ line = [line_part if line_part != "" else None for line_part in line]
25
+
26
+ # The various parts of the definition.
27
+ functional_name = line[0]
28
+ aliases = line[1].split(",") if line[1] is not None else []
29
+ functional = {"name": functional_name, "aliases": aliases, "turbomole": line[2], "gaussian": line[3]}
30
+
31
+ # Get all the names we can refer to this functional by.
32
+ all_names = [functional_name] + aliases
33
+ for program in ("turbomole", "gaussian"):
34
+ # Make sure we don't overwrite existing values with program specific ones.
35
+ if functional[program] is not None and functional[program] not in functional_db:
36
+ all_names.append(functional[program])
37
+
38
+ # Set into the DB, using all possible names.
39
+ for ref_name in all_names:
40
+ functional_db[ref_name] = functional
41
+
42
+
43
+ # Load solvent conversion table from file.
44
+ solvent_db = {}
45
+ with open(get_resource('data/solvents.csv')) as csv_file:
46
+ _reader = csv.reader(csv_file)
47
+ headers = None
48
+ for line_num, line in enumerate(_reader):
49
+ if line_num == 0:
50
+ headers = line
51
+
52
+ else:
53
+ line = [line_part if line_part != "" else None for line_part in line]
54
+
55
+ # The various parts of the definition.
56
+ solvent = {"name": line[0], "aliases": [line[1]] if line[1] is not None else [], "gaussian": line[2], "epsilon": float(line[3]) if line[3] is not None else None, "refractive": float(line[4]) if line[4] is not None else None}
57
+
58
+ solvent_db[solvent['name']] = solvent
59
+
60
+
61
+ class Translate():
62
+ """
63
+ ABC for translate classes.
64
+ """
65
+
66
+ def __init__(self, value):
67
+ """
68
+ Constructor for Translate classes.
69
+ """
70
+ if isinstance(value, type(self)):
71
+ self.value = value.value
72
+
73
+ else:
74
+ self.value = value
75
+
76
+ def translate(self, program):
77
+ """
78
+ Translate into a name appropriate for a given program.
79
+ """
80
+ raise NotImplementedError("Implement in subclass")
81
+
82
+ def to_turbomole(self):
83
+ """
84
+ Translate into a name appropriate for Turbomole.
85
+ """
86
+ return self.translate("turbomole")
87
+
88
+ def to_gaussian(self):
89
+ """
90
+ Translate into a name appropriate for Turbomole.
91
+ """
92
+ return self.translate("gaussian")
93
+
94
+ def to_orca(self):
95
+ """
96
+ Translate into a name appropriate for ORCA.
97
+ """
98
+ return self.translate("orca")
99
+
100
+
101
+ class Basis_set(Translate):
102
+ """
103
+ A class for converting between the names of basis sets.
104
+ """
105
+
106
+ @property
107
+ def basis_set(self):
108
+ return self.value
109
+
110
+ @classmethod
111
+ def build_db(self):
112
+ """
113
+ Build a table of basis set names.
114
+
115
+ :returns: A dictionary, where each key is the name of a basis set and each value is a BSE metadata dict.
116
+ """
117
+ import basis_set_exchange as bse
118
+ # Build a database of basis set metadata, adding the display name and alternative names as additional keys for each basis set.
119
+ # BSE's database has some semi-duplicate entries (such as '6-31g(d,p)' and '6-31g_st__st_').
120
+ # Although these basis sets are identical, they have reversed metadata (ie, for '6-31g(d,p)', 'display_name' is set to '6-31G(d,p)' and 'other_names'
121
+ # contains '6-31G**', while for '6-31g_st__st_' 'display_name' is '6-31G**' and 'other_names' is '6-31G(d,p)'.
122
+ # This is a problem for us because we need to predictably choose from these names (ie the star form '6-31G**' is suitable for both
123
+ # Gaussian and Turbomole), but the keys referring to these same names are different.
124
+ #
125
+ # Firstly, build a new dict using basename as the key, which appears to be the same even for duplicates.
126
+ db = {value["basename"]: value for key, value in bse.get_metadata().items()}
127
+
128
+ new_db = {}
129
+
130
+ for basis_key, basis_set_meta in db.items():
131
+ # First, add the normal key.
132
+ basis_set = {
133
+ "key": basis_set_meta['basename'],
134
+ "name": basis_set_meta['display_name'],
135
+ "aliases": basis_set_meta['other_names'],
136
+ "meta": basis_set_meta
137
+ }
138
+
139
+ # Decide on our program specific names.
140
+ basis_set["gaussian"] = basis_set['name']
141
+ basis_set["turbomole"] = basis_set['name']
142
+
143
+ # Turbomole doesn't like pople sets with polarisation function spelled out,
144
+ # it prefers the star format.
145
+ if basis_set['name'][-5:] == "(d,p)":
146
+ basis_set["turbomole"] = basis_set['name'][:-5] + "**"
147
+
148
+ # Gaussian has a strange, contracted style naming scheme for Karlsruhe,
149
+ # and a misleading/incorrect name for def2-SVP(P).
150
+ #print(basis_set['name'])
151
+ if basis_set['name'] == "def2-SV(P)":
152
+ basis_set['gaussian'] = "def2SVPP"
153
+
154
+ elif basis_set['name'][:5] == "def2-":
155
+ basis_set['gaussian'] = basis_set['name'][:4] + basis_set['name'][5:]
156
+
157
+ new_db[basis_key] = basis_set
158
+
159
+ # TODO: Need to support aux basis sets (which end in things like -rifit, -jfit, -jkfit etc.
160
+ # Orca, for example, uses /C, /J and /JK instead...
161
+ # Turbomole uses nothing.
162
+
163
+ # Basis set exchange seems to be missing some non-polarized Karlsruhe basis sets (or else Gaussian made them up).
164
+ # Add them manually so we can still convert from Gaussian's weird representation.
165
+ new_db.update({
166
+ "def2-TZV": {"key": "def2-TZV", "name": "def2-TZV", "aliases": [], "meta": {}, "gaussian": "def2TZV"},#, "turbomole": "def2-TZV"},
167
+ "def2-QZV": {"key": "def2-QZV", "name": "def2-QZV", "aliases": [], "meta": {}, "gaussian": "def2QZV"}#, "turbomole": "def2-TZV"}
168
+ })
169
+
170
+ return new_db
171
+
172
+ @classmethod
173
+ def find_in_db(self, hint):
174
+ """
175
+ Try and find the entry for a basis set in the basis set exchange.
176
+
177
+ :raises ValueError: If the given hint could not be found.
178
+ :param hint: The name of the basis set to search for.
179
+ :return: A dictionary of BSE metadata.
180
+ """
181
+ # Get the basis set DB.
182
+ db = self.build_db()
183
+
184
+ # First, try and see if we can just use hint as an exact match.
185
+ try:
186
+ return db[hint]
187
+
188
+ except KeyError:
189
+ pass
190
+
191
+ # Try again, this time ignoring case and looking through all possible names.
192
+ str_hint = "".join(str(hint).lower().split())
193
+ for basis_set in db.values():
194
+ names = [basis_set.get(name, "") for name in ("key", "name", "gaussian", "turbomole")] + basis_set['aliases']
195
+ if str_hint in [name.lower() for name in names if name is not None]:
196
+ return basis_set
197
+
198
+ # No luck.
199
+ raise ValueError("Could not find basis set definition for '{}'".format(hint))
200
+
201
+ def translate(self, program = "name"):
202
+ """
203
+ Translate the name of this basis set into one appropriate for a calculation program.
204
+
205
+ Basis sets are currently handled the same for all programs.
206
+ """
207
+ try:
208
+ basis_def = self.find_in_db(self.basis_set)
209
+ return basis_def[program]
210
+
211
+ except ValueError:
212
+ # Just return as is.
213
+ if self.basis_set != "auto":
214
+ digichem.log.get_logger().debug("Could not find basis set with name '{}' in the basis set exchange; using name unmodified".format(self.basis_set))
215
+ return self.basis_set
216
+
217
+ except KeyError:
218
+ return basis_def['name']
219
+
220
+ def __str__(self):
221
+ return str(self.translate())
222
+
223
+
224
+ class Functional(Translate):
225
+ """
226
+ A class for converting between the names of DFT functionals.
227
+ """
228
+
229
+ @property
230
+ def functional(self):
231
+ return self.value
232
+
233
+
234
+ @classmethod
235
+ def find_in_db(self, hint):
236
+ """
237
+ Try and find an entry for a functional in the internal library.
238
+
239
+ :raises ValueError: If the functional could not be found.
240
+ :param hint: The name of the functional to look for.
241
+ :returns: The corresponding functional dict.
242
+ """
243
+ # First try using exact name.
244
+ try:
245
+ return functional_db[hint]
246
+
247
+ except KeyError:
248
+ pass
249
+
250
+ # Try again, this time ignoring case.
251
+ functional_names = [functional_name.upper() for functional_name in functional_db]
252
+
253
+ try:
254
+ return functional_db[list(functional_db.keys())[functional_names.index(hint.upper())]]
255
+
256
+ except IndexError:
257
+ pass
258
+
259
+ # No luck.
260
+ raise ValueError("Could not find functional definition for '{}'".format(hint))
261
+
262
+
263
+ def translate(self, program):
264
+ """
265
+ Translate this functional name to one recognised by a given program.
266
+
267
+ :param program: The program to translate for.
268
+ """
269
+ # Try and get a definition for the functional from our db.
270
+ try:
271
+ func_def = self.find_in_db(self.functional)
272
+
273
+ # If there's an explicit value for our program, use that.
274
+ if program in func_def and func_def[program] is not None:
275
+ return func_def[program]
276
+
277
+ # Otherwise, use the main common name.
278
+ return func_def['name']
279
+
280
+ except ValueError:
281
+ pass
282
+
283
+ # No definition found, use as is.
284
+ return self.functional
285
+
286
+ def to_turbomole(self):
287
+ """
288
+ Translate into a name appropriate for Turbomole.
289
+ """
290
+ func_name = self.translate("turbomole")
291
+ # All turbomole functionals are lower case, except for the word 'Gaussian'.
292
+ return func_name.lower().replace("gaussian", "Gaussian")
293
+
294
+ def __str__(self):
295
+ return str(self.translate("name"))
296
+
297
+
298
+ class Solvent(Translate):
299
+ """A class for converting between different representations of solvents."""
300
+
301
+ @property
302
+ def solvent(self):
303
+ return self.value
304
+
305
+ @classmethod
306
+ def find_in_db(self, hint):
307
+ """
308
+ Try and find an entry for a solvent in the internal library.
309
+
310
+ :raises ValueError: If the multiplicity could not be found.
311
+ :param hint: The name, number or symbol of the multiplicity to look for.
312
+ :returns: The corresponding multiplicity dict.
313
+ """
314
+ # First, just try to lookup exactly.
315
+ try:
316
+ return solvent_db[hint]
317
+
318
+ except KeyError:
319
+ # No luck, look through for a match.
320
+ str_hint = "".join(str(hint).lower().split())
321
+ for solvent in solvent_db.values():
322
+ if hint == solvent['epsilon'] or str_hint in [name.lower() for name in solvent['aliases'] + [solvent['name']] + [solvent['gaussian']] if name is not None]:
323
+ return solvent
324
+
325
+ # No luck.
326
+ raise ValueError("Could not find solvent definition for '{}'".format(hint))
327
+
328
+ @classmethod
329
+ def epsilon_to_name(self, epsilon, threshold = 0.0001):
330
+ """
331
+ Find the name of a solvent based on its epsilon value.
332
+ """
333
+ close = {}
334
+ for solvent_def in solvent_db.values():
335
+ if solvent_def['epsilon'] == epsilon:
336
+ # Exact match, stop.
337
+ return solvent_def['name']
338
+
339
+ elif abs(solvent_def['epsilon'] - epsilon) < threshold:
340
+ # Close match.
341
+ close[abs(solvent_def['epsilon'] - epsilon)] = solvent_def['name']
342
+
343
+ # Sort values that were close based on how close they were.
344
+ close_names = [close[closeness] for closeness in sorted(close)]
345
+
346
+ try:
347
+ return close_names[0]
348
+
349
+ except IndexError:
350
+ raise ValueError("No solvent definitions within {} of {}".format(threshold, epsilon)) from None
351
+
352
+ @property
353
+ def epsilon(self):
354
+ return self.find_in_db(self.solvent)['epsilon']
355
+
356
+ @property
357
+ def refractive_index(self):
358
+ return self.find_in_db(self.solvent)['refractive']
359
+
360
+ def translate(self, program):
361
+ """
362
+ Translate this solvent name to one recognised by a given program.
363
+
364
+ :param program: The program to translate for.
365
+ """
366
+ if program == "turbomole":
367
+ program = "epsilon"
368
+
369
+ elif program == "orca":
370
+ program = "name"
371
+
372
+ try:
373
+ solvent_def = self.find_in_db(self.solvent)
374
+
375
+ if program in solvent_def and solvent_def[program] is not None:
376
+ return solvent_def[program]
377
+
378
+ else:
379
+ return solvent_def["name"]
380
+
381
+ except ValueError:
382
+ return self.solvent
383
+
384
+ def __str__(self):
385
+ return str(self.translate("name"))
386
+
387
+
388
+ class SCF_convergence(Translate):
389
+ """
390
+ A class for converting between different shortcuts for SCF convergence thresholds.
391
+ """
392
+
393
+ table = [
394
+ {"name": "Loose", "energy": -5, "density": -3, "orca": "LooseSCF"}, # Note energy stops changing here.
395
+ {"name": "Weak", "energy": -5, "density": -4, "orca": "SloppySCF"},
396
+ {"name": "Medium", "energy": -6, "density": -5, "orca": "MediumSCF"},
397
+ {"name": "Strong", "energy": -7, "density": -6, "orca": "StrongSCF"},
398
+ {"name": "Tight", "energy": -8, "density": -7, "orca": "TightSCF"}, # Typically a sensible default.
399
+ {"name": "VTight", "energy": -9, "density": -8, "orca": "VeryTightSCF"},
400
+ {"name": "VVTight", "energy": -10, "density": -9, "orca": "VeryVeryTightSCF"},
401
+ {"name": "Extreme", "energy": -14, "density": -14, "orca": "ExtremeSCF"},
402
+ ]
403
+
404
+ @classmethod
405
+ def names(self):
406
+ return [row['name'] for row in self.table]
407
+
408
+ @classmethod
409
+ def choices(self):
410
+ return [self(row['name']) for row in self.table]
411
+
412
+ @classmethod
413
+ def find_in_db(self, hint):
414
+ """
415
+ Try and find an entry in the internal library.
416
+
417
+ :raises ValueError: If the value could not be found.
418
+ :param hint: The name to look for
419
+ :returns: The corresponding value dict.
420
+ """
421
+ for row in self.table:
422
+ name, energy, density, orca = row.values()
423
+
424
+ if str(hint).upper() == name.upper() or str(hint).upper() == orca.upper():
425
+ return row
426
+
427
+ if str(hint) == str(energy) or str(hint).upper() == "ENERGY{}".format(energy) or str(hint).upper() == "DENSITY{}".format(density):
428
+ return row
429
+
430
+ # No luck.
431
+ raise ValueError("Could not find convergence definition for '{}'".format(hint))
432
+
433
+ def translate(self, to_type):
434
+ """
435
+ Translate into a name appropriate for a given program.
436
+ """
437
+ try:
438
+ return self.find_in_db(self.value)[to_type]
439
+
440
+ except ValueError:
441
+ # Couldn't find in conversion table.
442
+ return self.value
443
+
444
+ def __str__(self):
445
+ return str(self.translate("name"))
446
+
447
+ def __eq__(self, other):
448
+ return str(self) == str(other)
449
+
450
+
451
+ class Cube_grid_points(Translate):
452
+ """A class for converting between cube grid sizes."""
453
+
454
+ table = [
455
+ {"name": "Default", "points": 100, "gaussian": 0, "turbomole": 100, "orca": 100}, # Default depends on the calc program. Gaussian uses a special value of 'zero' to select a default algorithm.
456
+ {"name": "Tiny", "points": 25},
457
+ {"name": "Small", "points": 50},
458
+ {"name": "Medium", "points": 100},
459
+ {"name": "Large", "points": 200},
460
+ {"name": "Huge", "points": 500},
461
+
462
+ ]
463
+
464
+ @classmethod
465
+ def find_in_db(self, hint):
466
+ """
467
+ Try and find an entry in the internal library.
468
+
469
+ :raises ValueError: If the value could not be found.
470
+ :param hint: The name to look for
471
+ :returns: The corresponding value dict.
472
+ """
473
+ for row in self.table:
474
+ if str(hint).upper() == row['name'].upper():
475
+ return row
476
+
477
+ # No luck.
478
+ raise ValueError("Could not find grid point definition for '{}'".format(hint))
479
+
480
+ def translate(self, to_type):
481
+ """
482
+ Translate into a name appropriate for a given program.
483
+ """
484
+ try:
485
+ grid_def = self.find_in_db(self.value)
486
+ return grid_def[to_type]
487
+
488
+ except KeyError:
489
+ # Just return the equivalent number of points.
490
+ return grid_def['points']
491
+
492
+ except ValueError:
493
+ # Couldn't find in conversion table.
494
+ return self.value
495
+
496
+ def __str__(self):
497
+ return str(self.translate("name"))
498
+
499
+
500
+ # TODO: Use this.
501
+ class Multiplicity(Translate):
502
+ """A class for converting between different representations of multiplicity."""
503
+
504
+ table = [
505
+ {"name": "no multiplicity", "number": 0, "symbol": "?"},
506
+ {"name": "Singlet", "number": 1, "symbol": "S"},
507
+ {"name": "Doublet", "number": 2, "symbol": "D"},
508
+ {"name": "Triplet", "number": 3, "symbol": "T"},
509
+ {"name": "Quartet", "number": 4, "symbol": "Q"}
510
+ ]
511
+
512
+ @property
513
+ def multiplicity(self):
514
+ return self.value
515
+
516
+ @classmethod
517
+ def find_in_db(self, hint):
518
+ """
519
+ Try and find an entry for a multiplicity in the internal library.
520
+
521
+ :raises ValueError: If the multiplicity could not be found.
522
+ :param hint: The name, number or symbol of the multiplicity to look for.
523
+ :returns: The corresponding multiplicity dict.
524
+ """
525
+ for row in self.table:
526
+ name, number, symbol = row.values()
527
+ if hint == number or str(hint).upper() in (str(number), name.upper(), symbol.upper()):
528
+ return row
529
+
530
+ # No luck.
531
+ raise ValueError("Could not find multiplicity definition for '{}'".format(hint))
532
+
533
+ @property
534
+ def symbol(self):
535
+ """
536
+ Get this multiplicity as a symbol.
537
+ """
538
+ # Get a shorthand symbol if we can.
539
+ try:
540
+ return self.find_in_db(self.multiplicity)['symbol']
541
+
542
+ except ValueError:
543
+ if self.multiplicity % 1 == 0:
544
+ # Multiplicity is an integer, so return as a stringy whole number.
545
+ return str(int(self.multiplicity))
546
+ else:
547
+ return str(self.multiplicity)
548
+
549
+ @property
550
+ def string(self):
551
+ """
552
+ Get this multiplicity as a string.
553
+ """
554
+ return self.translate("name")
555
+
556
+ def __str__(self):
557
+ return self.string
558
+
559
+ @property
560
+ def number(self):
561
+ """
562
+ Get this multiplicity as a number.
563
+ """
564
+ try:
565
+ return self.find_in_db(self.multiplicity)['number']
566
+
567
+ except ValueError:
568
+ # No pre-defined number, see if it is an int.
569
+ if is_int(self.multiplicity):
570
+ return int(self.multiplicity)
571
+
572
+ else:
573
+ return float(self.multiplicity)
574
+
575
+ def translate(self, to_type):
576
+ """
577
+ Translate into a name appropriate for a given program.
578
+ """
579
+ if to_type == "gaussian":
580
+ to_type = "name"
581
+
582
+ elif to_type == "turbomole":
583
+ to_type = "number"
584
+
585
+
586
+ try:
587
+ return self.find_in_db(self.multiplicity)[to_type]
588
+
589
+ except ValueError:
590
+ # Couldn't find in conversion table.
591
+ return self.multiplicity
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.3
2
+ Name: digichem-core
3
+ Version: 6.0.0rc1
4
+ Summary: Open-source library for Digichem core components
5
+ Project-URL: Homepage, https://github.com/Digichem-Project/digichem-core
6
+ Project-URL: Documentation, https://doc.digi-chem.co.uk
7
+ Project-URL: Issues, https://github.com/Digichem-Project/digichem-core/issues
8
+ Author-email: "Oliver S. Lee" <osl@digi-chem.ac.uk>
9
+ License: Copyright 2024 Digichem
10
+
11
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
12
+
13
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
14
+
15
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
20
+ License-File: COPYING.md
21
+ License-File: LICENSE
22
+ Classifier: License :: OSI Approved :: BSD License
23
+ Classifier: Operating System :: Unix
24
+ Classifier: Programming Language :: Python :: 3
25
+ Requires-Python: >=3.9
26
+ Requires-Dist: adjusttext
27
+ Requires-Dist: basis-set-exchange
28
+ Requires-Dist: cclib
29
+ Requires-Dist: colour-science
30
+ Requires-Dist: configurables
31
+ Requires-Dist: deepmerge
32
+ Requires-Dist: dill
33
+ Requires-Dist: matplotlib
34
+ Requires-Dist: openprattle
35
+ Requires-Dist: periodictable
36
+ Requires-Dist: pillow
37
+ Requires-Dist: pyyaml
38
+ Requires-Dist: rdkit
39
+ Requires-Dist: scipy
40
+ Provides-Extra: test
41
+ Requires-Dist: pytest; extra == 'test'
42
+ Requires-Dist: pytest-lazy-fixture; extra == 'test'
43
+ Description-Content-Type: text/markdown
44
+
45
+ <img src="Banner.png" alt="Banner" />
46
+
47
+ # Digichem-core
48
+
49
+ Welcome to Digichem: the computational chemistry management suite!
50
+
51
+ This is Digichem-core, the open-source library for Digichem. If you are looking to build your own computational workflows using the tools that Digichem has to offer, then you have come to the right place.
52
+
53
+ - Alternatively, if you are looking for the full Digichem program, try [Build-boy](https://github.com/Digichem-Project/build-boy)
54
+ - If you'd like more information on the Digichem project, check out the [website](https://www.digi-chem.co.uk)
55
+
56
+ ## Dependencies
57
+
58
+ - adjustText
59
+ - basis_set_exchange
60
+ - cclib
61
+ - colour-science
62
+ - deepmerge
63
+ - dill
64
+ - matplotlib
65
+ - openprattle
66
+ - periodictable
67
+ - pillow
68
+ - pyyaml
69
+ - rdkit
70
+ - scipy
71
+
72
+ ## Installation
73
+
74
+ ### Pip
75
+
76
+ Digichem-core can be installed using pip. Simply run the following command:
77
+
78
+ ```Shell
79
+ pip install digichem-core
80
+ ```
81
+
82
+ Or, on some platforms:
83
+
84
+ ```Shell
85
+ pip3 install digichem-core
86
+ ```
87
+
88
+ ## Usage
89
+
90
+ Documentation coming soon.
91
+
92
+ ## License
93
+
94
+ Digichem-core is licensed under the BSD-3-Clause license, but some files are licensed separately. See [COPYING.md](COPYING.md) for full details.
95
+
96
+ The Digichem logo and branding is Copyright Digichem 2024, you may not use them in any way (although you are welcome to look at them).