这个动力学模型大概是在放假前一两个星期开始写的,由于我写代码的低效,今天才刚刚完成了model中的load()的部分,其中包括解析setup file中的基元反应的反应方程式以及检测方程式质量守恒以及吸附位守恒等主要功能。在这里大概记录下写这个模型的大致过程,以便以后回来方便浏览和回想思路。
首先是整个代码的组织框架,这个当时想了一段时间大概要怎么组织整个模型,这个还是很重要的。因为看过经典的《C Premier Plus》上面有写道:

随着程序的变得更长更复杂,头脑中的想象就开始无能为力了,而且错误也将变得难以发现。最终,那些忽略计划步骤的人会浪费大量时间并带来混乱的挫折,因为他们编写出了难看、功能不正常而且艰深难懂的程序。工作越来越复杂,需要的计划工作量就越大。

这里有一句忠告,那就是应该养成在编写代码前进行规划的习惯。使用古老而可敬的笔记技术来大略记录下程序的目标,并购了出设计的样貌。如果您这样做了,最终会节省时间并感到满意。

这学期通过上课之余的其他时间,零零碎碎的把CatMap的90%的代码看过了,大概抓住了整个模型的BIG PICTURE.由于CatMap模型的目的是进行催化剂的筛选,主要是利用BEP关系分析原始的DFT计算出的能量进行简单粗暴的线性拟合,拟合出基于两个descriptors的map,从而对于map上的每一个点进行动力学稳态求解,输出的变量主要包括coverage, rate等。对于动力学模型的学习我还是主要以了解和学习为目的的。总览这个代码,catmap充分利用了Python OO的特性把动力学模型的几个组成部分有机的组合在了一起,这是很好的,非常有利于今后的代码维护和功能的增加。在看代码的过程中,catmap给我的印象就是它过度的修改了python的内置方法,例如__getattr__(), __getattribute__(), __setattr__()等,这样做的目的我觉得作者是想给model这个类至高无上的”权利”, 修改前面的内置方法主要是在ModelWrapper类中,而这个ModekWrapper类是其他”工具”类的父类,在ModelWrapper类中修改了__getattr__(), __getattribute__(), __setattr__()等方法,使得其他”工具”类的属性都附给model使其成为model的属性。

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
class ReactionModelWrapper:
def __getattribute__(self,attr):
"Force use of custom getattr"
return self.__getattr__(self,attr)

def __getattr__(self,attr):
"Return the value of the reaction model instance if its there. Otherwise return the instances own value (or none if the instance does not have the attribute defined and the attribute is not private)"
if attr == '_rxm':
return object.__getattribute__(self,attr)

elif hasattr(self._rxm,attr):
return getattr(self._rxm,attr)
else:
if attr in self.__dict__:
val = object.__getattribute__(self,attr)
del self.__dict__[attr]
#this makes sure that the attr is read from _rxm
setattr(self._rxm,attr,val)
return val
elif attr.startswith('_'):
raise AttributeError()
else:
return None

def __setattr__(self,attr,val):
"Set attribute for the instance as well as the reaction_model instance"
accumulate = ['_required','_log_strings','_function_strings']
if attr == '_rxm':
self.__dict__[attr] = val
elif attr in accumulate:
self._rxm.__dict__[attr].update(val)# val = (k, v)
else:
setattr(self._rxm,attr,val)

这种关系不知道为什么作者会这么做,在后面调用的时候就出现了不知道什么原因的报错(如下图)。虽然这样使得model的组织更有灵活性,但是一定要谨慎,灵活也是有代价的。

