Skip to content

Single Atom Alloys

generate_saa_structures(host_species, dopant_species, crystal_structures=None, facets=None, supercell_dim=(3, 3, 4), default_lat_param_lib=None, a_dict=None, c_dict=None, set_host_magnetic_moments=None, host_magnetic_moments=None, set_dopant_magnetic_moments=None, dopant_magnetic_moments=None, vacuum=10.0, n_fixed_layers=0, place_dopant_at_center=True, write_to_disk=False, write_location='.', dirs_exist_ok=False)

Builds single-atom alloys for all combinations of host species and dopant species given. Will write the structures to separate directories if specified.

Parameters:

Name Type Description Default
host_species List[str]

List of chemical species of desired host (substrate) species.

required

dopant_species (REQUIRED): List of chemical symbols of desired single-atom dopant species.

crystal_structures: Dictionary with crystal structure to be used for each species. These will be passed on as input to ase.build.bulk. So, must be one of sc, fcc, bcc, tetragonal, bct, hcp, rhombohedral, orthorhombic, diamond, zincblende, rocksalt, cesiumchloride, fluorite or wurtzite. If not specified, the default reference crystal structure for each species from ase.data will be used.

facets: Dictionary with the surface facets to be considered for each species. If not specified for a given species, the following defaults will be used based on the crystal structure: fcc/bcc: 100, 111, 110 hcp: 0001

supercell_dim: Tuple or List specifying the size of the supercell to be generated in the format (nx, ny, nz). Defaults to (3, 3, 4).

default_lat_param_lib: String indicating which library the lattice constants should be pulled from if not specified in either a_dict or c_dict.

Options:
pbe_fd: parameters calculated using xc=PBE and finite-difference
beefvdw_fd: parameters calculated using xc=BEEF-vdW and finite-difference
pbe_pw: parameters calculated using xc=PBE and a plane-wave basis set
beefvdw_fd: parameters calculated using xc=BEEF-vdW and a plane-wave basis set

N.B. if there is a species present in `host_species` that is NOT in the
reference library specified, it will be pulled from `ase.data`.

a_dict: Dictionary with lattice parameters to be used for each species. If not specified, defaults from default_lat_param_lib are used.

c_dict: Dictionary with lattice parameters to be used for each species. If not specified, defaults from default_lat_param_lib are used.

set_host_magnetic_moments: List of host species for which magnetic moments need to be set. If not specified, magnetic moments will be set only for Fe, Co, Ni (the ferromagnetic elements).

host_magnetic_moments: Dictionary with the magnetic moments to be set for the host chemical species listed previously. If not specified, default ground state magnetic moments from ase.data are used.

set_dopant_magnetic_moments: List of single-atom species for which magnetic moments need to be set. If not specified, magnetic moments will guessed for all dopant species from ase.data.

dopant_magnetic_moments: Dictionary with the magnetic moments to be set for the single-atom dopant species listed previously. If not specified, default ground state magnetic moments from ase.data are used.

vacuum: Float specifying the amount of vacuum (in Angstrom) to be added to the slab (the slab is placed at the center of the supercell). Defaults to 10.0 Angstrom.

n_fixed_layers: Integer giving the number of layers of the slab to be fixed starting from the bottom up (e.g., a value of 2 will fix the bottom 2 layers). Defaults to 0 (i.e., no layers in the slab fixed).

place_dopant_at_center: Boolean specifying whether the single-atom should be placed at the center of the unit cell. If False, the single-atom will be placed at the origin. Defaults to True.

write_to_disk: Boolean specifying whether the bulk structures generated should be written to disk. Defaults to False.

write_location: String with the location where the per-species/per-crystal structure directories must be constructed and structure files written to disk. In the specified write_location, the following directory structure will be created: [host]/[dopant]/[facet]/substrate/input.traj

dirs_exist_ok: Boolean specifying whether existing directories/files should be overwritten or not. This is passed on to the os.makedirs builtin. Defaults to False (raises an error if directories corresponding the species and crystal structure already exist).

Returns:

Type Description
Dictionary with the single-atom alloy structures as
write-location, if any, for each
each input host and dopant species combination.

