def main(): """ :brief Convert an AEI zeolite structure to a borate structure Main routine :return borate: borate structure """ directories = cell_operations.Directories(structure='aei', input='inputs', output='aei_outputs') silicate = full_coordinated_structure(directories) xyz('fully_coordinated/fully_coord', silicate) print("If atomic ordering changes => Different starting structure," "I break get_structural_components") structures = get_structural_components(silicate, visualise=False) # Boron framework components upper_ring = ring_mods.ring_substitutions(structures['upper_ring']) xyz('fully_coordinated/upper_ring_swap', upper_ring) # lower_ring = ring_substitutions(structures['lower_ring']) # chains = chain_substitutions(structures['chains']) # Piece the borate components together # Some guess at the new lattice vectors # Fold atoms back into central cell and remove ~ duplicates # Return the final structure for relaxing return
def output_cell_from_general_criterion(system, cutoff): """ (Hopefully) works for any cell Expect it to give the same max integers as cubic cells """ name = system.system_label lattice = system.lattice unit_cell = system.unit_cell max_integers = lattice_module.translation_integers_for_radial_cutoff( lattice, cutoff) print("General criterion for " + name, "Lattice integers:", max_integers) translations = lattice_sum(lattice, max_integers, cutoff) super_cell = supercell.build_supercell(unit_cell, translations) write.xyz(name + "_general_criterion", super_cell) return
def replace_uncoordinated_atoms(unit_cell: list, translations: list) -> List[atoms.Atom]: """ Identify non-bonded atoms in the unit cell and replace with their equivalent positions in adjacent unit cells that result in a fully connected structure Translate each loose atom by all translation vectors and see if any equivalent position puts each loose atom as a NN of an atom in the central cell Retain these and dump the uncoordinated atoms, producing a fully-coordinated structure. This is the structure one works with for boron substitutions. Once the boron framework has been constructed, one wraps all atoms outside the cell to inside. :param unit_cell: atoms.Atoms Unit cell of atoms :param translations: List of translation vectors :return unit_cell: Unit cell with no uncoordinated atoms """ # AEI Si and O bond bounds (angstrom) bond_bounds = BondLengthBounds(1.4, 1.8) n_atoms = len(unit_cell) unit_cell_positions = [atom.position for atom in unit_cell] loose_atom_indices = index_loose_atoms(unit_cell_positions, bond_bounds.upper) assert len(loose_atom_indices) == 4, \ "For the AEI structure, expect 4 Oxy atoms with no connections in the central cell" # One equivalent position per loose atom equivalent_positions = [ find_equivalent_position(ia, unit_cell, translations, bond_bounds) for ia in loose_atom_indices ] unit_cell = replace_loose_atoms(unit_cell, loose_atom_indices, equivalent_positions) assert len(unit_cell) == n_atoms output = True if output: output_dir = 'aei_outputs' xyz(output_dir + '/' + "aei_primitive_cell_fullcon", unit_cell) print( "Output a cell with full connectivity. " "Atoms that lie outside the primitive cell can be folded back in") return unit_cell
def output_cell_from_cubic_criterion(system, cutoff): """ Works for simple cubic cells, but is not appropriate as cell angles deviation from 90 degrees This typically underestimates max_integers by 1 (see BCC or FCC), such that one can improve the situation by doing: max_integers = [i + 1 for i in max_integers] """ name = system.system_label lattice = system.lattice unit_cell = system.unit_cell max_integers = lattice_module.simple_cubic_cell_translation_integers( lattice, cutoff) print("Cubic criterion for " + name, "Lattice integers:", max_integers) translations = lattice_sum(lattice, max_integers, cutoff) super_cell = supercell.build_supercell(unit_cell, translations) write.xyz(name + "_cubic_criterion", super_cell) return
def local_snip(molecule, neighbours, shared_oxy): silicons = neighbours[shared_oxy] assert(len(silicons) == 2) # List corresponding tetrahedrons wth common corner-sharing # oxygen removed tetrahedra = [] for silicon in silicons: tetrahedron = [molecule[silicon]] for oxy in neighbours[silicon]: if oxy != shared_oxy: tetrahedron.append(molecule[oxy]) tetrahedra.append(tetrahedron) molecule = [] for tetrahedron in tetrahedra: for atom in tetrahedron: molecule.append(atom) write.xyz("two_tet.xyz", molecule, "Two tetra") # tetrahedron -> triangle triangles = [] for tetrahedron in tetrahedra: # If the tetrahedron has one si and 3 oxy if len(tetrahedron) == 4: triangles.append(operations.triangle_from(tetrahedron)) else: triangles.append([]) # Remove shared oxygen last, due to indexing changes = [SnipClass(old_atom_index=shared_oxy, modifier='remove')] # Extract B positions and labels to B for i,silicon in enumerate(silicons): new_atom = [atom for atom in molecule if atom.species.lower()=='si'][0] change = SnipClass(old_atom_index=silicon, new_atom=new_atom, modifier='replace') changes.append(change) return operations
def replace_loose_atoms(unit_cell: list, loose_atom_indices: List[int], equivalent_positions: list, visualise_parts=False) -> list: """ Remove uncoordinated atoms and their equivalents to the unit cell :param unit_cell: List of atoms :param loose_atom_indices: Indices for uncoordinated atoms :param equivalent_positions: List of equivalent positions for uncoordinated atoms. of size len(loose_atom_indices) :param visualise_parts: bool, output .xyz of original unit_cell, removed atoms and replacement atoms :return new_unit_cell with no uncoordinated atoms """ assert len(loose_atom_indices) == len(equivalent_positions), "Should be one new (equivalent) " \ "position per uncoordinated atom" # Can't use pop as it changes the indexing each time new_unit_cell = [] removed_atoms = [] for ia in range(0, len(unit_cell)): if ia not in loose_atom_indices: new_unit_cell.append(unit_cell[ia]) else: removed_atoms.append(unit_cell[ia]) #TODO(Alex) Generalise # I know they're all oxygens but should treat this correctly if making general replacements = [ atoms.Atom(position=position, species='O') for position in equivalent_positions ] if visualise_parts: output_dir = 'aei_outputs' xyz(output_dir + '/' + "aei_central_cell", unit_cell) xyz(output_dir + '/' + "aei_replacements_cell", replacements) xyz(output_dir + '/' + "aei_removed_atoms", removed_atoms) return new_unit_cell + replacements
# If odd, can centre on T=(0,0,0), else can't n = [3, 3, 3] translations = supercell.translation_vectors(lattice, n, centred_on_zero=False) super_cell = supercell.build_supercell(unit_cell, translations) # Information print('Number of atoms in supercell:', len(super_cell)) #print("Supercell lattice constants: ", , '(Ang)') Write this structure_string = input_strings.structure(super_cell, unit=atoms.CoordinateType.XYZ) #print(structure_string) xyz_string = write.xyz(super_cell) print(xyz_string) quit() # Find central cell icentre = supercell.flatten_supercell_limits([0,0,0], n, centred_on_zero=True) # could return this from the build_supercell command cells = supercell.list_global_atom_indices_per_cells(unit_cell, translations) central_cell_atom_indices = cells[icentre] print("Central cell contains atoms with this indices:", central_cell_atom_indices) central_cell = [] for iatom in central_cell_atom_indices: central_cell.append(super_cell[iatom])
nearest neighbours to atoms in the central cell. One can then perform substitutions on this system and fold any positions that lie outside the central cell of the final structure, back inside """ from modules.fileio.write import xyz import cell_operations # Main Routine directories = cell_operations.Directories(structure='aei', input='inputs', output='aei_outputs') unit_cell, lattice_vectors = cell_operations.get_primitive_unit_cell( directories, visualise=True) xyz("cell_primitive", unit_cell) translations = cell_operations.translations_for_fully_coordinated_unit( unit_cell, lattice_vectors) # Find atoms neighbouring central cell coordinating_atoms = cell_operations.find_atoms_neighbouring_central_cell( unit_cell, translations, visualise=True) # Replace uncoordinated atoms in central cell (doesn't utilise coordinating_atoms) unit_cell_replaced = cell_operations.replace_uncoordinated_atoms( unit_cell, translations) xyz("cell_replaced", unit_cell_replaced) # Check moved atoms can be restored to positions in the central cell unit_cell_restored = cell_operations.ensure_atoms_in_central_cell( unit_cell_replaced, lattice_vectors)
def convert_ring(species, positions): n_atoms = len(species) ring_centre = geometry.find_centre(positions) si_oo_units, oxy_ring_atoms = identify_si_o_o_units(species, positions, plane_index=0) # Else same atom will get shifted for each time it reappears in a unit si_oo_units = remove_sharing_oxygens(si_oo_units) # Move each si_oo_unit, length found from trial and error (specific to AEI) length = 8 for unit in si_oo_units: # Find unit centre unit_centre = geometry.find_centre( [positions[iatom] for iatom in unit]) # Point outwards from the ring centre radial_vector = np.asarray(unit_centre) - ring_centre scaled_radial_vector = scaled_vector(length, radial_vector) # Update each position for atom in unit: positions[atom] = scaled_radial_vector + positions[atom] # Move each oxy atom in the ring oxy_ring_positions = move_atoms_radially(oxy_ring_atoms, positions, ring_centre, 2) # 5 cnt = 0 for iatom in oxy_ring_atoms: positions[iatom] = oxy_ring_positions[cnt] cnt += 1 print_intermediate = True if print_intermediate: molecule = atoms.Atoms(species, positions) write.xyz("select.xyz", molecule) quit('up to here - looks like things move unexpectedly') # Assume Br-O bond length bond_length_bo = 1.7 # For each Si-O-O of a former tetrahedron, move a unit towards each of the two closest ring oxygens # cleave off one of the oxy and convert Si -> Br translated_species = [] translated_positions = [] for si_oo_unit in si_oo_units: neighbouring_ring_atoms = find_closest_ring_oxygens( species, positions, si_oo_unit) ts, tp = translate_si_o_o_unit(species, positions, si_oo_unit, neighbouring_ring_atoms) translated_species += ts translated_positions += tp # Remove old atoms atom_indices = np.delete(np.arange(0, n_atoms), flatten(si_oo_units)) new_species = [] new_positions = [] for iatom in atom_indices: new_species.append(species[iatom]) new_positions.append(positions[iatom]) # Add new atoms assert len(translated_species) % 2 == 0 for iatom in range(0, len(translated_species)): new_species.append(translated_species[iatom]) new_positions.append(translated_positions[iatom]) assert (len(new_species) == len(new_positions)) # Smart way to do this is to create sets of pair indices from the translated silicons, # then put oxygens between them # I always do operations in pairs, so should go [b,o,b,o,b,o...] => elements 0 and 2 are a pair in the ring. n_atoms = len(new_species) boron_indices = [i for i in range(0, n_atoms) if new_species[i] == 'B'] # Create list of pairs boron_pairs = [] for i in range(0, len(boron_indices), 2): boron_A = boron_indices[i] boron_B = boron_indices[i + 1] print(new_species[boron_A], new_species[boron_B]) boron_pairs.append([boron_A, boron_B]) # Put oxygens between these pairs for pair in boron_pairs: pos_A = np.asarray(new_positions[pair[0]]) pos_B = np.asarray(new_positions[pair[1]]) print(np.linalg.norm(pos_A - pos_B)) pos_oxy = 0.5 * (pos_A + pos_B) new_positions.append(pos_oxy.tolist()) new_species.append('O') return new_species, new_positions
One can then perform substitutions on this system and fold any positions that lie outside the central cell of the final structure, back inside (assuming I can get the new lattice vectors correct) """ from modules.fileio.write import xyz import cell_operations # Main Routine directories = cell_operations.Directories(structure='aei', input='inputs', output='aei_outputs') unit_cell, lattice_vectors = cell_operations.get_primitive_unit_cell(directories, visualise=True) xyz("fully_coordinated/unit_cell", unit_cell) translations = cell_operations.translations_for_fully_coordinated_unit(unit_cell, lattice_vectors) # Ah, the below won't work properly if I pass unit_cell_no_dangling to find_neighbour_cell_oxygens # because the supercell is then constructed from a unit cell that doesn't contain ALL oxygen atoms # # Find and delete atoms that are not coordinated # unit_cell_no_dangling = cell_operations.delete_uncoordinated_atoms(unit_cell) # xyz("fully_coordinated/unit_no_dangling", unit_cell_no_dangling) # Find oxygens from other cells that neighbour Si in the central cell coordinating_atoms = cell_operations.find_neighbour_cell_oxygens(unit_cell, translations) xyz("fully_coordinated/coordinating_oxy", coordinating_atoms)
def get_structural_components(unit_cell, visualise=False): """ :brief For an input unit cell, split into main structural components upper ring, lower ring and connecting chains ghost atoms are duplicates present in the two connecting structures (i.e. ring + chain), and act to indicate the bonding direction NOTE: those returned are erroenous. I've tabulated Si but should have looked at oxy :return structures : List of structures """ upper_ring = ring_mods.get_ring(unit_cell, lambda atom: atom.position[0] > 11.5, erroneous_indices=[4, 11]) lower_ring = ring_mods.get_ring(unit_cell, lambda atom: atom.position[0] <= 7, erroneous_indices=[3, 12]) # Don't need these ghosts: the terminating oxygens are the actual ghost/duplicate atoms chain1, chain2 = ring_mods.get_connecting_chains(unit_cell) structures = {'upper_ring': upper_ring, "lower_ring": lower_ring, 'chain1': chain1, 'chain2': chain2} if visualise: xyz('fully_coordinated/upper_ring', structures['upper_ring']) xyz('fully_coordinated/lower_ring', structures['lower_ring']) xyz('fully_coordinated/chain1', structures['chain1']['main']) xyz('fully_coordinated/chain2', structures['chain2']['main']) xyz('fully_coordinated/ghost1', structures['chain1']['ghost']) xyz('fully_coordinated/ghost2', structures['chain2']['ghost']) return structures
def find_atoms_neighbouring_central_cell(unit_cell: atoms.Atoms, translations: list, upper_bound_length=1.8, visualise=False): """ :brief: For a given unit cell, list all atoms in adjacent cells that are neighbours with atoms in the (central) unit cell :param unit_cell: A list of atoms in the unit cell :param translations: List of translation vectors :param upper_bound_length: Upper bound for NN bond length of AEI framework (in angstrom) :param visualise: Optional, output intermediate structures :return coordinating_atoms: A list of atoms in adjacent unit cells that neighbour atoms in the central unit_cell. """ assert len(unit_cell) > 0, "len(unit_cell) = 0" assert isinstance(unit_cell, list), "unit_cell should be list[atoms.Atom]" assert isinstance(unit_cell[0], atoms.Atom), "unit_cell should be list[atoms.Atom]" n_atoms_prim = len(unit_cell) # Move central cell translation vector to start of list # Makes central-cell atoms entries [0: n_atoms_prim] zero_translation = np.array([0, 0, 0]) for i, translation in enumerate(translations): if np.array_equal(translation, zero_translation): translations.insert(0, translations.pop(i)) break # Super cell = Unit cell plus all coordinating cells super_cell = supercell.build_supercell(unit_cell, translations) assert len(super_cell) == len(translations) * n_atoms_prim positions = [atom.position for atom in super_cell] d_supercell = spatial.distance_matrix(positions, positions) assert d_supercell.shape[0] == len( super_cell), "d_supercell.shape[0] != len(super_cell)" assert d_supercell.shape[1] == d_supercell.shape[ 0], "distance matrix is not square" # For atoms (indexed by ia) in central cell, # list neighbours inside and outside of central cell coordinating_atom_indices = [] for ia in range(0, n_atoms_prim): # This should only check for neighbours outside of the central cell # but doesn't give the correct answer and I can't see why # indices = np.where((d_supercell[ia, n_atoms_prim:] > 0.) & # (d_supercell[ia, n_atoms_prim:] <= upper_bound_length))[0] indices = np.where((d_supercell[ia, :] > 0.) & (d_supercell[ia, :] <= upper_bound_length))[0] coordinating_atom_indices.extend(indices) # Remove dups coordinating_atom_indices = list(set(coordinating_atom_indices)) # Remove indices associated with atoms in central cell coordinating_atom_indices = np.asarray(coordinating_atom_indices) coordinating_atom_indices = coordinating_atom_indices[ coordinating_atom_indices >= n_atoms_prim] # Store coordinating atoms coordinating_atoms = [super_cell[ia] for ia in coordinating_atom_indices] if visualise: #TODO(Alex) Resolve this to make general output_dir = 'aei_outputs' xyz(output_dir + '/' + "aei_supercell", super_cell) xyz(output_dir + '/' + "aei_central_cell", unit_cell) xyz(output_dir + '/' + "aei_neighbours", coordinating_atoms) return coordinating_atoms