<jupyter_start><jupyter_text>Enabling and testing the GPUFirst, you'll need to enable GPUs for the notebook:- Navigate to Edit→Notebook Settings- select GPU from the Hardware Accelerator drop-down  Analyzing the COVID-19 Moonshot datasetFirst let's declare the GCP project we'll be accessing and authenticate with Google for access to the project.<jupyter_code>project_id = 'cmb-covid19-discovery-collab'
from google.colab import auth
auth.authenticate_user()
print('Authenticated')<jupyter_output>Authenticated<jupyter_text>Download a recent version of the Postera/Diamond Mpro structures<jupyter_code>!wget https://fragalysis.diamond.ac.uk/media/targets/Mpro.zip
!mkdir -p Mpro
!cd Mpro; unzip ../Mpro.zip
import glob
import os

structures = glob.glob('Mpro/aligned/*')
receptor_files = [f + "/" + os.path.basename(f) + "_apo-desolv.pdb" for f in structures]
ligand_files = [f + "/" + os.path.basename(f) + ".pdb" for f in structures]<jupyter_output><empty_output><jupyter_text>Contacts calculationInstall the GPU kernel to calculate atom-atom contacts between the ligand and the protein. *Needs to be done first before the other installation of the other software packages.*<jupyter_code>%%time
!pip install git+https://github.com/jglaser/contacts.git@add_python<jupyter_output>Collecting git+https://github.com/jglaser/contacts.git@add_python
  Cloning https://github.com/jglaser/contacts.git (to revision add_python) to /tmp/pip-req-build-t99g0x41
  Running command git clone -q https://github.com/jglaser/contacts.git /tmp/pip-req-build-t99g0x41
  Running command git checkout -b add_python --track origin/add_python
  Switched to a new branch 'add_python'
  Branch 'add_python' set up to track remote branch 'add_python' from 'origin'.
