diff --git a/architector/complex_construction.py b/architector/complex_construction.py
index ff31c29..a2a75df 100644
--- a/architector/complex_construction.py
+++ b/architector/complex_construction.py
@@ -280,9 +280,10 @@ def final_eval(self,single_point=False):
fix_m_neighbors=True,
ff_preopt_run=True
)
- self.calculator = CalcExecutor(tmp_relax.mol, parameters=self.parameters,
- final_sanity_check=self.parameters['full_sanity_checks'],
- relax=(not single_point)
+ self.calculator = CalcExecutor(tmp_relax.mol,
+ parameters=self.parameters,
+ final_sanity_check=self.parameters['full_sanity_checks'],
+ relax=(not single_point)
)
else: # Ensure calculation object at least exists
self.calculator = CalcExecutor(self.complexMol,method='UFF',fix_m_neighbors=False,relax=False)
diff --git a/architector/io_molecule.py b/architector/io_molecule.py
index 7c7344d..774521b 100644
--- a/architector/io_molecule.py
+++ b/architector/io_molecule.py
@@ -815,12 +815,7 @@ def append_ligand(self, ligand=None, non_coordinating=False):
newligbodict.update({newkey:val})
if lig_constraints is not None:
for ind,dist in lig_constraints.items():
- if non_coordinating:
- newind = natoms + ind
- elif ind > 1:
- newind = natoms + ind - 1
- else:
- newind = 1
+ newind = natoms + ind
self.ase_constraints.update({(0,newind):dist})
self.BO_dict.update(newligbodict)
self.ase_atoms += lig_ase_atoms
@@ -1295,6 +1290,7 @@ def classify_metal_geo_type(self,return_result=False):
def get_lig_dists(self,
calc_nonbonded_dists=True,
skin=0.3,
+ radius=None,
ref_ind='metals'):
"""Calculate metal-ligand distances and tabulate for a given structure.
@@ -1305,6 +1301,8 @@ def get_lig_dists(self,
skin : int, optional
Cutoff for nonbonded distances in Angstroms (distance considered "close" by within this many angstroms
of another coordinating atom to the metal), by default 0.3
+ radius : float, optional
+ Cutoff for radius of interaction to visualize around the molecule, by default None.
ref_ind : int, optional
Index of the atom to reference to, None will reference to all metals.
If integer passed, the distances will be calculated from this index
@@ -1321,18 +1319,24 @@ def get_lig_dists(self,
if isinstance(ref_ind,str):
if ref_ind == 'metals':
atoms = np.array(self.find_metals())
+ metals = atoms
else:
raise NotImplementedError('Not yet implemented for other keywords - only "metals".')
elif isinstance(ref_ind,bool):
atoms = np.array(self.find_metals())
+ metals = atoms
elif isinstance(ref_ind,(int,float)):
atoms = np.array([int(ref_ind)])
+ metals = np.array(self.find_metals())
elif isinstance(ref_ind,list):
atoms = np.array(ref_ind)
+ metals = np.array(self.find_metals())
elif isinstance(ref_ind,np.ndarray):
atoms = ref_ind
+ metals = np.array(self.find_metals())
elif ref_ind is None:
atoms = []
+ metals = np.array(self.find_metals())
symbols = self.ase_atoms.get_chemical_symbols()
ml_dist_dicts = []
index = 0
@@ -1347,6 +1351,7 @@ def get_lig_dists(self,
for met in atoms:
con_atoms = np.nonzero(self.graph[met])[0]
con_atom_dists = distmat[met][con_atoms]
+ m_visited = False
for j,c in enumerate(con_atoms):
for i,ind_set in enumerate(info_dict['original_lig_inds']):
if c in ind_set: # Find ligand this atom belongs to.
@@ -1362,13 +1367,30 @@ def get_lig_dists(self,
'atom_symbols':'{}-{}'.format(symbols[met],symbols[c])
})
index += 1
+ elif (c in metals) and (c != met) and (not m_visited):
+ m_visited=True
+ ml_dist_dicts.append({
+ 'atom_pair':(met,c),
+ 'bond_type':'explicit_bond',
+ 'smiles':None,
+ 'smiles_index':None,
+ 'distance':con_atom_dists[j],
+ 'sum_cov_radii':io_ptable.rcov1[io_ptable.elements.index(symbols[met])] + \
+ io_ptable.rcov1[io_ptable.elements.index(symbols[c])],
+ 'atom_symbols':'{}-{}'.format(symbols[met],symbols[c])
+ })
+ index += 1
if calc_nonbonded_dists:
- other_close_atoms = np.where(distmat[met] < (np.max(con_atom_dists)+skin))[0]
+ if radius is None:
+ other_close_atoms = np.where(distmat[met] < (np.max(con_atom_dists)+skin))[0]
+ else:
+ other_close_atoms = np.where(distmat[met] < (radius))[0]
other_close_atoms = np.array([x for x in other_close_atoms if x not in (con_atoms.tolist() + \
[int(met)])])
if len(other_close_atoms) > 0:
other_close_atom_dists = distmat[met][other_close_atoms]
for j,c in enumerate(other_close_atoms):
+ m_visited = False
for i,ind_set in enumerate(info_dict['original_lig_inds']):
if c in ind_set: # Find ligand this atom belongs to.
ind_in_ligand = np.where(ind_set == c)[0][0]
@@ -1383,4 +1405,17 @@ def get_lig_dists(self,
'atom_symbols':'{}-{}'.format(symbols[met],symbols[c])
})
index += 1
+ elif (c in metals) and (c != met) and (not m_visited):
+ m_visited=True
+ ml_dist_dicts.append({
+ 'atom_pair':(met,c),
+ 'bond_type':'implicit_bond',
+ 'smiles':None,
+ 'smiles_index':None,
+ 'distance':other_close_atom_dists[j],
+ 'sum_cov_radii':io_ptable.rcov1[io_ptable.elements.index(symbols[met])] + \
+ io_ptable.rcov1[io_ptable.elements.index(symbols[c])],
+ 'atom_symbols':'{}-{}'.format(symbols[met],symbols[c])
+ })
+ index += 1
return pd.DataFrame(ml_dist_dicts)
diff --git a/architector/io_obabel.py b/architector/io_obabel.py
index 3ff33d0..571ae3e 100644
--- a/architector/io_obabel.py
+++ b/architector/io_obabel.py
@@ -986,7 +986,8 @@ def obmol_lig_split(mol2string,
if calc_all:
for i,lig_obmol in enumerate(lig_obmols):
_,anums,_ = get_OBMol_coords_anums_graph(lig_obmol,get_types=False)
- lig_coord_ats.append(','.join([io_ptable.elements[x] for x in np.array(anums)[np.array(coord_atom_lists[i])]]))
+ if len(coord_atom_lists[i]) > 0:
+ lig_coord_ats.append(','.join([io_ptable.elements[x] for x in np.array(anums)[np.array(coord_atom_lists[i])]]))
info_dict['lig_coord_ats'] = lig_coord_ats
info_dict['original_lig_inds'] = ligs_inds
info_dict['mapped_smiles_inds'] = mapped_smiles_inds
diff --git a/architector/visualization.py b/architector/visualization.py
index cfe47b2..d1e9d69 100644
--- a/architector/visualization.py
+++ b/architector/visualization.py
@@ -58,11 +58,13 @@ def type_convert(structures):
def add_bonds(view_ats,
mol,
labelsize=12,
- distradius=0.3,
+ distvisradius=0.3,
distcolor='black',
distskin=0.3,
distopacity=0.85,
- vis_distances=None,
+ vis_distances=None,
+ distradius=None,
+ distlabelposit=1.0,
viewer=None):
"""Add bonds to visualization displayer?
@@ -80,7 +82,7 @@ def add_bonds(view_ats,
vis_distances='metals' will do the same
vis_distances=0 will add arrows and distance labels from the atom 0 to nearby atoms.
vis_distances=[0,1] will add arrows and distances labesl from both atoms 0 and 1 to nearby atoms.
- distradius : float,
+ distvisradius : float,
radius of drawn distance vectors, by default 0.3
distcolor : str,
color of drawn distance vectors, by default 'black'
@@ -88,14 +90,30 @@ def add_bonds(view_ats,
opacity (from 0(transparent) to 1(no transparent) for drawn distance vectors, by default 0.85
distskin : float,
skin around given atom to flag "nearby" neighbors, by default 0.3
+ distradius : float,
+ Radius around a given atom to flag "nearby" neighbors, by default None.
+ distlabelposit : float,
+ Fraction of the distance (towards the ending atom) that the distance label should be placed, by default 1.0
viewer : None,
which viewer to add the arrows to, by default None
"""
if vis_distances is not None:
bondsdf = mol.get_lig_dists(calc_nonbonded_dists=True,
skin=distskin,
- ref_ind=vis_distances)
+ ref_ind=vis_distances,
+ radius=distradius)
+ visited = list()
+ count = 0
for i,row in bondsdf.iterrows():
+ # Allow for multiple different colors of interatomic distances.
+ if (row['atom_pair'][0] in visited) and (isinstance(distcolor,(list,np.ndarray))):
+ tcolor = distcolor[visited.index(row['atom_pair'][0])]
+ elif (isinstance(distcolor,(list,np.ndarray))):
+ tcolor = distcolor[count]
+ visited.append(row['atom_pair'][0])
+ count += 1
+ else:
+ tcolor = distcolor
starting = mol.ase_atoms.get_positions()[row['atom_pair'][0]] # Should be metal.
ending = mol.ase_atoms.get_positions()[row['atom_pair'][1]]
sx= starting[0]
@@ -104,19 +122,21 @@ def add_bonds(view_ats,
ex= ending[0]
ey= ending[1]
ez= ending[2]
+ dxyz = np.array([ex-sx,ey-sy,ez-sz])
vector = {'start': {'x':sx, 'y':sy, 'z':sz}, 'end': {'x':ex, 'y':ey, 'z':ez},
- 'radius':distradius,'color':distcolor,'opacity':distopacity}
+ 'radius':distvisradius,'color':tcolor,'opacity':distopacity}
+ lposit = starting + distlabelposit * dxyz
if viewer is None:
view_ats.addArrow(vector)
- view_ats.addLabel("{0:.2f}".format(row['distance']), {'position':{'x':'{}'.format(ex),
- 'y':'{}'.format(ey),'z':'{}'.format(ez)},
+ view_ats.addLabel("{0:.2f}".format(row['distance']), {'position':{'x':'{}'.format(lposit[0]),
+ 'y':'{}'.format(lposit[1]),'z':'{}'.format(lposit[2])},
'backgroundColor':"'black'",'backgroundOpacity':'0.3',
'fontOpacity':'1', 'fontSize':'{}'.format(labelsize),
'fontColor':"white",'inFront':'true'})
else:
view_ats.addArrow(vector,viewer=viewer)
- view_ats.addLabel("{0:.2f}".format(row['distance']), {'position':{'x':'{}'.format(ex),
- 'y':'{}'.format(ey),'z':'{}'.format(ez)},
+ view_ats.addLabel("{0:.2f}".format(row['distance']), {'position':{'x':'{}'.format(lposit[0]),
+ 'y':'{}'.format(lposit[1]),'z':'{}'.format(lposit[2])},
'backgroundColor':"'black'",'backgroundOpacity':'0.3',
'fontOpacity':'1', 'fontSize':'{}'.format(labelsize),
'fontColor':"white",'inFront':'true'},viewer=viewer)
@@ -126,7 +146,8 @@ def add_bonds(view_ats,
def view_structures(structures, w=200, h=200, columns=4, representation='ball_stick', labelsize=12,
labels=False, labelinds=None, vector=None, sphere_scale=0.3, stick_scale=0.25,
metal_scale=0.75, modes=None, trajectory=False, interval=200, vis_distances=None,
- distradius=0.3, distcolor='black', distopacity=0.85, distskin=0.3):
+ distvisradius=0.3, distcolor='black', distopacity=0.85, distskin=0.3, distradius=None,
+ distlabelposit=1.0):
"""view_structures
Jupyter-notebook-based visualization of molecular structures.
@@ -190,14 +211,18 @@ def view_structures(structures, w=200, h=200, columns=4, representation='ball_st
vis_distances='metals' will do the same
vis_distances=0 will add arrows and distance labels from the atom 0 to nearby atoms.
vis_distances=[0,1] will add arrows and distances labesl from both atoms 0 and 1 to nearby atoms.
- distradius : float,
+ distvisradius : float,
radius of drawn distance vectors, by default 0.3
distcolor : str,
color of drawn distance vectors, by default 'black'
distopacity: float,
opacity (from 0(transparent) to 1(no transparent) for drawn distance vectors, by default 0.85
distskin : float,
- skin around given atom to flag "nearby" neighbors, by default 0.3
+ "Skin" on top of sum of cov radii around given atom to flag "nearby" neighbors, by default 0.3
+ distradius : float,
+ Radius around a given atom to flag "nearby" neighbors, by default None.
+ distlabelposit : float,
+ Fraction of the distance (towards the ending atom) that the distance label should be placed, by default 1.0
"""
mols = type_convert(structures)
if len(mols) == 1:
@@ -278,10 +303,12 @@ def view_structures(structures, w=200, h=200, columns=4, representation='ball_st
view_ats.addArrow(vector)
add_bonds(view_ats, mol,
labelsize=labelsize,
- distradius=distradius,
+ distvisradius=distvisradius,
distcolor=distcolor,
distskin=distskin,
distopacity=distopacity,
+ distradius=distradius,
+ distlabelposit=distlabelposit,
vis_distances=vis_distances)
view_ats.zoomTo()
view_ats.show()
@@ -385,10 +412,12 @@ def view_structures(structures, w=200, h=200, columns=4, representation='ball_st
if vector:
view_ats.addArrow(vector, viewer=(x,y))
add_bonds(view_ats, mol,
- distradius=distradius,
+ distvisradius=distvisradius,
distcolor=distcolor,
distskin=distskin,
distopacity=distopacity,
+ distradius=distradius,
+ distlabelposit=distlabelposit,
labelsize=labelsize, vis_distances=vis_distances, viewer=(x,y))
view_ats.zoomTo(viewer=(x,y))
if y+1 < columns: # Fill in columns
@@ -424,10 +453,12 @@ def view_structures(structures, w=200, h=200, columns=4, representation='ball_st
if vector:
view_ats.addArrow(vector)
add_bonds(view_ats, mol,
- distradius=distradius,
+ distvisradius=distvisradius,
distcolor=distcolor,
distskin=distskin,
distopacity=distopacity,
+ distradius=distradius,
+ distlabelposit=distlabelposit,
labelsize=labelsize, vis_distances=vis_distances)
view_ats.zoomTo()
view_ats.animate({'interval':interval,'loop':'forward'}) # Infinite repetition
diff --git a/documentation/tutorials/11-Distance_Analysis.ipynb b/documentation/tutorials/11-Distance_Analysis.ipynb
new file mode 100644
index 0000000..dac867e
--- /dev/null
+++ b/documentation/tutorials/11-Distance_Analysis.ipynb
@@ -0,0 +1,1370 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# 11 - Distance Manipulation and Analysis:\n",
+ "\n",
+ "### In this tutorial we will learn about bond distance manipulation, calculation and visualization routines within Architector.\n",
+ "\n",
+ "**(A)** Generating with fixed metal-ligand bond lengths in an Iron Hexa-aqua complex.\n",
+ "\n",
+ "**(B)** Visualizing tricks for bond lengths.\n",
+ "\n",
+ "**(C)** Tabulating and plotting bond lengths."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Starting from **(A)**: \n",
+ "Importing necessary functions!\n",
+ "\n",
+ "Let's build a reference set of Fe-Hexa-Aqua Structures"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from architector import (build_complex,\n",
+ " view_structures,\n",
+ " convert_io_molecule)\n",
+ "import architector.io_ptable as io_ptable # Contains ligand references"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "inputDict = {'core':{'metal':'Fe','coreCN':6}, #Specify metal coordination number (CN)\n",
+ " 'ligands':['water']*6, # Specify what is filling the coordination environment\n",
+ " 'parameters':{}} # No additional parameters needed for default\n",
+ "fe_h2o_6_out = build_complex(inputDict) # Now just build using the dictionary!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/3dmoljs_load.v0": "
\n
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
\n",
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Now, we can visualize interatomic distances\n",
+ "view_structures(fe_h2o_6_out,vis_distances=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, we can create elonged metal-ligand distances by specifying our own ligand definition"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'smiles': 'O', 'coordList': [0], 'ligType': 'mono'}"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "stretch_water_lig = io_ptable.ligands_dict['water'].copy()\n",
+ "stretch_water_lig"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Now, we can add metal-distance constraints \n",
+ "# These take the form {coordinatng_atom_index:distance}\n",
+ "stretch_water_lig['ca_metal_dist_constraints'] = {0:2.6}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Now, we can build with 2 ligands stretched!\n",
+ "inputDict2 = {'core':{'metal':'Fe','coreCN':6}, #Specify metal coordination number (CN)\n",
+ " 'ligands':['water']*4+[stretch_water_lig]*2, # Specify what is filling the coordination environment\n",
+ " 'parameters':{'relax':True,'return_full_complex_class':True}} # No additional parameters needed for default\n",
+ "fe_h2o_6_out2 = build_complex(inputDict2) # Now just build using the dictionary!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/3dmoljs_load.v0": "
\n
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
\n",
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Note that 2 waters are now fixed at ~2.58 Angstroms while the rest have different bond lengths from above!\n",
+ "view_structures(fe_h2o_6_out2, vis_distances=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Beyond bond lengths shown in (A), we can see more diverse visualizations in **(B)**\n",
+ "\n",
+ "First, we will get the last molecule from (A):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/3dmoljs_load.v0": "
\n
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
\n",
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "view_structures(example_mol,\n",
+ " vis_distances = True, # For vis_distances=True, the metal(s) in a system are selected for calculation.\n",
+ " distradius=4, # By Specifying distradius, it will show all bonds within a cutoff of the requested atom\n",
+ " ) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This looks very crowded! How can we see these individual distances better."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/3dmoljs_load.v0": "
\n
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
\n",
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "view_structures(example_mol,\n",
+ " vis_distances = True, # For vis_distances=True, the metal(s) in a system are selected for calculation.\n",
+ " distradius=4, # By Specifying distradius, it will show all bonds within a cutoff of the requested atom\n",
+ " distvisradius=0.1, # distvisradius makes the bond visualization smaller\n",
+ " distlabelposit=0.9, # distlabelposit shifts the labels a fraction of the bond length (1 closer to end atom, 0 closer to start atom)\n",
+ " distopacity=1, # distopacity shows how transparent/dark the bonds are drawn\n",
+ " labelsize=9, # labelsize changes the text size\n",
+ " representation='sphere', # Will only show spheres instead of bonds.\n",
+ " ) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Looks better, but can we only do metal-ligand distances? No!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/3dmoljs_load.v0": "
\n
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
\n",
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "view_structures(example_mol,\n",
+ " vis_distances = 1, # For vis_distances=int, the atom with index of int is used as the center for plotting distances.\n",
+ " distradius=4, # By Specifying distradius, it will show all bonds within a cutoff of the requested atom\n",
+ " distvisradius=0.1, # distvisradius makes the bond visualization smaller\n",
+ " distlabelposit=0.9, # distlabelposit shifts the labels a fraction of the bond length (1 closer to end atom, 0 closer to start atom)\n",
+ " distopacity=1, # distopacity shows how transparent/dark the bonds are drawn\n",
+ " labelsize=9, # labelsize changes the text size\n",
+ " representation='sphere', # Will only show spheres instead of bonds.\n",
+ " ) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/3dmoljs_load.v0": "
\n
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
\n",
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "view_structures(example_mol,\n",
+ " vis_distances = [1,4], # For vis_distances=list, the atoms with indices of the center(s) are used for plotting distances.\n",
+ " distradius=4, # By Specifying distradius, it will show all bonds within a cutoff of the requested atom\n",
+ " distvisradius=0.1, # distvisradius makes the bond visualization smaller\n",
+ " distlabelposit=0.9, # distlabelposit shifts the labels a fraction of the bond length (1 closer to end atom, 0 closer to start atom)\n",
+ " distopacity=1, # distopacity shows how transparent/dark the bonds are drawn\n",
+ " labelsize=9, # labelsize changes the text size\n",
+ " representation='sphere', # Will only show spheres instead of bonds.\n",
+ " ) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This looks better, and interesting, but distinguishing these can be tricky! Let's do just one more tweak."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/3dmoljs_load.v0": "
\n
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n jupyter labextension install jupyterlab_3dmol
\n
\n",
+ "text/html": [
+ "
\n",
+ "
You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension: \n",
+ " jupyter labextension install jupyterlab_3dmol
\n",
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "view_structures(example_mol,\n",
+ " vis_distances = [1,4], # For vis_distances=list, the atoms with indices of the center(s) are used for plotting distances.\n",
+ " distradius=4, # By Specifying distradius, it will show all bonds within a cutoff of the requested atom\n",
+ " distvisradius=0.1, # distvisradius makes the bond visualization smaller\n",
+ " distlabelposit=0.9, # distlabelposit shifts the labels a fraction of the bond length (1 closer to end atom, 0 closer to start atom)\n",
+ " distopacity=1, # distopacity shows how transparent/dark the bonds are drawn\n",
+ " distcolor=['red','blue'], # If a list of color labels is passed, they can be manually changed to represent the indices selected in the vis_distances list!\n",
+ " labelsize=9, # labelsize changes the text size\n",
+ " representation='sphere', # Will only show spheres instead of bonds.\n",
+ " ) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Beyond visualizations in (B), we may want to simply plot or tabulate the bond distances.\n",
+ "## This leads us to **(C)**:\n",
+ "\n",
+ "Here, we can immediately obtain the bond distances in a table with one call from Architector!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
atom_pair
\n",
+ "
bond_type
\n",
+ "
smiles
\n",
+ "
smiles_index
\n",
+ "
distance
\n",
+ "
sum_cov_radii
\n",
+ "
atom_symbols
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
(0, 1)
\n",
+ "
explicit_bond
\n",
+ "
O
\n",
+ "
0
\n",
+ "
2.584900
\n",
+ "
1.79
\n",
+ "
Fe-O
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
(0, 4)
\n",
+ "
explicit_bond
\n",
+ "
O
\n",
+ "
0
\n",
+ "
2.584900
\n",
+ "
1.79
\n",
+ "
Fe-O
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
(0, 7)
\n",
+ "
explicit_bond
\n",
+ "
O
\n",
+ "
0
\n",
+ "
2.405006
\n",
+ "
1.79
\n",
+ "
Fe-O
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
(0, 10)
\n",
+ "
explicit_bond
\n",
+ "
O
\n",
+ "
0
\n",
+ "
2.405006
\n",
+ "
1.79
\n",
+ "
Fe-O
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
(0, 13)
\n",
+ "
explicit_bond
\n",
+ "
O
\n",
+ "
0
\n",
+ "
2.402900
\n",
+ "
1.79
\n",
+ "
Fe-O
\n",
+ "
\n",
+ "
\n",
+ "
5
\n",
+ "
(0, 16)
\n",
+ "
explicit_bond
\n",
+ "
O
\n",
+ "
0
\n",
+ "
2.402800
\n",
+ "
1.79
\n",
+ "
Fe-O
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " atom_pair bond_type smiles smiles_index distance sum_cov_radii \\\n",
+ "0 (0, 1) explicit_bond O 0 2.584900 1.79 \n",
+ "1 (0, 4) explicit_bond O 0 2.584900 1.79 \n",
+ "2 (0, 7) explicit_bond O 0 2.405006 1.79 \n",
+ "3 (0, 10) explicit_bond O 0 2.405006 1.79 \n",
+ "4 (0, 13) explicit_bond O 0 2.402900 1.79 \n",
+ "5 (0, 16) explicit_bond O 0 2.402800 1.79 \n",
+ "\n",
+ " atom_symbols \n",
+ "0 Fe-O \n",
+ "1 Fe-O \n",
+ "2 Fe-O \n",
+ "3 Fe-O \n",
+ "4 Fe-O \n",
+ "5 Fe-O "
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df = example_mol.get_lig_dists()\n",
+ "df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that the atom_pair corresponds to the distances, and all ofhter information including the sum of covalent radii in angstroms are included in the table.\n",
+ "\n",
+ "Further, note that for ligands, both the smiles string AND the index of the atom in the smiles string are computed.\n",
+ "\n",
+ "We might be more interested in longer distances as before and not use the metal as the index."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
atom_pair
\n",
+ "
bond_type
\n",
+ "
smiles
\n",
+ "
smiles_index
\n",
+ "
distance
\n",
+ "
sum_cov_radii
\n",
+ "
atom_symbols
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
(2, 1)
\n",
+ "
explicit_bond
\n",
+ "
O
\n",
+ "
0.0
\n",
+ "
0.960857
\n",
+ "
0.95
\n",
+ "
H-O
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
(2, 0)
\n",
+ "
implicit_bond
\n",
+ "
None
\n",
+ "
NaN
\n",
+ "
3.250759
\n",
+ "
1.48
\n",
+ "
H-Fe
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
(2, 3)
\n",
+ "
implicit_bond
\n",
+ "
O
\n",
+ "
2.0
\n",
+ "
1.543559
\n",
+ "
0.64
\n",
+ "
H-H
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
(2, 10)
\n",
+ "
implicit_bond
\n",
+ "
O
\n",
+ "
0.0
\n",
+ "
3.629175
\n",
+ "
0.95
\n",
+ "
H-O
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
(2, 13)
\n",
+ "
implicit_bond
\n",
+ "
O
\n",
+ "
0.0
\n",
+ "
3.790972
\n",
+ "
0.95
\n",
+ "
H-O
\n",
+ "
\n",
+ "
\n",
+ "
5
\n",
+ "
(2, 15)
\n",
+ "
implicit_bond
\n",
+ "
O
\n",
+ "
2.0
\n",
+ "
3.987336
\n",
+ "
0.64
\n",
+ "
H-H
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " atom_pair bond_type smiles smiles_index distance sum_cov_radii \\\n",
+ "0 (2, 1) explicit_bond O 0.0 0.960857 0.95 \n",
+ "1 (2, 0) implicit_bond None NaN 3.250759 1.48 \n",
+ "2 (2, 3) implicit_bond O 2.0 1.543559 0.64 \n",
+ "3 (2, 10) implicit_bond O 0.0 3.629175 0.95 \n",
+ "4 (2, 13) implicit_bond O 0.0 3.790972 0.95 \n",
+ "5 (2, 15) implicit_bond O 2.0 3.987336 0.64 \n",
+ "\n",
+ " atom_symbols \n",
+ "0 H-O \n",
+ "1 H-Fe \n",
+ "2 H-H \n",
+ "3 H-O \n",
+ "4 H-O \n",
+ "5 H-H "
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df = example_mol.get_lig_dists(radius=4,ref_ind=2)\n",
+ "df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that the bond_type and atom_symbols for the Fe-H bonds have been flagged as \"implicit_bonds\". Because they were not included in the graph.\n",
+ "\n",
+ "Now, pandas/matplotlib can be used to generate nice plots illustrating the distance distributions!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Text(0.5, 1.0, 'Distance Plot')"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHJCAYAAABtzYa7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABFNklEQVR4nO3deXxU5d3///cQsrAlQCALBcK+BBTCIoQ9BYKgiFYK7X0bFkFFsGxSNVirSL9GVCDsSItEbu8C1gi4ABLKqqS0wQSrIptoECaNICQQJCHJ9fvDm/k5ZB8SJsl5PR+P83hwrrnOOZ/rnHmYt9ecM2MzxhgBAABYSA13FwAAAHC7EYAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAFCkuLk42m82x+Pj4KCgoSBEREYqJiVF6enqBbV544QXZbLYyHefq1at64YUXtHfv3nKq3P1+ft5sNpv8/Pw0aNAgffjhh079WrRooQkTJpR5/9XxnAG3EwEIQInWrVunxMREJSQkaMWKFeratasWLFigjh07ateuXU59J0+erMTExDLt/+rVq5o3b161+2M+evRoJSYm6pNPPtGKFSuUlpamkSNHFghBrqiu5wy4XWq6uwAAlV/nzp3Vo0cPx/qDDz6oWbNmqV+/fvrVr36lEydOKDAwUJLUtGlTNW3a1F2lViqBgYHq3bu3JKlPnz4KDw9XmzZtFBsbq3vuucfN1QHWxgwQAJc0b95cCxcu1OXLl/X666872gv7CGz37t0aNGiQ/P39VatWLTVv3lwPPvigrl69qm+++UaNGzeWJM2bN8/xkdGNj4VOnjypiRMnqm3btqpdu7Z+8YtfaOTIkfr3v//tdIy9e/fKZrNpw4YNevbZZ9WkSRP5+vpqyJAhOnbsWIH6d+zYocGDB8vPz0+1a9dWx44dFRMT49QnKSlJ9913nxo2bCgfHx+FhYXp7bffdvmctW7dWo0bN9a3335bbL/U1FQ99NBDCggIkLe3tzp27KiFCxcqPz9fkko8ZwBKxgwQAJeNGDFCHh4e2r9/f5F9vvnmG91zzz3q37+/3njjDdWvX19nz57Vjh07lJOTo+DgYO3YsUN33323Jk2apMmTJ0uS4w/8uXPn5O/vr5dfflmNGzfWDz/8oDfffFO9evVScnKy2rdv73S8uXPnqm/fvvrLX/6izMxMPf300xo5cqSOHj0qDw8PSdLatWv1yCOPaODAgVq9erUCAgJ0/Phxff7554797NmzR3fffbd69eql1atXy8/PTxs3btTYsWN19epVl8LGxYsXdeHCBbVt27bIPt9//7369OmjnJwczZ8/Xy1atNAHH3ygOXPm6NSpU1q5cmWJ5wxAKRgAKMK6deuMJPOvf/2ryD6BgYGmY8eOjvXnn3/e/Pw/Le+8846RZFJSUorcx/fff28kmeeff77EmnJzc01OTo5p27atmTVrlqN9z549RpIZMWKEU/+3337bSDKJiYnGGGMuX75sfH19Tb9+/Ux+fn6Rx+nQoYMJCwsz169fd2q/9957TXBwsMnLyyu2Tklm6tSp5vr16yYnJ8ccPXrUDB8+3EgyK1ascPQLCQkx48ePd6w/88wzRpI5dOiQ0/4ef/xxY7PZzLFjx4wxZTtnAAriIzAAt8QYU+zrXbt2lZeXlx599FG9+eab+vrrr8u0/9zcXL300ksKDQ2Vl5eXatasKS8vL504cUJHjx4t0P++++5zWr/zzjslyfGx08GDB5WZmampU6cW+bTayZMn9dVXX+m///u/HTXcWEaMGCG73V7ox2o3W7lypTw9PeXl5aWOHTvq4MGDevHFFzV16tQit9m9e7dCQ0N11113ObVPmDBBxhjt3r27xOMCKBkBCIDLsrKydOHCBTVp0qTIPq1bt9auXbsUEBCgadOmqXXr1mrdurWWLFlSqmPMnj1bzz33nO6//369//77OnTokP71r3+pS5cu+vHHHwv09/f3d1r39vaWJEff77//XpKKvVH7P//5jyRpzpw58vT0dFpuhJfz58+XWPuYMWP0r3/9S0lJSTp27JguXLig5557rthtLly4oODg4ALtN87xhQsXSjwugJJxDxAAl3344YfKy8vToEGDiu3Xv39/9e/fX3l5eUpKStKyZcs0c+ZMBQYG6je/+U2x27711lsaN26cXnrpJaf28+fPq379+mWu+cZ9Mt99912RfRo1aiRJio6O1q9+9atC+9x871FRx/r503Ol4e/vL7vdXqD93LlzTrUBuDXMAAFwSWpqqubMmSM/Pz899thjpdrGw8NDvXr10ooVKyRJn376qaSCszQ/Z7PZHK/f8OGHH+rs2bMu1d2nTx/5+flp9erVRX581759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umvGjBkaMGCAFixYoFGjRmnr1q3atWtXqT5es7rCQk5xunXrpvj4eLVo0UI1a1aKCUUAAErk1pugV61apYyMDA0aNEjBwcGOZdOmTY4+drtdqampjvWWLVtq27Zt2rt3r7p27ar58+dr6dKljkfgJalPnz7auHGj1q1bpzvvvFNxcXHatGlThX4HkFVNmzZNP/zwg37729/qn//8p77++mvt3LlTDz/8cKnuLQIAwB3c/hFYSeLi4gq0DRw40Ol+lMKMHj1ao0ePdrU0lFKTJk30ySef6Omnn9awYcOUnZ2tkJAQ3X333Y77hAAAqGxspjQpxGIyMzPl5+enjIwMp/tgJOnatWs6ffq0WrZsKR8fHzdVWLVxDgFYHU+BVcxTYMX9/b4Z/4sOAAAshwAEAAAshwAEAAAshwAEAAAshwAEAAAshwAEAAAshwAEAAAshwAEAAAshwAEAAAshwAEAAAsh5/vLk97Ym7v8SKiy9R9woQJunTpkrZs2eLUvnfvXkVEROjixYuqX79+ge3i4uI0c+ZMXbp0qcBrNptNmzdv1v3331+mWgAAcCdmgAAAgOUQgAAAgOUQgAAAgOVwD5DFfPDBB6pbt65TW15eXonbZWRkFNgOAICqigBkMREREVq1apVT26FDh/TQQw9JklPIeeihh7R69WpJUr169fTpp58W2F/btm0rsFoAACoGAchi6tSpozZt2ji1fffdd45/p6SkOP7t6+vr+HeNGjUKbAcAQFVFAIITQg4AwAq4CRoAAFgOAQgAAFgOH4GVpzJ+M/PtFhcXV2j7oEGDZIwpcrsJEyZowoQJhb5W3HYAAFRWzAABAADLIQABAADLIQABAADLIQABAADLIQABAADLIQABAADLIQABAADLIQABAADLIQABAADLIQABAADL4acwytHKlJW39XhTu04tU/8JEybo0qVL2rJli1P73r17FRERoYsXL6p+/fpFbv/BBx/otdde0+HDh5WXl6dOnTpp2rRpRf5MBgAAlZVbZ4D279+vkSNHqkmTJrLZbAX+MN9swoQJstlsBZZOnTo5+sTFxRXa59q1axU8mupt2bJlGjVqlPr06aNDhw7ps88+029+8xtNmTJFc+bMcXd5AACUiVtngLKystSlSxdNnDhRDz74YIn9lyxZopdfftmxnpubqy5duujXv/61Uz9fX18dO3bMqc3Hx6d8iragM2fO6Mknn9TMmTP10ksvOdqffPJJeXl5afr06fr1r3+tXr16ubFKAABKz60BaPjw4Ro+fHip+/v5+cnPz8+xvmXLFl28eFETJ0506mez2RQUFFRudVrdO++8o+vXrxc60/PYY49p7ty52rBhAwEIAFBlVOl7gNauXashQ4YoJCTEqf3KlSsKCQlRXl6eunbtqvnz5yssLMxNVVYuH3zwgerWrevUlpeXV+w2x48fl5+fn4KDgwu85uXlpVatWun48ePlWicAABWpyj4FZrfbtX37dk2ePNmpvUOHDoqLi9N7772nDRs2yMfHR3379tWJEyeK3Fd2drYyMzOdluoqIiJCKSkpTstf/vIXx+t169Z1LFOmTCnVPo0xstlsFVUyAADlrsrOAMXFxal+/fq6//77ndp79+6t3r17O9b79u2rbt26admyZVq6dGmh+4qJidG8efMqstxKo06dOmrTpo1T23fffef4d0pKiuPfvr6+kqR27dopIyND586dU5MmTZy2zcnJ0ddff61f/vKXFVc0AADlrErOABlj9MYbbygqKkpeXl7F9q1Ro4Z69uxZ7AxQdHS0MjIyHMuZM2fKu+Qqo02bNo4lICBAkvTggw+qZs2aWrhwYYH+q1evVlZWln7729/e7lIBAHBZlZwB2rdvn06ePKlJkyaV2NcYo5SUFN1xxx1F9vH29pa3t3d5llitNG/eXK+88ormzJkjHx8fRUVFydPTU1u3btXcuXP15JNPcgM0AKBKcWsAunLlik6ePOlYP336tFJSUtSwYUM1b95c0dHROnv2rNavX++03dq1a9WrVy917ty5wD7nzZun3r17q23btsrMzNTSpUuVkpKiFStWVPh4qrNZs2apdevWeu2117RkyRLHFyGuWrWqwFN4AABUdm4NQElJSYqIiHCsz549W5I0fvx4xcXFyW63KzU11WmbjIwMxcfHa8mSJYXu89KlS3r00UeVlpYmPz8/hYWFaf/+/brrrrsqbiD/p6zfzHy7xcXFFdo+aNAgGWNK3P6+++7TfffdV85VAQBw+7k1AJX0h7ewP9h+fn66evVqkdssXrxYixcvLo/yAABANVUlb4IGAAC4FQQgAABgOQQgAABgOQQgF5XmpmEUjnMHAHA3AlAZeXp6SlKxN2KjeDfO3Y1zCQDA7VYlvwjRnTw8PFS/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iXGEH0lq0KCB/vSnP5XpKbCkpCSFhYUpLCxM0k8frYWFhemPf/yjJMlutys1NdXRv2XLltq2bZv27t2rrl27av78+Vq6dKkefPBBR58+ffpo48aNWrdune68807FxcVp06ZN6tWrlytDBQAA1ZBLM0CZmZn6z3/+o06dOjm1p6en6/Lly6Xez6BBg1Tc1xDFxcUVaBs4cKA+/fTTYvc7evRojR49utR1AAAAa3FpBuiBBx7QxIkT9c477+i7777Td999p3feeUeTJk3Sr371q/KuEQAAoFy5NAO0evVqzZkzRw899JCuX7/+045q1tSkSZP06quvlmuBAAAA5c2lAFS7dm2tXLlSr776qk6dOiVjjNq0aaM6deqUd30AAADl7pZ+DNVut8tut6tdu3aqU6dOsffzAAAAVBYuBaALFy5o8ODBateunUaMGCG73S5Jmjx5cqkfgQcAAHAXlwLQrFmz5OnpqdTUVNWuXdvRPnbsWO3YsaPcigMAAKgILt0DtHPnTn300Udq2rSpU3vbtm317bfflkthAAAAFcWlGaCsrCynmZ8bzp8/z09KAACASs+lADRgwADHz1NIP/3mV35+vl599VVFRESUW3EAAAAVwaWPwF599VUNGjRISUlJysnJ0VNPPaUvvvhCP/zwgz755JPyrhEAAKBcuTQDFBoaqs8++0x33XWXhg4dqqysLP3qV79ScnKyWrduXd41AgAAlKsyzwBdv35dkZGRev311zVv3ryKqAkAAKBClXkGyNPTU59//rlsNltF1AMAAFDhXPoIbNy4cVq7dm151wIAAHBbuHQTdE5Ojv7yl78oISFBPXr0KPAbYIsWLSqX4gAAACpCmQLQ119/rRYtWujzzz9Xt27dJEnHjx936sNHYwAAoLIrUwBq27at7Ha79uzZI+mnn75YunSpAgMDK6Q4AACAilCme4Bu/rX37du3Kysrq1wLAgAAqGgu3QR9w82BCAAAoCooUwCy2WwF7vHhnh8AAFDVlOkeIGOMJkyY4PjB02vXrmnKlCkFngJ79913y69CAACAclamADR+/Hin9YceeqhciwEAALgdyhSA1q1bV1F1AAAA3Da3dBM0AABAVUQAAgAAlkMAAgAAlkMAAgAAlkMAAgAAlkMAAgAAlkMAAgAAlkMAAgAAlkMAAgAAlkMAAgAAluP2ALRy5Uq1bNlS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",
+ "text/plain": [
+ "