Coverage for /builds/debichem-team/python-ase/ase/cli/build.py: 58.82%

102 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-03-06 04:00 +0000

1# Note: 

2# Try to avoid module level import statements here to reduce 

3# import time during CLI execution 

4import sys 

5 

6import numpy as np 

7 

8 

9class CLICommand: 

10 """Build an atom, molecule or bulk structure. 

11 

12 Atom: 

13 

14 ase build <chemical symbol> ... 

15 

16 Molecule: 

17 

18 ase build <formula> ... 

19 

20 where <formula> must be one of the formulas known to ASE 

21 (see here: https://wiki.fysik.dtu.dk/ase/ase/build/build.html#molecules). 

22 

23 Bulk: 

24 

25 ase build -x <crystal structure> <formula> ... 

26 

27 Examples: 

28 

29 ase build Li # lithium atom 

30 ase build Li -M 1 # ... with a magnetic moment of 1 

31 ase build Li -M 1 -V 3.5 # ... in a 7x7x7 Ang cell 

32 ase build H2O # water molecule 

33 ase build -x fcc Cu -a 3.6 # FCC copper 

34 """ 

35 

36 @staticmethod 

37 def add_arguments(parser): 

38 add = parser.add_argument 

39 add('name', metavar='formula/input-file', 

40 help='Chemical formula or input filename.') 

41 add('output', nargs='?', help='Output file.') 

42 add('-M', '--magnetic-moment', 

43 metavar='M1,M2,...', 

44 help='Magnetic moments. ' 

45 'Use "-M 1" or "-M 2.3,-2.3"') 

46 add('--modify', metavar='...', 

47 help='Modify atoms with Python statement. ' 

48 'Example: --modify="atoms.positions[-1,2]+=0.1"') 

49 add('-V', '--vacuum', type=float, 

50 help='Amount of vacuum to add around isolated atoms ' 

51 '(in Angstrom)') 

52 add('-v', '--vacuum0', type=float, 

53 help='Deprecated. Use -V or --vacuum instead') 

54 add('--unit-cell', metavar='CELL', 

55 help='Unit cell in Angstrom. Examples: "10.0" or "9,10,11"') 

56 add('--bond-length', type=float, metavar='LENGTH', 

57 help='Bond length of dimer in Angstrom') 

58 add('-x', '--crystal-structure', 

59 help='Crystal structure', 

60 choices=['sc', 'fcc', 'bcc', 'hcp', 'diamond', 

61 'zincblende', 'rocksalt', 'cesiumchloride', 

62 'fluorite', 'wurtzite']) 

63 add('-a', '--lattice-constant', default='', metavar='LENGTH', 

64 help='Lattice constant or comma-separated lattice constantes in ' 

65 'Angstrom') 

66 add('--orthorhombic', action='store_true', 

67 help='Use orthorhombic unit cell') 

68 add('--cubic', action='store_true', 

69 help='Use cubic unit cell') 

70 add('-r', '--repeat', 

71 help='Repeat unit cell. Use "-r 2" or "-r 2,3,1"') 

72 add('-g', '--gui', action='store_true', 

73 help='open ase gui') 

74 add('--periodic', action='store_true', 

75 help='make structure fully periodic') 

76 

77 @staticmethod 

78 def run(args, parser): 

79 from ase.db import connect 

80 from ase.io import read, write 

81 from ase.visualize import view 

82 

83 if args.vacuum0: 

84 parser.error('Please use -V or --vacuum instead!') 

85 

86 if '.' in args.name: 

87 # Read from file: 

88 atoms = read(args.name) 

89 elif args.crystal_structure: 

90 atoms = build_bulk(args) 

91 else: 

92 atoms = build_molecule(args) 

93 

94 if args.magnetic_moment: 

95 magmoms = np.array( 

96 [float(m) for m in args.magnetic_moment.split(',')]) 

