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 copy
import os

import numpy as np
from ase import Atoms
# Import pymatgen
from pymatgen.core import Lattice, Structure
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from pymatgen.transformations.advanced_transformations import \
    CubicSupercellTransformation
from scipy.spatial.transform import Rotation as R

# Import pycofbuilder building_block
from pycofbuilder.building_block import BuildingBlock
# Import pycofbuilder topology data
from pycofbuilder.data.topology import TOPOLOGY_DICT
# Import pycofbuilder exceptions
from pycofbuilder.exceptions import BBConnectivityError, BondLenghError
# Import pycofbuilder io_tools
from pycofbuilder.io_tools import (save_chemjson, save_cif, save_gjf, save_pdb,
                                   save_pqr, save_qe, save_turbomole,
                                   save_vasp, save_xsf, save_xyz)
from pycofbuilder.logger import create_logger
# Import pycofbuilder tools
from pycofbuilder.tools import (angle, cell_to_cellpar, cellpar_to_cell,
                                get_bond_atom, get_bonds,
                                get_framework_symm_text,
                                rotation_matrix_from_vectors, unit_vector)


[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 = None, **kwargs): self.name: str | None = name self.out_path: str = kwargs.get("out_path", os.path.join(os.getcwd(), "out")) self.save_bb: bool = kwargs.get("save_bb", False) 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: float = kwargs.get("symm_tol", 0.1) self.angle_tol: float = kwargs.get("angle_tol", 0.5) self.dist_threshold: float = kwargs.get("dist_threshold", 0.8) self.bond_threshold: float = kwargs.get("bond_threshold", 1.3) self.bb1_name = None self.bb2_name = None self.topology = None self.stacking: str | int = 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_A", "FXT", "FXT_A", ] # To add: ['dia', 'bor', 'srs', 'pts', 'ctn', 'rra', 'fcc', 'stp', 'acs', 'tbo', 'bcu', 'fjh', 'ceq'] self.available_3D_top = ["DIA", "DIA_A", "BOR", "LON", "LON_A"] 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)], "LON": [str(i + 1) for i in range(15)], "LON_A": [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 a string" 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_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, "LON": self.create_lon_structure, "LON_A": self.create_lon_a_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: tuple = (1, 1, 1), save_dir=None, primitive=False, save_bonds=True, save_labels=False, ) -> 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 : tuple, 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_bonds : bool, optional If True, the bonds are saved in the structure. Default: True save_labels : bool, optional If True, the atom labels are saved in the structure. 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}, ) structure.make_supercell(supercell) if save_bonds: bonds = get_bonds(structure, self.bond_threshold) else: bonds = [] structure_dict = 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"]] if save_labels: atom_labels = [ "{}{}_{}".format(atom_types[i], i, atom_labels[i]) for i in range(len(atom_types)) ] else: atom_labels = [atom_types[i] + str(i) for i in range(len(atom_types))] 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 os.makedirs(save_path, exist_ok=True) save_dict[fmt]( path=save_path, file_name=self.name, cell=cell, atomTypes=atom_types, atomLabels=atom_labels, atomPos=atom_pos, bonds=bonds, )
[docs] def make_cubic( self, min_length=10, force_diagonal=False, force_90_degrees=True, min_atoms=None, max_atoms=None, angle_tolerance=1e-3, ): """ Transform the primitive structure into a supercell with alpha, beta, and gamma equal to 90 degrees. The algorithm will iteratively increase the size of the supercell until the largest inscribed cube's side length is at least 'min_length' and the number of atoms in the supercell falls in the range ``min_atoms < n < max_atoms``. Parameters ---------- min_length : float, optional Minimum length of the cubic cell (default is 10) force_diagonal : bool, optional If True, generate a transformation with a diagonal transformation matrix (default is False) force_90_degrees : bool, optional If True, force the angles to be 90 degrees (default is True) min_atoms : int, optional Minimum number of atoms in the supercell (default is None) max_atoms : int, optional Maximum number of atoms in the supercell (default is None) angle_tolerance : float, optional The angle tolerance for the transformation (default is 1e-3) """ cubic_dict = ( CubicSupercellTransformation( min_length=min_length, force_90_degrees=force_90_degrees, force_diagonal=force_diagonal, min_atoms=min_atoms, max_atoms=max_atoms, angle_tolerance=angle_tolerance, ) .apply_transformation(self.prim_structure) .as_dict() ) self.cellMatrix = np.array(cubic_dict["lattice"]["matrix"]).astype(float) self.cellParameters = cell_to_cellpar(self.cellMatrix) self.atom_types = [i["label"] for i in cubic_dict["sites"]] self.atom_pos = [i["xyz"] for i in cubic_dict["sites"]] self.atom_labels = [i["properties"]["source"] for i in cubic_dict["sites"]]
[docs] def to_ase(self): """ Convert the framework to an ASE Atoms object. """ atoms = Atoms( symbols=self.atom_types, positions=self.atom_pos, cell=self.cellMatrix, pbc=True, ) return atoms
# --------------- Net creation methods -------------------------- #
[docs] def create_hcb_structure( self, BB_T3_A, BB_T3_B, stacking: str = "AA", slab: float = 10.0, shift_vector: tuple = (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: tuple, 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) ).tolist() 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 = 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) ).tolist() 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 = 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) ).tolist() 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_A: str, BB_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_A.connectivity != 4: self.logger.error(connectivity_error.format("A", 4, BB_A.connectivity)) raise BBConnectivityError(4, BB_A.connectivity) if BB_B.connectivity != 4: self.logger.error(connectivity_error.format("B", 4, BB_B.connectivity)) raise BBConnectivityError(4, BB_B.connectivity) self.name = f"{BB_A.name}-{BB_B.name}-SQL-{stacking}" self.topology = "SQL" self.staking = stacking self.dimension = 2 self.charge = BB_A.charge + BB_B.charge self.chirality = BB_A.chirality or BB_B.chirality self.logger.debug(f"Starting the creation of {self.name}") # Get the positions of the "X" atoms _, X_A = BB_A.get_X_points() _, X_B = BB_B.get_X_points() # Calculate the alpha angle for the rotation of the building blocks alpha_A = -np.arctan2(X_A[0][1] - X_A[-1][1], X_A[0][0] - X_A[-1][0]) alpha_B = -np.arctan2(X_B[0][1] - X_B[-1][1], X_B[0][0] - X_B[-1][0]) # Detect the bond atom from the connection groups type bond_atom = get_bond_atom(BB_A.conector, BB_B.conector) self.logger.debug( "{} detected as bond atom for groups {} and {}".format( bond_atom, BB_A.conector, BB_B.conector ) ) # Replace "X" the building block BB_A.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_A.remove_X() BB_B.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size_A = ( np.abs(BB_A.size[0] * np.sin(alpha_A)) + np.abs(BB_B.size[0] * np.cos(alpha_B)) ) * 2 size_B = ( np.abs(BB_A.size[0] * np.cos(alpha_A)) + np.abs(BB_B.size[0] * np.cos(alpha_B)) ) * 2 # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_A.atom_pos)[2])) + abs( min(np.transpose(BB_B.atom_pos)[2]) ) delta_b = abs(max(np.transpose(BB_A.atom_pos)[2])) + abs( min(np.transpose(BB_B.atom_pos)[2]) ) delta_max = max([delta_a, delta_b]) # Calculate the cell parameters a = topology_info["a"] * size_A b = topology_info["b"] * size_B 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 self.atom_types += BB_A.atom_types vertice_pos = np.array([0, 0, 0]) R_Matrix = R.from_euler("z", -alpha_A, degrees=False).as_matrix() rotated_pos = np.dot(BB_A.atom_pos, R_Matrix) self.atom_pos += rotated_pos.tolist() self.atom_labels += ["C1" if i == "C" else i for i in BB_A.atom_labels] # Add the second building block to the structure self.atom_types += BB_B.atom_types vertice_pos = np.array([a / 2, b / 2, 0]) R_Matrix = R.from_euler("z", -alpha_B, degrees=False).as_matrix() rotated_pos = np.dot(BB_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_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}") # Get the position of the "X" atoms _, X_S4 = BB_S4.get_X_points() # Calculate the alpha angle for the rotation of the building blocks alpha_S4 = -np.arctan2(X_S4[0][1] - X_S4[-1][1], X_S4[0][0] - X_S4[-1][0]) # 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_A = ((BB_S4.size[0] + BB_L2.size[0]) * np.abs(np.sin(alpha_S4))) * 4 size_B = ((BB_S4.size[0] + BB_L2.size[0]) * np.abs(np.cos(alpha_S4))) * 4 # 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_A b = topology_info["b"] * size_B 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 S4 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"]) * np.array([a, b, c]) R_Matrix = R.from_euler("z", -alpha_S4, degrees=False).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] L2_angle_list = [-alpha_S4, alpha_S4, -alpha_S4, alpha_S4] # Add the L2 building blocks to the structure for i, edge_data in enumerate(topology_info["edges"]): self.atom_types += BB_L2.atom_types edge_pos = np.array(edge_data["position"]) * np.array([a, b, c]) R_Matrix = R.from_euler("z", L2_angle_list[i], degrees=False).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_R4_A: BuildingBlock, BB_R4_B: BuildingBlock, stacking: str = "AA", print_result: bool = True, slab: float = 10.0, shift_vector: list = (1.0, 1.0, 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_R4_A : BuildingBlock, required The BuildingBlock object of the rectangular tetrapodal Buiding Block A BB_R4_B : BuildingBlock, required The BuildingBlock object of the rectangular 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.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_R4_A.connectivity != 4: self.logger.error(connectivity_error.format("A", 4, BB_R4_A.connectivity)) raise BBConnectivityError(4, BB_R4_A.connectivity) if BB_R4_B.connectivity != 4: self.logger.error(connectivity_error.format("B", 4, BB_R4_B.connectivity)) raise BBConnectivityError(4, BB_R4_B.connectivity) self.name = f"{BB_R4_A.name}-{BB_R4_B.name}-FXT-{stacking}" self.topology = "FXT" self.staking = stacking self.dimension = 2 self.charge = BB_R4_A.charge + BB_R4_B.charge self.chirality = BB_R4_A.chirality or BB_R4_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_R4_A.conector, BB_R4_B.conector) self.logger.debug( "{} detected as bond atom for groups {} and {}".format( bond_atom, BB_R4_A.conector, BB_R4_B.conector ) ) # Find the d' vector to realign the S4/R4 building blocks for bb in [BB_R4_A, BB_R4_B]: x_vec = bb.get_X_points()[1] R_Matrix = R.from_euler( "z", angle([0, 1, 0], (x_vec[0] + x_vec[1]) / 2), degrees=True ).as_matrix() rotated_pos = np.dot(bb.atom_pos, R_Matrix) bb.atom_pos = rotated_pos # Replace "X" the building block BB_R4_A.replace_X(bond_atom) # Remove the "X" atoms from the the building block BB_R4_A.remove_X() BB_R4_B.remove_X() # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = 2 * (BB_R4_A.size[0] + BB_R4_B.size[0]) # Calculate the delta size to add to the c parameter delta_a = abs(max(np.transpose(BB_R4_A.atom_pos)[2])) + abs( min(np.transpose(BB_R4_B.atom_pos)[2]) ) delta_b = abs(max(np.transpose(BB_R4_A.atom_pos)[2])) + abs( min(np.transpose(BB_R4_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_R4_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_R4_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_R4_A.atom_labels] # Add the second building block to the structure for vertice_data in topology_info["vertices"][1:]: self.atom_types += BB_R4_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_R4_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_R4_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 ) ) x_vec = BB_S4.get_X_points()[1] R_Matrix = R.from_euler( "z", angle([0, 1, 0], (x_vec[0] + x_vec[1]) / 2), degrees=True ).as_matrix() rotated_pos = np.dot(BB_S4.atom_pos, R_Matrix) BB_S4.atom_pos = rotated_pos # 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( a * unit_vector(topology_info["vertices"][0]["align_v"]), degrees=False ).apply(Q_vertice_pos) for a 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( a * unit_vector(topology_info["vertices"][0]["align_v"]), degrees=False ).apply(Q_vertice_pos) for a 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() 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( a * unit_vector(topology_info["vertices"][0]["align_v"]), degrees=False ).apply(Q_vertice_pos) for a 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 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 BOR 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 """ raise NotImplementedError("The BOR network is not implemented yet") 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), ]
[docs] def create_lon_structure( self, BB_D4_A: str, BB_D4_B: str, interp_dg: str = "1", d_param_base: float = 7.2, print_result: bool = True, **kwargs, ): """Creates a COF with LON network. The LON net is composed of two tetrapodal tetrahedical building blocks. Parameters ---------- BB_D4_A : BuildingBlock, required The BuildingBlock object of the tetrapodal tetrahedical Buiding Block BB_D4_B : 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_D4_A.connectivity != 4: self.logger.error(connectivity_error.format("A", 4, BB_D4_A.connectivity)) raise BBConnectivityError(4, BB_D4_A.connectivity) if BB_D4_B.connectivity != 4: self.logger.error(connectivity_error.format("B", 4, BB_D4_B.connectivity)) raise BBConnectivityError(4, BB_D4_B.connectivity) self.name = f"{BB_D4_A.name}-{BB_D4_B.name}-LON-{interp_dg}" self.topology = "LON" self.staking = interp_dg self.dimension = 3 self.charge = BB_D4_A.charge + BB_D4_B.charge self.chirality = BB_D4_A.chirality or BB_D4_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_D4_A.conector, BB_D4_B.conector) self.logger.debug( "{} detected as bond atom for groups {} and {}".format( bond_atom, BB_D4_A.conector, BB_D4_B.conector ) ) # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] # Measure the base size of the building blocks size = np.average(BB_D4_A.size) + np.average(BB_D4_B.size) alat = 3 / 2 * size * np.cos(np.radians(19.4712)) / np.cos(np.radians(30)) # Calculate the primitive cell vector assuming perfect tetrahedical building blocks self.cellMatrix = Lattice(alat * np.array(topology_info["lattice"])) self.cellParameters = np.array( [ alat * topology_info["a"], alat * topology_info["b"], alat * topology_info["c"], topology_info["alpha"], topology_info["beta"], topology_info["gamma"], ] ).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_A.align_to(topology_info["vertices"][0]["align_v"]) BB_D4_B.align_to(topology_info["vertices"][2]["align_v"]) for site in [0, 1]: BB = copy.deepcopy(BB_D4_A) BB.align_to(topology_info["vertices"][site]["align_v"]) BB.rotate_around( rotation_axis=topology_info["vertices"][site]["align_v"], angle=topology_info["vertices"][site]["angle"], ) BB.shift(np.array(topology_info["vertices"][site]["position"]) * alat) 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 += ["C1" if i == "C" else i for i in BB.atom_labels] for site in [2, 3]: BB = copy.deepcopy(BB_D4_B) BB.align_to(topology_info["vertices"][site]["align_v"]) BB.rotate_around( rotation_axis=topology_info["vertices"][site]["align_v"], angle=topology_info["vertices"][site]["angle"], ) BB.shift(np.array(topology_info["vertices"][site]["position"]) * alat) BB.remove_X() 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, 0, 1]) * 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 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_lon_a_structure( self, BB_D4: BuildingBlock, BB_L2: BuildingBlock, interp_dg: str | int = 0, d_param_base: float = 10, print_result: bool = True, **kwargs, ): """Creates a COF with LON network. The LON_A net is composed of one tetrapodal tetrahedical and one linear 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 """ # Get the topology information topology_info = TOPOLOGY_DICT[self.topology] connectivity_error = "Building block {} must present connectivity {} not {}" if BB_D4.connectivity != topology_info["vertice_connectivity"]: self.logger.error( connectivity_error.format( "A", topology_info["vertice_connectivity"], BB_D4.connectivity ) ) raise BBConnectivityError( topology_info["vertice_connectivity"], BB_D4.connectivity ) if BB_L2.connectivity != topology_info["edge_connectivity"]: self.logger.error( connectivity_error.format( "B", topology_info["edge_connectivity"], BB_L2.connectivity ) ) raise BBConnectivityError( topology_info["edge_connectivity"], BB_L2.connectivity ) # Check if stacking is present in kwargs if "stacking" in kwargs: self.logger.warning( "The stacking parameter should not be used in 3D nets. The interp_dg parameter should be used instead" ) self.name = f"{BB_D4.name}-{BB_L2.name}-LON_A-{interp_dg}" self.topology = "LON_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 ) ) # Measure the base size of the building blocks size = np.average(BB_D4.size) + np.average(BB_L2.size) alat = 4 * size * np.cos(np.radians(35.26439)) # Calculate the primitive cell vector assuming perfect tetrahedical building blocks self.cellMatrix = Lattice(alat * np.array(topology_info["lattice"])) self.cellParameters = np.array( [ alat * topology_info["a"], alat * topology_info["b"], alat * topology_info["c"], topology_info["alpha"], topology_info["beta"], topology_info["gamma"], ] ).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"]) for site in range(4): BB = copy.deepcopy(BB_D4) BB.align_to(topology_info["vertices"][site]["align_v"]) BB.rotate_around( rotation_axis=topology_info["vertices"][site]["align_v"], angle=topology_info["vertices"][site]["angle"], ) BB.shift(np.array(topology_info["vertices"][site]["position"]) * alat) 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 += ["C1" if i == "C" else i for i in BB.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"]) * alat) 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([1, 1, 1]) * 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 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), ]