Building wheels for collected packages: contacts
  Building wheel for contacts (setup.py) ... [?25l[?25hdone
  Created wheel for contacts: filename=contacts-0.1-cp36-none-any.whl size=2826 sha256=c4727ad5f54c9450e928ca4751356c6912e25de74cf07625b36b8284121b2dc7
  Stored in directory: /tmp/pip-ephem-wheel-cache-hhnudrwc/wheels/f4/53/e4/6f7d288276128c34fc4ef29378b0dca24349ef9fdd0151d1c1
Successfully built contacts
Installing collected packages: contacts
Successfully installed contacts-0.1
CPU times: user 38.4 ms, sys: 11.9 ms, total:[...]<jupyter_text>Install some cheminformatics software packages (takes a few minutes)<jupyter_code># see also https://colab.research.google.com/drive/12pdvG99aij2put7o_QX-aw3MkRWdZa2g#scrollTo=y2IaxgQ5hAZx
!wget -c https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
!chmod +x Miniconda3-latest-Linux-x86_64.sh
!time bash ./Miniconda3-latest-Linux-x86_64.sh -b -f -p /usr/local
!time conda create -y -n py37 python=3.7 
!time conda install -n py37 -q -y -c conda-forge rdkit
!time conda install -n py37 -q -y -c conda-forge openbabel oddt
!time pip install py3dmol
import sys
import os
sys.path.append('/usr/local/envs/py37/lib/python3.7/site-packages/')
os.environ['PATH'] = '/usr/local/envs/py37/bin:' + os.environ['PATH']<jupyter_output>--2021-02-17 20:47:41--  https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
Resolving repo.continuum.io (repo.continuum.io)... 172.16.31.10, 172.16.17.32, fd00:a516:7c1b:17cd:6d81:2137:bd2a:2c5b:c94f, ...
Connecting to repo.continuum.io (repo.continuum.io)|172.16.31.10|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh [following]
--2021-02-17 20:47:41--  https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
Resolving repo.anaconda.com (repo.anaconda.com)... 172.16.31.10, 172.16.31.10, fd00:a516:7c1b:17cd:6d81:2137:bd2a:2c5b:8203, ...
Connecting to repo.anaconda.com (repo.anaconda.com)|172.16.31.10|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 94235922 (90M) [application/x-sh]
Saving to: ‘Miniconda3-latest-Linux-x86_64.sh’

Miniconda3-latest-L 100%[===================>]  89.87M   201MB/s    in 0.4s    

2021-02-17 20:47:42 (201 MB/s) - ‘Miniconda3-latest-Linux[...]<jupyter_text>ScoringHere we calculate the rf-score-3, a random forest-based scoring function, on the protein-ligand complexes. There's an easy (but slow) way to do it by just using pre-compiled binaries, and a more generalizable one, which however requires training the scoring function first. In this notebook, we'll choose the easy way. First, download the binary<jupyter_code>!wget http://ballester.marseille.inserm.fr/rf-score-3.tgz
!tar xvfz rf-score-3.tgz<jupyter_output>--2021-02-15 04:52:42--  http://ballester.marseille.inserm.fr/rf-score-3.tgz
Resolving ballester.marseille.inserm.fr (ballester.marseille.inserm.fr)... 192.168.127.12
Connecting to ballester.marseille.inserm.fr (ballester.marseille.inserm.fr)|192.168.127.12|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 33347743 (32M) [application/x-gzip]
Saving to: ‘rf-score-3.tgz’

rf-score-3.tgz      100%[===================>]  31.80M  8.13MB/s    in 5.9s    

2021-02-15 04:52:48 (5.35 MB/s) - ‘rf-score-3.tgz’ saved [33347743/33347743]

rf-score-3/
rf-score-3/rf-score
rf-score-3/receptor.pdbqt
rf-score-3/ligand.pdbqt
rf-score-3/pdbbind-2015-refined.rf
rf-score-3/pdbbind-2013-refined.rf
rf-score-3/README.md
rf-score-3/rf-score.exe
rf-score-3/pdbbind-2014-refined.rf<jupyter_text>Now let's apply this scoring function to the experimental data!<jupyter_code>from tqdm.notebook import tqdm
import subprocess

rf3_scores = []
for l,r in tqdm(list(zip(ligand_files, receptor_files))):
  try:
    rf3_scores.append(float(subprocess.check_output(['./rf-score-3/rf-score','rf-score-3/pdbbind-2015-refined.rf',
                                  r,
                                  l,
                                  #pdbqt_dir+'/'+os.path.basename(r).split('.')[0]+'.pdbqt',
                                  #pdbqt_dir+'/'+os.path.basename(l).split('.')[0]+'.pdbqt'
                                  ])))
  except:
    rf3_scores.append(None)
# store these results in a DataFrame
import pandas as pd
df_diamond = pd.DataFrame.from_dict({
    'id': [os.path.basename(l).split('.')[0] for l in ligand_files],
    'rf3': rf3_scores
})
# plot the distribution of scores
df_diamond.rf3.hist(bins=50)<jupyter_output><empty_output><jupyter_text>Compute the enrichment factor based on experimental affinities We'll calculate the how many active compounds can be found in the top x% of the data according to RFscore3. Activities are available (only) for a subset of the crystal structures<jupyter_code># load the activity data
import pandas as pd
activities_diamond = pd.read_csv('Mpro/Mpro_compound_tracker_csv.csv')
activities_diamond.columns<jupyter_output><empty_output><jupyter_text>Let's look at the distribution of activities (pIC50 values)<jupyter_code>import numpy as np
activities_diamond['f_pIC50'] = 6 - np.log(activities_diamond['Fluorescence avg IC50 (uM)'])
activities_diamond.f_pIC50.hist(bins=50)
df_diamond['Dataset'] = [os.path.basename(l).split('.')[0].split('_')[0]  for l in ligand_files]
# merge the two dataframes
scores_activities = df_diamond.merge(activities_diamond,on='Dataset',how='inner') 
# plot the cross correlation
scores_activities.plot.scatter(x='rf3',y='f_pIC50')
import numpy as np
import matplotlib.pyplot as plt
# compute the enrichment
def enrichment_factor(y_true, y_score, percentage=1):
    sample = y_true[y_score>=np.quantile(y_score, 1-percentage/100)]
    return 100*np.count_nonzero(sample)/len(sample)

x = np.linspace(.2,10,100)

fig, ax = plt.subplots()
c = 'Fluorescence avg IC50 (uM)'
df_select = scores_activities[scores_activities[c].notnull()]
y_true = df_select[c] <= 1
y = [enrichment_factor(y_true, df_select.rf3, percentage=p) for p in x]
ax.plot(x,y,label='Fluorescence IC50 $<$ 1$\mu M$ ($N={}$)'.format(len(df_select)))

# c = 'Fluorescence inhibition at 50 uM'
# df_select = scores_activities[scores_activities[c].notnull()]
# y_true = df_select[c] >= 50
# y = [enrichment_factor(y_true, df_select.rf3, percentage=p) for p in x]
# ax.plot(x,y,label='Flourescence inhibition at 50uM >= 50% ($N={}$)'.format(len(df_select)))

ax.legend(frameon=False,title='rf-score-3')
ax.set_xlabel('percentage',fontsize=16)
ax.set_ylabel('Enrichment factor EF$_{x \%}$',fontsize=16)
fig.show()<jupyter_output><empty_output><jupyter_text>Match activities with Enamine REAL docked compounds Here we'll match the activities from Moonshot with the scores of compounds in the Gigadocking dataset, for those compounds that are present in the Enamine REAL database<jupyter_code># download the dataset
!wget https://covid.postera.ai/covid/submissions.csv
!wget https://covid.postera.ai/covid/activity_data.csv
submissions = pd.read_csv('submissions.csv',sep=',')
submissions.columns
activities = pd.read_csv('activity_data.csv',sep=',')
activities.columns
df = activities.merge(submissions[['CID (canonical)','Enamine - REAL Space']],
                      left_on='canonical_CID',right_on='CID (canonical)',how='inner')
df = df.drop(columns=['CID (canonical)'],axis=1) # remove invalid/duplicate column names
df = df.rename(columns={'Enamine - REAL Space': 'enamine_id'})

(df['enamine_id'] != 'False').sum()<jupyter_output><empty_output><jupyter_text>Merge the Moonshot data with the Gigadocking results*Note: Uploading the Moonshot data will only be possible with if you have permission to edit the BQ tables.*You can however proceed by skipping this step, assuming the data has already been uploaded by someone else :)<jupyter_code>%load_ext google.colab.data_table
# this will only work with the right privileges
df.to_gbq('moonshot.activities',project_id=project_id,if_exists='replace')
%%bigquery df_merge --project $project_id
SELECT * FROM
  (SELECT *, SPLIT(name,"_")[OFFSET(0)] AS name_first
   FROM mpro_5r84.scores_rank0
  ) AS A
INNER JOIN moonshot.activities
ON A.name_first=activities.enamine_id
df_merge.head()
df_merge['f_avg_pIC50'].hist()
df_merge.plot.scatter(x='rf3',y='f_inhibition_at_20_uM')
## Try with raw AutodockGPU scores
%%bigquery df_autodock --project $project_id
SELECT * FROM
  (SELECT *, SPLIT(name,"_")[OFFSET(0)] AS name_first
   FROM mpro_5r84.scores
   WHERE rank = 0
  ) AS A
INNER JOIN moonshot.activities
ON A.name_first=activities.enamine_id
df_autodock.plot.scatter(x='score',y='f_inhibition_at_20_uM')
# plot the enrichment
x = np.linspace(.2,10,100)

fig, ax = plt.subplots()

c = 'f_inhibition_at_20_uM'
df_select = df_merge[df_merge[c].notnull()]
y_true = df_select[c] >= 50
y = [enrichment_factor(y_true, df_select.rf3, percentage=p) for p in x]
ax.plot(x,y,label='rf-score-3')

c = 'f_inhibition_at_20_uM'
df_select = df_merge[df_merge[c].notnull()]
y_true = df_select[c] >= 50
y = [enrichment_factor(y_true, df_select.vs_dude_v2, percentage=p) for p in x]
ax.plot(x,y,label='VS DUD-E v2')

ax.legend(frameon=False,title='Fluorescence inhibition at 20uM >= 50% ($N={}$)'.format(len(df_select)))
ax.set_xlabel('percentage',fontsize=16)
ax.set_ylabel('Enrichment factor EF$_{x \%}$',fontsize=16)
fig.show()<jupyter_output><empty_output><jupyter_text>*Here's another idea* .. .. correlate docked scores with scores from crystal structures<jupyter_code>df = scores_activities[['Compound ID','rf3','Dataset','Fluorescence avg IC50 (uM)']].merge(submissions[['CID (canonical)','Enamine - REAL Space']],
                      left_on='Compound ID',right_on='CID (canonical)',how='inner')
