Merge pull request #109 from jensengroup/nathan/logging

Remove logging abstraction to facilitate debugging and reduce bloat.
This commit is contained in:
Nathan Baker
2020-12-01 11:55:26 -08:00
committed by GitHub
16 changed files with 423 additions and 330 deletions

View File

@@ -5,12 +5,14 @@ Bonds
PROPKA representation of bonds.
"""
import logging
import math
import json
import pkg_resources
import propka.calculations
# TODO - replace the info/warning imports with logging functionality
from propka.lib import info
_LOGGER = logging.getLogger(__name__)
# TODO - should these constants be defined higher up in the module?
@@ -30,8 +32,8 @@ class BondMaker:
"""
def __init__(self):
# predefined bonding distances
self.distances = {'S-S' : DISULFIDE_DISTANCE,
'F-F' : FLUORIDE_DISTANCE}
self.distances = {'S-S': DISULFIDE_DISTANCE,
'F-F': FLUORIDE_DISTANCE}
self.distances_squared = {}
for key in self.distances:
self.distances_squared[key] = (
@@ -53,22 +55,22 @@ class BondMaker:
'C': ['CA', 'O'], 'O': ['C']}
self.num_pi_elec_bonds_backbone = {'C': 1, 'O': 1}
self.num_pi_elec_conj_bonds_backbone = {'N': 1}
self.num_pi_elec_bonds_sidechains = {'ARG-CZ' : 1, 'ARG-NH1': 1,
'ASN-OD1': 1, 'ASN-CG' : 1,
'ASP-OD1': 1, 'ASP-CG' : 1,
'GLU-OE1': 1, 'GLU-CD' : 1,
'GLN-OE1': 1, 'GLN-CD' : 1,
'HIS-CG' : 1, 'HIS-CD2': 1,
self.num_pi_elec_bonds_sidechains = {'ARG-CZ': 1, 'ARG-NH1': 1,
'ASN-OD1': 1, 'ASN-CG': 1,
'ASP-OD1': 1, 'ASP-CG': 1,
'GLU-OE1': 1, 'GLU-CD': 1,
'GLN-OE1': 1, 'GLN-CD': 1,
'HIS-CG': 1, 'HIS-CD2': 1,
'HIS-ND1': 1, 'HIS-CE1': 1,
'PHE-CG' : 1, 'PHE-CD1': 1,
'PHE-CE1': 1, 'PHE-CZ' : 1,
'PHE-CG': 1, 'PHE-CD1': 1,
'PHE-CE1': 1, 'PHE-CZ': 1,
'PHE-CE2': 1, 'PHE-CD2': 1,
'TRP-CG' : 1, 'TRP-CD1': 1,
'TRP-CG': 1, 'TRP-CD1': 1,
'TRP-CE2': 1, 'TRP-CD2': 1,
'TRP-CE3': 1, 'TRP-CZ3': 1,
'TRP-CH2': 1, 'TRP-CZ2': 1,
'TYR-CG' : 1, 'TYR-CD1': 1,
'TYR-CE1': 1, 'TYR-CZ' : 1,
'TYR-CG': 1, 'TYR-CD1': 1,
'TYR-CE1': 1, 'TYR-CZ': 1,
'TYR-CE2': 1, 'TYR-CD2': 1}
self.num_pi_elec_conj_bonds_sidechains = {'ARG-NE': 1, 'ARG-NH2': 1,
'ASN-ND2': 1, 'GLN-NE2': 1,
@@ -90,13 +92,13 @@ class BondMaker:
Args:
protein: the protein to search for bonds
"""
info('++++ Side chains ++++')
_LOGGER.info('++++ Side chains ++++')
# side chains
for chain in protein.chains:
for residue in chain.residues:
if residue.res_name.replace(' ', '') not in ['N+', 'C-']:
self.find_bonds_for_side_chain(residue.atoms)
info('++++ Backbones ++++')
_LOGGER.info('++++ Backbones ++++')
# backbone
last_residues = []
for chain in protein.chains:
@@ -108,11 +110,11 @@ class BondMaker:
self.connect_backbone(chain.residues[i-1],
chain.residues[i])
last_residues.append(chain.residues[i])
info('++++ terminal oxygen ++++')
_LOGGER.info('++++ terminal oxygen ++++')
# terminal OXT
for last_residue in last_residues:
self.find_bonds_for_terminal_oxygen(last_residue)
info('++++ cysteines ++++')
_LOGGER.info('++++ cysteines ++++')
# Cysteines
for chain in protein.chains:
for i in range(0, len(chain.residues)):
@@ -204,8 +206,8 @@ class BondMaker:
if key in list(self.num_pi_elec_conj_bonds_sidechains.keys()):
atom1.num_pi_elec_conj_2_3_bonds = (
self.num_pi_elec_conj_bonds_sidechains[key])
if not atom1.name in self.backbone_atoms:
if not atom1.name in self.terminal_oxygen_names:
if atom1.name not in self.backbone_atoms:
if atom1.name not in self.terminal_oxygen_names:
for atom2 in atoms:
if atom2.name in (
self
@@ -266,7 +268,6 @@ class BondMaker:
Returns:
list of atoms
"""
#self.find_bonds_for_protein(molecule)
atoms = []
for chain in molecule.chains:
for residue in chain.residues:
@@ -424,9 +425,9 @@ class BondMaker:
"""
if atom1 == atom2:
return
if not atom1 in atom2.bonded_atoms:
if atom1 not in atom2.bonded_atoms:
atom2.bonded_atoms.append(atom1)
if not atom2 in atom1.bonded_atoms:
if atom2 not in atom1.bonded_atoms:
atom1.bonded_atoms.append(atom2)
def generate_protein_bond_dictionary(self, atoms):
@@ -441,21 +442,21 @@ class BondMaker:
name_i = atom.name
resi_j = bonded_atom.res_name
name_j = bonded_atom.name
if not name_i in (
if name_i not in (
self.backbone_atoms
or not name_j in self.backbone_atoms):
if not name_i in (
or name_j not in self.backbone_atoms):
if name_i not in (
self.terminal_oxygen_names
and not name_j in self.terminal_oxygen_names):
if not resi_i in list(self.protein_bonds.keys()):
and name_j not in self.terminal_oxygen_names):
if resi_i not in list(self.protein_bonds.keys()):
self.protein_bonds[resi_i] = {}
if not name_i in self.protein_bonds[resi_i]:
if name_i not in self.protein_bonds[resi_i]:
self.protein_bonds[resi_i][name_i] = []
if not name_j in self.protein_bonds[resi_i][name_i]:
if name_j not in self.protein_bonds[resi_i][name_i]:
self.protein_bonds[resi_i][name_i].append(name_j)
if not resi_j in list(self.protein_bonds.keys()):
if resi_j not in list(self.protein_bonds.keys()):
self.protein_bonds[resi_j] = {}
if not name_j in self.protein_bonds[resi_j]:
if name_j not in self.protein_bonds[resi_j]:
self.protein_bonds[resi_j][name_j] = []
if not name_i in self.protein_bonds[resi_j][name_j]:
if name_i not in self.protein_bonds[resi_j][name_j]:
self.protein_bonds[resi_j][name_j].append(name_i)

View File

@@ -4,6 +4,7 @@ Molecular data structures
Container data structure for molecular conformations.
"""
import logging
import functools
import propka.ligand
from propka.output import make_interaction_map
@@ -12,11 +13,14 @@ from propka.coupled_groups import NCCG
from propka.determinants import set_backbone_determinants, set_ion_determinants
from propka.determinants import set_determinants
from propka.group import Group, is_group
from propka.lib import info
_LOGGER = logging.getLogger(__name__)
#: A large number that gets multipled with the integer obtained from applying
#: :func:`ord` to the atom chain ID. Used in calculating atom keys for sorting.
#: :func:`ord` to the atom chain ID. Used in calculating atom keys for
#: sorting.
UNICODE_MULTIPLIER = 1e7
#: A number that gets mutiplied with an atom's residue number. Used in
@@ -60,7 +64,8 @@ class ConformationContainer:
else:
group = atom.group
# if the atom has been checked in a another conformation, check
# if it has a group that should be used in this conformation as well
# if it has a group that should be used in this conformation
# as well
if group:
self.setup_and_add_group(group)
@@ -101,7 +106,7 @@ class ConformationContainer:
'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_)
_LOGGER.info("Coupling map:\n%s", map_)
def find_non_covalently_coupled_groups(self, verbose=False):
"""Find non-covalently coupled groups and set common charge centres.
@@ -109,7 +114,8 @@ class ConformationContainer:
Args:
verbose: verbose output
"""
# check if non-covalent coupling has already been set up in an input file
# check if non-covalent coupling has already been set up in an input
# file
if len(list(filter(lambda g: len(g.non_covalently_coupled_groups) > 0,
self.get_titratable_groups()))) > 0:
self.non_covalently_coupled_groups = True
@@ -186,7 +192,7 @@ class ConformationContainer:
version: version object
options: option object
"""
info('\nCalculating pKas for', self)
_LOGGER.info('Calculating pKas for %s', self)
# calculate desolvation
for group in self.get_titratable_groups() + self.get_ions():
version.calculate_desolvation(group)
@@ -208,7 +214,7 @@ class ConformationContainer:
penalised_labels = self.coupling_effects()
if (self.parameters.remove_penalised_group
and len(penalised_labels) > 0):
info('Removing penalised groups!!!')
_LOGGER.info('Removing penalised groups!!!')
for group in self.get_titratable_groups():
group.remove_determinants(penalised_labels)
# re-calculating the total pKa values
@@ -250,7 +256,7 @@ class ConformationContainer:
# group with the highest pKa is allowed to titrate...
continue
group.coupled_titrating_group = first_group
#... and the rest are penalised
# ... and the rest are penalised
penalised_labels.append(group.label)
return penalised_labels
@@ -426,9 +432,9 @@ class ConformationContainer:
def get_groups_for_calculations(self):
"""Get a list of groups that should be included in results report.
If --titrate_only option is specified, only residues that are titratable
and are in that list are included; otherwise all titratable residues
and CYS residues are included.
If --titrate_only option is specified, only residues that are
titratable and are in that list are included; otherwise all titratable
residues and CYS residues are included.
Returns:
list of groups
@@ -518,7 +524,7 @@ class ConformationContainer:
if not atom.molecular_container:
atom.molecular_container = self.molecular_container
# store chain id for bookkeeping
if not atom.chain_id in self.chains:
if atom.chain_id not in self.chains:
self.chains.append(atom.chain_id)
def copy_atom(self, atom):
@@ -549,7 +555,7 @@ class ConformationContainer:
"""
my_residue_labels = {a.residue_label for a in self.atoms}
for atom in other.atoms:
if not atom.residue_label in my_residue_labels:
if atom.residue_label not in my_residue_labels:
self.copy_atom(atom)
def find_group(self, group):

View File

@@ -4,12 +4,14 @@ Coupling between groups
Describe and analyze energetic coupling between groups.
"""
import logging
import itertools
import propka.lib
from propka.group import Group
from propka.output import make_interaction_map
from propka.lib import info
_LOGGER = logging.getLogger(__name__)
class NonCovalentlyCoupledGroups:
@@ -80,7 +82,8 @@ class NonCovalentlyCoupledGroups:
if (abs(group1.intrinsic_pka - group2.intrinsic_pka)
> self.parameters.max_intrinsic_pka_diff and return_on_fail):
return {'coupling_factor': -1.0}
# if everything is OK, calculate the coupling factor and return all info
# if everything is OK, calculate the coupling factor and return all
# info
factor = (
self.get_free_energy_diff_factor(default_energy, swapped_energy)
* self.get_pka_diff_factor(group1.intrinsic_pka,
@@ -107,7 +110,7 @@ class NonCovalentlyCoupledGroups:
if intrinsic_pka_diff <= self.parameters.max_intrinsic_pka_diff:
res = (
1-(intrinsic_pka_diff
/self.parameters.max_intrinsic_pka_diff)**2)
/ self.parameters.max_intrinsic_pka_diff)**2)
return res
def get_free_energy_diff_factor(self, energy1, energy2):
@@ -139,7 +142,7 @@ class NonCovalentlyCoupledGroups:
res = (
(interaction_energy-self.parameters.min_interaction_energy)
/ (1.0+interaction_energy
-self.parameters.min_interaction_energy))
- self.parameters.min_interaction_energy))
return res
def identify_non_covalently_coupled_groups(self, conformation,
@@ -162,17 +165,25 @@ class NonCovalentlyCoupledGroups:
'\n'
' Detecting non-covalently coupled residues\n'
'{sep}\n'
' Maximum pKa difference: {c.max_intrinsic_pka_diff:>4.2f} pKa units\n'
' Minimum interaction energy: {c.min_interaction_energy:>4.2f} pKa units\n'
' Maximum free energy diff.: {c.max_free_energy_diff:>4.2f} pKa units\n'
' Minimum swap pKa shift: {c.min_swap_pka_shift:>4.2f} pKa units\n'
' pH: {c.pH:>6} \n'
' Reference: {c.reference}\n'
' Min pKa: {c.min_pka:>4.2f}\n'
' Max pKa: {c.max_pka:>4.2f}\n'
' Maximum pKa difference: '
'{c.max_intrinsic_pka_diff:>4.2f} pKa units\n'
' Minimum interaction energy: '
'{c.min_interaction_energy:>4.2f} pKa units\n'
' Maximum free energy diff.: '
'{c.max_free_energy_diff:>4.2f} pKa units\n'
' Minimum swap pKa shift: '
'{c.min_swap_pka_shift:>4.2f} pKa units\n'
' pH: '
'{c.pH:>6} \n'
' Reference: '
'{c.reference}\n'
' Min pKa: '
'{c.min_pka:>4.2f}\n'
' Max pKa: '
'{c.max_pka:>4.2f}\n'
'\n')
sep = "-" * 103
info(info_fmt.format(sep=sep, c=self))
_LOGGER.info(info_fmt.format(sep=sep, c=self))
# find coupled residues
titratable_groups = conformation.get_titratable_groups()
if not conformation.non_covalently_coupled_groups:
@@ -202,7 +213,7 @@ class NonCovalentlyCoupledGroups:
'Non-covalent coupling map for {0:s}'.format(str(conformation)),
conformation.get_non_covalently_coupled_groups(),
lambda g1, g2: g1 in g2.non_covalently_coupled_groups)
info(map_)
_LOGGER.info(map_)
for system in conformation.get_coupled_systems(
conformation.get_non_covalently_coupled_groups(),
Group.get_non_covalently_coupled_groups):
@@ -215,7 +226,7 @@ class NonCovalentlyCoupledGroups:
conformation: conformation to print
system: system to print
"""
info(
_LOGGER.info(
'System containing {0:d} groups:'.format(len(system)))
# make list of interactions within this system
interactions = list(itertools.combinations(system, 2))
@@ -230,7 +241,7 @@ class NonCovalentlyCoupledGroups:
coup_info += (
self.make_data_to_string(data, interaction[0], interaction[1])
+ '\n\n')
info(coup_info)
_LOGGER.info(coup_info)
# make list of possible combinations of swap to try out
combinations = propka.lib.generate_combinations(interactions)
# Make possible swap combinations
@@ -246,7 +257,7 @@ class NonCovalentlyCoupledGroups:
for interaction in combination:
self.swap_interactions([interaction[0]], [interaction[1]])
swap_info += self.print_determinants_section(system, 'Swapped')
info(swap_info)
_LOGGER.info(swap_info)
@staticmethod
def get_interaction(group1, group2, include_side_chain_hbs=True):

View File

@@ -6,10 +6,13 @@ Energy calculations.
"""
import math
from propka.lib import warning
import logging
from propka.calculations import squared_distance, get_smallest_distance
_LOGGER = logging.getLogger(__name__)
# TODO - I have no idea what these constants mean so I labeled them "UNK_"
UNK_MIN_DISTANCE = 2.75
MIN_DISTANCE_4TH = math.pow(UNK_MIN_DISTANCE, 4)
@@ -72,7 +75,10 @@ def calculate_scale_factor(parameters, weight):
Returns:
scaling factor
"""
scale_factor = 1.0 - (1.0 - parameters.desolvationSurfaceScalingFactor)*(1.0 - weight)
scale_factor = (
1.0 - (1.0 - parameters.desolvationSurfaceScalingFactor)
* (1.0 - weight)
)
return scale_factor
@@ -83,7 +89,8 @@ def calculate_weight(parameters, num_volume):
Args:
parameters: parameters for desolvation calculation
num_volume: number of heavy atoms within desolvation calculation volume
num_volume: number of heavy atoms within desolvation calculation
volume
Returns:
desolvation weight
"""
@@ -135,12 +142,14 @@ def hydrogen_bond_energy(dist, dpka_max, cutoffs, f_angle=1.0):
def angle_distance_factors(atom1=None, atom2=None, atom3=None, center=None):
"""Calculate distance and angle factors for three atoms for backbone interactions.
"""Calculate distance and angle factors for three atoms for backbone
interactions.
NOTE - you need to use atom1 to be the e.g. ASP atom if distance is reset at
return: [O1 -- H2-N3].
NOTE - you need to use atom1 to be the e.g. ASP atom if distance is reset
at return: [O1 -- H2-N3].
Also generalized to be able to be used for residue 'centers' for C=O COO interactions.
Also generalized to be able to be used for residue 'centers' for C=O COO
interactions.
Args:
atom1: first atom for calculation (could be None)
@@ -194,9 +203,11 @@ def hydrogen_bond_interaction(group1, group2, version):
# find the smallest distance between interacting atoms
atoms1 = group1.get_interaction_atoms(group2)
atoms2 = group2.get_interaction_atoms(group1)
[closest_atom1, dist, closest_atom2] = get_smallest_distance(atoms1, atoms2)
[closest_atom1, dist, closest_atom2] = get_smallest_distance(
atoms1, atoms2
)
if None in [closest_atom1, closest_atom2]:
warning(
_LOGGER.warning(
'Side chain interaction failed for {0:s} and {1:s}'.format(
group1.label, group2.label))
return None
@@ -210,11 +221,16 @@ def hydrogen_bond_interaction(group1, group2, version):
return None
# check that bond distance criteria is met
min_hbond_dist = version.parameters.min_bond_distance_for_hydrogen_bonds
if group1.atom.is_atom_within_bond_distance(group2.atom, min_hbond_dist, 1):
if group1.atom.is_atom_within_bond_distance(
group2.atom, min_hbond_dist, 1
):
return None
# set angle factor
f_angle = 1.0
if group2.type in version.parameters.angular_dependent_sidechain_interactions:
if (
group2.type in
version.parameters.angular_dependent_sidechain_interactions
):
if closest_atom2.element == 'H':
heavy_atom = closest_atom2.bonded_atoms[0]
hydrogen = closest_atom2
@@ -225,7 +241,10 @@ def hydrogen_bond_interaction(group1, group2, version):
# is closer to the titratable atom than the hydrogen. In either
# case, we set the angle factor to 0
f_angle = 0.0
elif group1.type in version.parameters.angular_dependent_sidechain_interactions:
elif (
group1.type in
version.parameters.angular_dependent_sidechain_interactions
):
if closest_atom1.element == 'H':
heavy_atom = closest_atom1.bonded_atoms[0]
hydrogen = closest_atom1
@@ -236,7 +255,9 @@ def hydrogen_bond_interaction(group1, group2, version):
# is closer to the titratable atom than the hydrogen. In either
# case, we set the angle factor to 0
f_angle = 0.0
weight = version.calculate_pair_weight(group1.num_volume, group2.num_volume)
weight = version.calculate_pair_weight(
group1.num_volume, group2.num_volume
)
exception, value = version.check_exceptions(group1, group2)
if exception:
# Do nothing, value should have been assigned.
@@ -256,12 +277,15 @@ def electrostatic_interaction(group1, group2, dist, version):
dist: distance between groups
version: version-specific object with parameters and functions
Returns:
electrostatic interaction energy or None (if no interaction is appropriate)
electrostatic interaction energy or None (if no interaction is
appropriate)
"""
# check if we should do coulomb interaction at all
if not version.check_coulomb_pair(group1, group2, dist):
return None
weight = version.calculate_pair_weight(group1.num_volume, group2.num_volume)
weight = version.calculate_pair_weight(
group1.num_volume, group2.num_volume
)
value = version.calculate_coulomb_energy(dist, weight)
return value
@@ -334,7 +358,9 @@ def backbone_reorganization(_, conformation):
weight = titratable_group.buried
dpka = 0.00
for bbc_group in bbc_groups:
center = [titratable_group.x, titratable_group.y, titratable_group.z]
center = [
titratable_group.x, titratable_group.y, titratable_group.z
]
atom2 = bbc_group.get_interaction_atoms(titratable_group)[0]
dist, f_angle, _ = angle_distance_factors(atom2=atom2,
atom3=bbc_group.atom,
@@ -359,7 +385,8 @@ def check_exceptions(version, group1, group2):
group2: second group for check
Returns:
1. Boolean indicating atypical behavior,
2. value associated with atypical interaction (None if Boolean is False)
2. value associated with atypical interaction (None if Boolean is
False)
"""
res_type1 = group1.type
res_type2 = group2.type
@@ -398,7 +425,8 @@ def check_coo_arg_exception(group_coo, group_arg, version):
version: version object
Returns:
1. Boolean indicating atypical behavior,
2. value associated with atypical interaction (None if Boolean is False)
2. value associated with atypical interaction (None if Boolean is
False)
"""
exception = True
value_tot = 0.00
@@ -409,13 +437,18 @@ def check_coo_arg_exception(group_coo, group_arg, version):
atoms_arg.extend(group_arg.get_interaction_atoms(group_coo))
for _ in ["shortest", "runner-up"]:
# find the closest interaction pair
[closest_coo_atom, dist, closest_arg_atom] = get_smallest_distance(atoms_coo,
atoms_arg)
[dpka_max, cutoff] = version.get_hydrogen_bond_parameters(closest_coo_atom,
closest_arg_atom)
[closest_coo_atom, dist, closest_arg_atom] = get_smallest_distance(
atoms_coo, atoms_arg
)
[dpka_max, cutoff] = version.get_hydrogen_bond_parameters(
closest_coo_atom, closest_arg_atom
)
# calculate and sum up interaction energy
f_angle = 1.00
if group_arg.type in version.parameters.angular_dependent_sidechain_interactions:
if (
group_arg.type in
version.parameters.angular_dependent_sidechain_interactions
):
atom3 = closest_arg_atom.bonded_atoms[0]
dist, f_angle, _ = angle_distance_factors(closest_coo_atom,
closest_arg_atom,
@@ -437,18 +470,23 @@ def check_coo_coo_exception(group1, group2, version):
version: version object
Returns:
1. Boolean indicating atypical behavior,
2. value associated with atypical interaction (None if Boolean is False)
2. value associated with atypical interaction (None if Boolean is
False)
"""
exception = True
interact_groups12 = group1.get_interaction_atoms(group2)
interact_groups21 = group2.get_interaction_atoms(group1)
[closest_atom1, dist, closest_atom2] = get_smallest_distance(interact_groups12,
interact_groups21)
[dpka_max, cutoff] = version.get_hydrogen_bond_parameters(closest_atom1,
closest_atom2)
[closest_atom1, dist, closest_atom2] = get_smallest_distance(
interact_groups12, interact_groups21
)
[dpka_max, cutoff] = version.get_hydrogen_bond_parameters(
closest_atom1, closest_atom2
)
f_angle = 1.00
value = hydrogen_bond_energy(dist, dpka_max, cutoff, f_angle)
weight = calculate_pair_weight(version.parameters, group1.num_volume, group2.num_volume)
weight = calculate_pair_weight(
version.parameters, group1.num_volume, group2.num_volume
)
value = value * (1.0 + weight)
return exception, value
@@ -462,7 +500,8 @@ def check_coo_his_exception(group1, group2, version):
version: version object
Returns:
1. Boolean indicating atypical behavior,
2. value associated with atypical interaction (None if Boolean is False)
2. value associated with atypical interaction (None if Boolean is
False)
"""
exception = False
if check_buried(group1.num_volume, group2.num_volume):
@@ -479,7 +518,8 @@ def check_oco_his_exception(group1, group2, version):
version: version object
Returns:
1. Boolean indicating atypical behavior,
2. value associated with atypical interaction (None if Boolean is False)
2. value associated with atypical interaction (None if Boolean is
False)
"""
exception = False
if check_buried(group1.num_volume, group2.num_volume):
@@ -496,7 +536,8 @@ def check_cys_his_exception(group1, group2, version):
version: version object
Returns:
1. Boolean indicating atypical behavior,
2. value associated with atypical interaction (None if Boolean is False)
2. value associated with atypical interaction (None if Boolean is
False)
"""
exception = False
if check_buried(group1.num_volume, group2.num_volume):
@@ -513,7 +554,8 @@ def check_cys_cys_exception(group1, group2, version):
version: version object
Returns:
1. Boolean indicating atypical behavior,
2. value associated with atypical interaction (None if Boolean is False)
2. value associated with atypical interaction (None if Boolean is
False)
"""
exception = False
if check_buried(group1.num_volume, group2.num_volume):

View File

@@ -9,12 +9,15 @@ Routines and classes for storing groups important to PROPKA calculations.
Removed :func:`initialize_atom_group` as reading PROPKA inputs is no longer
supported.
"""
import logging
import math
import propka.ligand
import propka.protonate
from propka.ligand_pka_values import LigandPkaValues
from propka.determinant import Determinant
from propka.lib import info, warning
_LOGGER = logging.getLogger(__name__)
# Constants that start with "UNK_" are a mystery to me
@@ -25,7 +28,7 @@ EXPECTED_ATOMS_ACID_INTERACTIONS = {
'COO': {'O': 2}, 'HIS': {'H': 2, 'N': 2}, 'CYS': {'S': 1}, 'TYR': {'O': 1},
'LYS': {'N': 1}, 'ARG': {'H': 5, 'N': 3}, 'ROH': {'O': 1},
'AMD': {'H': 2, 'N': 1}, 'TRP': {'H': 1, 'N': 1}, 'N+': {'N': 1},
'C-': {'O': 2}, 'BBN': {'H': 1, 'N': 1,}, 'BBC': {'O': 1},
'C-': {'O': 2}, 'BBN': {'H': 1, 'N': 1}, 'BBC': {'O': 1},
'NAR': {'H': 1, 'N': 1}, 'NAM': {'H': 1, 'N': 1}, 'F': {'F': 1},
'Cl': {'Cl': 1}, 'OH': {'H': 1, 'O': 1}, 'OP': {'O': 1}, 'O3': {'O': 1},
'O2': {'O': 1}, 'SH': {'S': 1}, 'CG': {'H': 5, 'N': 3},
@@ -118,9 +121,9 @@ class Group:
other: other group for coupling
"""
# do the coupling
if not other in self.covalently_coupled_groups:
if other not in self.covalently_coupled_groups:
self.covalently_coupled_groups.append(other)
if not self in other.covalently_coupled_groups:
if self not in other.covalently_coupled_groups:
other.covalently_coupled_groups.append(self)
def couple_non_covalently(self, other):
@@ -130,9 +133,9 @@ class Group:
other: other group for coupling
"""
# do the coupling
if not other in self.non_covalently_coupled_groups:
if other not in self.non_covalently_coupled_groups:
self.non_covalently_coupled_groups.append(other)
if not self in other.non_covalently_coupled_groups:
if self not in other.non_covalently_coupled_groups:
other.non_covalently_coupled_groups.append(self)
def get_covalently_coupled_groups(self):
@@ -369,26 +372,26 @@ class Group:
str_ = 'Missing atoms or failed protonation for '
str_ += '{0:s} ({1:s}) -- please check the structure'.format(
self.label, self.type)
warning(str_)
warning('{0:s}'.format(str(self)))
_LOGGER.warning(str_)
_LOGGER.warning('{0:s}'.format(str(self)))
num_acid = sum(
[EXPECTED_ATOMS_ACID_INTERACTIONS[self.type][e]
for e in EXPECTED_ATOMS_ACID_INTERACTIONS[self.type].keys()])
num_base = sum(
[EXPECTED_ATOMS_BASE_INTERACTIONS[self.type][e]
for e in EXPECTED_ATOMS_BASE_INTERACTIONS[self.type].keys()])
warning(
_LOGGER.warning(
'Expected {0:d} interaction atoms for acids, found:'.format(
num_acid))
for i in range(len(self.interaction_atoms_for_acids)):
warning(
_LOGGER.warning(
' {0:s}'.format(
str(self.interaction_atoms_for_acids[i])))
warning(
_LOGGER.warning(
'Expected {0:d} interaction atoms for bases, found:'.format(
num_base))
for i in range(len(self.interaction_atoms_for_bases)):
warning(
_LOGGER.warning(
' {0:s}'.format(
str(self.interaction_atoms_for_bases[i])))
@@ -642,7 +645,9 @@ class HISGroup(Group):
# Find the atoms in the histidine ring
ring_atoms = propka.ligand.is_ring_member(self.atom)
if len(ring_atoms) != 5:
warning('His group does not seem to contain a ring', self)
_LOGGER.warning(
'His group does not seem to contain a ring %s', self
)
# protonate ring
for ring_atom in ring_atoms:
PROTONATOR.protonate_atom(ring_atom)
@@ -652,7 +657,8 @@ class HISGroup(Group):
else:
# Missing side-chain atoms
self.set_center([self.atom])
# TODO - perhaps it would be better to ignore this group completely?
# TODO - perhaps it would be better to ignore this group
# completely?
# find the hydrogens on the ring-nitrogens
hydrogens = []
nitrogens = [ra for ra in ring_atoms if ra.element == 'N']
@@ -785,7 +791,7 @@ class CtermGroup(Group):
the_other_oxygen = the_carbons[0].get_bonded_elements('O')
the_other_oxygen.remove(self.atom)
# set the center and interaction atoms
the_oxygens = [self.atom]+ the_other_oxygen
the_oxygens = [self.atom] + the_other_oxygen
self.set_center(the_oxygens)
self.set_interaction_atoms(the_oxygens, the_oxygens)
@@ -836,7 +842,7 @@ class NARGroup(Group):
Group.__init__(self, atom)
self.type = 'NAR'
self.residue_type = 'NAR'
info('Found NAR group:', atom)
_LOGGER.info('Found NAR group: %s', atom)
def setup_atoms(self):
"""Set up atoms in group."""
@@ -859,7 +865,7 @@ class NAMGroup(Group):
Group.__init__(self, atom)
self.type = 'NAM'
self.residue_type = 'NAM'
info('Found NAM group:', atom)
_LOGGER.info('Found NAM group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -879,7 +885,7 @@ class FGroup(Group):
Group.__init__(self, atom)
self.type = 'F'
self.residue_type = 'F'
info('Found F group:', atom)
_LOGGER.info('Found F group: %s', atom)
class ClGroup(Group):
@@ -889,7 +895,7 @@ class ClGroup(Group):
Group.__init__(self, atom)
self.type = 'Cl'
self.residue_type = 'Cl'
info('Found Cl group:', atom)
_LOGGER.info('Found Cl group: %s', atom)
class OHGroup(Group):
@@ -899,7 +905,7 @@ class OHGroup(Group):
Group.__init__(self, atom)
self.type = 'OH'
self.residue_type = 'OH'
info('Found OH group:', atom)
_LOGGER.info('Found OH group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -919,7 +925,7 @@ class OPGroup(Group):
Group.__init__(self, atom)
self.type = 'OP'
self.residue_type = 'OP'
info('Found OP group:', atom)
_LOGGER.info('Found OP group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -940,7 +946,7 @@ class O3Group(Group):
Group.__init__(self, atom)
self.type = 'O3'
self.residue_type = 'O3'
info('Found O3 group:', atom)
_LOGGER.info('Found O3 group: %s', atom)
class O2Group(Group):
@@ -953,7 +959,7 @@ class O2Group(Group):
Group.__init__(self, atom)
self.type = 'O2'
self.residue_type = 'O2'
info('Found O2 group:', atom)
_LOGGER.info('Found O2 group: %s', atom)
class SHGroup(Group):
@@ -963,7 +969,7 @@ class SHGroup(Group):
Group.__init__(self, atom)
self.type = 'SH'
self.residue_type = 'SH'
info('Found SH group:', atom)
_LOGGER.info('Found SH group: %s', atom)
class CGGroup(Group):
@@ -973,7 +979,7 @@ class CGGroup(Group):
Group.__init__(self, atom)
self.type = 'CG'
self.residue_type = 'CG'
info('Found CG group:', atom)
_LOGGER.info('Found CG group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -996,7 +1002,7 @@ class C2NGroup(Group):
Group.__init__(self, atom)
self.type = 'C2N'
self.residue_type = 'C2N'
info('Found C2N group:', atom)
_LOGGER.info('Found C2N group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -1020,7 +1026,7 @@ class OCOGroup(Group):
Group.__init__(self, atom)
self.type = 'OCO'
self.residue_type = 'OCO'
info('Found OCO group:', atom)
_LOGGER.info('Found OCO group: %s', atom)
def setup_atoms(self):
"""Set up atoms in group."""
@@ -1041,7 +1047,7 @@ class N30Group(Group):
Group.__init__(self, atom)
self.type = 'N30'
self.residue_type = 'N30'
info('Found N30 group:', atom)
_LOGGER.info('Found N30 group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -1063,7 +1069,7 @@ class N31Group(Group):
Group.__init__(self, atom)
self.type = 'N31'
self.residue_type = 'N31'
info('Found N31 group:', atom)
_LOGGER.info('Found N31 group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -1085,7 +1091,7 @@ class N32Group(Group):
Group.__init__(self, atom)
self.type = 'N32'
self.residue_type = 'N32'
info('Found N32 group:', atom)
_LOGGER.info('Found N32 group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -1107,7 +1113,7 @@ class N33Group(Group):
Group.__init__(self, atom)
self.type = 'N33'
self.residue_type = 'N33'
info('Found N33 group:', atom)
_LOGGER.info('Found N33 group: %s', atom)
def setup_atoms(self):
"""Set up atoms in this group."""
@@ -1129,7 +1135,7 @@ class NP1Group(Group):
Group.__init__(self, atom)
self.type = 'NP1'
self.residue_type = 'NP1'
info('Found NP1 group:', atom)
_LOGGER.info('Found NP1 group: %s', atom)
def setup_atoms(self):
"""Set up atoms in group."""
@@ -1151,7 +1157,7 @@ class N1Group(Group):
Group.__init__(self, atom)
self.type = 'N1'
self.residue_type = 'N1'
info('Found N1 group:', atom)
_LOGGER.info('Found N1 group: %s', atom)
class IonGroup(Group):
@@ -1161,7 +1167,7 @@ class IonGroup(Group):
Group.__init__(self, atom)
self.type = 'ION'
self.residue_type = atom.res_name.strip()
info('Found ion group:', atom)
_LOGGER.info('Found ion group: %s', atom)
class NonTitratableLigandGroup(Group):
@@ -1193,8 +1199,10 @@ class TitratableLigandGroup(Group):
# this is not true if we are reading an input file
if atom.marvin_pka:
self.model_pka = atom.marvin_pka
info('Titratable ligand group ',
atom, self.model_pka, self.charge)
_LOGGER.info(
'Titratable ligand group %s %s %s',
atom, self.model_pka, self.charge
)
self.model_pka_set = True
@@ -1243,12 +1251,12 @@ def is_protein_group(parameters, atom):
"""
if atom.type != 'atom':
return None
### Check for termial groups
# Check for termial groups
if atom.terminal == 'N+':
return NtermGroup(atom)
elif atom.terminal == 'C-':
return CtermGroup(atom)
### Backbone
# Backbone
if atom.type == 'atom' and atom.name == 'N':
# ignore proline backbone nitrogens
if atom.res_name != 'PRO':
@@ -1257,7 +1265,7 @@ def is_protein_group(parameters, atom):
# ignore C- carboxyl
if atom.count_bonded_elements('O') == 1:
return BBCGroup(atom)
### Filters for side chains based on PDB protein atom names
# Filters for side chains based on PDB protein atom names
key = '{0:s}-{1:s}'.format(atom.res_name, atom.name)
if key in parameters.protein_group_mapping.keys():
class_str = "{0:s}Group".format(parameters.protein_group_mapping[key])
@@ -1275,7 +1283,7 @@ def is_ligand_group_by_groups(_, atom):
Returns:
group for atom or None
"""
### Ligand group filters
# Ligand group filters
if atom.type != 'hetatm':
return None
PROTONATOR.protonate_atom(atom)
@@ -1297,7 +1305,8 @@ def is_ligand_group_by_groups(_, atom):
if atom.sybyl_type == 'N.1':
return N1Group(atom)
if atom.sybyl_type == 'N.pl3':
# make sure that this atom is not part of a guadinium or amidinium group
# make sure that this atom is not part of a guadinium or amidinium
# group
bonded_carbons = atom.get_bonded_elements('C')
if len(bonded_carbons) == 1:
bonded_nitrogens = bonded_carbons[0].get_bonded_elements('N')

View File

@@ -6,12 +6,14 @@ Calculations related to hydrogen placement.
"""
import math
from propka.lib import info
import logging
from propka.protonate import Protonate
from propka.bonds import BondMaker
from propka.atom import Atom
_LOGGER = logging.getLogger(__name__)
def setup_bonding_and_protonation(molecular_container):
"""Set up bonding and protonation for a molecule.
@@ -68,7 +70,7 @@ def protonate_30_style(molecular_container):
molecular_container: molecule
"""
for name in molecular_container.conformation_names:
info('Now protonating', name)
_LOGGER.info('Now protonating %s', name)
# split atom into residues
curres = -1000000
residue = []
@@ -78,19 +80,19 @@ def protonate_30_style(molecular_container):
if atom.res_num != curres:
curres = atom.res_num
if len(residue) > 0:
#backbone
# backbone
[o_atom, c_atom] = add_backbone_hydrogen(
residue, o_atom, c_atom)
#arginine
# arginine
if residue[0].res_name == 'ARG':
add_arg_hydrogen(residue)
#histidine
# histidine
if residue[0].res_name == 'HIS':
add_his_hydrogen(residue)
#tryptophan
# tryptophan
if residue[0].res_name == 'TRP':
add_trp_hydrogen(residue)
#amides
# amides
if residue[0].res_name in ['GLN', 'ASN']:
add_amd_hydrogen(residue)
residue = []
@@ -116,7 +118,6 @@ def add_arg_hydrogen(residue):
Returns:
list of hydrogen atoms
"""
#info('Adding arg H',residue)
for atom in residue:
if atom.name == "CD":
cd_atom = atom
@@ -348,5 +349,3 @@ def make_new_h(atom, x, y, z):
atom.bonded_atoms.append(new_h)
atom.conformation_container.add_atom(new_h)
return new_h

View File

@@ -6,8 +6,11 @@ Iterative functions for pKa calculations. These appear to mostly
involve :class:`propka.determinant.Determinant` instances.
"""
import logging
from propka.determinant import Determinant
from propka.lib import info, debug
_LOGGER = logging.getLogger(__name__)
# TODO - these are undocumented constants
@@ -215,9 +218,12 @@ def add_determinants(iterative_interactions, version, _=None):
iteratives.append(new_iterative)
done_group.append(group)
# Initialize iterative scheme
debug(
"\n --- pKa iterations ({0:d} groups, {1:d} interactions) ---".format(
len(iteratives), len(iterative_interactions)))
_LOGGER.debug(
"\n --- pKa iterations ({0:d} groups, {1:d} interactions) "
"---".format(
len(iteratives), len(iterative_interactions)
)
)
converged = False
iteration = 0
# set non-iterative pka values as first step
@@ -267,26 +273,27 @@ def add_determinants(iterative_interactions, version, _=None):
itres.pka_iter.append(itres.pka_new)
if iteration == 10:
info("did not converge in {0:d} iterations".format(iteration))
_LOGGER.info(
"did not converge in {0:d} iterations".format(iteration)
)
break
# printing pKa iterations
# formerly was conditioned on if options.verbosity >= 2 - now unnecessary
str_ = ' '
for index in range(iteration+1):
str_ += "{0:>8d}".format(index)
debug(str_)
_LOGGER.debug(str_)
for itres in iteratives:
str_ = "{0:s} ".format(itres.label)
for pka in itres.pka_iter:
str_ += "{0:>8.2f}".format(pka)
if not itres.converged:
str_ += " *"
debug(str_)
_LOGGER.debug(str_)
# creating real determinants and adding them to group object
for itres in iteratives:
for type_ in ['sidechain', 'backbone', 'coulomb']:
for interaction in itres.determinants[type_]:
#info('done',itres.group.label,interaction[0],interaction[1])
value = interaction[1]
if value > UNK_MIN_VALUE or value < -UNK_MIN_VALUE:
group = interaction[0]

View File

@@ -11,10 +11,7 @@ import argparse
import pkg_resources
_LOGGER = logging.getLogger("propka")
_STDOUT_HANDLER = logging.StreamHandler(sys.stdout)
_STDOUT_HANDLER.setFormatter(logging.Formatter("%(message)s"))
_LOGGER.addHandler(_STDOUT_HANDLER)
_LOGGER = logging.getLogger(__name__)
EXPECTED_ATOM_NUMBERS = {'ALA': 5, 'ARG': 11, 'ASN': 8, 'ASP': 8, 'CYS': 6,
@@ -53,7 +50,7 @@ def protein_precheck(conformations, names):
"{res:s} in conformation {conf:s}".format(
num=len(res_atoms), res=residue_label,
conf=name))
warning(str_)
_LOGGER.warning(str_)
continue
# check number of atoms in residue
if len(res_atoms) != EXPECTED_ATOM_NUMBERS[res_name]:
@@ -61,7 +58,7 @@ def protein_precheck(conformations, names):
"{res:s} in conformation {conf:s}".format(
num=len(res_atoms), res=residue_label,
conf=name))
warning(str_)
_LOGGER.warning(str_)
def resid_from_atom(atom):
@@ -101,7 +98,7 @@ def make_molecule(atom, atoms):
list of atoms
"""
bonded_atoms = [a for a in atoms if atom in a.bonded_atoms]
res_atoms = [atom,]
res_atoms = [atom]
for bond_atom in bonded_atoms:
if bond_atom in atoms:
atoms.remove(bond_atom)
@@ -190,9 +187,9 @@ def build_parser(parser=None):
"""Build an argument parser for PROPKA.
Args:
parser: existing parser. If this is not None, then the PROPKA parser will
be created as a subparser to this existing parser. Otherwise, a
new parser will be created.
parser: existing parser. If this is not None, then the PROPKA parser
will be created as a subparser to this existing parser.
Otherwise, a new parser will be created.
Returns:
ArgumentParser object.
@@ -214,7 +211,10 @@ def build_parser(parser=None):
group.add_argument("input_pdb", help="read data from <filename>")
group.add_argument(
"-f", "--file", action="append", dest="filenames", default=[],
help="read data from <filename>, i.e. <filename> is added to arguments")
help=(
"read data from <filename>, i.e. <filename> is added to arguments"
)
)
group.add_argument(
"-r", "--reference", dest="reference", default="neutral",
help=("setting which reference to use for stability calculations "
@@ -226,8 +226,8 @@ def build_parser(parser=None):
group.add_argument(
"-i", "--titrate_only", dest="titrate_only",
help=('Treat only the specified residues as titratable. Value should '
'be a comma-separated list of "chain:resnum" values; for example: '
'-i "A:10,A:11"'))
'be a comma-separated list of "chain:resnum" values; for '
'example: -i "A:10,A:11"'))
group.add_argument(
"-t", "--thermophile", action="append", dest="thermophiles",
help=("defining a thermophile filename; usually used in "
@@ -270,7 +270,11 @@ def build_parser(parser=None):
"related properties [0.0, 14.0, 0.1]"))
group.add_argument(
"--mutator", dest="mutator",
help="setting approach for mutating <filename> [alignment/scwrl/jackal]")
help=(
"setting approach for mutating <filename> "
"[alignment/scwrl/jackal]"
)
)
group.add_argument(
"--mutator-option", dest="mutator_options", action="append",
help="setting property for mutator [e.g. type=\"side-chain\"]")
@@ -370,47 +374,3 @@ def get_sorted_configurations(configuration_keys):
def configuration_compare(conf):
"""TODO - figure out what this function does."""
return 100*int(conf[1:-2]) + ord(conf[-1])
def _args_to_str(arg_list):
"""Summarize list of arguments in string.
Args:
arg_list: list of arguments
Returns:
string
"""
return " ".join(map(str, arg_list))
def info(*args):
"""Log a message to info.
Level defaults to INFO unless overridden.
Args:
args: argument list
"""
_LOGGER.info(_args_to_str(args))
def debug(*args):
"""Log a message to debug.
Level defaults to DEBUG unless overridden.
Args:
args: argument list
"""
_LOGGER.debug(_args_to_str(args))
def warning(*args):
"""Log a message to warning.
Level defaults to WARNING unless overridden.
Args:
args: argument list
"""
_LOGGER.warning(_args_to_str(args))

