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.052';
2             #ABSTRACT: Role for a group of atoms
3             use Moose::Role;
4 17     17   11826 use Carp;
  17         44  
  17         124  
5 17     17   80348 use Math::Trig;
  17         39  
  17         1144  
6 17     17   6812 use Math::Vector::Real;
  17         99188  
  17         2729  
7 17     17   6025 use FileHandle;
  17         116436  
  17         1059  
8 17     17   7751 use Scalar::Util 'reftype';
  17         134116  
  17         98  
9 17     17   5321 use List::Util qw(sum);
  17         38  
  17         889  
10 17     17   115  
  17         38  
  17         48493  
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   1849 my $sum = 0;
70 11         16 my $d = ($n-1)/2;
71 11         35 my $i = 0;
72 11         20 $sum += ($i++ - $d)*$_ foreach @$xis;
73 11         13 return $sum * 12 / ($n * ( $n * $n - 1)) ;
74 11         111 }
75 11         53  
76             my $self = shift;
77             my $centered_flag = shift;
78             my @mvrs = map {$_->xyz} $self->all_atoms;
79 2     2 1 14 die "2 atoms needed for a centered_vector" unless @mvrs > 1;
80 2         3 my @x = map { $_->[0] } @mvrs;
81 2         59 my @y = map { $_->[1] } @mvrs;
  136         229  
82 2 50       10 my @z = map { $_->[2] } @mvrs;
83 2         5 my $mvr = V( map { _lsq_slope($_) } \@x,\@y,\@z);
  136         155  
84 2         6 return $mvr->versor;
  136         159  
85 2         5 }
  136         155  
86 2         6  
  6         10  
87 2         24  
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 13 }
95 2 50       62 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         56 $sd += ( $_ - $bfact_mean )**2 foreach @bfacts;
100 2         4 unless ( $sd > 0 ) {
  6         119  
101 2         9 warn "calc_bfps> no variance in the group bfactors\n";
102 2         4 return;
103 2         11 }
104 2 50       7 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         6 }
109 2         3 return map { $_->bfp } @atoms;
110 6         136 }
111 6         152  
112             my $self = shift;
113 2         6 return ( V(0) ) unless ( $self->count_atoms );
  6         123  
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 54 my $dipole = V( 0, 0, 0 );
118 9 100       328 if ( $#vectors != $#charges ) {
119 8         248 carp
120 8         32 "build_dipole> mismatch number of coords and charges. all defined?";
  1748         2356  
  1748         35349  
121 8         64 return $dipole;
  1748         2295  
  1748         35742  
122 8         90 }
123 8 100       34 $dipole += $vectors[$_] * $charges[$_] foreach 0 .. $#charges;
124 1         11 return ($dipole);
125             }
126 1         384  
127             my $self = shift;
128 7         2195 return ( V(0) ) unless ( $self->count_atoms );
129 7         277 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 826 return ( $com / $self->total_mass );
134 40 100       1335 }
135 39         1108  
136 39         126 my $self = shift;
  7502         161462  
137 39         212 return ( V(0) ) unless ( $self->count_atoms );
138 39         2378 my @atoms = $self->all_atoms;
139 39         182 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 22 }
144 4 50       135  
145 4         150 my $self = shift;
146 4         10 return ( V(0) ) unless ( $self->count_atoms );
  150         3029  
147 4         18 my @atoms = $self->all_atoms;
148 4         129 my @z_vectors = map { $_->Z * $_->get_coords( $_->t ) } @atoms;
149 4         134 my $coz = V( 0, 0, 0 );
150             $coz += $_ foreach @z_vectors;
151             return ( $coz / $self->total_Z );
152             }
153 6     6 1 18  
154 6 100       309  
155 5         182 #set group time
156 5         32 my $self = shift;
  1161         21799  
157 5         42 my $t = shift;
158 5         372 $self->do_forall( 't', $t );
159 5         27 }
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 12467 $_->$method(@_) foreach @atoms;
166 39         59 }
167 39         85  
168             my $self = shift;
169             return (0) unless ( $self->count_atoms );
170             my @atoms = $self->all_atoms;
171 63     63 0 5931 my @charges = map { $_->get_charges( $_->t ) } @atoms;
172 63         92 my $sum = 0;
173 63 100       143 $sum += $_ foreach @charges;
  1         10  
  1         386  
