双电极体系的电极建模看似简单,但实际上也可能对计算有重要的影响。清楚的了解与电极有关的基本概念,有利于计算得到更可靠的结果。电极建模的最关键的一点是电极的长度。下面将会介绍,电极长度太短会可能会导致结果不准确或计算无法收敛。
电极单胞需要符合以下规则:
ATK通常能够自动判断单胞是否满足这两个条件。
电极单胞还须满足以下规则:
例如,创建fcc格子的[111]方向晶胞时,我们会得到Z方向有三层A-B-C重复单元。
整个电极体系在Z方向可以是非周期性的。这有两点含义,一方面,体系在有限偏压下,即使完整周期结构也变成了不对称的非周期结构;另一方面,左、右电极可以是不同种材料。
电极体系在X、Y方向则必须是周期性的,对使用FFT2D方法求解泊松方程是如此,对MultiGrid方法求解泊松方程时仍然如此。
研究一维、二维体系时,用户应该在X、Y方向留下足够的真空填充,以避免重复单元之间的基组重叠和静电相互作用。
研究三维表面的分子结时,同样也需要考虑同样的问题,电极表面XY方向的周期应足够大,以保证重复单元中的分子间距大于应有的相互作用距离,这样我们研究的体系才是孤立分子的导电性,而没有引入分子间相互作用。
XY方向上的周期性在模拟层状结构、或层状堆叠的界面结构时非常有用,因为我们可以用一个比较小的单胞研究这类体系。
上文提到对于fcc[111]方向的电极单胞为ABC三层原子,多数文章中用到的电极都是如此。
但是如果考虑更简单的体系,比如实例教程中的Li-H2-Li体系,似乎可以采用“单个原子”作为电极周期:
然而,用这样的模型进行计算,可能会遇到不收敛的问题,或者即使“运气好”收敛了,也很难保证结果的可靠性。这种在建模时就埋下的隐患极有可能会导致计算失败,而且很难察觉。
导致这个模型失败的原因是:由于电极太短,电极的费米能级与中间区域的费米能级无法匹配。为了避免处理无限大体系,计算时先分别处理两个有限的周期电极体系,然后再将电极与同样是非周期有限大小的中间区域耦合。
因此,算法要求电极单胞中的原子仅与Z方向最近邻晶胞中的原子相互作用。这样的假设忽略了除此以外的相互作用。这使得双电极体系与周期电极体系的哈密顿量无法匹配,因为后者在计算时考虑了所有的相互作用。结果导致电极的费米能级与双电极体系的平衡态费米能级无法匹配。
由于采用SIESTA有限基组(作用范围有限),以上要求在计算有限大小晶胞时可以严格遵守。用高斯基组或平面波基组是无法做到这一点的。
上面提到的相互作用有两类:
直接相互作用很重要,必须包括,否则结果会受到明显的影响。二阶的相互作用项则在很多时候对结果的影响微乎其微,可以忽略。唯独对于态密度在费米面处剧烈变化的体系,可能需要对计算准确的费米能级有比较大的影响。
足以包含所有相互作用的标准可以定义为:
如果只考虑直接重叠的影响,基本上就是基组半径的二倍。(通常这个标准也足以包括二阶相互作用。)
实际操作时的问题时:如何判断电极是否长度合适?答案很明显,找出体系中最长的相互作用半径,将电极长度与之比较。
ATK-DFT中Li原子的基组是4.04Å,projector最大半径是2.01Å,相互作用半径为6.05Å。Li原子间距为2.9Å,因此,电极应该包含三个原子(周期8.7Å)就保证了大于二倍的基组作用半径。当电极包含四个原子时,尽管长度仍然不足最大相互作用半径的二倍,但相互作用也大体上都被包括进来。
def ranges(device_configuration,method): """ Returns the max basis set and projector radii, in Angstrom @param device_configuration The device configuration to check. """ # Import required modules. from NL.Calculators.BulkCalculatorInterface import minimalBasis calculator = device_configuration.calculator() # If no calculator is attached, use specified method with default parameters if calculator is None: # In order not to destroy the input, we should make a local copy in that case print "No calculator on configuration, using %s with default parameters" % method if method=='DFT': calculator = DeviceLCAOCalculator() else: calculator = DeviceHuckelCalculator() # If it is a device configuration we will need to take into account projectors. if isinstance(calculator, DeviceLCAOCalculator): # Get the exchange-correlation calculator. xc_calculator = calculator.exchangeCorrelation()._radialBackEngine() # Get the minimal basis. minimal_basis = minimalBasis(device_configuration, calculator.basisSet()) # Find the basis range. basis_range = 0.0 for basis in minimal_basis: b = basis._backEngine(xc_calculator) N = b.size() for i in range(N): basis_range = max(basis_range, b[i].range()) # Find the projector range. projector_range = 0.0 for basis in minimal_basis: pseudopotential = basis.pseudopotential() pseudopotential.load() projectors = pseudopotential._projectors() N = projectors.size() for i in range(N): projector_range = max(projector_range, projectors[i].range()) return ((basis_range*Bohr).convertTo(Angstrom),(projector_range*Bohr).convertTo(Angstrom)) elif isinstance(calculator, DeviceHuckelCalculator): # Get the builder. builder = calculator._builder()(device_configuration, calculator) matrix_calculator = builder.createMatrixCalculator() # Find the basis ranges. basis_range = 0.0 for e in numpy.unique(device_configuration.elements()): ranges = matrix_calculator.basisSet().ranges(e.symbol()) for i in range(ranges.size()): basis_range = max(basis_range, ranges[i]) return ((basis_range*Bohr).convertTo(Angstrom),0.) def minimal_nextnext_distance(electrode): ''' Computes the smallest distance between the atoms in the electrode and the atoms in the corresponding next-next neighbor electrode cell @param electrode A BulkConfiguration @return Minimal distance, in Angstrom ''' # Length of electrode C vector in Z electrode_Cz = electrode.bravaisLattice().primitiveVectors()[2,2].inUnitsOf(Angstrom) # Original electrode coordinates elec_coords = electrode.cartesianCoordinates().inUnitsOf(Angstrom) # Coordinates of atoms in next-next cell test_coords = elec_coords+numpy.array([0,0,2.*electrode_Cz]) # Compute the distance from each atom in next-next cell to all atoms in original cell (order N) distances = [] for test in test_coords: distances.append(numpy.sqrt(((test-elec_coords)**2).sum(axis=1))) # Return the smallest such distance return numpy.min(numpy.array(distances)) def print_status(lr,dist,basis_range,projector_range): if dist < 2.*basis_range: print 'WARNING: Truncation of direct basis pair overlaps will occur in the %s electrode in this system. Your results will be incorrect!' % lr elif dist < 2.0*(basis_range + projector_range): print 'NOTE: Truncation of some second-order interactions will occur in the %s electrode in this system. This may influence convergence and the results.' % lr else: print 'The %s electrode is sufficiently long to include all interations.' % lr def validateElectrodes(device_configuration, method): """ Method for checking that the electrodes is long enough @param device_configuration The device configuration to check """ if device_configuration==None: return None # Compute the maximum basis set and projector (for DFT only) radius basis_range,projector_range = ranges(device_configuration,method) left_mini_dist = minimal_nextnext_distance(device_configuration.electrodes()[0]) right_mini_dist = minimal_nextnext_distance(device_configuration.electrodes()[1]) print 'Maximum basis set radius = %8.4f Ang' % basis_range print 'Maximum basis + projector radius = %8.4f Ang' % (basis_range + projector_range) print 'Basis set overlap range = %8.4f Ang' % (2.0*basis_range) print 'Maximum interaction range = %8.4f Ang' % (2.0*(basis_range + projector_range)) print 'Left electrode, truncation distance = %8.4f Ang' % left_mini_dist print 'Right electrode, truncation distance = %8.4f Ang' % right_mini_dist print '(Truncation occurs at the smallest atom-atom distance between electrode and its next-nearest neighbor cell)\n' print_status("LEFT",left_mini_dist,basis_range,projector_range) print print_status("RIGHT",right_mini_dist,basis_range,projector_range) return device_configuration # Initialize the Analyzer builder = Builder() builder.title('Electrode length check') # Set the Analysis function builder.setConfigurationGenerator(validateElectrodes) # Set up a Analysis widget interface builder.configuration('device_configuration', label='Device configuration', text='Drop system here') builder.choice('method', [ ('DFT', 'DFT'), ('Extended Huckel', 'Extended Huckel'), ], 'DFT', 'Default method')
Drop system here
;