df = df.drop(columns=['Compound ID'], axis=1)
df = df.rename(columns={'CID (canonical)': 'CID_canonical',
                        'Enamine - REAL Space': 'enamine_id',
                        'Fluorescence avg IC50 (uM)': 'f_avg_IC50'})
df['f_pIC50'] = 6 - np.log(df['f_avg_IC50'])
# again, this will only work with the right privileges
df.to_gbq('moonshot.diamond_rescored',project_id=project_id,if_exists='replace')
%%bigquery df_merge --project $project_id
SELECT * FROM
  (SELECT *, SPLIT(name,"_")[OFFSET(0)] AS name_first
   FROM mpro_5r84.scores_rank0
  ) AS A
INNER JOIN moonshot.diamond_rescored
ON A.name_first=diamond_rescored.enamine_id
len(df_merge)<jupyter_output><empty_output><jupyter_text>The cross correlation between docked RF3 scores and crystal RF3 scores, for those crystallized compounds which exist in the Gigadocking dataset. It is indirectly a measure of whether docking can predict the correct pose.<jupyter_code>df_merge.plot.scatter(x='rf3',y='rf3_1')
df_select.columns<jupyter_output><empty_output><jupyter_text>Calculate protein-ligand fingerprintsHere, we'll compute the interaction fingerprint between the Mpro receptors and the ligands from the crystal structures  Convert PDBs to PDBQTs Use `obabel` to convert the ligands (only) from input **PDB** to **PDBQT** file format. We don't recalculate charges, as we don't need them for the fingerprints (which makes the conversion a bit faster...). We also remove the hydrogens.We'll deal with the receptor PDBs later, as their files are larger.<jupyter_code># Store outputs in the subdirectory `pdbqt`.
pdbqt_dir = 'pdbqt'
import subprocess
from tqdm.notebook import tqdm
! mkdir -p $pdbqt_dir
for l,r in tqdm(list(zip(ligand_files, receptor_files))):
  subprocess.run(['obabel',l,'--partialcharge','none','-d','-O',pdbqt_dir+'/'+os.path.basename(l).split('.')[0]+'.pdbqt'])<jupyter_output><jupyter_text>Setup the receptor atom types<jupyter_code># define the residues of interest
