Skip to content

Commit d15d3f4

Browse files
committed
get_atoms constraints, asim-check job_ids
1 parent 44bba9f commit d15d3f4

File tree

4 files changed

+61
-16
lines changed

4 files changed

+61
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ the string at the given key_sequence
1515
https://www.chemie.uni-bonn.de/grimme/de/software/dft-d3/get_dft-d3. 2. Some
1616
calculators which return a 3x3 matrix for stress will break. One can modify
1717
ASE source for this as ASIMTools can't go into the calculator code.
18+
- asim_check now also reports the job_ids
19+
- get_atoms now allows addition of FixAtoms constraint
1820

1921
### Changed
2022
- VASP interface changed to align more with pymatgen

asimtools/scripts/asim_check.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,9 @@ def print_job_tree(
133133
workdir = job_tree['workdir_name']
134134
status, color = get_status_and_color(job_tree['job'])
135135
asimmodule = job_tree['job'].sim_input['asimmodule']
136+
job_ids = job_tree['job'].get_output().get('job_ids', 'none')
136137
print(color + f'{indent_str}{workdir}, asimmodule: {asimmodule},' + \
137-
f'status: {status}' + reset)
138+
f'status: {status}, job_ids: {job_ids}' + reset)
138139
if level > 0:
139140
indent_str = '| ' + ' ' * level
140141
for subjob_id in subjobs:
@@ -149,9 +150,10 @@ def print_job_tree(
149150
subjob_dir = job_tree['workdir_name']
150151
subjob = job_tree['job']
151152
asimmodule = subjob.sim_input['asimmodule']
153+
job_ids = job_tree['job'].get_output().get('job_ids', 'none')
152154
status, color = get_status_and_color(subjob)
153155
print(color + f'{indent_str}{subjob_dir}, asimmodule: {asimmodule}, '+\
154-
f'status: {status}' + reset)
156+
f'status: {status}, job_ids: {job_ids}' + reset)
155157

156158
if __name__ == "__main__":
157159
main(sys.argv[1:])

asimtools/utils.py

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from ase.parallel import paropen
2222
import ase.db
2323
import ase.build
24+
from ase.constraints import FixAtoms
2425
from pymatgen.core import Structure, Lattice
2526
from pymatgen.io.ase import AseAtomsAdaptor
2627

