Merge pull request #100 from IAlibay/rm-propkainput

Removal of PROPKA input
This commit is contained in:
Nathan Baker
2020-11-29 15:41:28 -08:00
committed by GitHub
12 changed files with 70 additions and 321 deletions

View File

@@ -12,8 +12,7 @@ protein-ligand complexes based in the 3D structure. The
propka3 [-h] [-f FILENAMES] [-r REFERENCE] [-c CHAINS] [-i TITRATE_ONLY] [-t THERMOPHILES] [-a ALIGNMENT] [-m MUTATIONS]
[-v VERSION_LABEL] [-p PARAMETERS] [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-o PH] [-w WINDOW WINDOW WINDOW]
[-g GRID GRID GRID] [--mutator MUTATOR] [--mutator-option MUTATOR_OPTIONS] [-d] [-l] [-k] [-q] [--generate-propka-input]
[--protonate-all]
[-g GRID GRID GRID] [--mutator MUTATOR] [--mutator-option MUTATOR_OPTIONS] [-d] [-l] [-k] [-q] [--protonate-all]
input_pdb
@@ -120,10 +119,6 @@ protein-ligand complexes based in the 3D structure. The
suppress non-warning messages (default: None)
.. option:: --generate-propka-input
Generate a PROPKA input file (default: False)
.. option:: --protonate-all
Protonate all atoms (will not influence pKa calculation)

View File

@@ -12,10 +12,6 @@ from . import hybrid36
# Format strings that get used in multiple places (or are very complex)
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 = (
"{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}"
@@ -33,7 +29,13 @@ STR_FMT = (
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):
"""Initialize Atom object.
@@ -56,9 +58,6 @@ class Atom:
self.z = None
self.group = None
self.group_type = None
self.group_label = None
self.group_model_pka = None
self.group_model_pka_set = None
self.number_of_bonded_elements = {}
self.cysteine_bridge = False
self.bonded_atoms = []
@@ -255,31 +254,6 @@ class Atom:
new_atom.icode = self.icode
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):
"""PDB line for bonding within this molecule.
@@ -298,45 +272,11 @@ class Atom:
res += '\n'
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):
"""Create PDB line.
TODO - this could/should be a @property method/attribute
TODO - figure out difference between make_pdb_line, make_input_line,
and make_pdb_line2
TODO - figure out difference between make_pdb_line, and make_pdb_line2
Returns:
String with PDB line.
@@ -367,8 +307,7 @@ class Atom:
"""Create a PDB line.
TODO - this could/should be a @property method/attribute
TODO - figure out difference between make_pdb_line, make_input_line,
and make_pdb_line2
TODO - figure out difference between make_pdb_line, and make_pdb_line2
Returns:
String with PDB line.

View File

