Coverage for /builds/ase/ase/ase/io/siesta_input.py: 67.47%
83 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"""SiestaInput"""
4import warnings
6import numpy as np
8from ase import Atoms
9from ase.constraints import FixAtoms, FixCartesian, FixedLine, FixedPlane
12class SiestaInput:
13 """SiestaInput"""
14 @classmethod
15 def is_along_cartesian(cls, norm_dir: np.ndarray) -> bool:
16 """Return whether `norm_dir` is along a Cartesian coordidate."""
17 directions = [
18 [+1, 0, 0],
19 [-1, 0, 0],
20 [0, +1, 0],
21 [0, -1, 0],
22 [0, 0, +1],
23 [0, 0, -1],
24 ]
25 for direction in directions:
26 if np.allclose(norm_dir, direction, rtol=0.0, atol=1e-6):
27 return True
28 return False
30 @classmethod
31 def generate_kpts(cls, kpts):
32 """Write kpts.
34 Parameters:
35 - f : Open filename.
36 """
37 yield '\n'
38 yield '#KPoint grid\n'
39 yield '%block kgrid_Monkhorst_Pack\n'
41 for i in range(3):
42 s = ''
43 if i < len(kpts):
44 number = kpts[i]
45 displace = 0.0
46 else:
47 number = 1
48 displace = 0
49 for j in range(3):
50 if j == i:
51 write_this = number
52 else:
53 write_this = 0
54 s += f' {write_this:d} '
55 s += f'{displace:1.1f}\n'
56 yield s
57 yield '%endblock kgrid_Monkhorst_Pack\n'
58 yield '\n'
60 @classmethod
61 def get_species(cls, atoms, species, basis_set):
62 from ase.calculators.siesta.parameters import Species
64 # For each element use default species from the species input, or set
65 # up a default species from the general default parameters.
66 tags = atoms.get_tags()
67 default_species = [
68 s for s in species
69 if (s['tag'] is None) and s['symbol'] in atoms.symbols]
70 default_symbols = [s['symbol'] for s in default_species]
71 for symbol in atoms.symbols:
72 if symbol not in default_symbols:
73 spec = Species(symbol=symbol,
74 basis_set=basis_set,
75 tag=None)
76 default_species.append(spec)
77 default_symbols.append(symbol)
78 assert len(default_species) == len(set(atoms.symbols))
80 # Set default species as the first species.
81 species_numbers = np.zeros(len(atoms), int)
82 i = 1
83 for spec in default_species:
84 mask = atoms.symbols == spec['symbol']
85 species_numbers[mask] = i
86 i += 1
88 # Set up the non-default species.
89 non_default_species = [s for s in species if s['tag'] is not None]
90 for spec in non_default_species:
91 mask1 = tags == spec['tag']
92 mask2 = atoms.symbols == spec['symbol']
93 mask = np.logical_and(mask1, mask2)
94 if sum(mask) > 0:
95 species_numbers[mask] = i
96 i += 1
97 all_species = default_species + non_default_species
99 return all_species, species_numbers
101 @classmethod
102 def make_xyz_constraints(cls, atoms: Atoms):
103 """ Create coordinate-resolved list of constraints [natoms, 0:3]
104 The elements of the list must be integers 0 or 1
105 1 -- means that the coordinate will be updated during relaxation
106 0 -- mains that the coordinate will be fixed during relaxation
107 """
108 moved = np.ones((len(atoms), 3), dtype=int) # (0: fixed, 1: updated)
109 for const in atoms.constraints:
110 if isinstance(const, FixAtoms):
111 moved[const.get_indices()] = 0
112 elif isinstance(const, FixedLine):
113 norm_dir = const.dir / np.linalg.norm(const.dir)
114 if not cls.is_along_cartesian(norm_dir):
115 raise RuntimeError(
116 f'norm_dir {norm_dir} is not one of the Cartesian axes'
117 )
118 norm_dir = norm_dir.round().astype(int)
119 moved[const.get_indices()] = norm_dir
120 elif isinstance(const, FixedPlane):
121 norm_dir = const.dir / np.linalg.norm(const.dir)
122 if not cls.is_along_cartesian(norm_dir):
123 raise RuntimeError(
124 f'norm_dir {norm_dir} is not one of the Cartesian axes'
125 )
126 norm_dir = norm_dir.round().astype(int)
127 moved[const.get_indices()] = abs(1 - norm_dir)
128 elif isinstance(const, FixCartesian):
129 moved[const.get_indices()] = 1 - const.mask.astype(int)
130 else:
131 warnings.warn(f'Constraint {const!s} is ignored')
132 return moved