97 atoms.set_initial_magnetic_moments( 

98 np.tile(magmoms, len(atoms) // len(magmoms))) 

99 

100 if args.modify: 

101 exec(args.modify, {'atoms': atoms}) 

102 

103 if args.repeat is not None: 

104 r = args.repeat.split(',') 

105 if len(r) == 1: 

106 r = 3 * r 

107 atoms = atoms.repeat([int(c) for c in r]) 

108 

109 if args.gui: 

110 view(atoms) 

111 

112 if args.output: 

113 write(args.output, atoms) 

114 elif sys.stdout.isatty(): 

115 write(args.name + '.json', atoms) 

116 else: 

117 con = connect(sys.stdout, type='json') 

118 con.write(atoms, name=args.name) 

119 

120 

121def build_molecule(args): 

122 from ase.atoms import Atoms 

123 from ase.build import molecule 

124 from ase.data import ( 

125 atomic_numbers, 

126 covalent_radii, 

127 ground_state_magnetic_moments, 

128 ) 

129 from ase.symbols import string2symbols 

130 

131 try: 

132 # Known molecule or atom? 

133 atoms = molecule(args.name) 

134 except (NotImplementedError, KeyError): 

135 symbols = string2symbols(args.name) 

136 if len(symbols) == 1: 

137 Z = atomic_numbers[symbols[0]] 

138 magmom = ground_state_magnetic_moments[Z] 

139 atoms = Atoms(args.name, magmoms=[magmom]) 

140 elif len(symbols) == 2: 

141 # Dimer 

142 if args.bond_length is None: 

143 b = (covalent_radii[atomic_numbers[symbols[0]]] + 

144 covalent_radii[atomic_numbers[symbols[1]]]) 

145 else: 

146 b = args.bond_length 

147 atoms = Atoms(args.name, positions=[(0, 0, 0), 

148 (b, 0, 0)]) 

149 else: 

150 raise ValueError('Unknown molecule: ' + args.name) 

151 else: 

152 if len(atoms) == 2 and args.bond_length is not None: 

153 atoms.set_distance(0, 1, args.bond_length) 

154 

155 if args.unit_cell is None: 

156 if args.vacuum: 

157 atoms.center(vacuum=args.vacuum) 

158 else: 

159 atoms.center(about=[0, 0, 0]) 

160 else: 

161 a = [float(x) for x in args.unit_cell.split(',')] 

162 if len(a) == 1: 

163 cell = [a[0], a[0], a[0]] 

164 elif len(a) == 3: 

165 cell = a 

166 else: 

167 a, b, c, alpha, beta, gamma = a 

168 degree = np.pi / 180.0 

169 cosa = np.cos(alpha * degree) 

170 cosb = np.cos(beta * degree) 

171 sinb = np.sin(beta * degree) 

172 cosg = np.cos(gamma * degree) 

173 sing = np.sin(gamma * degree) 

174 cell = [[a, 0, 0], 

175 [b * cosg, b * sing, 0], 

176 [c * cosb, c * (cosa - cosb * cosg) / sing, 

177 c * np.sqrt( 

178 sinb**2 - ((cosa - cosb * cosg) / sing)**2)]] 

179 atoms.cell = cell 

180 atoms.center() 

181 

182 atoms.pbc = args.periodic 

183 

184 return atoms 

185 

186 

187def build_bulk(args): 

188 from ase.build import bulk 

189 

190 L = args.lattice_constant.replace(',', ' ').split() 

191 d = {key: float(x) for key, x in zip('ac', L)} 

192 atoms = bulk(args.name, crystalstructure=args.crystal_structure, 

193 a=d.get('a'), c=d.get('c'), 

194 orthorhombic=args.orthorhombic, cubic=args.cubic) 

195 

196 M, X = {'Fe': (2.3, 'bcc'), 

197 'Co': (1.2, 'hcp'), 

198 'Ni': (0.6, 'fcc')}.get(args.name, (None, None)) 

199 if M is not None and args.crystal_structure == X: 

200 atoms.set_initial_magnetic_moments([M] * len(atoms)) 

201 

202 return atoms