这是本文档旧的修订版!
因为插件是包含在 Python 模块中的,所以它们应存在于自己的目录中。我们将研究一个读取 XYZ 文件的插件作为示例。它的目录结构如下:
XYZFilters/ __init__.py XYZLabFloor.py XYZFileRawReader.py
init.py
文件是一个特殊文件,用于告诉 Python 该文件夹是一个模块。它还包含导入模块时执行的代码。对于插件,此文件还需要包含一些信息用用以告知 QuantumATK 关于它自身。
XYZFilters
附加组件中的 init.py
文件包含以下代码:
1 import datetime 2 import XYZLabFloor 3 4 __addon_description__ = "Plugins for importing and exporting XYZ configurations." 5 __addon_version__ = "1.0" 6 __addon_date__ = datetime.date(year=2015, month=8, day=6) 7 8 __plugins__ = [XYZLabFloor.XYZLabFloor] 9 10 def reloadPlugins(): 11 reload(XYZLabFloor)
此代码给出了附加组件的说明,分配版本号和上次更新的日期。它还定义了附加组件提供的插件列表。本例中,有一个 XYZLabFloor.py
文件,包含了一个名为 XYZLabFloor
的插件类。此外,还提供了一个名为 reloadPlugins
的函数,以便在源代码文件发生更改时,QuantumATK 可以重新加载插件。
我们现在来看一下包含插件类的 XYZLabFloor.py
文件的结构。在文件的顶部,我们导入了编写插件所需的模块和类:
1 import os 2 3 from API import LabFloorImporterPlugin 4 from API import LabFloorItem 5 from NL.CommonConcepts.Configurations.MoleculeConfiguration import MoleculeConfiguration 6 from NL.CommonConcepts.PeriodicTable import SYMBOL_TO_ELEMENT 7 from NL.CommonConcepts.PhysicalQuantity import Angstrom 8 from NL.ComputerScienceUtilities import Exceptions 9 10 from XYZFileRawReader import XYZFileRawReader
接下来我们需要定义插件类。插件必须从 QuantumATK 定义的特定类中继承。在这种情况下,我们将定义一个从 LabFloorImporterPlugin
继承的类:
13 class XYZLabFloor(LabFloorImporterPlugin):
这种类型的插件必须定义两种方式。第一种方式是 scan
。该方法的作用是确定插件是否处理特定文件,如果是的话,则确定该文件包含哪种类型的 LabFloor 对象。它会将项目列表返回给 LabFloor。第二种方式是 load
,负责解析文件并将对象加载到内存中。
对于我们的 XYZLabFloor
插件,scan 法定义如下:
18 def scan(self, filename): 19 """ 20 Scans a file to check if it is supported by the plugin 21 22 @param filename : The path to be scanned. 23 @type : string 24 25 @return A list of LabFloorItems 26 """ 27 # Setup a resulting vector. 28 result = [] 29 30 # Determine extension 31 basename = os.path.basename(filename) 32 no_extension_name, extension = os.path.splitext(basename) 33 34 # Return empty string if extension isn't ".xyz" 35 if extension != '.xyz': 36 return result 37 38 # Try to load configuration 39 try: 40 reader = XYZFileRawReader(filename) 41 except Exception: 42 return result 43 44 for molecule_idx in xrange(reader.numOfMolecules()): 45 46 # Read the comment for this molecule. 47 comment = reader.comment(molecule=molecule_idx) 48 49 # Create and add LabFloorItem to list 50 if reader.numOfMolecules() == 1: 51 title = no_extension_name 52 else: 53 title = no_extension_name + " (" + str(molecule_idx) + ")" 54 55 # Create labfloor item. 56 item = LabFloorItem(MoleculeConfiguration, 57 title=title, 58 tool_tip=comment, 59 molecule_idx=molecule_idx) 60 61 # Add to result list. 62 result.append(item) 63 64 # Return the result list. 65 return result
该代码通过首先测试文件名中是否含有 “xyz” 的扩展名,然后尝试实际解析文件来检测文件是否为有效的 XYZ 文件。如果文件不是有效的 XYZ 文件,则返回空列表。如果它是有效文件,则读入文件中包含的每个分子,并为每个分子创建 LabFloorItem
。
LabFloorItem
是一个 LabFloor 上表示项目的分类。它包含对象的类型,在本例中,它是 MoleculeConfiguration
以及标题和工具提示(当鼠标光标悬停在项目上时可见的文本)。有关该项目的额外信息也可以作为关键参数传递。正如我们将在下面看到的,这些参数将传递给 load 方式。
当用户与项目交互时,QuantumATK 会调用 load 方式。例如,当使用 viewer 可视化结构或将其导入 builder 时会发生这种情况。Load 法的定义为:
67 def load(self, filename, molecule_idx=0): 68 """ 69 Load the desired object in memory. 70 71 @param filename : The path of the XYZ-file. 72 @type : string 73 74 @return Desired object (MoleculeConfiguration) 75 """ 76 # Read the file 77 reader = XYZFileRawReader(filename) 78 79 # Lists of elements and positions. 80 elements = [] 81 positions = [] 82 83 # Loop over atoms. 84 for atom in reader.atomList(molecule=molecule_idx): 85 elements.append(SYMBOL_TO_ELEMENT[atom["element"]]) 86 positions.append(atom["coords"]*Angstrom) 87 88 # Create configuration. 89 configuration = MoleculeConfiguration( 90 elements=elements, 91 cartesian_coordinates=positions) 92 93 return configuration
该方式读取文件内容并提取所要求分子的元素和位置。该方法被传递了要读取分子的索引(信息存储在 scan
式中创建的 LabFloorItem
中)并创建了一个 MoleculeConfiguration。MoleculeConfiguration 类是 QuantumATK 中表示分子的方式。对于周期系统,有一个相应的 BulkConfiguration 类,用于存储晶格矢量以及坐标和元素。
此附加组件的完整源代码可在此处下载 ↓ source code。
在此示例中,我们将编写一个插件以允许 QuantumATK 将结构导出到 XYZ 文件。该模块的目录结构如下所示:
XYZExporter/ __init__.py XYZExporter.py
第一步是编译 init.py
文件:
1 import datetime 2 import XYZExporterPlugin 3 4 __addon_description__ = "Plugins for exporting XYZ files." 5 __addon_version__ = "1.0" 6 __addon_date__ = datetime.date(year=2015, month=8, day=6) 7 8 __plugins__ = [XYZExporterPlugin.XYZExporterPlugin] 9 10 def reloadPlugins(): 11 reload(XYZExporterPlugin)
下一步就是要编写真正的插件类。在之前的示例中,插件类是从 LabFloorImporterPlugin
类派生的,表明它是一个用于导入数据的插件。但本例的插件将派生自 ExportConfigurationPlugin
。这些插件用于扩展 builder 支持的导出格式数量。打开构建器后,可以选择 File→Export 查看支持的文件格式列表。
从 ExportConfigurationPlugin 继承的类必须执行四种方式:title
,extension
,canExport
和 export
。Title
式需要返回此类文件的名称 (“XYZ”)。Extension
式应该返回文件扩展名 (“xyz”)。canExport
式确定此插件是否支持结构类型(MoleculeConfiguration
,BulkConfiguration
,DeviceConfiguration
或 NudgedElasticBand
)(XYZ文件仅支持 MoleculeConfiguration
对象)。最后,export
式是将结构传入并写进磁盘的位置。
以下为 XYZExporterPlugin.py
文件中 XYZExporterPlugin
的代码:
1 from NL.CommonConcepts.Configurations.MoleculeConfiguration import MoleculeConfiguration 2 from NL.CommonConcepts.PhysicalQuantity import Angstrom 3 4 from API import ExportConfigurationPlugin, showMessage 5 6 class XYZExporterPlugin(ExportConfigurationPlugin): 7 """ Class for handling the export of the XYZ input files. """ 8 9 def title(self): 10 """ Return the title file selection dialog. """ 11 12 return 'XYZ' 13 14 def extension(self): 15 """ The default extension of XYZ. """ 16 17 return 'xyz' 18 19 def export(self, configuration, path): 20 """ 21 Export the configuration. 22 23 @param configuration : The configuration to export. 24 @param path : The path to save the configuration to. 25 26 @return None 27 """ 28 29 # XYZ files only supports molecules. 30 if not isinstance(configuration, MoleculeConfiguration): 31 showMessage('XYZExporter can only export MoleculeConfigurations') 32 return 33 34 # Open the file with write permission. 35 with open(path, 'w') as f: 36 37 # Get the total number of atoms. 38 number_of_atoms = len(configuration) 39 40 # Write out the header to the file. 41 f.write('%i\n' % number_of_atoms) 42 f.write('Generated by XYZExporter\n') 43 44 # Get the list of atomic symbols. 45 symbols = [ element.symbol() for element in configuration.elements() ] 46 # Get the cartesian coordinates in units of Angstrom. 47 coordinates = configuration.cartesianCoordinates().inUnitsOf(Angstrom) 48 49 # Loop over each atom and write out its symbol and coordinates. 50 for i in xrange(number_of_atoms): 51 x, y, z = coordinates[i] 52 f.write('%3s %16.8f %16.8f %16.8f\n' % (symbols[i], x, y, z)) 53 54 55 def canExport(self, configuration): 56 """ 57 Method to determine if an exporter class can export a given configuration. 58 59 @param configuration : The configuration to test. 60 61 @return A bool, True if the plugin can export, False if it cannot. 62 """ 63 64 supported_configurations = [MoleculeConfiguration] 65 66 return isinstance(configuration, supported_configurations)
此附加组件的完整源代码可在此处下载 ↓ source code。
在这个例子中,我们将创建一个读取电子密度数据的新插件。在本教程中,我们将为存储在 NumPy 二进制 .npz
文件中的 3D 网格数据定义一种新格式。