View File

@@ -9,11 +9,15 @@ programs are required).
.. _Marvin: https://chemaxon.com/products/marvin
"""
import logging
import os
import subprocess
import sys
from propka.output import write_mol2_for_atoms
from propka.lib import info, warning, split_atoms_into_molecules
from propka.lib import split_atoms_into_molecules
_LOGGER = logging.getLogger(__name__)
class LigandPkaValues:
@@ -29,9 +33,9 @@ class LigandPkaValues:
# attempt to find Marvin executables in the path
self.molconvert = self.find_in_path('molconvert')
self.cxcalc = self.find_in_path('cxcalc')
info('Found Marvin executables:')
info(self.cxcalc)
info(self.molconvert)
_LOGGER.info('Found Marvin executables:')
_LOGGER.info(self.cxcalc)
_LOGGER.info(self.molconvert)
@staticmethod
def find_in_path(program):
@@ -50,7 +54,7 @@ class LigandPkaValues:
if len(locs) == 0:
str_ = "'Error: Could not find {0:s}.".format(program)
str_ += ' Please make sure that it is found in the path.'
info(str_)
_LOGGER.info(str_)
sys.exit(-1)
return locs[0]
@@ -146,7 +150,7 @@ class LigandPkaValues:
"Didn't find a user-modified file '{0:s}' "
"- generating one".format(
filename))
warning(errstr)
_LOGGER.warning(errstr)
write_mol2_for_atoms(atoms, filename)
# Marvin calculate pKa values
fmt = (
@@ -159,25 +163,22 @@ class LigandPkaValues:
[self.cxcalc, filename]+options.split(), stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
if len(errors) > 0:
info('***********************************************************'
'*********************************************')
info('* Warning: Marvin execution failed: '
' *')
info('* {0:<100s} *'.format(errors))
info('* '
' *')
info('* Please edit the ligand mol2 file and re-run PropKa with '
'the -l option: {0:>29s} *'.format(filename))
info('***********************************************************'
'*********************************************')
sys.exit(-1)
err = (
f'Error: Marvin execution failed: {errors}\n'
f'Please edit the ligand mol2 file and re-run PropKa with '
f'the -l option: {filename}'
)
_LOGGER.error(err)
raise OSError(err)
# extract calculated pkas
indices, pkas, types = self.extract_pkas(output)
# store calculated pka values
for i, index in enumerate(indices):
atoms[index].marvin_pka = pkas[i]
atoms[index].charge = {'a': -1, 'b': 1}[types[i]]
info('{0:s} model pKa: {1:<.2f}'.format(atoms[index], pkas[i]))
_LOGGER.info(
'{0:s} model pKa: {1:<.2f}'.format(atoms[index], pkas[i])
)
@staticmethod
def extract_pkas(output):

View File

@@ -4,11 +4,15 @@ PDB molecular container
Molecular container for storing all contents of PDB files.
"""
import logging
import os
import propka.version
from propka.output import write_pka, print_header, print_result
from propka.conformation_container import ConformationContainer
from propka.lib import info, warning, make_grid
from propka.lib import make_grid
_LOGGER = logging.getLogger(__name__)
# TODO - these are constants whose origins are a little murky
@@ -61,13 +65,11 @@ class MolecularContainer:
def find_covalently_coupled_groups(self):
"""Find covalently coupled groups."""
info('-' * 103)
for name in self.conformation_names:
self.conformations[name].find_covalently_coupled_groups()
def find_non_covalently_coupled_groups(self):
"""Find non-covalently coupled groups."""
info('-' * 103)
verbose = self.options.display_coupled_residues
for name in self.conformation_names:
self.conformations[name].find_non_covalently_coupled_groups(
@@ -111,7 +113,7 @@ class MolecularContainer:
'Group {0:s} could not be found in '
'conformation {1:s}.'.format(
group.atom.residue_label, name))
warning(str_)
_LOGGER.warning(str_)
# ... and store the average value
avr_group = avr_group / len(self.conformation_names)
avr_conformation.groups.append(avr_group)

