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 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.
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)
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)
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)
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')
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()
Recommended Posts