simpyson 0.0.1__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ graft src/simpyson
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: simpyson
3
+ Version: 0.0.1
4
+ Summary: A python interface to Simpson
5
+ Author-email: Carlos Bornes <carlos.bornes@natur.cuni.cz>
6
+ License: BSD-3
7
+ Project-URL: repository, https://github.com/carlosbornes/simpyson
8
+ Project-URL: documentation, https://carlosbornes.github.io/simpyson/
9
+ Project-URL: changelog, https://github.com/simpyson/simpyson/blob/main/CHANGELOG.md
10
+ Keywords: NMR,Simulation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Intended Audience :: Science/Research
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Operating System :: Unix
23
+ Classifier: Operating System :: MacOS
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE.md
27
+ Requires-Dist: numpy
28
+ Requires-Dist: ase
29
+ Requires-Dist: pandas
30
+ Requires-Dist: soprano
31
+ Requires-Dist: plotly
32
+ Requires-Dist: PyQt5
33
+ Requires-Dist: PyQtWebEngine
34
+ Provides-Extra: dev
35
+ Requires-Dist: codecov-cli>=0.4.1; extra == "dev"
36
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
37
+ Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
38
+ Requires-Dist: ruff>=0.0.285; extra == "dev"
39
+ Provides-Extra: docs
40
+ Requires-Dist: mkdocs-material>=9.4.0; extra == "docs"
41
+ Requires-Dist: mkdocstrings[python]>=0.22.0; extra == "docs"
42
+ Requires-Dist: mkdocs-gen-files>=0.5.0; extra == "docs"
43
+ Requires-Dist: mkdocs-literate-nav>=0.6.0; extra == "docs"
44
+ Requires-Dist: pillow>=10.0.0; extra == "docs"
45
+ Requires-Dist: cairosvg>=2.7.1; extra == "docs"
46
+ Dynamic: license-file
47
+
48
+ # SimPYson: A Pythonic Interface for SIMPSON
49
+
50
+ [![DOI](https://zenodo.org/badge/813117518.svg)](https://doi.org/10.5281/zenodo.14041918)
51
+
52
+ SimPYson is a Python package designed to simplify the use of [SIMPSON](https://inano.au.dk/about/research-centers-and-projects/nmr/software/simpson), a powerful code to simulate solid-state NMR experiments. Born out of the need to streamline the process, SimPYson makes it easier to prepare input files from DFT calculations and analyze results from SIMPSON simulations all within a Python.
53
+
54
+ ## Features 🤌
55
+
56
+ - **Convert DFT data to SIMPSON input files**: Prepare SIMPSON input files from DFT data (CASTEP, Quantum Espresso, VASP).
57
+
58
+ - **Read SIMPSON output files**: Load and manipulate NMR data from SIMPSON `.spe`, `.fid`, and `.xreim` files directly in Python for further analysis and visualization.
59
+
60
+ - **Templates for common experiments**: Use ready-made templates for typical Simpson NMR simulations, currently 90-degree pulse and no-pulse. More soon.
61
+
62
+ ## Documentation 📖
63
+
64
+ To learn more about simpyson, including some tutorials check the [documentation](https://carlosbornes.github.io/simpyson/).
65
+
66
+ # Planned Features 🔜
67
+
68
+ - **Expand number of pulse sequences**: Additional templates for more complex NMR experiments.
69
+
70
+ - **Expand number of DFT codes**: Improve the support of other DFT codes, suggestions are welcomed.
@@ -0,0 +1,23 @@
1
+ # SimPYson: A Pythonic Interface for SIMPSON
2
+
3
+ [![DOI](https://zenodo.org/badge/813117518.svg)](https://doi.org/10.5281/zenodo.14041918)
4
+
5
+ SimPYson is a Python package designed to simplify the use of [SIMPSON](https://inano.au.dk/about/research-centers-and-projects/nmr/software/simpson), a powerful code to simulate solid-state NMR experiments. Born out of the need to streamline the process, SimPYson makes it easier to prepare input files from DFT calculations and analyze results from SIMPSON simulations all within a Python.
6
+
7
+ ## Features 🤌
8
+
9
+ - **Convert DFT data to SIMPSON input files**: Prepare SIMPSON input files from DFT data (CASTEP, Quantum Espresso, VASP).
10
+
11
+ - **Read SIMPSON output files**: Load and manipulate NMR data from SIMPSON `.spe`, `.fid`, and `.xreim` files directly in Python for further analysis and visualization.
12
+
13
+ - **Templates for common experiments**: Use ready-made templates for typical Simpson NMR simulations, currently 90-degree pulse and no-pulse. More soon.
14
+
15
+ ## Documentation 📖
16
+
17
+ To learn more about simpyson, including some tutorials check the [documentation](https://carlosbornes.github.io/simpyson/).
18
+
19
+ # Planned Features 🔜
20
+
21
+ - **Expand number of pulse sequences**: Additional templates for more complex NMR experiments.
22
+
23
+ - **Expand number of DFT codes**: Improve the support of other DFT codes, suggestions are welcomed.
@@ -0,0 +1,147 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "simpyson"
7
+ description="A python interface to Simpson"
8
+ version = "0.0.1"
9
+ readme = "README.md"
10
+ license = { text = "BSD-3" }
11
+ authors = [{ name = "Carlos Bornes", email = "carlos.bornes@natur.cuni.cz" }]
12
+ keywords = ["NMR", "Simulation"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Science/Research",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Intended Audience :: Science/Research",
23
+ "Topic :: Scientific/Engineering",
24
+ "Operating System :: Microsoft :: Windows",
25
+ "Operating System :: Unix",
26
+ "Operating System :: MacOS",
27
+ ]
28
+ requires-python = ">=3.9"
29
+ dependencies = ["numpy", "ase", "pandas", "soprano", "plotly", "PyQt5", "PyQtWebEngine"]
30
+
31
+ [project.optional-dependencies]
32
+ dev = ["codecov-cli>=0.4.1", "pytest>=7.4.0", "pytest-cov>=3.0.0", "ruff>=0.0.285"]
33
+ docs = [
34
+ "mkdocs-material>=9.4.0",
35
+ "mkdocstrings[python]>=0.22.0",
36
+ "mkdocs-gen-files>=0.5.0",
37
+ "mkdocs-literate-nav>=0.6.0",
38
+ "pillow>=10.0.0",
39
+ "cairosvg>=2.7.1"
40
+ ]
41
+
42
+ [project.urls]
43
+ repository = "https://github.com/carlosbornes/simpyson"
44
+ documentation = "https://carlosbornes.github.io/simpyson/"
45
+ changelog = "https://github.com/simpyson/simpyson/blob/main/CHANGELOG.md"
46
+
47
+ [project.scripts]
48
+ simpyson-gui = "simpyson.gui:main"
49
+ simpyson = "simpyson.cli:main"
50
+
51
+ [tool.setuptools.package-data]
52
+ simpyson = ["py.typed", "isotope_data.json"]
53
+
54
+ [tool.pyright]
55
+ include = ["simpyson"]
56
+ exclude = ["**/__pycache__"]
57
+
58
+ [tool.pytest.ini_options]
59
+ minversion = "6.0"
60
+ addopts = ["-p no:warnings", "--import-mode=importlib"]
61
+ xfail_strict = true
62
+ log_cli_level = "warn"
63
+ pythonpath = "src"
64
+ testpaths = ["tests"]
65
+
66
+ [tool.black]
67
+ exclude = '''
68
+ /(
69
+ \.git
70
+ | \.tox
71
+ )/
72
+ '''
73
+ skip-magic-trailing-comma = true
74
+
75
+ [tool.isort]
76
+ profile = 'black'
77
+ skip_gitignore = true
78
+
79
+ [tool.coverage.run]
80
+ source = ["src"]
81
+
82
+ [tool.coverage.report]
83
+ exclude_also = [
84
+ "if TYPE_CHECKING:",
85
+ "if __name__ == .__main__.:",
86
+ "except ImportError",
87
+ ]
88
+
89
+ [tool.ruff]
90
+ lint.select = [
91
+ "E", "F", "W", # flake8
92
+ "B", # flake8-bugbear
93
+ "I", # isort
94
+ "ARG", # flake8-unused-arguments
95
+ "C4", # flake8-comprehensions
96
+ "EM", # flake8-errmsg
97
+ "ICN", # flake8-import-conventions
98
+ "ISC", # flake8-implicit-str-concat
99
+ "G", # flake8-logging-format
100
+ "PGH", # pygrep-hooks
101
+ "PIE", # flake8-pie
102
+ "PL", # pylint
103
+ "PT", # flake8-pytest-style
104
+ "PTH", # flake8-use-pathlib
105
+ "RET", # flake8-return
106
+ "RUF", # Ruff-specific
107
+ "SIM", # flake8-simplify
108
+ "T20", # flake8-print
109
+ "UP", # pyupgrade
110
+ "YTT", # flake8-2020
111
+ "EXE", # flake8-executable
112
+ "NPY", # NumPy specific rules
113
+ "PD", # pandas-vet
114
+ ]
115
+ lint.extend-ignore = [
116
+ "PLR", # Design related pylint codes
117
+ "E501", # Line too long
118
+ "B028", # No explicit stacklevel
119
+ "EM101", # Exception must not use a string literal
120
+ "EM102", # Exception must not use an f-string literal
121
+ "G004", # f-string in Logging statement
122
+ "RUF015", # Prefer next(iter())
123
+ "RET505", # Unnecessary `elif` after `return`
124
+ ]
125
+ lint.typing-modules = ["mypackage._compat.typing"]
126
+ lint.unfixable = [
127
+ "T20", # Removes print statements
128
+ "F841", # Removes unused variables
129
+ ]
130
+ lint.flake8-unused-arguments.ignore-variadic-names = true
131
+ lint.pydocstyle.convention = "numpy"
132
+ lint.isort.required-imports = ["from __future__ import annotations"]
133
+ lint.isort.known-first-party = ["simpyson"]
134
+ src = ["src"]
135
+ exclude = []
136
+ extend-exclude = ["tests"]
137
+
138
+ [tool.docformatter]
139
+ pre-summary-newline = true
140
+ black = true
141
+
142
+ [tool.mypy]
143
+ ignore_missing_imports = true
144
+ namespace_packages = true
145
+ explicit_package_bases = true
146
+ no_implicit_optional = false
147
+ disable_error_code = "annotation-unchecked"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """Init data"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import version
6
+
7
+ # Load the version
8
+ __version__ = version("simpyson")
@@ -0,0 +1,16 @@
1
+ import argparse
2
+ from simpyson.gui import main as gui_main
3
+
4
+ def main():
5
+ parser = argparse.ArgumentParser(prog="simpyson")
6
+ subparsers = parser.add_subparsers(dest="command")
7
+
8
+ parser_gui = subparsers.add_parser("gui", help="Launch the GUI")
9
+ parser_gui.set_defaults(func=lambda args: gui_main())
10
+
11
+ args = parser.parse_args()
12
+ if hasattr(args, "func"):
13
+ args.func(args)
14
+ else:
15
+ parser.print_help()
16
+
@@ -0,0 +1,188 @@
1
+ import ase.io
2
+ import numpy as np
3
+ import scipy.constants as const
4
+ import os
5
+ import json
6
+
7
+
8
+ def read_vasp(file, format):
9
+ """
10
+ This function reads NMR data from a VASP OUTCAR file.
11
+
12
+ Args:
13
+ file (str): The path to the VASP OUTCAR file.
14
+ format (str): The format of the VASP OUTCAR file.
15
+
16
+ Returns:
17
+ ase.Atoms: The Atoms object with the NMR data.
18
+
19
+ Example:
20
+ reader = read_vasp('OUTCAR', 'vasp-out')
21
+ """
22
+ filename = ase.io.read(file, format=format)
23
+ n_atoms = filename.get_global_number_of_atoms()
24
+ np.set_printoptions(suppress=True)
25
+ efg = []
26
+ sym_tensor = []
27
+ const_shield = []
28
+ core_shield_dict = []
29
+ ms = []
30
+ volume = None
31
+ with open(file, 'r') as outcar:
32
+ lines = outcar.readlines()
33
+ #Find lines with specific header
34
+ for line in lines:
35
+ if line.find("Electric field gradients (V/A^2)") != -1:
36
+ idx_header1 = lines.index(line) #efg
37
+ if line.find("SYMMETRIZED TENSORS") != -1:
38
+ idx_header2 = []
39
+ idx_header2.append(lines.index(line)) #sym tensor - need to take second one (unsym. tensors)
40
+ if line.find("G=0 CONTRIBUTION TO CHEMICAL SHIFT") != -1:
41
+ idx_header3 = lines.index(line) #constant shielding
42
+ if line.find('Core NMR properties') != -1:
43
+ idx_header4 = lines.index(line) #core NMR prop - depends on atom type
44
+ if line.find("Core contribution to magnetic susceptibility:") != -1:
45
+ val = line.split()[5]
46
+ exp = line.split()[6][-2:]
47
+ mag_sus = float(val)*10**int(exp)
48
+ if line.find("volume of cell") != -1 and volume is None:
49
+ volume = float(line.split()[4])
50
+
51
+ chi_fact = 3.0/8.0/np.pi*volume*6.022142e23/1e24 # Conversion factor for magnetic susceptibility
52
+
53
+ #Calculate tensors for every atoms
54
+ for i in range(n_atoms):
55
+ grad = (lines[idx_header1+4+i]).split()[1:] # V_xx, V_yy, V_zz, V_xy, V_xz, V_yz
56
+ matrix = np.array([[grad[0],grad[3],grad[4]], #xx xy xz
57
+ [grad[3],grad[1],grad[5]], #xy yy yz
58
+ [grad[4],grad[5],grad[2]]]) #xz yz zz
59
+ efg.append(matrix)
60
+
61
+ for i in range(n_atoms*4):
62
+ if i % 4 != 0:
63
+ sym=lines[idx_header2[-1] + i + 1].split()
64
+ sym_tensor.append(lines[idx_header2[-1] + i + 1].split())
65
+
66
+ # Newer version of VASP 6.4.1
67
+ if lines[idx_header3 + 1].strip() == "using pGv susceptibility, excluding core contribution":
68
+ start_idx = idx_header3 + 5
69
+ # Older version of VASP
70
+ else:
71
+ start_idx = idx_header3 + 4
72
+
73
+ for i in range(3):
74
+ const_shield.append((lines[start_idx + i]).split()[1:])
75
+
76
+ for i in range(len(np.unique(filename.get_chemical_symbols()))):
77
+ core_shield_dict.append((lines[idx_header4 + 4 + i]).split()[1:])
78
+ core_shield_dict = dict((k[0], float(k[1:][0])) for k in core_shield_dict)
79
+ core_shield = []
80
+
81
+ for i in filename.get_chemical_symbols():
82
+ core_shield.append(core_shield_dict[i])
83
+
84
+ #Process results
85
+ efg = np.array(efg,dtype=float) #resulting EFG matrix
86
+ efg = efg * 1e20 / const.physical_constants["atomic unit of electric field gradient"][0]
87
+ sym_tensor = np.split(np.array(sym_tensor,dtype=float), n_atoms) #symmetry tensors
88
+ const_shield = np.array(const_shield, dtype=float) #constant shielding of the lattice
89
+ core_shield = np.array(core_shield,dtype=float) #core shielding depending on atom type
90
+
91
+ for i in range(n_atoms):
92
+ core_diag = np.diag(core_shield[i] * np.ones(3))
93
+ ms_tensor = sym_tensor[i] + const_shield + core_diag + mag_sus/chi_fact*1e6*np.eye(3) #calculating MS tensor
94
+ ms.append(-ms_tensor) # calculating MS tensors
95
+ ms=np.array(np.array(ms).tolist(),dtype=float) #processing MS tensor to work with ase
96
+
97
+ filename.set_array('efg', efg)
98
+ filename.set_array('ms', ms)
99
+
100
+ return filename
101
+
102
+ def hz2ppm(hz, b0, nucleus, isotope_file=None):
103
+ """
104
+ Convert Hz values to ppm values.
105
+
106
+ Args:
107
+ hz (numpy.ndarray): Frequency values in Hz
108
+ b0 (str): Magnetic field strength (e.g., '400MHz' or '9.4T')
109
+ nucleus (str): Nucleus type (e.g., '1H' or '13C')
110
+ isotope_file (str, optional): Path to isotope data file. If None, uses default.
111
+
112
+ Returns:
113
+ numpy.ndarray: Chemical shift values in ppm
114
+
115
+ Raises:
116
+ ValueError: If B0 unit is invalid or nucleus not found
117
+ """
118
+
119
+ if isotope_file is None:
120
+ dir = os.path.dirname(os.path.realpath(__file__))
121
+ isotope_file = os.path.join(dir, 'isotope_data.json')
122
+
123
+ isotope = int(''.join(filter(str.isdigit, nucleus)))
124
+ element = ''.join(filter(str.isalpha, nucleus)).upper()
125
+ b0_unit = ''.join(filter(str.isalpha, b0)).lower()
126
+
127
+ with open(isotope_file) as f:
128
+ data = json.load(f)
129
+ if element in data and str(isotope) in data[element]:
130
+ gamma = data[element][str(isotope)]['Gamma']
131
+ else:
132
+ raise ValueError(f'Nucleus {nucleus} not found in isotope data.')
133
+
134
+ if b0_unit == 't':
135
+ b0_value = float(''.join(filter(str.isdigit, b0)))
136
+ ppm = hz / (b0_value * gamma)
137
+ elif b0_unit == 'mhz':
138
+ b0_value = float(''.join(filter(str.isdigit, b0)))
139
+ gamma_h = data['H']['1']['Gamma']
140
+ ppm = hz / (b0_value/(gamma_h/gamma))
141
+ else:
142
+ raise ValueError('B0 unit must be T or MHz.')
143
+
144
+ return ppm
145
+
146
+ def ppm2hz(ppm, b0, nucleus, isotope_file=None):
147
+ """
148
+ Convert ppm values to Hz values.
149
+
150
+ Args:
151
+ ppm (numpy.ndarray): Chemical shift values in ppm
152
+ b0 (str): Magnetic field strength (e.g., '400MHz' or '9.4T')
153
+ nucleus (str): Nucleus type (e.g., '1H' or '13C')
154
+ isotope_file (str, optional): Path to isotope data file. If None, uses default.
155
+
156
+ Returns:
157
+ numpy.ndarray: Frequency values in Hz
158
+
159
+ Raises:
160
+ ValueError: If B0 unit is invalid or nucleus not found
161
+ """
162
+
163
+ if isotope_file is None:
164
+ dir = os.path.dirname(os.path.realpath(__file__))
165
+ isotope_file = os.path.join(dir, 'isotope_data.json')
166
+
167
+ isotope = int(''.join(filter(str.isdigit, nucleus)))
168
+ element = ''.join(filter(str.isalpha, nucleus)).upper()
169
+ b0_unit = ''.join(filter(str.isalpha, b0)).lower()
170
+
171
+ with open(isotope_file) as f:
172
+ data = json.load(f)
173
+ if element in data and str(isotope) in data[element]:
174
+ gamma = data[element][str(isotope)]['Gamma']
175
+ else:
176
+ raise ValueError(f'Nucleus {nucleus} not found in isotope data.')
177
+
178
+ if b0_unit == 't':
179
+ b0_value = float(''.join(filter(str.isdigit, b0)))
180
+ hz = ppm * (b0_value * gamma)
181
+ elif b0_unit == 'mhz':
182
+ b0_value = float(''.join(filter(str.isdigit, b0)))
183
+ gamma_h = data['H']['1']['Gamma']
184
+ hz = ppm * (b0_value/(gamma_h/gamma))
185
+ else:
186
+ raise ValueError('B0 unit must be T or MHz.')
187
+
188
+ return hz