[PYTHON] Suche nach stabilen Strukturen von Metallnanoclustern mithilfe genetischer Algorithmen

ASE (Atomic Simulation Environment) ist mit einer stabilen Struktursuchfunktion unter Verwendung eines genetischen Algorithmus (GA) ausgestattet. Genetische Algorithmen können verwendet werden, um effizient nach stabilen Strukturen in Bulk- und Nanoclustern zu suchen.

Lassen Sie uns die Struktur von Metallnanoclustern anhand des Official Document Tutorial durchsuchen. Der Einfachheit halber wird EMT (Effective Medium Theory) zur Energieberechnung verwendet.

Die folgende Berechnung wurde im Jupyter Lab durchgeführt.

Erstellen Sie eine erste Gruppe

Erstellen Sie eine zufällige Struktur, indem Sie das chemische Verhältnis angeben. Hier werden 10 Cluster von Pt 15 Au 15 in einer 25 × 25 × 25A-Simulationsbox erzeugt.

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)]

Verwenden Sie die Funktion view, um die ursprüngliche Struktur zu überprüfen.

from ase.visualize import view

view(initial_population)

Eine solche anfängliche Population wurde erzeugt. Da es mit Zufallszahlen generiert wird, ändert sich dieses Ergebnis von Versuch zu Versuch.

ga_initial.png

In die Datenbank schreiben

Speichern Sie die erstellte Anfangsgruppe in einer externen Datenbankdatei. Mit dem Modul "ase.ga.data" können Sie große Mengen an Rechendaten effizient verwalten.

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)

Strukturelle Entspannung der Ausgangspopulation

Optimieren Sie eine zufällig erstellte Struktur.

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)

Bedingungen für genetische Algorithmen festlegen

Stellen Sie die Crossover- und Mutationsalgorithmen ein.

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

#Algorithmus zur Bestimmung der Identität der Atomstruktur:Wird verwendet, um die Vielfalt der Bevölkerung sicherzustellen
comperator = InteratomicDistanceComparator(n_top=len(atom_numbers),
                                     pair_cor_cum_diff=0.015,
                                     pair_cor_max=0.7,
                                     dE=0.02,
                                     mic=False)

#Kreuzung
pairing = CutAndSplicePairing(slab, len(atom_numbers), cd)

#Mutation:Wählen Sie zufällig einen von zwei Mutationsalgorithmen
mutations = OperationSelector([1., 1.], #Wahrscheinlichkeitsverhältnis zur Übernahme jedes Algorithmus
                              [MirrorMutation(cd, len(atom_numbers)),
                               RattleMutation(cd, len(atom_numbers))])
population = Population(data_connection=db,
                        population_size=population_size,
                        comparator=comperator)

Ausführung des genetischen Algorithmus

Führt einen genetischen Algorithmus aus. Das wird ein paar Minuten dauern. Wenn Sie DFT anstelle von EMT verwenden, dauert es noch länger.

from random import random

for i in range(population_size * n_generation):
    #Kreuzung
    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
        
    #Strukturelle Entspannung der Nachkommen
    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')

Ergebnisse anzeigen

Zeichnen Sie mit matplotlib die stabilste Struktur für jede Generation.

%matplotlib inline

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

#Konvertieren Sie das Atoms-Objekt in OffsetImage von 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))    

#Holen Sie sich das Atoms-Objekt für jede Generation
X, Y = [], []
for i_gen in range(n_generation):
    atoms = db.get_all_relaxed_candidates_after_generation(i_gen)[0] #Nach Energie sortiert
    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

Referenz

Recommended Posts

Suche nach stabilen Strukturen von Metallnanoclustern mithilfe genetischer Algorithmen
Automatisierung der Algorithmusgenerierung mit genetischen Algorithmen
Suchen Sie mit COTOHA nach profitablen Marken
Über die zirkuläre Überkreuzung genetischer Algorithmen
Eindrücke von der Verwendung von Flask für einen Monat