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.
- digichem/__init__.py +75 -0
- digichem/basis.py +116 -0
- digichem/config/README +3 -0
- digichem/config/__init__.py +5 -0
- digichem/config/base.py +321 -0
- digichem/config/locations.py +14 -0
- digichem/config/parse.py +90 -0
- digichem/config/util.py +117 -0
- digichem/data/README +4 -0
- digichem/data/batoms/COPYING +18 -0
- digichem/data/batoms/LICENSE +674 -0
- digichem/data/batoms/README +2 -0
- digichem/data/batoms/__init__.py +0 -0
- digichem/data/batoms/batoms-renderer.py +351 -0
- digichem/data/config/digichem.yaml +714 -0
- digichem/data/functionals.csv +15 -0
- digichem/data/solvents.csv +185 -0
- digichem/data/tachyon/COPYING.md +5 -0
- digichem/data/tachyon/LICENSE +30 -0
- digichem/data/tachyon/tachyon_LINUXAMD64 +0 -0
- digichem/data/vmd/common.tcl +468 -0
- digichem/data/vmd/generate_combined_orbital_images.tcl +70 -0
- digichem/data/vmd/generate_density_images.tcl +45 -0
- digichem/data/vmd/generate_dipole_images.tcl +68 -0
- digichem/data/vmd/generate_orbital_images.tcl +57 -0
- digichem/data/vmd/generate_spin_images.tcl +66 -0
- digichem/data/vmd/generate_structure_images.tcl +40 -0
- digichem/datas.py +14 -0
- digichem/exception/__init__.py +7 -0
- digichem/exception/base.py +133 -0
- digichem/exception/uncatchable.py +63 -0
- digichem/file/__init__.py +1 -0
- digichem/file/base.py +364 -0
- digichem/file/cube.py +284 -0
- digichem/file/fchk.py +94 -0
- digichem/file/prattle.py +277 -0
- digichem/file/types.py +97 -0
- digichem/image/__init__.py +6 -0
- digichem/image/base.py +113 -0
- digichem/image/excited_states.py +335 -0
- digichem/image/graph.py +293 -0
- digichem/image/orbitals.py +239 -0
- digichem/image/render.py +617 -0
- digichem/image/spectroscopy.py +797 -0
- digichem/image/structure.py +115 -0
- digichem/image/vmd.py +826 -0
- digichem/input/__init__.py +3 -0
- digichem/input/base.py +78 -0
- digichem/input/digichem_input.py +500 -0
- digichem/input/gaussian.py +140 -0
- digichem/log.py +179 -0
- digichem/memory.py +166 -0
- digichem/misc/__init__.py +4 -0
- digichem/misc/argparse.py +44 -0
- digichem/misc/base.py +61 -0
- digichem/misc/io.py +239 -0
- digichem/misc/layered_dict.py +285 -0
- digichem/misc/text.py +139 -0
- digichem/misc/time.py +73 -0
- digichem/parse/__init__.py +13 -0
- digichem/parse/base.py +220 -0
- digichem/parse/cclib.py +138 -0
- digichem/parse/dump.py +253 -0
- digichem/parse/gaussian.py +130 -0
- digichem/parse/orca.py +96 -0
- digichem/parse/turbomole.py +201 -0
- digichem/parse/util.py +523 -0
- digichem/result/__init__.py +6 -0
- digichem/result/alignment/AA.py +114 -0
- digichem/result/alignment/AAA.py +61 -0
- digichem/result/alignment/FAP.py +148 -0
- digichem/result/alignment/__init__.py +3 -0
- digichem/result/alignment/base.py +310 -0
- digichem/result/angle.py +153 -0
- digichem/result/atom.py +742 -0
- digichem/result/base.py +258 -0
- digichem/result/dipole_moment.py +332 -0
- digichem/result/emission.py +402 -0
- digichem/result/energy.py +323 -0
- digichem/result/excited_state.py +821 -0
- digichem/result/ground_state.py +94 -0
- digichem/result/metadata.py +644 -0
- digichem/result/multi.py +98 -0
- digichem/result/nmr.py +1086 -0
- digichem/result/orbital.py +647 -0
- digichem/result/result.py +244 -0
- digichem/result/soc.py +272 -0
- digichem/result/spectroscopy.py +514 -0
- digichem/result/tdm.py +267 -0
- digichem/result/vibration.py +167 -0
- digichem/test/__init__.py +6 -0
- digichem/test/conftest.py +4 -0
- digichem/test/test_basis.py +71 -0
- digichem/test/test_calculate.py +30 -0
- digichem/test/test_config.py +78 -0
- digichem/test/test_cube.py +369 -0
- digichem/test/test_exception.py +16 -0
- digichem/test/test_file.py +104 -0
- digichem/test/test_image.py +337 -0
- digichem/test/test_input.py +64 -0
- digichem/test/test_parsing.py +79 -0
- digichem/test/test_prattle.py +36 -0
- digichem/test/test_result.py +489 -0
- digichem/test/test_translate.py +112 -0
- digichem/test/util.py +207 -0
- digichem/translate.py +591 -0
- digichem_core-6.0.0rc1.dist-info/METADATA +96 -0
- digichem_core-6.0.0rc1.dist-info/RECORD +111 -0
- digichem_core-6.0.0rc1.dist-info/WHEEL +4 -0
- digichem_core-6.0.0rc1.dist-info/licenses/COPYING.md +10 -0
- digichem_core-6.0.0rc1.dist-info/licenses/LICENSE +11 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from matplotlib import ticker
|
|
2
|
+
import statistics
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
from digichem.exception.base import Result_unavailable_error
|
|
6
|
+
from digichem.image.graph import OneD_graph_image_maker
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Orbital_diagram_maker(OneD_graph_image_maker):
|
|
10
|
+
"""
|
|
11
|
+
Class for making orbital energy diagrams.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, output, orbitals, full_axis_lines = False, y_limits = "auto", limits_fallback = True, **kwargs):
|
|
15
|
+
"""
|
|
16
|
+
Constructor for graph makers.
|
|
17
|
+
|
|
18
|
+
:param output: A path to an output file to write to. The extension of this path is used to determine the format of the file (eg: png, jpeg).
|
|
19
|
+
:param orbitals: A list of orbitals to plot.
|
|
20
|
+
:param full_axis_lines: If False, a boundary line is drawn for the x axis but nowhere else. If True, boundary lines are drawn on all 4 sides of the plotting area.
|
|
21
|
+
:param y_limits: String controlling how the y axis limits are set. Options are 'all' for showing all orbitals, 'auto' for standard auto scaling showing the HOMO, LUMO and 0 point, or 'center' for placing the HOMO-LUMO gap in the center of the y axis. Alternatively, limits_method can be a tuple of (y_min, y_max) which will be used directly as axis limits.
|
|
22
|
+
:param limits_fallback: If True, use simple_limits() if the chosen limits_method fails. If false, an exception is raised.
|
|
23
|
+
"""
|
|
24
|
+
super().__init__(output, **kwargs)
|
|
25
|
+
self.orbitals = orbitals
|
|
26
|
+
|
|
27
|
+
# Set our output width and height.
|
|
28
|
+
self.init_image_width = 4.1
|
|
29
|
+
# We use a wider graph if we are using unrestricted orbitals
|
|
30
|
+
if self.orbitals.spin_type != "none":
|
|
31
|
+
self.init_image_width = 4.9
|
|
32
|
+
self.init_image_height = 5.6
|
|
33
|
+
self.output_dpi = 130
|
|
34
|
+
|
|
35
|
+
# Axes label (no x label here).
|
|
36
|
+
self.y_label = 'Energy /eV'
|
|
37
|
+
# Y-axis scaling.
|
|
38
|
+
self.inch_per_y = 0.62
|
|
39
|
+
|
|
40
|
+
# Save options.
|
|
41
|
+
self.full_axis_lines = full_axis_lines
|
|
42
|
+
self.limits_method = y_limits
|
|
43
|
+
self.limits_fallback = limits_fallback
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_options(self, output, *, orbitals, options, **kwargs):
|
|
47
|
+
"""
|
|
48
|
+
Constructor that takes a dictionary of config like options.
|
|
49
|
+
"""
|
|
50
|
+
return self(
|
|
51
|
+
output,
|
|
52
|
+
orbitals = orbitals,
|
|
53
|
+
full_axis_lines = options['orbital_diagram']['full_axis_lines'],
|
|
54
|
+
y_limits = options['orbital_diagram']['y_limits'],
|
|
55
|
+
enable_rendering = options['orbital_diagram']['enable_rendering'],
|
|
56
|
+
**kwargs
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def plot_lines(self):
|
|
60
|
+
"""
|
|
61
|
+
Plot the lines that make up the body of the graph.
|
|
62
|
+
|
|
63
|
+
This method is called automatically as part of the make() method.
|
|
64
|
+
:return: Nothing.
|
|
65
|
+
"""
|
|
66
|
+
# We'll plot occupied and virtual orbitals separately, with different line styles.
|
|
67
|
+
occupied = [mo.energy for mo in self.orbitals if mo.HOMO_difference <= 0]
|
|
68
|
+
virtual = [mo.energy for mo in self.orbitals if mo.HOMO_difference > 0]
|
|
69
|
+
|
|
70
|
+
# We can use an event plot which is good for 1D graphs like this.
|
|
71
|
+
self.axes.eventplot(occupied, linestyles = "solid", **self.plot_options)
|
|
72
|
+
self.axes.eventplot(virtual, linestyles = "dashed", **self.plot_options)
|
|
73
|
+
|
|
74
|
+
def plot_labels(self):
|
|
75
|
+
"""
|
|
76
|
+
Plot the labels on our graph that identify the HOMO, LUMO and energy gap.
|
|
77
|
+
|
|
78
|
+
This method is called automatically as part of the make() method.
|
|
79
|
+
:return: Nothing.
|
|
80
|
+
"""
|
|
81
|
+
# First work our what our x coord is (this is the same for all points).
|
|
82
|
+
x_pos = self.plot_options["lineoffsets"]
|
|
83
|
+
start_x_pos = self.plot_options["lineoffsets"] - self.plot_options["linelengths"] /2
|
|
84
|
+
|
|
85
|
+
# Try just plotting labels for HOMO and LUMO (because we're pretty sure there should be space).
|
|
86
|
+
try:
|
|
87
|
+
HOMO = self.orbitals.get_orbital(HOMO_difference = 0)
|
|
88
|
+
except Exception:
|
|
89
|
+
# Couldn't get HOMO.
|
|
90
|
+
HOMO = None
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
LUMO = self.orbitals.get_orbital(HOMO_difference = 1)
|
|
94
|
+
except Exception:
|
|
95
|
+
# Couldn't get LUMO.
|
|
96
|
+
LUMO = None
|
|
97
|
+
|
|
98
|
+
if HOMO is not None:
|
|
99
|
+
# HOMO text goes above the line.
|
|
100
|
+
self.axes.annotate(
|
|
101
|
+
"{}: {:0.2f} eV".format(HOMO.label, HOMO.energy),
|
|
102
|
+
(x_pos, HOMO.energy),
|
|
103
|
+
textcoords = "offset pixels",
|
|
104
|
+
xytext = (0, 7),
|
|
105
|
+
horizontalalignment = "center"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if LUMO is not None:
|
|
109
|
+
# LUMO text goes below the line.
|
|
110
|
+
self.axes.annotate(
|
|
111
|
+
"{}: {:0.2f} eV".format(LUMO.label, LUMO.energy),
|
|
112
|
+
(x_pos, LUMO.energy),
|
|
113
|
+
textcoords = "offset pixels",
|
|
114
|
+
xytext = (0, -7),
|
|
115
|
+
verticalalignment = "top",
|
|
116
|
+
horizontalalignment = "center"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if HOMO is not None and LUMO is not None:
|
|
120
|
+
# Now plot our dE HOMO-LUMO arrow.
|
|
121
|
+
self.axes.annotate(
|
|
122
|
+
"",
|
|
123
|
+
xy = (start_x_pos, HOMO.energy),
|
|
124
|
+
xytext = (start_x_pos, LUMO.energy),
|
|
125
|
+
arrowprops=dict(arrowstyle="<->")
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# And then our dE HOMO-LUMO energy.
|
|
129
|
+
self.axes.annotate(
|
|
130
|
+
"ΔE: {:0.2f} eV".format(LUMO.energy - HOMO.energy),
|
|
131
|
+
(x_pos, statistics.median([HOMO.energy, LUMO.energy])),
|
|
132
|
+
verticalalignment = "center",
|
|
133
|
+
horizontalalignment = "center"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def all_limits(self):
|
|
137
|
+
"""
|
|
138
|
+
Limit the Y axis so all orbitals are visible.
|
|
139
|
+
"""
|
|
140
|
+
self.axes.autoscale(enable = True, axis = 'y')
|
|
141
|
+
|
|
142
|
+
def center_limits(self):
|
|
143
|
+
"""
|
|
144
|
+
Limit the Y axis so the HOMO and LUMO are in the center of the diagram, and the 0 energy point is visible.
|
|
145
|
+
|
|
146
|
+
:raises Result_unavailable_error: If either the HOMO or LUMO is not available.
|
|
147
|
+
"""
|
|
148
|
+
# Get our FMOs.
|
|
149
|
+
HOMO = self.orbitals.get_orbital(HOMO_difference = 0)
|
|
150
|
+
LUMO = self.orbitals.get_orbital(HOMO_difference = 1)
|
|
151
|
+
|
|
152
|
+
# Calculate the midpoint between the FMOs.
|
|
153
|
+
midpoint = HOMO.energy - ((HOMO.energy - LUMO.energy) /2)
|
|
154
|
+
|
|
155
|
+
# Work out y_max, which is set depending on the position of the LUMO.
|
|
156
|
+
y_max = max(0, math.ceil(LUMO.energy +1.1))
|
|
157
|
+
|
|
158
|
+
# Y min is then set so that midpoint is indeed in the midpoint.
|
|
159
|
+
y_min = midpoint - (y_max - midpoint)
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
y_min = round(y_min)
|
|
163
|
+
|
|
164
|
+
# Set our axis limits.
|
|
165
|
+
self.axes.set_ylim(y_min, y_max)
|
|
166
|
+
|
|
167
|
+
def standard_limits(self):
|
|
168
|
+
"""
|
|
169
|
+
Limit the Y axis so that the HOMO, LUMO and 0 energy point are all visible.
|
|
170
|
+
|
|
171
|
+
:raises Result_unavailable_error: If either the HOMO or LUMO is not available.
|
|
172
|
+
"""
|
|
173
|
+
# Get our FMOs.
|
|
174
|
+
HOMO = self.orbitals.get_orbital(HOMO_difference = 0)
|
|
175
|
+
LUMO = self.orbitals.get_orbital(HOMO_difference = 1)
|
|
176
|
+
|
|
177
|
+
# Add on a bit to both HOMO and LUMO so we expand if the orbital is close to the end of the axis.
|
|
178
|
+
y_min = math.floor(HOMO.energy -1.1) if HOMO is not None else -8
|
|
179
|
+
y_max = max(0, math.ceil(LUMO.energy +1.1)) if LUMO is not None else 0
|
|
180
|
+
self.axes.set_ylim(y_min, y_max)
|
|
181
|
+
|
|
182
|
+
def simple_limits(self, y_min = -8, y_max = 0):
|
|
183
|
+
"""
|
|
184
|
+
Limit the Y axis between between two points.
|
|
185
|
+
|
|
186
|
+
:param y_min: The start of the y axis.
|
|
187
|
+
:param y_max: The end of the y axis.
|
|
188
|
+
"""
|
|
189
|
+
self.axes.set_ylim(y_min, y_max)
|
|
190
|
+
|
|
191
|
+
def adjust_axes(self):
|
|
192
|
+
"""
|
|
193
|
+
Adjust the axes of our graph.
|
|
194
|
+
|
|
195
|
+
This method is called automatically as part of the make() method.
|
|
196
|
+
:return: Nothing.
|
|
197
|
+
"""
|
|
198
|
+
super().adjust_axes()
|
|
199
|
+
|
|
200
|
+
# Orbital diagrams only have the left axis spine to enable easy stacking.
|
|
201
|
+
if not self.full_axis_lines:
|
|
202
|
+
self.axes.spines['top'].set_visible(False)
|
|
203
|
+
self.axes.spines['right'].set_visible(False)
|
|
204
|
+
self.axes.spines['bottom'].set_visible(False)
|
|
205
|
+
|
|
206
|
+
# Adjust axis
|
|
207
|
+
# Our x axis is always the same.
|
|
208
|
+
self.axes.set_xlim(0, 1.5)
|
|
209
|
+
|
|
210
|
+
# Our Y axis changes with our given data. If we a HOMO and/or LUMO, we'll stretch appropriately to fit them in.
|
|
211
|
+
# If We're missing one of the FMO (unlikely, but possible), we'll resort to semi-random defaults (for now).
|
|
212
|
+
try:
|
|
213
|
+
if self.limits_method == "all":
|
|
214
|
+
self.all_limits()
|
|
215
|
+
elif self.limits_method == "center":
|
|
216
|
+
self.center_limits()
|
|
217
|
+
elif self.limits_method == "auto":
|
|
218
|
+
self.standard_limits()
|
|
219
|
+
elif isinstance(self.limits_method, tuple) or isinstance(self.limits_method, list):
|
|
220
|
+
self.simple_limits(self.limits_method[0], self.limits_method[1])
|
|
221
|
+
else:
|
|
222
|
+
raise ValueError("Unknown limits method '{}'".format(self.limits_method))
|
|
223
|
+
except Result_unavailable_error:
|
|
224
|
+
# HOMO-LUMO not available.
|
|
225
|
+
if self.limits_fallback:
|
|
226
|
+
self.simple_limits()
|
|
227
|
+
else:
|
|
228
|
+
raise
|
|
229
|
+
|
|
230
|
+
# Change spacing of tick markers.
|
|
231
|
+
# No tick marks for x axis.
|
|
232
|
+
self.axes.set_xticks([])
|
|
233
|
+
self.axes.yaxis.set_major_locator(ticker.MultipleLocator(1))
|
|
234
|
+
|
|
235
|
+
# Our y axis scales with data.
|
|
236
|
+
self.figure.tight_layout()
|
|
237
|
+
self.constant_scale(1, self.inch_per_y)
|
|
238
|
+
|
|
239
|
+
|