Coverage for ase / io / siesta_input.py: 67.47%
83 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"""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 ----------
36 - f : Open filename.
37 """
38 yield '\n'
39 yield '#KPoint grid\n'
40 yield '%block kgrid_Monkhorst_Pack\n'
42 for i in range(3):
43 s = ''
44 if i < len(kpts):
45 number = kpts[i]
46 displace = 0.0
47 else:
48 number = 1
49 displace = 0
50 for j in range(3):
51 if j == i:
52 write_this = number
53 else:
54 write_this = 0
55 s += f' {write_this:d} '
56 s += f'{displace:1.1f}\n'
57 yield s
58 yield '%endblock kgrid_Monkhorst_Pack\n'
59 yield '\n'
61 @classmethod
62 def get_species(cls, atoms, species, basis_set):
63 from ase.calculators.siesta.parameters import Species
65 # For each element use default species from the species input, or set
66 # up a default species from the general default parameters.
67 tags = atoms.get_tags()
68 default_species = [
69 s for s in species
70 if (s['tag'] is None) and s['symbol'] in atoms.symbols]
71 default_symbols = [s['symbol'] for s in default_species]
72 for symbol in atoms.symbols:
73 if symbol not in default_symbols:
74 spec = Species(symbol=symbol,
75 basis_set=basis_set,
76 tag=None)
77 default_species.append(spec)
78 default_symbols.append(symbol)
79 assert len(default_species) == len(set(atoms.symbols))
81 # Set default species as the first species.
82 species_numbers = np.zeros(len(atoms), int)
83 i = 1
84 for spec in default_species:
85 mask = atoms.symbols == spec['symbol']
86 species_numbers[mask] = i
87 i += 1
89 # Set up the non-default species.
90 non_default_species = [s for s in species if s['tag'] is not None]
91 for spec in non_default_species:
92 mask1 = tags == spec['tag']
93 mask2 = atoms.symbols == spec['symbol']
94 mask = np.logical_and(mask1, mask2)
95 if sum(mask) > 0:
96 species_numbers[mask] = i
97 i += 1
98 all_species = default_species + non_default_species
100 return all_species, species_numbers
102 @classmethod
103 def make_xyz_constraints(cls, atoms: Atoms):
104 """ Create coordinate-resolved list of constraints [natoms, 0:3]
105 The elements of the list must be integers 0 or 1
106 1 -- means that the coordinate will be updated during relaxation
107 0 -- mains that the coordinate will be fixed during relaxation
108 """
109 moved = np.ones((len(atoms), 3), dtype=int) # (0: fixed, 1: updated)
110 for const in atoms.constraints:
111 if isinstance(const, FixAtoms):
112 moved[const.get_indices()] = 0
113 elif isinstance(const, FixedLine):
114 norm_dir = const.dir / np.linalg.norm(const.dir)
115 if not cls.is_along_cartesian(norm_dir):
116 raise RuntimeError(
117 f'norm_dir {norm_dir} is not one of the Cartesian axes'
118 )
119 norm_dir = norm_dir.round().astype(int)
120 moved[const.get_indices()] = norm_dir
121 elif isinstance(const, FixedPlane):
122 norm_dir = const.dir / np.linalg.norm(const.dir)
123 if not cls.is_along_cartesian(norm_dir):
124 raise RuntimeError(
125 f'norm_dir {norm_dir} is not one of the Cartesian axes'
126 )
127 norm_dir = norm_dir.round().astype(int)
128 moved[const.get_indices()] = abs(1 - norm_dir)
129 elif isinstance(const, FixCartesian):
130 moved[const.get_indices()] = 1 - const.mask.astype(int)
131 else:
132 warnings.warn(f'Constraint {const!s} is ignored')
133 return moved