pack-mm 0.0.20__tar.gz → 0.0.22__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pack-mm
3
- Version: 0.0.20
3
+ Version: 0.0.22
4
4
  Summary: packing materials and molecules in boxes using for machine learnt interatomic potentials
5
5
  Author: Alin M. Elena
6
6
  Classifier: Programming Language :: Python
@@ -42,7 +42,7 @@ It provides both a cli and a python api, with some examples below.
42
42
  uv pip install pack-mm
43
43
 
44
44
  ```
45
- or install the lates
45
+ or install the latest
46
46
 
47
47
  ```bash
48
48
 
@@ -61,7 +61,7 @@ or install the lates
61
61
 
62
62
  ```
63
63
 
64
- ![](examples/pics/UiO66water.png)
64
+ ![](examples/pics/UiO66water.webp)
65
65
 
66
66
  ### Zeolite in cylindrical channel
67
67
 
@@ -72,7 +72,7 @@ or install the lates
72
72
 
73
73
  ```
74
74
 
75
- ![](examples/pics/MFIwater.png)
75
+ ![](examples/pics/MFIwater.webp)
76
76
 
77
77
  ### NaCl on surface
78
78
 
@@ -81,7 +81,7 @@ or install the lates
81
81
 
82
82
  ```
83
83
 
84
- ![](examples/pics/NaClwater.png)
84
+ ![](examples/pics/NaClwater.webp)
85
85
 
86
86
  ### MOF ellipsoid
87
87
 
@@ -93,7 +93,7 @@ packmm --system examples/data/Cu2L.cif --molecule examples/data/Ethanol.xyz --nm
93
93
 
94
94
  ```
95
95
 
96
- !()[examples/pics/Cu2L-ethanol.png]
96
+ ![](examples/pics/Cu2L-ethanol.webp)
97
97
 
98
98
  ``` bash
99
99
 
@@ -102,7 +102,7 @@ packmm --system Cu2L-ethanol.cif --molecule H2O --nmols 10 --where ellipsoid --
102
102
 
103
103
  ```
104
104
 
105
- !()[examples/pics/Cu2l-ethanol-water.png]
105
+ ![](examples/pics/Cu2l-ethanol-water.webp)
106
106
 
107
107
  ### Liquid water
108
108
 
@@ -113,7 +113,8 @@ packmm --molecule H2O --nmols 33 --where anywhere --cell-a 10.0 --cell-b 10.0
113
113
 
114
114
  ```
115
115
 
116
- !()[examples/pics/water.png]
116
+ ![](examples/pics/water.webp)
117
+
117
118
  ### interstitials
118
119
 
119
120
  ```bash
@@ -124,13 +125,12 @@ packmm --system Pd-super.cif --molecule H2 --nmols 50 --where anywhere --mode
124
125
 
125
126
  before optimisation
126
127
 
127
- !()[examples/pics/Pd-H2-noopt.png]
128
+ ![](examples/pics/Pd-super+50H2.webp)
128
129
 
129
130
 
130
131
  after optimisation
131
132
 
132
- !()[examples/pics/Pd-H2.png]
133
-
133
+ ![](examples/pics/Pd-H2.webp)
134
134
 
135
135
 
136
136
  ### full list of options
@@ -21,7 +21,7 @@ It provides both a cli and a python api, with some examples below.
21
21
  uv pip install pack-mm
22
22
 
23
23
  ```
24
- or install the lates
24
+ or install the latest
25
25
 
26
26
  ```bash
27
27
 
@@ -40,7 +40,7 @@ or install the lates
40
40
 
41
41
  ```
42
42
 
43
- ![](examples/pics/UiO66water.png)
43
+ ![](examples/pics/UiO66water.webp)
44
44
 
45
45
  ### Zeolite in cylindrical channel
46
46
 
@@ -51,7 +51,7 @@ or install the lates
51
51
 
52
52
  ```
53
53
 
54
- ![](examples/pics/MFIwater.png)
54
+ ![](examples/pics/MFIwater.webp)
55
55
 
56
56
  ### NaCl on surface
57
57
 
@@ -60,7 +60,7 @@ or install the lates
60
60
 
61
61
  ```
62
62
 
63
- ![](examples/pics/NaClwater.png)
63
+ ![](examples/pics/NaClwater.webp)
64
64
 
65
65
  ### MOF ellipsoid
66
66
 
@@ -72,7 +72,7 @@ packmm --system examples/data/Cu2L.cif --molecule examples/data/Ethanol.xyz --nm
72
72
 
73
73
  ```
74
74
 
75
- !()[examples/pics/Cu2L-ethanol.png]
75
+ ![](examples/pics/Cu2L-ethanol.webp)
76
76
 
77
77
  ``` bash
78
78
 
@@ -81,7 +81,7 @@ packmm --system Cu2L-ethanol.cif --molecule H2O --nmols 10 --where ellipsoid --
81
81
 
82
82
  ```
83
83
 
84
- !()[examples/pics/Cu2l-ethanol-water.png]
84
+ ![](examples/pics/Cu2l-ethanol-water.webp)
85
85
 
86
86
  ### Liquid water
87
87
 
@@ -92,7 +92,8 @@ packmm --molecule H2O --nmols 33 --where anywhere --cell-a 10.0 --cell-b 10.0
92
92
 
93
93
  ```
94
94
 
95
- !()[examples/pics/water.png]
95
+ ![](examples/pics/water.webp)
96
+
96
97
  ### interstitials
97
98
 
98
99
  ```bash
@@ -103,13 +104,12 @@ packmm --system Pd-super.cif --molecule H2 --nmols 50 --where anywhere --mode
103
104
 
104
105
  before optimisation
105
106
 
106
- !()[examples/pics/Pd-H2-noopt.png]
107
+ ![](examples/pics/Pd-super+50H2.webp)
107
108
 
108
109
 
109
110
  after optimisation
110
111
 
111
- !()[examples/pics/Pd-H2.png]
112
-
112
+ ![](examples/pics/Pd-H2.webp)
113
113
 
114
114
 
115
115
  ### full list of options
@@ -25,6 +25,22 @@ class InsertionMethod(str, Enum):
25
25
  ELLIPSOID = "ellipsoid"
26
26
 
27
27
 
28
+ class InsertionStrategy(str, Enum):
29
+ """Insertion options."""
30
+
31
+ # propose randomly a point
32
+ MC = "mc"
33
+ # hybrid monte carlo
34
+ HMC = "hmc"
35
+
36
+
37
+ class RelaxStrategy(str, Enum):
38
+ """Relaxation options."""
39
+
40
+ GEOMETRY_OPTIMISATION = "geometry_optimisation"
41
+ MD = "md"
42
+
43
+
28
44
  app = typer.Typer(no_args_is_help=True)
