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/image/vmd.py ADDED
@@ -0,0 +1,826 @@
1
+ from pathlib import Path
2
+ import subprocess
3
+ import math
4
+ from PIL import Image
5
+ from uuid import uuid4
6
+ import os
7
+ from math import fabs
8
+ import shutil
9
+
10
+ from digichem.exception.base import File_maker_exception
11
+ from digichem.image.render import Render_maker
12
+ import digichem.log
13
+ from digichem.datas import get_resource
14
+
15
+
16
+ class VMD_image_maker(Render_maker):
17
+ """
18
+ Class for generating image files from Gaussian outputs using VMD.
19
+
20
+ Nearly all of the work here is done by other programs, primarily VMD (https://www.ks.uiuc.edu/Research/vmd/). These classes are mostly just wrappers.
21
+ """
22
+
23
+ # The name of the vmd/tcl script (relative to the vmd script folder) used to render images. Inheriting classes should set this to an appropriate script file.
24
+ vmd_script = ""
25
+ # The filename extension to use for molecule scene files produced by vmd.
26
+ scene_file_extension = ".scene"
27
+ # 'Path' to the vmd executable.
28
+ #vmd_executable = "vmd"
29
+ # 'Path' to the tachyon executable.
30
+ #tachyon_executable = "tachyon"
31
+ # The initial resolution at which an image is rendered. This test image will then be discarded once relevant info has been extracted.
32
+ test_resolution = 300
33
+
34
+ # Name of the section where we get some specific configs.
35
+ options_name = "orbital"
36
+
37
+ def __init__(self, *args, cube_file = None, rotations = None, auto_crop = True, rendering_style = "pastel", resolution = 1024, also_make_png = True, isovalue = 0.2,
38
+ vmd_executable = "vmd", tachyon_executable = "tachyon", vmd_logging = False,
39
+ **kwargs):
40
+ """
41
+ Constructor for Image_maker objects.
42
+
43
+ :param output: The path to write image files to. As more than one image can be created by this class, this name is used as the base name and is modified for each image. The suffix will be honoured (rendered imges will use it as a hint for their output format.
44
+ :param cube_file: The path to a cube_file to use to render images.
45
+ :param rotations: A list of tuples of rotations, where the first index in the tuple specifies the axis to rotate about and the second is the angle to rotate (in radians).
46
+ :param auto_crop: If False, images will not have excess white space cropped.
47
+ :param rendering_style: A string describing the rendering style to use, either 'digichem' or 'gaussian'.
48
+ :param resolution: The max width or height of the rendered images in pixels.
49
+ :param also_make_png: If True, additional images will be rendered in PNG format. This option is useful to generate higher quality images alongside more portable formats. If 'output' is a .png file, then it is wise to set this option to False (otherwise two png files will be rendered, which is a waste).
50
+ :param isovalue: The isovalue to use for rendering isosurfaces. Has no effect when rendering only atoms.
51
+ :param vmd_executable: 'Path' to the vmd executable to use for image rendering. Defaults to relying on the command 'vmd'.
52
+ :param tachyon_executable: 'Path' to the tachyon executable to use for image rendering. Defaults to relying on the command 'tachyon'.
53
+ :param vmd_logging: Whether to print output from vmd.
54
+ """
55
+ super().__init__(
56
+ *args,
57
+ cube_file = cube_file,
58
+ rotations = rotations,
59
+ auto_crop = auto_crop,
60
+ resolution = resolution,
61
+ also_make_png = also_make_png,
62
+ isovalue = isovalue,
63
+ **kwargs
64
+ )
65
+
66
+ # Some options that control how we function.
67
+ self.rendering_style = rendering_style
68
+
69
+ # Save executable paths.
70
+ self.vmd_executable = vmd_executable
71
+ self.tachyon_executable = tachyon_executable
72
+
73
+ self.vmd_logging = vmd_logging
74
+
75
+
76
+ if self.rendering_style is None:
77
+ # Panic time.
78
+ raise ValueError("rendering_style must not be None")
79
+
80
+ # The colours we will be using for rendering (depends on rendering_stype and is only for reference; the actual definition of each style is in VMD).
81
+ if self.rendering_style == "gaussian":
82
+ self.primary_colour = "green"
83
+ self.secondary_colour = "red"
84
+ elif self.rendering_style == "vesta":
85
+ self.primary_colour = "blue"
86
+ self.secondary_colour = "yellow"
87
+ else:
88
+ self.primary_colour = "blue"
89
+ self.secondary_colour = "red"
90
+
91
+ @property
92
+ def rotations(self):
93
+ # Digichem rotates the wrong way round for some reason, reverse for our rendering engine.
94
+ # VMD also likes degrees not radians.
95
+ return [(axis, math.degrees(-theta)) for axis, theta in self._rotations]
96
+
97
+ @classmethod
98
+ def from_options(self, output, *, cube_file = None, rotations = None, options, **kwargs):
99
+ """
100
+ Constructor that takes a dictionary of config like options.
101
+ """
102
+ return self(
103
+ output,
104
+ cube_file = cube_file,
105
+ rotations = rotations,
106
+ auto_crop = options['render']['auto_crop'],
107
+ rendering_style = options['render']['vmd']['rendering_style'],
108
+ resolution = options['render']['resolution'],
109
+ isovalue = options['render'][self.options_name]['isovalue'],
110
+ use_existing = options['render']['use_existing'],
111
+ dont_modify = not options['render']['enable_rendering'],
112
+ vmd_executable = options['render']['vmd']['executable'],
113
+ tachyon_executable = options['render']['vmd']['tachyon'],
114
+ vmd_logging = options['logging']['render_logging'],
115
+ **kwargs
116
+ )
117
+
118
+ @classmethod
119
+ def safe_name(self, file_name):
120
+ """
121
+ Get a filename free of 'unusual' characters.
122
+ :return: The safe path name.
123
+ """
124
+ safe_chars = "._"
125
+ return "".join([char if char.isalnum() or char in safe_chars else "_" for char in file_name])
126
+
127
+ @property
128
+ def vmd_script_path(self):
129
+ """
130
+ Get the file path to the VMD script to be used by this class to render images (as a pathlib Path).
131
+ """
132
+ return get_resource('data/vmd/{}'.format(self.vmd_script))
133
+
134
+ @property
135
+ def tcl_common_path(self):
136
+ """
137
+ Get the file path to the Tcl script that is common to all of our other Tcl scripts (as a pathlib Path).
138
+ """
139
+ return get_resource('data/vmd/common.tcl')
140
+
141
+ @property
142
+ def creation_message(self):
143
+ """
144
+ A short message that may (depending on log-level) be printed to the user before make_files() is called.
145
+ """
146
+ return "Rendering '{}' to file(s)".format(self.output if self.full_path_names else self.output.name)
147
+
148
+ def make_files(self):
149
+ """
150
+ Make the image files referenced by this object.
151
+
152
+ The new image will be written to file.
153
+ """
154
+ # Run VMD, which writes a plain text description of our scene to file.
155
+ self.run_VMD_script()
156
+
157
+ # Next run tachyon for each of our 4 scene files, which creates tga files.
158
+ for image_name in ['x0y0z0', 'x90y0z0', 'x0y90z0', 'x45y45z45']:
159
+ image_path = self.file_path[image_name]
160
+ try:
161
+ # First we'll render a test image at a lower resolution. We'll then crop it, and use the decrease in final resolution to know how much bigger we need to render in our final image to hit our target resolution.
162
+ # Unless of course auto_crop is False, in which case we use our target resolution immediately.
163
+ resolution = self.test_resolution if self.auto_crop else self.target_resolution
164
+ self.run_tachyon_renderer(
165
+ image_path.with_name(self.safe_name(image_path.with_suffix(self.scene_file_extension).name)),
166
+ image_path.with_suffix(".tga"),
167
+ resolution
168
+ )
169
+
170
+ if self.auto_crop:
171
+ # Load the test image and autocrop it.
172
+ with Image.open(image_path.with_suffix(".tga"), "r") as test_im:
173
+ small_test_im = self.auto_crop_image(test_im)
174
+
175
+ # Get the cropped size. We're interested in the largest dimension, as this is what we'll output as.
176
+ cropped_resolution = max(small_test_im.size)
177
+
178
+ # From this we can work out the ratio between our true resolution and the resolution we've been asked for.
179
+ resolution_ratio = cropped_resolution / self.test_resolution
180
+
181
+ # Now we can re-render asking for a larger resolution so that when we crop again, we should get our target resolution.
182
+ # Note that resolution ratio is a fraction, so self.target_resolution / resolution_ratio results in an increase.
183
+ self.run_tachyon_renderer(
184
+ image_path.with_name(self.safe_name(image_path.with_suffix(self.scene_file_extension).name)),
185
+ image_path.with_suffix(".tga"),
186
+ self.target_resolution / resolution_ratio
187
+ )
188
+
189
+ # Delete the now unneeded scene file.
190
+ os.remove(image_path.with_name(self.safe_name(image_path.with_suffix(self.scene_file_extension).name)))
191
+ except Exception as e:
192
+ raise File_maker_exception(self, "Error in tachyon rendering") from e
193
+
194
+ # Convert to a better set of formats.
195
+ # Open the file we just rendered.
196
+ with Image.open(image_path.with_suffix(".tga"), "r") as im:
197
+
198
+ # If we've been asked to autocrop, do so.
199
+ if self.auto_crop:
200
+ try:
201
+ cropped_image = self.auto_crop_image(im)
202
+ except Exception:
203
+ raise File_maker_exception(self, "Error in post-rendering auto-crop")
204
+ else:
205
+ cropped_image = im
206
+
207
+ # Now save in our main output format.
208
+ cropped_image.save(image_path)
209
+
210
+ # And also as a higher quality png if we've been asked to.
211
+ if self.also_make_png:
212
+ cropped_image.save(self.file_path[image_name + "_big"])
213
+
214
+ # And delete the old .tga (unless we've just rendered to .tga!).
215
+ if image_path.suffix != ".tga":
216
+ os.remove(image_path.with_suffix(".tga"))
217
+
218
+ @property
219
+ def prepared_translations(self):
220
+ """
221
+ Our list of translations in a from ready for VMD/Tcl.
222
+ """
223
+ return "{},{},{}".format(self.translations[0], self.translations[1], self.translations[2])
224
+
225
+ @property
226
+ def prepared_rotations(self):
227
+ """
228
+ Our list of rotations in a form ready for VMD/Tcl.
229
+ """
230
+ # This is a stringified form of our list of tuples that we'll pass to VMD (or rather the TCL interpreter).
231
+ if len(self.rotations) > 0:
232
+ rot_string = ":".join(["{},{}".format(axis, angle) for axis, angle in self.rotations])
233
+ else:
234
+ # An empty string will be misinterpreted on the command line.
235
+ rot_string = "0,0"
236
+ return rot_string
237
+
238
+ @property
239
+ def VMD_signature(self):
240
+ """
241
+ The signature pass to subprocess.run used to call VMD. Inheriting classes should write their own implementation.
242
+ """
243
+ return ""
244
+
245
+ @property
246
+ def inputs(self):
247
+ """
248
+ A dictionary of all the input files required by VMD for this image.
249
+
250
+ inputs is a dictionary where each key is the path to the locally accessible file,
251
+ and each value is the true location.
252
+ """
253
+ working_directory = self.output.parent
254
+ return {
255
+ Path(working_directory, self.vmd_script_path.name): self.vmd_script_path,
256
+ Path(working_directory, self.tcl_common_path.name): self.tcl_common_path,
257
+ Path(working_directory, self.safe_name(Path(str(self.input_file)).name)): Path(str(self.input_file)).absolute()
258
+ }
259
+
260
+ def run_VMD_script(self):
261
+ """
262
+ Called as part of the make() method, inheriting classes can implement this method if they have a different VMD call signature.
263
+
264
+ Also see VMD_signature, which may do what you need.
265
+
266
+ :return: The CompletedProcess object.
267
+ """
268
+ # Setting these variables greatly improves VMD startup time.
269
+ os.environ["VMDNOOPTIX"] = "1"
270
+ os.environ["VMDNOOSPRAY"] = "1"
271
+
272
+ # VMD has two run scripts, one in C-shell (which is obviously garbage) and one in bash.
273
+ # Sadly, C-Shell is often the default, and it's doesn't support filenames properly.
274
+ # We'll use the usual workaround of changing the working directory and using relative file names.
275
+ working_directory = self.output.parent
276
+
277
+ try:
278
+ # Create local links to our input files.
279
+ for input_dst, input_src in self.inputs.items():
280
+ # Don't symlink if the source is already in the output dir.
281
+ if input_dst.absolute() != input_src.absolute():
282
+ try:
283
+ os.symlink(input_src, input_dst)
284
+
285
+ except Exception:
286
+ # Couldn't symlink for some reason, print a warning and copy instead.
287
+ digichem.log.get_logger().warning("Failed to create symlink for VMD input file, falling back to copy (this may take a while)")
288
+ shutil.copy(input_src, input_dst)
289
+
290
+ digichem.log.get_logger().debug(str(self.VMD_signature))
291
+ # Run VMD, which renders our image for us.
292
+ try:
293
+ return subprocess.run(
294
+ self.VMD_signature,
295
+ # We normally capture and discard stdout (because VMD is VERY verbose), but if we're at a suitable log level, we'll print it.
296
+ # Nothing useful appears to be printed to stderr, so we'll treat it the same as stdout.,
297
+ stdin = subprocess.DEVNULL,
298
+ stdout = subprocess.DEVNULL if not self.vmd_logging else None,
299
+ stderr = subprocess.STDOUT,
300
+ universal_newlines = True,
301
+ cwd = working_directory,
302
+ # VMD has a tendency to sigsegv when closing with VMDNOOPTIX set to on (even tho everything is fine) so we can't check retval sadly.
303
+ #check = True
304
+ )
305
+ except FileNotFoundError:
306
+ raise File_maker_exception(self, "Could not locate vmd executable '{}'".format(self.vmd_executable))
307
+
308
+ finally:
309
+ # Clean up.
310
+ # Remove the copied inputs
311
+ for input_dst, input_src in self.inputs.items():
312
+ # Don't symlink if the source is already in the output dir.
313
+ if input_dst.absolute() != input_src.absolute():
314
+ try:
315
+ input_dst.unlink()
316
+
317
+ except Exception:
318
+ # Warnings are useful here, if we can't delete the files we probably failed to copy them in the first place.
319
+ digichem.log.get_logger().warning("Failed to delete VMD input file '{}'".format(input_dst), exc_info = True)
320
+
321
+
322
+ def run_tachyon_renderer(self, scene_file, tga_file, resolution):
323
+ """
324
+ Called as part of the make() method.
325
+
326
+ Take a molecule 'scene' file produced by VMD and render to an image with tachyon.
327
+
328
+ :param scene_file: A VMD scene file to read in as input.
329
+ :param tga_file: The filename to write to.
330
+ :param resolution: The resolution to render with.
331
+ """
332
+ # Sadly, tachyon is old and decrepit and doesn't parse the output filename correctly (but weirdly the input file is fine).
333
+ # To get around this, we'll use a semi-random output name that we know is safe, and change-directory to the output directory immediately before calling tachyon.
334
+ # This way we can ensure there will be no nasty file-names for tachyon to get stuck on.
335
+ working_directory = tga_file.parent
336
+ tmpfile_name = ".tachyon_output_" + uuid4().hex + ".tga"
337
+ tmpfile_full_path = Path(tga_file.parent, tmpfile_name)
338
+ tachyon_process = None
339
+ try:
340
+ try:
341
+ # Now we can run tachyon.
342
+ tachyon_process = subprocess.run(
343
+ [
344
+ "{}".format(self.tachyon_executable),
345
+ scene_file.relative_to(working_directory),
346
+ "-aasamples", "12",
347
+ "-res", "{}".format(resolution), "{}".format(resolution),
348
+ "-o", tmpfile_name
349
+ ],
350
+ stdout = subprocess.PIPE if not self.vmd_logging else None,
351
+ stderr = subprocess.STDOUT,
352
+ universal_newlines = True,
353
+ check = True,
354
+ cwd = working_directory
355
+ )
356
+ except FileNotFoundError:
357
+ # THINK this can only occur if the command itself cannot be found, but I might be wrong.
358
+ raise File_maker_exception(self, "Could not locate tachyon executable '{}'".format(self.tachyon_executable))
359
+
360
+ # Render complete (sadly we don't appear to have any ability to check for errors).
361
+ # Now rename our tmpfile.
362
+ #os.rename(tmpfile_full_path, tga_file)
363
+ tmpfile_full_path.rename(tga_file)
364
+
365
+ except Exception:
366
+ # Something went wrong, remove our tmpfile.
367
+ try:
368
+ os.remove(tmpfile_full_path)
369
+ except Exception:
370
+ pass
371
+
372
+ # If we didn't already show output, dump it now.
373
+ if not self.vmd_logging and tachyon_process is not None:
374
+ digichem.log.get_logger().error("Tachyon did not exit successfully, dumping output:\n{}".format(tachyon_process.stdout))
375
+
376
+ raise
377
+
378
+
379
+ class Structure_image_maker(VMD_image_maker):
380
+ """
381
+ Class for creating structure images.
382
+ """
383
+
384
+ vmd_script ="generate_structure_images.tcl"
385
+
386
+ @property
387
+ def VMD_signature(self):
388
+ """
389
+ The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
390
+ """
391
+ return [
392
+ "{}".format(self.vmd_executable),
393
+ "-dispdev", "none",
394
+ "-e", "{}".format(self.vmd_script_path.name),
395
+ "-args",
396
+ "{}".format(self.safe_name(Path(str(self.input_file)).name)),
397
+ "{}".format(self.tcl_common_path.name),
398
+ "{}".format(self.rendering_style),
399
+ "{}".format(self.prepared_translations),
400
+ "{}".format(self.prepared_rotations),
401
+ "{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
402
+ "{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
403
+ "{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
404
+ "{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
405
+ ]
406
+
407
+
408
+ class Orbital_image_maker(Structure_image_maker):
409
+ """
410
+ Class for creating orbital images.
411
+ """
412
+
413
+ vmd_script ="generate_orbital_images.tcl"
414
+
415
+ @property
416
+ def VMD_signature(self):
417
+ """
418
+ The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
419
+ """
420
+ return [
421
+ "{}".format(self.vmd_executable),
422
+ "-dispdev", "none",
423
+ "-e", "{}".format(self.vmd_script_path.name),
424
+ "-args",
425
+ "{}".format(self.safe_name(Path(str(self.input_file)).name)),
426
+ "{}".format(self.tcl_common_path.name),
427
+ "{}".format(self.rendering_style),
428
+ "{}".format(self.isovalue),
429
+ "{}".format(self.prepared_translations),
430
+ "{}".format(self.prepared_rotations),
431
+ "{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
432
+ "{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
433
+ "{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
434
+ "{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
435
+ ]
436
+
437
+
438
+ class Difference_density_image_maker(Orbital_image_maker):
439
+
440
+ # Name of the section where we get some specific configs.
441
+ options_name = "difference_density"
442
+
443
+
444
+ class Spin_density_image_maker(Orbital_image_maker):
445
+ """
446
+ Class for creating spin density images.
447
+ """
448
+
449
+ vmd_script ="generate_spin_images.tcl"
450
+
451
+ # Name of the section where we get some specific configs.
452
+ options_name = "spin"
453
+
454
+ def __init__(self, *args, spin = "both", **kwargs):
455
+ """
456
+ Constructor for Spin_density_image_maker objects.
457
+
458
+ See Orbital_image_maker for a complete constructor.
459
+ :param spin: A string indicating which net-spins to render, either 'positive', 'negative' or 'both'.
460
+ """
461
+ super().__init__(*args, **kwargs)
462
+ self.spin = spin
463
+
464
+ @property
465
+ def VMD_signature(self):
466
+ """
467
+ The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
468
+ """
469
+ return [
470
+ "{}".format(self.vmd_executable),
471
+ "-dispdev", "none",
472
+ "-e", "{}".format(self.vmd_script_path.name),
473
+ "-args",
474
+ "{}".format(self.safe_name(Path(str(self.input_file)).name)),
475
+ "{}".format(self.tcl_common_path.name),
476
+ "{}".format(self.rendering_style),
477
+ "{}".format(fabs(self.isovalue)),
478
+ "{}".format(self.spin),
479
+ "{}".format(self.prepared_translations),
480
+ "{}".format(self.prepared_rotations),
481
+ "{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
482
+ "{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
483
+ "{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
484
+ "{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
485
+ ]
486
+
487
+ class Alpha_orbital_image_maker(Orbital_image_maker):
488
+ pass
489
+ #cubegen_type = "AMO"
490
+
491
+
492
+ class Beta_orbital_image_maker(Orbital_image_maker):
493
+ pass
494
+ #cubegen_type = "BMO"
495
+
496
+ class Density_image_maker(Orbital_image_maker):
497
+ """
498
+ Class for creating spin density images.
499
+ """
500
+
501
+ vmd_script ="generate_density_images.tcl"
502
+
503
+ # Name of the section where we get some specific configs.
504
+ options_name = "density"
505
+
506
+ def __init__(self, *args, **kwargs):
507
+ """
508
+ Constructor for Spin_density_image_maker objects.
509
+
510
+ See Orbital_image_maker for a complete constructor.
511
+ """
512
+ super().__init__(*args, **kwargs)
513
+
514
+ @property
515
+ def type(self):
516
+ """
517
+ The density type.
518
+ """
519
+ return self.input_file.type
520
+
521
+ @property
522
+ def VMD_signature(self):
523
+ """
524
+ The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
525
+ """
526
+ return [
527
+ "{}".format(self.vmd_executable),
528
+ "-dispdev", "none",
529
+ "-e", "{}".format(self.vmd_script_path.name),
530
+ "-args",
531
+ "{}".format(self.safe_name(Path(str(self.input_file)).name)),
532
+ "{}".format(self.tcl_common_path.name),
533
+ "{}".format(self.rendering_style),
534
+ "{}".format(fabs(self.isovalue)),
535
+ "{}".format(self.prepared_translations),
536
+ "{}".format(self.prepared_rotations),
537
+ "{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
538
+ "{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
539
+ "{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
540
+ "{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
541
+ ]
542
+
543
+ class Combined_orbital_image_maker(VMD_image_maker):
544
+ """
545
+ Class for creating images with both the HOMO and LUMO shown together.
546
+ """
547
+
548
+ vmd_script ="generate_combined_orbital_images.tcl"
549
+
550
+ def __init__(self, *args, HOMO_cube_file = None, LUMO_cube_file = None, **kwargs):
551
+ """
552
+ Constructor for combined orbital image maker objects.
553
+
554
+ :param output: Path to write to. See the constructor for VMD_image_maker for how this works.
555
+ :param HOMO_cube_file: Path to the HOMO cube file.
556
+ :param LUMO_cube_file: Path to the LUMO cube file.
557
+ :param *args: See the constructor for VMD_image_maker for further options.
558
+ :param **kwargs: See the constructor for VMD_image_maker for further options.
559
+ """
560
+ super().__init__(*args, cube_file = None, **kwargs)
561
+ self.HOMO_cube_file = HOMO_cube_file
562
+ self.LUMO_cube_file = LUMO_cube_file
563
+
564
+ @classmethod
565
+ def from_options(self, output, *, HOMO_cube_file = None, LUMO_cube_file = None, rotations = None, options, **kwargs):
566
+ """
567
+ Constructor that takes a dictionary of config like options.
568
+ """
569
+ return self(
570
+ output,
571
+ HOMO_cube_file = HOMO_cube_file,
572
+ LUMO_cube_file = LUMO_cube_file,
573
+ rotations = rotations,
574
+ auto_crop = options['render']['auto_crop'],
575
+ rendering_style = options['render']['vmd']['rendering_style'],
576
+ resolution = options['render']['resolution'],
577
+ isovalue = options['render'][self.options_name]['isovalue'],
578
+ use_existing = options['render']['use_existing'],
579
+ dont_modify = not options['render']['enable_rendering'],
580
+ vmd_executable = options['render']['vmd']['executable'],
581
+ tachyon_executable = options['render']['vmd']['tachyon'],
582
+ vmd_logging = options['logging']['render_logging'],
583
+ **kwargs
584
+ )
585
+
586
+ def check_can_make(self):
587
+ """
588
+ Check whether it is feasible to try and render the image(s) that we represent.
589
+
590
+ Reasons for rendering not being possible are varied and are up to the inheriting class, but include eg, a required input (cube, fchk) file not being given.
591
+
592
+ This method returns nothing, but will raise a File_maker_exception exception if the rendering is not possible.
593
+ """
594
+ try:
595
+ if self.HOMO_cube_file is None or self.HOMO_cube_file.safe_get_file() is None:
596
+ raise File_maker_exception(self, "No HOMO cube file is available")
597
+ except AttributeError:
598
+ if not hasattr(self.HOMO_cube_file, 'safe_get_file'):
599
+ # Input file does not have a safe_get_file() method.
600
+ pass
601
+
602
+ else:
603
+ raise
604
+
605
+ try:
606
+ if self.LUMO_cube_file is None or self.LUMO_cube_file.safe_get_file() is None:
607
+ raise File_maker_exception(self, "No LUMO cube file is available")
608
+
609
+ except AttributeError:
610
+ if not hasattr(self.LUMO_cube_file, 'safe_get_file'):
611
+ # Input file does not have a safe_get_file() method.
612
+ pass
613
+
614
+ else:
615
+ raise
616
+
617
+ @property
618
+ def inputs(self):
619
+ """
620
+ A dictionary of all the input files required by VMD for this image.
621
+
622
+ inputs is a dictionary where each key is the path to the locally accessible file,
623
+ and each value is the true location.
624
+ """
625
+ working_directory = self.output.parent
626
+ return {
627
+ Path(working_directory, self.vmd_script_path.name): self.vmd_script_path,
628
+ Path(working_directory, self.tcl_common_path.name): self.tcl_common_path,
629
+ Path(working_directory, self.safe_name(Path(str(self.HOMO_cube_file)).name)): Path(str(self.HOMO_cube_file)).absolute(),
630
+ Path(working_directory, self.safe_name(Path(str(self.LUMO_cube_file)).name)): Path(str(self.LUMO_cube_file)).absolute()
631
+ }
632
+
633
+ @property
634
+ def VMD_signature(self):
635
+ """
636
+ The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
637
+ """
638
+ return [
639
+ "{}".format(self.vmd_executable),
640
+ "-dispdev", "none",
641
+ "-e", "{}".format(self.vmd_script_path.name),
642
+ "-args",
643
+ "{}".format(self.safe_name(Path(str(self.HOMO_cube_file)).name)),
644
+ "{}".format(self.safe_name(Path(str(self.LUMO_cube_file)).name)),
645
+ "{}".format(self.tcl_common_path.name),
646
+ "{}".format(self.rendering_style),
647
+ "{}".format(fabs(self.isovalue)),
648
+ "{}".format(self.prepared_translations),
649
+ "{}".format(self.prepared_rotations),
650
+ "{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
651
+ "{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
652
+ "{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
653
+ "{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
654
+ ]
655
+
656
+
657
+
658
+ class Dipole_image_maker(Structure_image_maker):
659
+ """
660
+ Class for creating dipole images.
661
+ """
662
+
663
+ vmd_script ="generate_dipole_images.tcl"
664
+
665
+
666
+ def __init__(self, *args, dipole_moment = None, magnetic_dipole_moment = None, scaling = 1, magnetic_scaling = 1, **kwargs):
667
+ """
668
+ Constructor for Dipole_image_maker objects.
669
+
670
+ :param output: Path to write to. See the constructor for VMD_image_maker for how this works.
671
+ :param cube_file: A Gaussian cube file to use to render the new images.
672
+ :param dipole_moment: A Dipole_moment object that will be rendered as a red arrow in the scene.
673
+ :param magnetic_dipole_moment: A second dipole moment object that will be rendered as a blue arrow in the scene.
674
+ :param scaling: A factor to scale the dipole moment by.
675
+ :param magnetic_scaling: A factor to scale the magnetic dipole moment by.
676
+ :param *args: See the constructor for VMD_image_maker for further options.
677
+ :param **kwargs: See the constructor for VMD_image_maker for further options.
678
+ """
679
+ super().__init__(*args, **kwargs)
680
+ self.dipole_moment = dipole_moment
681
+ self.magnetic_dipole_moment = magnetic_dipole_moment
682
+ self.scaling = scaling
683
+ self.magnetic_scaling = magnetic_scaling
684
+ self.electric_arrow_colour = "red"
685
+ self.magnetic_arrow_colour = "green"
686
+
687
+ def get_coords(self, dipole, scaling):
688
+ if dipole is None:
689
+ return ( (0.0,0.0,0.0), (0.0,0.0,0.0))
690
+
691
+ else:
692
+ return (
693
+ tuple([coord * scaling for coord in dipole._origin_coords]),
694
+ tuple([coord * scaling for coord in dipole._vector_coords])
695
+ )
696
+
697
+ @property
698
+ def VMD_signature(self):
699
+ """
700
+ The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
701
+ """
702
+ return [
703
+ "{}".format(self.vmd_executable),
704
+ "-dispdev", "none",
705
+ "-e", "{}".format(self.vmd_script_path.name),
706
+ "-args",
707
+ "{}".format(self.safe_name(Path(str(self.input_file)).name)),
708
+ "{}".format(self.tcl_common_path.name),
709
+ "{}".format(self.rendering_style),
710
+ "{}".format(self.prepared_translations),
711
+ "{}".format(self.prepared_rotations),
712
+ # We don't use the normal origin_coords/vector_coords because these are already rotated, while we want/need to do this rotation with the camera in VMD.
713
+ # Hence use _origin_coords/_vector_coods, which aren't rotated.
714
+ # Dipole 1 (electric).
715
+ "{}:{}:{}".format(*self.get_coords(self.dipole_moment, self.scaling)[0]),
716
+ "{}:{}:{}".format(*self.get_coords(self.dipole_moment, self.scaling)[1]),
717
+ # Dipole 2 (magnetic).
718
+ "{}:{}:{}".format(*self.get_coords(self.magnetic_dipole_moment, self.magnetic_scaling)[0]),
719
+ "{}:{}:{}".format(*self.get_coords(self.magnetic_dipole_moment, self.magnetic_scaling)[1]),
720
+ "{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
721
+ "{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
722
+ "{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
723
+ "{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
724
+ ]
725
+
726
+
727
+ class Permanent_dipole_image_maker(Dipole_image_maker):
728
+ """
729
+ Class for creating dipole images.
730
+ """
731
+
732
+ def __init__(self, *args, dipole_moment = None, scaling = 1, **kwargs):
733
+ """
734
+ Constructor for Dipole_image_maker objects.
735
+
736
+ :param output: Path to write to. See the constructor for VMD_image_maker for how this works.
737
+ :param cube_file: A Gaussian cube file to use to render the new images.
738
+ :param dipole_moment: A Dipole_moment object that will be rendered as a red arrow in the scene.
739
+ :param scaling: A factor to scale the dipole moment by.
740
+ :param *args: See the constructor for VMD_image_maker for further options.
741
+ :param **kwargs: See the constructor for VMD_image_maker for further options.
742
+ """
743
+ super().__init__(*args, dipole_moment = dipole_moment, scaling = scaling, **kwargs)
744
+
745
+ @classmethod
746
+ def from_options(self, output, *, dipole_moment = None, cube_file = None, rotations = None, options, **kwargs):
747
+ """
748
+ Constructor that takes a dictionary of config like options.
749
+ """
750
+ return self(
751
+ output,
752
+ cube_file = cube_file,
753
+ rotations = rotations,
754
+ dipole_moment = dipole_moment,
755
+ auto_crop = options['render']['auto_crop'],
756
+ rendering_style = options['render']['vmd']['rendering_style'],
757
+ resolution = options['render']['resolution'],
758
+ isovalue = options['render'][self.options_name]['isovalue'],
759
+ use_existing = options['render']['use_existing'],
760
+ dont_modify = not options['render']['enable_rendering'],
761
+ vmd_executable = options['render']['vmd']['executable'],
762
+ tachyon_executable = options['render']['vmd']['tachyon'],
763
+ vmd_logging = options['logging']['render_logging'],
764
+ scaling = options['render']['dipole_moment']['scaling'],
765
+ **kwargs
766
+ )
767
+
768
+ def check_can_make(self):
769
+ """
770
+ Check whether it is feasible to try and render the image(s) that we represent.
771
+
772
+ Reasons for rendering not being possible are varied and are up to the inheriting class, but include eg, a required input (cube, fchk) file not being given.
773
+
774
+ This method returns nothing, but will raise a File_maker_exception exception if the rendering is not possible.
775
+ """
776
+ super().check_can_make()
777
+
778
+ # Also make sure we have a dipole.
779
+ if self.dipole_moment is None:
780
+ raise File_maker_exception(self, "No dipole moment is available.")
781
+
782
+
783
+ class Transition_dipole_image_maker(Dipole_image_maker):
784
+ """
785
+ Class for creating TDM images.
786
+ """
787
+
788
+ def check_can_make(self):
789
+ """
790
+ Check whether it is feasible to try and render the image(s) that we represent.
791
+
792
+ Reasons for rendering not being possible are varied and are up to the inheriting class, but include eg, a required input (cube, fchk) file not being given.
793
+
794
+ This method returns nothing, but will raise a File_maker_exception exception if the rendering is not possible.
795
+ """
796
+ super().check_can_make()
797
+
798
+ # Also make sure we have a dipole.
799
+ if self.dipole_moment is None and self.magnetic_dipole_moment is None:
800
+ raise File_maker_exception(self, "No dipole moment is available.")
801
+
802
+ @classmethod
803
+ def from_options(self, output, *, dipole_moment = None, magnetic_dipole_moment, cube_file = None, rotations = None, options, **kwargs):
804
+ """
805
+ Constructor that takes a dictionary of config like options.
806
+ """
807
+ return self(
808
+ output,
809
+ cube_file = cube_file,
810
+ rotations = rotations,
811
+ dipole_moment = dipole_moment,
812
+ magnetic_dipole_moment = magnetic_dipole_moment,
813
+ auto_crop = options['render']['auto_crop'],
814
+ rendering_style = options['render']['vmd']['rendering_style'],
815
+ resolution = options['render']['resolution'],
816
+ isovalue = options['render'][self.options_name]['isovalue'],
817
+ use_existing = options['render']['use_existing'],
818
+ dont_modify = not options['render']['enable_rendering'],
819
+ vmd_executable = options['render']['vmd']['executable'],
820
+ tachyon_executable = options['render']['vmd']['tachyon'],
821
+ vmd_logging = options['logging']['render_logging'],
822
+ scaling = options['render']['transition_dipole_moment']['electric_scaling'],
823
+ magnetic_scaling = options['render']['transition_dipole_moment']['magnetic_scaling'],
824
+ **kwargs
825
+ )
826
+