Source code for pycofbuilder.framework

# -*- coding: utf-8 -*-
# Created by Felipe Lopes de Oliveira
# Distributed under the terms of the MIT License.

"""
The Framework class implements definitions and methods for a Framework buiding
"""

import os
import copy
import numpy as np

# Import pymatgen
from pymatgen.core import Lattice, Structure
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer

from scipy.spatial.transform import Rotation as R

# Import pycofbuilder exceptions
from pycofbuilder.exceptions import (BondLenghError,
                                     BBConnectivityError)

# Import pycofbuilder building_block
from pycofbuilder.building_block import BuildingBlock

# Import pycofbuilder topology data
from pycofbuilder.data.topology import TOPOLOGY_DICT

# Import pycofbuilder tools
from pycofbuilder.tools import (get_bond_atom,
                                cell_to_cellpar,
                                cellpar_to_cell,
                                rotation_matrix_from_vectors,
                                unit_vector,
                                angle,
                                get_framework_symm_text,
                                get_bonds)

# Import pycofbuilder io_tools
from pycofbuilder.io_tools import (save_chemjson,
                                   save_cif,
                                   save_xyz,
                                   save_turbomole,
                                   save_vasp,
                                   save_xsf,
                                   save_pdb,
                                   save_pqr,
                                   save_qe,
                                   save_gjf)

from pycofbuilder.logger import create_logger


