今天重构了VASPy中的OutCar类,使其能够正确操作标准VASP文件,并在其中使用了延迟初始化方法进行了一定的优化。

由于之前版本的VASPy是基于课题组修改过的VASP版本所写的,其中OUTCAR中的一些受力信息是标准VASP代码中不会输出的,但是当时为了省事我就直接拿课题组VASP输出的OUTCAR中的信息写了VASPy中的OutCar类。。。

为了能让VASPy具有更好的通用性,今天特地重构了OutCar这个类使其能够操作标准VASP的OUTCAR文件。

具体重构后的OutCar类代码的链接: VASPy/iter.py at master · PytLab/VASPy

由于OUTCAR是VASP所有输出的文件,自然会比较大,因此要操作这个文件就要特别的处理。

使用生成器获取迭代数据

直接读取OUTCAR整个文件必然会占用很多内存,即使不读取整个文件,要把每步迭代的信息都保存的数组中也同样会占用很多的内存。

一个解决的好办法就是使用迭代器来减小内存的开销。而且针对不同数据获取不同的迭代器,比如现在针对OUTCAR中的原子受力数据,我就写了一个force_iterator函数来获取受力信息的迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@property
def force_iterator(self):
"""
Return a generator yield ionic_step, coordinates, forces on atoms.

NOTE: ionic step starts from 1 **NOT 0**.
"""
with open(self.filename, "r") as f:
ion_step = 0

# Force data collection flags.
collect_begin = False
collecting = False

# Collect force data for each ionic step and yield.
for line in f:
if not collect_begin:
if self.force_regex.match(line):
collect_begin = True
ion_step += 1
elif not collecting:
if "-"*6 in line:
collecting = True
coordinates = []
forces = []
else:
if "-"*6 in line:
collecting = False
collect_begin = False
yield ion_step, coordinates, forces
else:
x, y, z, fx, fy, fz = line2list(line)
coordinates.append([x, y, z])
forces.append([fx, fy, fz])

使用延迟初始化(lazy property)

为了给OutCar实例初始化属性,必须要考虑OUTCAR的大体积,如果在构造实例的时候把所有的属性全部初始化同样会占用大量的内存,为了在内存和CPU进行平衡,我决定使用延迟初始化来处理OutCar类的属性初始化。

使用延迟初始化一般有两种方法:

  1. 使用描述符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class lazy(object): 
    def __init__(self, func):
    self.func = func

    def __get__(self, instance, cls):
    val = self.func(instance)
    setattr(instance, self.func.__name__, val)
    return val

    class Circle(object):
    def __init__(self, radius):
    self.radius = radius

    @lazy
    def area(self):
    print 'evalute'
    return 3.14 * self.radius ** 2

    c = Circle(4)
    print c.radius
    print c.area
    print c.area
    print c.area
  2. 使用装饰器,并在装饰器中使用默认的描述符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def lazy_property(func):
    attr_name = "_lazy_" + func.__name__

    @property
    def _lazy_property(self):
    if not hasattr(self, attr_name):
    setattr(self, attr_name, func(self))
    return getattr(self, attr_name)

    return _lazy_property

    class Circle(object):
    def __init__(self, radius):
    self.radius = radius

    @lazy_property
    def area(self):
    print 'evalute'
    return 3.14 * self.radius ** 2

这里我直接定义了新的描述符来实现延迟初始化,方法很简单,就是定义了一个非描述符(即只有__get__()方法的描述符)

1
2
3
4
5
6
7
8
9
10
11
class LazyProperty(object):
"""
Descriptor for lazy property.
"""
def __init__(self, func):
self.func = func

def __get__(self, instance, owner):
val = self.func(instance)
setattr(instance, self.func.__name__, val)
return val

这样就实现了OutCar类的延迟初始化。

Comments