174 62         2028 return ($sum);
175 62         1452 }
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       189 my $sum = 0;
181 4         156 $sum += $_ foreach @masses;
182 4         19 return ($sum);
  1723         35275  
183 4         29 }
184 4         196  
185 4         174 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 100 $sum += $_ foreach @Zs;
190 48 100       1614 return ($sum);
191 47         1382 }
  10647         211741  
192 47         695  
193 47         1279 my $self = shift;
194 47         2233 return ( abs( $self->dipole ) * $angste_debye );
195             }
196              
197              
198 7     7 1 15 # Called with no arguments.
199 7 100       239 # Returns a hash with a count of unique atom symbols
200 6         177 my $self = shift;
  1165         19965  
201 6         78 my $bin_hr = $self->bin_this('symbol');
202 6         132 return ($bin_hr);
203 6         276 }
204              
205             my $self = shift;
206             my $bin_hr = $self->bin_atoms;
207 6     6 1 14 return ( scalar( keys %{$bin_hr} ) );
208 6         26 }
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 28 $z_hr->{ $_->symbol } = $_->Z foreach $self->all_atoms;
216 13         38  
217 13         37 my @names = map {
218             my $name = $_ . $bin_hr->{$_};
219             $name =~ s/(\w+)1$/$1/;
220             $name; # substitue 1 away?
221 5     5 1 13 }
222 5         21 sort {
223 5         8 $z_hr->{$b} <=> $z_hr->{$a} # sort by Z! see above...
  5         50  
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         21 my $tf = shift;
231 7         13  
232 7         219 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         29  
236 9         27 foreach my $at (@atoms) {
237 9         31 my $v = $at->xyz + $tvec;
238             $at->set_coords( $tf, $v );
239             }
240 2         9 }
241 7         101  
  7         155  
242 7         78  
243             #rotate about origin. having origin allows rotation of subgroup
244             #without having to translate everything.
245             my $self = shift;
246 5     5 1 339 my $rvec = shift or croak "pass MVR rotation vector";
247 5 100       29 my $ang = shift or croak "pass rotation angle";
248 4         91 my $orig = shift or croak "pass MVR origin";
249             my $tf = shift;
250 4         197  
251 4 100       17 my @atoms = $self->all_atoms;
  1         11  
  1         406  
252 3 100       51 my $t = $atoms[0]->t;
253             $tf = $t unless ( defined($tf) );
254 3         7 $rvec = $rvec->versor; #unit vector
255 69         135  
256 69         1851 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 925 # 1. rotation matrix (3x3): ar, each column (cx,cy,cz, below) is a Math::Vector::Real
265 8 100       34 # 2. translate vector, MVR
266 7 100       140 # r' = x*cx + y*cy + z*cz + v_trans
267 6 100       23 # 3. t final, the final t location for transformed coordinates [defaults to current t]
268 5         82 my $self = shift;
269             my $rmat = shift;
270 5         167 my $trns = shift;
271 5         116 my $tf = shift;
272 5 100       22 my @atoms = $self->all_atoms;
273 5         18 my ( $cx, $cy, $cz ) = @{$rmat};
274              
275 5         13 my $t = $atoms[0]->t;
  192         5017  
276 5         29 $tf = $t unless ( defined($tf) );
277              
278 5         1219 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 1076 croak "must pass arrayref containing ts";
315 3         6 }
316 3 100       7 my @ts = @$ts;
317 3         16 unless ( scalar(@ts) ) {
318 3         6 croak "must pass array with atleast one t";
319             }
320             my $tmax = $self->tmax;
321             my $nt = grep { $_ > $tmax } @ts;
322 4     4 1 2496 croak "$nt ts out of bounds" if $nt;
323              
324             my $tnow = $self->what_time;
325              
326 2     2 1 1902 # 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   12 $fh = $self->$print_method($fh);
333             }
334              
335 6         9 # return to original t
336 6         8 $self->gt($tnow);
337 6 100       15 }
338 1         24  
339              
340 5         10 #return hash{$_->method}++
341 5 100       9 my $self = shift;
342 1         8 my $method = shift;
343              
344 4         10 return ( {} ) unless $self->count_atoms;
345 4         8  
  26         35  