Example: { "Fe": { "Cu": { "bcc100": { "structure": FeN-1_Cu1_saa_obj, "traj_file_path": "/path/to/Cu/on/bcc/Fe/100/surface/traj/file" }, "bcc110": ..., }, "Ru": { ... }, }, "Rh": { ... } }

Source code in autocat/saa.py
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def generate_saa_structures(
    host_species: List[str],
    dopant_species: List[str],
    crystal_structures: Dict[str, str] = None,
    facets: Dict[str, str] = None,
    supercell_dim: Sequence[int] = (3, 3, 4),
    default_lat_param_lib: str = None,
    a_dict: Dict[str, float] = None,
    c_dict: Dict[str, float] = None,
    set_host_magnetic_moments: List[str] = None,
    host_magnetic_moments: Dict[str, float] = None,
    set_dopant_magnetic_moments: List[str] = None,
    dopant_magnetic_moments: Dict[str, float] = None,
    vacuum: float = 10.0,
    n_fixed_layers: int = 0,
    place_dopant_at_center: bool = True,
    write_to_disk: bool = False,
    write_location: str = ".",
    dirs_exist_ok: bool = False,
) -> Dict[str, Dict[str, Dict[str, Dict[str, Any]]]]:
    """
    Builds single-atom alloys for all combinations of host species and dopant
    species given. Will write the structures to separate directories if
    specified.

    Parameters
    ----------

    host_species (REQUIRED):
        List of chemical species of desired host (substrate) species.

    dopant_species (REQUIRED):
        List of chemical symbols of desired single-atom dopant species.

    crystal_structures:
        Dictionary with crystal structure to be used for each species.
        These will be passed on as input to `ase.build.bulk`. So, must be one
        of sc, fcc, bcc, tetragonal, bct, hcp, rhombohedral, orthorhombic,
        diamond, zincblende, rocksalt, cesiumchloride, fluorite or wurtzite.
        If not specified, the default reference crystal structure for each
        species from `ase.data` will be used.

    facets:
        Dictionary with the surface facets to be considered for each
        species.
        If not specified for a given species, the following defaults will be
        used based on the crystal structure:
        fcc/bcc: 100, 111, 110
        hcp: 0001

    supercell_dim:
        Tuple or List specifying the size of the supercell to be
        generated in the format (nx, ny, nz).
        Defaults to (3, 3, 4).

    default_lat_param_lib:
        String indicating which library the lattice constants should be pulled
        from if not specified in either a_dict or c_dict.

        Options:
        pbe_fd: parameters calculated using xc=PBE and finite-difference
        beefvdw_fd: parameters calculated using xc=BEEF-vdW and finite-difference
        pbe_pw: parameters calculated using xc=PBE and a plane-wave basis set
        beefvdw_fd: parameters calculated using xc=BEEF-vdW and a plane-wave basis set

        N.B. if there is a species present in `host_species` that is NOT in the
        reference library specified, it will be pulled from `ase.data`.

    a_dict:
        Dictionary with lattice parameters <a> to be used for each species.
        If not specified, defaults from `default_lat_param_lib` are used.

    c_dict:
        Dictionary with lattice parameters <c> to be used for each species.
        If not specified, defaults from `default_lat_param_lib` are used.

    set_host_magnetic_moments:
        List of host species for which magnetic moments need to be set.
        If not specified, magnetic moments will be set only for Fe, Co, Ni
        (the ferromagnetic elements).

    host_magnetic_moments:
        Dictionary with the magnetic moments to be set for the host chemical
        species listed previously.
        If not specified, default ground state magnetic moments from
        `ase.data` are used.

    set_dopant_magnetic_moments:
        List of single-atom species for which magnetic moments need to be set.
        If not specified, magnetic moments will guessed for all dopant species from
        `ase.data`.

    dopant_magnetic_moments:
        Dictionary with the magnetic moments to be set for the single-atom
        dopant species listed previously.
        If not specified, default ground state magnetic moments from
        `ase.data` are used.

    vacuum:
        Float specifying the amount of vacuum (in Angstrom) to be added to
        the slab (the slab is placed at the center of the supercell).
        Defaults to 10.0 Angstrom.

    n_fixed_layers:
        Integer giving the number of layers of the slab to be fixed
        starting from the bottom up (e.g., a value of 2 will fix the
        bottom 2 layers).
        Defaults to 0 (i.e., no layers in the slab fixed).

    place_dopant_at_center:
        Boolean specifying whether the single-atom should be placed
        at the center of the unit cell. If False, the single-atom will
        be placed at the origin.
        Defaults to True.

    write_to_disk:
        Boolean specifying whether the bulk structures generated should be
        written to disk.
        Defaults to False.

    write_location:
        String with the location where the per-species/per-crystal structure
        directories must be constructed and structure files written to disk.
        In the specified write_location, the following directory structure
        will be created:
        [host]/[dopant]/[facet]/substrate/input.traj

    dirs_exist_ok:
        Boolean specifying whether existing directories/files should be
        overwritten or not. This is passed on to the `os.makedirs` builtin.
        Defaults to False (raises an error if directories corresponding the
        species and crystal structure already exist).

    Returns
    -------

    Dictionary with the single-atom alloy structures as `ase.Atoms` objects and
    write-location, if any, for each {crystal structure and facet} specified for
    each input host and dopant species combination.

    Example:
    {
        "Fe": {
            "Cu": {
                "bcc100": {
                    "structure": FeN-1_Cu1_saa_obj,
                    "traj_file_path": "/path/to/Cu/on/bcc/Fe/100/surface/traj/file"
                },
                "bcc110": ...,
            },
            "Ru": {
                ...
            },
        },
        "Rh": {
            ...
        }
    }

    """

    hosts = generate_surface_structures(
        host_species,
        crystal_structures=crystal_structures,
        facets=facets,
        supercell_dim=supercell_dim,
        default_lat_param_lib=default_lat_param_lib,
        a_dict=a_dict,
        c_dict=c_dict,
        set_magnetic_moments=set_host_magnetic_moments,
        magnetic_moments=host_magnetic_moments,
        vacuum=vacuum,
        n_fixed_layers=n_fixed_layers,
    )

    if set_dopant_magnetic_moments is None:
        set_dopant_magnetic_moments = dopant_species
    if dopant_magnetic_moments is None:
        dopant_magnetic_moments = {}

    dop_mm_library = {
        dop: ground_state_magnetic_moments[atomic_numbers[dop]]
        for dop in dopant_species
    }
    dop_mm_library.update(dopant_magnetic_moments)

    saa_structures = {}
    # iterate over hosts
    for host in hosts:
        saa_structures[host] = {}
        # iterate over single-atoms
        for dopant in dopant_species:
            # ensure host != single-atom
            if dopant == host:
                continue
            saa_structures[host][dopant] = {}
            # iterate over surface facets
            for facet in hosts[host]:
                host_structure = hosts[host][facet].get("structure")
                doped_structure = substitute_single_atom_on_surface(
                    host_structure,
                    dopant,
                    place_dopant_at_center=place_dopant_at_center,
                    dopant_magnetic_moment=dop_mm_library.get(dopant),
                )

                traj_file_path = None
                if write_to_disk:
                    dir_path = os.path.join(
                        write_location, host, dopant, facet, "substrate"
                    )
                    os.makedirs(dir_path, exist_ok=dirs_exist_ok)
                    traj_file_path = os.path.join(dir_path, "input.traj")
                    doped_structure.write(traj_file_path)
                    print(
                        f"{dopant}/{host}({facet}) structure written to {traj_file_path}"
                    )

                saa_structures[host][dopant][facet] = {
                    "structure": doped_structure,
                    "traj_file_path": traj_file_path,
                }
    return saa_structures