@@ -194,19 +195,22 @@ def write_atoms(
194195
if fmt in ['extxyz']:
195196
if kwargs.get('write_info', False):
196197
write_info = kwargs.pop('write_info')
197-
# if kwargs.get('columns', False):
198-
# columns = kwargs.pop('columns')
199-
# else:
200-
# reserved_ks = ['symbols', 'positions', 'numbers', 'species', 'pos']
201-
# columns = ['symbols', 'positions'] + kwargs.get(
202-
# 'columns',
203-
# [k for k in atoms.arrays.keys() if k not in reserved_ks]
204-
# )
205-
# if columns_append is not None:
206-
# columns += columns_append
207-
208-
# if len(atoms.constraints) > 0:
209-
# columns.append('move_mask')
198+
199+
reserved_ks = ['symbols', 'positions', 'numbers', 'species', 'pos']
200+
columns = ['symbols', 'positions'] + [
201+
k for k in atoms.arrays.keys() if k not in reserved_ks
202+
]
203+
204+
if len(atoms.constraints) > 0:
205+
columns.append('move_mask')
206+
207+
if images[0].calc is not None:
208+
if 'forces' in images[0].calc.results:
209+
columns.append('forces')
210+
211+
user_columns = kwargs.get('columns', None)
212+
if user_columns is not None:
213+
columns = list(set(columns + user_columns))
210214

211215
for atoms in images:
212216
# Current workaround magmoms being NaNs is to replace NaNs with 0.0
@@ -245,6 +249,7 @@ def get_atoms(
245249
mp_id: Optional[str] = None,
246250
user_api_key: Optional[str] = None,
247251
return_type: str = 'ase',
252+
constraints: Sequence[dict] = None,
248253
**kwargs
249254
) -> Union[Atoms, Structure]:
250255
"""Return an ASE Atoms or pymatgen Structure object based on specified
@@ -272,6 +277,9 @@ def get_atoms(
272277
:param user_api_key: Material Project API key, must be provided to get
273278
structures from Materials Project, defaults to None
274279
:type user_api_key: str, optional
280+
:param constraints: List of constraints to apply to the atoms object,
281+
currently only supports FixAtoms constraints, defaults to None
282+
:type constraints: Sequence[dict], optional
275283
:param return_type: When set to `ase` returns a :class:`ase.Atoms` object,
276284
when set to `pymatgen` returns a
277285
:class:`pymatgen.core.structure.Structure` object, defaults to 'ase'
@@ -298,8 +306,13 @@ def get_atoms(
298306
Atoms(symbols='Cu', pbc=True, cell=[[0.0, 1.805, 1.805], [1.805, 0.0, 1.805], [1.805, 1.805, 0.0]])
299307
>>> get_atoms(builder='bulk', name='Ar', crystalstructure='fcc', a=3.4, cubic=True)
300308
Atoms(symbols='Ar4', pbc=True, cell=[3.4, 3.4, 3.4])
301-
>>> get_atoms(builder='fcc100', symbol='Fe', vacuum=8, size=[4,4, 5])
309+
>>> get_atoms(builder='fcc100', symbol='Fe', vacuum=8, size=[4,4,5])
302310
Atoms(symbols='Cu80', pbc=[True, True, False], cell=[10.210621920333747, 10.210621920333747, 23.22], tags=...)
311+
312+
You can also specify constraints to fix atoms in place, for example
313+
>>> get_atoms(builder='fcc100', symbol='Fe', vacuum=8, size=[4,4,5], constraints=[{'constraint': 'FixAtoms', 'indices': [0,1]}])
314+
Atoms(symbols='Cu80', pbc=[True, True, False], cell=[10.210621920333747, 10.210621920333747, 23.22], tags=...)
315+
303316
304317
Some examples for reading an image from a file using :func:`ase.io.read`
305318
are given below. All ``**kwargs`` are passed to :func:`ase.io.read`
@@ -426,6 +439,19 @@ def get_atoms(
426439
elif rattle_stdev is not None and interface == 'pymatgen':
427440
struct.perturb(distance=rattle_stdev, min_distance=0)
428441

442+
if constraints is not None and interface == 'ase':
443+
consts = []
444+
for constraint_args in constraints:
445+
name = constraint_args.pop('constraint')
446+
assert name == 'FixAtoms', \
447+
'Only FixAtoms constraints are supported for ASE interface'
448+
constraint_cls = getattr(ase.constraints, name, None)
449+
const = constraint_cls(**constraint_args)
450+
consts.append(const)
451+
452+
for const in consts:
453+
atoms.set_constraint(const)
454+
429455
if return_type == 'ase' and interface == 'ase':
430456
return atoms
431457
elif return_type == 'pymatgen' and interface == 'ase':

tests/unit/test_utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ def test_get_atoms(test_input, expected):
134134
''' Test getting atoms from different inputs '''
135135
assert get_atoms(**test_input) == expected
136136

137+
def test_get_atoms_constraints(tmp_path):
138+
atoms = ase.build.bulk('Cu').repeat((2,2,2))
139+
constrained_atoms = get_atoms(
140+
atoms=atoms,
141+
constraints=[{'constraint': 'FixAtoms', 'indices': [0, 1]}]
142+
)
143+
assert len(constrained_atoms.constraints) == 1
144+
137145
@pytest.mark.parametrize("test_input, expected",[
138146
({'image_file': str(STRUCT_DIR / 'images.xyz')},
139147
[ase.build.bulk('Ar'), ase.build.bulk('Cu'), ase.build.bulk('Fe')]),
@@ -191,6 +199,13 @@ def test_write_atoms(test_input, expected, tmp_path):
191199
for prop in expected:
192200
assert prop in lines[1], f'"{prop}" not in file header'
193201

202+
def test_write_atoms_constraints(tmp_path):
203+
atoms = ase.build.bulk('Cu').repeat((2,2,2))
204+
atoms.set_constraint(ase.constraints.FixAtoms(indices=[0, 1]))
205+
write_atoms(tmp_path / 'test_constraints.xyz', atoms)
206+
read_atoms = read(tmp_path / 'test_constraints.xyz')
207+
assert len(read_atoms.constraints) > 0
208+
194209
@pytest.mark.parametrize("test_input, expected",[
195210
(
196211
{'name': 'Cu', 'interface': 'ase', 'builder': 'bulk'},

0 commit comments

Comments
 (0)