Introductory MAZE Tutorial ========================== These tutorials showcase the capabilities of the MAZE code, along with comments describing how to use the functions shown in the demos. Verify Installation ^^^^^^^^^^^^^^^^^^^ Verify the installation by importing maze and making a simple empty Zeolite .. code:: python import maze maze.Zeolite() .. code-block:: text Zeolite(symbols='', pbc=False) -------------- Cif Fetching from the Database of Zeolite Structures ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The `database of zeolite structures `_ is a useful resource for zeolite simulation experiments. It contains cif files for all synthesized zeolites, organized by their three letter zeolite code. Downloading them from the website is easy when working on a local machine, but challenging when working on a remote machine. To facilitate smoother workflows, a simple python function which downloads cif files from the database was created. An example of using this to download a few different cif files is shown below. **Note:** Some users have had trouble using the ``cif_download`` function and the ``make`` method. The cause of this issue is typically latency issues with the IZA database's website. If you encounter these issues you can download all of the CIF files in the database by going to this `google drive link `_, downloading the zip folder and extracting the ``data`` folder. Place this data folder in your working directory. You can see your working directory by running the python command ``import os`` followed by ``print(os.getcwd())``. After placing the ``data`` folder, which contains all of the CIF files from the IZA database, in your working directory, you will be able to run the ``make`` command without accessing the IZA database. **Note:** The CIF reading capabilities of MAZE rely on those of ASE. Version 3.21.0 of ASE introduced a refactored CIF reader, which is not able to read all of the CIF files on the IZA database, thus most, but not all of the CIF files in the IZA database are readable with MAZE and the most recent version of ASE. This issue can be mitigated by using MAZE with any ASE version < 3.21.0, which is able to read and label the T-sites of all of the CIF files in the IZA database. This issuse might be fixed in future versions of ASE. First, we import the MAZE package, the glob package, and the ``download_cif`` function from the ``maze.cif_download`` module. >>> import maze >>> from maze.cif_download import download_cif >>> import glob Next, we declare a helper function which prints out all of the directories in the current working directory. This will help us visualize the ``download_cif`` function's behavior. >>> def print_dirs(): ... print('dirs in cwd', glob.glob('**/')) ... We can view the directories names in our current directory using our helper function. >>> print_dirs() dirs in cwd [] Now, let's download the GOO cif file, using the ``download_cif`` function. By default, the cif file is downloaded to the `data` directory; if this 'data' directory doesn't exist, it is created. >>> download_cif("GOO") # downloads "GOO.cif" to data/GOO.cif >>> print_dirs() dirs in cwd ['data/'] >>> print('files in data dir', glob.glob("data/*")) files in data dir ['data/goo.cif'] We can download the cif file to a custom location by specifying the directory we want to use: >>> download_cif("off", data_dir="my_other_data") >>> print_dirs() dirs in cwd ['my_other_data/', 'data/'] >>> print('files in my_other_data dir', glob.glob("my_other_data/*")) files in my_other_data dir ['my_other_data/off.cif'] -------------- Making a Zeolite Using the Make Function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A cif file contains the crystallographic information that defines a zeolite structure. A downloaded cif from the iza-sc database of zeolite strucutres looks like this: .. code-block:: text #************************************************************************** # # cif taken from the iza-sc database of zeolite structures # ch. baerlocher and l.b. mccusker # database of zeolite structures: http://www.iza-structure.org/databases/ # # the atom coordinates and the cell parameters were optimized with dls76 # assuming a pure sio2 composition. # #************************************************************************** _cell_length_a 13.6750(0) _cell_length_b 13.6750(0) _cell_length_c 14.7670(0) _cell_angle_alpha 90.0000(0) _cell_angle_beta 90.0000(0) _cell_angle_gamma 120.0000(0) _symmetry_space_group_name_h-m 'r -3 m' _symmetry_int_tables_number 166 _symmetry_cell_setting trigonal loop_ _symmetry_equiv_pos_as_xyz '+x,+y,+z' '2/3+x,1/3+y,1/3+z' '1/3+x,2/3+y,2/3+z' '-y,+x-y,+z' ... skipping all of this info for space ... loop_ _atom_site_label _atom_site_type_symbol _atom_site_fract_x _atom_site_fract_y _atom_site_fract_z o1 o 0.9020 0.0980 0.1227 o2 o 0.9767 0.3101 0.1667 o3 o 0.1203 0.2405 0.1315 o4 o 0.0000 0.2577 0.0000 t1 si 0.9997 0.2264 0.1051 An important piece of information in each cif file is the \_atom_site_label (01, 02, … t1, t2.. ect.) that is located in the first column near the atom position information. This information about the atoms identities is lost when the ``ase.io.read`` function is used to build an atoms object from a cif file. Because the identity of the T-sites is critical for zeolite simulation experiments, this issue inspired the creation of a custom constructor of the ``Zeolite`` object: ``make``. This static method creates a ``Zeolite`` object, labels the unique atoms by tagging them, and then stores the mapping between the ``atom_site_label`` and the atom indices in the dictionaries ``site_to_atom_indices`` and ``atom_indices_to_site``. To demonstrate this feature, let us try building a ``Zeolite`` object from a cif file. First, we import the MAZE package, the cif_download function and the Zeolite object .. code:: python import maze from maze.cif_download import download_cif Next we import some ase packages to help us view the Zeolites we make. .. code:: python import matplotlib.pyplot as plt from ase.visualize.plot import plot_atoms First we use the download_cif function to fetch the ``CHA.cif`` file from the IZA database. .. code:: python download_cif('CHA', data_dir='data') Now we can use the static ``make`` method to ``Zeolite`` with labeled atoms. .. code:: python from maze import Zeolite cha_zeolite = Zeolite.make('CHA', data_dir='data') Our ``Zeolite`` object has been built. We can view it with the ase plot_atoms method (or view method). This works flawlessly because the ``Zeolite`` class is a subclass of the ``Atoms`` class. .. code:: python plot_atoms(cha_zeolite) .. image:: output_17_1.png The atom identity information is stored in two dictionaries. Let’s take a look at them: .. code:: python print(cha_zeolite.site_to_atom_indices) .. parsed-literal:: {'O1': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], 'O2': [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35], 'O3': [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53], 'O4': [54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71], 'T1': [72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107]} .. code:: python print(cha_zeolite.atom_indices_to_sites) .. parsed-literal:: {0: 'O1', 1: 'O1', 2: 'O1', 3: 'O1', 4: 'O1', 5: 'O1', 6: 'O1', 7: 'O1', 8: 'O1', 9: 'O1', 10: 'O1', 11: 'O1', 12: 'O1', 13: 'O1', 14: 'O1', 15: 'O1', 16: 'O1', 17: 'O1', 18: 'O2', 19: 'O2', 20: 'O2', 21: 'O2', 22: 'O2', 23: 'O2', 24: 'O2', 25: 'O2', 26: 'O2', 27: 'O2', 28: 'O2', 29: 'O2', 30: 'O2', 31: 'O2', 32: 'O2', 33: 'O2', 34: 'O2', 35: 'O2', 36: 'O3', 37: 'O3', 38: 'O3', 39: 'O3', 40: 'O3', 41: 'O3', 42: 'O3', 43: 'O3', 44: 'O3', 45: 'O3', 46: 'O3', 47: 'O3', 48: 'O3', 49: 'O3', 50: 'O3', 51: 'O3', 52: 'O3', 53: 'O3', 54: 'O4', 55: 'O4', 56: 'O4', 57: 'O4', 58: 'O4', 59: 'O4', 60: 'O4', 61: 'O4', 62: 'O4', 63: 'O4', 64: 'O4', 65: 'O4', 66: 'O4', 67: 'O4', 68: 'O4', 69: 'O4', 70: 'O4', 71: 'O4', 72: 'T1', 73: 'T1', 74: 'T1', 75: 'T1', 76: 'T1', 77: 'T1', 78: 'T1', 79: 'T1', 80: 'T1', 81: 'T1', 82: 'T1', 83: 'T1', 84: 'T1', 85: 'T1', 86: 'T1', 87: 'T1', 88: 'T1', 89: 'T1', 90: 'T1', 91: 'T1', 92: 'T1', 93: 'T1', 94: 'T1', 95: 'T1', 96: 'T1', 97: 'T1', 98: 'T1', 99: 'T1', 100: 'T1', 101: 'T1', 102: 'T1', 103: 'T1', 104: 'T1', 105: 'T1', 106: 'T1', 107: 'T1'} Depending on the situation, one dictionary may be more useful than the other. One Step Zeolite Construction ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Downloading a cif file everytime you want to load a new Zeolite can be annoying. Thus, the ``make`` function automatically downloads the cif file from the IZA database if it cannot be located in the provided directory. It places the downloaded cif file in folder called ``data`` in the current working directory. Data paths can be specified with the ``data_dir`` optional argument. .. code:: python from maze import Zeolite bea_zeolite = Zeolite.make('BEA') # Download the BEA cif file and build zeolite plot_atoms(bea_zeolite) .. image:: output_23_1.png -------------- Identifying Atom Types in a Zeolite Structure ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``Zeotype`` class includes methods for identifying the different types of atoms in a zeolite structure. These methods will work on all ``Zeotype`` objects, even those where the ``atom_indices_to_site`` and ``site_to_atom_indices`` are not set. Let’s import the Zeolite object and make a BEA structure and some tools to visualize the structures we create. .. code:: python from maze import zeolite import matplotlib.pyplot as plt from ase.visualize.plot import plot_atoms .. code:: python bea_zeolite = Zeolite.make('BEA') plot_atoms(bea_zeolite) .. image:: output_30_1.png To make things more interesting, let us replace the Si-T1 sites in the bea Zeolite with Aluminum. .. code:: python for t1_index in bea_zeolite.site_to_atom_indices['T1']: bea_zeolite[t1_index].symbol = 'Al' .. code:: python plot_atoms(bea_zeolite) .. image:: output_33_1.png Making a replacement is that simple! Now we will use the ``count_elements`` method to get the count of each atom in the zeolite and the idenity of each atom. .. code:: python atoms_indices, count = bea_zeolite.count_elements() .. code:: python print(atoms_indices) .. parsed-literal:: {'O': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127], 'Al': [128, 129, 130, 131, 132, 133, 134, 135], 'Si': [136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191]} .. code:: python print(count) .. parsed-literal:: {'O': 128, 'Al': 8, 'Si': 56} -------------- Extracting, Adding and Capping Clusters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ One of the most useful features of the MAZE package is the ability to add and remove atoms from a ``Zeolite`` object. To demonstrate this, we will extract a cluster from an ``Zeolite`` object, change some of the atoms, and then integrate it back into the main ``Zeolite``. First, we import the zeolite object and the plot_atoms function. .. code:: python from maze import zeolite import matplotlib.pyplot as plt from ase.visualize.plot import plot_atoms Then we make a bea_zeolite object .. code:: python bea_zeolite = Zeolite.make('BEA') plot_atoms(bea_zeolite) .. image:: output_44_1.png The next step is to pick a T-site and then use one of the static methods in the ``Cluster`` class to select indices to build the cluster. The atom 154 is right in the middle of the zeolite, which will make viewing the cluster creation easy. One could also use the ``site_to_atom_indices`` dictionary to select a specific T site. The Zeolite object uses a ``ClusterMaker`` object to select out certain groups of atoms from the Zeolite. By default a ``DefaultClusterMaker`` object is used. This ``DefaultClusterMaker`` object has a ``get_cluster_indices`` method which uses ``get_oh_cluster_indices`` function to selects the indices of central t atom, and surrounding oxygens and hydrogens. There are other cluster functions avalible in the source code of ``DefaultClusterMaker``. If you need some different functionality, simply make your own ClusterMaker object and set the ``Zeolite.cluster_maker`` attribute to your custom cluster maker. Let us call our ``bea_zeolite``\ ’s ``ClusterMaker`` object’s ``get_cluster_indices`` fun, to see what indices it will select. .. code:: python site = 154 cluster_indices = bea_zeolite.cluster_maker.get_cluster_indices(bea_zeolite, site) print(cluster_indices) .. parsed-literal:: [2, 66, 74, 138, 77, 82, 146, 22, 154, 30, 38, 102, 186, 42, 174, 50, 114, 117, 118, 58, 126] These are the indices of the atoms that will make up the resulting cluster and the indices of the atoms that will be absent in the open defect zeolite. We can now make the cluster and open defefect zeolites by using the ``get_cluster`` method. .. code:: python cluster, od = bea_zeolite.get_cluster(154) The cluster looks like this .. code:: python plot_atoms(cluster) .. image:: output_50_1.png the open defect looks like this .. code:: python plot_atoms(od) .. image:: output_52_1.png Both the open defect and the cluster are ``Zeolite`` objects, yet they have a different ztype attribute .. code:: python display(type(bea_zeolite)) display(type(od)) display(type(cluster)) .. code-block:: text maze.zeolite.Zeolite maze.zeolite.Zeolite maze.zeolite.Zeolite .. code:: python display(bea_zeolite.ztype) display(od.ztype) display(cluster.ztype) .. code-block:: text 'Zeolite' 'Open Defect' 'Cluster' Next we want to cap the cluster and changes some of the atoms its structure. Capping involves adding hydrogens and oxygens to the cluster. The built-in ``cap_atoms()`` method returns a new cluster object that has hydrogen caps added to it. .. code:: python capped_cluster = cluster.cap_atoms() .. code:: python plot_atoms(capped_cluster) .. image:: output_58_1.png In a typical zeolite workflow, this cluster would be optimized. Configuring an optimizer can be tricky, and so for this tutorial we instead replace the oxygen atoms with aluminum atoms. .. code:: python for atom in capped_cluster: if atom.symbol == 'O': capped_cluster[atom.index].symbol = 'Al' .. code:: python plot_atoms(capped_cluster) .. image:: output_61_1.png The next stage is removing the caps from the atom and reintegrating it back into the original zeolite. To remove caps, we have to find the name of the caps in ``additions`` dictionary .. code:: python dict(capped_cluster.additions) .. code-block:: text {'h_caps': ['h_caps_34']} or we can just select the last h_caps added using pythons list methods .. code:: python additon_category = 'h_caps' addition_name = capped_cluster.additions[additon_category][-1] display(addition_name) .. code-block:: text 'h_caps_34' Next we call the remove_addition method .. code:: python uncapped_cluster = capped_cluster.remove_addition(addition_name, additon_category) .. code:: python plot_atoms(uncapped_cluster) .. image:: output_68_1.png The caps have been removed. We can now integrate the cluster back into the original zeolite. .. code:: python bea_zeolite_with_al = bea_zeolite.integrate(uncapped_cluster) plot_atoms(bea_zeolite_with_al) .. image:: output_71_1.png The changes have been made. An important thing to notice is that none of the structural manipulation features of MAZE have side-effects. The ``bea_zeolite`` remains unchanged by this integration and a new ``bea_zeolite_with_al`` is created. Along with leading to cleaner code with fewew bugs, this style of programming also allows for method chanining. This demo showed the power of the MAZE code to extract and add clusters to zeotypes. This is one of the most useful features in the MAZE code.