File Coverage

blib/lib/Chemistry/Ring.pm
Criterion Covered Total %
statement 70 83 84.3
branch 12 16 75.0
condition 3 7 42.8
subroutine 14 16 87.5
pod 7 8 87.5
total 106 130 81.5


line stmt bran cond sub pod time code
1             package Chemistry::Ring;
2              
3             our $VERSION = '0.21'; # VERSION
4             # $Id$
5              
6             =head1 NAME
7              
8             Chemistry::Ring - Represent a ring as a substructure of a molecule
9              
10             =head1 SYNOPSIS
11              
12             use Chemistry::Ring;
13            
14             # already have a molecule in $mol...
15             # create a ring with the first six atoms in $mol
16             my $ring = Chemistry::Ring->new;
17             $ring->add_atom($_) for $mol->atoms(1 .. 6);
18              
19             # find the centroid
20             my $vector = $ring->centroid;
21              
22             # find the plane that fits the ring
23             my ($normal, $distance) = $ring->plane;
24              
25             # is the ring aromatic?
26             print "is aromatic!\n" if $ring->is_aromatic;
27              
28             # "aromatize" a molecule
29             Chemistry::Ring::aromatize_mol($mol);
30              
31             # get the rings involving an atom (after aromatizing)
32             my $rings = $mol->atoms(3)->attr('ring/rings');
33              
34             =head1 DESCRIPTION
35              
36             This module provides some basic methods for representing a ring. A ring is
37             a subclass of molecule, because it has atoms and bonds. Besides that, it
38             has some useful geometric methods for finding the centroid and the ring plane,
39             and methods for aromaticity detection.
40              
41             This module does not detect the rings by itself; for that, look at
42             L.
43              
44             This module is part of the PerlMol project, L.
45              
46             =cut
47              
48 6     6   324534 use strict;
  6         59  
  6         188  
49 6     6   41 use warnings;
  6         12  
  6         194  
50 6     6   1702 use Math::VectorReal qw(:axis vector);
  6         16582  
  6         1019  
51 6     6   3503 use Statistics::Regression;
  6         27802  
  6         196  
52 6     6   1676 use Chemistry::Mol;
  6         97765  
  6         302  
53 6     6   51 use base 'Chemistry::Mol', 'Exporter';
  6         14  
  6         653  
54 6     6   40 use Scalar::Util 'weaken';
  6         13  
  6         3295  
