Skip to content

Commit c4df253

Browse files
committed
lammps restart, image_array args
1 parent a16ae2e commit c4df253

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

asimtools/asimmodules/lammps/lammps.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
Author: mkphuthi@github.com
66
'''
7-
from typing import Dict, Optional
7+
from typing import Dict, Optional, Sequence
88
import sys
99
from pathlib import Path
1010
from numpy.random import randint
@@ -24,6 +24,8 @@ def lammps(
2424
masses: bool = True,
2525
velocities: bool = False,
2626
seed: Optional[int] = None,
27+
restart_template: Optional[str] = None,
28+
specorder: Sequence[str] = None,
2729
) -> Dict:
2830
"""Runs a lammps script based on a specified template, variables can be
2931
specified as arguments to be defined in the final LAMMPS input file if
@@ -55,6 +57,12 @@ def lammps(
5557
seed to be placed, if seed=None, a random one is generated,
5658
defaults to None
5759
:type seed: int, optional
60+
:param restart_template: Optional lammps input template to be used to
61+
generate a restart.lammps file, defaults to None
62+
:type restart_template: str, optional
63+
:param specorder: Optional list of atomic species in the order they
64+
should appear in the LAMMPS data input file, defaults to None
65+
:type specorder: Sequence[str], optional
5866
:return: LAMMPS out file names
5967
:rtype: Dict
6068
"""
@@ -73,6 +81,7 @@ def lammps(
7381
atom_style=atom_style,
7482
masses=masses,
7583
velocities=velocities,
84+
specorder=specorder,
7685
)
7786
except ValueError as te:
7887
err_txt = 'Need ASE version >=3.23 to support writing '
@@ -92,6 +101,7 @@ def lammps(
92101
variables['IMAGE_FILE'] = 'image_input.lmpdat'
93102

94103
lmp_txt = ''
104+
95105
for variable, value in variables.items():
96106
lmp_txt += f'variable {variable} equal {value}\n'
97107

@@ -103,6 +113,18 @@ def lammps(
103113
if placeholders is None:
104114
placeholders = {}
105115

116+
if restart_template is not None:
117+
restart_txt = lmp_txt
118+
with open(restart_template, 'r', encoding='utf-8') as f:
119+
restart_lines = f.readlines()
120+
for rline in restart_lines:
121+
if placeholders is not None:
122+
for placeholder in placeholders:
123+
rline = rline.replace(placeholder, str(placeholders[placeholder]))
124+
restart_txt += rline
125+
with open('restart.lammps', 'w', encoding='utf-8') as f:
126+
f.write(restart_txt)
127+
106128
for line in lines:
107129
if 'SEED' in line and 'SEED' not in placeholders:
108130
if seed is None:

asimtools/asimmodules/workflows/image_array.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def image_array(
9090
secondary_array_values=secondary_array_values,
9191
)
9292

93+
if template_sim_input is not None:
94+
subsim_input = template_sim_input
9395
if key_sequence is None:
9496
key_sequence = ['args', 'image']
9597
# For backwards compatibility where we don't have to specify image

asimtools/utils.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ def get_atoms(
245245
builder: Optional[str] = 'bulk',
246246
atoms: Optional[Atoms] = None,
247247
repeat: Optional[Tuple[int, int, int]] = None,
248+
repeat_to_N_args: Optional[Dict] = None,
248249
rattle_stdev: Optional[float] = None,
249250
mp_id: Optional[str] = None,
250251
user_api_key: Optional[str] = None,
@@ -329,14 +330,16 @@ def get_atoms(
329330
>>> get_atoms(image_file='molecules.xyz', index=0) # Pick out one structure using indexing
330331
Atoms(symbols='OH2', pbc=False)
331332
332-
You can also make supercells and rattle the atoms
333+
You can also make supercells and rattle the atoms or repeat to a target
333334
334335
>>> li_bulk = get_atoms(name='Li')
335336
>>> li_bulk.write('POSCAR', format='vasp')
336337
>>> get_atoms(image_file='POSCAR', repeat=[3,3,3])
337338
Atoms(symbols='Li27', pbc=True, cell=[[-5.235, 5.235, 5.235], [5.235, -5.235, 5.235], [5.235, 5.235, -5.235]])
338339
>>> get_atoms(builder='bulk', name='Li', repeat=[2,2,2], rattle_stdev=0.01)
339340
Atoms(symbols='Li8', pbc=True, cell=[[-3.49, 3.49, 3.49], [3.49, -3.49, 3.49], [3.49, 3.49, -3.49]])
341+
>>> get_atoms(builder='bulk', name='Li', repeat_to_N_args={'N': 16, 'max_dim': 10.0})
342+
Atoms(symbols='Li16', pbc=True, cell=[[-6.98, 6.98, 6.98], [6.98, -6.98, 6.98], [6.98, 6.98, -6.98]])
340343
341344
Mostly for internal use and use in asimmodules, one can specify atoms
342345
directly
@@ -439,6 +442,13 @@ def get_atoms(
439442
elif rattle_stdev is not None and interface == 'pymatgen':
440443
struct.perturb(distance=rattle_stdev, min_distance=0)
441444

445+
if repeat_to_N_args is not None and interface == 'ase':
446+
atoms = repeat_to_N(atoms, **repeat_to_N_args)
447+
elif repeat_to_N_args is not None and interface == 'pymatgen':
448+
raise NotImplementedError(
449+
'repeat_to_N_args is only implemented for ASE interface'
450+
)
451+
442452
if constraints is not None and interface == 'ase':
443453
consts = []
444454
for constraint_args in constraints:
@@ -491,6 +501,47 @@ def parse_slice(value: str, bash: bool = False) -> slice:
491501
parts.append('1')
492502
return f'$(seq {parts[0]} {parts[2]} {parts[1]})'
493503

504+
def repeat_to_N(
505+
atoms: Atoms,
506+
N: int,
507+
max_dim: float = 50.0,
508+
) -> Atoms:
509+
"""Scale a structure to have approximately N atoms in the unit cell.
510+
The function repeats the shortest axis of the unit cell until the
511+
number of atoms >= N or the longest axis of the unit cell is > max_dim.
512+
513+
:param atoms: Input atoms object
514+
:type atoms: Atoms
515+
:param N: Target number of atoms
516+
:type N: int
517+
:param max_dim: Maximum length of the longest axis of the unit cell,
518+
defaults to 50.0
519+
:type max_dim: float, optional
520+
:raises ValueError: If it fails to scale the structure to N atoms
521+
:return: Scaled atoms object
522+
:rtype: Atoms
523+
"""
524+
cell = atoms.get_cell()
525+
lengths = [np.linalg.norm(vec) for vec in cell]
526+
num_atoms = len(atoms)
527+
new_atoms = atoms.copy()
528+
529+
while num_atoms < N and np.max(lengths) < max_dim:
530+
shortest_axis = np.argmin(lengths)
531+
repeat_vec = [1, 1, 1]
532+
repeat_vec[shortest_axis] += 1
533+
new_atoms = new_atoms.repeat(repeat_vec)
534+
cell = new_atoms.get_cell()
535+
lengths = [np.linalg.norm(vec) for vec in cell]
536+
num_atoms = len(new_atoms)
537+
538+
if num_atoms < N:
539+
raise ValueError(
540+
f'Failed to scale structure to {N} atoms without exceeding \
541+
max_dim of {max_dim} Angstroms'
542+
)
543+
return new_atoms
544+
494545
def get_images(
495546
image_file: str = None,
496547
pattern: str = None,

tests/unit/test_utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
get_str_btn,
2525
expand_wildcards,
2626
write_atoms,
27+
repeat_to_N,
2728
)
2829
import ase.build
2930

@@ -132,6 +133,7 @@ def test_join_names(test_input, expected):
132133
])
133134
def test_get_atoms(test_input, expected):
134135
''' Test getting atoms from different inputs '''
136+
print('e', expected)
135137
assert get_atoms(**test_input) == expected
136138

137139
def test_get_atoms_constraints(tmp_path):
@@ -373,4 +375,16 @@ def test_expand_wildcards(test_input, expected, tmp_path):
373375
f.write('')
374376
print(f'Found paths in {os.getcwd()}: {[f for f in Path(tmp_path).glob("*")]}')
375377

376-
assert expand_wildcards(test_input, root_path=tmp_path) == expected
378+
assert expand_wildcards(test_input, root_path=tmp_path) == expected
379+
380+
def test_repeat_to_N():
381+
''' Test repeating unit cell to at least N atoms '''
382+
atoms = ase.build.bulk('Cu', crystalstructure='fcc', cubic=True, a=2.0)
383+
repeated_atoms = repeat_to_N(atoms, 16)
384+
assert len(repeated_atoms) == 16
385+
assert np.abs(repeated_atoms.get_cell()[0][0] - 2*2.0) < 1e-6
386+
assert np.abs(repeated_atoms.get_cell()[1][1] - 2*2.0) < 1e-6
387+
assert np.abs(repeated_atoms.get_cell()[2][2] - 1*2.0) < 1e-6
388+
assert len(repeat_to_N(atoms, 15)) == 16
389+
with pytest.raises(ValueError):
390+
repeat_to_N(atoms, 16, max_dim=4)

0 commit comments

Comments
 (0)