File Coverage

blib/lib/Chemistry/InternalCoords.pm
Criterion Covered Total %
statement 67 84 79.7
branch 9 10 90.0
condition 5 9 55.5
subroutine 12 16 75.0
pod 7 7 100.0
total 100 126 79.3


line stmt bran cond sub pod time code
1             package Chemistry::InternalCoords;
2              
3             $VERSION = '0.18';
4             # $Id: InternalCoords.pm,v 1.6 2004/09/24 20:48:23 itubert Exp $
5              
6 2     2   115065 use 5.006;
  2         7  
  2         95  
7 2     2   10 use strict;
  2         5  
  2         69  
8 2     2   13 use warnings;
  2         4  
  2         213  
9 2     2   13 use Math::VectorReal qw(:all);
  2         3  
  2         585  
10 2     2   162 use Carp;
  2         6  
  2         301  
11 2     2   12 use overload '""' => "stringify", fallback => 1;
  2         4  
  2         22  
12 2     2   183 use Scalar::Util 'weaken';
  2         4  
  2         231  
13 2     2   18 use Math::Trig 'deg2rad';
  2         4  
  2         12558  
14              
15              
16             =head1 NAME
17              
18             Chemistry::InternalCoords - Represent the position of an atom using internal
19             coordinates and convert it to Cartesian coordinates.
20              
21             =head1 SYNOPSIS
22            
23             use Chemistry::InternalCoords;
24              
25             # ... have a molecule in $mol
26             my $atom = $mol->new_atom;
27              
28             # create an internal coordinate object for $atom
29             # with respect to atoms with indices 4, 3, and 2.
30             my $ic = Chemistry::InternalCoords->new(
31             $atom, 4, 1.1, 3, 109.5, 2, 180.0
32             );
33              
34             # can also use atom object references instead of indices
35             ($atom4, $atom3, $atom2) = $mol->atoms(4,3,2);
36             my $ic = Chemistry::InternalCoords->new(
37             $atom, $atom4, 1.1, $atom3, 109.5, $atom2, 180.0
38             );
39              
40             # calculate the Cartesian coordinates for
41             # the atom from the internal coordinates
42             my $vector = $ic->cartesians;
43              
44             # calculate and set permanently the Cartesian coordinates
45             # for the atom from the internal coordinates
46             my $vector = $ic->add_cartesians;
47             # same as $atom->coords($ic->cartesians);
48              
49             # dump as string
50             print $ic;
51             # same as print $ic->stringify;
52              
53             =head1 DESCRIPTION
54              
55             This module implements an object class for representing internal coordinates
56             and provides methods for converting them to Cartesian coordinates.
57              
58             For generating an internal coordinate representation (aka a Z-matrix) of a
59             molecule from its Cartesian coordinates, see the
60             L module.
61              
62             This module is part of the PerlMol project, L.
63              
64             =head1 METHODS
65              
66             =over
67              
68             =item my $ic = Chemistry::InternalCoords->new($atom, $len_ref, $len_val,
69             $ang_ref, $ang_val, $dih_ref, $dih_val)
70              
71             Create a new internal coordinate object. $atom is the atom to which the
72             coordinates apply. $len_ref, $ang_ref, and $dih_ref are either atom references
73             or atom indices and are used to specify the distance, angle, and dihedral that
74             are used to define the current position. $len_val, $ang_val, and $dih_val are
75             the values of the distance, angle, and dihedral. The angle and the dihedral are
76             expected to be in degrees.
77              
78             For example,
79              
80             my $ic = Chemistry::InternalCoords->new(
81             $atom, 4, 1.1, 3, 109.5, 2, 180.0
82             );
83              
84             means that $atom is 1.1 distance units from atom 4, the angle $atom-4-3 is
85             109.5 degrees, and the dihedral $atom-4-3-2 is 180.0 degrees.
86              
87             The first three atoms in the molecule don't need all the internal coordinates:
88             the first atom doesn't need anything (except for the atom reference $atom)
89             because it will always be placed at the origin; the second atom only needs
90             a distance, and it will be placed on the X axis; the third atom needs a
91             distance and an angle, and it will be placed on the XY plane.
92              
93             =cut
94              
95             sub new {
96 43     43 1 89809 my $class = shift;
97 43         81 my $atom = shift;
98              
99 43   33     496 my $self = bless {
100             atom => $atom,
101             len_ref => shift, len_val => shift,
102             ang_ref => shift, ang_val => shift,
103             dih_ref => shift, dih_val => shift,
104             }, ref $class || $class;
105              
106 43         170 weaken($self->{atom}); # to avoid memory leaks
107              
108 43         116 for (@$self{qw(len_ref ang_ref dih_ref)}) {
109 129 100 66     2232 if ($_ and not ref) {
110 117         299 $_ = $atom->parent->atoms($_);
111             }
112             }
113              
114 43         589 $self;
115             }
116              
117             =item my ($atom, $distance) = $ic->distance
118              
119             Return the atom reference and distance value contained in the
120             Chemistry::InternalCoords object.
121              
122             =cut
123              
124             sub distance {
125 0     0 1 0 my ($self) = @_;
126 0         0 ($self->{len_ref}, $self->{len_val});
127             }
128              
129             =item my ($atom, $angle) = $ic->angle
130              
131             Return the atom reference and angle value contained in the
132             Chemistry::InternalCoords object.
133              
134             =cut
135              
136             sub angle {
137 0     0 1 0 my ($self) = @_;
138 0         0 ($self->{ang_ref}, $self->{ang_val});
139             }
140              
141             =item my ($atom, $dihedral) = $ic->dihedral
142              
143             Return the atom reference and dihedral value contained in the
144             Chemistry::InternalCoords object.
145              
146             =cut
147              
148             sub dihedral {
149 0     0 1 0 my ($self) = @_;
150 0         0 ($self->{dih_ref}, $self->{dih_val});
151             }
152              
153             =item my $vector = $ic->cartesians
154              
155             Calculate the Cartesian coordinates from an internal coordinate object.
156             Returns a Math::VectorReal object. Note that the Cartesian coordinates of the
157             atoms referenced by the $ic object should already be calculated.
158              
159             =cut
160              
161             sub cartesians {
162 43     43 1 55 my ($self) = @_;
163              
164             #print "cartesians\n";
165 43 100       172 unless ($self->{len_ref}) { # origin
166 2         6 return vector(0, 0, 0);
167             }
168              
169 41 100       287 unless ($self->{ang_ref}) { # second atom; place on X axis
170 2         7 return vector($self->{len_val}, 0, 0);
171             }
172              
173 39 100 66     259 if (!$self->{dih_ref} # third atom; place on XY plane
174             or abs($self->{ang_val} % 180) < 0.005) { # linear angle
175 2         5 my $len = $self->{len_val};
176 2         15 my $ang = deg2rad(180 - $self->{ang_val});
177 2         35 my $d1 = $self->{len_ref}->coords - $self->{ang_ref}->coords;
178 2         133 $d1 = $d1->norm;
179 2         163 my $v = $len * $d1 * cos($ang) + $len * Y * sin($ang);
180             #vector($len * cos($ang), $len * sin($ang), 0) ;
181             #print "len=$len; ang=$ang; v=$v; d1=$d1\n";
182 2         378 return( $v + $self->{len_ref}->coords);
183            
184             }
185              
186             # the real thing...
187 37         472 my $v1 = $self->{dih_ref}->coords; # 'oldest' point
188 37         265 my $v2 = $self->{ang_ref}->coords;
189 37         270 my $v3 = $self->{len_ref}->coords; # 'newest' point
190 37         243 my $d1 = $v1 - $v2;
191 37         1627 my $d2 = $v3 - $v2;
192              
193             # $xp = normal to atoms 1 2 3
194 37         1418 my $xp = $d1 x $d2;
195              
196 37 50       1355 if ($xp->length == 0) { # ill-defined dihedral
197 0         0 my $len = $self->{len_val};
198 0         0 my $ang = deg2rad(180 - $self->{ang_val});
199 0         0 my $d1 = $self->{len_ref}->coords - $self->{ang_ref}->coords;
200 0         0 $d1 = $d1->norm;
201 0         0 my $v = $len * $d1 * cos($ang) + $len * Y * sin($ang);
202 0         0 return( $v + $self->{len_ref}->coords);
203             }
204              
205             # $yp = normal to xp and atoms 2 3
206 37         1275 my $yp = $d2 x $xp;
207              
208 37         1325 my $ang1 = deg2rad($self->{dih_val}); # dihedral
209             # $r = normal to atoms 2 3 4 (where 4 is the new atom)
210             # obtained by rotating $xp through $d2
211 37         433 my $r = $xp->norm * cos($ang1) + $yp->norm * sin($ang1);
212              
213 37         7429 my $ypp = $d2 x $r; # complete new frame of reference
214 37         1406 my $ang2 = deg2rad($self->{ang_val}); # angle
215 37         653 my $d3 = -$d2->norm * cos($ang2) + $ypp->norm * sin($ang2);
216              
217 37         9143 $d3 = $d3 * $self->{len_val}; # mult by distance to $v3
218 37         1746 my $v4 = $v3 + $d3; # define new point
219              
220 37         2043 return $v4;
221             }
222              
223             =item my $vector = $ic->add_cartesians
224              
225             Same as $ic->cartesians, but also adds the newly calculated Cartesian
226             coordinates to the atom. It is just shorthand for the following:
227              
228             $atom->coords($ic->cartesians);
229              
230             The best way of calculating the Cartesian coordinates for an entire molecule,
231             assuming that every atom is defined only in terms of previous atoms (as it
232             should be), is the following:
233              
234             # we have all the internal coords in @ics
235             for my $ic (@ics) {
236             $ic->add_cartesians;
237             }
238              
239             =cut
240              
241             sub add_cartesians {
242 43     43 1 281 my ($self) = @_;
243 43         94 my $v = $self->cartesians;
244 43         289 $self->{atom}->coords($v);
245 43         602 $v;
246             }
247              
248             =item my $string = $ic->stringify
249              
250             Dump the object as a string representation. May be useful for debugging.
251             This method overloads the "" operator.
252              
253             =cut
254              
255             sub stringify {
256 0     0 1   my ($self) = shift;
257 2     2   25 no warnings 'uninitialized';
  2         4  
  2         343  
258 0           my $ret;
259 0           for my $key (qw(len_ref len_val ang_ref ang_val dih_ref dih_val)) {
260 0           $ret .= "$key=($self->{$key}), ";
261             }
262 0           "$ret\n";
263             }
264              
265             =back
266              
267             =head1 VERSION
268              
269             0.18
270              
271             =head1 SEE ALSO
272              
273             L,
274             L, L,
275             L, L.
276              
277             =head1 AUTHOR
278              
279             Ivan Tubert-Brohman
280              
281             =head1 COPYRIGHT
282              
283             Copyright (c) 2004 Ivan Tubert-Brohman. All rights reserved. This program is
284             free software; you can redistribute it and/or modify it under the same terms as
285             Perl itself.
286              
287             =cut
288              
289             1;
290