File Coverage

blib/lib/HackaMol/Roles/PhysVecMVRRole.pm
Criterion Covered Total %
statement 143 143 100.0
branch 42 42 100.0
condition 3 3 100.0
subroutine 28 28 100.0
pod 19 22 86.3
total 235 238 98.7


line stmt bran cond sub pod time code
1             $HackaMol::Roles::PhysVecMVRRole::VERSION = '0.053';
2             # ABSTRACT: Provides the core of HackaMol Atom and Molecule classes.
3             use Math::Vector::Real;
4 20     20   14963 use Math::Trig;
  20         40575  
  20         1248  
5 20     20   3538  
  20         81568  
  20         2604  
6             #use Moose::Util::TypeConstraints;
7             use Moose::Role;
8 20     20   182 use Carp;
  20         39  
  20         183  
9 20     20   97119 #use Data::Structure::Util qw (unbless);
  20         67  
  20         35341  
10             #use MooseX::Storage;
11              
12             #with Storage( 'format' => 'JSON', 'io' => 'File' );
13              
14             requires qw(_build_mass charge);
15              
16             #MooseX::Storage::Engine->add_custom_type_handler(
17             # 'Math::Vector::Real' => (
18             # expand => sub {my $v = shift; Math::Vector::Real->new(@{$v})},
19             # collapse => sub {my $mvr = shift; return (unbless($mvr)) },
20             # )
21             #);
22             #use Data::Dumper;
23             #MooseX::Storage::Engine->add_custom_type_handler(
24             # 'ArrayRef[Math::Vector::Real]' => (
25             # expand => sub {print Dumper $_; my @vs = map {Math::Vector::Real->new(@{$_}); print $_} @$_; return [@vs] },
26             # collapse => sub {my $a_rf = shift; my @vs = map {unbless($_)} @$a_rf; return \@vs },
27             # )
28             #);
29              
30              
31             has 't', is => 'rw', isa => 'Int|ScalarRef', default => 0;
32              
33             has "$_" => (
34             traits => ['Array'],
35             is => 'ro',
36             isa => 'ArrayRef[Math::Vector::Real]',
37             default => sub { [] },
38             handles => {
39             "push_$_" => 'push',
40             "get_$_" => 'get',
41             "delete_$_" => 'delete',
42             "set_$_" => 'set',
43             "all_$_" => 'elements',
44             "clear_$_" => 'clear',
45             "count_$_" => 'count',
46             },
47             lazy => 1,
48             ) for qw(coords forces);
49              
50             has "$_" => (
51             traits => ['Array'],
52             is => 'ro',
53             isa => 'ArrayRef[Num]',
54             default => sub { [] },
55             predicate => 'has_charges',
56             handles => {
57             "push_$_" => 'push',
58             "get_$_" => 'get',
59             "delete_$_" => 'delete',
60             "set_$_" => 'set',
61             "all_$_" => 'elements',
62             "clear_$_" => 'clear',
63             "count_$_" => 'count',
64             },
65             lazy => 1,
66             ) for qw(charges);
67              
68             has 'units', is => 'rw', isa => 'Str'; #flag for future use [SI]
69              
70             has 'origin' => (
71             is => 'rw',
72             isa => 'Math::Vector::Real',
73             default => sub { V( 0, 0, 0 ) },
74             lazy => 1,
75             );
76              
77             has 'xyzfree' => (
78             is => 'rw',
79             isa => 'ArrayRef[Int]',
80             default => sub { [ 1, 1, 1 ] },
81             lazy => 1,
82             trigger => \&_freedom,
83             clearer => 'clear_xyzfree',
84             );
85              
86             has 'mass' => (
87             is => 'rw',
88             isa => 'Num',
89             lazy => 1,
90             clearer => 'clear_mass',
91             builder => '_build_mass',
92             );
93              
94             my ( $self, $new, $old ) = @_;
95             if ( grep { $_ == 0 } @{$new} ) {
96 3     3   8 $self->is_fixed(1);
97 3 100       3 }
  9         17  
  3         7  