由于学习动力学模型的初衷是学习python用来做科学计算,还是想自己把整个模型都自己写一遍来实现,以便达到最初的学习目的,改代码什么的这种无聊的事情自己真的做不下去。趴在地上想了会,我需要实现的功能其实是CatMap的子集,其中要设计多个表面的迁移过程,但是这种过程也不过是添加了site type的类型,在解稳态方程的时候添加了单独的表面归一条件。在解析表达式的时候将多个表面考虑进来写的更加general应该就可以了。
下图是我大概组织的整个代码的组织结构
model_structure
在这里我没有赋予model过于重要的地位,而是尽量将model与tools的功能分开来实现,各自完成各自的任务,通过实例化过程中初始化调用,使几个部分与主体model进行联系。同时我还单独写了个logger类用来生成logger对象记录model以及model的各个工具在运行过程中的日志文件,这样就把记录日志的任务交给了loggers来完成,这种分治的感觉还是很好的,起码是整个模型的代码清晰不让人感觉揉成一团,使用set_logger()方法在初始化过程中自动实例化logger,其中应用了CatMap中所谓的”BLACK MAGIC”, 说白了就是调用python内置的__import__()方法来import包含类的文件,并自动在执行代码的过程中实现其他文件中类的实例化(python官方文档对这个方法的介绍:https://docs.python.org/2/library/functions.html#import).
相对于CatMap,我多加了一个table_maker用来根据model的物种信息生成setup file的表格,方便用户输入能量信息,并计算出generalized formation energy. 在这里我默认写了csv格式的table,当然还能写其他的table_maker实例。另外csv文件修改数据的话最好还是用NotePad打开修改,excel会造成数字精度损失,查看整个文件不做能容修改的话还是可以用excel打开的,有表格更容易查看整体内容信息。
贴上set_logger()的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def set_logger(self):
"""
import logger and get an instance of Logger class
"""
#The 'BLACK MAGIC' is hacked from CatMap (catmap/model.py)
#https://docs.python.org/2/library/functions.html#__import__
basepath = os.path.dirname(
inspect.getfile(inspect.currentframe()))
if basepath not in sys.path:
sys.path.append(basepath)
#from loggers import logger
_module = __import__('loggers.logger', globals(), locals())
logger_instance = getattr(_module, 'Logger')(owner=self)
setattr(self, 'logger', logger_instance)

这个方法打算在后面补充load()中实例化各个工具类的时候模仿CatMap的方法写,这里就大概记录下目前来讲我组织代码的结构,充分利用python OO特性方便代码维护和扩充,以便在今后在此框架的基础上添加更多的方法和工具类以完成更加复杂的动力学计算任务。还有,毕竟代码是写给人看的,不是写给计算机看的。

2015.2.25补充:
前几天在开始写solver之前意识到不仅仅table_maker需要依赖parser解析的数据,solver也依赖parser解析的数据,例如parse_elementary_rxns()获取基元反应的列表方便对solver物种进行分析。之前我是把load()中的set_table_maker()单独写在遍历环境变量实例化工具类的代码之前的。

1. 还是要有一个TableBase类,用于生成不同的table_maker的子类。目前,这个基类主要包含一些获取于model对象中的一些变量。这些变量都是用parser解析setupfile获取的,因此若要用table_maker,必须要有parser并且对setupfile进行了正确的解析。因此在model的load()中一开始我是把table_maker作为和parser等工具一样通过执行setupfile后遍历局部变量的,结果在实例化parser之前先循环到了table_maker,就抛出异常了。。。因此后面我又单独写了个set_table_maker()方法,

在遍历局部变量的时候,进行了对table_maker的判断:

方法是获取table_maker的名称(string),然后在load()之后通过set_table_maker()方法对table_maker进行实例化。
上面是在动力学模型工具类–TableMaker中我当时写的。

如今开始写solver的时候发现,parser应该是最优先实例化的,实例化parser以后,在用parser解析一些数据,以供后面的工具实例化的过程中使用。贴上代码:

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
#assign parser ahead to provide essential attrs for other tools
self.set_parser(locs['parser'])

#assign other tools
for key in locs.keys():
#ignore tools which will be loaded later
if key in self._tools:
#setattr(self, 'table_maker_name', locs[key])
continue
#check type of variables
if key in self._attr_type_dict:
#chech attr type
if type(locs[key]) != self._attr_type_dict[key]:
try:
locs[key] = self._attr_type_dict[key](locs[key])
setattr(self, key, locs[key])
except:
raise ValueError('\''+key+'\' is in wrong type. '
+ str(self._attr_type_dict[key])
+ ' object is expected.')
setattr(self, key, locs[key])

#use parser parse essential attrs for other tools
#parse elementary rxns
if self.rxn_expressions:
self.parser.parse_elementary_rxns(self.rxn_expressions)
#get total rxn equation
if self.elementary_rxns_list:
self.parser.get_total_rxn_equation(self.elementary_rxns_list)

在这里为了能使_tools中的属性在最后添加到对象中,我把工具类属性的赋值放在了其他属性赋值的后面,就这样分隔了开来。
遍历局部变量实例化工具类的代码:
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
#load tools of model
for key in self._tools:
#black magic to auto-import classes
#HACKED from CatMap
if key == 'parser': #ignore parser loaded before
continue
try:
if locs[key]:
if not key.endswith('s'):
pyfile = key + 's'
else:
pyfile = key
basepath=os.path.dirname(
inspect.getfile(inspect.currentframe()))
if basepath not in sys.path:
sys.path.append(basepath)
sublocs = {}
_temp = \
__import__(pyfile, globals(), sublocs, [locs[key]])
tool_instance = getattr(_temp, locs[key])(owner=self)
setattr(self, key, tool_instance)
else:
setattr(self, key, None)
except ImportError:
raise AttributeError(key.capitalize()+' '+locs[key]+\
' could not be imported. Ensure that the class '+\
'exists and is spelled properly.')
#HACK END

Comments