接口与抽象基类

接口与抽象基类

对于Python的抽象基类,知乎上的一个例子是

当你开发一个项目或服务,你需要给上下游的组件提供接口。让别人来调用你的程序接口,上下游组件该怎么样才能达到想要的目的和你的组件无缝衔接?需要通过按照你接口中规定的抽象方法来实现,例如,你提供一个访问网络请求的接口,你不会去实现host、username、password的注册和发送请求,这些需要调用的用户去实现,你只需要规定:“调用者必须实现指定方法才能实现调用”即可。

实际上在科学计算里这种需求.. 比如要实现一类混合模拟,他们求解的方程、求解方式都不同,但是都能给出同一物理量的结果. 比如A区域求解玻尔兹曼方程,可能采用离散坐标或者蒙特卡洛来求解. B区域求解扩散方程,可能用有限元或者有限差分来求解. 在迭代过程中,我们需要每一步都执行两个区域的模拟,通过他们中间的overlap区域来实现信息交换或判断收敛. 算法、区域都可能不同,但是核心迭代的流程应该都是类似的,我们怎么针对这种需求提供统一的接口?似乎Python的抽象基类也是一个可以接受的选择..

比如下面我们定义了一个Hybrid类,A_obj可能类似于上面左侧红框的模拟区域对象,B_obj类似于中间的蓝色模拟区域对象,他们两个组合成了一个hybrid对.

1
2
3
4
5
6
7
8
9
10
11
class Hybrid(object):

def __init__(self, A_obj, B_obj, overlap_zone):
self.A_obj = A_obj
self.B_obj = B_obj
self.overlap_zone = overlap_zone

def evaluate(self):
T_A = self.A_obj.evaluate(self.overlap_zone)
T_B = self.B_obj.evaluate(self.overlap_zone)
return (T_A - T_B) / T_B

A_obj和B_obj都是抽象基类Simulation的子类,Simulation类定义了几个抽象方法以用作统一接口,比如求解、返回结果,具体怎么求解、不同模拟方法经过怎样的后处理才能返回同一宏观量的分布,抽象基类不去定义,但是它的子类必须给出实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import abc

from scipy.interpolate import interp1d

class Simulation(abc.ABC):

@abc.abstractmethod
def simulate(self):
"""求解"""

@abc.abstractmethod
def result(self):
"""返回求解结果"""

def evaluate(self, x):
"""根据模拟的结果,在相应的坐标处插值返回求解结果"""
return interp1d(*self.result())(x)

比如我可以直接建立一个FEM类,这个类就是接受建立好的Comsol对象,求解过程直接调用Comsol接口的run方法,返回求解结果调用mph库的evaluate方法.

1
2
3
4
5
6
7
8
9
10
11
12
import mph

class FEM(Simulation):
def __init__(self, py_comsol_obj):
self.py_model = py_comsol_obj
self.java_model = self.py_model.java

def simulate(self):
self.java_model.std_case.run()

def result(self):
return self.py_model.evaluate(['x', 'T'])

把这个符合接口的FEM类的对象丢到Hybrid里,就可以去迭代了.