[PYTHON] Recherche de structures stables de nanoclusters métalliques à l'aide d'algorithmes génétiques

ASE (Atomic Simulation Environment) est équipé d'une fonction de recherche de structure stable utilisant un algorithme génétique (GA). Les algorithmes génétiques peuvent être utilisés pour rechercher efficacement des structures stables en vrac et en nanoclusters.

Cherchons la structure des nanoclusters métalliques en nous référant au Official Document Tutorial. Pour plus de simplicité, EMT (Effective Medium Theory) est utilisé pour le calcul de l'énergie.

Le calcul suivant a été effectué sur Jupyter Lab.

Créer un premier groupe

Créez une structure aléatoire en spécifiant le rapport chimique. Ici, 10 clusters de Pt 15 </ sub> Au 15 </ sub> sont créés dans une boîte de simulation 25 × 25 × 25A.

import numpy as np
from ase.atoms import Atoms
from ase.ga.startgenerator import StartGenerator
from ase.ga.utilities import closest_distances_generator

L = 25.
population_size = 10
atom_numbers = Atoms('Pt15Au15').get_atomic_numbers()
slab = Atoms(cell=np.array([L, L, L]))

cd = closest_distances_generator(atom_numbers=atom_numbers,
                                 ratio_of_covalent_radii=0.7)
sg = StartGenerator(slab=slab,
                    atom_numbers=atom_numbers,
                    closest_allowed_distances=cd,
                    box_to_place_in=[np.zeros(3), np.eye(3)*L/2.])

initial_population = [sg.get_new_candidate() for _ in range(population_size)]

Utilisez la fonction view pour vérifier la structure initiale.

from ase.visualize import view

view(initial_population)

Une telle population initiale a été générée. Puisqu'il est généré avec des nombres aléatoires, ce résultat changera d'un essai à l'autre.

ga_initial.png

Écrire dans la base de données

Enregistrez le groupe initial créé dans un fichier de base de données externe. Le module ʻase.ga.data` vous permet de gérer efficacement de grandes quantités de données de calcul.

from ase.ga.data import PrepareDB

db_file = 'ga.db'

db = PrepareDB(db_file_name=db_file,
              simulation_cell=slab,
              stoichiometry=atom_numbers,
              population_size=population_size)

for atoms in initial_population:
    db.add_unrelaxed_candidate(atoms)

Relaxation structurelle de la population initiale

Optimiser une structure créée aléatoirement.

from ase.ga.data import DataConnection
from ase.optimize import BFGS
from ase.calculators.emt import EMT

db = DataConnection(db_file)

while db.get_number_of_unrelaxed_candidates() > 0:
    atoms = db.get_an_unrelaxed_candidate()
    atoms.set_calculator(EMT())
    BFGS(atoms, trajectory=None, logfile=None).run(fmax=0.05, steps=100)
    atoms.info['key_value_pairs']['raw_score'] = -atoms.get_potential_energy()
    db.add_relaxed_step(atoms)

Définition des conditions des algorithmes génétiques

Définissez les algorithmes de croisement et de mutation.

from random import random
from ase.ga.data import DataConnection
from ase.ga.population import Population
from ase.ga.standard_comparators import InteratomicDistanceComparator
from ase.ga.cutandsplicepairing import CutAndSplicePairing
from ase.ga.offspring_creator import OperationSelector
from ase.ga.standardmutations import (MirrorMutation, RattleMutation)

mutation_probability = 0.3
n_generation = 10

#Algorithme de détermination d'identité de structure atomique:Utilisé pour assurer la diversité de la population
comperator = InteratomicDistanceComparator(n_top=len(atom_numbers),
                                     pair_cor_cum_diff=0.015,
                                     pair_cor_max=0.7,
                                     dE=0.02,
                                     mic=False)

#Traversée
pairing = CutAndSplicePairing(slab, len(atom_numbers), cd)

#mutation:Choisissez au hasard l'un des deux algorithmes de mutation
mutations = OperationSelector([1., 1.], #Ratio de probabilité pour adopter chaque algorithme
                              [MirrorMutation(cd, len(atom_numbers)),
                               RattleMutation(cd, len(atom_numbers))])
population = Population(data_connection=db,
                        population_size=population_size,
                        comparator=comperator)

Exécution d'algorithme génétique

Exécute un algorithme génétique. Cela prendra quelques minutes. Si vous utilisez DFT au lieu d'EMT, cela prendra encore plus de temps.

from random import random

for i in range(population_size * n_generation):
    #Traversée
    atoms1, atoms2 = population.get_two_candidates()
    atoms3, desc = pairing.get_new_individual([atoms1, atoms2])
    if atoms3 is None: continue
    db.add_unrelaxed_candidate(atoms3, description=desc)
    
    #mutation
    if random() < mutation_probability:
        atoms3_mut, desc = mutations.get_new_individual([atoms3])
        if atoms3_mut is not None:
            db.add_unrelaxed_step(atoms3_mut, description=desc)
            atoms3 = atoms3_mut
        
    #Relaxation structurelle de la progéniture
    atoms3.set_calculator(EMT())
    BFGS(atoms3, trajectory=None, logfile=None).run(fmax=0.05, steps=100)    
    atoms3.info['key_value_pairs']['raw_score'] = -atoms3.get_potential_energy()
    db.add_relaxed_step(atoms3)
    population.update()
    
    print(f'{i}th structure relaxation completed')

Voir les résultats

Tracez la structure la plus stable pour chaque génération en utilisant matplotlib.

%matplotlib inline

from tempfile import NamedTemporaryFile
from ase.io import write
import matplotlib.pyplot as plt
from matplotlib.offsetbox import (OffsetImage, AnnotationBbox)

#Convertir l'objet Atoms en Offset Image de matplotlib
def atoms_to_img(atoms):
    pngfile = NamedTemporaryFile(suffix='.png').name
    write(pngfile, atoms, scale=10, show_unit_cell=False)
    return OffsetImage(plt.imread(pngfile, format='png'), zoom=1.0)   

plt.rcParams["font.size"] = 24
fig, ax = plt.subplots(figsize=(12,8))    

#Obtenez un objet Atoms pour chaque génération
X, Y = [], []
for i_gen in range(n_generation):
    atoms = db.get_all_relaxed_candidates_after_generation(i_gen)[0] #Trié par énergie
    e = atoms.get_potential_energy()
    X += [i_gen-0.5, i_gen+0.5]
    Y += [e, e]
    if i_gen % 3 == 0:
        abb = AnnotationBbox(atoms_to_img(atoms), [i_gen+0.5, e+2.], frameon=False)
        ax.add_artist(abb)

ax.plot(X, Y, color='darkblue', linewidth=3.)
ax.set_ylim((min(Y)-1, max(Y)+5))
ax.set_title('Optimal Structure of Pt$_{15}$Au$_{15}$ by Generation')
ax.set_xlabel('GA Generation')
ax.set_ylabel('Total Energy (eV)')
plt.show()

GA.png

référence

Recommended Posts

Recherche de structures stables de nanoclusters métalliques à l'aide d'algorithmes génétiques
Automatisation de la génération d'algorithmes à l'aide d'algorithmes génétiques
Rechercher des marques rentables avec COTOHA
À propos du croisement circulaire d'algorithmes génétiques
Impressions d'utilisation de Flask pendant un mois