Share Pastebin
Guest
Public paste!

timvdm

By: a guest | Sep 16th, 2009 | Syntax: None | Size: 15.55 KB | Hits: 172 | Expires: Never
Copy text to clipboard
  1. /***************************************************************************
  2.  *   Copyright (C) 2009 by Tim Vandermeersch                               *
  3.  *                                                                         *
  4.  *   This program is free software; you can redistribute it and/or modify  *
  5.  *   it under the terms of the GNU General Public License as published by  *
  6.  *   the Free Software Foundation; either version 2 of the License, or     *
  7.  *   (at your option) any later version.                                   *
  8.  *                                                                         *
  9.  *   This program is distributed in the hope that it will be useful,       *
  10.  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
  11.  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
  12.  *   GNU General Public License for more details.                          *
  13.  *                                                                         *
  14.  *   You should have received a copy of the GNU General Public License     *
  15.  *   along with this program; if not, write to the                         *
  16.  *   Free Software Foundation, Inc.,                                       *
  17.  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  18.  ***************************************************************************/
  19. #include <iostream>
  20. #include <vector>
  21. #include <algorithm>
  22. #include <cassert>
  23.  
  24. #include <Eigen/Core>
  25.  
  26. #include <openbabel/mol.h>
  27. #include <openbabel/obconversion.h>
  28. #include <openbabel/graphsym.h>
  29.  
  30. /**
  31.  * This class represents a shortened mapping for permutations.
  32.  *
  33.  * There are three ways to symbolically represent permutations:
  34.  @verbatim
  35.  cycle notation:  (1 2 3)    (rotate to the left: 1 2 3 -> 2 3 1)
  36.  
  37.  standard mapping:  1 2 3    (first line are the original numbers in ascending order)
  38.                     2 3 1    (second line contains the element to which it is permuted)
  39.  
  40.  shortened mapping:  2 3 1   (avoid first line in standard mapping: efficient storage)
  41.  @endverbatim
  42.  *
  43.  * For efficiency reasons, the shortened mapping notation is used to internally store
  44.  * the permutations. The elements are always in the range \f$0-n\f$ where \f$n\f$ is the
  45.  * number of elements (i.e. atoms).
  46.  *
  47.  * Multiple Permutations can be combined in a PermutationGroup. The permutation
  48.  * group \f$S_n\f$, is the group containing all \f$n!\f$ permutations.
  49.  *
  50.  * The automorphism group is a subgroup of \f$S_n\f$. It consists of all vertex (i.e. atom)
  51.  * permutations which conserve the edge connections (i.e. bonds). There is always
  52.  * at least one permutation in the automorphism group, namely the identity permutation.
  53.  * Since it doesn't change any connections, it fulfills the previously given definition.
  54.  * To check if other permuations belong to the automorphism group, the following matrix
  55.  * multiplication equation can be used
  56.  \f[
  57.    P^T A P = A
  58.  \f]
  59.  * For small molecules (less than 10 atoms) it is possible to generate all the
  60.  * permutations in the \f$S_n\f$ group and apply the formula. However, to have reasonable
  61.  * performance for larger molecules, it is needed to limit the number of permutations
  62.  * that need to be checked. In practise, a Graph Invariant Atom Partitioning (GIAP)
  63.  * algorithm is used for this (e.g. Morgan's Extended Connectivity (EC)). See the
  64.  * OBGraphSym class documentation for more details. [note: an orbit is the group of atoms
  65.  * in a molecule that belong to the same partitioned group -- or the atoms with the same
  66.  * symmetry class]
  67.  *
  68.  * Once the initial atom partitioning in orbits is obtained, this can be used to generate
  69.  * the subgroup of \f$S_n\f$ knowing that permutations between orbits are not allowed
  70.  * (i.e. these permutations will never satisfy the matrix multiplication equation).
  71.  * Orbits with only one atom will never be changed in any permutation belonging to the
  72.  * automorphisms group and will therefor be ignored. The number of permutations to be
  73.  * tested depends on the number of remaining orbits and the number of particles contained
  74.  * in them. For example a molecule with 12 atoms, 2 orbits, each containing 3 atoms will require
  75.  * \f$3! \times 3! = 36\f$ tests. [note: \f$12! = 4790 001 600\f$]
  76.  *
  77.  * @image html 12a2o3a.png
  78.  *
  79.  */
  80. struct Permutation
  81. {
  82.   /**
  83.    * Default constructor.
  84.    */
  85.   Permutation()
  86.   {}
  87.  
  88.   /**
  89.    * Constructor taking a @p map as argument.
  90.    */
  91.   Permutation(const std::vector<unsigned int> &_map) : map(_map)
  92.   {}
  93.  
  94.   /**
  95.    * Constructor taking a @p matrix as argument.
  96.    */
  97.   Permutation(const Eigen::MatrixXi &matrix)
  98.   {
  99.     setMatrix(matrix);
  100.   }
  101.  
  102.   /**
  103.    * Copy constructor.
  104.    */
  105.   Permutation(const Permutation &other)
  106.   {
  107.     this->map = other.map;
  108.   }
  109.  
  110.   /**
  111.    * Print the permutation to std::cout in shortened notation.
  112.    */
  113.   void print() const
  114.   {
  115.     std::vector<unsigned int>::const_iterator i;
  116.     for (i = map.begin(); i != map.end(); ++i)
  117.       std::cout << *i << " ";
  118.     std::cout << std::endl;    
  119.   }
  120.  
  121.   /**
  122.    * Compute the permutation matrix for this Permutation.
  123.    */
  124.   Eigen::MatrixXi matrix() const
  125.   {
  126.     int n = map.size();
  127.     Eigen::MatrixXi P = Eigen::MatrixXi::Zero(n, n);
  128.  
  129.     for (int i = 0; i < n; ++i) {
  130.       P(map.at(i)-1, i) = 1;
  131.       P(i, map.at(i)-1) = 1;
  132.     }
  133.  
  134.     return P;
  135.   }
  136.  
  137.   /**
  138.    * Set the permutation matrix for this permutation. The matrix is
  139.    * converted to a shortened notation permutation.
  140.    */
  141.   void setMatrix(const Eigen::MatrixXi &m)
  142.   {
  143.     if (m.rows() != m.cols())
  144.       return;
  145.  
  146.     int size = m.rows();
  147.     map.resize(size);
  148.  
  149.     for (int i = 0; i < size; ++i)
  150.       for (int j = 0; j < size; ++j)
  151.         if (m(i,j))
  152.           map[i] = j + 1;  
  153.   }
  154.  
  155.   /**
  156.    * Multiply two permutation matrices and return the resulting permutation.
  157.    * This is the same as applying the two permutations consecutively.
  158.    */
  159.   Permutation operator*(const Permutation &rhs) const
  160.   {
  161.     Eigen::MatrixXi res = rhs.matrix() * matrix();
  162.     return Permutation(res);
  163.   }
  164.  
  165.   /**
  166.    * Equality operator. Two permutations are equal if their shortened notations
  167.    * are the same (compared element-by-element).
  168.    */
  169.   bool operator==(const Permutation &rhs) const
  170.   {
  171.     if (map.size() != rhs.map.size())
  172.       return false;
  173.    
  174.     std::vector<unsigned int>::const_iterator i1 = map.begin();
  175.     std::vector<unsigned int>::const_iterator i2 = rhs.map.begin();
  176.     for (; i1 != map.end(); ++i1, ++i2)
  177.       if (*i1 != *i2)
  178.         return false;
  179.  
  180.     return true;  
  181.   }
  182.  
  183.   std::vector<unsigned int> map; //!< The actual mapping in shortened notation
  184. };
  185.  
  186. struct PermutationGroup
  187. {
  188.   /**
  189.    * Default constructor.
  190.    */
  191.   PermutationGroup()
  192.   {}
  193.  
  194.   /**
  195.    * Constructor taking vector of permutations as argument.
  196.    */
  197.   PermutationGroup(const std::vector<Permutation> &_permutations) : permutations(_permutations)
  198.   {}
  199.  
  200.   /**
  201.    * @return The number of permutations in this group.
  202.    */
  203.   unsigned int size() const
  204.   {
  205.     return permutations.size();
  206.   }
  207.  
  208.   /**
  209.    * Add permutation @p p to this permutation group.
  210.    */
  211.   void add(const Permutation &p)
  212.   {
  213.     permutations.push_back(p);
  214.   }
  215.  
  216.   /**
  217.    * @return A constant reference to the @p index-th permutation.
  218.    */
  219.   const Permutation& at(unsigned int index)
  220.   {
  221.     return permutations.at(index);
  222.   }
  223.  
  224.   /**
  225.    * @return True if this permutation group contains permutation @p p.
  226.    */
  227.   bool contains(const Permutation &p) const
  228.   {
  229.     std::vector<Permutation>::const_iterator i;
  230.     for (i = permutations.begin(); i != permutations.end(); ++i)
  231.       if (*i == p)
  232.         return true;
  233.     return false;
  234.   }
  235.  
  236.   std::vector<Permutation> permutations; //!< The actual permutations in the group
  237. };
  238.  
  239.  
  240.  
  241. struct Orbit
  242. {
  243.   Orbit(const std::vector<unsigned int> &_ordered) : ordered(_ordered), current(ordered)
  244.   {
  245.   }
  246.  
  247.   Orbit(const Orbit &other) : ordered(other.ordered), current(other.ordered)
  248.   {
  249.   }
  250.  
  251.   std::vector<unsigned int> ordered;
  252.   std::vector<unsigned int> current;
  253. };
  254.  
  255. typedef std::vector<Orbit> Orbits;
  256.  
  257. /**
  258.  * Compute the \f$S_n\f$ group consisting of all \f$n!\f$ permutations.
  259.  */
  260. PermutationGroup computeAllPermutations(const Permutation &input)
  261. {
  262.   PermutationGroup Sn;
  263.   Permutation p = input;
  264.   Sn.add(p);
  265.          
  266.   while (std::next_permutation(p.map.begin(), p.map.end())) {
  267.     Sn.add(p);
  268.   }
  269.  
  270.   return Sn;
  271. }
  272.  
  273. Orbits computeOrbits(const std::vector<unsigned int> &symClasses)
  274. {
  275.   Orbits orbits;
  276.   std::vector<bool> visit(symClasses.size(), false);
  277.  
  278.   std::vector<unsigned int>::const_iterator symClass;
  279.   for (symClass = symClasses.begin(); symClass != symClasses.end(); ++symClass) {
  280.     if (visit.at(*symClass))
  281.       continue;
  282.     visit[*symClass] = true;
  283.  
  284.     unsigned int n = std::count(symClasses.begin(), symClasses.end(), *symClass);
  285.     if (n >= 2) {
  286.       std::vector<unsigned int> orbit;
  287.      
  288.       for (unsigned int i = 0; i < symClasses.size(); ++i)
  289.         if (symClasses.at(i) == *symClass)
  290.           orbit.push_back(i);
  291.  
  292.       orbits.push_back(Orbit(orbit));
  293.     }
  294.   }
  295.  
  296.   return orbits;
  297. }
  298.  
  299. Permutation processOrbitPermutations(const Orbit &orbit, const Permutation &E)
  300. {
  301.   Permutation p = E;
  302.   for (unsigned int i = 0; i < orbit.ordered.size(); ++i) {
  303.     std::cout << "p.map.at(" << orbit.ordered.at(i) << ") = E.map.at(" << orbit.current.at(i) << ")" << std::endl;
  304.     p.map.at(orbit.ordered.at(i)) = E.map.at(orbit.current.at(i));
  305.   }
  306.   return p;
  307. }
  308.  
  309. bool sameMatrix(const Eigen::MatrixXi &m1, const Eigen::MatrixXi &m2)
  310. {
  311.   if (m1.cols() != m2.cols())
  312.     return false;
  313.   if (m1.rows() != m2.rows())
  314.     return false;
  315.  
  316.   for (int i = 0; i < m1.rows(); ++i)
  317.     for (int j = 0; j < m1.cols(); ++j)
  318.       if (m1(j,i) != m2(j,i))
  319.         return false;
  320.  
  321.   return true;
  322. }
  323.  
  324. PermutationGroup computeAutomorphisms(const PermutationGroup &g, const Eigen::MatrixXi &A)
  325. {
  326.   PermutationGroup G;
  327.  
  328.   std::vector<Permutation>::const_iterator p;
  329.   for (p = g.permutations.begin(); p != g.permutations.end(); ++p) {
  330.     Eigen::MatrixXi P = (*p).matrix();
  331.     Eigen::MatrixXi A2 = P.transpose() * A *  P;
  332.  
  333.     if (sameMatrix(A, A2)) {
  334.       G.add(*p);
  335.       (*p).print();
  336.     }
  337.   }
  338.  
  339.   return G;
  340. }
  341.  
  342.  
  343. /**
  344.  * Compute the adjacency matrix for the specified molecule. This matrix
  345.  * represents the connectivity in the molecule. It can be used to determine
  346.  * if a Permutation is part of the automorphism group.
  347.  */
  348. Eigen::MatrixXi adjacencyMatrix(OpenBabel::OBMol *mol)
  349. {
  350.   int n = mol->NumAtoms();
  351.   Eigen::MatrixXi A = Eigen::MatrixXi::Zero(n, n);
  352.  
  353.   using OpenBabel::OBMolBondIter;
  354.   std::cout << "bonds:" << std::endl;
  355.   FOR_BONDS_OF_MOL (bond, mol)
  356.     std::cout << "  " << bond->GetBeginAtomIdx() << "-" << bond->GetEndAtomIdx() << std::endl;
  357.  
  358.   for (int i = 0; i < n; ++i) {
  359.     OpenBabel::OBAtom *ai = mol->GetAtom(i+1);
  360.     for (int j = 0; j < n; ++j) {
  361.       if (i == j)
  362.         continue;
  363.       if (ai->IsConnected(mol->GetAtom(j+1)))
  364.         A(i, j) = 1;
  365.     }
  366.   }
  367.  
  368.   return A;
  369. }
  370.  
  371. ////////////////////////////////////////////////////////////////////////////////
  372. //
  373. //
  374. //  O P E N B A B E L   S P E C I F I C   C O D E
  375. //
  376. //
  377. ////////////////////////////////////////////////////////////////////////////////
  378.  
  379. OpenBabel::OBMol* ReadMolecule(const std::string &filename)
  380. {
  381.   OpenBabel::OBConversion conv;
  382.   OpenBabel::OBFormat *format = conv.FormatFromExt(filename.c_str());
  383.   if (!format || !conv.SetInFormat(format)) {
  384.     std::cout << "ERROR: Could not detect file format for: " << filename << std::endl;
  385.     return 0;
  386.   }
  387.  
  388.   std::ifstream ifs;
  389.   ifs.open(filename.c_str());
  390.   if (!ifs) {
  391.     std::cout << "ERROR: Could not open file: " << filename << std::endl;
  392.     return 0;  
  393.   }
  394.  
  395.   OpenBabel::OBMol *obmol = new OpenBabel::OBMol;
  396.   if (!conv.Read(obmol, &ifs)) {
  397.     std::cout << "ERROR: Could not read molecule from file: " << filename << std::endl;
  398.     delete obmol;
  399.     ifs.close();
  400.     return 0;
  401.   }
  402.  
  403.   ifs.close();
  404.   return obmol;
  405. }
  406.  
  407. ////////////////////////////////////////////////////////////////////////////////
  408. //
  409. //
  410. //  U N I T   T E S T S
  411. //
  412. //
  413. ////////////////////////////////////////////////////////////////////////////////
  414.  
  415.  
  416.  
  417. void testPermutation()
  418. {
  419.   std::vector<unsigned int> e;
  420.   e.push_back(1);
  421.   e.push_back(2);
  422.   e.push_back(3);
  423.   e.push_back(4);
  424.   Permutation p1(e), p2(e);
  425.  
  426.   assert(p1 == p2);
  427. }
  428.  
  429. void testPermutationGroup()
  430. {
  431.   Permutation p;
  432.   p.map.push_back(1);
  433.   p.map.push_back(2);
  434.   p.map.push_back(3);
  435.   p.map.push_back(4);
  436.  
  437.   PermutationGroup group;
  438.   assert( !group.contains(p) );
  439.  
  440.   group.add(p);
  441.   assert( group.contains(p) );
  442. }
  443.  
  444. void unitTests()
  445. {
  446.   testPermutation();
  447.   testPermutationGroup();
  448.  
  449. }
  450.  
  451. int main(int argc, char **argv)
  452. {
  453.   unitTests();
  454.  
  455.   if (argc < 2) {
  456.     std::cout << "Usage: " << argv[0] << " <filename>" << std::endl;
  457.     return -1;
  458.   }
  459.  
  460.   OpenBabel::OBMol *obmol = ReadMolecule(argv[1]);
  461.   if (!obmol)
  462.     return -1;
  463.  
  464.   std::vector<unsigned int> elements;
  465.   for (int i = 0; i < obmol->NumAtoms(); ++i) {
  466.     elements.push_back(i+1);
  467.   }
  468.  
  469.   Permutation E(elements); // identity
  470.  
  471.   OpenBabel::OBGraphSym graphSym(obmol);
  472.   std::vector<unsigned int> symClasses;
  473.   graphSym.GetSymmetry(symClasses);
  474.   const Orbits sortedOrbits = computeOrbits(symClasses);
  475.  
  476.   PermutationGroup G0, G1;
  477.  
  478.   Orbits orbits = sortedOrbits;;
  479.   for (unsigned int i = 0; i < orbits.size(); ++i) {
  480.     // print the orbit
  481.     std::cout << "orbit: ";
  482.     for (unsigned int j = 0; j < orbits.at(i).ordered.size(); ++j) {
  483.       std::cout << orbits.at(i).ordered.at(j) << " ";
  484.     }
  485.     std::cout << std::endl;
  486.  
  487.     // sorted orbit
  488.     Orbit e = orbits.at(i);
  489.  
  490.     Permutation p = processOrbitPermutations(orbits.at(i), E);
  491.     if (!G0.contains(p))
  492.       G0.add(p);
  493.     while (std::next_permutation(orbits.at(i).current.begin(), orbits.at(i).current.end())) {
  494.       p = processOrbitPermutations(orbits.at(i), E);
  495.  
  496.       if (!G0.contains(p))
  497.         G0.add(p);
  498.     }
  499.     std::cout << std::endl;
  500.   }
  501.  
  502.   for (unsigned int i = 0; i < G0.size(); ++i) {
  503.     for (unsigned int j = 0; j < G0.size(); ++j) {
  504.       if (i == j)
  505.         continue;
  506.  
  507.       Permutation p = G0.at(i) * G0.at(j);
  508.       if (!G1.contains(p))
  509.         G1.add(p);
  510.     }
  511.   }
  512.  
  513.   for (unsigned int i = 0; i < G1.size(); ++i)
  514.     if (!G0.contains(G1.at(i)))
  515.       G0.add(G1.at(i));
  516.  
  517.   G1.permutations.clear();
  518.  
  519.   // print automorphisms
  520.   std::cout << "G0:" << std::endl;
  521.   for (unsigned int i = 0; i < G0.size(); ++i)
  522.     G0.at(i).print();
  523.   // print automorphisms
  524.   std::cout << "G1:" << std::endl;
  525.   for (unsigned int i = 0; i < G1.size(); ++i)
  526.     G1.at(i).print();
  527.  
  528.  
  529.   // compute adjacency matrix
  530.   Eigen::MatrixXi A = adjacencyMatrix(obmol);
  531.   // compute the permutation group of all n! permutations
  532.   PermutationGroup Sn = computeAllPermutations(E);
  533.   // compute automorphisms starting with the limited group
  534.   PermutationGroup G3 = computeAutomorphisms(G0, A);
  535.   // compute automorphisms starting with entire permutation group
  536.   PermutationGroup G4 = computeAutomorphisms(Sn, A);
  537.  
  538.  
  539.   if (G3.size() != G4.size()) {
  540.     std::cout << "NOT ALL AUTOMORPHISMS ARE FOUND!!" << std::endl;
  541.     return 0;
  542.   }
  543.   bool success = true;
  544.   for (unsigned int i = 0; i < G4.size(); ++i)
  545.     if (!G3.contains(G4.at(i))) {
  546.       std::cout << "COULD NOT FIND AUTOMORPHISM: ";
  547.       G3.at(i).print();
  548.       std::cout << std::endl;
  549.       success = false;
  550.     }
  551.  
  552.   if (success) {
  553.     std::cout << "ALL AUTOMORPHISMS FOUND!!" << std::endl;
  554.   }
  555.  
  556.   return 0;
  557. }