diff --git a/ase_sim.py b/ase_sim.py index d270acef037011009d0e77b6976248e60407d56e..529e8b36fdd74d375ed59569bc9e84d8edc6b36d 100644 --- a/ase_sim.py +++ b/ase_sim.py @@ -3,6 +3,7 @@ import numpy as np import math import ase.lattice.cubic +import ase.data import ase.io import ase.units @@ -18,6 +19,7 @@ class AseSim: self.atoms = None self.dyn = None self.run = None + self.tagged_elements = {} def create_lattice(self, lattice_size, basis, empty_space, pot_filename, rand_ox=False): self.atoms = ase.lattice.cubic.BodyCenteredCubic(basis, size=lattice_size, pbc=(True, True, False)) @@ -41,23 +43,22 @@ class AseSim: nbor_list = NeighborList(nbor_cutoffs, skin=0.01, self_interaction=False, bothways=True) nbor_list.update(self.atoms) - pot_path = os.path.join('data', pot_filename) - - self.dyn = SajuhakDynamics(self.atoms, nbor_list, pot_path, rand_ox) - self.run = self.dyn.run - self.atoms.set_calculator(KokkoPotential(nbor_list, pot_path)) - # initialize atom modifications - symbols = self.dyn.atoms.get_chemical_symbols() - positions = self.dyn.atoms.get_positions() + symbols = self.atoms.get_chemical_symbols() + positions = self.atoms.get_positions() max_miller = lattice_size[2] * (1 - empty_space) # replace atoms with empty spaces above a certain z position for i, position in enumerate(positions): - if position[2] > self.dyn.atoms.millerbasis[2][2]*max_miller: + if position[2] > self.atoms.millerbasis[2][2]*max_miller: symbols[i] = 'X' - self.dyn.atoms.set_chemical_symbols(symbols) + # store modifications and initialize dyn and calc + pot_path = os.path.join('data', pot_filename) + self.atoms.set_chemical_symbols(symbols) + self.dyn = SajuhakDynamics(self.atoms, nbor_list, pot_path, rand_ox) + self.run = self.dyn.run + self.atoms.set_calculator(KokkoPotential(nbor_list, pot_path)) # @profile # add -m memory_profiler to interpreter options def dope(self, dopants, basis): @@ -109,3 +110,26 @@ class AseSim: if self.dyn.atoms.get_atomic_numbers()[nbor_idx] == 0: empty_nbor_list[idx] += 1 self.dyn.atoms.set_array('empty_nbors', empty_nbor_list) + + def tag_elements(self): + """ + Change the tags of random elements in the lattice so they can be tracked + + :return: None + """ + tag_target = 5 # how many atoms to tag of each element + number_list = self.atoms.get_atomic_numbers() + element_list = np.unique(number_list) + tag_list = self.atoms.get_tags() + tagged_counts = {} # dict of amount of tagged atoms per element + tag_index = 1 + while sum(tagged_counts.values()) < tag_target * len(element_list): + rand_idx = np.random.randint(len(self.atoms)) + rand_number = number_list[rand_idx] + tagged_count = tagged_counts.get(rand_number, 0) + if not tagged_count >= tag_target: + tag_list[rand_idx] = tag_index + self.tagged_elements[tag_index] = rand_number + tag_index += 1 + tagged_counts[rand_number] = tagged_count + 1 + self.atoms.set_tags(tag_list) diff --git a/sajuhak_dyn.py b/sajuhak_dyn.py index 935d16d05898c3d46165f351249b3f18bc67791c..4838c5ad6a9790dd5e8c5072ed44764d1493ad64 100644 --- a/sajuhak_dyn.py +++ b/sajuhak_dyn.py @@ -97,7 +97,7 @@ class SajuhakDynamics(Dynamics): self.nbor_list = nbor_list # self.nbor_list.update(self.atoms) self.rand_ox = rand_ox - self.diff_const_dict = {} + self.tag_count_list = {} def run(self, kT, n_steps=100): for i in range(n_steps): @@ -111,38 +111,26 @@ class SajuhakDynamics(Dynamics): rand_idx = np.random.randint(len(self.atoms)) rand_number = self.atoms.get_atomic_numbers()[rand_idx] rand_symbol = ase.atoms.chemical_symbols[rand_number] - if rand_symbol not in self.diff_const_dict.keys(): - self.diff_const_dict[rand_symbol] = [0, 0] - self.diff_const_dict[rand_symbol][1] += 1 nbor_indices = self.nbor_list.neighbors[rand_idx] nbor_idx = np.random.choice(nbor_indices) nbor_number = self.atoms.get_atomic_numbers()[nbor_idx] nbor_symbol = ase.atoms.chemical_symbols[nbor_number] - if nbor_symbol not in self.diff_const_dict.keys(): - self.diff_const_dict[nbor_symbol] = [0, 0] - self.diff_const_dict[nbor_symbol][1] += 1 if rand_number == nbor_number: - # if the two atoms are the same, nothing changes, remove from total exchanges - self.diff_const_dict[rand_symbol][1] -= 1 - self.diff_const_dict[nbor_symbol][1] -= 1 + # if the two atoms are the same, nothing changes return # if either point is empty, initiate random (de)oxidation elif self.rand_ox and (rand_number == 0 or nbor_number == 0): # if either point is not oxygen, initiate random oxidation if rand_number != 8 and nbor_number != 8: if self.random_oxidation(kT, rand_idx, nbor_idx): - # if the oxidation was successful, don't swap these atoms yet, remove from total exchanges - self.diff_const_dict[rand_symbol][1] -= 1 - self.diff_const_dict[nbor_symbol][1] -= 1 + # if the oxidation was successful, don't swap these atoms yet return # else initiate random deoxidation else: if self.random_deoxidation(kT, rand_idx, nbor_idx): - # if the deoxidation was successful, don't swap these atoms yet, remove from total exchanges - self.diff_const_dict[rand_symbol][1] -= 1 - self.diff_const_dict[nbor_symbol][1] -= 1 + # if the deoxidation was successful, don't swap these atoms yet return energy_start = self.calc_energy_at_points([rand_idx, nbor_idx]) @@ -154,8 +142,14 @@ class SajuhakDynamics(Dynamics): if not np.random.random() < exp(-delta_e / kT): self.swap_atoms(nbor_idx, rand_idx) else: - self.diff_const_dict[rand_symbol][0] += 1 - self.diff_const_dict[nbor_symbol][0] += 1 + # swap tags + tag_list = self.atoms.get_tags() + rand_tag = tag_list[rand_idx] + self.tag_count_list[rand_tag] = self.tag_count_list.get(rand_tag, 0) + 1 + nbor_tag = tag_list[nbor_idx] + self.tag_count_list[nbor_tag] = self.tag_count_list.get(nbor_tag, 0) + 1 + tag_list[rand_idx], tag_list[nbor_idx] = tag_list[nbor_idx], tag_list[rand_idx] + self.atoms.set_tags(tag_list) def swap_atoms(self, index1, index2): atom1_number = self.atoms[index1].number diff --git a/sim_controller.py b/sim_controller.py index a146eed8e85343267a7bd22c661634d37d39a5b3..7645ba4a8e79195942534652958b365b9e2b956b 100644 --- a/sim_controller.py +++ b/sim_controller.py @@ -13,6 +13,7 @@ from collections import OrderedDict # import cProfile import ase_sim +from ase.data import chemical_symbols import vis @@ -175,6 +176,7 @@ class SimController(object): self.sim.create_lattice(**self.lattice_params, rand_ox=self.sim_params['rand_ox']) self.bulk_counts = self.sim.dope(self.bulk_dopants, self.sim.basis) self.empty_space_counts = self.sim.dope(self.empty_space_dopants, 'X') + self.sim.tag_elements() def run(self): if type(self.sim_params['kT']) is list: @@ -223,23 +225,22 @@ class SimController(object): lattice_counts = bulk_counts_header + bulk_counts_str + vacuum_counts_header + vacuum_counts_str - diff_const_header = "Exchanges per element (successful/total (percentage)):\n" - diff_const_line = " {symbol}: {successful}/{total} ({perc:.2f}%)\n" + diff_const_header = "Distances traveled by tagged atoms (average, list):\n" + diff_const_line = " {symbol}: {average}, {list}\n" diff_const_str = "" - # tot_exchanges = self.sim_params['n_exchanges'] - # if type(tot_exchanges) is list: - # tot_exchanges = sum(tot_exchanges) - - diff_const_dict_items_sorted_by_total = sorted(self.sim.dyn.diff_const_dict.items(), - key=lambda item: item[1][1]) - for symbol, success_tot in diff_const_dict_items_sorted_by_total: - if symbol == 'X': - symbol = 'Vac' - diff_const_str += diff_const_line.format(symbol=symbol, - successful=success_tot[0], - total=success_tot[1], - perc=success_tot[0] / success_tot[1] * 100) + counts_by_element = {} + for index, number in self.sim.tagged_elements.items(): + count = self.sim.dyn.tag_count_list.get(index, 0) + if number not in counts_by_element.keys(): + counts_by_element[number] = [] + counts_by_element[number].append(count) + + for number, counts in sorted(counts_by_element.items()): + if number != 0: + diff_const_str += diff_const_line.format(symbol=chemical_symbols[number], + average=np.mean(counts), + list=counts) diff_const = diff_const_header + diff_const_str diff --git a/vis.py b/vis.py index 73827315bfcb33637f611639a5478815da4ad10c..bf9f3f73d70c11ec7eabd213cf2f2b534552b45f 100644 --- a/vis.py +++ b/vis.py @@ -26,15 +26,17 @@ def read_xyz_frame(xyz_file_in): list_properties = [] comment = xyz_file_in.readline() info = ase.io.extxyz.key_val_str_to_dict(comment) - # properties, names, dtype, convs = ase.io.extxyz.parse_properties(info['Properties']) + _, names, _, _ = ase.io.extxyz.parse_properties(info['Properties']) # read the next n_sites lines, the index is not used for _ in range(n_sites): - # an extxyz line is "element x y z properties" line = xyz_file_in.readline() - if info['Properties'].find('empty_nbors:I:1') > info['Properties'].find('Z:I:1'): - element, x, y, z, _, empty_nbors = line.split() - else: - element, x, y, z, empty_nbors, _ = line.split() + values = line.split() + element, x, y, z = values[:4] + values = values[4:] + row = {} + for i, name in enumerate(names[2:]): + row[name] = values[i] + empty_nbors = row['empty_nbors'] # add to the list of particles list_properties.append((element, float(x), float(y), float(z), int(empty_nbors))) # return the list of particles