Coverage for /builds/ase/ase/ase/cli/build.py: 58.82%

102 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3# Note: 

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

5# import time during CLI execution 

6import sys 

7 

8import numpy as np 

9 

10 

11class CLICommand: 

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

13 

14 Atom: 

15 

16 ase build <chemical symbol> ... 

17 

18 Molecule: 

19 

20 ase build <formula> ... 

21 

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

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

24 

25 Bulk: 

26 

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

28 

29 Examples: 

30 

31 ase build Li # lithium atom 

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

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

34 ase build H2O # water molecule 

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

36 """ 

37 

38 @staticmethod 

39 def add_arguments(parser): 

40 add = parser.add_argument 

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

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

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

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

45 metavar='M1,M2,...', 

46 help='Magnetic moments. ' 

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

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

49 help='Modify atoms with Python statement. ' 

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

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

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

53 '(in Angstrom)') 

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

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

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

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

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

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

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

61 help='Crystal structure', 

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

63 'zincblende', 'rocksalt', 'cesiumchloride', 

64 'fluorite', 'wurtzite']) 

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

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

67 'Angstrom') 

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

69 help='Use orthorhombic unit cell') 

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

71 help='Use cubic unit cell') 

72 add('-r', '--repeat', 

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

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

75 help='open ase gui') 

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

77 help='make structure fully periodic') 

78 

79 @staticmethod 

80 def run(args, parser): 

81 from ase.db import connect 

82 from ase.io import read, write 

83 from ase.visualize import view 

84 

85 if args.vacuum0: 

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

87 

88 if '.' in args.name: 

89 # Read from file: 

90 atoms = read(args.name) 

91 elif args.crystal_structure: 

92 atoms = build_bulk(args) 

93 else: 

94 atoms = build_molecule(args) 

95 

96 if args.magnetic_moment: 

97 magmoms = np.array( 

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

99 atoms.set_initial_magnetic_moments( 

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

101 

102 if args.modify: 

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

104 

105 if args.repeat is not None: 

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

107 if len(r) == 1: 

108 r = 3 * r 

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

110 

111 if args.gui: 

112 view(atoms) 

113 

114 if args.output: 

115 write(args.output, atoms) 

116 elif sys.stdout.isatty(): 

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

118 else: 

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

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

121 

122 

123def build_molecule(args): 

124 from ase.atoms import Atoms 

125 from ase.build import molecule 

126 from ase.data import ( 

127 atomic_numbers, 

128 covalent_radii, 

129 ground_state_magnetic_moments, 

130 ) 

131 from ase.symbols import string2symbols 

132 

133 try: 

134 # Known molecule or atom? 

135 atoms = molecule(args.name) 

136 except (NotImplementedError, KeyError): 

137 symbols = string2symbols(args.name) 

138 if len(symbols) == 1: 

139 Z = atomic_numbers[symbols[0]] 

140 magmom = ground_state_magnetic_moments[Z] 

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

142 elif len(symbols) == 2: 

143 # Dimer 

144 if args.bond_length is None: 

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

146 covalent_radii[atomic_numbers[symbols[1]]]) 

147 else: 

148 b = args.bond_length 

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

150 (b, 0, 0)]) 

151 else: 

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

153 else: 

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

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

156 

157 if args.unit_cell is None: 

158 if args.vacuum: 

159 atoms.center(vacuum=args.vacuum) 

160 else: 

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

162 else: 

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

164 if len(a) == 1: 

165 cell = [a[0], a[0], a[0]] 

166 elif len(a) == 3: 

167 cell = a 

168 else: 

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

170 degree = np.pi / 180.0 

171 cosa = np.cos(alpha * degree) 

172 cosb = np.cos(beta * degree) 

173 sinb = np.sin(beta * degree) 

174 cosg = np.cos(gamma * degree) 

175 sing = np.sin(gamma * degree) 

176 cell = [[a, 0, 0], 

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

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

179 c * np.sqrt( 

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

181 atoms.cell = cell 

182 atoms.center() 

183 

184 atoms.pbc = args.periodic 

185 

186 return atoms 

187 

188 

189def build_bulk(args): 

190 from ase.build import bulk 

191 

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

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

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

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

196 orthorhombic=args.orthorhombic, cubic=args.cubic) 

197 

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

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

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

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

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

203 

204 return atoms