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/__init__.py ADDED
@@ -0,0 +1,75 @@
1
+ """Computational chemistry management"""
2
+
3
+ from datetime import datetime
4
+ import sys
5
+
6
+ import digichem.log
7
+ from digichem.datas import get_resource
8
+
9
+ ####################
10
+ # Package metadata.#
11
+ ####################
12
+
13
+
14
+ # # Version information.
15
+ # major_version = 6
16
+ # minor_version = 0
17
+ # revision = 0
18
+ # prerelease = 1
19
+ # # Whether this is a development version.
20
+ # development = prerelease is not None
21
+ # # The full version number of this package.
22
+ # __version__ = "{}.{}.{}{}".format(major_version, minor_version, revision, "-pre.{}".format(prerelease) if development else "")
23
+ __version__ = "6.0.0-pre.1"
24
+ _v_parts = __version__.split("-")[0].split(".")
25
+ major_version = int(_v_parts[0])
26
+ minor_version = int(_v_parts[1])
27
+ revision = int(_v_parts[2])
28
+ if "-pre" in __version__:
29
+ development = True
30
+ prerelease = int(__version__.split(".")[3])
31
+ else:
32
+ development = False
33
+ prerelease = None
34
+
35
+
36
+ # Those who wrote this.
37
+ __author__ = [
38
+ "Oliver S. Lee"
39
+ ]
40
+
41
+ # Program date (when we were last updated). This is changed automatically.
42
+ _last_updated_string = "31/05/2024"
43
+ last_updated = datetime.strptime(_last_updated_string, "%d/%m/%Y")
44
+
45
+ # The sys attribute 'frozen' is our flag, '_MEIPASS' is the dir location.
46
+ # https://pyinstaller.readthedocs.io/en/stable/runtime-information.html#run-time-information
47
+ if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
48
+ frozen = True
49
+
50
+ # We are frozen, expand PATH to include the possibly bundled oprattle.
51
+ from pathlib import Path
52
+ import os
53
+ freeze_dir = Path(sys._MEIPASS)
54
+ if freeze_dir.name == "_internal":
55
+ freeze_dir = freeze_dir.parent
56
+
57
+ os.environ['PATH'] = os.environ.get("PATH", "") + ":" + str(freeze_dir)
58
+
59
+ else:
60
+ frozen = False
61
+
62
+ import rdkit.RDLogger
63
+ # WrapLogs() outputs rdkit logging to python's stderr (which might be redirected to an urwid widget).
64
+ # If/when rdkit is further intergrated into digichem, this call will likely be moved elsewhere.
65
+ #rdkit.Chem.rdchem.WrapLogs()
66
+ # Sadly the behaviour of WrapLogs() is a bit bizzare, although we do get redirection to our custom widgets etc,
67
+ # logs are also still dumped to screen...
68
+ # for now, disable logging...
69
+ rdkit.RDLogger.DisableLog('rdApp.*')
70
+
71
+ # Setup the logger
72
+ digichem.log.init_logger()
73
+
74
+ # At end to avoid circular imports.
75
+ import digichem.config
digichem/basis.py ADDED
@@ -0,0 +1,116 @@
1
+ import deepmerge
2
+
3
+ # Hidden imports.
4
+ # import basis_set_exchange.misc
5
+ # import basis_set_exchange.writers
6
+
7
+ class BSE_basis_set(dict):
8
+ """
9
+ A class for representing a (number of) basis sets fetched from the basis set exchange.
10
+ """
11
+
12
+ def __init__(self, *args, **kwargs):
13
+ """
14
+ Constructor for BSE_basis_set objects.
15
+
16
+ :param definition: A dictionary where each key is the name of a basis set and the value is the elements it applies to. The values 'all', '*', and None can be used to indicate the basis set applies to all atoms.
17
+ """
18
+ super().__init__( *args, **kwargs)
19
+
20
+ @classmethod
21
+ def is_filter_all(self, element_filter):
22
+ """
23
+ Determine whether a given element filter implicitly includes all elements.
24
+ """
25
+ return element_filter == "*" or element_filter == "all" or element_filter is None
26
+
27
+ def __str__(self):
28
+ return self.name
29
+
30
+ @property
31
+ def name(self):
32
+ """
33
+ Get a descriptive name of this basis set.
34
+ """
35
+ import basis_set_exchange.misc
36
+
37
+ names = []
38
+
39
+ for basis_set_name, basis_set_elements in self.items():
40
+ name = basis_set_name
41
+ if not self.is_filter_all(basis_set_elements):
42
+ name += " []".format(basis_set_exchange.misc.compact_elements(basis_set_exchange.misc.expand_elements(basis_set_elements)))
43
+
44
+ return ", ".join(names)
45
+
46
+ def has_ECPs(self, elements_filter = None):
47
+ """
48
+ Determine whether this basis set would provide effective core potentials (ECPs) when applied to a list of elements.
49
+
50
+ :param elements_filter: The elements to check against.
51
+ """
52
+ basis_sets = self.to_dict(elements_filter)
53
+
54
+ return 'scalar_ecp' in basis_sets['function_types']
55
+
56
+ def to_dict(self, elements_filter = None):
57
+ """
58
+ Convert the basis set information represented by this object to a basis set dict.
59
+
60
+ :param elements_filter: A list/string of elements to filter by. Only elements given here will be printed in the final format. Each item can be an int or str representing a single element (1, '1', 'H' etc), or a range of elements ('1-5' etc).
61
+ """
62
+ import basis_set_exchange.misc
63
+
64
+ if elements_filter is not None and not isinstance(elements_filter, str):
65
+ elements_filter = ",".join(elements_filter)
66
+
67
+ if elements_filter is not None:
68
+ elements_filter = basis_set_exchange.misc.expand_elements(elements_filter)
69
+
70
+ basis_sets = {}
71
+
72
+ # First, convert each definition to to a basis_set_exchange dict
73
+ for basis_set_name, basis_set_elements in self.items():
74
+ # Only elements that are actually present will have a basis set recorded.
75
+ # The values of 'all', '*' and None are also accepted as meaning all atoms.
76
+ if not self.is_filter_all(basis_set_elements):
77
+ basis_set_elements = basis_set_exchange.misc.expand_elements(basis_set_elements)
78
+ if elements_filter is not None:
79
+ elements = list(set(basis_set_elements).intersection(elements_filter))
80
+
81
+ else:
82
+ elements = basis_set_elements
83
+
84
+ else:
85
+ # We are using all elements from this basis set.
86
+ elements = elements_filter
87
+
88
+ if elements is None or len(elements) > 0:
89
+ # Get the basis set.
90
+ basis_set = basis_set_exchange.get_basis(basis_set_name, elements)
91
+
92
+ # Merge it with our total basis set.
93
+ # TODO: Check this is actually safe?
94
+ deepmerge.always_merger.merge(basis_sets, basis_set)
95
+
96
+ return basis_sets
97
+
98
+ def to_format(self, fmt = None, elements_filter = None):
99
+ """
100
+ Convert the basis set information represented by this object to a basis set format (for example, one suitable for a CC program).
101
+
102
+ :param fmt: The format to write to, see basis_set_exchange.get_formats().
103
+ :param elements_filter: A list of elements to filter by. Only elements given here will be printed in the final format. Each item can be an int or str representing a single element (1, '1', 'H' etc), or a range of elements ('1-5' etc).
104
+ :returns: The formatted basis set.
105
+ """
106
+ import basis_set_exchange.writers
107
+
108
+ basis_sets = self.to_dict(elements_filter)
109
+
110
+ # Output the merged basis set.
111
+ if fmt is not None:
112
+ return basis_set_exchange.writers.write_formatted_basis_str(basis_sets, fmt = fmt)
113
+
114
+ else:
115
+ return basis_sets
116
+
digichem/config/README ADDED
@@ -0,0 +1,3 @@
1
+ This directory (digichem/config) contains python code for parsing digichem config files etc.
2
+
3
+ If you are looking for the config files themselves, these are in digichem/data/config.
@@ -0,0 +1,5 @@
1
+ # from .base import Config
2
+ from .base import Digichem_options, Auto_type
3
+ from .locations import master_config_path, user_config_location, system_config_location
4
+ from .parse import Config_file_parser
5
+ from .util import get_config
@@ -0,0 +1,321 @@
1
+ import yaml
2
+ from deepmerge import Merger
3
+ from pathlib import Path
4
+
5
+ from configurables import Configurable, Options, Option
6
+ from configurables.option import Nested_dict_type
7
+
8
+ from digichem.config.locations import user_config_location
9
+ from digichem.misc.io import atomic_write
10
+ from digichem.translate import Cube_grid_points
11
+
12
+
13
+ class Auto_type():
14
+ """
15
+ A class that when called, converts a string into a more appropriate type automatically.
16
+
17
+ The rules for deciding which type to convert to are the same as for parsing yaml using pyyaml, as that is the module that is relied on for conversion.
18
+ """
19
+
20
+ def __new__(cls, value):
21
+ return yaml.safe_load(value)
22
+
23
+
24
+ class Digichem_options(Configurable):
25
+ """
26
+ Class for holding main digichem options from various sources.
27
+ """
28
+
29
+ alignment = Option(help = "The default alignment method to use, MIN: minimal, FAP: furthest atom pair, AA: average angle, AAA: advanced average angle.", choices = ["MIN", "FAP", "AA", "AAA"], default = "MIN")
30
+ angle_units = Option(help = "The default angle units to use, deg: degrees, rad: radians.", choices = ["deg", "rad"], default = "deg")
31
+
32
+ external = Options(
33
+ help = "Options specifying paths to various external programs that digichem may use. If no path is given, then these programs will simply be executed by name (so relying on OS path resolution to find the necessary executables, which is normally fine.)",
34
+ formchk = Option(help = "Gaussian's formchk utility https://gaussian.com/formchk/", default = "formchk"),
35
+ cubegen = Option(help = "Gaussian's cubegen utility https://gaussian.com/cubegen/", default = "cubegen"),
36
+ )
37
+
38
+ skeletal_image = Options(
39
+ help = "Options for controlling the rendering of 2D skeletal images.",
40
+ resolution = Options(help = "The resolution for rendered images",
41
+ absolute = Option(help = "The output resolution (x and y) in pixels", type = int, default = None),
42
+ relative = Option(help = "The output resolution (x and y) in multiples of the length of the molecule", type = int, default = 100)
43
+ )
44
+ )
45
+
46
+ logging = Options(
47
+ help = "Options relating to output of error messages. Note that the final logging level is determined by combining both 'log_level' and 'verbose', so a 'log_level' of 'OFF' and 'verbose' of '2' is equal to 'ERROR'.",
48
+ log_level = Option(help = "The level of messages to output, one of OFF (no logging at all), CRITICAL (fewest messages), ERROR, WARNING, INFO or DEBUG (most messages)", choices = ["OFF", "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], default = "INFO"),
49
+ verbose = Option(help = "Increase the verbosity of the program by this amount. Each integer increase of verbosity will increase 'log_level' by 1 degree.", type = int, default = 0),
50
+ render_logging = Option(help = "Whether to print output from render engines.", type = bool, default = False)
51
+ )
52
+
53
+ render = Options(
54
+ help = "Options for controlling the appearance of 3D molecule images.",
55
+ enable_rendering = Option(help = "Set to False to disable image rendering.", type = bool, default = True),
56
+ engine = Option(help = "The rendering engine to use", choices = ["vmd", "batoms"], default = "vmd"),
57
+ vmd = Options(help = "VMD specific options (only applies if engine == 'vmd'",
58
+ executable = Option(help = "Path to the VMD (Visual Molecular Dynamics) executable", default = "vmd"),
59
+ tachyon = Option(help = "The tachyon ray-tracing library, performs the actual rendering. Tachyon is typically packaged with VMD, but often isn't added to the path automatically", default = "tachyon"),
60
+ rendering_style = Option(help =\
61
+ """The render/display mode, changes the appearance of rendered molecules/orbitals.
62
+ Possible options are:
63
+ pastel: The default style, uses light, pastel-ish colours for orbitals with low transparency. Normal atom colours.
64
+ light-pastel: Similar to pastel, but with lighter orbital colours.
65
+ dark-pastel: Similar to pastel, but with darker orbital colours, reminiscant of the 'sharp' style.
66
+ sharp: Orbitals are darkly coloured around their edge but have high transparency in their center. Normal atom colours.
67
+ gaussian: A style attempting to mimic that of GaussView.
68
+ vesta: A style attempting to mimic that of VESTA.""",
69
+ choices = ["pastel", "light-pastel", "dark-pastel", "sharp", "gaussian", "vesta"], default = "pastel"
70
+ ),
71
+ ),
72
+ batoms = Options(help = "Beautiful Atoms/Blender specific options (only applies if engine == 'batoms'",
73
+ blender = Option(help = "Path to the blender executable, in which beautiful atoms should be installed", default = None),
74
+ cpus = Option(help = "The number of CPUs/threads to use. This option is overridden if running in a calculation environemnt (where it uses the same number of CPUs as the calculation did)", type = int, default = 1),
75
+ render_samples = Option(help = "The number of render samples (or passes) to use. Higher values result in higher image quality and greater render times", type = int, default = 256),
76
+ perspective = Option(help = "The perspective mode", choices = ["orthographic", "perspective"], default = "orthographic")
77
+ # TODO: Colour options.
78
+ ),
79
+ safe_cubes = Option(help = "Whether to sanitize cubes so older software can parse them (VMD < 1.9.2 etc)", type = bool, default = False),
80
+ use_existing = Option(help =\
81
+ """If True, previously created files will be reused. If False, new images will rendered, replacing the old.
82
+ This is on by default for 3D rendered images because they are expensive (time-consuming) to render.""", type = bool, default = True
83
+ ),
84
+
85
+ auto_crop = Option(help = "Whether to enable automatic cropping of excess whitespace around the border of generated images. If False, overall image rendering is likely to take less time, but molecules may only occupy a small portion of the true image.", type = bool, default = True),
86
+ resolution = Option(help = "The target resolution for rendered images. Higher values will increase image quality, at the cost of increased render time and file size.", type = int, default = 512),
87
+ orbital = Options(help = "Specific options for orbital density plots.",
88
+ cube_grid_size = Option(help =\
89
+ """The size of grid used to generate cube files.
90
+ Densities are plotted on a 3D grid of points, this option controls how many points there are in the grid (per dimension).
91
+ In addition to an integer number of points (~100 is often sufficient), any of the following keywords can also be specified:
92
+ Tiny, Small, Medium, Large, Huge or Default.""", type = Cube_grid_points, default = Cube_grid_points("Default")
93
+ ),
94
+ isovalue = Option(help = "The isovalue to use for rendering orbital density.", type = float, default = 0.02)
95
+ ),
96
+ spin = Options(help = "Specific options for spin density plots.",
97
+ cube_grid_size = Option(help = "The size of the grid use to plot cube data. As cubes of this type are rendered with a smaller isovalue, it is often necessary to use a larger grid size than normal to maintain quality.", type = Cube_grid_points, default = Cube_grid_points("Large")),
98
+ isovalue = Option(help = "The isovalue to use for plotting spin density.", type = float, default = 0.0004)
99
+ ),
100
+ density = Options(help = "Specific options for total density plots.",
101
+ cube_grid_size = Option(help = "The size of the grid use to plot cube data.", type = Cube_grid_points, default = Cube_grid_points("Large")),
102
+ isovalue = Option(help = "The isovalue to use for plotting total density.", type = float, default = 0.02)
103
+ ),
104
+ difference_density = Options(help = "Specific options for excited states difference density plots.",
105
+ cube_grid_size = Option(help = "The size of the grid use to plot cube data.", type = Cube_grid_points, default = Cube_grid_points("Default")),
106
+ isovalue = Option(help = "The isovalue to use for difference density plots.", type = float, default = 0.001)
107
+ ),
108
+ natural_transition_orbital = Options(help = "Specific options for NTO plots.",
109
+ cube_grid_size = Option(help = "The size of the grid use to plot cube data.", type = Cube_grid_points, default = Cube_grid_points("Default")),
110
+ isovalue = Option(help = "The isovalue to use for NTO plots.", type = float, default = 0.02)
111
+
112
+ ),
113
+ dipole_moment = Options(help = "Specific options for permanent dipole moment plots.",
114
+ scaling = Option(help = "The value (x) to scale the TDM by, where 1 D = x Å.", type = float, default = 1.0)
115
+
116
+ ),
117
+ transition_dipole_moment = Options(help = "Specific options for transition dipole moment plots.",
118
+ electric_scaling = Option(help = "The value (x) to scale the TEDM by, where 1 D = x Å.", type = float, default = 5.0),
119
+ magnetic_scaling = Option(help = "The value (x) to scale the TMDM by, where 1 au = x Å.", type = float, default = 10.0),
120
+ ),
121
+
122
+ )
123
+
124
+ orbital_diagram = Options(help = "Options that control the appearance of orbital energy diagrams.",
125
+ enable_rendering = Option(help = "Set to False to disable image rendering.", type = bool, default = True),
126
+ y_limits = Option(help =\
127
+ """The method used to set the min/max limits of the Y axis.
128
+ Possible options are:
129
+ - 'all': Limits are set so all orbitals are visible.
130
+ - 'auto': Limits are set automatically so the 0 point (ie, 'X' axis), HOMO and LUMO are clearly visible.
131
+ - 'center': Like 'auto' except the lower limit is set more negative so the HOMO-LUMO are closer to the middle of the diagram.
132
+ - a list of [y_min, y_max], where y_min is the most negative value on the Y axis, and y_max is the most positive value on the Y axis (both in eV).""", type = Auto_type, default = "auto"
133
+ ),
134
+ full_axis_lines = Option(help = "If True, black lines are drawn around the boarder of the diagram. If False, a line is drawn for the Y axis but not for the other 3 sides of the diagram.", type = bool, default = False)
135
+ )
136
+
137
+ excited_states_diagram = Options(help = "Options that control the appearance of excited states energy diagrams.",
138
+ enable_rendering = Option(help = "Set to False to disable image rendering.", type = bool, default = True),
139
+ y_limits = Option(help =\
140
+ """The method used to set the min/max limits of the Y axis.
141
+ Possible options are:
142
+ - 'all': Limits are set so all excited states are visible.
143
+ - 'auto': Limits are set automatically so the lowest excited state of each multiplicity is clearly visible (S1, D1, T1, Q1... N1 etc).
144
+ - a list of [y_min, y_max], where y_min is the most negative value on the Y axis, and y_max is the most positive value on the Y axis (both in eV).""", type = Auto_type, default = "all"
145
+ ),
146
+ show_dest = Option(help = "Whether or not to show the dE(ST) label.", type = bool, default = True)
147
+ )
148
+
149
+ absorption_spectrum = Options(help = "Options for controlling the appearance of simulated UV-Vis like absorption spectra.",
150
+ enable_rendering = Option(help = "Set to False to disable image rendering.", type = bool, default = True),
151
+ y_limits = Option(help =\
152
+ """The method used to set the min/max limits of the Y axis.
153
+ Possible options are:
154
+ - 'auto': Limits are set automatically so all peaks are clearly visible.
155
+ - a list of [y_min, y_max], where y_min is the most negative value on the Y axis, and y_max is the most positive value on the Y axis (both in oscillator strength).""", type = Auto_type, default = "auto"
156
+ ),
157
+ x_limits = Option(help =\
158
+ """The method used to set the min/max limits of the X axis.
159
+ Possible options are:
160
+ - 'auto': Limits are set automatically so all peaks are clearly visible.
161
+ - a list of [x_min, x_max], where x_min is the most negative value on the X axis, and x_max is the most positive value on the X axis (both in nm).""", type = Auto_type, default = "auto"
162
+ ),
163
+ max_width = Option(help =\
164
+ """The maximum image width in pixels.
165
+ Absorption graphs will grow/shrink their width to fit available data, keeping a constant scale (constant pixels to nm ratio) but only up to this maximum.
166
+ To disable the maximum width, set to null.""", type = int, default = 1200
167
+ ),
168
+ peak_cutoff = Option(help =\
169
+ """The minimum oscillator strength that a peak must have to be shown in the graph, as a fraction ofthe highest peak.
170
+ Set to 0 for no cutoff (all peaks shown), which may results in the graph being extended well beyond the drawn peaks (because many peaks are too small to see).
171
+ This option has no effect when using manual x limits.""", type = float, default = 0.01
172
+ ),
173
+ x_padding = Option(help = "The amount (in nm) to extend the x axis past the highest/lowest energy peak.", type = int, default = 40),
174
+ fwhm = Option(help = "The full-width at half-maximum; changes how wide the drawn peaks are. Note that the choice of peak width is essentially arbitrary; only the peak height is given by calculation. Units are eV.", type = float, default = 0.4),
175
+ gaussian_cutoff = Option(help = "The minimum y value to plot using the Gaussian function (controls how close to the x axis we draw the gaussian) as a fraction of the max peak height.", type = float, default = 0.001),
176
+ gaussian_resolution = Option(help = "The spacing between x values to plot using the Gaussian function, in eV. Values that are too large will result in 'curves' made up of a series of straight edges.", type = float, default = 0.01),
177
+ use_jacobian = Option(help = "Whether or not to use the jacobian transformation to correctly scale the y-axis (see J. Phys. Chem. Lett. 2014, 5, 20, 3497)", type = bool, default = True),
178
+ plot_bars = Option(help = "Whether to plot vertical bars for each excited state.", type = bool, default = True),
179
+ plot_peaks = Option(help = "Whether to plot individual Gaussian functions for each excited state.", type = bool, default = False),
180
+ plot_cumulative_peak = Option(help = "Whether to plot the sum of all Gaussian functions (most closely simulates a real spectrum).", type = bool, default = True)
181
+ )
182
+
183
+ emission_spectrum = Options(help = "Options for controlling the appearance of simulated emission spectra. 'emission_spectrum' and 'absorption_spectrum 'take the same options.",
184
+ enable_rendering = Option(help = "Set to False to disable image rendering.", type = bool, default = True),
185
+ y_limits = Option(help =\
186
+ """The method used to set the min/max limits of the Y axis.
187
+ Possible options are:
188
+ - 'auto': Limits are set automatically so all peaks are clearly visible.
189
+ - a list of [y_min, y_max], where y_min is the most negative value on the Y axis, and y_max is the most positive value on the Y axis (both in oscillator strength).""", type = Auto_type, default = "auto"
190
+ ),
191
+ x_limits = Option(help =\
192
+ """The method used to set the min/max limits of the X axis.
193
+ Possible options are:
194
+ - 'auto': Limits are set automatically so all peaks are clearly visible.
195
+ - a list of [x_min, x_max], where x_min is the most negative value on the X axis, and x_max is the most positive value on the X axis (both in nm).""", type = Auto_type, default = "auto"
196
+ ),
197
+ max_width = Option(help =\
198
+ """The maximum image width in pixels.
199
+ Absorption graphs will grow/shrink their width to fit available data, keeping a constant scale (constant pixels to nm ratio) but only up to this maximum.
200
+ To disable the maximum width, set to null.""", type = int, default = 1200
201
+ ),
202
+ peak_cutoff = Option(help =\
203
+ """The minimum oscillator strength that a peak must have to be shown in the graph, as a fraction ofthe highest peak.
204
+ Set to 0 for no cutoff (all peaks shown), which may results in the graph being extended well beyond the drawn peaks (because many peaks are too small to see).
205
+ This option has no effect when using manual x limits.""", type = float, default = 0.01
206
+ ),
207
+ x_padding = Option(help = "The amount (in nm) to extend the x axis past the highest/lowest energy peak.", type = int, default = 40),
208
+ fwhm = Option(help = "The full-width at half-maximum; changes how wide the drawn peaks are. Note that the choice of peak width is essentially arbitrary; only the peak height is given by calculation. Units are eV.", type = float, default = 0.4),
209
+ gaussian_cutoff = Option(help = "The minimum y value to plot using the Gaussian function (controls how close to the x axis we draw the gaussian) as a fraction of the max peak height.", type = float, default = 0.001),
210
+ gaussian_resolution = Option(help = "The spacing between x values to plot using the Gaussian function, in eV. Values that are too large will result in 'curves' made up of a series of straight edges.", type = float, default = 0.01),
211
+ use_jacobian = Option(help = "Whether or not to use the jacobian transformation to correctly scale the y-axis (see J. Phys. Chem. Lett. 2014, 5, 20, 3497)", type = bool, default = True),
212
+ plot_bars = Option(help = "Whether to plot vertical bars for each excited state.", type = bool, default = True),
213
+ plot_peaks = Option(help = "Whether to plot individual Gaussian functions for each excited state.", type = bool, default = False),
214
+ plot_cumulative_peak = Option(help = "Whether to plot the sum of all Gaussian functions (most closely simulates a real spectrum).", type = bool, default = True)
215
+ )
216
+
217
+ IR_spectrum = Options(help = "Options for controlling the appearance of simulated IR like vibrational frequency spectra.",
218
+ enable_rendering = Option(help = "Set to False to disable image rendering.", type = bool, default = True),
219
+ y_limits = Option(help =\
220
+ """The method used to set the min/max limits of the Y axis.
221
+ Possible options are:
222
+ - 'auto': Limits are set automatically so all peaks are clearly visible.
223
+ - a list of [y_min, y_max], where y_min is the most negative value on the Y axis, and y_max is the most positive value on the Y axis (both in km/mol).""", type = Auto_type, default = "auto"
224
+ ),
225
+ x_limits = Option(help =\
226
+ """The method used to set the min/max limits of the X axis.
227
+ Possible options are:
228
+ - 'auto': Limits are set automatically so all peaks are clearly visible.
229
+ - a list of [x_min, x_max], where x_min is the most negative value on the X axis, and x_max is the most positive value on the X axis (both in cm-1).""", type = Auto_type, default = "auto"
230
+ ),
231
+ fwhm = Option(help = "The full-width at half-maximum; changes how wide the drawn peaks are. Note that the choice of peak width is essentially arbitrary; only the peak height is given by calculation. Units are cm-1.", type = float, default = 80),
232
+ max_width = Option(help =\
233
+ """The maximum image width in pixels.
234
+ IR spectra will grow/shrink their width to fit available data, keeping a constant scale (constant pixels to nm ratio) but only up to this maximum.
235
+ To disable the maximum width, set to null.""", type = int, default = 1500),
236
+ gaussian_cutoff = Option(help = "The minimum y value to plot using the Gaussian function (controls how close to the x axis we draw the gaussian) as a fraction of the max peak height.", type = float, default = 0.001),
237
+ gaussian_resolution = Option(help = "The spacing between x values to plot using the Gaussian function, in eV. Values that are too large will result in 'curves' made up of a series of straight edges.", type = float, default = 1.0)
238
+ )
239
+
240
+ nmr = Options(help = "Options for controlling simulated NMR spectra",
241
+ enable_rendering = Option(help = "Set to False to disable image rendering.", type = bool, default = True),
242
+ coupling_filter = Option(help = "Discard J coupling that is below this threshold (in Hz)", type = float, default = 1),
243
+ fwhm = Option(help = "The full-width at half-maximum; changes how wide the drawn peaks are. Note that the choice of peak width is essentially arbitrary; only the peak height is given by calculation. Units are ppm.", type = float, default = 0.01),
244
+ gaussian_cutoff = Option(help = "The minimum y value to plot using the Gaussian function (controls how close to the x axis we draw the gaussian) as a fraction of the max peak height.", type = float, default = 0.001),
245
+ gaussian_resolution = Option(help = "The spacing between x values to plot using the Gaussian function, in ppm. Values that are too large will result in 'curves' made up of a series of straight edges.", type = float, default = 0.001),
246
+ frequency = Option(help = "The frequency to run the simulated spectrometer at. Larger values will result in narrower coupling. Units are MHz", type = float, default = 100),
247
+ pre_merge = Option(help = "The threshold within which similar peaks will be merged together, before they are split by coupling. This option typically results in faster execution, but more error, compared to post_merge. Units in ppm.", type = float, default = 0.01),
248
+ post_merge = Option(help = "The threshold within which similar peaks will be merged together. Units in ppm.", type = float, default = None),
249
+ standards = Option(help = "The chemical shift of a standard reference peak (in ppm) to use to adjust the spectrum. One for each element.", type = Nested_dict_type, default = Nested_dict_type({
250
+ "H": 31.68766666666667,
251
+ "B": 111.27199999999999,
252
+ "C": 197.90316666666664,
253
+ "F": 183.2779999999999,
254
+ "P": 290.8630000000001,
255
+ })),
256
+ plot_bars = Option(help = "Whether to plot vertical bars for each NMR shift", type = bool, default = False),
257
+ plot_zoom_bars = Option(help = "Whether to plot vertical bars for each NMR shift in zoomed spectra", type = bool, default = True),
258
+
259
+ # TODO: Validation on these sorts of options is poor and needs looking at.
260
+ isotopes = Option(help = "Isotope specific options. Each key should consist of a tuple of (proton_number, isotope).", type = Nested_dict_type, default = Nested_dict_type({
261
+ # Resonance frequencies calculated at 9.3947 T.
262
+ # 1H, increase fidelity to see more detail.
263
+ "1H": {"frequency": 400, "fwhm": 0.005, "gaussian_resolution": 0.0005, "coupling_filter": 0.001, "pre_merge": 0.0005},
264
+ # 11B.
265
+ "11B": {"frequency": 128.3},
266
+ # 13C.
267
+ "13C": {"frequency": 100.6},
268
+ # 19F
269
+ "19F": {"frequency": 376.5},
270
+ # 31P
271
+ "31P": {"frequency": 162.0}
272
+ }))
273
+ )
274
+
275
+ def __init__(self, *args, **kwargs):
276
+ super().__init__(*args, allow_unrecognised_options = True, **kwargs)
277
+
278
+ @classmethod
279
+ def _from_reduce(self, kwargs):
280
+ """
281
+ Function to help un-pickling this class.
282
+ """
283
+ return self(**kwargs)
284
+
285
+ def __reduce__(self):
286
+ """
287
+ Magic function to help pickling this class.
288
+ """
289
+ return (self._from_reduce, (self.dump(),))
290
+
291
+ # These methods allow the main digichem options object to be accessed as a dict.
292
+ # This is largely to provide compatibility with legacy code, where the digichem options object was, in fact, a dict.
293
+ def __getitem__(self, key):
294
+ return getattr(self, key)
295
+
296
+ def __setitem__(self, key, value):
297
+ setattr(self, key, value)
298
+
299
+ def __delitem__(self, key):
300
+ # Deleting a configurable option doesn't actually delete it (because that wouldn't make much sense),
301
+ # Instead it reverts that option to its default.
302
+ delattr(self, key)
303
+
304
+ def save(self, path = user_config_location):
305
+ """
306
+ Save the current value of these options to file, so that they will be reloaded on next program start.
307
+
308
+ :param path: Where to save to (the user's config file by default).
309
+ """
310
+ data = yaml.dump(self.dump())
311
+
312
+ path = Path(path)
313
+
314
+ try:
315
+ path.parent.mkdir(exist_ok = True)
316
+ atomic_write(path, data)
317
+
318
+ except FileNotFoundError as e:
319
+ # We lost the race, give up.
320
+ raise Exception("Failed to write settings to file '{}'; one of the parent directories does not exist".format(path)) from e
321
+
@@ -0,0 +1,14 @@
1
+ from pathlib import Path
2
+
3
+ import digichem
4
+ from digichem.datas import get_resource
5
+
6
+
7
+ # A config file included in the digichem install directory.
8
+ master_config_path = get_resource("data/config/digichem.yaml")
9
+
10
+ # The location of the system-wide config file (which has precedence over the master).
11
+ system_config_location = Path("/etc/digichem{}/digichem.yaml".format(digichem.major_version))
12
+
13
+ # The location of the user specific config file (which has precedence over the system).
14
+ user_config_location = Path(Path.home(), ".config/digichem{}/digichem.yaml".format(digichem.major_version))
@@ -0,0 +1,90 @@
1
+ """Code for loading/reading/parsing config files and configurable files."""
2
+
3
+ import yaml
4
+
5
+ class Config_parser():
6
+ """
7
+ Class for loading standard digichem options (from a string).
8
+ """
9
+
10
+ def __init__(self, config_string):
11
+ """
12
+ Constructor for Options loader objects.
13
+
14
+ :param config_string: A string to parse options from.
15
+ """
16
+ self.config_string = config_string
17
+ self.config_path = None
18
+
19
+ def parse(self, config_stream):
20
+ """
21
+ """
22
+ # Load with PyYaml.
23
+ try:
24
+ config = yaml.safe_load(config_stream)
25
+
26
+ except Exception as e:
27
+ # We could ignore errors here but it might not be obvious to the user why their settings have been ignored
28
+ raise Exception("Error parsing settings") from e
29
+
30
+ # Check the config isn't None (which it will be if the file is empty.)
31
+ try:
32
+ if not isinstance(config, dict):
33
+ raise Exception("Config option '{}' is formatted incorrectly".format(config_stream))
34
+ return self.pre_process(config)
35
+
36
+ except Exception as e:
37
+ if config is None:
38
+ # Don't panic, the file was just empty.
39
+ return self.pre_process({})
40
+ else:
41
+ # Something else went wrong; panic.
42
+ raise e
43
+
44
+ def load(self):
45
+ """
46
+ Load the config file.
47
+ """
48
+ return self.parse(self.config_string)
49
+
50
+ def pre_process(self, config):
51
+ """
52
+ Peform some pre-processing on a just-loaded config.
53
+
54
+ :param config: Dictionary of config options.
55
+ """
56
+ return config
57
+ # return Config(config, FILE_NAME = self.config_path)
58
+
59
+
60
+ class Config_file_parser(Config_parser):
61
+ """
62
+ Class for loading config files.
63
+ """
64
+
65
+ def __init__(self, path):
66
+ """
67
+ Constructor for Options loader objects.
68
+
69
+ :param path: Path to the config file to load.
70
+ """
71
+ self.config_path = path
72
+
73
+ def load(self, not_exists_ok = False):
74
+ """
75
+ Load the config file.
76
+
77
+ :param not_exists_ok: If False and our config file does not exist, raise an exception.
78
+ """
79
+ try:
80
+ with open(self.config_path, "rt") as config_stream:
81
+ return self.parse(config_stream)
82
+
83
+ except FileNotFoundError:
84
+ if not_exists_ok:
85
+ # No need to panic.
86
+ return self.pre_process({})
87
+
88
+ else:
89
+ # Panic.
90
+ raise