substitute_single_atom_on_surface(host_structure, dopant_element, place_dopant_at_center=True, dopant_magnetic_moment=0.0)

For a given host (elemental surface) structure and a dopant element, returns a slab with one host atom on the surface substituted with the specified dopant element with a specified magnetic moment. Note that for the current implementation (single-atom alloys), there will exist only one symmetrically unique site to substitute on the surface of the elemental slab.

Parameters:

Name Type Description Default
host_structure Atoms

ase.Atoms object of the host slab to be doped.

required

dopant_element (REQUIRED): String of the elemental species to be substitutionally doped into the host structure.

place_dopant_at_center: Boolean specifying whether the single-atom dopant should be placed at the center of the unit cell. If False, the dopant atom will be placed at the origin. Defaults to True.

dopant_magnetic_moment: Float with the initial magnetic moment on the doped single-atom. Defaults to no spin polarization (i.e., magnetic moment of 0).

Returns:

Type Description
The elemental slab with a single-atom dopant on the surface as an
Atoms

Raises:

Type Description
NotImplementedError

If multiple symmetrically equivalent sites are found on the surface to dope. Note that is intended more as a "guardrail" on current functionality to match the maturity/implementation of other modules in autocat than an actual error. The error should no longer be present when the substitution functionality is folded into a more general form.

