Chem-Structure-Parser
view release on metacpan or search on metacpan
#!/usr/bin/env perl
# mmCIF.
#
# The claim this file exists to check is a single one: which format a
# structure arrived in does not change the structure. So most of what is
# below reads the same structure twice, once from a .pdb and once from a .cif,
# and asserts that the two are equal -- not similar, not equal in the fields
# that were thought of, but equal, by is_deeply, over the whole coordinate
# half of the returned hash.
#
# The fixture pairs are written by t/data/generate.pl, which converts the PDB
# records into the mmCIF loop rather than typing the atoms twice, so that any
# difference between the two files is one the generator put there on purpose.
# The one it puts there on purpose is the naming: the .cif files carry
# label_asym_id and label_seq_id that deliberately disagree with the chain ids
# and residue numbers, because auth_* is what a PDB record carries and auth_*
# is what a reader has to use.
require 5.010;
use strict;
use warnings FATAL => 'all';
use Cwd 'abs_path';
use File::Basename 'dirname';
use File::Temp 'tempdir';
use Chem::Structure::Parser;
use Test::Exception;
use Test::More;
my $data = dirname(abs_path(__FILE__)) . '/data';
# coords() -- the half of a parsed structure that the coordinates decide: every
# chain, every residue, every atom, and the counts over them.
#
# What is taken back out is the handful of things _chain_stats() folds into a
# chain from the header, because those are the header's answer and not the
# coordinates', and the header section below tests them against each other one
# at a time. A fixture pair cannot be equal on all of them anyway -- mini.cif
# names the entity every ligand belongs to and mini.pdb has no record that
# does -- and burying that in this comparison would only make it say
# 'structures differ' about something the header tests say properly.
my @FROM_HEADER = qw(seqres seqres_length n_missing mol_id molecule organism
fragment ec dbref);
sub coords {
my ($i) = @_;
my %s = %{ $i->{stats} };
delete $s{n_lines}; # an mmCIF row and a PDB record are not the same line
my $strip = sub {
my ($set) = @_;
return { map {
my %c = %{ $set->{$_} };
delete @c{@FROM_HEADER};
$_ => \%c;
} keys %$set };
};
return {
chains => $strip->($i->{chains}),
chain_order => $i->{chain_order},
stats => \%s,
model => $i->{model},
n_models => $i->{n_models},
(exists $i->{models}
? (models => { map { $_ => { chains => $strip->($i->{models}{$_}{chains}),
chain_order => $i->{models}{$_}{chain_order} } }
keys %{ $i->{models} } })
: ()),
};
}
#--------------------------------------------------------------------
# the same structure, both ways
#--------------------------------------------------------------------
for my $pair ([ 'mini', 'one of everything' ],
[ 'nmr', 'a three model ensemble' ],
[ 'bare', 'coordinates and nothing else' ]) {
my ($stem, $what) = @$pair;
my $p = structure_info("$data/$stem.pdb");
my $c = structure_info("$data/$stem.cif");
is($p->{format}, 'pdb', "$stem.pdb is read as PDB");
is($c->{format}, 'mmcif', "$stem.cif is read as mmCIF");
is_deeply(coords($c), coords($p),
"$stem: $what -- every chain, residue, atom and count is the same from either file");
# and the views over it, which are what most callers actually touch
is_deeply(structure_atoms($c), structure_atoms($p), "$stem: structure_atoms agrees");
is_deeply(structure_residues($c), structure_residues($p), "$stem: structure_residues agrees");
is_deeply(structure_ligands($c), structure_ligands($p), "$stem: structure_ligands agrees");
is_deeply(structure_sequences($c), structure_sequences($p), "$stem: structure_sequences agrees");
is_deeply(structure_sequences("$data/$stem.cif"), structure_sequences("$data/$stem.pdb"),
"$stem: structure_sequences agrees when handed the file name");
}
# bare.cif has no _atom_site.type_symbol, as bare.pdb has no element columns,
# so both readers have to get the element out of the atom name -- and get the
# same answer, which is what stops a CA becoming calcium in one and carbon in
# the other
{
my $c = structure_info("$data/bare.cif");
is($c->{chains}{A}{residues}{1}{atoms}{CA}{element}, 'C',
'with no type_symbol the element is worked out from the atom name');
is_deeply($c->{stats}{elements}, structure_info("$data/bare.pdb")->{stats}{elements},
( run in 0.801 second using v1.01-cache-2.11-cpan-364913b4093 )