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