Source code in autocat/saa.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def substitute_single_atom_on_surface(
    host_structure: Atoms,
    dopant_element: str,
    place_dopant_at_center: bool = True,
    dopant_magnetic_moment: float = 0.0,
) -> Atoms:
    """
    For a given host (**elemental surface**) structure and a dopant element,
    returns a slab with one host atom on the surface substituted with the
    specified dopant element with a specified magnetic moment.
    Note that for the current implementation (single-atom alloys), there will
    exist only one symmetrically unique site to substitute on the surface of the
    elemental slab.

    Parameters
    ----------

    host_structure (REQUIRED):
        ase.Atoms object of the host slab to be doped.

    dopant_element (REQUIRED):
        String of the elemental species to be substitutionally doped into the
        host structure.

    place_dopant_at_center:
        Boolean specifying whether the single-atom dopant should be placed at
        the center of the unit cell. If False, the dopant atom will be placed at
        the origin.
        Defaults to True.

    dopant_magnetic_moment:
        Float with the initial magnetic moment on the doped single-atom.
        Defaults to no spin polarization (i.e., magnetic moment of 0).

    Returns
    -------

    The elemental slab with a single-atom dopant on the surface as an
    `ase.Atoms` object.

    Raises
    ------

    NotImplementedError
        If multiple symmetrically equivalent sites are found on the surface to dope.
        Note that is intended more as a "guardrail" on current functionality to
        match the maturity/implementation of other modules in `autocat` than an
        actual error. The error should no longer be present when the
        substitution functionality is folded into a more general form.

    """

    all_surface_indices = _find_all_surface_atom_indices(host_structure)

    ase_all_doped_structures = []
    for idx in all_surface_indices:
        dop_struct = host_structure.copy()
        dop_struct[idx].symbol = dopant_element
        dop_struct[idx].magmom = dopant_magnetic_moment
        ase_all_doped_structures.append(dop_struct)

    # convert ase substrate to pymatgen structure
    converter = AseAtomsAdaptor()
    pmg_doped_structures = [
        converter.get_structure(struct) for struct in ase_all_doped_structures
    ]

    # check that only one unique surface doped structure
    matcher = StructureMatcher()
    pmg_symm_equiv_doped_structure = [
        s[0] for s in matcher.group_structures(pmg_doped_structures)
    ]
    if len(pmg_symm_equiv_doped_structure) > 1:
        msg = "Multiple symmetrically unique sites to dope found."
        raise NotImplementedError(msg)

    # assumes only a single unique doped structure
    ase_substituted_structure = ase_all_doped_structures[0]

    # center the single-atom dopant
    if place_dopant_at_center:
        cent_x = (
            ase_substituted_structure.cell[0][0] / 2
            + ase_substituted_structure.cell[1][0] / 2
        )
        cent_y = (
            ase_substituted_structure.cell[0][1] / 2
            + ase_substituted_structure.cell[1][1] / 2
        )
        cent = (cent_x, cent_y, 0)
        ase_substituted_structure.translate(cent)
        ase_substituted_structure.wrap()

    return ase_substituted_structure