File Coverage

blib/lib/HackaMol/Roles/ReadPdbxRole.pm
Criterion Covered Total %
statement 48 205 23.4
branch 13 108 12.0
condition 0 9 0.0
subroutine 7 10 70.0
pod 0 2 0.0
total 68 334 20.3


line stmt bran cond sub pod time code
1             package HackaMol::Roles::ReadPdbxRole;
2             $HackaMol::Roles::ReadPdbxRole::VERSION = '0.051';
3             # ABSTRACT: parse PDBx/mmCIF files
4 11     11   6422 use Moose::Role;
  11         33  
  11         79  
5 11     11   57857 use HackaMol::PeriodicTable qw(_element_name _trim _qstring_num);
  11         31  
  11         715  
6 11     11   78 use Math::Vector::Real;
  11         39  
  11         514  
7 11     11   79 use Carp;
  11         28  
  11         34014  
8              
9             requires 'readline_func';
10              
11             sub read_cif_info {
12             # EXPERIMENTAL: use with caution, getting to work, then need to refactor to min DRY
13 0     0 0 0 my $self = shift;
14 0         0 my $fh = shift;
15 0         0 my $info = shift;
16 0         0 my $in_loop = 0;
17 0         0 my $atom_site_position = 0;
18              
19 0         0 while (<$fh>) {
20              
21             # set loop flag if loop is open
22 0 0       0 if (/^loop_/) {
23 0         0 $in_loop = 1;
24             }
25 0 0 0     0 if ( /^#/ && $in_loop ) {
26 0         0 $in_loop = 0;
27             }
28              
29 0 0       0 if (/^_pdbx_database_status.recvd_initial_deposition_date\s+(\S+)/) {
30 0         0 $info->{deposition_date} = $1;
31             }
32 0 0       0 if (/^_pdbx_audit_revision_history.revision_date\s*(\S+)?/) {
33 0         0 my $revision_date = $1;
34 0 0       0 if ($in_loop) {
35 0         0 while ( my $line = <$fh> ) {
36              
37 0 0       0 if ( $line =~ /^\#/ ) {
38 0         0 $in_loop = 0;
39 0         0 last;
40             }
41              
42 0         0 chomp($line);
43              
44 0         0 ($revision_date) = $line =~ /.+(\d{4}\-\d{2}\-\d{2})/;
45             }
46             }
47 0         0 $info->{last_revision_date} = $revision_date;
48             }
49 0 0       0 if (/^_pdbx_struct_assembly_gen.asym_id_list\s*(\S+)?/){
50 0         0 my @asym_list;
51 0 0       0 if ($1){
52 0         0 @asym_list = split /\s*,\s*/, $1;
53             }
54             else {
55 0         0 while (my $line = <$fh>){
56 0         0 chomp($line);
57 0 0       0 last if $line =~ /^(;\s*$|#)/;
58 0 0       0 if( $line =~ /^;/ ){
59 0         0 @asym_list = split /\s*,\s*/, $line;
60             }
61 0         0 @asym_list = split /\s*,\s*/, $line;
62             }
63              
64             }
65 0         0 $info->{auth_asym_id_list}{$_} = 0 foreach @asym_list; # use hash to connect with entities?
66             }
67             # this works for 3j7p and others but not using for now because not needed by me
68             # if (/^_struct_conn.id\s*$/) {
69             # die "assumed loop exception" unless $in_loop;
70             # my @labels = ("_struct_conn.id");
71              
72             # while ( my $line = <$fh> ) {
73             # if ( $line =~ /^(_struct\S+)/ ) {
74             # push @labels, $1;
75             # next;
76             # }
77             # if ( $line =~ /^#/ ) {
78             # $in_loop = 0;
79             # last;
80             # }
81              
82             # chomp($line);
83              
84             # # watch out for things like 'C-U MISPAIR' in 3j7p
85             # $line = _quote_hack($line);
86             # my @tokens = split /\s+/, $line;
87              
88             # # suck up lines until @tokens == @lables
89             # while ( scalar(@tokens) < scalar(@labels) ) {
90             # my $next_line = <$fh>;
91             # chomp($next_line);
92             # $next_line = _quote_hack($next_line);
93             # push @tokens, split /\s+/, $next_line;
94             # }
95             # die "too many tokens! [@labels] [@tokens] " if scalar(@tokens) != scalar(@labels);
96             # next unless ($tokens[0] =~ /disulf/);
97             # my $connect;
98             # foreach my $i ( 1 .. $#labels ) {
99             # next if ( $tokens[$i] eq '.' || $tokens[$i] eq '?' );
100             # $connect->{ $labels[$i] } = _unhack_quote($tokens[$i]);
101             # }
102             # $info->{connect}{ $tokens[0] } = $connect;
103              
104             # }
105             # }
106 0 0       0 if (/^_entity_poly.entity_id\s*(\d+)?/) {
107 0         0 chomp;
108             # suck up the entity sequences, do it until # if in a loop
109 0         0 my $entity_id = $1;
110              
111 0 0       0 if ($entity_id) {
112 0 0       0 die "should not be in loop..." if $in_loop;
113 0         0 my $seq;
114             my $seq_can;
115 0         0 while ( my $line = <$fh> ) {
116 0         0 chomp($line);
117 0 0       0 if ( $line =~ /^_entity_poly\.pdbx_seq_one_letter_code\s+(\S+)?\s*$/ )
118             {
119 0 0       0 if ($1){
120 0         0 $seq = $1;
121             }
122             else {
123 0         0 while (my $next_line = <$fh>){
124 0         0 chomp $next_line;
125 0 0       0 last if $next_line =~ /^;\s*$/;
126 0         0 $seq .= $next_line;
127             }
128             }
129             }
130              
131 0 0       0 if ( $line =~ /^_entity_poly\.pdbx_seq_one_letter_code_can\s+(\S+)?\s*$/ )
132             {
133 0 0       0 if ($1){
134 0         0 $seq_can = $1;
135             }
136             else {
137 0         0 while (my $next_line = <$fh>){
138 0         0 chomp $next_line;
139 0 0       0 last if $next_line =~ /^;\s*$/;
140 0         0 $seq_can .= $next_line;
141             }
142             }
143             }
144            
145 0 0       0 last if $line =~ /^#/;
146            
147             }
148 0         0 $seq =~ s/[;'"]//g;
149 0         0 $seq_can =~ s/[;'"]//g;
150 0         0 $info->{entity}{$entity_id}{'_entity_poly.pdbx_seq_one_letter_code'} = $seq;
151 0         0 $info->{entity}{$entity_id}{'_entity_poly.pdbx_seq_one_letter_code_can'} = $seq_can;
152             }
153             else {
154 0 0       0 die "expected loop exception" unless ($in_loop);
155             # we are in a loop
156 0         0 my @labels = ($_);
157             #my @labels = ("_entity_poly.entity_id");
158             # TODO FACTOR OUT DRY
159 0         0 while ( my $line = <$fh> ) {
160 0 0       0 if ( $line =~ /^(_entity_poly.\S+)/ ) {
161 0         0 push @labels, $1;
162 0         0 next;
163             }
164 0 0       0 if ( $line =~ /^\#/ ) {
165 0         0 $in_loop = 0;
166 0         0 last;
167             }
168 0         0 chomp($line);
169 0         0 $line = _quote_hack($line);
170 0         0 my @tokens = split /\s+/, $line;
171             # suck up lines until @tokens == @lables
172 0         0 while ( scalar(@tokens) < scalar(@labels) ) {
173 0         0 my $next_line = <$fh>;
174 0         0 $next_line = _quote_hack ($next_line);
175 0         0 chomp $next_line;
176 0         0 my $seq;
177 0 0       0 if( $next_line =~ /^;/ ){
178 0         0 $seq .= $next_line;
179 0         0 while (my $seq_line = <$fh>){
180 0         0 chomp($seq_line);
181             #$seq_line = _quote_hack($seq_line);
182 0 0       0 last if $seq_line =~ /;\s*$/;
183 0         0 $seq .= $seq_line;
184             }
185 0         0 $tokens[$#tokens+1] = $seq;
186             }
187             else {
188              
189 0         0 push @tokens, split /\s+/, $next_line;
190             }
191             }
192            
193 0 0       0 die "too many tokens! [@labels] [@tokens]" if scalar(@tokens) != scalar(@labels);
194              
195 0         0 my $entity;
196 0         0 foreach my $i ( 1 .. $#labels ) {
197 0 0 0     0 next if ( $tokens[$i] eq '.' || $tokens[$i] eq '?' );
198 0         0 ($entity->{ $labels[$i] } = _unhack_quote($tokens[$i])) =~ s/[;'"]//g;
199             }
200 0         0 $info->{entity}{ $tokens[0] } = $entity;
201             }
202              
203             # $pdbx_seq_one_letter_code = 0;
204             # while ( my $line = <$fh> ) {
205             # chomp($line);
206             # if ( $line =~ /^(\d+)\s/ ) {
207             # $pdbx_seq_one_letter_code = 1;
208             # $entity_id = $1;
209             # next;
210             # }
211             # if ($pdbx_seq_one_letter_code) {
212             # if ( $line =~ /^;$/ )
213             # { # taking the first sequence that ends with ^;\n
214             # $pdbx_seq_one_letter_code = 0;
215             # next;
216             # }
217             # $seq = $line;
218             # $seq =~ s/(\s|;)//g;
219             # $info->{entity}{$entity_id} .= $seq;
220             # }
221             # if ( $line =~ /^\#/ ) {
222             # $in_loop = 0;
223             # last;
224             # }
225             # }
226             }
227             }
228 0 0       0 if (/^_struct_keywords.text\s*(?:\'?(.+)\'?)?\s*$/) {
229 0 0       0 if ($1){
230 0         0 $info->{keywords} = $1;
231             }
232             else {
233 0         0 while (my $line = <$fh>){
234 0         0 chomp($line);
235 0 0       0 last if $line =~ /^#/;
236 0         0 $info->{keywords} .= $line
237             }
238             }
239 0         0 $info->{keywords} =~ s/^;|;$|['"]|\s+$//g;
240             }
241 0 0       0 if (/^_exptl\.(\S+)\s*(?:\'(.+)\')?/){ # 2ah8 changes order
242 0 0       0 if ($1 eq 'method'){
    0          
243 0         0 $info->{exp_method} = $2;
244             }
245             elsif ($in_loop){
246 0         0 my @labels = ($_);
247 0         0 my $exptl = {};
248 0         0 while (my $line = <$fh>){
249 0 0       0 if ( $line =~ /^(_exptl\S+)/ ) {
250 0         0 push @labels, $1;
251 0         0 next;
252             }
253              
254 0 0       0 if ( $line =~ /^\#/ ) {
255 0         0 $in_loop = 0;
256 0         0 last;
257             }
258 0         0 chomp($line);
259 0         0 $line = _quote_hack($line);
260 0         0 my @tokens = split /\s+/, $line;
261 0 0       0 die "too many tokens! [@labels] [@tokens]" if scalar(@tokens) != scalar(@labels);
262              
263 0         0 foreach my $i (0 .. $#labels){
264 0         0 push @{$exptl->{$labels[$i]}},$tokens[$i]
  0         0  
265             }
266             }
267            
268             $info->{exp_method} = join ';', map {
269 0         0 my $str = _unhack_quote($_);
270 0         0 $str =~ s/['"]//g; $str } @{$exptl->{'_exptl.method'}};
  0         0  
  0         0  
  0         0  
271             }
272             else {
273 0         0 while (my $line = <$fh>){
274 0 0       0 last if $line =~ /^#/;
275 0 0       0 if ($line =~ /^_exptl\.method\s+(?:\'(.+)\')?/) {
276 0 0       0 die "unable to parse exp_method" unless($1);
277 0         0 $info->{exp_method} = $1;
278 0         0 last;
279             }
280            
281             }
282             }
283             }
284 0 0       0 if (/^_refine_hist\.d_res_high\s+(\d+\.\d+)/) {
285 0         0 $info->{resolution} = sprintf("%s",$1);
286             }
287 0 0       0 if (/^_em_3d_reconstruction.resolution\s+(\d+\.\d+)/) {
288 0         0 $info->{resolution} = $1;
289             }
290 0 0       0 if (/^_citation.pdbx_database_id_DOI\s+(\S+)/) {
291 0         0 $info->{doi} = $1;
292             }
293 0 0       0 if (/^_atom_site.group_PDB/){
294 0         0 $atom_site_position = $. - 2 ;
295 0         0 $info->{fh_position_atom_site} = $atom_site_position ;
296             }
297             }
298            
299             # move $fh to atom_site_position
300 0         0 seek ($fh, 0, 0 );
301 0         0 $. = 0;
302 0         0 my $LINE;
303 0   0     0 do { $LINE = <$fh> } until $. == $atom_site_position || eof;
  0         0  
304              
305 0         0 return $info;
306             }
307              
308             sub _quote_hack {
309             # watch out for things like 'C-U MISPAIR' in 3j7p
310 0     0   0 my $line = shift;
311 0         0 my @q_strs = $line =~ /['"](?:[^"'\\]++|\\.)*+["']/g; # via man perlre
312 0         0 foreach my $q_str (@q_strs){
313 0         0 (my $hack_str = $q_str) =~ s/(\s+)/__HACK__/g;
314 0         0 $line =~ s/$q_str/$hack_str/;
315             }
316 0         0 return $line;
317             }
318              
319             sub _unhack_quote {
320             # watch out for things like 'C-U MISPAIR' in 3j7p
321 0     0   0 my $line = shift;
322 0         0 $line =~ s/__HACK__/ /g ;
323 0         0 return $line;
324             }
325              
326             sub read_cif_atoms {
327              
328             # merge atoms here???
329 1     1 0 3 my $self = shift;
330 1         8 my ( $atoms, $models, $count_check ) = $self->_read_cif_atoms(@_);
331 1         3 my @sets;
332 1         3 foreach my $model_num ( @{$models} ) {
  1         33  
333 2         11 push @sets, [ grep { $_->model_num == $model_num } @$atoms ];
  124         2924  
334             }
335 1 50       37 wantarray ? return @sets : return $sets[0];
336             }
337              
338             my %line_parser_dp = (
339             'def' => sub {
340             my (
341             $record_name, $serial, $element, $name,
342             $altloc, $res_name, $chain_id, $entity_id,
343             $res_id, $icod, $x, $y,
344             $z, $occ, $B, $charge,
345             $auth_seq_id, $auth_comp_id, $auth_asym_id, $auth_atom_id,
346             $model_num
347             ) = split;
348              
349             $element = ucfirst( lc($element) );
350             my $xyz = V( $x, $y, $z );
351              
352             my $atom = HackaMol::Atom->new(
353             name => $name,
354             record_name => $record_name,
355             serial => $serial,
356             chain => $chain_id,
357             symbol => $element,
358             coords => [$xyz],
359             occ => $occ * 1,
360             bfact => $B * 1,
361             resname => $res_name,
362             resid => $res_id,
363             auth_seq_id => $auth_seq_id,
364             auth_comp_id => $auth_comp_id,
365             auth_asym_id => $auth_asym_id,
366             auth_atom_id => $auth_atom_id,
367             entity_id => $entity_id,
368             model_num => $model_num,
369             );
370             $atom->altloc($altloc) if ( $altloc ne '.' );
371             $atom->icode($icod) if ( $icod ne '?' );
372             return ($atom, $model_num);
373             },
374             'esd' => sub {
375             my (
376             $record_name, $serial, $element, $name,
377             $altloc, $res_name, $chain_id, $entity_id,
378             $res_id, $icod, $x, $y,
379             $z, $occ, $B,
380             undef, undef, undef, undef, undef, # ignor esd for now
381             $charge,
382             $auth_seq_id, $auth_comp_id, $auth_asym_id, $auth_atom_id,
383             $model_num
384             ) = split;
385              
386             $element = ucfirst( lc($element) );
387             my $xyz = V( $x, $y, $z );
388              
389             my $atom = HackaMol::Atom->new(
390             name => $name,
391             record_name => $record_name,
392             serial => $serial,
393             chain => $chain_id,
394             symbol => $element,
395             coords => [$xyz],
396             occ => $occ * 1,
397             bfact => $B * 1,
398             resname => $res_name,
399             resid => $res_id,
400             auth_seq_id => $auth_seq_id,
401             auth_comp_id => $auth_comp_id,
402             auth_asym_id => $auth_asym_id,
403             auth_atom_id => $auth_atom_id,
404             entity_id => $entity_id,
405             model_num => $model_num,
406             );
407             $atom->altloc($altloc) if ( $altloc ne '.' );
408             $atom->icode($icod) if ( $icod ne '?' );
409             return ($atom, $model_num);
410             },
411              
412             );
413              
414             sub _read_cif_atoms {
415              
416             # read pdb file and generate list of Atom objects
417             # no model branching
418 1     1   2 my $self = shift;
419 1         3 my $fh = shift;
420 1         4 my @atoms;
421             my %track_models;
422              
423 1         8 my @expected_labels = (
424             "_atom_site.group_PDB",# (record_name) _atom_site.group_PDB
425             "_atom_site.id",# (serial) _atom_site.id
426             "_atom_site.type_symbol",# (element) _atom_site.type_symbol
427             "_atom_site.label_atom_id",# (name) _atom_site.label_atom_id
428             "_atom_site.label_alt_id",# (altloc) _atom_site.label_alt_id
429             "_atom_site.label_comp_id",# (res_name) _atom_site.label_comp_id
430             "_atom_site.label_asym_id",# (chain_id) _atom_site.label_asym_id
431             "_atom_site.label_entity_id",# (entity_id) _atom_site.label_entity_id
432             "_atom_site.label_seq_id",# (res_id) _atom_site.label_seq_id
433             "_atom_site.pdbx_PDB_ins_code",# (icod) _atom_site.pdbx_PDB_ins_code
434             "_atom_site.Cartn_x",# (x) _atom_site.Cartn_x
435             "_atom_site.Cartn_y",# (y) _atom_site.Cartn_y
436             "_atom_site.Cartn_z",# (z) _atom_site.Cartn_z
437             "_atom_site.occupancy",# (occ) _atom_site.occupancy
438             "_atom_site.B_iso_or_equiv",# (B) _atom_site.B_iso_or_equiv
439             "_atom_site.pdbx_formal_charge",# (charge) #_atom_site.Cartn_x_esd #
440             "_atom_site.auth_seq_id",# (auth_seq_id) #_atom_site.Cartn_y_esd #
441             "_atom_site.auth_comp_id",# (auth_comp_id) #_atom_site.Cartn_z_esd #
442             "_atom_site.auth_asym_id",# (auth_asym_id) #_atom_site.occupancy_esd #
443             "_atom_site.auth_atom_id",# (auth_atom_id) #_atom_site.B_iso_or_equiv_esd #
444             "_atom_site.pdbx_PDB_model_num",# (model_num) #_atom_site.pdbx_formal_charge
445             #_atom_site.auth_seq_id
446             ); #_atom_site.auth_comp_id
447             #_atom_site.auth_asym_id
448             #_atom_site.auth_atom_id
449             #_atom_site.pdbx_PDB_model_num
450            
451             # look for the labels to determine order of attrs
452 1         3 my $site_type = 'def'; # 'esd'
453 1         6 LABEL: while (<$fh>){
454 9 50       20 if (/^_atom_site.Cartn_x_esd/){
455 0         0 $site_type = 'esd';
456 0         0 last;
457             }
458 9 100       18 last if (/^_atom_site.pdbx_PDB_model_num/);
459              
460 8 100       26 if (/^_atom_site.group_PDB/){
461 1         2 my $i = 1;
462 1         7 while (my $line = <$fh>){
463 14 50       181 die "labels out of order $. $line $expected_labels[$i]" unless $line =~ /$expected_labels[$i]\s?/;
464 14 100       40 last if ($line =~ /^_atom_site.B_iso_or_equiv/);
465 13         36 $i++;
466             }
467             }
468             }
469              
470 1         5 while (<$fh>) {
471 62 50       1943 if ( $self->has_readline_func ) {
472 0 0       0 next if $self->readline_func->($_) eq 'PDB_SKIP';
473             }
474              
475 62 50       165 last if (/^#/); # this works in combination with order search above
476              
477 62 50       240 if (/^(?:HETATM|ATOM)\s/) {
478 62         164 my ($atom, $model_num) = $line_parser_dp{$site_type}->($_);
479 62         120 $track_models{$model_num}++;
480 62         391 push @atoms, $atom;
481             }
482             }
483              
484 1         10 my %model_atom_count = reverse %track_models;
485              
486 1 50       7 if ( keys(%model_atom_count) > 1 ) {
487 0         0 warn "atom number inconsistencies in models. use third return value\n";
488             }
489              
490 1         9 return ( \@atoms, [ sort { $a <=> $b } keys %track_models ],
  1         18  
491             \%model_atom_count );
492             }
493              
494 11     11   120 no Moose::Role;
  11         52  
  11         96  
495              
496             1;
497              
498             __END__
499              
500             =pod
501              
502             =head1 NAME
503              
504             HackaMol::Roles::ReadPdbxRole - parse PDBx/mmCIF files
505              
506             =head1 VERSION
507              
508             version 0.051
509              
510             =head1 SYNOPSIS
511              
512             my @atoms = HackaMol->new
513             ->read_cif_atoms("some.cif");
514              
515             =head1 DESCRIPTION
516              
517             The HackaMol::Roles::ReadPdbxRole provides methods (such as read_cif_atoms for building HM atoms) for pulling information from PDBx/mmcif.
518             More info available in cif and more attributes are populated than via ReadPdbRole.
519              
520             =head1 METHODS
521              
522             =head2 read_pdb_atoms
523              
524             One argument: the filehandle
525             Returns a list of HackaMol::Atom objects.
526              
527             The implementation differs from the PDB parser in how models are handled. Each atom tracks the model number through the model_num attribute;
528             the read_pdb_atoms adds configurations to each subsequent atom into t (with identical metadata) as model number increases. Still considering the
529             best way to handle this.
530              
531             =head1 SEE ALSO
532              
533             =over 4
534              
535             =item *
536              
537             L<HackaMol>
538              
539             =item *
540              
541             L<HackaMol::Atom>
542              
543             =item *
544              
545             L<HackaMol::Roles::MolReadRole>
546              
547             =item *
548              
549             L<Protein Data Bank|http://pdb.org>
550              
551             =back
552              
553             =head1 AUTHOR
554              
555             Demian Riccardi <demianriccardi@gmail.com>
556              
557             =head1 COPYRIGHT AND LICENSE
558              
559             This software is copyright (c) 2017 by Demian Riccardi.
560              
561             This is free software; you can redistribute it and/or modify it under
562             the same terms as the Perl 5 programming language system itself.
563              
564             =cut