File Coverage

blib/lib/HackaMol/Roles/AtomGroupRole.pm
Criterion Covered Total %
statement 262 283 92.5
branch 67 76 88.1
condition 1 2 50.0
subroutine 38 39 97.4
pod 22 28 78.5
total 390 428 91.1


line stmt bran cond sub pod time code
1             package HackaMol::Roles::AtomGroupRole;
2             $HackaMol::Roles::AtomGroupRole::VERSION = '0.051';
3             #ABSTRACT: Role for a group of atoms
4 17     17   10714 use Moose::Role;
  17         54  
  17         124  
5 17     17   87382 use Carp;
  17         40  
  17         1595  
6 17     17   4566 use Math::Trig;
  17         107136  
  17         3001  
7 17     17   5454 use Math::Vector::Real;
  17         127851  
  17         1001  
8 17     17   7852 use FileHandle;
  17         149789  
  17         106  
9 17     17   6002 use Scalar::Util 'reftype';
  17         46  
  17         991  
10 17     17   125 use List::Util qw(sum);
  17         39  
  17         55643  
11              
12             #use MooseX::Storage;
13             #with Storage( 'format' => 'JSON', 'io' => 'File', traits => ['OnlyWhenBuilt'] );
14              
15             my $angste_debye = 4.80320;
16              
17             has 'atoms' => (
18             traits => ['Array'],
19             is => 'ro',
20             isa => 'ArrayRef[HackaMol::Atom]',
21             default => sub { [] },
22             handles => {
23             unshift_atoms => 'unshift',
24             push_atoms => 'push',
25             select_atoms => 'grep',
26             map_atoms => 'map',
27             sort_atoms => 'sort',
28             get_atoms => 'get',
29             set_atoms => 'set',
30             insert_atoms => 'insert',
31             delete_atoms => 'delete',
32             all_atoms => 'elements',
33             count_atoms => 'count',
34             natoms => 'count',
35             clear_atoms => 'clear',
36             has_atoms => 'count',
37             },
38             lazy => 1,
39             );
40              
41             has 'is_constrained' => (
42             is => 'rw',
43             isa => 'Bool',
44             lazy => 1,
45             default => 0,
46             );
47              
48             has 'qcat_print' => (
49             is => 'rw',
50             isa => 'Bool',
51             lazy => 1,
52             default => 0,
53             );
54              
55             has 'info' => (
56             is => 'rw',
57             isa => 'Str',
58             lazy => 1,
59             default => "",
60             );
61              
62             sub _lsq_slope {
63             # private function used for each coordinate
64             # translation of tcl function written by Justin Gullingsrud @ uiuc.edu
65             # algorithm reference: Bevington
66             # Fit the points x to x = ai + b, i=0...N-1, and return the value of a
67             # a = 12/( (N(N^2 - 1)) ) sum[ (i-(N-1)/2) * xi]
68 11   50 11   2809 my $xis = shift || die "expecting array_ref of cartesian coordinate [x y or z]";
69 11         23 my $n = @$xis;
70 11         18 my $sum = 0;
71 11         30 my $d = ($n-1)/2;
72 11         16 my $i = 0;
73 11         144 $sum += ($i++ - $d)*$_ foreach @$xis;
74 11         41 return $sum * 12 / ($n * ( $n * $n - 1)) ;
75             }
76              
77             sub centered_vector {
78 2     2 1 21 my $self = shift;
79 2         5 my $centered_flag = shift;
80 2         77 my @mvrs = map {$_->xyz} $self->all_atoms;
  136         293  
81 2 50       15 die "2 atoms needed for a centered_vector" unless @mvrs > 1;
82 2         6 my @x = map { $_->[0] } @mvrs;
  136         200  
83 2         6 my @y = map { $_->[1] } @mvrs;
  136         232  
84 2         5 my @z = map { $_->[2] } @mvrs;
  136         208  
85 2         8 my $mvr = V( map { _lsq_slope($_) } \@x,\@y,\@z);
  6         15  
86 2         37 return $mvr->versor;
87             }
88              
89             sub calc_bfps {
90              
91             # this should be rerun for each selection
92             #10.1016/j.jmb.2015.09.024
93 2     2 0 11 my $self = shift;
94 2 50       75 unless ( $self->count_atoms > 1 ) {
95 0         0 warn "calc_bfps> group not large enough\n";
96 0         0 return;
97             }
98 2         81 my @atoms = $self->all_atoms;
99 2         5 my @bfacts = map { $_->bfact } @atoms;
  6         145  
100 2         14 my $bfact_mean = sum(@bfacts) / @bfacts;
101 2         3 my $sd = 0;
102 2         13 $sd += ( $_ - $bfact_mean )**2 foreach @bfacts;
103 2 50       17 unless ( $sd > 0 ) {
104 0         0 warn "calc_bfps> no variance in the group bfactors\n";
105 0         0 return;
106             }
107 2         6 my $bfact_std = sqrt( $sd / ( @bfacts - 1 ) );
108 2         5 foreach my $atom (@atoms) {
109 6         153 my $bfp = ( $atom->bfact - $bfact_mean ) / $bfact_std;
110 6         144 $atom->bfp($bfp);
111             }
112 2         5 return map { $_->bfp } @atoms;
  6         139  
113             }
114              
115             sub dipole {
116 9     9 1 64 my $self = shift;
117 9 100       377 return ( V(0) ) unless ( $self->count_atoms );
118 8         277 my @atoms = $self->all_atoms;
119 8         30 my @vectors = grep { defined } map { $_->get_coords( $_->t ) } @atoms;
  1748         2566  
  1748         39509  
120 8         72 my @charges = grep { defined } map { $_->get_charges( $_->t ) } @atoms;
  1748         2501  
  1748         40220  
121 8         103 my $dipole = V( 0, 0, 0 );
122 8 100       31 if ( $#vectors != $#charges ) {
123 1         13 carp
124             "build_dipole> mismatch number of coords and charges. all defined?";
125 1         472 return $dipole;
126             }
127 7         2381 $dipole += $vectors[$_] * $charges[$_] foreach 0 .. $#charges;
128 7         220 return ($dipole);
129             }
130              
131             sub COM {
132 40     40 1 1036 my $self = shift;
133 40 100       1622 return ( V(0) ) unless ( $self->count_atoms );
134 39         1363 my @atoms = $self->all_atoms;
135 39         139 my @m_vectors = map { $_->mass * $_->get_coords( $_->t ) } @atoms;
  7502         182678  
136 39         251 my $com = V( 0, 0, 0 );
137 39         2830 $com += $_ foreach @m_vectors;
138 39         202 return ( $com / $self->total_mass );
139             }
140              
141             sub center {
142 4     4 0 37 my $self = shift;
143 4 50       153 return ( V(0) ) unless ( $self->count_atoms );
144 4         146 my @atoms = $self->all_atoms;
145 4         14 my @vectors = map { $_->get_coords( $_->t ) } @atoms;
  150         3701  
146 4         25 my $center = V( 0, 0, 0 );
147 4         179 $center += $_ foreach @vectors;
148 4         180 return ( $center / $self->count_atoms );
149             }
150              
151             sub COZ {
152 6     6 1 22 my $self = shift;
153 6 100       243 return ( V(0) ) unless ( $self->count_atoms );
154 5         202 my @atoms = $self->all_atoms;
155 5         27 my @z_vectors = map { $_->Z * $_->get_coords( $_->t ) } @atoms;
  1161         23459  
156 5         42 my $coz = V( 0, 0, 0 );
157 5         440 $coz += $_ foreach @z_vectors;
158 5         28 return ( $coz / $self->total_Z );
159             }
160              
161             sub gt {
162              
163             #set group time
164 39     39 1 15286 my $self = shift;
165 39         82 my $t = shift;
166 39         111 $self->do_forall( 't', $t );
167             }
168              
169             sub do_forall {
170 63     63 0 7237 my $self = shift;
171 63         134 my $method = shift;
172 63 100       157 do { carp "doing nothing for all"; return } unless (@_);
  1         12  
  1         463  
173 62         2471 my @atoms = $self->all_atoms;
174 62         1796 $_->$method(@_) foreach @atoms;
175             }
176              
177             sub total_charge {
178 5     5 1 17 my $self = shift;
179 5 100       212 return (0) unless ( $self->count_atoms );
180 4         142 my @atoms = $self->all_atoms;
181 4         26 my @charges = map { $_->get_charges( $_->t ) } @atoms;
  1723         40491  
182 4         25 my $sum = 0;
183 4         228 $sum += $_ foreach @charges;
184 4         202 return ($sum);
185             }
186              
187             sub total_mass {
188 48     48 1 89 my $self = shift;
189 48 100       1829 return (0) unless ( $self->count_atoms );
190 47         1686 my @masses = map { $_->mass } $self->all_atoms;
  10647         247097  
191 47         431 my $sum = 0;
192 47         1478 $sum += $_ foreach @masses;
193 47         2150 return ($sum);
194             }
195              
196             sub total_Z {
197 7     7 1 27 my $self = shift;
198 7 100       283 return (0) unless ( $self->count_atoms );
199 6         230 my @Zs = map { $_->Z } $self->all_atoms;
  1165         22796  
200 6         57 my $sum = 0;
201 6         145 $sum += $_ foreach @Zs;
202 6         269 return ($sum);
203             }
204              
205             sub dipole_moment {
206 6     6 1 17 my $self = shift;
207 6         31 return ( abs( $self->dipole ) * $angste_debye );
208             }
209              
210             sub bin_atoms {
211              
212             # Called with no arguments.
213             # Returns a hash with a count of unique atom symbols
214 13     13 1 28 my $self = shift;
215 13         48 my $bin_hr = $self->bin_this('symbol');
216 13         33 return ($bin_hr);
217             }
218              
219             sub count_unique_atoms {
220 5     5 1 14 my $self = shift;
221 5         25 my $bin_hr = $self->bin_atoms;
222 5         12 return ( scalar( keys %{$bin_hr} ) );
  5         55  
223             }
224              
225             sub bin_atoms_name {
226              
227             # return something like C4H10 sort in order of descending Z
228 7     7 1 19 my $self = shift;
229 7         24 my $bin_hr = $self->bin_atoms;
230 7         16 my $z_hr;
231 7         273 $z_hr->{ $_->symbol } = $_->Z foreach $self->all_atoms;
232              
233             my @names = map {
234 9         32 my $name = $_ . $bin_hr->{$_};
235 9         26 $name =~ s/(\w+)1$/$1/;
236 9         40 $name; # substitue 1 away?
237             }
238             sort {
239 2         8 $z_hr->{$b} <=> $z_hr->{$a} # sort by Z! see above...
240 7         118 } keys %{$bin_hr};
  7         163  
241 7         98 return join( '', @names );
242             }
243              
244             sub translate {
245 5     5 1 222 my $self = shift;
246 5 100       31 my $tvec = shift or croak "pass MVR translation vector";
247 4         92 my $tf = shift;
248              
249 4         272 my @atoms = $self->all_atoms;
250 4 100       27 do { carp "no atoms to translate"; return } unless (@atoms);
  1         15  
  1         490  
251 3 100       60 $tf = $atoms[0]->t unless ( defined($tf) );
252              
253 3         17 foreach my $at (@atoms) {
254 69         163 my $v = $at->xyz + $tvec;
255 69         2347 $at->set_coords( $tf, $v );
256             }
257             }
258              
259             sub rotate {
260              
261             #rotate about origin. having origin allows rotation of subgroup
262             #without having to translate everything.
263 8     8 1 957 my $self = shift;
264 8 100       88 my $rvec = shift or croak "pass MVR rotation vector";
265 7 100       166 my $ang = shift or croak "pass rotation angle";
266 6 100       49 my $orig = shift or croak "pass MVR origin";
267 5         107 my $tf = shift;
268              
269 5         209 my @atoms = $self->all_atoms;
270 5         138 my $t = $atoms[0]->t;
271 5 100       65 $tf = $t unless ( defined($tf) );
272 5         28 $rvec = $rvec->versor; #unit vector
273              
274 5         24 my @cor = map { $_->get_coords($t) - $orig } @atoms;
  192         6325  
275 5         27 my @rcor = $rvec->rotate_3d( deg2rad($ang), @cor );
276              
277 5         1476 $atoms[$_]->set_coords( $tf, $rcor[$_] + $orig ) foreach 0 .. $#rcor;
278             }
279              
280             sub rotate_translate {
281              
282             # args:
283             # 1. rotation matrix (3x3): ar, each column (cx,cy,cz, below) is a Math::Vector::Real
284             # 2. translate vector, MVR
285             # r' = x*cx + y*cy + z*cz + v_trans
286             # 3. t final, the final t location for transformed coordinates [defaults to current t]
287 0     0 0 0 my $self = shift;
288 0         0 my $rmat = shift;
289 0         0 my $trns = shift;
290 0         0 my $tf = shift;
291 0         0 my @atoms = $self->all_atoms;
292 0         0 my ( $cx, $cy, $cz ) = @{$rmat};
  0         0  
293              
294 0         0 my $t = $atoms[0]->t;
295 0 0       0 $tf = $t unless ( defined($tf) );
296              
297 0         0 foreach my $atom (@atoms) {
298 0         0 my $xyz = $atom->xyz;
299              
300             #my ($x,$y,$z) = @{$xyz};
301 0         0 my $xr = $cx * $xyz;
302 0         0 my $yr = $cy * $xyz;
303 0         0 my $zr = $cz * $xyz;
304              
305             #my $xyz_new = $x*$cx + $y*$cy + $z*$cz + $trns;
306 0         0 my $xyz_new = V( $xr, $yr, $zr ) + $trns;
307 0         0 $atom->set_coords( $tf, $xyz_new );
308             }
309              
310             }
311              
312             sub fix_serial {
313 3     3 1 1258 my @atoms = shift->all_atoms;
314 3         8 my $offset = shift;
315 3 100       8 $offset = 1 unless defined($offset);
316 3         16 $atoms[$_]->{serial} = $_ + $offset foreach ( 0 .. $#atoms );
317 3         38 return $offset;
318             }
319              
320             sub print_xyz_ts {
321 4     4 1 2645 _print_ts( 'print_xyz', @_ );
322             }
323              
324             sub print_pdb_ts {
325 2     2 1 2105 _print_ts( 'print_pdb', @_ );
326             }
327              
328             sub _print_ts {
329              
330             #use one sub for xyz_ts and pdb_ts writing
331 6     6   13 my $print_method = shift;
332              
333             # two args: \@ts, optional filename
334 6         8 my $self = shift;
335 6         9 my $ts = shift;
336 6 100       15 unless ( defined($ts) ) {
337 1         17 croak "must pass arrayref containing ts";
338             }
339 5         13 my @ts = @$ts;
340 5 100       13 unless ( scalar(@ts) ) {
341 1         10 croak "must pass array with atleast one t";
342             }
343 4         11 my $tmax = $self->tmax;
344 4         10 my $nt = grep { $_ > $tmax } @ts;
  26         45  
345 4 100       33 croak "$nt ts out of bounds" if $nt;
346              
347 2         8 my $tnow = $self->what_time;
348              
349             # take the first out of the loop to setup fh
350 2         9 $self->gt( shift @ts );
351 2         9 my $fh = $self->$print_method(@_);
352              
353 2         8 foreach my $t (@ts) {
354 4         13 $self->gt($t);
355 4         14 $fh = $self->$print_method($fh);
356             }
357              
358             # return to original t
359 2         8 $self->gt($tnow);
360             }
361              
362             sub bin_this {
363              
364             #return hash{$_->method}++
365 34     34 1 59 my $self = shift;
366 34         66 my $method = shift;
367              
368 34 100       1357 return ( {} ) unless $self->count_atoms;
369              
370 32         1095 my @atoms = $self->all_atoms;
371              
372             # just test the first one...
373 32 50       192 croak "Atom does not do $method" unless $atoms[0]->can($method);
374              
375 32         59 my $bin;
376 32         76 $bin->{$_}++ foreach ( map { $_->$method } @atoms );
  4155         95921  
377 32         441 return ($bin);
378              
379             }
380              
381             sub tmax {
382              
383             # still not the best implementation! what about atoms without coords?
384 12     12 1 217 my $self = shift;
385 12         44 my $tbin = $self->bin_this('count_coords');
386 12         57 my @ts = keys(%$tbin);
387 12 100       56 croak "tmax differences within group" if ( scalar(@ts) > 1 );
388 11 100       104 $ts[0] ? return $ts[0] - 1 : return 0;
389             }
390              
391             sub what_time {
392 9     9 1 84 my $self = shift;
393 9         27 my $tbin = $self->bin_this('t');
394 9         28 my @ts = keys(%$tbin);
395 9 100       42 carp "what_time> t differences within group!!" if ( scalar(@ts) > 1 );
396 9         594 return $ts[0];
397             }
398              
399             sub string_xyz {
400 11     11 0 31 my $self = shift;
401 11         20 my $add_info_to_blank = shift;
402              
403 11         15 my $string;
404 11 50       423 $string .= $self->count_atoms . "\n" unless $self->qcat_print;
405 11 100       33 $string .= $add_info_to_blank if ( defined($add_info_to_blank) );
406 11         22 $string .= "\n";
407              
408 11         382 foreach my $at ( $self->all_atoms ) {
409             $string .= sprintf( "%3s %10.6f %10.6f %10.6f\n",
410 936         22611 $at->symbol, @{ $at->get_coords( $at->t ) } );
  936         23027  
411             }
412 11         364 return $string;
413             }
414              
415             sub print_xyz {
416 10     10 1 5574 my $self = shift;
417 10         28 my $fh = _open_file_unless_fh(shift);
418              
419 9         38 print $fh $self->string_xyz;
420              
421             # my @atoms = $self->all_atoms;
422             #print $fh $self->count_atoms . "\n\n" unless $self->qcat_print;
423             #foreach my $at (@atoms) {
424             # printf $fh (
425             # "%3s %10.6f %10.6f %10.6f\n",
426             # $at->symbol, @{ $at->get_coords( $at->t ) }
427             # );
428             #}
429              
430 9         157 return ($fh); # returns filehandle for future writing
431              
432             }
433              
434             sub string_pdb {
435 6     6 0 10 my $self = shift;
436              
437 6         14 my $t = $self->what_time;
438 6         205 my @atoms = $self->all_atoms;
439              
440 6         12 my $string;
441 6 100       182 $string .= sprintf( "MODEL %2i\n", $t + 1 ) unless $self->qcat_print;
442              
443 6         14 my $atform =
444             "%-6s%5i %-3s%1s%3s %1s%4s%1s %8.3f%8.3f%8.3f%6.2f%6.2f %4s%2s\n";
445              
446 6         13 foreach my $at (@atoms) {
447              
448             # front pad one space if name length is < 4
449 18         45 my $form = $atform;
450 18 50       507 if ( length $at->name > 3 ) {
451 0         0 $form =
452             "%-6s%5i %4s%1s%3s %1s%4s%1s %8.3f%8.3f%8.3f%6.2f%6.2f %4s%2s\n";
453             }
454             $string .= sprintf(
455             $form,
456             (
457 144         3668 map { $at->$_ }
458             qw (
459             record_name
460             serial
461             name
462             altloc
463             resname
464             chain
465             resid
466             icode
467             )
468             ),
469 18         40 @{ $at->get_coords( $at->t ) },
  18         456  
470             $at->occ,
471             $at->bfact,
472             $at->segid,
473             $at->symbol, # $at->charge
474             );
475             }
476 6 100       174 $string .= "ENDMDL\n" unless $self->qcat_print;
477 6         198 return $string;
478             }
479              
480             sub print_pdb {
481 6     6 1 2045 my $self = shift;
482 6         23 my $fh = _open_file_unless_fh(shift);
483              
484 6         15 print $fh $self->string_pdb;
485              
486             # my @atoms = $self->all_atoms;
487             # printf $fh ( "MODEL %2i\n", $atoms[0]->t + 1 ) unless $self->qcat_print;
488             # my $atform = "%-6s%5i %-3s%1s%3s %1s%4i%1s %8.3f%8.3f%8.3f%6.2f%6.2f %4s%2s\n";
489              
490             # foreach my $at (@atoms) {
491             # # front pad one space if name length is < 4
492             # my $form = $atform;
493             # if (length $at->name > 3){
494             # $form = "%-6s%5i %4s%1s%3s %1s%4i%1s %8.3f%8.3f%8.3f%6.2f%6.2f %4s%2s\n"
495             # }
496             # printf $fh (
497             # $form,
498             # (
499             # map { $at->$_ }
500             # qw (
501             # record_name
502             # serial
503             # name
504             # altloc
505             # resname
506             # chain
507             # resid
508             # icode
509             # )
510             # ),
511             # @{ $at->get_coords( $at->t ) },
512             # $at->occ,
513             # $at->bfact,
514             # $at->segid,
515             # $at->symbol, # $at->charge
516             # );
517              
518             # }
519             # print $fh "ENDMDL\n" unless $self->qcat_print;
520              
521 6         43 return ($fh); # returns filehandle for future writing
522             }
523              
524             sub _open_file_unless_fh {
525              
526 16     16   33 my $file = shift; # could be file or filehandle
527              
528 16         34 my $fh = \*STDOUT; # default to standard out
529             # if argument is passed, check if filehandle
530 16 100       41 if ( defined($file) ) {
531 9 100       36 if ( ref($file) ) {
532 6 100       29 if ( reftype($file) eq "GLOB" ) {
533 5         11 $fh = $file;
534             }
535             else {
536 1         26 croak "trying write to reference that is not a GLOB";
537             }
538             }
539             else {
540 3 100       79 carp "overwrite $file" if ( -e $file );
541 3         577 $fh = FileHandle->new(">$file");
542             }
543             }
544              
545 15         439 return ($fh);
546             }
547              
548 17     17   166 no Moose::Role;
  17         74  
  17         180  
549              
550             1;
551              
552             __END__
553              
554             =pod
555              
556             =head1 NAME
557              
558             HackaMol::Roles::AtomGroupRole - Role for a group of atoms
559              
560             =head1 VERSION
561              
562             version 0.051
563              
564             =head1 SYNOPSIS
565              
566             use HackaMol::AtomGroup;
567             use HackaMol::Atom;
568              
569             my $atom1 = HackaMol::Atom->new(
570             name => 'O1',
571             coords => [ V( 2.05274, 0.01959, -0.07701 ) ],
572             Z => 8,
573             );
574            
575             my $atom2 = HackaMol::Atom->new(
576             name => 'H1',
577             coords => [ V( 1.08388, 0.02164, -0.12303 ) ],
578             Z => 1,
579             );
580            
581             my $atom3 = HackaMol::Atom->new(
582             name => 'H2',
583             coords => [ V( 2.33092, 0.06098, -1.00332 ) ],
584             Z => 1,
585             );
586            
587             $atom1->push_charges(-0.834);
588             $_->push_charges(0.417) foreach ($atom1, $atom2);
589            
590             # instance of class that consumes the AtomGroupRole
591            
592             my $group = HackaMol::AtomGroup->new(atoms=> [$atom1,$atom2,$atom3]);
593            
594             print $group->count_atoms . "\n"; #3
595             print $group->total_charge . "\n"; # 0
596             print $group->total_mass . "\n";
597            
598             my @atoms = $group->all_atoms;
599            
600             print $group->dipole_moment . "\n";
601            
602             $group->do_forall('push_charges',0);
603             $group->do_forall('push_coords',$group->COM);
604              
605             $group->gt(1); # same as $group->do_forall('t',1);
606            
607             print $group->dipole_moment . "\n";
608             print $group->bin_atoms_name . "\n";
609             print $group->unique_atoms . "\n";
610              
611             $group->translate(V(10,0,0));
612              
613             $group->rotate( V(1,0,0),
614             180,
615             V(0,0,0));
616            
617             $group->print_xyz ; #STDOUT
618              
619             my $fh = $group->print_xyz("hackagroup.xyz"); #returns filehandle
620             $group->print_xyz($fh) foreach (1 .. 9); # boring VMD movie with 10 frames
621              
622             =head1 DESCRIPTION
623              
624             The HackaMol AtomGroupRole class provides core methods and attributes for
625             consuming classes that use groups of atoms. The original implementation of
626             this role relied heavily on attributes, builders, and clearers. Such an approach
627             naturally gives fast lookup tables, but the ability to change atoms and coordinates
628             made the role to difficult. Such an approach may be pursued again (without changing
629             the API) in the future after the API has matured. The AtomGroupRole calculates all
630             values for atoms using their own t attributes.
631              
632             =head1 METHODS
633              
634             =head2 do_for_all
635              
636             pass method and arguments down to atoms in group
637              
638             $group->do_for_all('t',1); #sets t to 1 for all atoms
639              
640             =head2 gt
641              
642             integer argument. wraps do_for_all for setting time within group
643              
644             $group->gt(1);
645              
646             =head2 dipole
647              
648             no arguments. return dipole calculated from charges and coordinates as Math::Vector::Real object
649              
650             =head2 COM
651              
652             no arguments. return center of mass calculated from masses and coordinates as Math::Vector::Real object
653              
654             =head2 COZ
655              
656             no arguments. return center of nuclear charge calculated from Zs and coordinates as Math::Vector::Real object
657              
658             =head2 total_charge
659              
660             no arguments. return sum of atom charges.
661              
662             =head2 total_mass
663              
664             no arguments. return sum of atom masses.
665              
666             =head2 total_Z
667              
668             no arguments. return sum of Zs.
669              
670             =head2 dipole_moment
671              
672             no arguments. returns the norm of the dipole in debye (assuming charges in electrons, AKMA)
673              
674             =head2 bin_atoms
675              
676             Called with no arguments. Returns a hash with a count for each unique atom symbol.
677              
678             =head2 count_unique_atoms
679              
680             no arguments. returns the number of unique atoms
681              
682             =head2 bin_atoms_name
683              
684             no arguments. returns a string summary of the atoms in the group sorted by decreasing atomic number. For example; OH2 for water or O2H2 for peroxide.
685              
686             =head2 tmax
687              
688             return (count_coords-1) if > 0; return 0 otherwise; croaks if not all atoms share the same tmax.
689              
690             =head2 translate
691              
692             requires L<Math::Vector::Real> vector argument. Optional argument: integer tf.
693              
694             Translates all atoms in group by the MVR vector. Pass tf to the translate
695             method to store new coordinates in tf rather than atom->t.
696              
697             =head2 rotate
698              
699             requires Math::Vector::Real vector, an angle (in degrees), and a MVR vector
700             origin as arguments. Optional argument: integer tf.
701              
702             Rotates all atoms in the group around the MVR vector. Pass tf to the translate
703             method to store new coordinates in tf rather than atom->t.
704              
705             =head2 print_xyz
706              
707             optional argument: filename or filehandle. with no argument, prints xyz formatted output to STDOUT. pass
708             a filename and an xyz file with that name will be written or overwritten (with warning). pass filehandle
709             for continuous writing to an open filehandle.
710              
711             =head2 print_xyz_ts
712              
713             argument: array_ref containing the values of t to be used for printing.
714             optional argument: filename or filehandle for writing out to file. For example,
715              
716             $mol->print_xyz_ts([0 .. 3, 8, 4], 'fun.xyz');
717              
718             will write the coordinates for all group atoms at t=0,1,2,3,8,4 to a file, in
719             that order.
720              
721             =head2 print_pdb
722              
723             same as print_xyz, but for pdb formatted output
724              
725             =head2 print_pdb_ts
726              
727             same as print_xyz_ts, but for pdb formatted output
728              
729             =head2 bin_this
730              
731             argument: Str , return hash_ref of binned $self->Str.
732              
733             $hash_ref{$_}++ foreach ( map {$_->$Str} $self->all_atoms );
734              
735             =head2 what_time
736              
737             returns the current setting of t by checking against all members of group.
738              
739             =head2 fix_serial
740              
741             argument, optional: Int, offset for resetting the serial number of atoms.
742             Returns the offset.
743              
744             $group->fix_serial(0); # serial starts from zero
745              
746             =head2 centered_vector
747              
748             calculates least squares fitted vector for the AtomGroup. Returns normalized Math::Vector::Real
749             object with origin V(0,0,0).
750              
751             $mvr = $group->centered_vector; # unit vector origin 0,0,0
752             # place two mercury atoms along the vector to visualize the fit
753             my $hg_1 = HackaMol::Atom->new(Z => 80, coords => [$group->center]);
754             my $hg_2 = HackaMol::Atom->new(Z => 80, coords => [$group->center + $mvr]);
755              
756             =head1 ARRAY METHODS
757              
758             =head2 push_atoms, get_atoms, set_atoms, all_atoms, count_atoms, clear_atoms
759              
760             ARRAY traits for the atoms attribute, respectively: push, get, set, elements, count, clear
761              
762             =head2 push_atoms, unshift_atoms
763              
764             push atom on to the end of the atoms array
765             or
766             unshift_atoms on to the front of the array
767              
768             $group->push_atoms($atom1, $atom2, @otheratoms);
769             $group->unshift_atoms($atom1, $atom2, @otheratoms); # maybe in reverse
770              
771             =head2 all_atoms
772              
773             returns array of all elements in atoms array
774              
775             print $_->symbol, "\n" foreach $group->all_atoms;
776              
777             =head2 get_atoms
778              
779             return element by index from atoms array
780              
781             print $group->get_atoms(1); # returns $atom2 from above
782              
783             =head2 set_atoms
784              
785             set atoms array by index
786              
787             $group->set_atoms(1, $atom1);
788              
789             =head2 count_atoms
790              
791             return number of atoms in group
792              
793             print $group->count_atoms;
794              
795             =head2 clear_atoms
796              
797             clears atoms array
798              
799             =head1 ATTRIBUTES
800              
801             =head2 atoms
802              
803             isa ArrayRef[Atom] that is lazy with public ARRAY traits described in ARRAY_METHODS
804              
805             =head2 qcat_print
806              
807             isa Bool that has a lazy default value of 0. if qcat_print, print all atoms coordinates in one go (no model breaks)
808              
809             =head1 SEE ALSO
810              
811             =over 4
812              
813             =item *
814              
815             L<HackaMol::AtomGroup>
816              
817             =item *
818              
819             L<HackaMol::Bond>
820              
821             =item *
822              
823             L<HackaMol::Angle>
824              
825             =item *
826              
827             L<HackaMol::Dihedral>
828              
829             =back
830              
831             =head1 AUTHOR
832              
833             Demian Riccardi <demianriccardi@gmail.com>
834              
835             =head1 COPYRIGHT AND LICENSE
836              
837             This software is copyright (c) 2017 by Demian Riccardi.
838              
839             This is free software; you can redistribute it and/or modify it under
840             the same terms as the Perl 5 programming language system itself.
841              
842             =cut