stjames 0.0.42__tar.gz → 0.0.43__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.

Potentially problematic release.


This version of stjames might be problematic. Click here for more details.

Files changed (59) hide show
  1. {stjames-0.0.42/stjames.egg-info → stjames-0.0.43}/PKG-INFO +1 -1
  2. {stjames-0.0.42 → stjames-0.0.43}/pyproject.toml +1 -1
  3. {stjames-0.0.42 → stjames-0.0.43}/stjames/atom.py +1 -1
  4. {stjames-0.0.42 → stjames-0.0.43}/stjames/molecule.py +87 -2
  5. {stjames-0.0.42 → stjames-0.0.43/stjames.egg-info}/PKG-INFO +1 -1
  6. {stjames-0.0.42 → stjames-0.0.43}/stjames.egg-info/SOURCES.txt +1 -0
  7. stjames-0.0.43/tests/test_from_extxyz.py +231 -0
  8. {stjames-0.0.42 → stjames-0.0.43}/LICENSE +0 -0
  9. {stjames-0.0.42 → stjames-0.0.43}/README.md +0 -0
  10. {stjames-0.0.42 → stjames-0.0.43}/setup.cfg +0 -0
  11. {stjames-0.0.42 → stjames-0.0.43}/stjames/__init__.py +0 -0
  12. {stjames-0.0.42 → stjames-0.0.43}/stjames/_deprecated_solvent_settings.py +0 -0
  13. {stjames-0.0.42 → stjames-0.0.43}/stjames/base.py +0 -0
  14. {stjames-0.0.42 → stjames-0.0.43}/stjames/basis_set.py +0 -0
  15. {stjames-0.0.42 → stjames-0.0.43}/stjames/calculation.py +0 -0
  16. {stjames-0.0.42 → stjames-0.0.43}/stjames/constraint.py +0 -0
  17. {stjames-0.0.42 → stjames-0.0.43}/stjames/correction.py +0 -0
  18. {stjames-0.0.42 → stjames-0.0.43}/stjames/data/__init__.py +0 -0
  19. {stjames-0.0.42 → stjames-0.0.43}/stjames/data/bragg_radii.json +0 -0
  20. {stjames-0.0.42 → stjames-0.0.43}/stjames/data/elements.py +0 -0
  21. {stjames-0.0.42 → stjames-0.0.43}/stjames/data/isotopes.json +0 -0
  22. {stjames-0.0.42 → stjames-0.0.43}/stjames/data/nist_isotopes.json +0 -0
  23. {stjames-0.0.42 → stjames-0.0.43}/stjames/data/read_nist_isotopes.py +0 -0
  24. {stjames-0.0.42 → stjames-0.0.43}/stjames/data/symbol_element.json +0 -0
  25. {stjames-0.0.42 → stjames-0.0.43}/stjames/diis_settings.py +0 -0
  26. {stjames-0.0.42 → stjames-0.0.43}/stjames/grid_settings.py +0 -0
  27. {stjames-0.0.42 → stjames-0.0.43}/stjames/int_settings.py +0 -0
  28. {stjames-0.0.42 → stjames-0.0.43}/stjames/message.py +0 -0
  29. {stjames-0.0.42 → stjames-0.0.43}/stjames/method.py +0 -0
  30. {stjames-0.0.42 → stjames-0.0.43}/stjames/mode.py +0 -0
  31. {stjames-0.0.42 → stjames-0.0.43}/stjames/opt_settings.py +0 -0
  32. {stjames-0.0.42 → stjames-0.0.43}/stjames/periodic_cell.py +0 -0
  33. {stjames-0.0.42 → stjames-0.0.43}/stjames/py.typed +0 -0
  34. {stjames-0.0.42 → stjames-0.0.43}/stjames/scf_settings.py +0 -0
  35. {stjames-0.0.42 → stjames-0.0.43}/stjames/settings.py +0 -0
  36. {stjames-0.0.42 → stjames-0.0.43}/stjames/solvent.py +0 -0
  37. {stjames-0.0.42 → stjames-0.0.43}/stjames/status.py +0 -0
  38. {stjames-0.0.42 → stjames-0.0.43}/stjames/task.py +0 -0
  39. {stjames-0.0.42 → stjames-0.0.43}/stjames/thermochem_settings.py +0 -0
  40. {stjames-0.0.42 → stjames-0.0.43}/stjames/types.py +0 -0
  41. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/__init__.py +0 -0
  42. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/admet.py +0 -0
  43. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/basic_calculation.py +0 -0
  44. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/bde.py +0 -0
  45. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/conformer.py +0 -0
  46. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/descriptors.py +0 -0
  47. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/fukui.py +0 -0
  48. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/molecular_dynamics.py +0 -0
  49. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/multistage_opt.py +0 -0
  50. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/pka.py +0 -0
  51. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/redox_potential.py +0 -0
  52. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/scan.py +0 -0
  53. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/spin_states.py +0 -0
  54. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/tautomer.py +0 -0
  55. {stjames-0.0.42 → stjames-0.0.43}/stjames/workflows/workflow.py +0 -0
  56. {stjames-0.0.42 → stjames-0.0.43}/stjames.egg-info/dependency_links.txt +0 -0
  57. {stjames-0.0.42 → stjames-0.0.43}/stjames.egg-info/requires.txt +0 -0
  58. {stjames-0.0.42 → stjames-0.0.43}/stjames.egg-info/top_level.txt +0 -0
  59. {stjames-0.0.42 → stjames-0.0.43}/tests/test_molecule.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stjames
