Chemical Descriptors Library: Fingerprints
Client Algorithms


Molecular fingerprinting is a common algorithm which basically produce a bitset that contains information on the molecular connectivity and its associated atomic elements.
For example, ethane (C2H6) is represented as a molecular graph (h depleted) which has two connected vertices. To generate a fingerprint, we will consider path-distances for each vertex, begining in 0, to a path-lenght that is user-defined, and we will annotate the atomic symbol (or other atomic property) of each vertex along the path. In the case of ethane: distance cero (0) will give C and C, distance one will give CC. Each one of these strings are converted to a numerical value with the use of a hash function. The next step will be to switch ON a bit of the fingerprint (bitset) that corresponds to this generated value. Usually, we want to generate a fingerprint of a given size, hence the hash function must be bounded, or we have to apply other trick.
There are plenity of flavours for hash functions out there: bounded hash functions, truncated, projected, and so on. CDL uses an unbounded hash function. Now you might be asking: gee, then what happens if the generated hash value is greater than the size of the fingherprint I want to generate? Then the hash value will seed a pseudo random number generator that produces numbers between a well defined range: normally from cero to the size of the bitset.
Now you see that the fingerprint algorithm gives a bitset with infomation on the connectivity of the molecule. This is 2d information that can express molecular similarity, 2d similarity.
The most common distance function used for this case (a distace function has three properties: given 'a' and 'b' points, i. d(a,b)==0 iff a==b; ii. d(a,b)==d(b,a) for every a and b; iii. for every a, b, d(a,b)>=0 and assigns one and only one real value) is the Tanimoto distance: The Tanimoto distance is calculated as follows: given two sets of values 'a' and 'b': TD = SUM( ((ai + bi) - abs(ai - bi))/(ai + bi)) / num i. Which in binary format translates to: TD = bits(a and b) / (bits(a) + bits(b) - bits(a and b)).
CDL fingerprints are used for both: substructure search and 2d similarity.
Following documentation is given to generate a fingerprint with CDL. Similarity functions documentation are provided somewhere else.

Practical Issues concerning the use of Fingerprints for substructure search screening :

The Fingerprints algorithm generates linear (non-cyclic) paths between any two vertices of the molecular graph. The algorithm only considers the shortest path between two vertices. This has an important practical issue, better described with an example:
Consider you are searching for CCCCCCCC (a chain of 8 Carbon atoms) in Cc1c(C)cccc1. You will get a hit using full substructure search, whereas you won't using FP pruning. This is because the FP algorithm will consider only the shortest path between the two substituent Carbons of the cyclic compound (shortest path will be CCCC instead of the longest path CCCCCCCC).
This is an intrinsic feature of the FP algorithm (not a bug). Consider using a full substructure search in situations when you want to consider all paths in rings to search for a linear substructure.


Is an overloaded function:
  template <class Molecule, class Block, class Allocator>
  generate_fingerprint(const Molecule& m, boost::dynamic_bitset<Block,Allocator>& bitset,
  	const size_t max_path_lenght = 7);

  template <class Molecule>
  generate_fingerprint(const Molecule& m, const size_t max_path_lenght = 7,
  	const size_t fingerprint_size = 1024);


m The molecule CDL's molecule
bitset The bitset boost::dynamic_bitset<>, or object that provides the same signature
max_path_lenght The max lenght of the path to consider size_t
fingerprint_size The lenght of the fingerprint to generate size_t


The first version of the function inserts values into the passed bitset, the second version returns the bitset.


#include <morpho/cdl/fingerprints/fingerprints.hpp>


For the first version, the size of the bitset is a valid range.


O(E.V + V^2). Application of a BFS for every atom of the molecule.


Consider that you want to calculate the tanimoto distance on the fingerprints of two molecules:

// --- std inclusions :
#include <iostream>
// --- morpho inclusions :
#include <morpho/cdl/molecule/molecule.hpp>
#include <morpho/cdl/parsers/parsers.cpp>
#include <morpho/cdl/fingerprints/fingerprints.hpp>
#include <morpho/cdl/similarity/similarity.hpp>
// --- boost inclusions :
#include <boost/dynamic_bitset.hpp>

int main() {
  using namespace std;
  using namespace morpho::cdl;
  typedef molecule<>    M;
  nail_juice<M>         j1, j2;
  M   m1(j1,false,true), m2(j2,false,true);
  boost::dynamic_bitset<>   fp1 = generate_fingerprint(m1);
  boost::dynamic_bitset<>   fp2 = generate_fingerprint(m2);
  std::cout <<  "The taminoto distance between : ";
  std::cout << get_can_smile(m1) << " and " << get_can_smile(m2);
  std::cout << " is : " << bin_tanimoto(fp1,fp2) << '\n';
  return 0;  


Copyright (c) Vladimir Josef Sykora & Morphochem AG 2003 Logo