98 2         50 else {
99             $self->is_fixed(0);
100             }
101 1         25 }
102              
103             has 'is_fixed' => (
104             is => 'rw',
105             isa => 'Bool',
106             lazy => 1,
107             default => 0,
108             );
109              
110             #intraobject methods
111             my $self = shift;
112             croak "intra_dcharges> pass initial and final time" unless ( @_ == 2 );
113             my $ti = shift;
114 5     5 1 464 my $tf = shift;
115 5 100       35 return ( $self->get_charges($tf) - $self->get_charges($ti) );
116 2         3 }
117 2         5  
118 2         63 my $self = shift;
119             my @tcharges = $self->all_charges;
120             my $sum = 0;
121             $sum += $_ foreach @tcharges;
122 2     2 1 4 return ( $sum / $self->count_charges );
123 2         75 }
124 2         3  
125 2         9 my $self = shift;
126 2         59 my $avg = $self->mean_charges;
127             my @tcharges = $self->all_charges;
128             my $sum = 0;
129             $sum += ( $_ - $avg )**2 foreach @tcharges;
130 1     1 1 3 return ( $sum / $self->count_charges );
131 1         5 }
132 1         29  
133 1         2  
134 1         11 #M::V::R makes much simpler
135 1         29 my $self = shift;
136             croak "intra_dcoords> pass initial and final time" unless ( @_ == 2 );
137             my $ti = shift;
138             my $tf = shift;
139             return ( $self->get_coords($tf) - $self->get_coords($ti) );
140             }
141 5     5 1 448  
142 5 100       31 my $self = shift;
143 2         4 my @tcoords = $self->all_coords;
144 2         4 my $sum = V( 0, 0, 0 );
145 2         63 $sum += $_ foreach @tcoords;
146             return ( $sum / $self->count_coords );
147             }
148              
149 2     2 1 599  
150 2         103 # returns scalar
151 2         13 my $self = shift;
152 2         17 my $avg = $self->mean_coords;
153 2         64 my @tcoords = $self->all_coords;
154             my $sum = 0;
155             foreach my $c (@tcoords) {
156             my $dc = $c - $avg;
157             $sum += $dc * $dc;
158             }
159 1     1 1 3 return ( $sum / $self->count_coords );
160 1         4 }
161 1         30  
162 1         3  
163 1         4 #M::V::R makes much simpler
164 10         20 my $self = shift;
165 10         22 croak "intra_dforces> pass initial and final time" unless ( @_ == 2 );
166             my $ti = shift;
167 1         33 my $tf = shift;
168             return ( $self->get_forces($tf) - $self->get_forces($ti) );
169             }
170              
171             my $self = shift;
172             my @tforces = $self->all_forces;
173 4     4 1 445 my $sum = V( 0, 0, 0 );
174 4 100       32 $sum += $_ foreach @tforces;
175 1         2 return ( $sum / $self->count_forces );
176 1         2 }
177 1         32  
178              
179             # returns scalar
180             my $self = shift;
181 2     2 1 4 my $avg = $self->mean_forces;
182 2         76 my @tforces = $self->all_forces;
183 2         9 my $sum = 0;
184 2         14 foreach my $c (@tforces) {
185 2         65 my $dc = $c - $avg;
186             $sum += $dc * $dc;
187             }
188             return ( $sum / $self->count_forces );
189             }
190              
191 1     1 1 3 #interobject methods
192 1         4 my $self = shift;
193 1         30 my $obj2 = shift or croak "need to pass another obj that does PhysVec";
194 1         3 my ( $ts, $t2 ) = ( $self->t, $obj2->t );
195 1         2 carp "comparing objects with different times" unless ( $ts == $t2 );
196 10         18 my $vs = $self->get_coords($ts);
197 10         19 my $v2 = $obj2->get_coords($t2);
198             return ( $vs->dist($v2) );
199 1         45 }
200              
201              
202             # obj2 obj3
203             # \ Ang /
204 1664     1664 1 2584 # \ /
205 1664 100       2535 # self
206 1663         32638 #
207 1663 100       2773 # returns in degrees
208 1663         42480 my ( $self, $obj2, $obj3 ) = @_;
209 1663         42553 croak "need to pass two objects that do PhysVecMVR" unless ( @_ == 3 );
210 1663         5132 my $v1 = $self->inter_dcoords($obj2);
211             my $v2 = $self->inter_dcoords($obj3);
212             return (0) if ( abs($v1) == 0 or abs($v2) == 0 );
213             return ( atan2( $v1, $v2 ) );
214             }
215              
216             my $self = shift;
217             my $angle = $self->angle_rad(@_);
218             return ( rad2deg($angle) );
219             }
220              
221 220     220 1 317  
222 220 100       387 # self obj4
223 218         389 # \ /
224 218         318 # \ Ang /
225 218 100 100     992 # obj2---obj3
226 215         492 #
227             # returns in degrees
228             my ( $self, $obj2, $obj3, $obj4 ) = @_;
229             croak "need to pass three objects that do PhysVecMVR" unless ( @_ == 4 );
230 210     210 1 2925  
231 210         368 my $v1 = $self->inter_dcoords($obj2);
232 208         3055 my $v2 = $obj2->inter_dcoords($obj3);
233             my $v3 = $obj3->inter_dcoords($obj4);
234             my $v3_x_v2 = $v3 x $v2;
235             my $v2_x_v1 = $v2 x $v1;
236             my $sign = $v1 * $v3_x_v2;
237              
238             my $dihe = atan2( $v3_x_v2, $v2_x_v1 );
239             $dihe *= -1 if ( $sign > 0 );
240             return $dihe;
241             }
242              
243 155     155 1 249 my $self = shift;
244 155 100       274 my $dihe = $self->dihedral_rad(@_);
245             return ( rad2deg($dihe) );
246 151         256 }
247 151         225  
248 151         272 my $self = shift;
249 151         407 my $obj2 = shift or croak "need to pass another obj that does PhysVec";
250 151         263 my ( $ts, $t2 ) = ( $self->t, $obj2->t );
251 151         256 carp "comparing objects with different times" unless ( $ts == $t2 );
252             my $dvec = $obj2->get_charges($t2) - $self->get_charges($ts);
253 151         360 return ($dvec);
254 151 100       2185 }
255 151         395  
256             my $self = shift;
257             my $obj2 = shift or croak "need to pass another obj that does PhysVec";
258             my ( $ts, $t2 ) = ( $self->t, $obj2->t );
259 153     153 1 4789 carp "comparing objects with different times" unless ( $ts == $t2 );
260 153         279 my $dvec = $obj2->get_coords($t2) - $self->get_coords($ts);
261 149         380 return ($dvec);
262             }
263              
264             my $self = shift;
265 3     3 0 187 my $obj2 = shift or croak "need to pass another obj that does PhysVec";
266 3 100       17 my ( $ts, $t2 ) = ( $self->t, $obj2->t );
267 2         51 carp "comparing objects with different times" unless ( $ts == $t2 );
268 2 100       13 my $dvec = $obj2->get_forces($t2) - $self->get_forces($ts);
269 2         414 return ($dvec);
270 2         13 }
271              
272             my $self = shift;
273             carp "xyz> takes no arguments. returns get_coords(t)" if (@_);
274 1009     1009 0 1294 return ( $self->get_coords( $self->t ) );
275 1009 100       1483 }
276 1008         19090  
277 1008 100       1657  
278 1008         24179 # returns a new MVR
279 1008         1799 # optionally takes t
280             my $self = shift;
281             my $t = shift;
282             $t = $self->t unless ( defined($t) );
283 3     3 0 188 return ( V( @{ $self->get_coords($t) } ) );
284 3 100       16 }
285 2         52  
286 2 100       17 my $self = shift;
287 2         453 carp "force> takes no arguments. returns get_forces(t)" if (@_);
288 2         17 return ( $self->get_forces( $self->t ) );
289             }
290              
291              
292 361     361 1 478 # returns a new MVR
293 361 100       545 # optionally takes t
294 361         7407 my $self = shift;
295             my $t = shift;
296             $t = $self->t unless ( defined($t) );
297             return ( V( @{ $self->get_forces($t) } ) );
298             }
299              
300             croak "need to pass [charges|coords|forces] t and tf" unless @_ == 4;
301 4     4 1 17 my ( $self, $qcf, $t, $tf ) = @_;
302 4         6 my ( $get_qcf, $set_qcf ) = map { $_ . $qcf } qw(get_ set_);
303 4 100       48 my $qcf_at_t = $self->$get_qcf($t);
304 4         5 $self->$set_qcf( $_, $qcf_at_t ) foreach ( $t + 1 .. $tf );
  4         110  
