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,258 @@
1
+ import scipy.constants
2
+ import warnings
3
+ import itertools
4
+ import math
5
+
6
+ from digichem.exception.base import Result_unavailable_error
7
+
8
+
9
+ class Result_object():
10
+ """
11
+ Top level class for objects that are designed to hold calculation results.
12
+ """
13
+
14
+ # A warning issued when attempting to merge non-equivalent objects
15
+ MERGE_WARNING = "Attempting to merge lists of results that are not identical; non-equivalent results will be ignored"
16
+
17
+ def safe_get(self, *attr_names, default = None):
18
+ """
19
+ Access an attribute of this object, returning default if the attribute is None or is not available (raises Result_unavailable_error).
20
+ """
21
+ # DO YOU LIKE MY B-TEAM SPAGHETTI CODE!?
22
+
23
+ # Get the first name, which we'll be getattr'ing.
24
+ first_name = attr_names[0]
25
+ remaining_names = attr_names[1:]
26
+
27
+ # Get.
28
+ try:
29
+ attr = getattr(self, first_name)
30
+
31
+ if attr is not None and len(remaining_names) > 0:
32
+ if isinstance(attr, Result_object):
33
+ # We have remaining names to resolve, call attr's safe_get().
34
+ attr = attr.safe_get(*remaining_names, default = default)
35
+ else:
36
+ # We have remaining names to resolve, but our attr is not a Result_set (so we can't use safe_get()).
37
+ for remaining_name in remaining_names:
38
+ attr = getattr(attr, remaining_name)
39
+ except Result_unavailable_error:
40
+ return default
41
+
42
+ # And done.
43
+ return attr
44
+
45
+ @classmethod
46
+ def wavelength_to_energy(self, wavelength):
47
+ """
48
+ Convert a wavelength (in nm) to energy (in eV).
49
+ """
50
+ # e = (c * h) / λ
51
+ return ((scipy.constants.speed_of_light * scipy.constants.Planck) / (wavelength / 1000000000)) / scipy.constants.eV
52
+
53
+ @classmethod
54
+ def energy_to_wavelength(self, energy):
55
+ """
56
+ Convert an energy (in eV) to wavelength (in nm).
57
+ """
58
+ # λ = (c * h) / e
59
+ return ((scipy.constants.speed_of_light * scipy.constants.Planck) / (energy * scipy.constants.eV)) * 1000000000
60
+
61
+ @classmethod
62
+ def wavenumbers_to_energy(self, wavenumbers):
63
+ """
64
+ Convert wavenumbers (in cm-1) to energy (in eV).
65
+ """
66
+ return self.wavelength_to_energy((1 / wavenumbers) * 10000000)
67
+
68
+ @classmethod
69
+ def merged_attr(self, name, objects):
70
+ """
71
+ Merge a number of str like attributes.
72
+
73
+ :param name: The name of the attribute to merge.
74
+ :param objects: A list of objects to merge from.
75
+ :return: A single string containing the merged attributes.
76
+ """
77
+ attributes = list(dict.fromkeys([getattr(obj, name) for obj in objects]))
78
+ attributes = [attribute for attribute in attributes if attribute is not None]
79
+ return ", ".join(attributes) if len(attributes) > 0 else None
80
+
81
+
82
+ @classmethod
83
+ def merge(self, *multiple_objects):
84
+ """
85
+ Merge multiple Result objects of the same type into a single Result object.
86
+
87
+ Note that this default implementation is intended for objects that cannot actually be merged;
88
+ truly mergable objects should implement their own merge() method.
89
+ """
90
+ # Check all items are the same.
91
+ if not all(obj == multiple_objects[0] for obj in multiple_objects if obj is not None):
92
+ warnings.warn(self.MERGE_WARNING)
93
+
94
+ return multiple_objects[0]
95
+
96
+ def generate_for_dump(self):
97
+ """
98
+ Method used to get a dictionary used to generate on-demand values for dumping.
99
+
100
+ This functionality is useful for hiding expense properties from the normal dump process, while still exposing them when specifically requested.
101
+
102
+ Each key in the returned dicrt is the name of a dumpable item, each value is a function to call with digichem_options as its only param.
103
+ """
104
+ return {}
105
+
106
+ def dump(self, digichem_options):
107
+ """
108
+ Abstract function that is called to dump the value of the result object to a primitive type, suitable for serializing with yaml.
109
+
110
+ For real objects, this will normally be a dict with (at least) the following items:
111
+ - 'value': The value of the result (for example, 34.5.
112
+ - 'units': The units of the result (for example, k m^-s).
113
+ """
114
+ raise NotImplementedError("Implement in subclass")
115
+
116
+
117
+ class Floatable_mixin():
118
+ """
119
+ A mixin class for result objects that have an unambigious numerical representation.
120
+
121
+ This mixin class expects the implementing class to define __float__().
122
+ """
123
+
124
+ def __lt__(self, other):
125
+ """
126
+ Is this class less than another?
127
+ """
128
+ return float(self) < float(other)
129
+
130
+ def __le__(self, other):
131
+ """
132
+ Is this class equal or less than another?
133
+ """
134
+ return math.isclose(float(self), float(other)) or float(self) < float(other)
135
+
136
+ def __eq__(self, other):
137
+ """
138
+ Is this class equal to another?
139
+ """
140
+ #return float(self) == float(other)
141
+ return math.isclose(float(self), float(other))
142
+
143
+ def __ge__(self, other):
144
+ """
145
+ Is this class greater or equal to another?
146
+ """
147
+ return math.isclose(float(self), float(other)) or float(self) > float(other)
148
+
149
+ def __gt__(self, other):
150
+ """
151
+ Is this class greater than another?
152
+ """
153
+ return float(self) > float(other)
154
+
155
+
156
+ class Unmergeable_container_mixin():
157
+ """
158
+ Mixin for Result_containers that cannot be merged.
159
+
160
+ Classes which inherit from this mixin should also set a class attribute with the name MERGE_WARNING, which should be a message to be displayed when attempting to merge this container with another.
161
+ """
162
+
163
+ @classmethod
164
+ def false_merge(self, *multiple_lists, **kwargs):
165
+ """
166
+ 'Merge' a number of containers of the same type.
167
+
168
+ As Unmergeable_container objects cannot be merged, this method will instead check that all given containers are equivalent, issuing warnings if this is not the case.
169
+
170
+ :param multiple_lists: A number of container objects to 'merge'.
171
+ :param kwargs: Key-word arguments to pass to the returned container.
172
+ """
173
+ items = self(multiple_lists[0], **kwargs)
174
+
175
+ # Check all other lists are the same.
176
+ for item_list in multiple_lists[1:]:
177
+ # If this list has atoms and our current doesn't, use this as our base list.
178
+ if len(item_list) > 0 and len(items) == 0:
179
+ items = item_list
180
+ else:
181
+ for index, item in enumerate(item_list):
182
+ try:
183
+ other_item = items[index]
184
+ if not self.are_items_equal(item, other_item):
185
+ warnings.warn(self.MERGE_WARNING)
186
+ except IndexError:
187
+ warnings.warn(self.MERGE_WARNING)
188
+
189
+ # Return the 'merged' list.
190
+ return items
191
+
192
+ @classmethod
193
+ def are_items_equal(self, item, other_item):
194
+ """
195
+ A method which determines whether two items are the same for the purposes of merging.
196
+
197
+ This default implementation simply checks for equivalence between the two items.
198
+ """
199
+ return item == other_item
200
+
201
+ class Result_container(list, Result_object):
202
+ """
203
+ Top level class for Result_objects that hold a list of results.
204
+ """
205
+ def __init__(self, *args, **kwargs):
206
+ list.__init__(self, *args, **kwargs)
207
+ Result_object.__init__(self)
208
+
209
+ def assign_levels(self):
210
+ """
211
+ (Re)assign total levels of the objects in this list.
212
+
213
+ The contents of this list will be modified in place.
214
+ """
215
+ total_level = 0
216
+
217
+ for state in self:
218
+ # Increment total level.
219
+ total_level += 1
220
+
221
+ # Set level
222
+ state.level = total_level
223
+
224
+ @classmethod
225
+ def merge_default(self, *multiple_lists, **kwargs):
226
+ """
227
+ The default implementation of the merge method.
228
+ """
229
+ # First, get a new list containing all objects.
230
+ merged_list = self(itertools.chain(*multiple_lists), **kwargs)
231
+
232
+ # Now sort.
233
+ # This works because the contained objects are all Floatables (see Floatable_mixin) and therefore have well-defined sort mechanics,
234
+ # or otherwise define their own __eq__ and related methods.
235
+ merged_list.sort()
236
+
237
+ # Next, we need to reassign levels of the contained objects.
238
+ merged_list.assign_levels()
239
+
240
+ # All done.
241
+ return merged_list
242
+
243
+ @classmethod
244
+ def merge(self, *multiple_lists, **kwargs):
245
+ """
246
+ Merge multiple lists of of the same type into a single, ordered list.
247
+
248
+ Note that this method will return a new list object, but will modify the contained objects (eg, object.level) in place.
249
+ Inheriting classes may safely override this method.
250
+ """
251
+ if hasattr(self, "false_merge"):
252
+ return self.false_merge(*multiple_lists, **kwargs)
253
+ else:
254
+ return self.merge_default(*multiple_lists, **kwargs)
255
+
256
+ def dump(self, digichem_options):
257
+ return [item.dump(digichem_options) for item in self]
258
+
@@ -0,0 +1,332 @@
1
+ # General imports
2
+ import math
3
+
4
+ # Digichem imports
5
+ from digichem.result import Result_object
6
+ from digichem.result.angle import Angle
7
+
8
+
9
+ class Dipole_moment_ABC(Result_object):
10
+ """
11
+ ABC that represents a dipole moment.
12
+
13
+ See inheriting classes for concrete implementations (depending on whether they are permanent or transition, and electric or magnetic).
14
+ """
15
+
16
+ # A warning issued when attempting to merge non-equivalent objects
17
+ MERGE_WARNING = "Attempting to merge list of DPMs that are not identical; non-equivalent DPMs will be ignored"
18
+
19
+ @property
20
+ def magnitude(self):
21
+ """
22
+ The dipole moment total (root-sum-square of the dipole moment vector).
23
+
24
+ :deprecated: 'magnitude' is a somewhat misleading name for this attribute, use 'total' instead.
25
+ """
26
+ return self.total
27
+
28
+ @property
29
+ def total(self):
30
+ """
31
+ The dipole moment total (root-sum-square of the dipole moment vector).
32
+ """
33
+ return math.sqrt( self.vector_coords[0] ** 2 + self.vector_coords[1] ** 2 + self.vector_coords[2] ** 2)
34
+
35
+ def __init__(self, origin_coords, vector_coords, atoms = None):
36
+ """
37
+ Constructor for Dipole_moment objects.
38
+
39
+ :param origin_coords: Tuple of (x, y, z) coordinates of the origin of this dipole moment.
40
+ :param vector_coords: Tuple of (x, y, z) coordinates of this dipole moment.
41
+ :param atoms: Optional atoms atom list object which is used to calculate addition data about this dipole moment.
42
+ """
43
+ super().__init__()
44
+ # The start of our vector, normally (0,0,0).
45
+ self.origin_coords = origin_coords
46
+ # The end of our vector.
47
+ self.vector_coords = vector_coords
48
+
49
+ # Save our atoms object.
50
+ self.atoms = atoms if atoms is not None else []
51
+
52
+ # Save a name describing which dipole we are (permanent vs transition etc).
53
+ self.dipole_type = "permanent"
54
+
55
+ @property
56
+ def name(self):
57
+ """
58
+ Name that describes this dipole moment.
59
+ """
60
+ return "PDM"
61
+
62
+ @property
63
+ def origin_coords(self):
64
+ """
65
+ The origin coords of this vector as a tuple of (x, y, z). origin_coords is automatically realigned by the atoms alignment object of this dipole, use _origin_coords if you do not want this behaviour.
66
+ """
67
+ if len(self.atoms) > 0:
68
+ return self.atoms.apply_transformation(self._origin_coords)
69
+ else:
70
+ return self._origin_coords
71
+
72
+ @origin_coords.setter
73
+ def origin_coords(self, value):
74
+ """
75
+ Set the origin coords of this dipole.
76
+ """
77
+ self._origin_coords = value
78
+
79
+ @property
80
+ def vector_coords(self):
81
+ """
82
+ The ending coords of this vector as a tuple of (x, y, z). vector_coords is automatically realigned by the atoms alignment object of this dipole, use _vector_coords if you do not want this behaviour.
83
+ """
84
+ if len(self.atoms) > 0:
85
+ return self.atoms.apply_transformation(self._vector_coords)
86
+ else:
87
+ return self._vector_coords
88
+
89
+ @vector_coords.setter
90
+ def vector_coords(self, value):
91
+ """
92
+ Set the ending coords of this dipole.
93
+ """
94
+ self._vector_coords = value
95
+
96
+ def __float__(self):
97
+ """
98
+ Floatify this Dipole_moment.
99
+ """
100
+ return self.magnitude
101
+
102
+ def __eq__(self, other):
103
+ """
104
+ Is this dipole moment equal to another?
105
+
106
+ A dipole moment is considered equivalent only if all coordinates match.
107
+ """
108
+ return self.origin_coords == other.origin_coords and self.vector_coords == other.vector_coords
109
+
110
+ @property
111
+ def X_axis_angle(self):
112
+ """
113
+ The angle between this dipole moment and the X axis of the atom set of the molecule.
114
+ """
115
+ if self.total == 0:
116
+ return Angle(0)
117
+ elif len(self.atoms) > 0:
118
+ return Angle(math.fabs(self.atoms.get_X_axis_angle(self.origin_coords, self.vector_coords)), "rad")
119
+ else:
120
+ return None
121
+
122
+ @property
123
+ def XY_plane_angle(self):
124
+ """
125
+ The angle between this dipole moment and the XY plane of the atom set of the molecule.
126
+ """
127
+ if self.total == 0:
128
+ return Angle(0)
129
+ elif len(self.atoms) > 0:
130
+ return Angle(math.fabs(self.atoms.get_XY_plane_angle(self.origin_coords, self.vector_coords)), "rad")
131
+ else:
132
+ return None
133
+
134
+ @property
135
+ def gaussian_cgs_vector(self):
136
+ """
137
+ The vector of this dipole moment in Gaussian-cgs units.
138
+
139
+ Note that the sign of the vector will be reversed in Gaussian-cgs units.
140
+ See J. Phys. Chem. Lett. 2021, 21, 686-695.
141
+ """
142
+ return tuple(self.to_gaussian_cgs(coord) for coord in self.vector_coords)
143
+
144
+ @property
145
+ def gaussian_cgs(self):
146
+ """
147
+ The total of this dipole moment in Gaussian-cgs units.
148
+
149
+ Note that the sign of the vector will be reversed in Gaussian-cgs units.
150
+ See J. Phys. Chem. Lett. 2021, 21, 686-695.
151
+ """
152
+ cgs_vector = self.gaussian_cgs_vector
153
+ return math.sqrt( cgs_vector[0] **2 + cgs_vector[1] **2 + cgs_vector[2] **2 )
154
+
155
+ def angle(self, other, cgs = True):
156
+ """
157
+ Find the angle between this dipole moment and another.
158
+
159
+ :param cgs: Whether to find the angle in Gaussian-cgs units or standard units (the direction of magnetic dipole moments is reversed in the former).
160
+ """
161
+ return Angle(math.acos(self.cos_angle(other, cgs)))
162
+
163
+ def cos_angle(self, other, cgs = True):
164
+ """
165
+ Find the cosine of the angle between this dipole moment and another.
166
+
167
+ :param cgs: Whether to find the angle in Gaussian-cgs units or standard units (the direction of magnetic dipole moments is reversed in the former).
168
+ """
169
+ if cgs:
170
+ vector_a = self.gaussian_cgs_vector
171
+ vector_b = other.gaussian_cgs_vector
172
+
173
+ else:
174
+ vector_a = self.vector_coords
175
+ vector_b = other.vector_coords
176
+
177
+ vector_a_total = math.sqrt( vector_a[0] **2 + vector_a[1] **2 + vector_a[2] **2 )
178
+ vector_b_total = math.sqrt( vector_b[0] **2 + vector_b[1] **2 + vector_b[2] **2 )
179
+
180
+ try:
181
+ return (vector_a[0] * vector_b[0] + vector_a[1] * vector_b[1] + vector_a[2] * vector_b[2]) / (vector_a_total * vector_b_total)
182
+ except (FloatingPointError, ZeroDivisionError):
183
+ return 0
184
+
185
+ def dump(self, digichem_options):
186
+ """
187
+ Get a representation of this result object in primitive format.
188
+ """
189
+ return {
190
+ "total": {
191
+ "value": self.total,
192
+ "units": self.units
193
+ },
194
+ "origin": {
195
+ "x": {
196
+ "value": float(self.origin_coords[0]),
197
+ "units": "Å",
198
+ },
199
+ "y": {
200
+ "value": float(self.origin_coords[1]),
201
+ "units": "Å",
202
+ },
203
+ "z": {
204
+ "value": float(self.origin_coords[2]),
205
+ "units": "Å",
206
+ }
207
+ },
208
+ "vector": {
209
+ "x": {
210
+ "value": float(self.vector_coords[0]),
211
+ "units": self.units
212
+ },
213
+ "y": {
214
+ "value": float(self.vector_coords[1]),
215
+ "units": self.units
216
+ },
217
+ "z": {
218
+ "value": float(self.vector_coords[2]),
219
+ "units": self.units
220
+ },
221
+ },
222
+ "x-angle": {
223
+ "value": self.X_axis_angle.angle,
224
+ "units": self.X_axis_angle.units
225
+ },
226
+ "xy-angle": {
227
+ "value": self.XY_plane_angle.angle,
228
+ "units": self.XY_plane_angle.units
229
+ }
230
+ }
231
+
232
+
233
+ class Electric_dipole_moment_mixin():
234
+ """
235
+ Mixin class for electric dipole moments.
236
+ """
237
+
238
+ @property
239
+ def units(self):
240
+ """
241
+ The units of this dipole moment object.
242
+ """
243
+ # Debye
244
+ return "D"
245
+
246
+ @property
247
+ def coulomb_meters(self):
248
+ """
249
+ The total of this dipole moment in Coulomb Meters.
250
+ """
251
+ return float(self) * 3.335640952e-30
252
+
253
+ @classmethod
254
+ def D_to_au(self, value):
255
+ """
256
+ Convert an electric dipole moment in D to a.u.
257
+ """
258
+ return value / 2.541746473
259
+
260
+ @classmethod
261
+ def to_gaussian_cgs(self, value):
262
+ """
263
+ Convert an electric dipole moment in D to Gaussian-cgs units.
264
+ See J. Phys. Chem. Lett. 2021, 21, 686-695.
265
+ """
266
+ # cgs = au * e * a0
267
+ return self.D_to_au(value) * 4.80320425e-10 * 5.29177210903e-9
268
+
269
+
270
+ class Magnetic_dipole_moment_mixin():
271
+ """
272
+ Mixin class for magnetic dipole moments.
273
+ """
274
+
275
+ @property
276
+ def units(self):
277
+ """
278
+ The units of this dipole moment object.
279
+ """
280
+ return "a.u."
281
+
282
+ @property
283
+ def bohr_magnetons(self):
284
+ """
285
+ The total of this magnetic dipole moment in bohr magnetons.
286
+ """
287
+ return self.total * 2
288
+
289
+ @property
290
+ def ampere_square_meters(self):
291
+ """
292
+ The total of this magnetic dipole moment in SI units.
293
+ """
294
+ return self.total * 1.8548e-23
295
+
296
+ @classmethod
297
+ def to_gaussian_cgs(self, value):
298
+ """
299
+ Convert a magnetic dipole moment in a.u. to Gaussian-cgs units.
300
+ See J. Phys. Chem. Lett. 2021, 21, 686-695.
301
+ """
302
+ return value * -9.2740100783e-21
303
+
304
+
305
+ class Dipole_moment(Dipole_moment_ABC, Electric_dipole_moment_mixin):
306
+ """
307
+ Class that represents the (permanent) dipole moment of a molecule.
308
+ """
309
+
310
+ @classmethod
311
+ def from_dump(self, data, result_set, options):
312
+ """
313
+ Get a list of instances of this class from its dumped representation.
314
+
315
+ :param data: The data to parse.
316
+ :param result_set: The partially constructed result set which is being populated.
317
+ """
318
+ return self((data['origin']['x']['value'], data['origin']['y']['value'], data['origin']['z']['value']), (data['vector']['x']['value'], data['vector']['y']['value'], data['vector']['z']['value']), atoms = result_set.atoms)
319
+
320
+ @classmethod
321
+ def from_parser(self, parser):
322
+ """
323
+ Construct a Dipole_moment object from an output file parser.
324
+
325
+ :param parser: An output file parser.
326
+ :result: A single Dipole_moment object, or None if no dipole information is available.
327
+ """
328
+ try:
329
+ return self(parser.data.moments[0], parser.data.moments[1], parser.results.atoms)
330
+ except AttributeError:
331
+ return None
332
+