Skip to content

Commit 2e6ae92

Browse files
authored
enh: enabled writing without selective dynamics (#190)
* enh: enabled writing without selective dynamics Signed-off-by: Nick Papior <[email protected]> * enh: enabled sorting of geometries before writing dcc-2019-jun Also added that as a staticmethod to vasp objects. Signed-off-by: Nick Papior <[email protected]> * bug: fixed sort and dynamic together Signed-off-by: Nick Papior <[email protected]> * enh: finalized TF suggestions and clarified intents Signed-off-by: Nick Papior <[email protected]>
2 parents c8e0d00 + 33d497d commit 2e6ae92

File tree

3 files changed

+113
-37
lines changed

3 files changed

+113
-37
lines changed

sisl/io/vasp/car.py

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,42 @@ def _setup(self, *args, **kwargs):
2323
self._scale = 1.
2424

2525
@sile_fh_open()
26-
def write_geometry(self, geometry, dynamic=True):
26+
def write_geometry(self, geometry, dynamic=True, group_species=False):
2727
r""" Writes the geometry to the contained file
2828
2929
Parameters
3030
----------
3131
geometry : Geometry
3232
geometry to be written to the file
33-
dynamic : bool or list, optional
33+
dynamic : None, bool or list, optional
3434
define which atoms are dynamic in the VASP run (default is True,
35-
which means all atoms are dynamic)
35+
which means all atoms are dynamic).
36+
If None, the resulting file will not contain any dynamic flags
37+
group_species: bool, optional
38+
before writing `geometry` first re-order species to
39+
have species in consecutive blocks (see `geometry_group`)
3640
3741
Examples
3842
--------
3943
>>> car = carSileVASP('POSCAR', 'w')
4044
>>> geom = geom.graphene()
45+
>>> geom.write(car) # regular car without Selective Dynamics
4146
>>> geom.write(car, dynamic=False) # fix all atoms
4247
>>> geom.write(car, dynamic=[False, (True, False, True)]) # fix 1st and y coordinate of 2nd
48+
49+
See Also
50+
--------
51+
geometry_group: method used to group atoms together according to their species
4352
"""
4453
# Check that we can write to the file
4554
sile_raise_write(self)
4655

56+
if group_species:
57+
geometry, idx = self.geometry_group(geometry, ret_index=True)
58+
else:
59+
# small hack to allow dynamic
60+
idx = _a.arangei(len(geometry))
61+
4762
# LABEL
4863
self._write('sisl output\n')
4964

@@ -52,10 +67,8 @@ def write_geometry(self, geometry, dynamic=True):
5267

5368
# Write unit-cell
5469
fmt = (' ' + '{:18.9f}' * 3) + '\n'
55-
tmp = np.zeros([3], np.float64)
5670
for i in range(3):
57-
tmp[:3] = geometry.cell[i, :]
58-
self._write(fmt.format(*tmp))
71+
self._write(fmt.format(*geometry.cell[i]))
5972

6073
# Figure out how many species
6174
pt = PeriodicTable()
@@ -78,21 +91,26 @@ def write_geometry(self, geometry, dynamic=True):
7891
self._write(fmt.format(*s))
7992
fmt = ' {:d}' * len(d) + '\n'
8093
self._write(fmt.format(*d))
81-
self._write('Selective dynamics\n')
94+
if dynamic is None:
95+
# We write in direct mode
96+
dynamic = [None] * len(geometry)
97+
def todyn(fix):
98+
return '\n'
99+
else:
100+
self._write('Selective dynamics\n')
101+
b2s = {True: 'T', False: 'F'}
102+
def todyn(fix):
103+
if isinstance(fix, bool):
104+
return ' {0} {0} {0}\n'.format(b2s[fix])
105+
return ' {} {} {}\n'.format(b2s[fix[0]], b2s[fix[1]], b2s[fix[2]])
82106
self._write('Cartesian\n')
83107

84108
if isinstance(dynamic, bool):
85109
dynamic = [dynamic] * len(geometry)
86110

87-
b2s = {True: 'T', False: 'F'}
88-
def todyn(fix):
89-
if isinstance(fix, bool):
90-
return '{0} {0} {0}\n'.format(b2s[fix])
91-
return '{} {} {}\n'.format(b2s[fix[0]], b2s[fix[1]], b2s[fix[2]])
92-
93-
fmt = '{:18.9f} ' * 3
111+
fmt = '{:18.9f}' * 3
94112
for ia in geometry:
95-
self._write(fmt.format(*geometry.xyz[ia, :]) + todyn(dynamic[ia]))
113+
self._write(fmt.format(*geometry.xyz[ia, :]) + todyn(dynamic[idx[ia]]))
96114

97115
@sile_fh_open(True)
98116
def read_supercell(self):
@@ -115,12 +133,13 @@ def read_supercell(self):
115133
def read_geometry(self, ret_dynamic=False):
116134
r""" Returns Geometry object from the CONTCAR/POSCAR file
117135
118-
Possibly also return the dynamics (if present)
136+
Possibly also return the dynamics (if present).
119137
120138
Parameters
121139
----------
122140
ret_dynamic : bool, optional
123-
also read selective dynamics (if present), if not, a list of True will be returned
141+
also return selective dynamics (if present), if not, None will
142+
be returned.
124143
"""
125144
sc = self.read_supercell()
126145

@@ -148,25 +167,26 @@ def read_geometry(self, ret_dynamic=False):
148167
for spec, nsp in zip(species, species_count)
149168
for i in range(nsp)]
150169