29
45
 
30
46
 
@@ -44,12 +60,26 @@ def packmm(
44
60
  ntries: int = typer.Option(
45
61
  50, help="Maximum number of attempts to insert each molecule."
46
62
  ),
63
+ every: int = typer.Option(
64
+ -1, help="Run MD-NVE or Geometry optimisation everyth insertion."
65
+ ),
47
66
  seed: int = typer.Option(2025, help="Random seed for reproducibility."),
67
+ md_steps: int = typer.Option(10, help="Number of steps to run MD."),
68
+ md_timestep: float = typer.Option(1.0, help="Timestep for MD integration, in fs."),
48
69
  where: InsertionMethod = typer.Option(
49
70
  InsertionMethod.ANYWHERE,
50
71
  help="""Where to insert the molecule. Choices: 'anywhere', 'sphere',
51
72
  'box', 'cylinderZ', 'cylinderY', 'cylinderX', 'ellipsoid'.""",
52
73
  ),
74
+ insert_strategy: InsertionStrategy = typer.Option(
75
+ InsertionStrategy.MC,
76
+ help="""How to insert a new molecule. Choices: 'mc', 'hmc',""",
77
+ ),
78
+ relax_strategy: RelaxStrategy = typer.Option(
79
+ RelaxStrategy.GEOMETRY_OPTIMISATION,
80
+ help="""How to relax the system to get more favourable structures.
81
+ Choices: 'geometry_optimisation', 'md',""",
82
+ ),
53
83
  centre: str | None = typer.Option(
54
84
  None,
55
85
  help="""Centre of the insertion zone, coordinates in Å,
@@ -84,6 +114,9 @@ def packmm(
84
114
  temperature: float = typer.Option(
85
115
  300.0, help="Temperature for the Monte Carlo acceptance rule."
86
116
  ),
117
+ md_temperature: float = typer.Option(
118
+ 100.0, help="Temperature for the Molecular dynamics relaxation."
119
+ ),
87
120
  cell_a: float = typer.Option(
88
121
  20.0, help="Side of the empty box along the x-axis in Å."
89
122
  ),
@@ -125,6 +158,12 @@ def packmm(
125
158
  print(f"{fmax=}")
126
159
  print(f"{geometry=}")
127
160
  print(f"{out_path=}")
161
+ print(f"{every=}")
162
+ print(f"insert_strategy={insert_strategy.value}")
163
+ print(f"relax_strategy={relax_strategy.value}")
164
+ print(f"{md_steps=}")
165
+ print(f"{md_timestep=}")
166
+ print(f"{md_temperature=}")
128
167
  if nmols == -1:
129
168
  print("nothing to do, no molecule to insert")
130
169
  raise typer.Exit(0)
@@ -161,8 +200,10 @@ def packmm(
161
200
  cell_b=cell_b,
162
201
  cell_c=cell_c,
163
202
  out_path=out_path,
203
+ every=every,
204
+ relax_strategy=relax_strategy,
205
+ insert_strategy=insert_strategy,
206
+ md_steps=md_steps,
207
+ md_timestep=md_timestep,
208
+ md_temperature=md_temperature,
164
209
  )
165
-
166
-
167
- if __name__ == "__main__":
168
- app()
@@ -13,6 +13,7 @@ from ase.build import molecule as build_molecule
13
13
  from ase.io import read, write
14
14
  from ase.units import kB
15
15
  from janus_core.calculations.geom_opt import GeomOpt
16
+ from janus_core.calculations.md import NVE
16
17
  from janus_core.helpers.mlip_calculators import choose_calculator
17
18
  from numpy import cos, exp, pi, random, sin, sqrt
18
19
 
@@ -130,7 +131,7 @@ def random_point_in_cylinder(
130
131
  return (x, y, z)
131
132
 
132
133
 
133
- def validate_value(label, x):
134
+ def validate_value(label: str, x: float | int) -> None:
134
135
  """Validate input value, and raise an exception."""
135
136
  if x is not None and x < 0.0:
136
137
  err = f"Invalid {label}, needs to be positive"
@@ -138,6 +139,52 @@ def validate_value(label, x):
138
139
  raise Exception(err)
139
140
 
140
141
 
142
+ def set_random_seed(seed: int) -> None:
143
+ """Set random seed."""
144
+ random.seed(seed)
145
+
146
+
147
+ def set_defaults(
148
+ cell: (float, float, float),
149
+ centre: (float, float, float) | None = None,
150
+ where: str | None = None,
151
+ a: float | None = None,
152
+ b: float | None = None,
153
+ c: float | None = None,
154
+ radius: float | None = None,
155
+ height: float | None = None,
156
+ ) -> tuple(
157
+ (float, float, float),
158
+ float | None,
159
+ float | None,
160
+ float | None,
161
+ float | None,
162
+ float | None,
163
+ ):
164
+ """Set defaults for insertion areas."""
165
+ if centre is None:
166
+ centre = (cell[0] * 0.5, cell[1] * 0.5, cell[2] * 0.5)
167
+
168
+ if where == "anywhere":
169
+ a, b, c = cell[0], cell[1], cell[2]
170
+ elif where == "sphere":
171
+ radius = radius or min(cell) * 0.5
172
+ elif where == "cylinderZ":
173
+ radius = radius or min(cell[0], cell[1]) * 0.5
174
+ height = height or 0.5 * cell[2]
175
+ elif where == "cylinderY":
176
+ radius = radius or min(cell[0], cell[2]) * 0.5
177
+ height = height or 0.5 * cell[1]
178
+ elif where == "cylinderX":
179
+ radius = radius or min(cell[2], cell[1]) * 0.5
180
+ height = height or 0.5 * cell[0]
181
+ elif where == "box":
182
+ a, b, c = a or cell[0], b or cell[1], c or cell[2]
183
+ elif where == "ellipsoid":
184
+ a, b, c = a or cell[0] * 0.5, b or cell[1] * 0.5, c or cell[2] * 0.5
185
+ return (centre, a, b, c, radius, height)
186
+
187
+
141
188
  def pack_molecules(
142
189
  system: str = None,
143
190
  molecule: str = "H2O",
@@ -161,6 +208,12 @@ def pack_molecules(
161
208
  cell_b: float = None,
162
209
  cell_c: float = None,
163
210
  out_path: str = ".",
211
+ every: int = -1,
212
+ relax_strategy: str = "geometry_optimisation",
213
+ insert_strategy: str = "random",
214
+ md_steps: int = 10,
215
+ md_timestep: float = 1.0,
216
+ md_temperature: float = 100.0,
164
217
  ) -> float:
165
218
  """
166
219
  Pack molecules into a system based on the specified parameters.
@@ -185,6 +238,14 @@ def pack_molecules(
185
238
  geometry (bool): Whether to perform geometry optimization after insertion.
186
239
  cell_a, cell_b, cell_c (float): Cell dimensions if system is empty.
187
240
  out_path (str): path to save various outputs
241
+ every (int): After how many instertions to do a relaxation,
242
+ default -1 means none..
243
+ md_temperature (float): Temperature in Kelvin for MD.
244
+ md_steps (int): Number of steps for MD.
245
+ md_timestep (float): Timestep in fs for MD.
246
+ insert_strategy (str): Insert strategy, "random" or "md"
247
+ relax_strategy (str): Relax strategy, "geometry_optimisation" or "md"
248
+
188
249
  """
189
250
  kbt = temperature * kB
190
251
  validate_value("temperature", temperature)
@@ -198,51 +259,31 @@ def pack_molecules(
198
259
  validate_value("ntries", ntries)
199
260
  validate_value("cell box cell a", cell_a)
200
261
  validate_value("cell box cell b", cell_b)
201
- validate_value("nmols", nmols)
202
262
  validate_value("cell box cell c", cell_c)
263
+ validate_value("nmols", nmols)
264
+ validate_value("MD steps", md_steps)
265
+ validate_value("MD timestep", md_timestep)
266
+ validate_value("MD temperature", md_temperature)
203
267
 
204
- random.seed(seed)
268
+ set_random_seed(seed)
205
269
 
206
- try:
207
- sys = read(system)
208
- sysname = Path(system).stem
209
- except Exception:
270
+ if system is None:
210
271
  sys = Atoms(cell=[cell_a, cell_b, cell_c], pbc=[True, True, True])
211
- sysname = "empty"
212
-
213
- cell = sys.cell.lengths()
272
+ sysname = ""
273
+ else:
274
+ sys = read(system)
275
+ sysname = Path(system).stem + "+"
214
276
 
215
277
  # Print summary
216
278
  print(f"Inserting {nmols} {molecule} molecules in {sysname}.")
217
279
  print(f"Using {arch} model {model} on {device}.")
218
280
  print(f"Insert in {where}.")
219
281
 
220
- if center is None:
221
- center = (cell[0] * 0.5, cell[1] * 0.5, cell[2] * 0.5)
282
+ cell = sys.cell.lengths()
222
283
 
223
- if where == "anywhere":
224
- a, b, c = cell[0], cell[1], cell[2]
225
- elif where == "sphere":
226
- if radius is None:
227
- radius = min(cell) * 0.5
228
- elif where in ["cylinderZ", "cylinderY", "cylinderX"]:
229
- if radius is None:
230
- if where == "cylinderZ":
231
- radius = min(cell[0], cell[1]) * 0.5
232
- if height is None:
233
- height = 0.5 * cell[2]
234
- elif where == "cylinderY":
235
- radius = min(cell[0], cell[2]) * 0.5
236
- if height is None:
237
- height = 0.5 * cell[1]
238
- elif where == "cylinderX":
239
- radius = min(cell[2], cell[1]) * 0.5
240
- if height is None:
241
- height = 0.5 * cell[0]
242
- elif where == "box":
243
- a, b, c = a or cell[0], b or cell[1], c or cell[2]
244
- elif where == "ellipsoid":
245
- a, b, c = a or cell[0], b or cell[1], c or cell[2]
284
+ center, a, b, c, radius, height = set_defaults(
285
+ cell, center, where, a, b, c, radius, height
286
+ )
246
287
 
247
288
  calc = choose_calculator(arch=arch, model_path=model, device=device)
248
289
  sys.calc = calc
@@ -250,7 +291,8 @@ def pack_molecules(
250
291
  e = sys.get_potential_energy() if len(sys) > 0 else 0.0
251
292
 
252
293
  csys = sys.copy()
253
- for i in range(nmols):
294
+ i = 0
295
+ while i < nmols:
254
296
  accept = False
255
297
  for _itry in range(ntries):
256
298
  mol = load_molecule(molecule)
@@ -259,6 +301,11 @@ def pack_molecules(
259
301
  mol.translate(tv)
260
302
 
261
303
  tsys = csys.copy() + mol.copy()
304
+ if insert_strategy == "hmc":
305
+ tsys = run_md_nve(
306
+ tsys, md_temperature, md_steps, md_timestep, arch, model, device
307
+ )
308
+
262
309
  tsys.calc = calc
263
310
  en = tsys.get_potential_energy()
264
311
  de = en - e
@@ -270,34 +317,55 @@ def pack_molecules(
270
317
  if u <= acc:
271
318
  accept = True
272
319
  break
320
+ if every > 0 and _itry / every == 0:
321
+ csys = save_the_day(
322
+ Path(out_path) / f"{sysname}{i}{Path(molecule).stem}.cif",
323
+ device,
324
+ arch,
325
+ model,
326
+ fmax,
327
+ out_path,
328
+ md_temperature,
329
+ md_steps,
330
+ md_timestep,
331
+ relax_strategy,
332
+ )
273
333
 
274
334
  if accept:
275
335
  csys = tsys.copy()
276
336
  e = en
277
- print(f"Inserted particle {i + 1}")
278
- write(Path(out_path) / f"{sysname}+{i + 1}{Path(molecule).stem}.cif", csys)
337
+ i += 1
338
+ print(f"Inserted particle {i}")
339
+ write(Path(out_path) / f"{sysname}{i}{Path(molecule).stem}.cif", csys)
279
340
  else:
280
341
  # Things are bad, maybe geomatry optimisation saves us
342
+ # once you hit here is bad, this can keep looping
281
343
  print(f"Failed to insert particle {i + 1} after {ntries} tries")
282
- _ = optimize_geometry(
283
- f"{sysname}+{i + 1}{Path(molecule).stem}.cif",
344
+ csys = save_the_day(
345
+ Path(out_path) / f"{sysname}{i}{Path(molecule).stem}.cif",
284
346
  device,
285
347
  arch,
286
348
  model,
287
349
  fmax,
288
350
  out_path,
351
+ md_temperature,
352
+ md_steps,
353
+ md_timestep,
354
+ relax_strategy,
289
355
  )
356
+
290
357
  energy_final = e
291
358
 
292
359
  # Perform final geometry optimization if requested
293
360
  if geometry:
294
361
  energy_final = optimize_geometry(
295
- f"{sysname}+{nmols}{Path(molecule).stem}.cif",
362
+ Path(out_path) / f"{sysname}{nmols}{Path(molecule).stem}.cif",
296
363
  device,
297
364
  arch,
298
365
  model,
299
366
  fmax,
300
367
  out_path,
368
+ True,
301
369
  )
302
370
  return energy_final
303
371
 
@@ -329,7 +397,6 @@ def get_insertion_position(
329
397
  if where in ["cylinderZ", "cylinderY", "cylinderX"]:
330
398
  axis = where[-1].lower()
331
399
  return random_point_in_cylinder(center, radius, height, axis)
332
- # now is anywhere
333
400
  return random.random(3) * [a, b, c]
334
401
 
335
402
 
@@ -342,6 +409,72 @@ def rotate_molecule(mol):
342
409
  return mol
343
410
 
344
411
 
412
+ def save_the_day(
413
+ struct_path: str = "",
414
+ device: str = "",
415
+ arch: str = "",
416
+ model: str = "",
417
+ fmax: float = 0.01,
418
+ out_path: str = ".",
419
+ md_temperature: float = 100.0,
420
+ md_steps: int = 10,
421
+ md_timestep: float = 1.0,
422
+ relax_strategy: str = "geometry_optimisation",
423
+ ) -> Atoms:
424
+ """Geometry optimisation or MD to get a better structure."""
425
+ if relax_strategy == "geometry_optimisation":
426
+ _ = optimize_geometry(
427
+ struct_path,
428
+ device,
429
+ arch,
430
+ model,
431
+ fmax,
432
+ out_path,
433
+ )
434
+ return read(Path(out_path) / f"{Path(struct_path).stem}-opt.cif")
435
+ if relax_strategy == "md":
436
+ return run_md_nve(
437
+ struct_path, md_temperature, md_steps, md_timestep, arch, model, device
438
+ )
439
+ return None
440
+
441
+
442
+ def run_md_nve(
443
+ struct_path: str | Atoms,
444
+ temp: float = 100.0,
445
+ steps: int = 10,
446
+ timestep: float = 1.0,
447
+ arch: str = "",
448
+ model: str = "",
449
+ device: str = "",
450
+ ) -> Atoms:
451
+ """Run nve simulation."""
452
+ if isinstance(struct_path, Atoms):
453
+ md = NVE(
454
+ struct=struct_path,
455
+ temp=temp,
456
+ device=device,
457
+ arch=arch,
458
+ calc_kwargs={"model_paths": model},
459
+ stats_every=1,
460
+ steps=steps,
461
+ timestep=timestep,
462
+ )
463
+ else:
464
+ md = NVE(
465
+ struct_path=struct_path,
466
+ temp=temp,
467
+ device=device,
468
+ arch=arch,
469
+ calc_kwargs={"model_paths": model},
470
+ stats_every=1,
471
+ steps=steps,
472
+ timestep=timestep,
473
+ )
474
+ md.run()
475
+ return md.struct
476
+
477
+
345
478
  def optimize_geometry(
346
479
  struct_path: str,
347
480
  device: str,
@@ -349,14 +482,16 @@ def optimize_geometry(
349
482
  model: str,
350
483
  fmax: float,
351
484
  out_path: str = ".",
485
+ opt_cell: bool = False,
352
486
  ) -> float:
353
487
  """Optimize the geometry of a structure."""
354
488
  geo = GeomOpt(
355
489
  struct_path=struct_path,
356
490
  device=device,
491
+ arch=arch,
357
492
  fmax=fmax,
358
493
  calc_kwargs={"model_paths": model},
359
- filter_kwargs={"hydrostatic_strain": True},
494
+ filter_kwargs={"hydrostatic_strain": opt_cell},
360
495
  )
361
496
  geo.run()
362
497
  write(Path(out_path) / f"{Path(struct_path).stem}-opt.cif", geo.struct)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pack-mm"
3
- version = "0.0.20"
3
+ version = "0.0.22"
4
4
  description = "packing materials and molecules in boxes using for machine learnt interatomic potentials"
5
5
  authors = [
6
6
  { name = "Alin M. Elena" },
@@ -0,0 +1,372 @@
1
+ """Tests for core."""
2
+
3
+ # -*- coding: utf-8 -*-
4
+ # Author; alin m elena, alin@elena.re
5
+ # Contribs;
6
+ # Date: 22-02-2025
7
+ # ©alin m elena,
8
+ from __future__ import annotations
9
+
10
+ from ase import Atoms
11
+ from ase.build import molecule as build_molecule
12
+ from ase.io import write
13
+ from numpy import random
14
+ import pytest
15
+
16
+ from pack_mm.core.core import (
17
+ get_insertion_position,
18
+ load_molecule,
19
+ optimize_geometry,
20
+ pack_molecules,
21
+ random_point_in_box,
22
+ random_point_in_cylinder,
23
+ random_point_in_ellipsoid,
24
+ random_point_in_sphere,
25
+ rotate_molecule,
26
+ run_md_nve,
27
+ save_the_day,
28
+ set_defaults,
29
+ validate_value,
30
+ )
31
+
32
+ err = 1.0e-8
33
+
34
+
35
+ # Set a fixed seed for reproducibility in tests
36
+ @pytest.fixture(autouse=True)
37
+ def set_random_seed():
38
+ """Set random seed."""
39
+ random.seed(2042)
40
+
41
+
42
+ def test_random_point_in_sphere():
43
+ """Test point in sphere."""
44
+ center = (2, 2, 2)
45
+ radius = 8.0
46
+ x, y, z = random_point_in_sphere(center, radius)
47
+ assert x == pytest.approx(-3.299696236298196, abs=err)
48
+ assert y == pytest.approx(-3.046619861327052, abs=err)
49
+ assert z == pytest.approx(1.1884891239565165, abs=err)
50
+
51
+
52
+ def test_random_point_in_ellipsoid():
53
+ """Test point in ellipsoid."""
54
+ center = (0, 0, 0)
55
+ a, b, c = 2.0, 2.0, 4.0
56
+ x, y, z = random_point_in_ellipsoid(center, a, b, c)
57
+ assert x == pytest.approx(0.27914659851849705, abs=err)
58
+ assert y == pytest.approx(-1.4815802529721946, abs=err)
59
+ assert z == pytest.approx(-1.2059956538672925, abs=err)
60
+
61
+
62
+ def test_random_point_in_box():
63
+ """Test point in box."""
64
+ center = (0, 0, 0)
65
+ a, b, c = 1.0, 2.0, 3.0
66
+ x, y, z = random_point_in_box(center, a, b, c)
67
+ assert x == pytest.approx(0.2796391455704136, abs=err)
68
+ assert y == pytest.approx(0.24221552377157196, abs=err)
69
+ assert z == pytest.approx(0.10546160764197499, abs=err)
70
+
71
+
72
+ def test_random_point_in_cylinder():
73
+ """Test point in cylinder."""
74
+ center = (0, 0, 0)
75
+ radius = 2.0
76
+ height = 5.0
77
+
78
+ direction = "z"
79
+ x, y, z = random_point_in_cylinder(center, radius, height, direction)
80
+ assert x == pytest.approx(0.29184067570623795, abs=err)
81
+ assert y == pytest.approx(-1.5489545079008842, abs=err)
82
+ assert z == pytest.approx(0.1757693460699583, abs=err)
83
+
84
+ direction = "y"
85
+ x, y, z = random_point_in_cylinder(center, radius, height, direction)
86
+ assert x == pytest.approx(-1.417921218944154, abs=err)
87
+ assert y == pytest.approx(-0.8295636754899149, abs=err)
88
+ assert z == pytest.approx(-1.2828806053300128, abs=err)
89
+
90
+ direction = "x"
91
+ x, y, z = random_point_in_cylinder(center, radius, height, direction)
92
+ assert x == pytest.approx(0.5063181926884202, abs=err)
93
+ assert y == pytest.approx(-0.5839696769917422, abs=err)
94
+ assert z == pytest.approx(1.10139156645954, abs=err)
95
+
96
+
97
+ def test_validate_value_positive():
98
+ """Test point in test value."""
99
+ validate_value("test_value", 1.0) # Should not raise an exception
100
+
101
+
102
+ def test_validate_value_negative():
103
+ """Test point in test value."""
104
+ with pytest.raises(Exception, match="Invalid test_value, needs to be positive"):
105
+ validate_value("test_value", -1.0)
106
+
107
+
108
+ def test_load_molecule_from_file(tmp_path):
109
+ """Test point in load molecule."""
110
+ molecule = build_molecule("H2O")
111
+ molecule_file = tmp_path / "water.xyz"
112
+ write(molecule_file, molecule)
113
+ loaded_molecule = load_molecule(str(molecule_file))
114
+ assert len(loaded_molecule) == 3
115
+
116
+
117
+ def test_load_molecule_from_name():
118
+ """Test point in load molecule."""
119
+ molecule = load_molecule("H2O")
120
+ assert len(molecule) == 3
121
+
122
+
123
+ def test_get_insertion_position_sphere():
124
+ """Test point in sphere."""
125
+ center = (5, 5, 5)
126
+ radius = 10.0
127
+ x, y, z = get_insertion_position("sphere", center, radius=radius)
128
+ assert x == pytest.approx(-1.624620295372745, abs=err)
129
+ assert y == pytest.approx(-1.3082748266588151, abs=err)
130
+ assert z == pytest.approx(3.9856114049456455, abs=err)
131
+
132
+
133
+ def test_get_insertion_position_box():
134
+ """Test point in box."""
135
+ center = (5, 5, 5)
136
+ a = 10.0
137
+ x, y, z = get_insertion_position("box", center, a=a, b=a, c=a)
138
+ assert x == pytest.approx(7.796391455704136, abs=err)
139
+ assert y == pytest.approx(6.21107761885786, abs=err)
140
+ assert z == pytest.approx(5.351538692139917, abs=err)
141
+
142
+
143
+ def test_get_insertion_position_ellipsoid():
144
+ """Test point in ellipsoid."""
145
+ center = (5, 5, 5)
146
+ x, y, z = get_insertion_position("ellipsoid", center, a=2.0, b=2.0, c=4.0)
147
+ assert x == pytest.approx(5.279146598518497, abs=err)
148
+ assert y == pytest.approx(3.5184197470278056, abs=err)
149
+ assert z == pytest.approx(3.7940043461327075, abs=err)
150
+
151
+
152
+ def test_get_insertion_position_cylinder():
153
+ """Test point in cylinder."""
154
+ center = (5, 5, 5)
155
+ x, y, z = get_insertion_position("cylinderZ", center, radius=3.0, height=10.0)
156
+ assert x == pytest.approx(5.437761013559357, abs=err)
157
+ assert y == pytest.approx(2.6765682381486737, abs=err)
158
+ assert z == pytest.approx(5.351538692139917, abs=err)
159
+
160
+
161
+ def test_set_defaults_centre():
162
+ """Test centre."""
163
+ cell = [10, 10, 10]
164
+ centre, _, _, _, _, _ = set_defaults(cell, centre=None)
165
+ assert centre[0] == pytest.approx(5.0, abs=err)
166
+ assert centre[1] == pytest.approx(5.0, abs=err)
167
+ assert centre[2] == pytest.approx(5.0, abs=err)
168
+
169
+
170
+ def test_set_defaults_box():
171
+ """Test box."""
172
+ cell = [10, 10, 10]
173
+ centre, a, b, c, _, _ = set_defaults(cell, where="box", b=5.0, centre=None)
174
+ assert centre[0] == pytest.approx(5.0, abs=err)
175
+ assert a == pytest.approx(10.0, abs=err)
176
+ assert b == pytest.approx(5.0, abs=err)
177
+ assert c == pytest.approx(10.0, abs=err)
178
+
179
+
180
+ def test_set_defaults_anywhere():
181
+ """Test box."""
182
+ cell = [10, 10, 10]
183
+ centre, a, b, c, _, _ = set_defaults(cell, where="anywhere", centre=None)
184
+ assert centre[0] == pytest.approx(5.0, abs=err)
185
+ assert a == pytest.approx(10.0, abs=err)
186
+ assert b == pytest.approx(10.0, abs=err)
187
+ assert c == pytest.approx(10.0, abs=err)
188
+
189
+
190
+ def test_set_defaults_ellipsoid():
191
+ """Test ellipsoid."""
192
+ cell = [10, 10, 10]
193
+ centre, a, b, c, _, _ = set_defaults(
194
+ cell, where="ellipsoid", b=3.0, a=3.0, centre=None
195
+ )
196
+ assert centre[0] == pytest.approx(5.0, abs=err)
197
+ assert a == pytest.approx(3.0, abs=err)
198
+ assert b == pytest.approx(3.0, abs=err)
199
+ assert c == pytest.approx(5.0, abs=err)
200
+
201
+
202
+ def test_set_defaults_cylinder():
203
+ """Test ellipsoid."""
204
+ cell = [10, 12, 14]
205
+ centre, _, _, _, radius, height = set_defaults(cell, where="cylinderZ", centre=None)
206
+ assert centre[0] == pytest.approx(5.0, abs=err)
207
+ assert radius == pytest.approx(5.0, abs=err)
208
+ assert height == pytest.approx(7.0, abs=err)
209
+
210
+ centre, _, _, _, radius, height = set_defaults(cell, where="cylinderX", centre=None)
211
+ assert centre[0] == pytest.approx(5.0, abs=err)
212
+ assert radius == pytest.approx(6.0, abs=err)
213
+ assert height == pytest.approx(5.0, abs=err)
214
+
215
+ centre, _, _, _, radius, height = set_defaults(cell, where="cylinderY", centre=None)
216
+ assert centre[0] == pytest.approx(5.0, abs=err)
217
+ assert radius == pytest.approx(5.0, abs=err)
218
+ assert height == pytest.approx(6.0, abs=err)
219
+
220
+
221
+ def test_rotate_molecule():
222
+ """Test rotate molecule."""
223
+ molecule = build_molecule("H2O")
224
+ a1 = molecule.get_angle(2, 0, 1)
225
+ a2 = molecule.get_angle(2, 1, 0)
226
+
227
+ rotated_molecule = rotate_molecule(molecule)
228
+ assert a1 == pytest.approx(rotated_molecule.get_angle(2, 0, 1))
229
+ assert a2 == pytest.approx(rotated_molecule.get_angle(2, 1, 0))
230
+
231
+
232
+ def test_optimize_geometry(tmp_path):
233
+ """Test go."""
234
+ molecule = build_molecule("H2O")
235
+ molecule.set_cell([10, 10, 10])
236
+ molecule.set_pbc([True, True, True])
237
+ structure_file = tmp_path / "water.cif"
238
+ write(structure_file, molecule)
239
+ optimized_energy = optimize_geometry(
240
+ str(structure_file),
241
+ device="cpu",
242
+ arch="mace_mp",
243
+ model="small-0b2",
244
+ fmax=0.01,
245
+ out_path=tmp_path,
246
+ opt_cell=True,
247
+ )
248
+ assert optimized_energy == pytest.approx(-14.17098106193308, abs=err)
249
+
250
+
251
+ def test_pack_molecules(tmp_path):
252
+ """Test pack molecule."""
253
+ system = Atoms(
254
+ "Ca", positions=[(5.0, 5.0, 5.0)], cell=[10, 10, 10], pbc=[True, True, True]
255
+ )
256
+ system_file = tmp_path / "system.cif"
257
+ write(system_file, system)
258
+
259
+ e = pack_molecules(
260
+ system=str(system_file),
261
+ molecule="H2O",
262
+ nmols=2,
263
+ arch="mace_mp",
264
+ model="small-0b2",
265
+ device="cpu",
266
+ where="sphere",
267
+ center=(5.0, 5.0, 5.0),
268
+ radius=5.0,
269
+ seed=2042,
270
+ temperature=300,
271
+ ntries=10,
272
+ geometry=False,
273
+ fmax=0.1,
274
+ out_path=tmp_path,
275
+ )
276
+
277
+ assert (tmp_path / "system+1H2O.cif").exists()
278
+ assert (tmp_path / "system+2H2O.cif").exists()
279
+ assert e == pytest.approx(-29.21589570470306, abs=err)
280
+
281
+
282
+ def test_pack_molecules_2(tmp_path, capsys):
283
+ """Test pack molecule."""
284
+ system = Atoms(
285
+ "Ca", positions=[(2.5, 2.5, 2.5)], cell=[5, 5, 5], pbc=[True, True, True]
286
+ )
287
+ system_file = tmp_path / "system.cif"
288
+ write(system_file, system)
289
+
290
+ e = pack_molecules(
291
+ system=str(system_file),
292
+ molecule="H2O",
293
+ nmols=3,
294
+ arch="mace_mp",
295
+ model="small-0b2",
296
+ device="cpu",
297
+ where="sphere",
298
+ center=(2.5, 2.5, 2.5),
299
+ radius=2.5,
300
+ seed=2042,
301
+ temperature=300,
302
+ ntries=2,
303
+ geometry=False,
304
+ fmax=0.1,
305
+ out_path=tmp_path,
306
+ )
307
+ captured = capsys.readouterr()
308
+
309
+ assert "Failed to insert particle 3 after 2 tries" in captured.out
310
+ assert e == pytest.approx(-47.19506254437384, abs=err)
311
+
312
+
313
+ def test_save_the_day(tmp_path):
314
+ """Test save the day."""
315
+ molecule = build_molecule("H2O")
316
+ molecule.set_cell([10, 10, 10])
317
+ molecule.set_pbc([True, True, True])
318
+ molecule.center()
319
+ structure_file = tmp_path / "water.cif"
320
+ write(structure_file, molecule)
321
+ print(molecule.positions)
322
+ s = save_the_day(
323
+ str(structure_file),
324
+ device="cpu",
325
+ arch="mace_mp",
326
+ model="small-0b2",
327
+ fmax=0.01,
328
+ out_path=tmp_path,
329
+ )
330
+ print(s.positions)
331
+ assert s[0].position == pytest.approx([5.0, 4.99619815, 5.30704738], abs=err)
332
+
333
+
334
+ def test_save_the_day_md(tmp_path):
335
+ """Test save the day."""
336
+ molecule = build_molecule("H2O")
337
+ molecule.set_cell([10, 10, 10])
338
+ molecule.set_pbc([True, True, True])
339
+ molecule.center()
340
+ structure_file = tmp_path / "water.cif"
341
+ write(structure_file, molecule)
342
+ print(molecule.positions)
343
+ s = save_the_day(
344
+ str(structure_file),
345
+ device="cpu",
346
+ arch="mace_mp",
347
+ model="small-0b2",
348
+ relax_strategy="md",
349
+ md_timestep=1.0,
350
+ md_steps=10.0,
351
+ md_temperature=100.0,
352
+ )
353
+ print(s.positions)
354
+ assert s[0].position == pytest.approx([4.99684244, 5.00440785, 5.2987255], abs=err)
355
+
356
+
357
+ def test_run_md(tmp_path):
358
+ """Test md."""
359
+ molecule = build_molecule("H2O")
360
+ molecule.set_cell([10, 10, 10])
361
+ molecule.set_pbc([True, True, True])
362
+ molecule.center()
363
+ s = run_md_nve(
364
+ molecule,
365
+ device="cpu",
366
+ arch="mace_mp",
367
+ model="small-0b2",
368
+ timestep=1.0,
369
+ steps=10.0,
370
+ temp=100.0,
371
+ )
372
+ assert s[0].position == pytest.approx([4.99684244, 5.00440785, 5.2987255], abs=err)
@@ -25,14 +25,14 @@ def test_packmm_custom_molecule():
25
25
  """Check molecule."""
26
26
  result = runner.invoke(app, ["--molecule", "CO2"])
27
27
  assert result.exit_code == 0
28
- assert "CO2" in strip_ansi_codes(result.output)
28
+ assert "molecule='CO2'" in strip_ansi_codes(result.output)
29
29
 
30
30
 
31
31
  def test_packmm_custom_nmols():
32
32
  """Check nmols."""
33
- result = runner.invoke(app, ["--nmols", "1"])
34
- assert result.exit_code == 0
35
- assert "nmols=1" in strip_ansi_codes(result.output)
33
+ result = runner.invoke(app, ["--nmols", "-2"])
34
+ assert result.exit_code == 1
35
+ assert "nmols=-2" in strip_ansi_codes(result.output)
36
36
 
37
37
 
38
38
  def test_packmm_custom_ntries():
@@ -49,6 +49,13 @@ def test_packmm_custom_seed():
49
49
  assert "seed=1234" in strip_ansi_codes(result.output)
50
50
 
51
51
 
52
+ def test_packmm_custom_every():
53
+ """Check seed."""
54
+ result = runner.invoke(app, ["--every", "10"])
55
+ assert result.exit_code == 0
56
+ assert "every=10" in strip_ansi_codes(result.output)
57
+
58
+
52
59
  def test_packmm_custom_insertion_method():
53
60
  """Check insertion."""
54
61
  result = runner.invoke(app, ["--where", "sphere"])
@@ -56,6 +63,20 @@ def test_packmm_custom_insertion_method():
56
63
  assert "where=sphere" in strip_ansi_codes(result.output)
57
64
 
58
65
 
66
+ def test_packmm_custom_insert_strategy():
67
+ """Check insertion."""
68
+ result = runner.invoke(app, ["--insert-strategy", "hmc"])
69
+ assert result.exit_code == 0
70
+ assert "insert_strategy=hmc" in strip_ansi_codes(result.output)
71
+
72
+
73
+ def test_packmm_custom_relax_strategy():
74
+ """Check relax."""
75
+ result = runner.invoke(app, ["--relax-strategy", "md"])
76
+ assert result.exit_code == 0
77
+ assert "relax_strategy=md" in strip_ansi_codes(result.output)
78
+
79
+
59
80
  def test_packmm_custom_center():
60
81
  """Check centre."""
61
82
  result = runner.invoke(app, ["--centre", "0.5,0.5,0.5"])
@@ -122,6 +143,27 @@ def test_packmm_custom_temperature():
122
143
  assert "temperature=400.0" in strip_ansi_codes(result.output)
123
144
 
124
145
 
146
+ def test_packmm_md_temperature():
147
+ """Check md temperature."""
148
+ result = runner.invoke(app, ["--md-temperature", "300.0"])
149
+ assert result.exit_code == 0
150
+ assert "md_temperature=300.0" in strip_ansi_codes(result.output)
151
+
152
+
153
+ def test_packmm_md_timestep():
154
+ """Check md temperature."""
155
+ result = runner.invoke(app, ["--md-timestep", "1.0"])
156
+ assert result.exit_code == 0
157
+ assert "md_timestep=1.0" in strip_ansi_codes(result.output)
158
+
159
+
160
+ def test_packmm_md_steps():
161
+ """Check md steps."""
162
+ result = runner.invoke(app, ["--md-steps", "10"])
163
+ assert result.exit_code == 0
164
+ assert "md_steps=10" in strip_ansi_codes(result.output)
165
+
166
+
125
167
  def test_packmm_custom_fmax():
126
168
  """Check fmax."""
127
169
  result = runner.invoke(app, ["--fmax", "0.05"])
@@ -143,16 +185,41 @@ def test_packmm_invalid_insertion_method():
143
185
  assert "Invalid value for '--where'" in strip_ansi_codes(result.output)
144
186
 
145
187
 
146
- def test_packmm_invalid_centre_format():
147
- """Check centre."""
148
- result = runner.invoke(app, ["--nmols", "1", "--centre", "0.5,0.5"])
188
+ def test_packmm_invalid_insertion_strategy():
189
+ """Check insertion strategt."""
190
+ result = runner.invoke(app, ["--insert-strategy", "invalid_method"])
149
191
  assert result.exit_code != 0
150
- assert "Invalid centre" in strip_ansi_codes(result.output)
192
+ assert "Invalid value for '--insert-strategy'" in strip_ansi_codes(result.output)
193
+
194
+
195
+ def test_packmm_invalid_relax_strategy():
196
+ """Check insertion strategt."""
197
+ result = runner.invoke(app, ["--relax-strategy", "invalid_method"])
198
+ assert result.exit_code != 0
199
+ assert "Invalid value for '--relax-strategy'" in strip_ansi_codes(result.output)
200
+
201
+
202
+ def test_packmm_invalid_md_steps():
203
+ """Check md steps."""
204
+ result = runner.invoke(app, ["--nmols", "1", "--md-steps", "-10"])
205
+ assert "Invalid MD steps" in strip_ansi_codes(result.output)
206
+
207
+
208
+ def test_packmm_invalid_md_temperature():
209
+ """Check md steps."""
210
+ result = runner.invoke(app, ["--nmols", "1", "--md-temperature", "-10.0"])
211
+ assert "Invalid MD temperature" in strip_ansi_codes(result.output)
151
212
 
152
213
 
153
- def test_packmm_invalid_centre_value():
214
+ def test_packmm_invalid_md_timestep():
215
+ """Check md timestep."""
216
+ result = runner.invoke(app, ["--nmols", "1", "--md-timestep", "-10.0"])
217
+ assert "Invalid MD timestep" in strip_ansi_codes(result.output)
218
+
219
+
220
+ def test_packmm_invalid_centre_format():
154
221
  """Check centre."""
155
- result = runner.invoke(app, ["--nmols", "1", "--centre", "-0.6,0.5,0.5"])
222
+ result = runner.invoke(app, ["--nmols", "1", "--centre", "0.5,0.5"])
156
223
  assert result.exit_code != 0
157
224
  assert "Invalid centre" in strip_ansi_codes(result.output)
158
225
 
@@ -1,178 +0,0 @@
1
- """Test cli for core."""
2
-
3
- # -*- coding: utf-8 -*-
4
- # Author; alin m elena, alin@elena.re
5
- # Contribs;
6
- # Date: 22-02-2025
7
- # ©alin m elena,
8
- from __future__ import annotations
9
-
10
- from ase import Atoms
11
- from ase.build import molecule as build_molecule
12
- from ase.io import write
13
- import numpy as np
14
- from numpy import random
15
- import pytest
16
-
17
- from pack_mm.core.core import (
18
- get_insertion_position,
19
- load_molecule,
20
- optimize_geometry,
21
- pack_molecules,
22
- random_point_in_box,
23
- random_point_in_cylinder,
24
- random_point_in_ellipsoid,
25
- random_point_in_sphere,
26
- rotate_molecule,
27
- validate_value,
28
- )
29
-
30
-
31
- # Set a fixed seed for reproducibility in tests
32
- @pytest.fixture(autouse=True)
33
- def set_random_seed():
34
- """Set random seed."""
35
- random.seed(2042)
36
-
37
-
38
- def test_random_point_in_sphere():
39
- """Test point in sphere."""
40
- center = (0, 0, 0)
41
- radius = 10.0
42
- point = random_point_in_sphere(center, radius)
43
- assert len(point) == 3
44
- distance = np.linalg.norm(np.array(point) - np.array(center))
45
- assert distance <= radius
46
-
47
-
48
- def test_random_point_in_ellipsoid():
49
- """Test point in ellipsoid."""
50
- center = (0, 0, 0)
51
- a, b, c = 1.0, 2.0, 3.0
52
- point = random_point_in_ellipsoid(center, a, b, c)
53
- assert len(point) == 3
54
- x, y, z = point
55
- assert (x**2 / a**2) + (y**2 / b**2) + (z**2 / c**2) <= 1.0
56
-
57
-
58
- def test_random_point_in_box():
59
- """Test point in box."""
60
- center = (0, 0, 0)
61
- a, b, c = 1.0, 2.0, 3.0
62
- point = random_point_in_box(center, a, b, c)
63
- assert len(point) == 3
64
- x, y, z = point
65
- assert center[0] - a * 0.5 <= x <= center[0] + a * 0.5
66
- assert center[1] - b * 0.5 <= y <= center[1] + b * 0.5
67
- assert center[2] - c * 0.5 <= z <= center[2] + c * 0.5
68
-
69
-
70
- def test_random_point_in_cylinder():
71
- """Test point in cylinder."""
72
- center = (0, 0, 0)
73
- radius = 1.0
74
- height = 2.0
75
- direction = "z"
76
- point = random_point_in_cylinder(center, radius, height, direction)
77
- assert len(point) == 3
78
- x, y, z = point
79
- assert x**2 + y**2 <= radius**2
80
- assert center[2] - height * 0.5 <= z <= center[2] + height * 0.5
81
-
82
-
83
- def test_validate_value_positive():
84
- """Test point in test value."""
85
- validate_value("test_value", 1.0) # Should not raise an exception
86
-
87
-
88
- def test_validate_value_negative():
89
- """Test point in test value."""
90
- with pytest.raises(Exception, match="Invalid test_value, needs to be positive"):
91
- validate_value("test_value", -1.0)
92
-
93
-
94
- def test_load_molecule_from_file(tmp_path):
95
- """Test point in load molecule."""
96
- molecule = build_molecule("H2O")
97
- molecule_file = tmp_path / "water.xyz"
98
- molecule.write(molecule_file)
99
- loaded_molecule = load_molecule(str(molecule_file))
100
- assert isinstance(loaded_molecule, Atoms)
101
- assert len(loaded_molecule) == 3 # H2O has 3 atoms
102
-
103
-
104
- def test_load_molecule_from_name():
105
- """Test point in load molecule."""
106
- molecule = load_molecule("H2O")
107
- assert isinstance(molecule, Atoms)
108
- assert len(molecule) == 3 # H2O has 3 atoms
109
-
110
-
111
- def test_get_insertion_position_sphere():
112
- """Test point in sphere."""
113
- center = (0, 0, 0)
114
- radius = 10.0
115
- point = get_insertion_position("sphere", center, radius=radius)
116
- assert len(point) == 3
117
- distance = np.linalg.norm(np.array(point) - np.array(center))
118
- assert distance <= radius
119
-
120
-
121
- def test_rotate_molecule():
122
- """Test rotate molecule."""
123
- molecule = build_molecule("H2O")
124
- rotated_molecule = rotate_molecule(molecule)
125
- assert isinstance(rotated_molecule, Atoms)
126
- assert len(rotated_molecule) == 3 # H2O has 3 atoms
127
-
128
-
129
- def test_optimize_geometry(tmp_path):
130
- """Test go."""
131
- # Create a temporary structure file
132
- molecule = build_molecule("H2O")
133
- molecule.set_cell([10, 10, 10])
134
- molecule.set_pbc([True, True, True])
135
- structure_file = tmp_path / "water.cif"
136
- write(structure_file, molecule)
137
- optimized_energy = optimize_geometry(
138
- str(structure_file),
139
- device="cpu",
140
- arch="mace_mp",
141
- model="medium-omat-0",
142
- fmax=0.01,
143
- )
144
- assert optimized_energy == pytest.approx(-13.759273983276572, abs=1.0e-8)
145
-
146
-
147
- # Test pack_molecules with a simple case
148
- def test_pack_molecules(tmp_path):
149
- """Test pack molecule."""
150
- # Create a temporary system file
151
- system = Atoms(
152
- "Ca", positions=[(5.0, 5.0, 5.0)], cell=[10, 10, 10], pbc=[True, True, True]
153
- )
154
- system_file = tmp_path / "system.cif"
155
- write(system_file, system)
156
-
157
- # Test packing molecules
158
- e = pack_molecules(
159
- system=str(system_file),
160
- molecule="H2O",
161
- nmols=2,
162
- arch="mace_mp",
163
- model="medium-omat-0",
164
- device="cpu",
165
- where="sphere",
166
- center=(5.0, 5.0, 5.0),
167
- radius=5.0,
168
- seed=2042,
169
- temperature=300,
170
- ntries=10,
171
- geometry=False,
172
- fmax=0.1,
173
- out_path=tmp_path,
174
- )
175
-
176
- assert (tmp_path / "system+1H2O.cif").exists()
177
- assert (tmp_path / "system+2H2O.cif").exists()
178
- assert e == pytest.approx(-28.251229837533085, abs=1.0e-6)
File without changes
File without changes
File without changes
File without changes