Advanced Void Analysis¶
Introduction¶
Note: The calculation of individual voids and their analysis is only available to CFC license users.
The class crystal.Crystal.Void allows calculation of individual voids and their analysis.
First, read in a crystal structure from a CSD refcode or a CIF / MOL2 file.
>>> from ccdc import io
>>> csd_reader = io.CrystalReader('CSD')
>>> cryst = csd_reader.crystal('ZIWSON')
Total void volume in the unit cell¶
The total void volume is calculated as a percentage of the unit cell, and can be easily converted to cubic Angstroms.
>>> total_void_percentage = cryst.void_volume(1.2, 0.3, mode='contact')
>>> round(total_void_percentage, 2)
2.09
>>> total_void_angstroms = total_void_percentage * cryst.cell_volume / 100
>>> round(total_void_angstroms, 2)
70.26
Individual voids¶
Individual voids can be calculated, allowing the number of voids and details of each void to be accessed. This includes the type of the void (pocket or channel), its unique label and volume. The symmetry ID (numerical identifier to indicate which voids are symmetry equivalent by having the same number) and location (point for a pocket, direction for a channel) are also accessible.
>>> voids = cryst.calculate_voids()
>>> len(voids)
5
>>> voids[2].type
'pocket'
>>> voids[2].label
'Pocket 3'
>>> round(voids[2].volume_angstroms, 2)
12.95
>>> round(voids[2].volume_percentage, 2)
0.38
>>> voids[2].symmetry_id
2
>>> tuple([round(x, 2) for x in voids[2].location])
(-1.29, 10.39, 5.03)
Atoms around a void¶
The atoms within a specified distance of a void surface can be returned. The distance can be an actual distance in Angstroms or relative to the vdW radius. This list can be refined, for example if only interested in the hydrogen bond donor atoms near a void.
>>> nearby_atoms = voids[2].atoms_around(distance=2.5, vdw_corrected=False)
>>> nearby_donors = list(set([a for a in nearby_atoms if a.is_donor]))
>>> sorted([d.label for d in nearby_donors])
['N9', 'O27']
Distance between a void and an atom¶
The closest distance between a specified void surface and atom can be returned. This can be used to get the distances to the nearest atoms.
>>> from ccdc.descriptors import GeometricDescriptors as GD
>>> sorted([(a.label, round(GD.point_void_distance(a.coordinates, voids[2]), 2)) for a in nearby_donors])
[('N9', 1.72), ('O27', 2.11)]
Solvents that fit¶
Additional functionality is available to explore what solvents might fit in an individual void. This is based on CSD Solvent Knowledge Bank data (occupied space per solvent molecule for 119 different solvents across non-polymeric organics in the Best R-factor subset). The criteria for whether a particular solvent fits is the volume for the selected void must be within the 0.05 Quantile and 0.95 Quantile for solvent occupied space.
For example, the void volume for Pocket 1 in the CSD structure AJIHAG is 57.53 cubic Angstroms. A shortlist of 6 solvents is returned where a solvent molecule might fit in this space, including methanol that occupies a mean space of 55.85 cubic Angstroms. This aligns well with AJIHAG being a methanol solvate where the methanol is unmodelled.
>>> cryst = csd_reader.crystal('AJIHAG')
>>> voids = cryst.calculate_voids()
>>> {voids[0].label: round(voids[0].volume_angstroms, 2)}
{'Pocket 1': 57.53}
>>> solvents_fit = voids[0].solvents_that_fit(multiple_mols=False)
>>> len(solvents_fit)
6
>>> [{s.name: round(s.mean, 2)} for s in solvents_fit]
[{'dimethyl ether': 67.8},
{'formamide': 56.16},
{'formic acid': 48.6},
{'methanol': 55.85},
{'oxalic acid': 69.95},
{'sulfur dioxide': 57.98}]
Statistics can be accessed for each solvent, based on the occupied space for a single solvent molecule (cubic Angstroms) in the Solvent Knowledge Bank data. The most popular intermolecular hydrogen bond motifs are also accessible for each solvent.
>>> s = solvents_fit[3]
>>> {'solvent': s.name, 'hits': s.hits, 'mean': round(s.mean, 2), 'st_dev': round(s.standard_deviation, 2), '0.05_quantile': round(s.quantile_low, 2), '0.95_quantile': round(s.quantile_high, 2), 'min': round(s.minimum, 2), 'max': round(s.maximum, 2), 'popular hbond motifs': s.hbond_motifs}
{'solvent': 'methanol',
'hits': 7084,
'mean': 55.85,
'st_dev': 13.96,
'0.05_quantile': 36.31,
'0.95_quantile': 79.26,
'min': 0.0,
'max': 158.47,
'popular hbond motifs': 'da (48.59%) d (27.19%) daa (6.83%) No H-bonds (5.32%) dda (4.43%) a (2.99%) dd (2.02%) aa (1.16%) ddaa (0.78%) daaa (0.31%)'}
The default is to return solvents where 1 molecule might fit in the specified void. This can be changed to show solvents where 2 or more molecules might fit in the specified void, based on the criteria that the 0.05 Quantile for solvent occupied space should be less than half of the void volume.
>>> solvents_fit = voids[0].solvents_that_fit(multiple_mols=True)
>>> len(solvents_fit)
2
>>> [{s.name: round(s.mean, 2)} for s in solvents_fit]
[{'formic acid': 48.6}, {'water': 8.0}]
The Solvent Knowledge Bank can be called directly to access information about all 119 solvents, regardless of what might fit in a specific void.
>>> from ccdc.minor_component_knowledge_banks import SolventKnowledgeBank
>>> skb = SolventKnowledgeBank(probe_radius=1.2)
>>> skb_entries = skb.entries
>>> len(skb_entries)
119
>>> first_solvent = skb_entries[0]
>>> first_solvent.name
'1,1,1-trichloroethane'
>>> round(first_solvent.mean, 2)
170.94