151-
# Read whether this is selective or direct
152-
# Currently direct is not used
170+
# Number of atoms
171+
na = len(atom)
172+
173+
# check whether this is Selective Dynamics
153174
opt = self.readline()
154-
dynamics = False
155175
if opt[0] in 'Ss':
156176
dynamics = True
177+
# pre-create the dynamic list
178+
dynamic = np.empty([na, 3], dtype=np.bool_)
157179
opt = self.readline()
180+
else:
181+
dynamics = False
182+
dynamic = None
158183

159184
# Check whether this is in fractional or direct
160-
# coordinates
185+
# coordinates (Direct == fractional)
161186
cart = False
162187
if opt[0] in 'CcKk':
163188
cart = True
164189

165-
# Number of atoms
166-
na = len(atom)
167-
# pre-create the dynamic list
168-
dynamic = [[False] * 3] * na
169-
170190
xyz = _a.emptyd([na, 3])
171191
for ia in range(na):
172192
line = self.readline().split()
@@ -183,7 +203,7 @@ def read_geometry(self, ret_dynamic=False):
183203
# The POT/CONT-CAR does not contain information on the atomic species
184204
geom = Geometry(xyz=xyz, atom=atom, sc=sc)
185205
if ret_dynamic:
186-
return geom, np.array(dynamic, dtype=np.bool_)
206+
return geom, dynamic
187207
return geom
188208

189209
def ArgumentParser(self, p=None, *args, **kwargs):

sisl/io/vasp/sile.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,62 @@
11
"""
22
Define a common VASP Sile
33
"""
4+
import sisl._array as _a
45

56
from ..sile import Sile, SileCDF, SileBin
67

78
__all__ = ['SileVASP', 'SileCDFVASP', 'SileBinVASP']
89

910

11+
def _geometry_group(geometry, ret_index=False):
12+
r""" Order atoms in geometry according to species such that all of one specie is consecutive
13+
14+
When creating VASP input files (`poscarSileVASP` for instance) the equivalent
15+
``POTCAR`` file needs to contain the pseudos for each specie as they are provided
16+
in blocks.
17+
18+
I.e. for a geometry like this:
19+
.. code::
20+
21+
[Atom(6), Atom(4), Atom(6)]
22+
23+
the resulting ``POTCAR`` needs to contain the pseudo for Carbon twice.
24+
25+
This method will re-order atoms according to the species"
26+
27+
Parameters
28+
----------
29+
geometry : Geometry
30+
geometry to be re-ordered
31+
ret_index : bool, optional
32+
return sorted indices
33+
34+
Returns
35+
-------
36+
geometry: reordered geometry
37+
"""
38+
na = len(geometry)
39+
idx = _a.emptyi(na)
40+
41+
ia = 0
42+
for _, idx_s in geometry.atoms.iter(species=True):
43+
idx[ia:ia + len(idx_s)] = idx_s
44+
ia += len(idx_s)
45+
46+
assert ia == na
47+
48+
if ret_index:
49+
return geometry.sub(idx), idx
50+
return geometry.sub(idx)
51+
52+
1053
class SileVASP(Sile):
11-
pass
54+
geometry_group = staticmethod(_geometry_group)
1255

1356

1457
class SileCDFVASP(SileCDF):
15-
pass
58+
geometry_group = staticmethod(_geometry_group)
1659

1760

1861
class SileBinVASP(SileBin):
19-
pass
62+
geometry_group = staticmethod(_geometry_group)

sisl/io/vasp/tests/test_car.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,23 @@ def test_geometry_car_mixed(sisl_tmp):
2727
assert carSileVASP(f).read_geometry() == geom
2828

2929

30-
def test_geometry_car_allsame(sisl_tmp):
31-
f = sisl_tmp('test_read_write.POSCAR', _dir)
30+
def test_geometry_car_group(sisl_tmp):
31+
f = sisl_tmp('test_sort.POSCAR', _dir)
3232

33-
atoms = Atom[1]
34-
xyz = np.random.rand(10, 3)
33+
atoms = [Atom[1],
34+
Atom[2],
35+
Atom[2],
36+
Atom[1],
37+
Atom[1],
38+
Atom[2],
39+
Atom[3]]
40+
xyz = np.random.rand(len(atoms), 3)
3541
geom = Geometry(xyz, atoms, 100)
3642

37-
geom.write(carSileVASP(f, 'w'))
43+
geom.write(carSileVASP(f, 'w'), group_species=True)
3844

45+
assert carSileVASP(f).read_geometry() != geom
46+
geom = carSileVASP(f).geometry_group(geom)
3947
assert carSileVASP(f).read_geometry() == geom
4048

4149

@@ -60,14 +68,19 @@ def test_geometry_car_dynamic(sisl_tmp):
6068

6169
read = carSileVASP(f)
6270

63-
geom.write(carSileVASP(f, 'w'))
71+
# no dynamic (direct geometry)
72+
geom.write(carSileVASP(f, 'w'), dynamic=None)
6473
g, dyn = read.read_geometry(ret_dynamic=True)
65-
assert np.all(dyn)
74+
assert dyn is None
6675

6776
geom.write(carSileVASP(f, 'w'), dynamic=False)
6877
g, dyn = read.read_geometry(ret_dynamic=True)
6978
assert not np.any(dyn)
7079

80+
geom.write(carSileVASP(f, 'w'), dynamic=True)
81+
g, dyn = read.read_geometry(ret_dynamic=True)
82+
assert np.all(dyn)
83+
7184
dynamic = [False] * len(geom)
7285
dynamic[0] = [True, False, True]
7386
geom.write(carSileVASP(f, 'w'), dynamic=dynamic)

0 commit comments

Comments
 (0)