residues_of_interest = ['163','166','41','49','54','165','145','142','140', '141', '143', '189', '192','164','187']
# download a receptor structure for Mpro (needs privileges)
from google.cloud import storage
fn = 'final_Mpro_5R84_gast.pdbqt'
gclient = storage.Client(project_id)
bucket = gclient.get_bucket('ccddc')
blob = bucket.get_blob(fn)
blob.download_to_filename(fn)
# define the mapping of unique ligand types and participating receptor atoms
import numpy as np
ligand_map = {'A': 0, 'C': 1, 'N': 2, 'NA': 3, 'O': 4, 'OA': 5, 'F': 6, 'P': 7, 'S': 8, 'SA': 9, 'CL': 10,
              'BR': 11, 'I': 12} # wide version
protein_types =  ['A','C','N','NA','O','OA','S','SA']
nligand = len(np.unique([ligand_map[a] for a in ligand_map]))
# define the receptor atoms of interest based on the protein atom types
conf_receptor = open(fn,'r').read().strip().split('\n')
conf_receptor_atoms = [l for l in conf_receptor if l.startswith('ATOM')]

atoms_of_interest = [int(l[7:11].lstrip())
                           for l in conf_receptor_atoms if l[77:79].strip() in protein_types
                           and l[22:26].lstrip() in residues_of_interest]
atom_names_of_interest = [(l[22:26].lstrip(),l[13:16].rstrip())
                           for l in conf_receptor_atoms if l[77:79].strip() in protein_types
                           and l[22:26].lstrip() in residues_of_interest]<jupyter_output><empty_output><jupyter_text>We'll be using diffferent receptor structures for Mpro, which have the same aminoacid sequence but potentially different numerical atom IDs. Therefore we'll create a dict that maps atom IDs onto receptor residues and atom names, which allows us to look up atoms of interest in the different receptor files.<jupyter_code># store the residue-atom information in a dict for later unmapping with the different receptor PDBQTs
from collections import OrderedDict
res_dict = OrderedDict()
for r, a in atom_names_of_interest:
    if r not in res_dict:
        res_dict[r] = []
    res_dict[r].append(a)<jupyter_output><empty_output><jupyter_text>Parameters that characterize the fingerprint<jupyter_code>nbins = 1 # number of distance bins
