Initial removal of PROPKA input

This commit is contained in:
IAlibay
2020-11-17 23:31:24 +00:00
parent 9dfd87a0eb
commit 239c8183c9
11 changed files with 63 additions and 284 deletions

View File

@@ -13,9 +13,6 @@ from . import hybrid36
# Format strings that get used in multiple places (or are very complex) # Format strings that get used in multiple places (or are very complex)
PKA_FMT = "{:6.2f}" PKA_FMT = "{:6.2f}"
INPUT_LINE_FMT = (
"{type:6s}{r.numb:>5d} {atom_label} {r.res_name}{r.chain_id:>2s}"
"{r.res_num:>4d}{r.x:>12.3f}{r.y:>8.3f}{r.z:>8.3f}{group:>6s}{pka:>6s} \n")
PDB_LINE_FMT1 = ( PDB_LINE_FMT1 = (
"{type:6s}{r.numb:>5d} {atom_label} {r.res_name}{r.chain_id:>2s}" "{type:6s}{r.numb:>5d} {atom_label} {r.res_name}{r.chain_id:>2s}"
"{r.res_num:>4d}{r.x:>12.3f}{r.y:>8.3f}{r.z:>8.3f}{r.occ:>6s}" "{r.res_num:>4d}{r.x:>12.3f}{r.y:>8.3f}{r.z:>8.3f}{r.occ:>6s}"
@@ -33,7 +30,13 @@ STR_FMT = (
class Atom: class Atom:
"""Atom class - contains all atom information found in the PDB file""" """Atom class - contains all atom information found in the PDB file
.. versionchanged:: 3.4.0
:meth:`make_input_line` and :meth:`get_input_parameters` have been
removed as reading/writing PROPKA input is no longer supported.
"""
def __init__(self, line=None): def __init__(self, line=None):
"""Initialize Atom object. """Initialize Atom object.
@@ -255,31 +258,6 @@ class Atom:
new_atom.icode = self.icode new_atom.icode = self.icode
return new_atom return new_atom
def make_input_line(self):
"""PDB line for this atom.
TODO - Could be @property method/attribute
TODO - figure out difference between make_pdb_line, make_input_line,
and make_pdb_line2
Returns:
String with PDB-format line.
"""
group = '-'
model_pka = '-'
if self.group:
group = self.group.type
if self.terminal == 'C-':
group = 'C-' ## circumventing C-/COO parameter unification
if self.group.titratable:
model_pka = PKA_FMT.format(self.group.model_pka)
str_ = INPUT_LINE_FMT.format(
type=self.type.upper(), r=self,
atom_label=make_tidy_atom_label(self.name, self.element),
group=group, pka=model_pka)
return str_
def make_conect_line(self): def make_conect_line(self):
"""PDB line for bonding within this molecule. """PDB line for bonding within this molecule.
@@ -298,45 +276,11 @@ class Atom:
res += '\n' res += '\n'
return res return res
def get_input_parameters(self):
"""Extract the input parameters stored in the occupancy and b-factor
fields in input files"""
# Set the group type
if self.occ != '-':
# make sure to set the terminal
if self.occ in ['N+', 'C-']:
self.terminal = self.occ
# save the ligand group charge
if self.occ == 'BLG':
self.charge = +1
elif self.occ == 'ALG':
self.charge = -1
# generic ions
if self.occ in ['1P', '2P', '1N', '2N']:
self.res_name = self.occ
self.occ = 'Ion'
# correct the group type
self.occ = self.occ.replace('N+', 'Nterm')
self.occ = self.occ.replace('C-', 'Cterm')
self.occ = self.occ.replace('ION', 'Ion')
self.occ = self.occ.replace('ALG', 'titratable_ligand')
self.occ = self.occ.replace('BLG', 'titratable_ligand')
self.occ = self.occ.replace('LG', 'non_titratable_ligand')
self.group_label = "{0:s}_group".format(self.occ)
# set the model pKa value
if self.beta != '-':
self.group_model_pka = float(self.beta)
self.group_model_pka_set = True
# set occ and beta to standard values
self.occ = '1.00'
self.beta = '0.00'
def make_pdb_line(self): def make_pdb_line(self):
"""Create PDB line. """Create PDB line.
TODO - this could/should be a @property method/attribute TODO - this could/should be a @property method/attribute
TODO - figure out difference between make_pdb_line, make_input_line, TODO - figure out difference between make_pdb_line, and make_pdb_line2
and make_pdb_line2
Returns: Returns:
String with PDB line. String with PDB line.
@@ -367,8 +311,7 @@ class Atom:
"""Create a PDB line. """Create a PDB line.
TODO - this could/should be a @property method/attribute TODO - this could/should be a @property method/attribute
TODO - figure out difference between make_pdb_line, make_input_line, TODO - figure out difference between make_pdb_line, and make_pdb_line2
and make_pdb_line2
Returns: Returns:
String with PDB line. String with PDB line.

View File

@@ -25,7 +25,13 @@ RESIDUE_MULTIPLIER = 1000
class ConformationContainer: class ConformationContainer:
"""Container for molecular conformations""" """Container for molecular conformations
.. versionchanged:: 3.4.0
Removed :meth:`additional_setup_when_reading_input_files` as reading
PROPKA inputs is no longer supported.
"""
def __init__(self, name='', parameters=None, molecular_container=None): def __init__(self, name='', parameters=None, molecular_container=None):
"""Initialize conformation container. """Initialize conformation container.

View File

@@ -40,7 +40,14 @@ EXPECTED_ATOMS_BASE_INTERACTIONS = {
class Group: class Group:
"""Class for storing groups important to pKa calculations.""" """Class for storing groups important to pKa calculations.
.. versionchanged:: 3.4.0
Removed :meth:`make_covalently_coupled_line` and
:meth:`make_non_covalently_coupled_line` as writing PROPKA inputs is no
longer supported.
"""
def __init__(self, atom): def __init__(self, atom):
"""Initialize with an atom. """Initialize with an atom.
@@ -178,48 +185,6 @@ class Group:
self.determinants[type_].append( self.determinants[type_].append(
Determinant(new_determinant.group, new_determinant.value)) Determinant(new_determinant.group, new_determinant.value))
def make_covalently_coupled_line(self):
"""Create line for covalent coupling.
Returns:
string
"""
# first check if there are any coupled groups at all
if len(self.covalently_coupled_groups) == 0:
return ''
line = 'CCOUPL{0:5d}'.format(self.atom.numb)
# extract and sort numbers of coupled groups
coupled = []
for group in self.covalently_coupled_groups:
coupled.append(group.atom.numb)
coupled.sort()
# write 'em out
for num in coupled:
line += '{0:5d}'.format(num)
line += '\n'
return line
def make_non_covalently_coupled_line(self):
"""Create line for non-covalent coupling.
Returns:
string
"""
# first check if there are any coupled groups at all
if len(self.non_covalently_coupled_groups) == 0:
return ''
line = 'NCOUPL{0:5d}'.format(self.atom.numb)
# extract and sort numbers of coupled groups
coupled = []
for group in self.non_covalently_coupled_groups:
coupled.append(group.atom.numb)
coupled.sort()
# write 'em out
for num in coupled:
line += '{0:5d}'.format(num)
line += '\n'
return line
def __eq__(self, other): def __eq__(self, other):
"""Needed for creating sets of groups.""" """Needed for creating sets of groups."""
if self.atom.type == 'atom': if self.atom.type == 'atom':

View File

@@ -3,6 +3,11 @@ Input handling
============== ==============
Input routines. Input routines.
.. versionchanged:: 3.4.0
Methods to read PROPKA input files (:func:`read_propka` and
:func:`get_atom_lines_from_input`) have been removed.
""" """
from pathlib import Path from pathlib import Path
from pkg_resources import resource_filename from pkg_resources import resource_filename
@@ -67,14 +72,17 @@ def read_molecule_file(filename: str, mol_container, stream=None):
usually have an associated file name, an appropirate file name should usually have an associated file name, an appropirate file name should
be passed to the ``filename`` argument. In this case, ``filename`` is be passed to the ``filename`` argument. In this case, ``filename`` is
not opened for reading, but instead is used to help recognise the file not opened for reading, but instead is used to help recognise the file
type (based on the extension being either `.pdb` or `.propka_input`) type (based on the extension being `.pdb`) and also uses that given
and also uses that given ``filename`` to assign a name to the input ``filename`` to assign a name to the input
:class:`~propka.molecular_container.MolecularContainer` object. :class:`~propka.molecular_container.MolecularContainer` object.
>>> read_molecule_file('test.pdb', mol_container, >>> read_molecule_file('test.pdb', mol_container,
stream=string_io_object) stream=string_io_object)
<propka.molecular_container.MolecularContainer at 0x7f6e0c8f2310> <propka.molecular_container.MolecularContainer at 0x7f6e0c8f2310>
.. versionchanged:: 3.4.0
PROPKA input files (extension: `.propka_input`) are no longer read.
""" """
input_path = Path(filename) input_path = Path(filename)
mol_container.name = input_path.stem mol_container.name = input_path.stem
@@ -109,17 +117,6 @@ def read_molecule_file(filename: str, mol_container, stream=None):
mol_container.conformations[name].sort_atoms() mol_container.conformations[name].sort_atoms()
# find coupled groups # find coupled groups
mol_container.find_covalently_coupled_groups() mol_container.find_covalently_coupled_groups()
elif input_file_extension.lower() == '.propka_input':
# input is a propka_input file
conformations, conformation_names = read_propka(
input_file, mol_container.version.parameters, mol_container)
mol_container.conformations = conformations
mol_container.conformation_names = conformation_names
# Extract groups - this merely sets up the groups found in the
# input file
mol_container.extract_groups()
# do some additional set up
mol_container.additional_setup_when_reading_input_file()
else: else:
str_ = "Unknown input file type {0!s} for file {1!s}".format( str_ = "Unknown input file type {0!s} for file {1!s}".format(
input_file_extension, input_path) input_file_extension, input_path)
@@ -220,94 +217,6 @@ def get_atom_lines_from_pdb(pdb_file, ignore_residues=[], keep_protons=False,
terminal = None terminal = None
def read_propka(input_file, parameters, molecule):
"""Read PROPKA input file for molecular container.
Args:
input_file: input file
parameters: parameters for parsing/setup
molecule: molecular container
Returns:
list with [conformations, names of conformations]
"""
conformations = {}
# read in all atoms in the input file
lines = get_atom_lines_from_input(input_file)
for (name, atom) in lines:
if not name in conformations.keys():
conformations[name] = ConformationContainer(
name=name, parameters=parameters,
molecular_container=molecule)
conformations[name].add_atom(atom)
# make a sorted list of conformation names
names = sorted(conformations.keys(), key=conformation_sorter)
return [conformations, names]
def get_atom_lines_from_input(input_file, tags=['ATOM ', 'HETATM']):
"""Get atom lines from a PROPKA input file.
Args:
input_file: input file
tags: tags defining atom lines
Yields:
conformation container, list of atoms
"""
lines = open_file_for_reading(input_file).readlines()
conformation = ''
atoms = {}
numbers = []
for line in lines:
tag = line[0:6]
# set the conformation
if tag == 'MODEL ':
conformation = line[6:].strip()
# found an atom - save it
if tag in tags:
atom = Atom(line=line)
atom.get_input_parameters()
initialize_atom_group(atom)
atom.groups_extracted = 1
atom.is_protonated = True
atoms[atom.numb] = atom
numbers.append(atom.numb)
# found bonding information - apply it
if tag == 'CONECT' and len(line) > 14:
conect_numbers = [line[i:i+5] for i in range(6, len(line)-1, 5)]
center_atom = atoms[int(conect_numbers[0])]
for num in conect_numbers[1:]:
bond_atom = atoms[int(num)]
# remember to check for cysteine bridges
if center_atom.element == 'S' and bond_atom.element == 'S':
center_atom.cysteine_bridge = True
bond_atom.cysteine_bridge = True
# set up bonding
if not bond_atom in center_atom.bonded_atoms:
center_atom.bonded_atoms.append(bond_atom)
if not center_atom in bond_atom.bonded_atoms:
bond_atom.bonded_atoms.append(center_atom)
# found info on covalent coupling
if tag == 'CCOUPL' and len(line) > 14:
conect_numbers = [line[i:i+5] for i in range(6, len(line)-1, 5)]
center_atom = atoms[int(conect_numbers[0])]
for num in conect_numbers[1:]:
cov_atom = atoms[int(num)]
center_atom.group.couple_covalently(cov_atom.group)
# found info on non-covalent coupling
if tag == 'NCOUPL' and len(line) > 14:
conect_numbers = [line[i:i+5] for i in range(6, len(line)-1, 5)]
center_atom = atoms[int(conect_numbers[0])]
for num in conect_numbers[1:]:
cov_atom = atoms[int(num)]
center_atom.group.couple_non_covalently(cov_atom.group)
# this conformation is done - yield the atoms
if tag == 'ENDMDL':
for num in numbers:
yield (conformation, atoms[num])
# prepare for next conformation
atoms = {}
numbers = []
def read_pdb(pdb_file, parameters, molecule): def read_pdb(pdb_file, parameters, molecule):
"""Parse a PDB file. """Parse a PDB file.

View File

@@ -195,6 +195,11 @@ def build_parser(parser=None):
new parser will be created. new parser will be created.
Returns: Returns:
ArgumentParser object. ArgumentParser object.
.. versionchanged:: 3.4.0
Argument `--generate-propka-input` has been removed as writing PROPKA
input files is no longer supported.
""" """
if parser is not None: if parser is not None:
group = parser.add_argument_group(title="PROPKA invoation options") group = parser.add_argument_group(title="PROPKA invoation options")
@@ -285,9 +290,6 @@ def build_parser(parser=None):
group.add_argument( group.add_argument(
"-q", "--quiet", action="store_const", const="WARNING", "-q", "--quiet", action="store_const", const="WARNING",
dest="log_level", help="suppress non-warning messages") dest="log_level", help="suppress non-warning messages")
group.add_argument(
"--generate-propka-input", action="store_true",
help="Generate a PROPKA input file")
group.add_argument( group.add_argument(
"--protonate-all", dest="protonate_all", action="store_true", "--protonate-all", dest="protonate_all", action="store_true",
help="Protonate all atoms (will not influence pKa calculation)", help="Protonate all atoms (will not influence pKa calculation)",

View File

@@ -6,7 +6,7 @@ Molecular container for storing all contents of PDB files.
""" """
import os import os
import propka.version import propka.version
from propka.output import write_propka, write_pka, print_header, print_result from propka.output import write_pka, print_header, print_result
from propka.conformation_container import ConformationContainer from propka.conformation_container import ConformationContainer
from propka.lib import info, warning, make_grid from propka.lib import info, warning, make_grid
@@ -22,6 +22,12 @@ class MolecularContainer:
TODO - this class name does not conform to PEP8 but has external use. TODO - this class name does not conform to PEP8 but has external use.
We should deprecate and change eventually. We should deprecate and change eventually.
.. versionchanged:: 3.4.0
Removed :meth:`write_propka` and
:meth:`additional_setup_when_reading_input_file` as reading and writing
PROPKA input files is no longer supported.
""" """
def __init__(self, parameters, options=None): def __init__(self, parameters, options=None):
@@ -72,11 +78,6 @@ class MolecularContainer:
for name in self.conformation_names: for name in self.conformation_names:
self.conformations[name].extract_groups() self.conformations[name].extract_groups()
def additional_setup_when_reading_input_file(self):
"""Additional setup."""
for name in self.conformation_names:
self.conformations[name].additional_setup_when_reading_input_file()
def calculate_pka(self): def calculate_pka(self):
"""Calculate pKa values.""" """Calculate pKa values."""
# calculate for each conformation # calculate for each conformation
@@ -123,16 +124,6 @@ class MolecularContainer:
self.conformation_names[0]].chains self.conformation_names[0]].chains
self.conformations['AVR'] = avr_conformation self.conformations['AVR'] = avr_conformation
def write_propka(self, filename=None):
"""Write PROPKA input file.
Args:
filename: file to write to
"""
if filename is None:
filename = os.path.join('{0:s}.propka_input'.format(self.name))
write_propka(self, filename)
def write_pka(self, filename=None, reference="neutral", def write_pka(self, filename=None, reference="neutral",
direction="folding", options=None): direction="folding", options=None):
"""Write pKa information to a file. """Write pKa information to a file.

View File

@@ -3,6 +3,11 @@ Output
====== ======
Output routines. Output routines.
.. versionchanged::3.4.0
Removed :func:`write_proka` as writing PROPKA input files is no longer
supported.
""" """
from datetime import date from datetime import date
from propka.lib import info from propka.lib import info
@@ -589,30 +594,3 @@ def write_mol2_for_atoms(atoms, filename):
out.write(bonds_section) out.write(bonds_section)
out.write(substructure_section) out.write(substructure_section)
out.close() out.close()
def write_propka(molecular_container, filename):
"""Write PROPKA input file for molecular container.
Args:
molecular_container: molecular container
filename: output file name
"""
out = open_file_for_writing(filename)
for conformation_name in molecular_container.conformation_names:
out.write('MODEL {0:s}\n'.format(conformation_name))
# write atoms
for atom in molecular_container.conformations[conformation_name].atoms:
out.write(atom.make_input_line())
# write bonds
for atom in molecular_container.conformations[conformation_name].atoms:
out.write(atom.make_conect_line())
# write covalently coupled groups
for group in (
molecular_container.conformations[conformation_name].groups):
out.write(group.make_covalently_coupled_line())
# write non-covalently coupled groups
for group in (
molecular_container.conformations[conformation_name].groups):
out.write(group.make_non_covalently_coupled_line())
out.write('ENDMDL\n')
out.close()

View File

@@ -21,7 +21,12 @@ _LOGGER = logging.getLogger("PROPKA")
def main(optargs=None): def main(optargs=None):
"""Read in structure files, calculate pKa values, and print pKa files.""" """Read in structure files, calculate pKa values, and print pKa files.
.. versionchanged:: 3.4.0
Removed ability to write out PROPKA input files.
"""
# loading options, flags and arguments # loading options, flags and arguments
optargs = optargs if optargs is not None else [] optargs = optargs if optargs is not None else []
options = loadOptions(*optargs) options = loadOptions(*optargs)
@@ -32,8 +37,6 @@ def main(optargs=None):
my_molecule = read_molecule_file(pdbfile, my_molecule) my_molecule = read_molecule_file(pdbfile, my_molecule)
my_molecule.calculate_pka() my_molecule.calculate_pka()
my_molecule.write_pka() my_molecule.write_pka()
if options.generate_propka_input:
my_molecule.write_propka()
def single(filename: str, optargs: tuple = (), stream=None, def single(filename: str, optargs: tuple = (), stream=None,
@@ -93,6 +96,9 @@ def single(filename: str, optargs: tuple = (), stream=None,
:func:`propka.input.read_molecule_file` :func:`propka.input.read_molecule_file`
.. versionchanged:: 3.4.0
Removed ability to write out PROPKA input files.
""" """
# Deal with input optarg options # Deal with input optarg options
optargs = tuple(optargs) optargs = tuple(optargs)
@@ -113,8 +119,6 @@ def single(filename: str, optargs: tuple = (), stream=None,
my_molecule.calculate_pka() my_molecule.calculate_pka()
# write outputs # write outputs
if options.generate_propka_input:
my_molecule.write_propka()
if write_pka: if write_pka:
my_molecule.write_pka() my_molecule.write_pka()

View File

@@ -78,8 +78,6 @@ def run_propka(options, pdb_path, tmp_path):
molecule = read_molecule_file(str(pdb_path), molecule) molecule = read_molecule_file(str(pdb_path), molecule)
molecule.calculate_pka() molecule.calculate_pka()
molecule.write_pka() molecule.write_pka()
if args.generate_propka_input:
molecule.write_propka()
finally: finally:
os.chdir(cwd) os.chdir(cwd)

View File

@@ -30,6 +30,7 @@ def test_single_file(tmpdir, pdb, options):
with tmpdir.as_cwd(): with tmpdir.as_cwd():
pkrun.single(filename, options) pkrun.single(filename, options)
compare_output(pdb, Path.cwd(), ref_path) compare_output(pdb, Path.cwd(), ref_path)
assert os.path.isfile(f'{pdb}.pka')
@pytest.mark.parametrize("pdb, options", [ @pytest.mark.parametrize("pdb, options", [
@@ -51,6 +52,7 @@ def test_single_filestream(tmpdir, pdb, options):
with tmpdir.as_cwd(): with tmpdir.as_cwd():
pkrun.single(filename, options, stream=filestream) pkrun.single(filename, options, stream=filestream)
compare_output(pdb, Path.cwd(), ref_path) compare_output(pdb, Path.cwd(), ref_path)
assert os.path.isfile(f'{pdb}.pka')
filestream.close() filestream.close()
@@ -69,27 +71,10 @@ def test_single_nopka(tmpdir):
assert not os.path.isfile(f"{pdb}.pka") assert not os.path.isfile(f"{pdb}.pka")
def test_single_propka_input(tmpdir):
"""Basic test to check that the propka_input file is written when
`--generate-propka-input` is passed"""
pdb = "1FTJ-Chain-A"
options = ('--generate-propka-input',)
ref_path, pdb_path = get_paths(pdb)
filename = f"{pdb}.pdb"
with open(pdb_path, 'r') as writer:
filestream = StringIO(writer.read())
with tmpdir.as_cwd():
pkrun.single(filename, options, stream=filestream)
assert os.path.isfile(f"{pdb}.propka_input")
def test_single_extra_files_logwarn(tmpdir, caplog): def test_single_extra_files_logwarn(tmpdir, caplog):
"""Tests that a logging warning is thrown if passing files via optargs""" """Tests that a logging warning is thrown if passing files via optargs"""
pdb = "1FTJ-Chain-A" pdb = "1FTJ-Chain-A"
options = ('-f foo.pdb bar.pdb', '-f test.pdb test2.pdb', options = ('-f foo.pdb bar.pdb', '-f test.pdb test2.pdb')
'--generate-propka-input')
ref_path, pdb_path = get_paths(pdb) ref_path, pdb_path = get_paths(pdb)
filename = str(pdb_path) filename = str(pdb_path)

View File

@@ -38,8 +38,6 @@ def run_propka_stream(options, input_file, filename):
molecule = read_molecule_file(filename, molecule, stream=input_file) molecule = read_molecule_file(filename, molecule, stream=input_file)
molecule.calculate_pka() molecule.calculate_pka()
molecule.write_pka() molecule.write_pka()
if args.generate_propka_input:
molecule.write_propka()
@pytest.mark.parametrize("pdb, options", [ @pytest.mark.parametrize("pdb, options", [