55              
56             our @EXPORT_OK = qw(aromatize_mol);
57             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
58              
59             our $N = 0;
60             our $DEBUG = 0;
61              
62             =head1 METHODS
63              
64             =over 4
65              
66             =item Chemistry::Ring->new(name => value, ...)
67              
68             Create a new Ring object with the specified attributes. Same as
69             C<< Chemistry::Mol->new >>.
70              
71             =cut
72              
73             sub nextID {
74 43     43 0 3052 "ring".++$N;
75             }
76              
77              
78             # make sure we don't become parent of the atoms added to us
79 46     46 1 262 sub add_atom { shift->SUPER::add_atom_np(@_) }
80 42     42 1 107 sub add_bond { shift->SUPER::add_bond_np(@_) }
81              
82             sub print {
83 0     0 1 0 my $self = shift;
84 0         0 return <
85             ring:
86             id: $self->{id}
87 0         0 atoms: @{$self->{atoms}}
88 0         0 bonds: @{$self->{bonds}}
89             EOF
90             }
91              
92             =item $ring->centroid
93              
94             Returns a vector with the centroid, defined as the average of the coordinates
95             of all the atoms in the ring. The vecotr is a L object.
96              
97             =cut
98              
99             sub centroid {
100 0     0 1 0 my $self = shift;
101 0         0 my $c = O; # origin
102 0         0 my $n = 0;
103 0         0 for my $a ($self->atoms) {
104 0         0 $c += $a->coords;
105 0         0 ++$n;
106             }
107 0         0 $c = $c / $n;
108             }
109              
110             =item my ($norm, $d) = $ring->plane
111              
112             Returns the normal and distance to the origin that define the plane that best
113             fits the atoms in the ring, by using multivariate regression. The normal
114             vector is a L object.
115              
116             =cut
117              
118             sub plane {
119 1     1 1 17 my $self = shift;
120 1         2 my $reg;
121 1 50       16 if( $Statistics::Regression::VERSION < 0.52 ) {
122 0         0 $reg = Statistics::Regression->new(3, "plane for $self", [qw(b mx my)]);
123             } else {
124 1         9 $reg = Statistics::Regression->new("plane for $self", [qw(b mx my)]);
125             }
126 1         142 for my $atom ($self->atoms) {
127 4         450 my ($x, $y, $z) = $atom->coords->array;
128 4         104 $reg->include($z, [1.0, $x, $y]);
129             }
130 1 50       146 $reg->print if $DEBUG;
131              
132             # convert the theta vector (z = a + bx + cy) to a normal vector and
133             # distance to the origin
134 1         2 my @coef = (@{$reg->theta}, -1.0); # -1 is d in a + bx + cx + dz = 0
  1         4  
135 1         77 my $d = shift @coef; # distance (not normalized)
136 1         3 my $sum_sq = 0; # normalization constant
137 1         6 $sum_sq += $_*$_ for @coef;
138 1   50     4 $sum_sq ||= 1;
139 1         3 ($d, @coef) = map { $_ / $sum_sq } ($d, @coef); # normalize
  4         11  
140 1         4 return (vector(@coef)->norm, $d);
141             }
142              
143             =item $ring->is_aromatic
144              
145             Naively guess whether ring is aromatic from the molecular graph, with a method
146             based on Huckel's rule. This method is not very accurate, but works for simple
147             molecules. Returns true or false.
148              
149             =cut
150              
151             sub is_aromatic {
152 33     33 1 1959 my ($self) = @_;
153 33         66 my $n_pi = 0;
154              
155 33         71 for my $atom ($self->atoms) {
156 6     6   54 no warnings 'uninitialized';
  6         13  
  6         2661  
157 76 100       343 return 0 if ($atom->bonds + $atom->hydrogens > 3);
158              
159             # build bond order histogram
160 51         826 my @order_freq = (0,0,0,0);
161 51         125 for my $bond ($atom->bonds) {
162 104         875 $order_freq[$bond->order]++;
163             }
164            
165 51 50 33     323 return 0 if ($order_freq[3] or $order_freq[2] > 1);
166 51 100       106 if ($order_freq[2] == 1) {
    100          
167 46         76 $n_pi += 1;
168             } elsif ($atom->symbol =~ /^[NOS]$/) {
169 2         22 $n_pi += 2;
170             }
171             }
172             #print "n_pi = $n_pi\n";
173 8 100       46 return ($n_pi % 4 == 2) ? 1 : 0;
174             }
175              
176             1;
177              
178             =back
179              
180             =head1 EXPORTABLE SUBROUTINES
181              
182             Nothing is exported by default, but you can export these subroutines
183             explicitly, or all of them by using the ':all' tag.
184              
185             =over
186              
187             =item aromatize_mol($mol)
188              
189             Finds all the aromatic rings in the molecule and marks all the atoms and bonds
190             in those rings as aromatic.
191              
192             It also adds the 'ring/rings' attribute to the molecule and to all ring atoms
193             and bonds; this attribute is an array reference containing the list of rings
194             that involve that atom or bond (or all the rings in the case of the molecule).
195             NOTE (the ring/rings attribute is experimental and might change in future
196             versions).
197              
198             =cut
199              
200             sub aromatize_mol {
201 1     1 1 2728 my ($mol) = @_;
202              
203 1         516 require Chemistry::Ring::Find;
204              
205 1         10 $_->aromatic(0) for ($mol->atoms, $mol->bonds);
206              
207 1         63 my @rings = Chemistry::Ring::Find::find_rings($mol);
208 1         9 $mol->attr("ring/rings", \@rings);
209 1         19 $_->attr("ring/rings", []) for ($mol->atoms, $mol->bonds);
210              
211 1         81 for my $ring (@rings) {
212 1 50       4 if ($ring->is_aromatic) {
213 0         0 $_->aromatic(1) for ($ring->atoms, $ring->bonds);
214             }
215 1         20 for ($ring->atoms, $ring->bonds) {
216 6   50     76 my $ringlist = $_->attr("ring/rings") || [];
217 6         50 push @$ringlist, $ring;
218 6         14 weaken($ringlist->[-1]);
219 6         12 $_->attr("ring/rings", $ringlist);
220             }
221             }
222 1         12 @rings;
223             }
224              
225             1;
226              
227             =back
228              
229             =head1 SOURCE CODE REPOSITORY
230              
231             L
232              
233             =head1 SEE ALSO
234              
235             L, L, L,
236             L.
237              
238             =head1 AUTHOR
239              
240             Ivan Tubert-Brohman
241              
242             =head1 COPYRIGHT
243              
244             Copyright (c) 2009 Ivan Tubert-Brohman. All rights reserved. This program is
245             free software; you can redistribute it and/or modify it under the same terms as
246             Perl itself.
247              
248             =cut