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
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3# Note:
4# Try to avoid module level import statements here to reduce
5# import time during CLI execution
6import sys
8import numpy as np
11class CLICommand:
12 """Build an atom, molecule or bulk structure.
14 Atom:
16 ase build <chemical symbol> ...
18 Molecule:
20 ase build <formula> ...
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).
25 Bulk:
27 ase build -x <crystal structure> <formula> ...
29 Examples:
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 """
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')
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
85 if args.vacuum0:
86 parser.error('Please use -V or --vacuum instead!')
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)
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)))
102 if args.modify:
103 exec(args.modify, {'atoms': atoms})
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])
111 if args.gui:
112 view(atoms)
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)
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
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)
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()
184 atoms.pbc = args.periodic
186 return atoms
189def build_bulk(args):
190 from ase.build import bulk
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)
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))
204 return atoms