[docs] class Framework(): """ A class used to represent a Covalent Organic Framework as a reticular entity. ... Attributes ---------- name : str Name of the material out_dir : str Path to save the results. If not defined, a `out` folder will be created in the current directory. verbosity : str Control the printing options. Can be 'none', 'normal', or 'debug'. Default: 'normal' save_bb : bool Control the saving of the building blocks. Default: True lib_path : str Path for saving the building block files. If not defined, a `building_blocks` folder will be created in the current directory. topology : str = None dimention : str = None lattice : str = None lattice_sgs : str = None space_group : str = None space_group_n : str = None stacking : str = None mass : str = None composition : str = None charge : int = 0 multiplicity : int = 1 chirality : bool = False atom_types : list = [] atom_pos : list = [] lattice : list = [[], [], []] symm_tol : float = 0.2 angle_tol : float = 0.2 dist_threshold : float = 0.8 available_2D_topologies : list List of available 2D topologies available_3D_topologies : list List of available 3D topologies available_topologies : list List of all available topologies available_stacking : list List of available stakings for all 2D topologies lib_bb : str String with the name of the folder containing the building block files Default: bb_lib """ def __init__(self, name: str = None, **kwargs): self.name: str = name self.out_path: str = kwargs.get('out_dir', os.path.join(os.getcwd(), 'out')) self.save_bb: bool = kwargs.get('save_bb', True) self.bb_out_path: str = kwargs.get('bb_out_path', os.path.join(self.out_path, 'building_blocks')) self.logger = create_logger(level=kwargs.get('log_level', 'info'), format=kwargs.get('log_format', 'simple'), save_to_file=kwargs.get('save_to_file', False), log_filename=kwargs.get('log_filename', 'pycofbuilder.log')) self.symm_tol = kwargs.get('symm_tol', 0.1) self.angle_tol = kwargs.get('angle_tol', 0.5) self.dist_threshold = kwargs.get('dist_threshold', 0.8) self.bond_threshold = kwargs.get('bond_threshold', 1.3) self.bb1_name = None self.bb2_name = None self.topology = None self.stacking = None self.smiles = None self.atom_types = [] self.atom_pos = [] self.atom_labels = [] self.cellMatrix = np.eye(3) self.cellParameters = np.array([1, 1, 1, 90, 90, 90]).astype(float) self.bonds = [] self.lattice_sgs = None self.space_group = None self.space_group_n = None self.dimention = None self.n_atoms = self.get_n_atoms() self.mass = None self.composition = None self.charge = 0 self.multiplicity = 1 self.chirality = False self.available_2D_top = ['HCB', 'HCB_A', 'SQL', 'SQL_A', 'KGD', 'HXL', 'HXL_A', 'FXT', 'FXT_A'] # To add: ['dia', 'bor', 'srs', 'pts', 'ctn', 'rra', 'fcc', 'lon', 'stp', 'acs', 'tbo', 'bcu', 'fjh', 'ceq'] self.available_3D_top = ['DIA', 'DIA_A', 'BOR'] # Temporary self.available_topologies = self.available_2D_top + self.available_3D_top # Define available stackings for all 2D topologies self.available_stacking = { 'HCB': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'HCB_A': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'SQL': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'SQL_A': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'KGD': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'HXL_A': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'FXT': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'FXT_A': ['A', 'AA', 'AB1', 'AB2', 'AAl', 'AAt', 'ABC1', 'ABC2'], 'DIA': [str(i + 1) for i in range(15)], 'DIA_A': [str(i + 1) for i in range(15)], 'BOR': [str(i + 1) for i in range(15)] } if self.name is not None: self.from_name(self.name) def __str__(self) -> str: return self.as_string() def __repr__(self) -> str: return f'Framework({self.bb1_name}, {self.bb2_name}, {self.topology}, {self.stacking})'
[docs] def as_string(self) -> str: """ Returns a string with the Framework information. """ fram_str = f'Name: {self.name}\n' # Get the formula of the framework if self.composition is not None: fram_str += f'Full Formula ({self.composition})\n' fram_str += f'Reduced Formula: ({self.composition})\n' else: fram_str += 'Full Formula ()\n' fram_str += 'Reduced Formula: \n' fram_str += 'abc : {:11.6f} {:11.6f} {:11.6f}\n'.format(*self.cellParameters[:3]) fram_str += 'angles: {:11.6f} {:11.6f} {:11.6f}\n'.format(*self.cellParameters[3:]) fram_str += 'A: {:11.6f} {:11.6f} {:11.6f}\n'.format(*self.cellMatrix[0]) fram_str += 'B: {:11.6f} {:11.6f} {:11.6f}\n'.format(*self.cellMatrix[1]) fram_str += 'C: {:11.6f} {:11.6f} {:11.6f}\n'.format(*self.cellMatrix[2]) fram_str += f'Cartesian Sites ({self.n_atoms})\n' fram_str += ' # Type a b c label\n' fram_str += '--- ---- -------- -------- -------- -------\n' for i in range(len(self.atom_types)): fram_str += '{:3d} {:4s} {:8.5f} {:8.5f} {:8.5f} {:>7}\n'.format(i, self.atom_types[i], self.atom_pos[i][0], self.atom_pos[i][1], self.atom_pos[i][2], self.atom_labels[i]) return fram_str
[docs] def get_n_atoms(self) -> int: ''' Returns the number of atoms in the unitary cell''' return len(self.atom_types)
[docs] def get_available_topologies(self, dimensionality: str = 'all', print_result: bool = True): """ Get the available topologies implemented in the class. Parameters ---------- dimensionality : str, optional The dimensionality of the topologies to be printed. Can be 'all', '2D' or '3D'. Default: 'all' print_result: bool, optional If True, the available topologies are printed. Returns ------- dimensionality_list: list A list with the available topologies. """ dimensionality_error = f'Dimensionality must be one of the following: all, 2D, 3D, not {dimensionality}' assert dimensionality in ['all', '2D', '3D'], dimensionality_error dimensionality_list = [] if dimensionality == 'all' or dimensionality == '2D': if print_result: print('Available 2D Topologies:') for i in self.available_2D_top: if print_result: print(i.upper()) dimensionality_list.append(i) if dimensionality == 'all' or dimensionality == '3D': if print_result: print('Available 3D Topologies:') for i in self.available_3D_top: if print_result: print(i.upper()) dimensionality_list.append(i) return dimensionality_list
[docs] def check_name_concistency(self, FrameworkName) -> tuple[str, str, str, str]: """ Checks if the name is in the correct format and returns a tuple with the building blocks names, the net and the stacking. In case the name is not in the correct format, an error is raised. Parameters ---------- FrameworkName : str, required The name of the COF to be created Returns ------- tuple[str, str, str, str] A tuple with the building blocks names, the net and the stacking. """ string_error = 'FrameworkName must be in the format: BB1_BB2_Net_Stacking' assert isinstance(FrameworkName, str), string_error name_error = 'FrameworkName must be in the format: BB1_BB2_Net_Stacking' assert len(FrameworkName.split('-')) == 4, name_error bb1_name, bb2_name, Net, Stacking = FrameworkName.split('-') net_error = f'{Net} not in the available list: {self.available_topologies}' assert Net in self.available_topologies, net_error stacking_error = f'{Stacking} not in the available list: {self.available_stacking[Net]}' assert Stacking in self.available_stacking[Net], stacking_error return bb1_name, bb2_name, Net, Stacking
[docs] def from_name(self, FrameworkName, **kwargs) -> None: """Creates a COF from a given FrameworkName. Parameters ---------- FrameworkName : str, required The name of the COF to be created Returns ------- COF : Framework The COF object """ bb1_name, bb2_name, Net, Stacking = self.check_name_concistency(FrameworkName) bb1 = BuildingBlock(name=bb1_name, bb_out_path=self.bb_out_path, save_bb=self.save_bb) bb2 = BuildingBlock(name=bb2_name, bb_out_path=self.bb_out_path, save_bb=self.save_bb) self.from_building_blocks(bb1, bb2, Net, Stacking, **kwargs)
[docs] def from_building_blocks(self, bb1: BuildingBlock, bb2: BuildingBlock, net: str, stacking: str, **kwargs): """Creates a COF from the building blocks. Parameters ---------- BB1 : BuildingBlock, required The first building block BB2 : BuildingBlock, required The second building block Net : str, required The network of the COF Stacking : str, required The stacking of the COF Returns ------- COF : Framework The COF object """ self.name = f'{bb1.name}-{bb2.name}-{net}-{stacking}' self.bb1_name = bb1.name self.bb2_name = bb2.name self.topology = net self.stacking = stacking # Check if the BB1 has the smiles attribute if hasattr(bb1, 'smiles') and hasattr(bb2, 'smiles'): self.smiles = f'{bb1.smiles}.{bb2.smiles}' else: print('WARNING: The smiles attribute is not available for the building blocks') net_build_dict = { 'HCB': self.create_hcb_structure, 'HCB_A': self.create_hcb_a_structure, 'SQL': self.create_sql_structure, 'SQL_A': self.create_sql_a_structure, 'KGD': self.create_kgd_structure, # 'HXL': self.create_hxl_structure, 'HXL_A': self.create_hxl_a_structure, 'FXT': self.create_fxt_structure, 'FXT_A': self.create_fxt_a_structure, 'DIA': self.create_dia_structure, 'DIA_A': self.create_dia_a_structure, 'BOR': self.create_bor_structure } result = net_build_dict[net](bb1, bb2, stacking, **kwargs) structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) self.bonds = get_bonds(structure, self.bond_threshold) return result
[docs] def save(self, fmt: str = 'cif', supercell: list = [1, 1, 1], save_dir=None, primitive=False, save_bonds=True) -> None: ''' Save the structure in a specif file format. Parameters ---------- fmt : str, optional The file format to be saved Can be `json`, `cif`, `xyz`, `turbomole`, `vasp`, `xsf`, `pdb`, `pqr`, `qe`. Default: 'cif' supercell : list, optional The supercell to be used to save the structure. Default: [1,1,1] save_dir : str, optional The path to save the structure. By default, the structure is saved in a `out` folder created in the current directory. primitive : bool, optional If True, the primitive cell is saved. Otherwise, the conventional cell is saved. Default: False ''' save_dict = { 'cjson': save_chemjson, 'cif': save_cif, 'xyz': save_xyz, 'turbomole': save_turbomole, 'vasp': save_vasp, 'xsf': save_xsf, 'pdb': save_pdb, 'pqr': save_pqr, 'qe': save_qe, 'gjf': save_gjf } file_format_error = f'Format must be one of the following: {save_dict.keys()}' assert fmt in save_dict.keys(), file_format_error if primitive: structure = self.prim_structure else: structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) final_structure = structure.make_supercell(supercell, in_place=False) if save_bonds: bonds = get_bonds(final_structure, self.bond_threshold) else: bonds = [] structure_dict = final_structure.as_dict() cell = structure_dict['lattice']['matrix'] atom_types = [site['species'][0]['element'] for site in structure_dict['sites']] atom_labels = [site['properties']['source'] for site in structure_dict['sites']] atom_pos = [site['xyz'] for site in structure_dict['sites']] if save_dir is None: save_path = self.out_path else: save_path = save_dir save_dict[fmt](path=save_path, file_name=self.name, cell=cell, atom_types=atom_types, atom_labels=atom_labels, atom_pos=atom_pos, bonds=bonds)
# --------------- Net creation methods -------------------------- #
[docs] def create_hcb_structure(self, BB_T3_A, BB_T3_B, stacking: str = 'AA', slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with HCB network. The HCB net is composed of two tripodal building blocks. Parameters ---------- BB_T3_1 : BuildingBlock, required The BuildingBlock object of the tripodal Buiding Block A BB_T3_2 : BuildingBlock, required The BuildingBlock object of the tripodal Buiding Block B stacking : str, optional The stacking pattern of the COF layers (default is 'AA') slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_T3_A.connectivity != 3: self.logger.error(connectivity_error.format('A', 3, BB_T3_A.connectivity)) raise BBConnectivityError(3, BB_T3_A.connectivity) if BB_T3_B.connectivity != 3: self.logger.error(connectivity_error.format('B', 3, BB_T3_B.connectivity)) raise BBConnectivityError(3, BB_T3_B.connectivity) self.name = f'{BB_T3_A.name}-{BB_T3_B.name}-HCB-{stacking}' self.topology = 'HCB' self.staking = stacking self.dimension = 2 self.charge = BB_T3_A.charge + BB_T3_B.charge self.chirality = BB_T3_A.chirality or BB_T3_B.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_T3_A.conector, BB_T3_B.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_T3_A.conector, BB_T3_B.conector)) # Replace "X" the building block BB_T3_A.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_T3_A.remove_X() BB_T3_B.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = BB_T3_A.size[0] + BB_T3_B.size[0] # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_T3_A.atom_pos)[2])) + abs(min(np.transpose(BB_T3_A.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_T3_B.atom_pos)[2])) + abs(min(np.transpose(BB_T3_B.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the A1 building blocks to the structure vertice_data = topology_info['vertices'][0] self.atom_types += BB_T3_A.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_T3_A.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_T3_A.atom_labels] # Add the A2 building block to the structure vertice_data = topology_info['vertices'][1] self.atom_types += BB_T3_B.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_T3_B.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_T3_B.atom_labels] # Creates a pymatgen structure StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.cellParameters = cell_to_cellpar(self.cellMatrix) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [2/3, 1/3, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (2/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (4/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.cellParameters = cell_to_cellpar(self.cellMatrix) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = StartingFramework.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_hcb_a_structure(self, BB_T3: str, BB_L2: str, stacking: str = 'AA', slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with HCB-A network. The HCB-A net is composed of one tripodal and one linear building blocks. Parameters ---------- BB_T3 : BuildingBlock, required The BuildingBlock object of the tripodal Buiding Block BB_L2 : BuildingBlock, required The BuildingBlock object of the linear Buiding Block stacking : str, optional The stacking pattern of the COF layers (default is 'AA') c_parameter_base : float, optional The base value for interlayer distance in angstroms (default is 3.6) print_result : bool, optional Parameter for the control for printing the result (default is True) slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name 2. lattice type 3. hall symbol of the cristaline structure 4. space group 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_T3.connectivity != 3: self.logger.error(connectivity_error.format('A', 3, BB_T3.connectivity)) raise BBConnectivityError(3, BB_T3.connectivity) if BB_L2.connectivity != 2: self.logger.error(connectivity_error.format('B', 3, BB_L2.connectivity)) raise BBConnectivityError(2, BB_L2.connectivity) self.name = f'{BB_T3.name}-{BB_L2.name}-HCB_A-{stacking}' self.topology = 'HCB_A' self.staking = stacking self.dimension = 2 self.charge = BB_L2.charge + BB_T3.charge self.chirality = BB_L2.chirality or BB_T3.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_T3.conector, BB_L2.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_T3.conector, BB_L2.conector)) # Replace "X" the building block BB_L2.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_T3.remove_X() BB_L2.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = BB_T3.size[0] + BB_L2.size[0] # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_T3.atom_pos)[2])) + abs(min(np.transpose(BB_T3.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_L2.atom_pos)[2])) + abs(min(np.transpose(BB_L2.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the building blocks to the structure for vertice_data in topology_info['vertices']: self.atom_types += BB_T3.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_T3.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_T3.atom_labels] # Add the building blocks to the structure for edge_data in topology_info['edges']: self.atom_types += BB_L2.atom_types R_Matrix = R.from_euler('z', edge_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_L2.atom_pos, R_Matrix) + np.array(edge_data['position'])*a self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_L2.atom_labels] StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [2/3, 1/3, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (2/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (4/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = stacked_structure.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_sql_structure(self, BB_S4_A: str, BB_S4_B: str, stacking: str = 'AA', slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with SQL network. The SQL net is composed of two tetrapodal building blocks. Parameters ---------- BB_S4_A : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block A BB_S4_B : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block B stacking : str, optional The stacking pattern of the COF layers (default is 'AA') print_result : bool, optional Parameter for the control for printing the result (default is True) slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_S4_A.connectivity != 4: self.logger.error(connectivity_error.format('A', 4, BB_S4_A.connectivity)) raise BBConnectivityError(4, BB_S4_A.connectivity) if BB_S4_B.connectivity != 4: self.logger.error(connectivity_error.format('B', 4, BB_S4_B.connectivity)) raise BBConnectivityError(4, BB_S4_B.connectivity) self.name = f'{BB_S4_A.name}-{BB_S4_B.name}-SQL-{stacking}' self.topology = 'SQL' self.staking = stacking self.dimension = 2 self.charge = BB_S4_A.charge + BB_S4_B.charge self.chirality = BB_S4_A.chirality or BB_S4_B.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_S4_A.conector, BB_S4_B.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_S4_A.conector, BB_S4_B.conector)) # Replace "X" the building block BB_S4_A.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_S4_A.remove_X() BB_S4_B.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = BB_S4_A.size[0] + BB_S4_B.size[0] # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_S4_A.atom_pos)[2])) + abs(min(np.transpose(BB_S4_B.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_S4_A.atom_pos)[2])) + abs(min(np.transpose(BB_S4_B.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the first building block to the structure vertice_data = topology_info['vertices'][0] self.atom_types += BB_S4_A.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_S4_A.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_S4_A.atom_labels] # Add the second building block to the structure vertice_data = topology_info['vertices'][1] self.atom_types += BB_S4_B.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_S4_B.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_S4_B.atom_labels] StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/4, 1/4, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/4, 1/4, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (1/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 2/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (1/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 2/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) # Create AAl stacking. Tetragonal cell with two sheets # per cell shifited by the shift_vector in angstroms. if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. # Tilted tetragonal cell with two sheets per cell tilted by tilt_angle. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = stacked_structure.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_sql_a_structure(self, BB_S4: str, BB_L2: str, stacking: str = 'AA', c_parameter_base: float = 3.6, slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with SQL-A network. The SQL-A net is composed of one tetrapodal and one linear building blocks. Parameters ---------- BB_S4 : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block BB_L2 : BuildingBlock, required The BuildingBlock object of the bipodal Buiding Block stacking : str, optional The stacking pattern of the COF layers (default is 'AA') slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_S4.connectivity != 4: self.logger.error(connectivity_error.format('A', 4, BB_S4.connectivity)) raise BBConnectivityError(4, BB_S4.connectivity) if BB_L2.connectivity != 2: self.logger.error(connectivity_error.format('B', 3, BB_L2.connectivity)) raise BBConnectivityError(2, BB_L2.connectivity) self.name = f'{BB_S4.name}-{BB_L2.name}-SQL_A-{stacking}' self.topology = 'SQL_A' self.staking = stacking self.dimension = 2 self.charge = BB_S4.charge + BB_L2.charge self.chirality = BB_S4.chirality or BB_L2.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_S4.conector, BB_L2.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_S4.conector, BB_L2.conector)) # Replace "X" the building block BB_L2.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_S4.remove_X() BB_L2.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = BB_S4.size[0] + BB_L2.size[0] # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_S4.atom_pos)[2])) + abs(min(np.transpose(BB_S4.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_L2.atom_pos)[2])) + abs(min(np.transpose(BB_L2.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the building blocks to the structure for vertice_data in topology_info['vertices']: self.atom_types += BB_S4.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_S4.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_S4.atom_labels] # Add the building blocks to the structure for edge_data in topology_info['edges']: self.atom_types += BB_L2.atom_types edge_pos = np.array(edge_data['position'])*a R_Matrix = R.from_euler('z', edge_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_L2.atom_pos, R_Matrix) + edge_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_L2.atom_labels] StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/4, 1/4, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/4, 1/4, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (1/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 2/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (1/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 2/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) # Create AAl stacking. Tetragonal cell with two sheets # per cell shifited by the shift_vector in angstroms. if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. # Tilted tetragonal cell with two sheets per cell tilted by tilt_angle. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = stacked_structure.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_kgd_structure(self, BB_H6: str, BB_T3: str, stacking: str = 'AA', print_result: bool = True, slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with KGD network. The KGD net is composed of one hexapodal and one tripodal building blocks. Parameters ---------- BB_H6 : BuildingBlock, required The BuildingBlock object of the hexapodal Buiding Block BB_T3 : BuildingBlock, required The BuildingBlock object of the tripodal Buiding Block stacking : str, optional The stacking pattern of the COF layers (default is 'AA') c_parameter_base : float, optional The base value for interlayer distance in angstroms (default is 3.6) print_result : bool, optional Parameter for the control for printing the result (default is True) slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_H6.connectivity != 6: self.logger.error(connectivity_error.format('A', 6, BB_H6.connectivity)) raise BBConnectivityError(6, BB_H6.connectivity) if BB_T3.connectivity != 3: self.logger.error(connectivity_error.format('B', 3, BB_T3.connectivity)) raise BBConnectivityError(3, BB_T3.connectivity) self.name = f'{BB_H6.name}-{BB_T3.name}-KGD-{stacking}' self.topology = 'KGD' self.staking = stacking self.dimension = 2 self.charge = BB_H6.charge + BB_T3.charge self.chirality = BB_H6.chirality or BB_T3.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_H6.conector, BB_T3.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_H6.conector, BB_T3.conector)) # Replace "X" the building block BB_H6.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_H6.remove_X() BB_T3.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = BB_H6.size[0] + BB_T3.size[0] # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_H6.atom_pos)[2])) + abs(min(np.transpose(BB_H6.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_T3.atom_pos)[2])) + abs(min(np.transpose(BB_T3.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the building blocks to the structure for vertice_data in topology_info['vertices']: self.atom_types += BB_H6.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_H6.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_H6.atom_labels] # Add the building blocks to the structure for edge_data in topology_info['edges']: self.atom_types += BB_T3.atom_types edge_pos = np.array(edge_data['position'])*a R_Matrix = R.from_euler('z', edge_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_T3.atom_pos, R_Matrix) + edge_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_T3.atom_labels] StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [2/3, 1/3, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (2/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (4/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = stacked_structure.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_hxl_a_structure(self, BB_H6: str, BB_L2: str, stacking: str = 'AA', print_result: bool = True, slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with HXL-A network. The HXL-A net is composed of one hexapodal and one linear building blocks. Parameters ---------- BB_H6 : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block A BB_L2 : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block B stacking : str, optional The stacking pattern of the COF layers (default is 'AA') print_result : bool, optional Parameter for the control for printing the result (default is True) slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_H6.connectivity != 6: self.logger.error(connectivity_error.format('A', 6, BB_H6.connectivity)) raise BBConnectivityError(6, BB_H6.connectivity) if BB_L2.connectivity != 2: self.logger.error(connectivity_error.format('B', 3, BB_L2.connectivity)) raise BBConnectivityError(2, BB_L2.connectivity) self.name = f'{BB_H6.name}-{BB_L2.name}-HXL_A-{stacking}' self.topology = 'HXL_A' self.staking = stacking self.dimension = 2 self.charge = BB_H6.charge + BB_L2.charge self.chirality = BB_H6.chirality or BB_L2.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_H6.conector, BB_L2.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_H6.conector, BB_L2.conector)) # Replace "X" the building block BB_L2.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_H6.remove_X() BB_L2.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = BB_H6.size[0] + BB_L2.size[0] # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_H6.atom_pos)[2])) + abs(min(np.transpose(BB_H6.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_L2.atom_pos)[2])) + abs(min(np.transpose(BB_L2.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the building blocks to the structure for vertice_data in topology_info['vertices']: self.atom_types += BB_H6.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_H6.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_H6.atom_labels] # Add the building blocks to the structure for edge_data in topology_info['edges']: self.atom_types += BB_L2.atom_types edge_pos = np.array(edge_data['position'])*a R_Matrix = R.from_euler('z', edge_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_L2.atom_pos, R_Matrix) + edge_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_L2.atom_labels] StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [2/3, 1/3, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (2/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (4/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = stacked_structure.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_fxt_structure(self, BB_S4_A: str, BB_S4_B: str, stacking: str = 'AA', print_result: bool = True, slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with FXT network. The FXT net is composed of two tetrapodal building blocks. Parameters ---------- BB_S4_A : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block A BB_S4_B : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block B stacking : str, optional The stacking pattern of the COF layers (default is 'AA') print_result : bool, optional Parameter for the control for printing the result (default is True) slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_S4_A.connectivity != 4: self.logger.error(connectivity_error.format('A', 4, BB_S4_A.connectivity)) raise BBConnectivityError(4, BB_S4_A.connectivity) if BB_S4_B.connectivity != 4: self.logger.error(connectivity_error.format('B', 4, BB_S4_B.connectivity)) raise BBConnectivityError(4, BB_S4_B.connectivity) self.name = f'{BB_S4_A.name}-{BB_S4_B.name}-FXT-{stacking}' self.topology = 'FXT' self.staking = stacking self.dimension = 2 self.charge = BB_S4_A.charge + BB_S4_B.charge self.chirality = BB_S4_A.chirality or BB_S4_B.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_S4_A.conector, BB_S4_B.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_S4_A.conector, BB_S4_B.conector)) # Replace "X" the building block BB_S4_A.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_S4_A.remove_X() BB_S4_B.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = 2 * (BB_S4_A.size[0] + BB_S4_B.size[0]) # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_S4_A.atom_pos)[2])) + abs(min(np.transpose(BB_S4_B.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_S4_A.atom_pos)[2])) + abs(min(np.transpose(BB_S4_B.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the first building block to the structure vertice_data = topology_info['vertices'][0] self.atom_types += BB_S4_A.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_S4_A.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_S4_A.atom_labels] # Add the second building block to the structure for vertice_data in topology_info['vertices'][1:]: self.atom_types += BB_S4_B.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_S4_B.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_S4_B.atom_labels] StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/4, 1/4, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/4, 1/4, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (1/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 2/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (1/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 2/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) # Create AAl stacking. Tetragonal cell with two sheets # per cell shifited by the shift_vector in angstroms. if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. # Tilted tetragonal cell with two sheets per cell tilted by tilt_angle. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = stacked_structure.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_fxt_a_structure(self, BB_S4: str, BB_L2: str, stacking: str = 'AA', c_parameter_base: float = 3.6, print_result: bool = True, slab: float = 10.0, shift_vector: list = [1.0, 1.0, 0], tilt_angle: float = 5.0): """Creates a COF with FXT-A network. The FXT-A net is composed of one tetrapodal and one linear building blocks. Parameters ---------- BB_S4 : BuildingBlock, required The BuildingBlock object of the tetrapodal Buiding Block BB_L2 : BuildingBlock, required The BuildingBlock object of the bipodal Buiding Block stacking : str, optional The stacking pattern of the COF layers (default is 'AA') print_result : bool, optional Parameter for the control for printing the result (default is True) slab : float, optional Default parameter for the interlayer slab (default is 10.0) shift_vector: list, optional Shift vector for the AAl and AAt stakings (defatult is [1.0,1.0,0]) tilt_angle: float, optional Tilt angle for the AAt staking in degrees (default is 5.0) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_S4.connectivity != 4: self.logger.error(connectivity_error.format('A', 4, BB_S4.connectivity)) raise BBConnectivityError(4, BB_S4.connectivity) if BB_L2.connectivity != 2: self.logger.error(connectivity_error.format('B', 3, BB_L2.connectivity)) raise BBConnectivityError(2, BB_L2.connectivity) self.name = f'{BB_S4.name}-{BB_L2.name}-FXT_A-{stacking}' self.topology = 'FXT_A' self.staking = stacking self.dimension = 2 self.charge = BB_S4.charge + BB_L2.charge self.chirality = BB_S4.chirality or BB_L2.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_S4.conector, BB_L2.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_S4.conector, BB_L2.conector)) # Replace "X" the building block BB_L2.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_S4.remove_X() BB_L2.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = 2 * (BB_S4.size[0] + BB_L2.size[0]) # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_S4.atom_pos)[2])) + abs(min(np.transpose(BB_S4.atom_pos)[2])) delta_b = abs(max(np.transpose(BB_L2.atom_pos)[2])) + abs(min(np.transpose(BB_L2.atom_pos)[2])) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info['a'] * size b = topology_info['b'] * size c = topology_info['c'] + delta_max alpha = topology_info['alpha'] beta = topology_info['beta'] gamma = topology_info['gamma'] if self.stacking == 'A': c = slab # Create the lattice self.cellMatrix = Lattice.from_parameters(a, b, c, alpha, beta, gamma) self.cellParameters = np.array([a, b, c, alpha, beta, gamma]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Add the building blocks to the structure for vertice_data in topology_info['vertices']: self.atom_types += BB_S4.atom_types vertice_pos = np.array(vertice_data['position'])*a R_Matrix = R.from_euler('z', vertice_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_S4.atom_pos, R_Matrix) + vertice_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_S4.atom_labels] # Add the building blocks to the structure for edge_data in topology_info['edges']: self.atom_types += BB_L2.atom_types edge_pos = np.array(edge_data['position'])*a R_Matrix = R.from_euler('z', edge_data['angle'], degrees=True).as_matrix() rotated_pos = np.dot(BB_L2.atom_pos, R_Matrix) + edge_pos self.atom_pos += rotated_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_L2.atom_labels] StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() # Translates the structure to the center of the cell StartingFramework.translate_sites( range(len(StartingFramework.as_dict()['sites'])), [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] if stacking == 'A' or stacking == 'AA': stacked_structure = StartingFramework if stacking == 'AB1': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [2/3, 1/3, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'AB2': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [1/2, 0, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [1/2, 0, 0.5], frac_coords=True, to_unit_cell=True ) if stacking == 'ABC1': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (2/3, 1/3, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (4/3, 2/3, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'ABC2': self.cellMatrix *= (1, 1, 3) self.cellParameters *= (1, 1, 3, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet _, B_list, C_list = np.split(np.arange(len(self.atom_types)), 3) # Translate the second sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( B_list, (1/3, 0, 1/3), frac_coords=True, to_unit_cell=True ) # Translate the third sheet by the vector (2/3, 1/3, 0) to generate the B positions stacked_structure.translate_sites( C_list, (2/3, 0, 2/3), frac_coords=True, to_unit_cell=True ) if stacking == 'AAl': self.cellMatrix *= (1, 1, 2) self.cellParameters *= (1, 1, 2, 1, 1, 1) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) sv = np.array(shift_vector) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos + sv)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) # Create AA tilted stacking. if stacking == 'AAt': cell = StartingFramework.as_dict()['lattice'] # Shift the cell by the tilt angle a_cell = cell['a'] b_cell = cell['b'] c_cell = cell['c'] * 2 alpha = cell['alpha'] - tilt_angle beta = cell['beta'] - tilt_angle gamma = cell['gamma'] self.cellMatrix = cellpar_to_cell([a_cell, b_cell, c_cell, alpha, beta, gamma]) self.cellParameters = np.array([a_cell, b_cell, c_cell, alpha, beta, gamma]).astype(float) self.atom_types = np.concatenate((self.atom_types, self.atom_types)) self.atom_pos = np.concatenate((self.atom_pos, self.atom_pos)) self.atom_labels = np.concatenate((self.atom_labels, self.atom_labels)) stacked_structure = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ) # Get the index of the atoms in the second sheet B_list = np.split(np.arange(len(self.atom_types)), 2)[1] # Translate the second sheet by the vector [2/3, 1/3, 0.5] to generate the B positions stacked_structure.translate_sites( B_list, [0, 0, 0.5], frac_coords=True, to_unit_cell=True ) dict_structure = stacked_structure.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = stacked_structure.formula dist_matrix = stacked_structure.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(stacked_structure, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_refined_structure(keep_site_properties=True) self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_dia_structure(self, BB_D41: str, BB_D42: str, interp_dg: str = '1', d_param_base: float = 7.2, print_result: bool = True, **kwargs): """Creates a COF with DIA network. The DIA net is composed of two tetrapodal tetrahedical building blocks. Parameters ---------- BB_D41 : BuildingBlock, required The BuildingBlock object of the tetrapodal tetrahedical Buiding Block BB_D42 : BuildingBlock, required The BuildingBlock object of the tetrapodal tetrahedical Buiding Block interp_dg : str, optional The degree of interpenetration of the framework (default is '1') d_param_base : float, optional The base value for interlayer distance in angstroms (default is 7.2) print_result : bool, optional Parameter for the control for printing the result (default is True) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_D41.connectivity != 4: self.logger.error(connectivity_error.format('A', 4, BB_D41.connectivity)) raise BBConnectivityError(4, BB_D41.connectivity) if BB_D42.connectivity != 4: self.logger.error(connectivity_error.format('B', 4, BB_D42.connectivity)) raise BBConnectivityError(4, BB_D42.connectivity) self.name = f'{BB_D41.name}-{BB_D42.name}-DIA-{interp_dg}' self.topology = 'DIA' self.staking = interp_dg self.dimension = 3 self.charge = BB_D41.charge + BB_D42.charge self.chirality = BB_D41.chirality or BB_D42.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_D41.conector, BB_D42.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_D41.conector, BB_D42.conector)) # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = np.average(BB_D41.size) + np.average(BB_D42.size) # Calculate the primitive cell vector assuming tetrahedical building blocks a_prim = np.sqrt(2)*size*np.sqrt((1 - np.cos(1.9106316646041868))) a_conv = np.sqrt(2)*a_prim # Create the primitive lattice self.cellMatrix = Lattice(a_conv/2*np.array(topology_info['lattice'])) self.cellParameters = np.array([a_prim, a_prim, a_prim, 60, 60, 60]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Align and rotate the building block 1 to their respective positions BB_D41.align_to(topology_info['vertices'][0]['align_v']) # Determine the angle that alings the X[1] to one of the vertices of the tetrahedron vertice_pos = unit_vector(np.array([1, 0, 1])) Q_vertice_pos = BB_D41.get_X_points()[1][1] rotated_list = [ R.from_rotvec( angle * unit_vector(topology_info['vertices'][0]['align_v']), degrees=False ).apply(Q_vertice_pos) for angle in np.linspace(0, 2*np.pi, 360) ] # Calculate the angle between the vertice_pos and the elements of rotated_list angle_list = [angle(vertice_pos, i) for i in rotated_list] rot_angle = np.linspace(0, 360, 360)[np.argmax(angle_list)] BB_D41.rotate_around(rotation_axis=np.array(topology_info['vertices'][0]['align_v']), angle=rot_angle, degree=True) BB_D41.shift(np.array(topology_info['vertices'][0]['position'])*a_conv) BB_D41.remove_X() # Add the building block 1 to the structure self.atom_types += BB_D41.atom_types self.atom_pos += BB_D41.atom_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_D41.atom_labels] # Align and rotate the building block 1 to their respective positions BB_D42.align_to(topology_info['vertices'][0]['align_v']) # Determine the angle that alings the X[1] to one of the vertices of the tetrahedron vertice_pos = unit_vector(np.array([1, 0, 1])) Q_vertice_pos = BB_D42.get_X_points()[1][1] rotated_list = [ R.from_rotvec( angle * unit_vector(topology_info['vertices'][0]['align_v']), degrees=False ).apply(Q_vertice_pos) for angle in np.linspace(0, 2*np.pi, 360) ] # Calculate the angle between the vertice_pos and the elements of rotated_list angle_list = [angle(vertice_pos, i) for i in rotated_list] rot_angle = np.linspace(0, 360, 360)[np.argmax(angle_list)] BB_D42.rotate_around(rotation_axis=np.array(topology_info['vertices'][0]['align_v']), angle=rot_angle, degree=True) BB_D42.atom_pos = -BB_D42.atom_pos BB_D42.shift(np.array(topology_info['vertices'][1]['position'])*a_conv) BB_D42.replace_X(bond_atom) BB_D42.remove_X() # Add the building block 2 to the structure self.atom_types += BB_D42.atom_types self.atom_pos += BB_D42.atom_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB_D42.atom_labels] atom_types, atom_labels, atom_pos = [], [], [] for n_int in range(int(self.stacking)): int_direction = np.array([0, 1, 0]) * d_param_base * n_int atom_types += self.atom_types atom_pos += (np.array(self.atom_pos) + int_direction).tolist() atom_labels += self.atom_labels self.atom_types = atom_types self.atom_pos = atom_pos self.atom_labels = atom_labels StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() StartingFramework.to(os.path.join(os.getcwd(), 'TESTE_DIA.cif'), fmt='cif') dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = StartingFramework.formula dist_matrix = StartingFramework.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(StartingFramework, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_primitive_standard_structure(keep_site_properties=True) dict_structure = symm.get_refined_structure(keep_site_properties=True).as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.cellParameters = np.array([dict_structure['lattice']['a'], dict_structure['lattice']['b'], dict_structure['lattice']['c'], dict_structure['lattice']['alpha'], dict_structure['lattice']['beta'], dict_structure['lattice']['gamma']]).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = self.prim_structure.formula self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_dia_a_structure(self, BB_D4: str, BB_L2: str, interp_dg: str = '1', d_param_base: float = 7.2, print_result: bool = True, **kwargs): """Creates a COF with DIA-A network. The DIA net is composed of two tetrapodal tetrahedical building blocks. Parameters ---------- BB_D4 : BuildingBlock, required The BuildingBlock object of the tetrapodal tetrahedical Buiding Block BB_L2 : BuildingBlock, required The BuildingBlock object of the dipodal linear Buiding Block interp_dg : str, optional The degree of interpenetration of the framework (default is '1') d_param_base : float, optional The base value for interlayer distance in angstroms (default is 7.2) print_result : bool, optional Parameter for the control for printing the result (default is True) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_D4.connectivity != 4: self.logger.error(connectivity_error.format('A', 4, BB_D4.connectivity)) raise BBConnectivityError(4, BB_D4.connectivity) if BB_L2.connectivity != 2: self.logger.error(connectivity_error.format('B', 2, BB_L2.connectivity)) raise BBConnectivityError(2, BB_L2.connectivity) self.name = f'{BB_D4.name}-{BB_L2.name}-DIA_A-{interp_dg}' self.topology = 'DIA_A' self.staking = interp_dg self.dimension = 3 self.charge = BB_D4.charge + BB_L2.charge self.chirality = BB_D4.chirality or BB_L2.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_D4.conector, BB_L2.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_D4.conector, BB_L2.conector)) # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = 2 * (np.average(BB_D4.size) + np.average(BB_L2.size)) # Calculate the primitive cell vector assuming tetrahedical building blocks a_prim = np.sqrt(2)*size*np.sqrt((1 - np.cos(1.9106316646041868))) a_conv = np.sqrt(2)*a_prim # Create the primitive lattice self.cellMatrix = Lattice(a_conv/2*np.array(topology_info['lattice'])) self.cellParameters = np.array([a_prim, a_prim, a_prim, 60, 60, 60]).astype(float) # Create the structure self.atom_types = [] self.atom_labels = [] self.atom_pos = [] # Align and rotate the building block 1 to their respective positions BB_D4.align_to(topology_info['vertices'][0]['align_v']) # Determine the angle that alings the X[1] to one of the vertices of the tetrahedron vertice_pos = unit_vector(np.array([1, 0, 1])) Q_vertice_pos = BB_D4.get_X_points()[1][1] rotated_list = [ R.from_rotvec( angle * unit_vector(topology_info['vertices'][0]['align_v']), degrees=False ).apply(Q_vertice_pos) for angle in np.linspace(0, 2*np.pi, 360) ] # Calculate the angle between the vertice_pos and the elements of rotated_list angle_list = [angle(vertice_pos, i) for i in rotated_list] rot_angle = np.linspace(0, 360, 360)[np.argmax(angle_list)] BB_D4.rotate_around(rotation_axis=np.array(topology_info['vertices'][0]['align_v']), angle=rot_angle, degree=True) BB_D4.shift(np.array(topology_info['vertices'][0]['position'])*a_conv) BB_D4.remove_X() # Add the building block 1 to the structure self.atom_types += BB_D4.atom_types self.atom_pos += BB_D4.atom_pos.tolist() self.atom_labels += ['C1' if i == 'C' else i for i in BB_D4.atom_labels] # Add the building block 1 to the structure self.atom_types += BB_D4.atom_types self.atom_pos += list(-np.array(BB_D4.atom_pos) + np.array(topology_info['vertices'][1]['position'])*a_conv) self.atom_labels += ['C1' if i == 'C' else i for i in BB_D4.atom_labels] # Add the building blocks to the structure for edge_data in topology_info['edges']: # Copy the building block 2 object BB = copy.deepcopy(BB_L2) # Align, rotate and shift the building block 2 to their respective positions BB.align_to(edge_data['align_v']) BB.rotate_around(rotation_axis=edge_data['align_v'], angle=edge_data['angle']) BB.shift(np.array(edge_data['position']) * a_conv) # Replace "X" the building block with the correct atom dicated by the connection group BB.replace_X(bond_atom) BB.remove_X() # Update the structure self.atom_types += BB.atom_types self.atom_pos += BB.atom_pos.tolist() self.atom_labels += ['C2' if i == 'C' else i for i in BB.atom_labels] atom_types, atom_labels, atom_pos = [], [], [] for n_int in range(int(self.stacking)): int_direction = np.array([0, 1, 0]) * d_param_base * n_int atom_types += self.atom_types atom_pos += (np.array(self.atom_pos) + int_direction).tolist() atom_labels += self.atom_labels self.atom_types = atom_types self.atom_pos = atom_pos self.atom_labels = atom_labels StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() StartingFramework.translate_sites( np.ones(len(self.atom_types)).astype(int).tolist(), [0, 0, 0], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.cellParameters = np.array([dict_structure['lattice']['a'], dict_structure['lattice']['b'], dict_structure['lattice']['c'], dict_structure['lattice']['alpha'], dict_structure['lattice']['beta'], dict_structure['lattice']['gamma']]).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = StartingFramework.formula StartingFramework.to(os.path.join(os.getcwd(), 'TESTE_DIA-A.cif'), fmt='cif') dist_matrix = StartingFramework.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(StartingFramework, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_primitive_standard_structure(keep_site_properties=True) dict_structure = symm.get_refined_structure(keep_site_properties=True).as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.cellParameters = np.array([dict_structure['lattice']['a'], dict_structure['lattice']['b'], dict_structure['lattice']['c'], dict_structure['lattice']['alpha'], dict_structure['lattice']['beta'], dict_structure['lattice']['gamma']]).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = self.prim_structure.formula self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]
[docs] def create_bor_structure(self, BB_D4: str, BB_T3: str, interp_dg: str = '1', d_param_base: float = 7.2, print_result: bool = True, **kwargs): """Creates a COF with BOR network. The DIA net is composed of one tetrapodal tetrahedical building block and one tripodal triangular building block. Parameters ---------- BB_D4 : BuildingBlock, required The BuildingBlock object of the tetrapodal tetrahedical Buiding Block BB_T3 : BuildingBlock, required The BuildingBlock object of the tripodal triangular Buiding Block interp_dg : str, optional The degree of interpenetration of the framework (default is '1') d_param_base : float, optional The base value for interlayer distance in angstroms (default is 7.2) print_result : bool, optional Parameter for the control for printing the result (default is True) Returns ------- list A list of strings containing: 1. the structure name, 2. lattice type, 3. hall symbol of the cristaline structure, 4. space group, 5. number of the space group, 6. number of operation symmetry """ connectivity_error = 'Building block {} must present connectivity {} not {}' if BB_D4.connectivity != 4: self.logger.error(connectivity_error.format('A', 4, BB_D4.connectivity)) raise BBConnectivityError(4, BB_D4.connectivity) if BB_T3.connectivity != 3: self.logger.error(connectivity_error.format('B', 3, BB_T3.connectivity)) raise BBConnectivityError(3, BB_T3.connectivity) # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] self.name = f'{BB_D4.name}-{BB_T3.name}-BOR-{interp_dg}' self.topology = 'BOR' self.staking = interp_dg self.dimension = 3 self.charge = BB_D4.charge + BB_T3.charge self.chirality = BB_D4.chirality or BB_T3.chirality self.logger.debug(f'Starting the creation of {self.name}') # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_D4.conector, BB_T3.conector) self.logger.debug('{} detected as bond atom for groups {} and {}'.format(bond_atom, BB_D4.conector, BB_T3.conector)) # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks d_size = (np.array(BB_D4.size).mean() + np.array(BB_T3.size).mean()) # Calculate the primitive cell vector assuming tetrahedical building blocks a_conv = np.sqrt(6) * d_size # Create the primitive lattice self.cellMatrix = Lattice(a_conv * np.array(topology_info['lattice'])) self.cellParameters = np.array([a_conv, a_conv, a_conv, 90, 90, 90]).astype(float) # Create the structure atom_types = [] atom_labels = [] atom_pos = [] for D_site in topology_info['vertices']: D4 = BB_D4.copy() D4.align_to( np.array(D_site['align_v']) ) D4.rotate_around( rotation_axis=D_site['align_v'], angle=D_site['angle']) D4.shift(np.array(D_site['position'])*a_conv) atom_types += D4.atom_types atom_pos += D4.atom_pos.tolist() atom_labels += D4.atom_labels.tolist() # Translate all atoms to inside the cell for i, pos in enumerate(atom_pos): for j, coord in enumerate(pos): if coord < 0: atom_pos[i][j] += a_conv X_pos = [atom_pos[i] for i in np.where(np.array(atom_types) == 'X')[0]] T_site = topology_info['edges'][0] _, X = BB_T3.get_X_points() BB_T3.rotate_around([0, 0, 1], T_site['angle'], True) R_matrix = rotation_matrix_from_vectors([0, 0, 1], T_site['align_v']) BB_T3.atom_pos = np.dot(BB_T3.atom_pos, R_matrix.T) BB_T3.replace_X('O') # Get the 3 atoms that are closer to T_site['position'])*a_conv X_pos_temp = sorted(X_pos, key=lambda x: np.linalg.norm(x - np.array(T_site['position'])*a_conv)) X_center = np.array(X_pos_temp[:3]).mean(axis=0) BB_T3.shift(X_center) atom_types += BB_T3.atom_types atom_pos += BB_T3.atom_pos.tolist() atom_labels += BB_T3.atom_labels.tolist() T4 = BB_T3.copy() T4.rotate_around([0, 0, 1], 180, True) atom_types += T4.atom_types atom_pos += T4.atom_pos.tolist() atom_labels += T4.atom_labels.tolist() T2 = BB_T3.copy() T2.rotate_around([0, 0, 1], 90, True) T2.rotate_around([1, 0, 0], -90, True) atom_types += T2.atom_types atom_pos += T2.atom_pos.tolist() atom_labels += T2.atom_labels.tolist() T3 = BB_T3.copy() T3.rotate_around([0, 0, 1], -90, True) T3.atom_pos *= np.array([1, 1, -1]) atom_types += T3.atom_types atom_pos += T3.atom_pos.tolist() atom_labels += T3.atom_labels.tolist() # Translate all atoms to inside the cell for i, pos in enumerate(atom_pos): for j, coord in enumerate(pos): if coord < 0: atom_pos[i][j] += a_conv # Remove the X atoms from the list X_index = np.where(np.array(atom_types) == 'X')[0] self.atom_types = [atom_types[i] for i in range(len(atom_types)) if i not in X_index] self.atom_pos = [atom_pos[i] for i in range(len(atom_pos)) if i not in X_index] self.atom_labels = [atom_labels[i] for i in range(len(atom_labels)) if i not in X_index] atom_types, atom_labels, atom_pos = [], [], [] for n_int in range(int(self.stacking)): int_direction = np.array([0, 1, 0]) * d_param_base * n_int atom_types += self.atom_types atom_pos += (np.array(self.atom_pos) + int_direction).tolist() atom_labels += self.atom_labels self.atom_types = atom_types self.atom_pos = atom_pos self.atom_labels = atom_labels StartingFramework = Structure( self.cellMatrix, self.atom_types, self.atom_pos, coords_are_cartesian=True, site_properties={'source': self.atom_labels} ).get_sorted_structure() StartingFramework.translate_sites( np.ones(len(self.atom_types)).astype(int).tolist(), [0, 0, 0], frac_coords=True, to_unit_cell=True ) dict_structure = StartingFramework.as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.cellParameters = np.array([dict_structure['lattice']['a'], dict_structure['lattice']['b'], dict_structure['lattice']['c'], dict_structure['lattice']['alpha'], dict_structure['lattice']['beta'], dict_structure['lattice']['gamma']]).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = StartingFramework.formula StartingFramework.to('TESTE_BOR.cif', fmt='cif') dist_matrix = StartingFramework.distance_matrix # Check if there are any atoms closer than 0.8 A for i in range(len(dist_matrix)): for j in range(i+1, len(dist_matrix)): if dist_matrix[i][j] < self.dist_threshold: raise BondLenghError(i, j, dist_matrix[i][j], self.dist_threshold) # Get the simmetry information of the generated structure symm = SpacegroupAnalyzer(StartingFramework, symprec=self.symm_tol, angle_tolerance=self.angle_tol) try: self.prim_structure = symm.get_primitive_standard_structure(keep_site_properties=True) dict_structure = symm.get_refined_structure(keep_site_properties=True).as_dict() self.cellMatrix = np.array(dict_structure['lattice']['matrix']).astype(float) self.cellParameters = np.array([dict_structure['lattice']['a'], dict_structure['lattice']['b'], dict_structure['lattice']['c'], dict_structure['lattice']['alpha'], dict_structure['lattice']['beta'], dict_structure['lattice']['gamma']]).astype(float) self.atom_types = [i['label'] for i in dict_structure['sites']] self.atom_pos = [i['xyz'] for i in dict_structure['sites']] self.atom_labels = [i['properties']['source'] for i in dict_structure['sites']] self.n_atoms = len(dict_structure['sites']) self.composition = self.prim_structure.formula self.logger.debug(self.prim_structure) self.lattice_type = symm.get_lattice_type() self.space_group = symm.get_space_group_symbol() self.space_group_n = symm.get_space_group_number() symm_op = symm.get_point_group_operations() self.hall = symm.get_hall() except Exception as e: self.logger.exception(e) self.lattice_type = 'Triclinic' self.space_group = 'P1' self.space_group_n = '1' symm_op = [1] self.hall = 'P 1' symm_text = get_framework_symm_text(self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)) self.logger.info(symm_text) return [self.name, str(self.lattice_type), str(self.hall[0:2]), str(self.space_group), str(self.space_group_n), len(symm_op)]