3
- Version: 0.0.42
3
+ Version: 0.0.43
4
4
  Summary: standardized JSON atom/molecule encoding scheme
5
5
  Author-email: Corin Wagen <corin@rowansci.com>
6
6
  Project-URL: Homepage, https://github.com/rowansci/stjames
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stjames"
3
- version = "0.0.42"
3
+ version = "0.0.43"
4
4
  description = "standardized JSON atom/molecule encoding scheme"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -60,7 +60,7 @@ class Atom(Base):
60
60
  Atom(1, [0.00000, 0.00000, 0.00000])
61
61
  """
62
62
  name, *xyz = xyz_line.split()
63
- symbol = int(name) if name.isdigit() else SYMBOL_ELEMENT[name]
63
+ symbol = int(name) if name.isdigit() else SYMBOL_ELEMENT[name.title()]
64
64
  if not len(xyz) == 3:
65
65
  raise ValueError("XYZ file should have 3 coordinates per atom")
66
66
  return cls(atomic_number=symbol, position=xyz)
@@ -1,8 +1,9 @@
1
+ import re
1
2
  from pathlib import Path
2
3
  from typing import Iterable, Optional, Self
3
4
 
4
5
  import pydantic
5
- from pydantic import NonNegativeInt, PositiveInt
6
+ from pydantic import NonNegativeInt, PositiveInt, ValidationError
6
7
 
7
8
  from .atom import Atom
8
9
  from .base import Base
@@ -135,6 +136,8 @@ class Molecule(Base):
135
136
  match format:
136
137
  case "xyz":
137
138
  return cls.from_xyz_lines(f.readlines(), charge=charge, multiplicity=multiplicity)
139
+ case "extxyz":
140
+ return cls.from_extxyz_lines(f.readlines(), charge=charge, multiplicity=multiplicity)
138
141
  case _:
139
142
  raise ValueError(f"Unsupported {format=}")
140
143
 
@@ -161,7 +164,7 @@ class Molecule(Base):
161
164
 
162
165
  try:
163
166
  return cls(atoms=[Atom.from_xyz(line) for line in lines], charge=charge, multiplicity=multiplicity)
164
- except Exception as e:
167
+ except (ValueError, ValidationError) as e:
165
168
  raise MoleculeReadError("Error reading molecule from xyz") from e
166
169
 
167
170
  def to_xyz(self, comment: str = "", out_file: Path | str | None = None) -> str:
@@ -190,3 +193,85 @@ class Molecule(Base):
190
193
  f.write(out)
191
194
 
192
195
  return out
196
+
197
+ @classmethod
198
+ def from_extxyz(cls: type[Self], extxyz: str, charge: int = 0, multiplicity: PositiveInt = 1) -> Self:
199
+ r"""
200
+ Generate a Molecule from a EXTXYZ string. Currently only supporting Lattice and Properties fields.
201
+
202
+ >>> Molecule.from_extxyz('''
203
+ ... 2
204
+ ... Lattice="6.0 0.0 0.0 6.0 0.0 0.0 6.0 0.0 0.0"Properties=species:S:1:pos:R:3
205
+ ... H 0 0 0
206
+ ... H 0 0 1
207
+ ... ''').cell.lattice_vectors
208
+ ((6.0, 0.0, 0.0), (6.0, 0.0, 0.0), (6.0, 0.0, 0.0))
209
+ """
210
+
211
+ return cls.from_extxyz_lines(extxyz.strip().splitlines(), charge=charge, multiplicity=multiplicity)
212
+
213
+ @classmethod
214
+ def from_extxyz_lines(cls: type[Self], lines: Iterable[str], charge: int = 0, multiplicity: PositiveInt = 1) -> Self:
215
+ # ensure first line is number of atoms
216
+ lines = list(lines)
217
+ if len(lines[0].split()) == 1:
218
+ natoms = lines[0].strip()
219
+ if not natoms.isdigit() or (int(lines[0]) != len(lines) - 2):
220
+ raise MoleculeReadError(f"First line of EXTXYZ file should be the number of atoms, got: {lines[0]} != {len(lines) - 2}")
221
+ lines = lines[1:]
222
+ else:
223
+ raise MoleculeReadError(f"First line of EXTXYZ should be only an int denoting number of atoms. Got {lines[0].split()}")
224
+
225
+ # ensure second line contains key-value pairs
226
+ if "=" not in lines[0]:
227
+ raise MoleculeReadError(f"Invalid property line, got {lines[0]}")
228
+
229
+ cell = parse_comment_line(lines[0])
230
+ lines = lines[1:]
231
+
232
+ try:
233
+ return cls(atoms=[Atom.from_xyz(line) for line in lines], cell=cell, charge=charge, multiplicity=multiplicity)
234
+ except (ValueError, ValidationError) as e:
235
+ raise MoleculeReadError("Error reading molecule from extxyz") from e
236
+
237
+
238
+ def parse_comment_line(line: str) -> PeriodicCell:
239
+ """
240
+ currently only supporting lattice and porperites fields from comment line
241
+ modify in future to support other fields from comment from_xyz_lines
242
+ ex: name, mulitplicity, charge, etc.
243
+ """
244
+ cell = None
245
+ # Regular expression to match key="value", key='value', or key=value
246
+ pattern = r"(\S+?=(?:\".*?\"|\'.*?\'|\S+))"
247
+ pairs = re.findall(pattern, line)
248
+
249
+ prop_dict = {}
250
+ for pair in pairs:
251
+ key, value = pair.split("=", 1)
252
+ if key.lower() == "lattice":
253
+ value = value.strip("'\"").split()
254
+ if len(value) != 9:
255
+ raise MoleculeReadError(f"Lattice should have 9 entries got {len(value)}")
256
+
257
+ # Convert the value to a 3x3 tuple of tuples of floats
258
+ try:
259
+ cell = tuple(tuple(map(float, value[i : i + 3])) for i in range(0, 9, 3))
260
+ except ValueError:
261
+ raise MoleculeReadError(f"Lattice should be floats, got {value}")
262
+
263
+ prop_dict[key] = value
264
+
265
+ elif key.lower() == "properties":
266
+ if value.lower() != "species:s:1:pos:r:3":
267
+ raise MoleculeReadError(f"Only accepting properties of form species:S:1:pos:R:3, got {value}")
268
+ prop_dict[key] = value
269
+ else:
270
+ raise MoleculeReadError(f"Currently only accepting lattice and propery keys. Got {key}")
271
+
272
+ if cell is None:
273
+ raise MoleculeReadError("Lattice field is required but missing.")
274
+
275
+ if "properties" not in [key.lower() for key in prop_dict.keys()]:
276
+ raise MoleculeReadError(f"Property field is required, got keys {prop_dict.keys()}")
277
+ return PeriodicCell(lattice_vectors=cell)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stjames
3
- Version: 0.0.42
3
+ Version: 0.0.43
4
4
  Summary: standardized JSON atom/molecule encoding scheme
5
5
  Author-email: Corin Wagen <corin@rowansci.com>
6
6
  Project-URL: Homepage, https://github.com/rowansci/stjames
@@ -53,4 +53,5 @@ stjames/workflows/scan.py
53
53
  stjames/workflows/spin_states.py
54
54
  stjames/workflows/tautomer.py
55
55
  stjames/workflows/workflow.py
56
+ tests/test_from_extxyz.py
56
57
  tests/test_molecule.py
@@ -0,0 +1,231 @@
1
+ import pytest
2
+
3
+ from stjames import Atom, Molecule, MoleculeReadError, PeriodicCell
4
+
5
+ valid_extxyz = """
6
+ 5
7
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
8
+ C 0.0 0.0 0.0
9
+ H 0.0 0.0 1.0
10
+ H 1.0 0.0 0.0
11
+ H 0.0 1.0 0.0
12
+ H 1.0 1.0 1.0
13
+ """
14
+
15
+ incorrect_num_atoms = """
16
+ 6
17
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
18
+ C 0.0 0.0 0.0
19
+ H 0.0 0.0 1.0
20
+ H 1.0 0.0 0.0
21
+ H 0.0 1.0 0.0
22
+ H 1.0 1.0 1.0
23
+ """
24
+
25
+ not_digit_num_atoms = """
26
+ v
27
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
28
+ C 0.0 0.0 0.0
29
+ H 0.0 0.0 1.0
30
+ H 1.0 0.0 0.0
31
+ H 0.0 1.0 0.0
32
+ H 1.0 1.0 1.0
33
+ """
34
+
35
+ many_num_atoms = """
36
+ 6 9
37
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
38
+ C 0.0 0.0 0.0
39
+ H 0.0 0.0 1.0
40
+ H 1.0 0.0 0.0
41
+ H 0.0 1.0 0.0
42
+ H 1.0 1.0 1.0
43
+ """
44
+ no_num_atoms = """
45
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
46
+ C 0.0 0.0 0.0
47
+ H 0.0 0.0 1.0
48
+ H 1.0 0.0 0.0
49
+ H 0.0 1.0 0.0
50
+ H 1.0 1.0 1.0
51
+ """
52
+
53
+ xyz_style = """
54
+ 5
55
+ Comment
56
+ C 0.0 0.0 0.0
57
+ H 0.0 0.0 1.0
58
+ H 1.0 0.0 0.0
59
+ H 0.0 1.0 0.0
60
+ H 1.0 1.0 1.0
61
+ """
62
+
63
+ missing_lattice = """
64
+ 5
65
+ Properties=species:S:1:pos:R:3
66
+ C 0.0 0.0 0.0
67
+ H 0.0 0.0 1.0
68
+ H 1.0 0.0 0.0
69
+ H 0.0 1.0 0.0
70
+ H 1.0 1.0 1.0
71
+ """
72
+
73
+ missing_properties = """
74
+ 5
75
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0"
76
+ C 0.0 0.0 0.0
77
+ H 0.0 0.0 1.0
78
+ H 1.0 0.0 0.0
79
+ H 0.0 1.0 0.0
80
+ H 1.0 1.0 1.0
81
+ """
82
+
83
+ incorrect_properites = """
84
+ 5
85
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3foo:1
86
+ C 0.0 0.0 0.0
87
+ H 0.0 0.0 1.0
88
+ H 1.0 0.0 0.0
89
+ H 0.0 1.0 0.0
90
+ H 1.0 1.0 1.0
91
+ """
92
+
93
+ incorrect_lattice_extra = """
94
+ 5
95
+ Lattice="6.0 0.0 0.0 0.0 6.0 0.0 0.0 0.0 6.0 3.14" Properties=species:S:1:pos:R:3
96
+ C 0.0 0.0 0.0
97
+ H 0.0 0.0 1.0
98
+ H 1.0 0.0 0.0
99
+ H 0.0 1.0 0.0
100
+ H 1.0 1.0 1.0
101
+ """
102
+
103
+ incorrect_lattice_equals = """
104
+ 5
105
+ Lattice="6.0 0.0 =0.0 0.0 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
106
+ C 0.0 0.0 0.0
107
+ H 0.0 0.0 1.0
108
+ H 1.0 0.0 0.0
109
+ H 0.0 1.0 0.0
110
+ H 1.0 1.0 1.0
111
+ """
112
+
113
+ incorrect_lattice_str = """
114
+ 5
115
+ Lattice="6.0 0.0 0.0 hi 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
116
+ C 0.0 0.0 0.0
117
+ H 0.0 0.0 1.0
118
+ H 1.0 0.0 0.0
119
+ H 0.0 1.0 0.0
120
+ H 1.0 1.0 1.0
121
+ """
122
+
123
+ incorrect_lattice_extra_string = """
124
+ 5
125
+ Lattice="6.0 0.0 0.0 0.0 sup 6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
126
+ C 0.0 0.0 0.0
127
+ H 0.0 0.0 1.0
128
+ H 1.0 0.0 0.0
129
+ H 0.0 1.0 0.0
130
+ H 1.0 1.0 1.0
131
+ """
132
+
133
+
134
+ incorrect_lattice_single_quote = """
135
+ 5
136
+ Lattice="6.0 0.0 0.0 0.0 6.0 '0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
137
+ C 0.0 0.0 0.0
138
+ H 0.0 0.0 1.0
139
+ H 1.0 0.0 0.0
140
+ H 0.0 1.0 0.0
141
+ H 1.0 1.0 1.0
142
+ """
143
+
144
+ incorrect_lattice_double_quote = """
145
+ 5
146
+ Lattice="6.0 0.0 0.0 0.0 "6.0 0.0 0.0 0.0 6.0" Properties=species:S:1:pos:R:3
147
+ C 0.0 0.0 0.0
148
+ H 0.0 0.0 1.0
149
+ H 1.0 0.0 0.0
150
+ H 0.0 1.0 0.0
151
+ H 1.0 1.0 1.0
152
+ """
153
+
154
+ incorrect_lattice_double_single_quote = """
155
+ 5
156
+ Lattice="6.0 0.0 0.0 0.0 '6.0 0.0 0.0 '0.0 6.0" Properties=species:S:1:pos:R:3
157
+ C 0.0 0.0 0.0
158
+ H 0.0 0.0 1.0
159
+ H 1.0 0.0 0.0
160
+ H 0.0 1.0 0.0
161
+ H 1.0 1.0 1.0
162
+ """
163
+
164
+ incorrect_lattice_double_double_quote = """
165
+ 5
166
+ Lattice="6.0 0.0 "0.0 0.0 6.0 0.0 0.0 "0.0 6.0" Properties=species:S:1:pos:R:3
167
+ C 0.0 0.0 0.0
168
+ H 0.0 0.0 1.0
169
+ H 1.0 0.0 0.0
170
+ H 0.0 1.0 0.0
171
+ H 1.0 1.0 1.0
172
+ """
173
+
174
+
175
+ expected_cell = (
176
+ (6.0, 0.0, 0.0),
177
+ (0.0, 6.0, 0.0),
178
+ (0.0, 0.0, 6.0),
179
+ )
180
+
181
+ expected_atoms = [
182
+ Atom(atomic_number=6, position=(0.0, 0.0, 0.0)), # C
183
+ Atom(atomic_number=1, position=(0.0, 0.0, 1.0)), # H
184
+ Atom(atomic_number=1, position=(1.0, 0.0, 0.0)), # H
185
+ Atom(atomic_number=1, position=(0.0, 1.0, 0.0)), # H
186
+ Atom(atomic_number=1, position=(1.0, 1.0, 1.0)), # H
187
+ ]
188
+
189
+ expected_molecule = Molecule(
190
+ charge=0,
191
+ multiplicity=1,
192
+ atoms=expected_atoms,
193
+ cell=PeriodicCell(lattice_vectors=expected_cell),
194
+ )
195
+
196
+
197
+ def test_molecule_from_extxyz_valid() -> None:
198
+ """
199
+ Test case for valid extxyz string.
200
+ """
201
+ molecule = Molecule.from_extxyz(valid_extxyz)
202
+ assert molecule == expected_molecule, f"Valid case failed: got {molecule}, expected {expected_molecule}"
203
+
204
+
205
+ @pytest.mark.parametrize(
206
+ "invalid_extxyz",
207
+ [
208
+ incorrect_num_atoms,
209
+ no_num_atoms,
210
+ not_digit_num_atoms,
211
+ many_num_atoms,
212
+ xyz_style,
213
+ missing_lattice,
214
+ missing_properties,
215
+ incorrect_properites,
216
+ incorrect_lattice_extra,
217
+ incorrect_lattice_equals,
218
+ incorrect_lattice_str,
219
+ incorrect_lattice_extra_string,
220
+ incorrect_lattice_single_quote,
221
+ incorrect_lattice_double_quote,
222
+ incorrect_lattice_double_single_quote,
223
+ incorrect_lattice_double_double_quote,
224
+ ],
225
+ )
226
+ def test_molecule_from_extxyz_invalid(invalid_extxyz: str) -> None:
227
+ """
228
+ Test case for invalid extxyz strings, ensuring they raise MoleculeReadError.
229
+ """
230
+ with pytest.raises(MoleculeReadError):
231
+ Molecule.from_extxyz(invalid_extxyz)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes