Coverage for ase / cli / build.py: 58.82%
102 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +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://ase-lib.org/ase/build/build.html#molecules).
25 Bulk:
27 ase build -x <crystal structure> <formula> ...
29 Examples
30 --------
32 ase build Li # lithium atom
33 ase build Li -M 1 # ... with a magnetic moment of 1
34 ase build Li -M 1 -V 3.5 # ... in a 7x7x7 Ang cell
35 ase build H2O # water molecule
36 ase build -x fcc Cu -a 3.6 # FCC copper
37 """
39 @staticmethod
40 def add_arguments(parser):
41 add = parser.add_argument
42 add('name', metavar='formula/input-file',
43 help='Chemical formula or input filename.')
44 add('output', nargs='?', help='Output file.')
45 add('-M', '--magnetic-moment',
46 metavar='M1,M2,...',
47 help='Magnetic moments. '
48 'Use "-M 1" or "-M 2.3,-2.3"')
49 add('--modify', metavar='...',
50 help='Modify atoms with Python statement. '
51 'Example: --modify="atoms.positions[-1,2]+=0.1"')
52 add('-V', '--vacuum', type=float,
53 help='Amount of vacuum to add around isolated atoms '
54 '(in Angstrom)')
55 add('-v', '--vacuum0', type=float,
56 help='Deprecated. Use -V or --vacuum instead')
57 add('--unit-cell', metavar='CELL',
58 help='Unit cell in Angstrom. Examples: "10.0" or "9,10,11"')
59 add('--bond-length', type=float, metavar='LENGTH',
60 help='Bond length of dimer in Angstrom')
61 add('-x', '--crystal-structure',
62 help='Crystal structure',
63 choices=['sc', 'fcc', 'bcc', 'hcp', 'diamond',
64 'zincblende', 'rocksalt', 'cesiumchloride',
65 'fluorite', 'wurtzite'])
66 add('-a', '--lattice-constant', default='', metavar='LENGTH',
67 help='Lattice constant or comma-separated lattice constantes in '
68 'Angstrom')
69 add('--orthorhombic', action='store_true',
70 help='Use orthorhombic unit cell')
71 add('--cubic', action='store_true',
72 help='Use cubic unit cell')
73 add('-r', '--repeat',
74 help='Repeat unit cell. Use "-r 2" or "-r 2,3,1"')
75 add('-g', '--gui', action='store_true',
76 help='open ase gui')
77 add('--periodic', action='store_true',
78 help='make structure fully periodic')
80 @staticmethod
81 def run(args, parser):
82 from ase.db import connect
83 from ase.io import read, write
84 from ase.visualize import view
86 if args.vacuum0:
87 parser.error('Please use -V or --vacuum instead!')
89 if '.' in args.name:
90 # Read from file:
91 atoms = read(args.name)
92 elif args.crystal_structure:
93 atoms = build_bulk(args)
94 else:
95 atoms = build_molecule(args)
97 if args.magnetic_moment:
98 magmoms = np.array(
99 [float(m) for m in args.magnetic_moment.split(',')])
100 atoms.set_initial_magnetic_moments(
101 np.tile(magmoms, len(atoms) // len(magmoms)))
103 if args.modify:
104 exec(args.modify, {'atoms': atoms})
106 if args.repeat is not None:
107 r = args.repeat.split(',')
108 if len(r) == 1:
109 r = 3 * r
110 atoms = atoms.repeat([int(c) for c in r])
112 if args.gui:
113 view(atoms)
115 if args.output:
116 write(args.output, atoms)
117 elif sys.stdout.isatty():
118 write(args.name + '.json', atoms)
119 else:
120 con = connect(sys.stdout, type='json')
121 con.write(atoms, name=args.name)
124def build_molecule(args):
125 from ase.atoms import Atoms
126 from ase.build import molecule
127 from ase.data import (
128 atomic_numbers,
129 covalent_radii,
130 ground_state_magnetic_moments,
131 )
132 from ase.symbols import string2symbols
134 try:
135 # Known molecule or atom?
136 atoms = molecule(args.name)
137 except (NotImplementedError, KeyError):
138 symbols = string2symbols(args.name)
139 if len(symbols) == 1:
140 Z = atomic_numbers[symbols[0]]
141 magmom = ground_state_magnetic_moments[Z]
142 atoms = Atoms(args.name, magmoms=[magmom])
143 elif len(symbols) == 2:
144 # Dimer
145 if args.bond_length is None:
146 b = (covalent_radii[atomic_numbers[symbols[0]]] +
147 covalent_radii[atomic_numbers[symbols[1]]])
148 else:
149 b = args.bond_length
150 atoms = Atoms(args.name, positions=[(0, 0, 0),
151 (b, 0, 0)])
152 else:
153 raise ValueError('Unknown molecule: ' + args.name)
154 else:
155 if len(atoms) == 2 and args.bond_length is not None:
156 atoms.set_distance(0, 1, args.bond_length)
158 if args.unit_cell is None:
159 if args.vacuum:
160 atoms.center(vacuum=args.vacuum)
161 else:
162 atoms.center(about=[0, 0, 0])
163 else:
164 a = [float(x) for x in args.unit_cell.split(',')]
165 if len(a) == 1:
166 cell = [a[0], a[0], a[0]]
167 elif len(a) == 3:
168 cell = a
169 else:
170 a, b, c, alpha, beta, gamma = a
171 degree = np.pi / 180.0
172 cosa = np.cos(alpha * degree)
173 cosb = np.cos(beta * degree)
174 sinb = np.sin(beta * degree)
175 cosg = np.cos(gamma * degree)
176 sing = np.sin(gamma * degree)
177 cell = [[a, 0, 0],
178 [b * cosg, b * sing, 0],
179 [c * cosb, c * (cosa - cosb * cosg) / sing,
180 c * np.sqrt(
181 sinb**2 - ((cosa - cosb * cosg) / sing)**2)]]
182 atoms.cell = cell
183 atoms.center()
185 atoms.pbc = args.periodic
187 return atoms
190def build_bulk(args):
191 from ase.build import bulk
193 L = args.lattice_constant.replace(',', ' ').split()
194 d = {key: float(x) for key, x in zip('ac', L)}
195 atoms = bulk(args.name, crystalstructure=args.crystal_structure,
196 a=d.get('a'), c=d.get('c'),
197 orthorhombic=args.orthorhombic, cubic=args.cubic)
199 M, X = {'Fe': (2.3, 'bcc'),
200 'Co': (1.2, 'hcp'),
201 'Ni': (0.6, 'fcc')}.get(args.name, (None, None))
202 if M is not None and args.crystal_structure == X:
203 atoms.set_initial_magnetic_moments([M] * len(atoms))
205 return atoms