View File

@@ -9,11 +9,14 @@ Output routines.
Removed :func:`write_proka` as writing PROPKA input files is no longer
supported.
"""
import logging
from datetime import date
from propka.lib import info
from . import __version__
_LOGGER = logging.getLogger(__name__)
def open_file_for_writing(input_file):
"""Open file or file-like stream for writing.
@@ -54,7 +57,7 @@ def print_header():
str_ = "{0:s}\n".format(get_propka_header())
str_ += "{0:s}\n".format(get_references_header())
str_ += "{0:s}\n".format(get_warning_header())
info(str_)
_LOGGER.info("\n%s", str_)
def write_pdb_for_protein(
@@ -74,7 +77,7 @@ def write_pdb_for_protein(
filename = "{0:s}.pdb".format(protein.name)
# TODO - this would be better as a context manager
pdbfile = open(filename, 'w')
info("writing pdbfile {0:s}".format(filename))
_LOGGER.info("writing pdbfile {0:s}".format(filename))
close_file = True
else:
# don't close the file, it was opened in a different place
@@ -127,7 +130,7 @@ def write_pka(protein, parameters, filename=None, conformation='1A',
# TODO - this would be much better with a context manager
file_ = open(filename, 'w')
if verbose:
info("Writing {0:s}".format(filename))
_LOGGER.info("Writing {0:s}".format(filename))
# writing propka header
str_ = "{0:s}\n".format(get_propka_header())
str_ += "{0:s}\n".format(get_references_header())
@@ -177,7 +180,7 @@ def print_tm_profile(protein, reference="neutral", window=[0., 14., 1.],
and (ph % window[2] < 0.01
or ph % window[2] > 0.99*window[2])):
str_ += "{0:>6.2f}{1:>10.2f}\n".format(ph, tm_)
info(str_)
_LOGGER.info(str_)
def print_result(protein, conformation, parameters):
@@ -201,9 +204,9 @@ def print_pka_section(protein, conformation, parameters):
"""
# geting the determinants section
str_ = get_determinant_section(protein, conformation, parameters)
info(str_)
_LOGGER.info("pKa determinants:\n%s", str_)
str_ = get_summary_section(protein, conformation, parameters)
info(str_)
_LOGGER.info("pKa summary:\n%s", str_)
def get_determinant_section(protein, conformation, parameters):
@@ -296,8 +299,9 @@ def get_folding_profile_section(
else:
str_ += "The pH of optimum stability is {0:>4.1f}".format(ph_opt)
str_ += (
" for which the free energy is {0:>6.1f} kcal/mol at 298K\n".format(
dg_opt))
" for which the free energy is {0:>6.1f} kcal/mol at "
"298K\n".format(dg_opt)
)
if dg_min is None or dg_max is None:
str_ += "Could not determine pH values where the free energy"
str_ += " is within 80 % of minimum\n"
@@ -337,7 +341,10 @@ def get_charge_profile_section(protein, conformation='AVR', _=None):
if pi_pro is None or pi_mod is None:
str_ += "Could not determine the pI\n\n"
else:
str_ += f"The pI is {pi_pro:>5.2f} (folded) and {pi_mod:>5.2f} (unfolded)\n"
str_ += (
f"The pI is {pi_pro:>5.2f} (folded) and {pi_mod:>5.2f} "
f"(unfolded)\n"
)
return str_

View File

@@ -2,13 +2,18 @@
Configuration file parameters
=============================
Holds parameters and settings that can be set in :file:`propka.cfg`. The file format consists of lines of ``keyword value [value ...]``, blank lines, and comment lines (introduced with ``#``).
Holds parameters and settings that can be set in :file:`propka.cfg`. The file
format consists of lines of ``keyword value [value ...]``, blank lines, and
comment lines (introduced with ``#``).
The module attributes below list the names and types of all key words
in configuration file.
"""
from propka.lib import info, warning
import logging
_LOGGER = logging.getLogger(__name__)
#: matrices
@@ -126,7 +131,7 @@ class Parameters:
"""
dict_ = getattr(self, words[0])
key = words[1]
if not key in dict_:
if key not in dict_:
dict_[key] = []
for value in words[2:]:
if isinstance(value, list):
@@ -204,12 +209,12 @@ class Parameters:
def print_interaction_parameters(self):
"""Print interaction parameters."""
info('--------------- Model pKa values ----------------------')
_LOGGER.info('--------------- Model pKa values ----------------------')
for k in self.model_pkas:
info('{0:>3s} {1:8.2f}'.format(k, self.model_pkas[k]))
_LOGGER.info('{0:>3s} {1:8.2f}'.format(k, self.model_pkas[k]))
info('')
info('--------------- Interactions --------------------------')
_LOGGER.info('')
_LOGGER.info('--------------- Interactions --------------------------')
agroups = [
'COO', 'HIS', 'CYS', 'TYR', 'SER', 'N+', 'LYS', 'AMD', 'ARG',
'TRP', 'ROH', 'CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR',
@@ -234,7 +239,9 @@ class Parameters:
map_interaction = ''
if group2 in map_:
for val in map_[group2]:
fmt = "|{grp1:>3s} {grp2:>3s} {mat:1s} {val1:4} {val2:4}"
fmt = (
"|{grp1:>3s} {grp2:>3s} {mat:1s} {val1:4} {val2:4}"
)
map_interaction += fmt.format(
group1, val, self.interaction_matrix[group1][val],
self.sidechain_cutoffs.get_value(group1, val)[0],
@@ -260,18 +267,18 @@ class Parameters:
group1, group2)[1]
!= 4)):
map_interaction += '? '
info(interaction, map_interaction)
_LOGGER.info("%s %s", interaction, map_interaction)
if group1 == group2:
break
info('-')
info('--------------- Exceptions ----------------------------')
info('COO-HIS', self.COO_HIS_exception)
info('OCO-HIS', self.OCO_HIS_exception)
info('CYS-HIS', self.CYS_HIS_exception)
info('CYS-CYS', self.CYS_CYS_exception)
_LOGGER.info('-')
_LOGGER.info('--------------- Exceptions ----------------------------')
_LOGGER.info('COO-HIS %s', self.COO_HIS_exception)
_LOGGER.info('OCO-HIS %s', self.OCO_HIS_exception)
_LOGGER.info('CYS-HIS %s', self.CYS_HIS_exception)
_LOGGER.info('CYS-CYS %s', self.CYS_CYS_exception)
info('--------------- Mapping -------------------------------')
info("""
_LOGGER.info('--------------- Mapping -------------------------------')
_LOGGER.info("""
Titratable:
CG ARG
C2N ARG
@@ -318,14 +325,16 @@ O2
"\\midrule",
"\\endfirsthead",
"",
"\\multicolumn{{5}}{{l}}{\\emph{{continued from the previous page}}}\\\\",
"\\multicolumn{{5}}{{l}}{\\emph{{continued from the previous "
"page}}}\\\\",
"\\toprule",
"Group1 & Group2 & Interaction & c1 &c2 \\\\",
"\\midrule",
"\\endhead",
"",
"\\midrule",
"\\multicolumn{{5}}{{r}}{\\emph{{continued on the next page}}}\\\\",
"\\multicolumn{{5}}{{r}}{\\emph{{continued on the next "
"page}}}\\\\",
"\\endfoot",
"",
"\\bottomrule",
@@ -350,11 +359,12 @@ O2
if group1 == group2:
break
str_ += ' \\end{{longtable}}\n'
info(str_)
_LOGGER.info(str_)
def print_interactions_latex(self):
"""Print interactions in LaTeX."""
# TODO - are these the same lists as above? Convert to module constants.
# TODO - are these the same lists as above? Convert to module
# constants.
agroups = ['COO', 'HIS', 'CYS', 'TYR', 'SER', 'N+', 'LYS', 'AMD',
'ARG', 'TRP', 'ROH', 'CG', 'C2N', 'N30', 'N31', 'N32',
'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM',
@@ -371,14 +381,16 @@ O2
"\\midrule",
"\\endfirsthead",
"",
"\\multicolumn{{5}}{{l}}{\\emph{{continued from the previous page}}}\\\\",
"\\multicolumn{{5}}{{l}}{\\emph{{continued from the previous "
"page}}}\\\\",
"\\toprule",
"Group1 & Group2 & Interaction & c1 &c2 \\\\",
"\\midrule",
"\\endhead",
"",
"\\midrule",
"\\multicolumn{{5}}{{r}}{\\emph{{continued on the next page}}}\\\\",
"\\multicolumn{{5}}{{r}}{\\emph{{continued on the next "
"page}}}\\\\",
"\\endfoot",
"",
"\\bottomrule",
@@ -388,7 +400,10 @@ O2
str_ = "\n".join(lines)
for group1 in agroups:
for group2 in agroups:
fmt = '{g1:>3s} & {g2:>3s} & {mat:1s} & {val1:>4s} & {val2:>4s}\\\\ \n'
fmt = (
'{g1:>3s} & {g2:>3s} & {mat:1s} & {val1:>4s} & '
'{val2:>4s}\\\\ \n'
)
str_ += fmt.format(
group1, group2, self.interaction_matrix[group1][group2],
str(self.sidechain_cutoffs.get_value(group1, group2)[0]),
@@ -396,7 +411,7 @@ O2
if group1 == group2:
break
str_ += ' \\end{{longtable}}\n'
info(str_)
_LOGGER.info(str_)
class InteractionMatrix:
@@ -421,7 +436,7 @@ class InteractionMatrix:
"""
new_group = words[0]
self.ordered_keys.append(new_group)
if not new_group in self.dictionary.keys():
if new_group not in self.dictionary.keys():
self.dictionary[new_group] = {}
for i, group in enumerate(self.ordered_keys):
if len(words) > i+1:
@@ -524,8 +539,8 @@ class PairwiseMatrix:
str_ = (
'Parameter value for {0:s}, {1:s} defined more '
'than once'.format(key1, key2))
warning(str_)
if not key1 in self.dictionary:
_LOGGER.warning(str_)
if key1 not in self.dictionary:
self.dictionary[key1] = {}
self.dictionary[key1][key2] = value

View File

@@ -7,11 +7,14 @@ The :class:`Protonate` processes a
protons.
"""
import logging
import math
import propka.bonds
import propka.atom
from propka.vector_algebra import rotate_vector_around_an_axis, Vector
from propka.lib import warning, debug
_LOGGER = logging.getLogger(__name__)
class Protonate:
@@ -49,7 +52,7 @@ class Protonate:
Args:
molecules: molecular containers
"""
debug('----- Protonation started -----')
_LOGGER.debug('----- Protonation started -----')
# Remove all currently present hydrogen atoms
self.remove_all_hydrogen_atoms(molecules)
# protonate all atoms
@@ -81,11 +84,11 @@ class Protonate:
if atom.type == 'atom':
key = '{0:3s}-{1:s}'.format(atom.res_name, atom.name)
if atom.terminal:
debug(atom.terminal)
_LOGGER.debug("%s", atom.terminal)
key = atom.terminal
if key in self.standard_charges:
atom.charge = self.standard_charges[key]
debug('Charge', atom, atom.charge)
_LOGGER.debug('Charge %s %s', atom, atom.charge)
atom.charge_set = True
# atom is a ligand atom
elif atom.type == 'hetatm':
@@ -130,21 +133,25 @@ class Protonate:
Args:
atom: atom for calculation
"""
debug('*'*10)
debug('Setting number of protons to add for', atom)
_LOGGER.debug('*'*10)
_LOGGER.debug('Setting number of protons to add for %s', atom)
atom.number_of_protons_to_add = 8
debug(" 8")
_LOGGER.debug(" 8")
atom.number_of_protons_to_add -= self.valence_electrons[atom.element]
debug('Valence electrons: {0:>4d}'.format(
_LOGGER.debug('Valence electrons: {0:>4d}'.format(
-self.valence_electrons[atom.element]))
atom.number_of_protons_to_add -= len(atom.bonded_atoms)
debug('Number of bonds: {0:>4d}'.format(-len(atom.bonded_atoms)))
_LOGGER.debug(
'Number of bonds: {0:>4d}'.format(-len(atom.bonded_atoms))
)
atom.number_of_protons_to_add -= atom.num_pi_elec_2_3_bonds
debug('Pi electrons: {0:>4d}'.format(-atom.num_pi_elec_2_3_bonds))
_LOGGER.debug(
'Pi electrons: {0:>4d}'.format(-atom.num_pi_elec_2_3_bonds)
)
atom.number_of_protons_to_add += int(atom.charge)
debug('Charge: {0:>4.1f}'.format(atom.charge))
debug('-'*10)
debug(atom.number_of_protons_to_add)
_LOGGER.debug('Charge: {0:>4.1f}'.format(atom.charge))
_LOGGER.debug('-'*10)
_LOGGER.debug(atom.number_of_protons_to_add)
def set_steric_number_and_lone_pairs(self, atom):
"""Set steric number and lone pairs for atom.
@@ -155,39 +162,41 @@ class Protonate:
# If we already did this, there is no reason to do it again
if atom.steric_num_lone_pairs_set:
return
debug('='*10)
debug('Setting steric number and lone pairs for', atom)
_LOGGER.debug('='*10)
_LOGGER.debug('Setting steric number and lone pairs for %s', atom)
atom.steric_number = 0
debug('{0:>65s}: {1:>4d}'.format(
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Valence electrons', self.valence_electrons[atom.element]))
atom.steric_number += self.valence_electrons[atom.element]
debug('{0:>65s}: {1:>4d}'.format(
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Number of bonds', len(atom.bonded_atoms)))
atom.steric_number += len(atom.bonded_atoms)
debug('{0:>65s}: {1:>4d}'.format(
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Number of hydrogen atoms to add', atom.number_of_protons_to_add))
atom.steric_number += atom.number_of_protons_to_add
debug('{0:>65s}: {1:>4d}'.format(
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Number of pi-electrons in double and triple bonds(-)',
atom.num_pi_elec_2_3_bonds))
atom.steric_number -= atom.num_pi_elec_2_3_bonds
debug('{0:>65s}: {1:>4d}'.format(
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Number of pi-electrons in conjugated double and triple bonds(-)',
atom.num_pi_elec_conj_2_3_bonds))
atom.steric_number -= atom.num_pi_elec_conj_2_3_bonds
debug('{0:>65s}: {1:>4d}'.format(
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Number of donated co-ordinated bonds', 0))
atom.steric_number += 0
debug('{0:>65s}: {1:>4.1f}'.format(
_LOGGER.debug('{0:>65s}: {1:>4.1f}'.format(
'Charge(-)', atom.charge))
atom.steric_number -= atom.charge
atom.steric_number = math.floor(atom.steric_number/2.0)
atom.number_of_lone_pairs = (
atom.steric_number-len(atom.bonded_atoms)-atom.number_of_protons_to_add)
debug('-'*70)
debug('{0:>65s}: {1:>4d}'.format(
atom.steric_number - len(atom.bonded_atoms)
- atom.number_of_protons_to_add
)
_LOGGER.debug('-'*70)
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Steric number', atom.steric_number))
debug('{0:>65s}: {1:>4d}'.format(
_LOGGER.debug('{0:>65s}: {1:>4d}'.format(
'Number of lone pairs', atom.number_of_lone_pairs))
atom.steric_num_lone_pairs_set = True
@@ -198,12 +207,14 @@ class Protonate:
atom: atom for calculation
"""
# decide which method to use
debug('PROTONATING', atom)
_LOGGER.debug('PROTONATING %s', atom)
if atom.steric_number in list(self.protonation_methods.keys()):
self.protonation_methods[atom.steric_number](atom)
else:
warning('Do not have a method for protonating', atom,
'(steric number: {0:d})'.format(atom.steric_number))
_LOGGER.warning(
'Do not have a method for protonating %s %s', atom,
'(steric number: {0:d})'.format(atom.steric_number)
)
def trigonal(self, atom):
"""Add hydrogens in trigonal geometry.
@@ -211,7 +222,9 @@ class Protonate:
Args:
atom: atom to protonate
"""
debug('TRIGONAL - {0:d} bonded atoms'.format(len(atom.bonded_atoms)))
_LOGGER.debug(
'TRIGONAL - {0:d} bonded atoms'.format(len(atom.bonded_atoms))
)
rot_angle = math.radians(120.0)
cvec = Vector(atom1=atom)
# 0 bonds
@@ -269,7 +282,7 @@ class Protonate:
Args:
atom: atom to protonate.
"""
debug(
_LOGGER.debug(
'TETRAHEDRAL - {0:d} bonded atoms'.format(len(atom.bonded_atoms)))
# TODO - might be good to move tetrahedral angle to constant
rot_angle = math.radians(109.5)
@@ -321,7 +334,8 @@ class Protonate:
chain_id=atom.chain_id,
res_num=atom.res_num,
x=round(position.x, 3), # round of to three decimal points to
# avoid round-off differences in input file
# avoid round-off differences in input
# file
y=round(position.y, 3),
z=round(position.z, 3),
occ=None,
@@ -350,7 +364,7 @@ class Protonate:
proton.residue_label = "{0:<3s}{1:>4d}{2:>2s}".format(
proton.name, proton.res_num, proton.chain_id)
i += 1
debug('added', new_h, 'to', atom)
_LOGGER.debug('added %s %s %s', new_h, 'to', atom)
def set_bond_distance(self, bvec, element):
"""Set bond distance between atom and element.
@@ -368,6 +382,6 @@ class Protonate:
str_ = (
'Bond length for {0:s} not found, using the standard value '
'of {1:f}'.format(element, dist))
warning(str_)
_LOGGER.warning(str_)
bvec = bvec.rescale(dist)
return bvec

View File

@@ -11,13 +11,14 @@ function. If similar functionality is desired from a Python script
"""
import logging
import sys
from propka.lib import loadOptions
from propka.input import read_parameter_file, read_molecule_file
from propka.parameters import Parameters
from propka.molecular_container import MolecularContainer
_LOGGER = logging.getLogger("PROPKA")
_LOGGER = logging.getLogger(__name__)
def main(optargs=None):
@@ -28,6 +29,10 @@ def main(optargs=None):
Removed ability to write out PROPKA input files.
"""
# loading options, flags and arguments
logger = logging.getLogger("")
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(stdout_handler)
optargs = optargs if optargs is not None else []
options = loadOptions(*optargs)
pdbfiles = options.filenames

View File

@@ -4,8 +4,12 @@ Vector calculations
Vector algebra for PROPKA.
"""
import logging
import math
from propka.lib import info, get_sorted_configurations
from propka.lib import get_sorted_configurations
_LOGGER = logging.getLogger(__name__)
class Vector:
@@ -63,7 +67,7 @@ class Vector:
elif type(other) in [int, float]:
return Vector(self.x * other, self.y * other, self.z * other)
else:
info('{0:s} not supported'.format(type(other)))
_LOGGER.info('{0:s} not supported'.format(type(other)))
raise TypeError
def __rmul__(self, other):

View File

@@ -6,7 +6,7 @@ Contains version-specific methods and parameters.
TODO - this module unnecessarily confuses the code. Can we eliminate it?
"""
from propka.lib import info
import logging
from propka.hydrogens import setup_bonding_and_protonation, setup_bonding
from propka.hydrogens import setup_bonding_and_protonation_30_style
from propka.energy import radial_volume_desolvation, calculate_pair_weight
@@ -16,6 +16,9 @@ from propka.energy import coulomb_energy, check_exceptions
from propka.energy import backbone_reorganization
_LOGGER = logging.getLogger(__name__)
class Version:
"""Store version-specific methods and parameters."""
def __init__(self, parameters):
@@ -140,12 +143,18 @@ class VersionA(Version):
[v, [c1, c3]] TODO - figure out what this is
"""
if backbone_atom.group_type == 'BBC':
if atom.group_type in self.parameters.backbone_CO_hydrogen_bond.keys():
if (
atom.group_type in
self.parameters.backbone_CO_hydrogen_bond.keys()
):
[v, c1, c2] = self.parameters.backbone_CO_hydrogen_bond[
atom.group_type]
return [v, [c1, c2]]
if backbone_atom.group_type == 'BBN':
if atom.group_type in self.parameters.backbone_NH_hydrogen_bond.keys():
if (
atom.group_type in
self.parameters.backbone_NH_hydrogen_bond.keys()
):
[v, c1, c2] = self.parameters.backbone_NH_hydrogen_bond[
atom.group_type]
return [v, [c1, c2]]
@@ -159,7 +168,7 @@ class SimpleHB(VersionA):
"""Initialize object with parameters."""
# set the calculation rutines used in this version
super().__init__(parameters)
info('Using simple hb model')
_LOGGER.info('Using simple hb model')
def get_hydrogen_bond_parameters(self, atom1, atom2):
"""Get hydrogen bond parameters for two atoms.
@@ -193,7 +202,7 @@ class ElementBasedLigandInteractions(VersionA):
"""Initialize object with parameters."""
# set the calculation rutines used in this version
super().__init__(parameters)
info('Using detailed SC model!')
_LOGGER.info('Using detailed SC model!')
return
def get_hydrogen_bond_parameters(self, atom1, atom2):
@@ -259,8 +268,9 @@ class ElementBasedLigandInteractions(VersionA):
res = self.parameters.hydrogen_bonds.get_value(
elements[0], elements[1])
if not res:
info(
'Could not determine backbone interaction parameters for:',
_LOGGER.info(
'Could not determine backbone interaction parameters '
'for: %s %s',
backbone_atom, atom)
return None
return None