346 4 100       25 my @atoms = $self->all_atoms;
347              
348 2         7 # 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         10 $bin->{$_}++ foreach ( map { $_->$method } @atoms );
353             return ($bin);
354 2         6  
355 4         14 }
356 4         11  
357              
358             # still not the best implementation! what about atoms without coords?
359             my $self = shift;
360 2         8 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 50 my $self = shift;
367 34         51 my $tbin = $self->bin_this('t');
368             my @ts = keys(%$tbin);
369 34 100       1149 carp "what_time> t differences within group!!" if ( scalar(@ts) > 1 );
370             return $ts[0];
371 32         1006 }
372              
373             my $self = shift;
374 32 50       181 my $add_info_to_blank = shift;
375              
376 32         48 my $string;
377 32         67 $string .= $self->count_atoms . "\n" unless $self->qcat_print;
  4155         82113  
378 32         408 $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 259 return $string;
386 12         40 }
387 12         55  
388 12 100       52 my $self = shift;
389 11 100       83 my $fh = _open_file_unless_fh(shift);
390              
391             print $fh $self->string_xyz;
392              
393 9     9 1 82 # my @atoms = $self->all_atoms;
394 9         18 #print $fh $self->count_atoms . "\n\n" unless $self->qcat_print;
395 9         29 #foreach my $at (@atoms) {
396 9 100       28 # printf $fh (
397 9         440 # "%3s %10.6f %10.6f %10.6f\n",
398             # $at->symbol, @{ $at->get_coords( $at->t ) }
399             # );
400             #}
401 11     11 0 28  
402 11         17 return ($fh); # returns filehandle for future writing
403              
404 11         17 }
405 11 50       332  
406 11 100       33 my $self = shift;
407 11         14  
408             my $t = $self->what_time;
409 11         327 my @atoms = $self->all_atoms;
410              
411 936         18520 my $string;
  936         18644  
412             $string .= sprintf( "MODEL %2i\n", $t + 1 ) unless $self->qcat_print;
413 11         379  
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 4944 foreach my $at (@atoms) {
418 10         27  
419             # front pad one space if name length is < 4
420 9         36 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         170 serial
432             name
433             altloc
434             resname
435             chain
436 6     6 0 12 resid
437             icode
438 6         11 )
439 6         174 ),
440             @{ $at->get_coords( $at->t ) },
441 6         12 $at->occ,
442 6 100       132 $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         22  
451 18 50       395 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         3057 # 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         33 # qw (
  18         406  
471             # record_name
472             # serial
473             # name
474             # altloc
475             # resname
476             # chain
477 6 100       142 # resid
478 6         204 # icode
479             # )
480             # ),
481             # @{ $at->get_coords( $at->t ) },
482 6     6 1 1871 # $at->occ,
483 6         17 # $at->bfact,
484             # $at->segid,
485 6         17 # $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         38 =pod
523              
524             =head1 NAME
525              
526             HackaMol::Roles::AtomGroupRole - Role for a group of atoms
527 16     16   22  
528             =head1 VERSION
529 16         27  
530             version 0.052
531 16 100       41  
532 9 100       34 =head1 SYNOPSIS
533 6 100       25  
534 5         8 use HackaMol::AtomGroup;
535             use HackaMol::Atom;
536              
537 1         11 my $atom1 = HackaMol::Atom->new(
538             name => 'O1',
539             coords => [ V( 2.05274, 0.01959, -0.07701 ) ],
540             Z => 8,
541 3 100       76 );
542 3         486
543             my $atom2 = HackaMol::Atom->new(
544             name => 'H1',
545             coords => [ V( 1.08388, 0.02164, -0.12303 ) ],
546 15         396 Z => 1,
547             );
548            
549 17     17   217 my $atom3 = HackaMol::Atom->new(
  17         71  
  17         195  
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