305             }
306              
307             no Moose::Role;
308 11     11 1 61  
309 11 100       36 1;
310 11         688  
311              
312             =pod
313              
314             =head1 NAME
315              
316             HackaMol::Roles::PhysVecMVRRole - Provides the core of HackaMol Atom and Molecule classes.
317 4     4 1 18  
318 4         5 =head1 VERSION
319 4 100       48  
320 4         6 version 0.053
  4         111  
321              
322             =head1 SYNOPSIS
323              
324 7 100   7 1 482 # instance of class that consumes the PhysVecRol
325 4         10 use HackaMol::Molecule;
326 4         11 my $obj = HackaMol::Molecule->new(
  8         21  
327 4         123 name => 'foo',
328 4         126 t => 0 ,
329             charges => [0.1],
330             coords => [ V(0,1,2) ]
331 20     20   184 );
  20         51  
  20         130  
332              
333             # add some charges
334              
335             $obj->push_charges($_) foreach ( 0.3, 0.2, 0.1, -0.1, -0.2, -0.36 );
336              
337             my $sum_charges = 0;
338              
339             $sum_charges += $_ foreach $obj->all_charges;
340             print "average charge: ", $sum_charges / $obj->count_charges;
341              
342             # add some coordinates
343              
344             $obj->push_coords($_) foreach ( V(0,0,0), V(1,1,1), V(-1.0,2.0,-4.0) ) );
345              
346             print $obj->mean_charges . "\n";
347             print $obj->msd_charges . "\n";
348             printf ("%10.3f %10.3f %10.3f \n", @{$obj->mean_coords};
349             print $obj->msd_coords . "\n";
350              
351             =head1 DESCRIPTION
352              
353             PhysVecMVR provides the core attributes and methods shared between Atom
354             and Molecule classes. Consuming this role gives Classes a place to store
355             coordinates, forces, and charges, perhaps, over the course of a simulation
356             or for a collection of configurations for which all other object metadata (name,
357             mass, etc) remains fixed. As such, the 't' attribute, described below, is
358             important to understand. The PhysVecMVR uses Math::Vector::Real (referred to as MVR),
359             which has pure Perl and XS implementations. MVR::XS is fast with many useful/powerful
360             overloaded methods. PhysVecMVR leaves many attributes rw so that they may be set and
361             reset on the fly. This seems most intuitive from the
362             perspective of carrying out computational work on molecules.
363              
364             Comparing the PhysVec within Atom and Molecule may be helpful. For both, the PhysVecRol
365             generates a little metadata (mass, name, etc.) and an array of coordinates, forces, and
366             charges. For an atom, the array of coordinates gives an atom (with fixed metadata) the ability
367             to store multiple [x,y,z] positions (as a function of time, symmetry, distribution, etc.). What
368             is the array of coordinates for Molecule? Usually, the coordinates for a molecule will likely
369             remain empty (because the atoms that Molecule contains have the more useful coordinates), but we
370             can imagine using the coordinates array to track the center of mass of the molecule if needed.
371              
372             In the following: Methods with mean_foo msd_foo intra_dfoo out front, carries out some analysis
373             within $self. Methods with inter_ out front carries out some analysis between
374             two objects of classes that consume PhysVecMVR at the $self->t and $obj->t.
375              
376             =head1 METHODS
377              
378             =head2 distance
379              
380             Takes one argument ($obj2) and calculates the distance using Math::Vector::Real
381              
382             $obj1->distance($obj2);
383              
384             =head2 angle_deg
385              
386             Takes two arguments ($obj2,$obj3) and calculates the angle (degrees) between
387             the vectors with $obj1 as orgin using Math::Vector::Real.
388              
389             $obj1->angle_deg($obj2,$obj3);
390              
391             =head2 angle_rad
392              
393             Takes two arguments ($obj2,$obj3) and calculates the angle (radians) between
394             the vectors with $obj1 as orgin using Math::Vector::Real.
395              
396             $obj1->angle_rad($obj2,$obj3);
397              
398             =head2 dihedral_deg
399              
400             Takes three arguments ($obj2,$obj3,obj4) and calculates the angle
401             (degrees) between the vectors normal to the planes containing the first three
402             and last three objects using Math::Vector::Real.
403              
404             $obj1->dihedral_deg($obj2,$obj3,$obj4);
405              
406             =head2 dihedral_rad
407              
408             Takes three arguments ($obj2,$obj3,obj4) and calculates the angle
409             (radians) between the vectors normal to the planes containing the first three
410             and last three objects using Math::Vector::Real.
411              
412             $obj1->dihedral_rad($obj2,$obj3,$obj4);
413              
414             =head2 intra_dcharges
415              
416             Calculates the change in charge from initial t ($ti) to final t ($tf). I.e.:
417              
418             $obj1->intra_dcharges( $ti, $tf );
419              
420             yields the same as:
421              
422             $self->get_charges($tf) - $self->get_charges($ti);
423              
424             =head2 mean_charges
425              
426             No arguments. Calculates the mean of all stored charges.
427              
428             =head2 msd_charges
429              
430             No arguments. Calculates the mean square deviation of all stored charges.
431              
432             =head2 intra_dcoords intra_dforces
433              
434             returns the difference (Math::Vector::Real object) from the initial t ($ti) to
435             the final t ($tf).
436              
437             $obj1->intra_dcoords($ti,$tf);
438              
439             $obj1->intra_dforces($ti,$tf);
440              
441             =head2 mean_coords mean_forces
442              
443             No arguments. Calculates the mean (Math::Vector::Real object) vector.
444              
445             =head2 msd_coords msd_forces
446              
447             No arguments. Calculates the mean (Math::Vector::Real object) dot product of the
448             vector difference of the xyz coords from the mean vector.
449              
450             =head2 charge
451              
452             called with no arguments. returns $self->get_charges($self->t);
453              
454             =head2 xyz
455              
456             called with no arguments. returns $self->get_coords($self->t);
457              
458             =head2 clone_xyz
459              
460             optionally called with t. t set to $self->t if not passed.
461             This method is intended for mapping from one atom to another, where the
462             coordinates are expected to be distinct. A copy of the xyz
463             coordinates with a new memory location is returned via
464             V( @{$self->get_coords($t)});
465              
466             my @Aus = map {
467             HackaMol::Atom->new(Z=>79, coords=>[$_->clone_xyz])
468             } grep {$_->Z == 80} @atoms;
469              
470             =head2 force
471              
472             called with no arguments. returns $self->get_forces($self->t);
473              
474             =head2 clone_force
475              
476             optionally called with t. t set to $self->t if not passed. Analagous to
477             clone_xyz.
478              
479             =head2 copy_ref_from_t1_through_t2
480              
481             called as $obj->copy_ref_from_t1_through_t2($qcf,$t,$tf); where qcf is
482             charges|coords|forces. Fills the qcf from t+1 to tf with the value at t.
483             Added this while trying to compare a dipole as a function of changing charges
484             at a single set of coordinates.
485              
486             =head1 ARRAY METHODS
487              
488             =head2 push_$_, all_$_, get_$_, set_$_, count_$_, clear_$_ foreach qw(charges coords forces)
489              
490             ARRAY traits, respectively: push, get, set, all, elements, delete, clear
491             Descriptions for charges and coords follows. forces analogous to coords.
492              
493             =head2 push_charges
494              
495             push value on to charges array
496              
497             $obj->push_charges($_) foreach (0.15, 0.17, 0.14, 0.13);
498              
499             =head2 all_charges
500              
501             returns array of all elements in charges array
502              
503             print $_ . " " foreach $obj->all_charges; # prints 0.15 0.17 0.14 0.13
504              
505             =head2 get_charges
506              
507             return element by index from charges array
508              
509             print $obj->get_charges(1); # prints 0.17
510              
511             =head2 set_charges
512              
513             set value of element by index from charges array
514              
515             $obj->set_charges(2, 1.00);
516             print $_ . " " foreach $obj->all_charges; # prints 0.15 0.17 1.00 0.13
517              
518             =head2 count_charges
519              
520             return number of elements in charges array
521              
522             print $obj->count_charges; # prints 4
523              
524             =head2 delete_charges($index)
525              
526             Removes the element at the given index from the array.
527              
528             This method returns the deleted value. Note that if no value exists, it will return undef.
529              
530             This method requires one argument.
531              
532             =head2 clear_charges
533              
534             clears charges array
535              
536             $obj->clear_charges;
537             print $_ . " " foreach $obj->all_charges; # does nothing
538             print $obj->count_charges; # prints 0
539              
540             =head2 push_coords
541              
542             push value on to coords array
543              
544             $obj->push_coords($_) foreach ([0,0,0],[1,1,1],[-1.0,2.0,-4.0], [3,3,3]);
545              
546             =head2 all_coords
547              
548             returns array of all elements in coords array
549              
550             printf ("%10.3f %10.3f %10.3f \n", @{$_}) foreach $obj->all_coords;
551              
552             my @new_coords = map {[$_->[0]+1,$_->[1]+1,$_->[2]+1]} $obj->all_coords;
553              
554             printf ("%10.3f %10.3f %10.3f \n", @{$_}) foreach @new_coords;
555              
556             =head2 get_coords
557              
558             return element by index from coords array
559              
560             printf ("%10.3f %10.3f %10.3f \n", @{$obj->get_coords(1)}); #
561              
562             =head2 set_coords
563              
564             set value of element by index from coords array
565              
566             $obj->set_coords(2, [100,100,100]);
567             printf ("%10.3f %10.3f %10.3f \n", @{$_}) foreach $obj->all_coords;
568              
569             =head2 count_coords
570              
571             return number of elements in coords array
572              
573             print $obj->count_coords; # prints 4
574              
575             =head2 delete_coords($index)
576              
577             Removes the element at the given index from the array.
578              
579             This method returns the deleted value. Note that if no value exists, it will return undef.
580              
581             This method requires one argument.
582              
583             clears coords array
584              
585             $obj->clear_coords;
586             print $_ . " " foreach $obj->all_coords; # does nothing
587             print $obj->count_coords # prints 0
588              
589             =head2 clear_coords
590              
591             clears coords array
592              
593             $obj->clear_coords;
594             print $_ . " " foreach $obj->all_coords; # does nothing
595             print $obj->count_coords # prints 0
596              
597             =head1 ATTRIBUTES
598              
599             =head2 t
600              
601             isa Int or ScalarRef that is rw with default of 0
602              
603             t is intended to describe the current "setting" of the object. Objects that
604             consume the PhysVecRol have arrays of coordinates, forces, and charges to
605             allow storage with the passing of time (hence t) or the generation alternative
606             configurations. For example, a crystal lattice can be stored as a single
607             object that consumes PhysVec (with associated metadata) along with the
608             array of 3d-coordinates resulting from lattice vector translations.
609              
610             Experimental: Setting t to a ScalarRef allows all objects to share the same t.
611             Although, to use this, a $self->t accessor that dereferences the value would
612             seem to be required. There is nothing, currently, in the core to do so. Not
613             sure yet if it is a good or bad idea to do so.
614              
615             my $t = 0;
616             my $rt = \$t;
617             $_->t($rt) for (@objects);
618             $t = 1; # change t for all objects.
619              
620             =head2 mass
621              
622             isa Num that is rw and lazy with a default of 0
623              
624             =head2 xyzfree
625              
626             isa ArrayRef that is rw and lazy with a default value [1,1,1]. Using this array
627             allows the object to be fixed for calculations that support it. For example, to
628             fix the X and Y coordinates:
629              
630             $obj->xyzfree([0,0,1]);
631              
632             fixing any coordinate will set trigger the is_fixed(1) flag.
633              
634             =head2 is_fixed
635              
636             isa Bool that is rw and lazy with a default of 0 (false).
637              
638             =head2 charges
639              
640             isa ArrayRef[Num] that is lazy with public ARRAY traits described in ARRAY_METHODS
641              
642             Gives atoms and molecules t-dependent arrays of charges. e.g. store and analyze
643             atomic charges from a quantum mechanical molecule in several intramolecular
644             configurations or a fixed configuration in varied external potentials.
645              
646             =head2 coords forces
647              
648             isa ArrayRef[Math::Vector::Real] that is lazy with public ARRAY traits described in ARRAY_METHODS
649              
650             Gives atoms and molecules t-dependent arrays of coordinates and forces,
651             for the purpose of analysis.
652              
653             =head1 SEE ALSO
654              
655             =over 4
656              
657             =item *
658              
659             L<HackaMol::Atom>
660              
661             =item *
662              
663             L<HackaMol::Molecule>
664              
665             =back
666              
667             =head1 AUTHOR
668              
669             Demian Riccardi <demianriccardi@gmail.com>
670              
671             =head1 COPYRIGHT AND LICENSE
672              
673             This software is copyright (c) 2017 by Demian Riccardi.
674              
675             This is free software; you can redistribute it and/or modify it under
676             the same terms as the Perl 5 programming language system itself.
677              
678             =cut