binsize = 4 # in A
nfeatures = nligand*len(atoms_of_interest)*nbins
print(nfeatures)<jupyter_output>1690<jupyter_text>Compute the fingerprints, one by oneWe'll compute the fingerprint separately for each protein-ligand pair, using the above mapping of atoms of interests to residues. This calculation uses a naîve loop over pairs, and there is not much to be gained from using the GPU here, except that the code is implemented in CUDA, which is why we'll use it anyway. In the data-parallel version, which is not implemented here, the calculation parses the coordinates for a batch of ligands **for the same target** in parallel. It uses `cudf` to do the (parallel) parsing of the PBDQTs.<jupyter_code>import cupy as cp
import contacts
from tqdm.notebook import tqdm

fingerprints = []

for l,r in tqdm(list(zip(ligand_files, receptor_files))):
  l_pdbqt = pdbqt_dir+'/'+os.path.basename(l).split('.')[0]+'.pdbqt'

  # load receptor atom types
  conf_receptor = open(r,'r').read().strip().split('\n')
  conf_receptor_atoms = [l for l in conf_receptor if l.startswith('ATOM')]

  # map atoms of interest onto this model
  atoms_of_interest = []
  atom_n = {}
  for r in res_dict:
      for a in res_dict[r]:
          for l in conf_receptor_atoms:
              if l[13:16].rstrip() == a and l[23:26].lstrip() == r:
                  
                  if r in atom_n:
                    if a not in atom_n[r]:
                      # some receptors have substitutions with two Calphas, only take the first one
                      atoms_of_interest.append(int(l[7:11].lstrip()))
                      atom_n[r].append(a)
                  else:
                    atoms_of_interest.append(int(l[7:11].lstrip()))
                    atom_n[r] = [a]

  # load receptor coordinates and atom types
  xyz_receptor = cp.array([(float(l[30:38]), float(l[38:46]), float(l[46:54]))
                          for l in conf_receptor_atoms if int(l[7:11].lstrip()) in atoms_of_interest],dtype=cp.float32)
  types_receptor = cp.array([atoms_of_interest.index(int(l[7:11].lstrip()))
                            for l in conf_receptor_atoms if int(l[7:11].lstrip()) in atoms_of_interest],dtype=cp.int32)

  xyz_receptor_T = xyz_receptor.T.copy()
  x_receptor, y_receptor, z_receptor = xyz_receptor_T[0], xyz_receptor_T[1], xyz_receptor_T[2]

  # load a single ligand PDBQT into a DataFrame
  conf_ligand = open(l_pdbqt,'r').read().strip().split('\n')
  conf_ligand_atoms = [l for l in conf_ligand if l.startswith('ATOM')]
  xyz_ligand = np.array([(float(l[30:38]), float(l[38:46]), float(l[46:54]))
                          for l in conf_ligand_atoms if l[77:79].strip() in ligand_map.keys()],dtype=cp.float32)
  t = cp.array([ligand_map[l[77:79].strip()] for l in conf_ligand_atoms if l[77:79].strip() in ligand_map.keys()],dtype=cp.int32)

  # compute
  x, y, z  = (cp.array(xyz_ligand[:,0]),
              cp.array(xyz_ligand[:,1]),
              cp.array(xyz_ligand[:,2]))
  offsets = cp.asarray([0,len(xyz_ligand)],dtype='int32')

  a = contacts.kernel.compute(x, y, z, t, offsets, x_receptor, y_receptor, z_receptor, types_receptor,
                                  cutoff=nbins*binsize,binsize=binsize, nbins=nbins,
                                  n_receptor_types=len(atoms_of_interest),
                                  n_ligand_types=nligand)
  
  # pack the histogram into a compact (binary) fingerprint using int64's
  a_packed = np.array(np.packbits(a.get(),axis=-1),dtype='uint8')
  a_padded = np.pad(a_packed,pad_width=((0,0),(0,((a_packed.shape[1]+7)//8)*8-a_packed.shape[1])),mode='constant',constant_values=0)
  fingerprints.append(a_padded.view(dtype='int64')[0])
  
fingerprints = np.vstack(fingerprints)
# Store the fingerprints in a DataFrame
import pandas as pd
moonshot_fingerprints = pd.DataFrame(fingerprints)
moonshot_fingerprints = moonshot_fingerprints.astype('int64')
moonshot_fingerprints.rename(columns = {c: 'fp'+str(i) for c, i in enumerate(moonshot_fingerprints.columns)}, inplace=True)
moonshot_fingerprints['moonshot_id'] = [os.path.basename(s) for s in structures]

# drop compounds with zero fingerprint
moonshot_fingerprints = moonshot_fingerprints[np.sum(fingerprints,axis=1)>0]
moonshot_fingerprints.head()
# upload to bigquery (if you have permissions)
moonshot_fingerprints.to_gbq('moonshot.fingerprints',project_id=project_id,if_exists='replace')<jupyter_output>1it [00:02,  2.90s/it]<jupyter_text>Search for similar fingerprints<jupyter_code>#%%bigquery --project $project_id moonshot_fingerprints
#SELECT * FROM moonshot.fingerprints<jupyter_output><empty_output><jupyter_text>We have to write a function to compute the Jaccard similarity in SQL! *Yay!*<jupyter_code>jaccard = """
CREATE TEMP FUNCTION
   jaccard_similarity(x ARRAY<INT64>, y ARRAY<INT64>)
AS 
( 
  (
  WITH mytable AS
    (
    SELECT BIT_COUNT(x[OFFSET(i)]) AS a,
           BIT_COUNT(y[OFFSET(i)]) AS b,
           BIT_COUNT(x[OFFSET(i)] & y[OFFSET(i)]) as c
    FROM UNNEST(x) WITH OFFSET AS i
    )
  SELECT (SUM(c))/(SUM(a)+SUM(b)-SUM(c))
  FROM mytable
  )
);
"""<jupyter_output><empty_output><jupyter_text>Now we'll compute the Jaccard similarity on the outer product (*cross join*) between the large table of fingerprints and our small table of experimental fingerprints.The below query will do a brute force search for similarity to a specific experimental structure, sort the scores, and then return the most similar compounds<jupyter_code># query a single compound (takes ~5min)
q = jaccard+"""
SELECT name, rank, jaccard_similarity([{}],[{}]) AS jaccard        
      FROM mpro_5r84.fingerprints64
      ORDER BY jaccard DESC
      LIMIT 100
""".format(','.join([str(moonshot_fingerprints[moonshot_fingerprints.moonshot_id == 'Mpro-x2608_0A']['fp'+str(i)].values[0])
                     for i in range(fingerprints.shape[1])]),
           ','.join(['desc_v2_'+str(i) for i in range(fingerprints.shape[1])]))
# run the query -- takes a couple of minutes
from google.cloud import bigquery
import pandas as pd

client = bigquery.Client(project=project_id)
df = client.query(q).to_dataframe()
df<jupyter_output><empty_output><jupyter_text>Extract the similar compounds In this query, we'll load the previous query result as a static table and then extract the conformations by joining it with the `confs_all` table<jupyter_code>q = """
WITH compounds AS
   ( 
     SELECT {}
   )
SELECT * FROM mpro_5r84.confs_all
INNER JOIN compounds
ON compounds.name = confs_all.name AND compounds.rank = confs_all.rank
""".format('UNION ALL SELECT '.join([f'"{n}" AS name, {r} AS rank ' for n,r in zip(df['name'].values,
                                                                                   df['rank'].values.astype('str'))]))
# run the query -- takes a few minutes
from google.cloud import bigquery
import pandas as pd

client = bigquery.Client(project=project_id)
df_conf = client.query(q).to_dataframe()
df_conf
import openbabel.pybel
def to_pdb(pdbqt):
  return openbabel.pybel.readstring('pdbqt',pdbqt).write('pdb')
# download a .zip archive with PDB (use Chrome)
from tqdm.notebook import tqdm
df_conf['pdb'] = df_conf.conf.apply(to_pdb)
pdbs = []
for name,pdb,rank in tqdm(zip(df_conf['name'].values,df_conf['pdb'].values,df_conf['rank'])):
  fn = name+'_rank'+str(rank)+'.pdb'
  with open(fn,'w') as f:
    f.write(pdb)
  pdbs.append(fn)
pdbs = ' '.join(pdbs)
!zip -FS pdb.zip $pdbs
from google.colab import files
files.download('pdb.zip')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1314602844259452, 'fraction_numerical': 0.0365071106486299, 'mean_word_length': 4.572602077796569, 'pattern_counts': {'":': 0, '<': 82, '<?xml version=': 0, '>': 89, 'https://': 12, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 8, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '21', 'dir': 'jupyter-structured-clean-dedup', 'id': '561106', 'license': 'CC0-1.0', 'n_tokens_mistral': 8802, 'n_tokens_neox': 8006, 'n_words': 1933, 'path': 'Analysis_of_Moonshot_structures.ipynb', 'repo_name': 'jglaser/gigadocking_notebooks', 'size': '23019'}