1. 群运算的构建#
想用python实现较为通用的(离散)群运算操作,需要一些技巧。因为群的运算有其特殊之处:
首先,需要定义一个群,一个群需要有它的乘法和它的群元素。这些是需要程序的使用者自己提供的。见群论基础知识
其次,在一个给定的群里,群元的运算应该要得到另一个群元。
所以,如果我们使用如下的定义:
class GroupElement:
elements = ...
product_table = ...
def __mul__(self, other: GroupElement
) -> GroupElement:
pass
def __imul__(self, other: GroupElement
) -> GroupElement:
pass
就只能定义一个特定的群,而无法做到对一系列的不同的离散群通用。因为群的元素和乘法表已经确定了。
对于一系列的群,比如分子结构中的\(C_{2v}\), \(D_{\infty h}\), \(C_S\)群之类的,可以用如下的构造类的函数。
小心
这不同于所谓“类的构造函数”。“类的构造函数”是指__init__函数,它为一个专门的类生成对象。而我这里说的“构造类的函数”指的是在某一个基础类上进行修饰的做法。
首先,我们定义一个通用的群和运算规则,它们有元素表和元素乘法表的声明,但是,这里我们并不直接提供一个运算表,而是只规定了表的数据结构。
class PrimaryGroupElement:
element_names = [ ... ]
element_arrays = [ [...],
... ]
def __init__():
pass
def __primul__(self, other) -> str:
pass
规定了数据结构后,我们就可以形式上定义运算的过程,比如如果元素是一个字符串数组,运算表是某个字典,总能定义这种查找操作。即使我们还没有真正把这数组和字典具体的定义出来。
也就是说,这个类现在还不能用,但是只需要给出运算表就行了。
但是,如果我们每次都使用类生成对象,再把运算表附上,显然过于麻烦。我们可以定义一个函数,产生派生类。
IRREP_ID_TABLE = {'C2v': [ ... ],
...}
CHARACTER_TABLE = {'C2v': [ (...), ... ],
...}
def build_group_element(symmetry_type: str = 'C2v'):
class group_element(PrimaryGroupElement):
element_names = IRREP_ID_TABLE[symmetry_type]
element_arrays = CHARACTER_TABLE[symmetry_type]
def __mul__(self, other: group_element):
new_name = self.__premul__(other)
return group_element(new_name)
def __imul__(self, other: group_element):
new_name = self.__premul__(other)
self.__init__(new_name)
return self
def copy(self):
pass
@classmethod
def from_idx(cls, idx: int) -> group_element:
pass
return group_element
可以看到,这是一个生成派生类的函数。这个函数在定义派生类的时候,通过查找总表,把\(C_{2v}\)的群元素和群运算表分配给了基类。并且额外定义了乘法运算。这样,当使用者要进行某一种群的操作时,就可以有如下方式:
>>> c2v_group_element = build_group_element('C2v')
>>> a1 = c2v_group_element('a1')
>>> a2 = c2v_group_element('a2')
>>> a2_new = a1 * a2
>>> type(a2_new)
c2v_group_element
总的来说,基类首先定义了一种通用的群的运算理念,而构造类的函数则返回一个可以调用的类,这个类可以生成某个特定的群元,进行这个特定群元的运算。