@@ -25,7 +25,13 @@ RESIDUE_MULTIPLIER = 1000
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):
"""Initialize conformation container.
@@ -58,19 +64,6 @@ class ConformationContainer:
if group:
self.setup_and_add_group(group)
def additional_setup_when_reading_input_file(self):
"""Generate interaction map and charge centers."""
# if a group is coupled and we are reading a .propka_input file, then
# some more configuration might be needed
map_ = make_interaction_map(
'Covalent coupling map for {0:s}'.format(str(self)),
self.get_covalently_coupled_groups(),
lambda g1, g2: g1 in g2.covalently_coupled_groups)
info(map_)
# check if we should set a common charge centre as well
if self.parameters.common_charge_centre:
self.set_common_charge_centres()
def set_common_charge_centres(self):
"""Assign charge centers to groups."""
for system in self.get_coupled_systems(

View File

@@ -3,6 +3,11 @@ Data structures for groups
==========================
Routines and classes for storing groups important to PROPKA calculations.
.. versionchanged:: 3.4.0
Removed :func:`initialize_atom_group` as reading PROPKA inputs is no longer
supported.
"""
import math
import propka.ligand
@@ -40,7 +45,14 @@ EXPECTED_ATOMS_BASE_INTERACTIONS = {
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):
"""Initialize with an atom.
@@ -178,48 +190,6 @@ class Group:
self.determinants[type_].append(
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):
"""Needed for creating sets of groups."""
if self.atom.type == 'atom':
@@ -1425,15 +1395,3 @@ def is_ion_group(parameters, atom):
if atom.res_name.strip() in parameters.ions.keys():
return IonGroup(atom)
return None
def initialize_atom_group(atom):
"""Initialize an atom group.
Args:
atom: atom to initialize
"""
# try to initialise the group
group_attr = globals()[atom.group_label]
atom.group = group_attr(atom)
atom.group.model_pka = atom.group_model_pka
atom.group.model_pka_set = atom.group_model_pka_set

View File

@@ -3,13 +3,17 @@ Input handling
==============
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 pkg_resources import resource_filename
from propka.lib import protein_precheck
from propka.atom import Atom
from propka.conformation_container import ConformationContainer
from propka.group import initialize_atom_group
def open_file_for_reading(input_file):
@@ -67,14 +71,17 @@ def read_molecule_file(filename: str, mol_container, stream=None):
usually have an associated file name, an appropirate file name should
be passed to the ``filename`` argument. In this case, ``filename`` is
not opened for reading, but instead is used to help recognise the file
type (based on the extension being either `.pdb` or `.propka_input`)
and also uses that given ``filename`` to assign a name to the input
type (based on the extension being `.pdb`) and also uses that given
``filename`` to assign a name to the input
:class:`~propka.molecular_container.MolecularContainer` object.
>>> read_molecule_file('test.pdb', mol_container,
stream=string_io_object)
<propka.molecular_container.MolecularContainer at 0x7f6e0c8f2310>
.. versionchanged:: 3.4.0
PROPKA input files (extension: `.propka_input`) are no longer read.
"""
input_path = Path(filename)
mol_container.name = input_path.stem
@@ -109,17 +116,6 @@ def read_molecule_file(filename: str, mol_container, stream=None):
mol_container.conformations[name].sort_atoms()
# find 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:
str_ = "Unknown input file type {0!s} for file {1!s}".format(
input_file_extension, input_path)
@@ -220,94 +216,6 @@ def get_atom_lines_from_pdb(pdb_file, ignore_residues=[], keep_protons=False,
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):
"""Parse a PDB file.

View File

@@ -195,9 +195,14 @@ def build_parser(parser=None):
new parser will be created.
Returns:
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:
group = parser.add_argument_group(title="PROPKA invoation options")
group = parser.add_argument_group(title="PROPKA invocation options")
else:
parser = argparse.ArgumentParser(
description=("PROPKA predicts the pKa values of ionizable "
@@ -285,9 +290,6 @@ def build_parser(parser=None):
group.add_argument(
"-q", "--quiet", action="store_const", const="WARNING",
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(
"--protonate-all", dest="protonate_all", action="store_true",
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 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.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.
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):
@@ -72,11 +78,6 @@ class MolecularContainer:
for name in self.conformation_names:
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):
"""Calculate pKa values."""
# calculate for each conformation
@@ -123,16 +124,6 @@ class MolecularContainer:
self.conformation_names[0]].chains
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",
direction="folding", options=None):
"""Write pKa information to a file.

View File

@@ -3,6 +3,11 @@ Output
======
Output routines.
.. versionchanged::3.4.0
Removed :func:`write_proka` as writing PROPKA input files is no longer
supported.
"""
from datetime import date
from propka.lib import info
@@ -589,30 +594,3 @@ def write_mol2_for_atoms(atoms, filename):
out.write(bonds_section)
out.write(substructure_section)
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):
"""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
optargs = optargs if optargs is not None else []
options = loadOptions(*optargs)
@@ -32,8 +37,6 @@ def main(optargs=None):
my_molecule = read_molecule_file(pdbfile, my_molecule)
my_molecule.calculate_pka()
my_molecule.write_pka()
if options.generate_propka_input:
my_molecule.write_propka()
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`
.. versionchanged:: 3.4.0
Removed ability to write out PROPKA input files.
"""
# Deal with input optarg options
optargs = tuple(optargs)
@@ -113,8 +119,6 @@ def single(filename: str, optargs: tuple = (), stream=None,
my_molecule.calculate_pka()
# write outputs
if options.generate_propka_input:
my_molecule.write_propka()
if 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.calculate_pka()
molecule.write_pka()
if args.generate_propka_input:
molecule.write_propka()
finally:
os.chdir(cwd)

View File

@@ -30,6 +30,7 @@ def test_single_file(tmpdir, pdb, options):
with tmpdir.as_cwd():
pkrun.single(filename, options)
compare_output(pdb, Path.cwd(), ref_path)
assert os.path.isfile(f'{pdb}.pka')
@pytest.mark.parametrize("pdb, options", [
@@ -51,6 +52,7 @@ def test_single_filestream(tmpdir, pdb, options):
with tmpdir.as_cwd():
pkrun.single(filename, options, stream=filestream)
compare_output(pdb, Path.cwd(), ref_path)
assert os.path.isfile(f'{pdb}.pka')
filestream.close()
@@ -69,27 +71,10 @@ def test_single_nopka(tmpdir):
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):
"""Tests that a logging warning is thrown if passing files via optargs"""
pdb = "1FTJ-Chain-A"
options = ('-f foo.pdb bar.pdb', '-f test.pdb test2.pdb',
'--generate-propka-input')
options = ('-f foo.pdb bar.pdb', '-f test.pdb test2.pdb')
ref_path, pdb_path = get_paths(pdb)
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.calculate_pka()
molecule.write_pka()
if args.generate_propka_input:
molecule.write_propka()
@pytest.mark.parametrize("pdb, options", [