diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 62aed47..6011b8f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -2,6 +2,17 @@ Changelog ********* +Upcoming release (TBD) +====================== + +Changes +------- + +* Removed the following unused command line options: ``-t``, ``--thermophile``, + ``-a``, ``--alignment``, ``-m``, ``--mutation``, ``--mutator``, + ``--mutator-option`` +* Removed several unused API functions + Current ======= diff --git a/docs/source/command.rst b/docs/source/command.rst index 86476b9..f5527d4 100644 --- a/docs/source/command.rst +++ b/docs/source/command.rst @@ -10,9 +10,9 @@ PROPKA predicts the pKa values of ionizable groups in proteins and protein-ligand complexes based in the 3D structure. The :program:`propka3` command has the following options:: - propka3 [-h] [-f FILENAMES] [-r REFERENCE] [-c CHAINS] [-i TITRATE_ONLY] [-t THERMOPHILES] [-a ALIGNMENT] [-m MUTATIONS] + propka3 [-h] [-f FILENAMES] [-r REFERENCE] [-c CHAINS] [-i TITRATE_ONLY] [-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] [--protonate-all] + [-g GRID GRID GRID] [-d] [-l] [-k] [-q] [--protonate-all] input_pdb @@ -47,21 +47,6 @@ protein-ligand complexes based in the 3D structure. The should be a comma-separated list of "chain:resnum" values; for example: ``-i "A:10,A:11"`` (default: None) -.. option:: -t THERMOPHILES, --thermophile THERMOPHILES - - defining a thermophile filename; usually used in - 'alignment-mutations' (default: None) - -.. option:: -a ALIGNMENT, --alignment ALIGNMENT - - alignment file connecting and - [.pir] (default: None) - -.. option:: -m MUTATIONS, --mutation MUTATIONS - - specifying mutation labels which is used to modify - according to, e.g. N25R/N181D (default: None) - .. option:: --version show program's version number and exit @@ -90,16 +75,6 @@ protein-ligand complexes based in the 3D structure. The setting the pH-grid to calculate e.g. stability related properties (default: (0.0, 14.0, 0.1)) -.. option:: --mutator MUTATOR - - setting approach for mutating - [alignment/scwrl/jackal] (default: None) - -.. option:: --mutator-option MUTATOR_OPTIONS - - setting property for mutator [e.g. type="side-chain"] - (default: None) - .. option:: -d, --display-coupled-residues Displays alternative pKa values due to coupling of diff --git a/propka/atom.py b/propka/atom.py index 0cca0e1..e9f01ff 100644 --- a/propka/atom.py +++ b/propka/atom.py @@ -7,8 +7,7 @@ """ import string -from typing import cast, List, NoReturn, Optional, TYPE_CHECKING -import warnings +from typing import List, Optional, TYPE_CHECKING from propka.lib import make_tidy_atom_label from . import hybrid36 @@ -46,7 +45,6 @@ class Atom: group: Optional["Group"] = None group_type: Optional[str] = None cysteine_bridge: bool = False - residue: NoReturn = None # type: ignore[assignment] conformation_container: Optional["ConformationContainer"] = None molecular_container: Optional["MolecularContainer"] = None is_protonated: bool = False @@ -87,7 +85,6 @@ def __init__(self, line: Optional[str] = None): Args: line: Line from a PDB file to set properties of atom. """ - self.number_of_bonded_elements: NoReturn = cast(NoReturn, {}) # FIXME unused? self.bonded_atoms: List[Atom] = [] self.set_properties(line) fmt = "{r.name:3s}{r.res_num:>4d}{r.chain_id:>2s}" @@ -302,45 +299,6 @@ def make_mol2_line(self, id_): atom_label=make_tidy_atom_label(self.name, self.element)) return str_ - def make_pdb_line2(self, numb=None, name=None, res_name=None, chain_id=None, - res_num=None, x=None, y=None, z=None, occ=None, - beta=None): - """Create a PDB line. - - TODO - this could/should be a @property method/attribute - TODO - figure out difference between make_pdb_line, and make_pdb_line2 - - Returns: - String with PDB line. - """ - warnings.warn("only used by unused function") - if numb is None: - numb = self.numb - if name is None: - name = self.name - if res_name is None: - res_name = self.res_name - if chain_id is None: - chain_id = self.chain_id - if res_num is None: - res_num = self.res_num - if x is None: - x = self.x - if y is None: - y = self.y - if z is None: - z = self.z - if occ is None: - occ = self.occ - if beta is None: - beta = self.beta - str_ = PDB_LINE_FMT2.format( - numb=numb, res_name=res_name, chain_id=chain_id, res_num=res_num, - x=x, y=y, z=z, occ=occ, beta=beta, - atom_label=make_tidy_atom_label(name, self.element) - ) - return str_ - def get_tidy_label(self): """Returns a 'tidier' atom label for printing the new pdbfile @@ -353,13 +311,3 @@ def get_tidy_label(self): def __str__(self): """Return an undefined-format string version of this atom.""" return STR_FMT.format(r=self) - - def set_residue(self, residue: NoReturn): - """ Makes a reference to the parent residue - - Args: - residue: the parent residue - """ - raise NotImplementedError("unused") - if self.residue is None: - self.residue = residue diff --git a/propka/bonds.py b/propka/bonds.py index 9bc00c0..70d4b21 100644 --- a/propka/bonds.py +++ b/propka/bonds.py @@ -87,63 +87,6 @@ def __init__(self): self.backbone_atoms = list(self.intra_residue_backbone_bonds.keys()) self.terminal_oxygen_names = ['OXT', 'O\'\''] - def find_bonds_for_protein(self, protein): - """Bonds proteins based on the way atoms normally bond. - - Args: - protein: the protein to search for bonds - """ - raise NotImplementedError("unused") - _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) - _LOGGER.info('++++ Backbones ++++') - # backbone - last_residues = [] - for chain in protein.chains: - for i in range(1, len(chain.residues)): - if (chain.residues[i-1].res_name.replace(' ', '') - not in ['N+', 'C-']): - if (chain.residues[i].res_name.replace(' ', '') - not in ['N+', 'C-']): - self.connect_backbone(chain.residues[i-1], - chain.residues[i]) - last_residues.append(chain.residues[i]) - _LOGGER.info('++++ terminal oxygen ++++') - # terminal OXT - for last_residue in last_residues: - self.find_bonds_for_terminal_oxygen(last_residue) - _LOGGER.info('++++ cysteines ++++') - # Cysteines - for chain in protein.chains: - for i in range(0, len(chain.residues)): - if chain.residues[i].res_name == 'CYS': - for j in range(0, len(chain.residues)): - if chain.residues[j].res_name == 'CYS' and j != i: - self.check_for_cysteine_bonds(chain.residues[i], - chain.residues[j]) - - def check_for_cysteine_bonds(self, cys1, cys2): - """Looks for potential bonds between two cysteines. - - Args: - cys1: one of the cysteines to check - cys1: one of the cysteines to check - """ - raise NotImplementedError("unused") - for atom1 in cys1.atoms: - if atom1.name == 'SG': - for atom2 in cys2.atoms: - if atom2.name == 'SG': - dist = propka.calculations.squared_distance(atom1, - atom2) - # TODO - is SS_dist_squared an attribute of this class? - if dist < self.SS_dist_squared: - self.make_bond(atom1, atom2) - def find_bonds_for_terminal_oxygen(self, residue): """Look for bonds for terminal oxygen. @@ -423,34 +366,3 @@ def make_bond(atom1, atom2): atom2.bonded_atoms.append(atom1) if atom2 not in atom1.bonded_atoms: atom1.bonded_atoms.append(atom2) - - def generate_protein_bond_dictionary(self, atoms): - """Generate dictionary of protein bonds. - - Args: - atoms: list of atoms for bonding - """ - for atom in atoms: - for bonded_atom in atom.bonded_atoms: - resi_i = atom.res_name - name_i = atom.name - resi_j = bonded_atom.res_name - name_j = bonded_atom.name - if name_i not in ( - self.backbone_atoms - or name_j not in self.backbone_atoms): - if name_i not in ( - self.terminal_oxygen_names - 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 name_i not in self.protein_bonds[resi_i]: - 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 resi_j not in list(self.protein_bonds.keys()): - self.protein_bonds[resi_j] = {} - if name_j not in self.protein_bonds[resi_j]: - 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) diff --git a/propka/conformation_container.py b/propka/conformation_container.py index 27601c4..be89b04 100644 --- a/propka/conformation_container.py +++ b/propka/conformation_container.py @@ -6,7 +6,7 @@ """ import logging import functools -from typing import Callable, Dict, Iterable, Iterator, List, NoReturn, Optional, TYPE_CHECKING, Set +from typing import Callable, Dict, Iterable, Iterator, List, Optional, TYPE_CHECKING, Set from propka.lib import Options from propka.version import Version @@ -497,18 +497,6 @@ def get_ions(self): group for group in self.groups if group.residue_type in self.parameters.ions.keys()] - def get_group_names(self, group_list: NoReturn) -> NoReturn: # FIXME unused? - """Get names of groups in list. - - Args: - group_list: list to check - Returns: - list of groups - """ - if TYPE_CHECKING: - assert False - return [group for group in self.groups if group.type in group_list] - def get_ligand_atoms(self) -> List["Atom"]: """Get atoms associated with ligands. diff --git a/propka/group.py b/propka/group.py index 4ff702c..cfad25a 100644 --- a/propka/group.py +++ b/propka/group.py @@ -11,7 +11,7 @@ """ import logging import math -from typing import cast, Dict, Iterable, List, NoReturn, Optional +from typing import Dict, List, Optional import propka.ligand from propka.parameters import Parameters @@ -119,8 +119,6 @@ def __init__(self, atom: Atom): fmt = "{type:<3s}{name:>4s}{chain:>2s}" self.label = fmt.format( type=self.residue_type, name=atom.name, chain=atom.chain_id) - # container for squared distances - self.squared_distances: NoReturn = cast(NoReturn, {}) # FIXME unused? def couple_covalently(self, other: "Group") -> None: """Couple this group with another group. @@ -162,46 +160,6 @@ def get_non_covalently_coupled_groups(self): """ return self.non_covalently_coupled_groups - def share_determinants(self, others: Iterable["Group"]) -> None: - """Share determinants between this group and others. - - Args: - others: list of other groups - """ - raise NotImplementedError("unused") - # for each determinant type - for other in others: - if other == self: - the_other = other - continue - for type_ in ['sidechain', 'backbone', 'coulomb']: - for det in other.determinants[type_]: - self.share_determinant(det, type_) - # recalculate pka values - self.calculate_total_pka() - the_other.calculate_total_pka() - - def share_determinant(self, new_determinant: Determinant, type_: str) -> None: - """Add determinant to this group's list of determinants. - - Args: - new_determinant: determinant to add - type_: type of determinant - """ - added = False - # first check if we already have a determinant with this label - for own_determinant in self.determinants[type_]: - if own_determinant.group == new_determinant.group: - # if so, find the average value - avr = 0.5*(own_determinant.value + new_determinant.value) - own_determinant.value = avr - new_determinant.value = avr - added = True - # otherwise we just add the determinant to our list - if not added: - self.determinants[type_].append( - Determinant(new_determinant.group, new_determinant.value)) - def __eq__(self, other): """Needed for creating sets of groups.""" if self.atom.type == 'atom': diff --git a/propka/iterative.py b/propka/iterative.py index 2ada994..fc32995 100644 --- a/propka/iterative.py +++ b/propka/iterative.py @@ -202,7 +202,7 @@ def add_iterative_ion_pair(object1: "Iterative", object2: "Iterative", object2.determinants['sidechain'].append(interaction) -def add_determinants(iterative_interactions: List[Interaction], version: Version, _=None): +def add_determinants(iterative_interactions: List[Interaction], version: Version): """Add determinants iteratively. The iterative pKa scheme. Later it is all added in 'calculateTotalPKA' diff --git a/propka/lib.py b/propka/lib.py index 17dfc86..e2d6b07 100644 --- a/propka/lib.py +++ b/propka/lib.py @@ -8,10 +8,11 @@ import logging import argparse from pathlib import Path -from typing import Iterable, Iterator, List, TYPE_CHECKING, NoReturn, Optional, Tuple, TypeVar +from typing import Dict, Iterable, Iterator, List, TYPE_CHECKING, NoReturn, Optional, Tuple, TypeVar if TYPE_CHECKING: from propka.atom import Atom + from propka.conformation_container import ConformationContainer T = TypeVar("T") @@ -30,7 +31,6 @@ class Options: # Note: All the "NoReturn" members appear to be unused - alignment: NoReturn # Optional[List[str]] chains: Optional[List[str]] display_coupled_residues: bool = False filenames: List[str] # List[Path]? @@ -38,20 +38,17 @@ class Options: input_pdb: str # Path? keep_protons: bool = False log_level: str = 'INFO' - mutations: NoReturn # Optional[List[str]] - mutator: NoReturn # Optional[str] # alignment/scwrl/jackal - mutator_options: NoReturn # Optional[List[str]] pH: NoReturn # float = 7.0 parameters: Path protonate_all: bool = False reference: NoReturn # str = 'neutral' reuse_ligand_mol2_file: bool = False # only used by unused function - thermophiles: NoReturn # Optional[List[str]] titrate_only: Optional[List[_T_RESIDUE_TUPLE]] window: Tuple[float, float, float] = (0.0, 14.0, 1.0) -def protein_precheck(conformations, names): +def protein_precheck(conformations: Dict[str, "ConformationContainer"], + names: Iterable[str]): """Check protein for correct number of atoms, etc. Args: @@ -60,7 +57,7 @@ def protein_precheck(conformations, names): for name in names: atoms = conformations[name].atoms # Group the atoms by their residue: - atoms_by_residue = {} + atoms_by_residue: Dict[str, List[Atom]] = {} for atom in atoms: if atom.element != 'H': res_id = resid_from_atom(atom) @@ -272,18 +269,6 @@ def build_parser(parser=None): 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"')) - group.add_argument( - "-t", "--thermophile", action="append", dest="thermophiles", - help=("defining a thermophile filename; usually used in " - "'alignment-mutations'")) - group.add_argument( - "-a", "--alignment", action="append", dest="alignment", - help=("alignment file connecting and " - "[.pir]")) - group.add_argument( - "-m", "--mutation", action="append", dest="mutations", - help=("specifying mutation labels which is used to modify " - " according to, e.g. N25R/N181D")) group.add_argument( "--version", action="version", version=f"%(prog)s {propka.__version__}") group.add_argument( @@ -311,16 +296,6 @@ def build_parser(parser=None): default=(0.0, 14.0, 0.1), help=("setting the pH-grid to calculate e.g. stability " "related properties [0.0, 14.0, 0.1]")) - group.add_argument( - "--mutator", dest="mutator", - help=( - "setting approach for mutating " - "[alignment/scwrl/jackal]" - ) - ) - group.add_argument( - "--mutator-option", dest="mutator_options", action="append", - help="setting property for mutator [e.g. type=\"side-chain\"]") group.add_argument( "-d", "--display-coupled-residues", dest="display_coupled_residues", action="store_true", diff --git a/propka/molecular_container.py b/propka/molecular_container.py index 394ae6f..7be3251 100644 --- a/propka/molecular_container.py +++ b/propka/molecular_container.py @@ -195,7 +195,7 @@ def get_folding_profile(self, conformation='AVR', reference="neutral", stability_range = (min(stable_values), max(stable_values)) return profile, opt, range_80pct, stability_range - def get_charge_profile(self, conformation: str = 'AVR', grid=[0., 14., .1]): + def get_charge_profile(self, conformation: str = 'AVR', grid=(0., 14., .1)): """Get charge profile for conformation as function of pH. Args: @@ -212,13 +212,13 @@ def get_charge_profile(self, conformation: str = 'AVR', grid=[0., 14., .1]): charge_profile.append([ph, q_unfolded, q_folded]) return charge_profile - def get_pi(self, conformation: str = 'AVR', grid=[0., 14., 1], *, + def get_pi(self, conformation: str = 'AVR', grid=(0., 14.), *, precision: float = 1e-4) -> Tuple[float, float]: """Get the isoelectric points for folded and unfolded states. Args: conformation: conformation to test - grid: grid of pH values [min, max, step] + grid: pH window [min, max] precision: Compute pI up to this precision Returns: 1. Folded state PI diff --git a/propka/output.py b/propka/output.py index d9d8617..48fdac8 100644 --- a/propka/output.py +++ b/propka/output.py @@ -14,7 +14,7 @@ from decimal import Decimal from os import PathLike from pathlib import Path -from typing import IO, AnyStr, List, NoReturn, Optional, Union, TYPE_CHECKING +from typing import IO, AnyStr, List, Optional, Tuple, Union, TYPE_CHECKING import warnings from .parameters import Parameters @@ -52,20 +52,6 @@ def open_file_for_writing(input_file: _TextIOSource) -> IO[str]: return input_file -def write_file(filename, lines): - """Writes a new file. - - Args: - filename: name of file - lines: lines to write to file - """ - warnings.warn("unused and untested by propka") - file_ = open_file_for_writing(filename) - for line in lines: - file_.write("{0:s}\n".format(line)) - file_.close() - - def print_header(): """Print header section of output.""" str_ = "{0:s}\n".format(get_propka_header()) @@ -74,46 +60,6 @@ def print_header(): _LOGGER.info("\n%s", str_) -def write_pdb_for_protein( - protein, pdbfile=None, filename=None, include_hydrogens=False, _=None): - """Write a residue to the new PDB file. - - Args: - protein: protein object - pdbfile: PDB file - filename: file to write to - include_hydrogens: Boolean indicating whether to include hydrogens - options: options object - """ - raise NotImplementedError("unused") - if pdbfile is None: - # opening file if not given - if filename is None: - filename = "{0:s}.pdb".format(protein.name) - # TODO - this would be better as a context manager - pdbfile = open(filename, 'w') - _LOGGER.info("writing pdbfile {0:s}".format(filename)) - close_file = True - else: - # don't close the file, it was opened in a different place - close_file = False - numb = 0 - for chain in protein.chains: - for residue in chain.residues: - if residue.res_name not in ["N+ ", "C- "]: - for atom in residue.atoms: - if (not include_hydrogens) and atom.name[0] == "H": - # don't print - pass - else: - numb += 1 - line = atom.make_pdb_line2(numb=numb) - line += "\n" - pdbfile.write(line) - if close_file: - pdbfile.close() - - def write_pdb_for_conformation(conformation: "ConformationContainer", filename: _PathArg): """Write PDB conformation to a file. @@ -129,22 +75,19 @@ def write_pdb_for_conformation(conformation: "ConformationContainer", def write_pka(protein: "MolecularContainer", parameters: Parameters, filename: Optional[_PathArg] = None, - conformation='1A', - reference="neutral", _="folding", verbose=False, - __=None): + conformation: str = '1A', + reference: str = "neutral", + *, + verbose: bool = True): """Write the pKa-file based on the given protein. Args: protein: protein object filename: output file name - conformation: TODO - figure this out + conformation: specific conformation reference: reference state - _: "folding" or other verbose: Boolean flag for verbosity - __: options object """ - # TODO - the code immediately overrides the verbose argument; why? - verbose = True if filename is None: filename = "{0:s}.pka".format(protein.name) if verbose: @@ -168,39 +111,6 @@ def write_pka(protein: "MolecularContainer", Path(filename).write_text(str_, encoding="utf-8") -def print_tm_profile(protein: NoReturn, reference="neutral", window=[0., 14., 1.], - __=[0., 0.], tms=None, ref=None, _=False, - options=None): - """Print Tm profile. - - I think Tm refers to the denaturation temperature. - - Args: - protein: protein object - reference: reference state - window: pH window [min, max, step] - __: temperature range [min, max] - tms: TODO - figure this out - ref: TODO - figure this out (probably reference state?) - _: Boolean for verbosity - options: options object - """ - raise NotImplementedError("unused") - profile = protein.getTmProfile( - reference=reference, grid=[0., 14., 0.1], tms=tms, ref=ref, - options=options) - if profile is None: - str_ = "Could not determine Tm-profile\n" - else: - str_ = " suggested Tm-profile for {0:s}\n".format(protein.name) - for (ph, tm_) in profile: - if (ph >= window[0] and ph <= window[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_) - _LOGGER.info(str_) - - def print_result(protein: "MolecularContainer", conformation: str, parameters: Parameters): """Prints all resulting output from determinants and down. @@ -283,8 +193,11 @@ def get_summary_section(protein: "MolecularContainer", conformation: str, def get_folding_profile_section( protein: "MolecularContainer", - conformation='AVR', direction="folding", reference="neutral", - window=[0., 14., 1.0], _=False, __=None): + conformation: str = 'AVR', + direction: str = "folding", + reference: str = "neutral", + window: Tuple[float, float, float] = (0., 14., 1.), +): """Returns string with the folding profile section of the results. Args: @@ -293,8 +206,6 @@ def get_folding_profile_section( direction: 'folding' or other reference: reference state window: pH window [min, max, step] - _: Boolean for verbose output - __: options object Returns: string """ @@ -340,7 +251,8 @@ def get_folding_profile_section( return str_ -def get_charge_profile_section(protein, conformation='AVR', _=None): +def get_charge_profile_section(protein: "MolecularContainer", + conformation: str = 'AVR'): """Returns string with the charge profile section of the results. Args: @@ -371,34 +283,6 @@ def get_charge_profile_section(protein, conformation='AVR', _=None): return str_ -def write_jackal_scap_file(mutation_data=None, filename="1xxx_scap.list", - _=None): - """Write a scap file for, i.e., generating a mutated protein - - TODO - figure out what this is - """ - raise NotImplementedError("unused") - with open(filename, 'w') as file_: - for chain_id, _, res_num, code2 in mutation_data: - str_ = "{chain:s}, {num:d}, {code:s}\n".format( - chain=chain_id, num=res_num, code=code2) - file_.write(str_) - - -def write_scwrl_sequence_file(sequence, filename="x-ray.seq", _=None): - """Write a scwrl sequence file for, e.g., generating a mutated protein - - TODO - figure out what this is - """ - warnings.warn("unused and untested by propka") - with open(filename, 'w') as file_: - start = 0 - while len(sequence[start:]) > 60: - file_.write("{0:s}s\n".format(sequence[start:start+60])) - start += 60 - file_.write("{0:s}\n".format(sequence[start:])) - - def get_propka_header(): """Create the header. diff --git a/propka/parameters.py b/propka/parameters.py index 654b3fa..7f09863 100644 --- a/propka/parameters.py +++ b/propka/parameters.py @@ -12,7 +12,7 @@ """ import logging from dataclasses import dataclass, field -from typing import Dict, List +from typing import Callable, Dict, List, Sequence, Tuple, TypeVar, Union try: # New in version 3.10, deprecated since version 3.12 @@ -39,6 +39,8 @@ def __set__(self, instance, value: float): setattr(instance, self._name_not_squared, value**0.5) +T = TypeVar("T") + _T_MATRIX: TypeAlias = "InteractionMatrix" _T_PAIR_WISE_MATRIX: TypeAlias = "PairwiseMatrix" _T_NUMBER_DICTIONARY = Dict[str, float] @@ -118,13 +120,10 @@ class Parameters: CYS_CYS_exception: float = 3.60 min_ligand_model_pka: float = -10.0 max_ligand_model_pka: float = 20.0 - # include_H_in_interactions: NoReturn = None coupling_max_number_of_bonds: int = 3 min_bond_distance_for_hydrogen_bonds: int = 4 - # coupling_penalty: NoReturn = None shared_determinants: _T_BOOL = False common_charge_centre: _T_BOOL = False - # hide_penalised_group: NoReturn = None remove_penalised_group: _T_BOOL = True max_intrinsic_pka_diff: float = 2.0 min_interaction_energy: float = 0.5 @@ -158,8 +157,10 @@ def parse_line(self, line): self.parse_to_matrix(words) elif typeannotation is _T_STRING_DICTIONARY: self.parse_to_string_dictionary(words) + elif typeannotation is int or typeannotation is _T_BOOL: + self.parse_parameter(words, int) else: - self.parse_parameter(words) + self.parse_parameter(words, float) def parse_to_number_dictionary(self, words): """Parse field to number dictionary. @@ -222,14 +223,14 @@ def parse_to_matrix(self, words): value = tuple(words[1:]) matrix.add(value) - def parse_parameter(self, words): + def parse_parameter(self, words, typefunc: Callable[[str], T]): """Parse field to parameters. Args: words: strings to parse """ assert len(words) == 2, words - value = float(words[1]) + value = typefunc(words[1]) setattr(self, words[0], value) def parse_string(self, words): @@ -451,37 +452,40 @@ def print_interactions_latex(self): class InteractionMatrix: """Interaction matrix class.""" - def __init__(self, name): + def __init__(self, name: str): """Initialize with name of matrix. Args: name: name of interaction matrix """ self.name = name - self.value = None - self.ordered_keys = [] - self.dictionary = {} + self.ordered_keys: List[str] = [] + self.dictionary: Dict[str, Dict[str, Union[str, float]]] = {} - def add(self, words): + def add(self, words: Sequence[str]): """Add values to matrix. Args: words: values to add """ + len_expected = len(self.ordered_keys) + 2 + if len(words) != len_expected: + raise ValueError(f"Expected {len_expected} arguments, got {words!r}") new_group = words[0] self.ordered_keys.append(new_group) 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: + value: Union[str, float] try: - self.value = float(words[i+1]) + value = float(words[i+1]) except ValueError: - self.value = words[i+1] - self.dictionary[group][new_group] = self.value - self.dictionary[new_group][group] = self.value + value = words[i+1] + self.dictionary[group][new_group] = value + self.dictionary[new_group][group] = value - def get_value(self, item1, item2): + def get_value(self, item1: str, item2: str) -> Union[str, float, None]: """Get specific matrix value. Args: @@ -495,7 +499,7 @@ def get_value(self, item1, item2): except KeyError: return None - def __getitem__(self, group): + def __getitem__(self, group: str): """Get specific group from matrix. Args: @@ -531,17 +535,17 @@ def __str__(self): class PairwiseMatrix: """Pairwise interaction matrix class.""" - def __init__(self, name): + def __init__(self, name: str): """Initialize pairwise matrix. Args: name: name of pairwise interaction """ self.name = name - self.dictionary = {} - self.default = [0.0, 0.0] + self.dictionary: Dict[str, Dict[str, Tuple[float, float]]] = {} + self.default = (0.0, 0.0) - def add(self, words): + def add(self, words: Sequence[str]): """Add information to the matrix. TODO - this function unnecessarily bundles arguments into a tuple @@ -551,16 +555,17 @@ def add(self, words): """ # assign the default value if len(words) == 3 and words[0] == 'default': - self.default = [float(words[1]), float(words[2])] + self.default = (float(words[1]), float(words[2])) return # assign non-default values + assert len(words) == 4 group1 = words[0] group2 = words[1] - value = [float(words[2]), float(words[3])] + value = (float(words[2]), float(words[3])) self.insert(group1, group2, value) self.insert(group2, group1, value) - def insert(self, key1, key2, value): + def insert(self, key1: str, key2: str, value: Tuple[float, float]): """Insert value into matrix. Args: @@ -578,7 +583,7 @@ def insert(self, key1, key2, value): self.dictionary[key1] = {} self.dictionary[key1][key2] = value - def get_value(self, item1, item2): + def get_value(self, item1: str, item2: str) -> Tuple[float, float]: """Get specified value from matrix. Args: @@ -592,7 +597,7 @@ def get_value(self, item1, item2): except KeyError: return self.default - def __getitem__(self, group): + def __getitem__(self, group: str): """Get item from matrix corresponding to specific group. Args: diff --git a/propka/version.py b/propka/version.py index ef5cec2..f6008db 100644 --- a/propka/version.py +++ b/propka/version.py @@ -7,6 +7,7 @@ TODO - this module unnecessarily confuses the code. Can we eliminate it? """ import logging +from typing import Sequence, Tuple from propka.atom import Atom from propka.hydrogens import setup_bonding_and_protonation, setup_bonding from propka.hydrogens import setup_bonding_and_protonation_30_style @@ -15,6 +16,7 @@ from propka.energy import electrostatic_interaction, check_coulomb_pair from propka.energy import coulomb_energy, check_exceptions from propka.energy import backbone_reorganization +from propka.parameters import Parameters _LOGGER = logging.getLogger(__name__) @@ -22,7 +24,7 @@ class Version: """Store version-specific methods and parameters.""" - def __init__(self, parameters): + def __init__(self, parameters: Parameters): self.parameters = parameters self.desolvation_model = self.empty_function self.weight_pair_method = self.empty_function @@ -99,7 +101,7 @@ def setup_bonding(self, molecular_container): """Setup bonding using assigned model.""" return self.prepare_bonds(self.parameters, molecular_container) - def get_hydrogen_bond_parameters(self, atom1: Atom, atom2: Atom) -> tuple: + def get_hydrogen_bond_parameters(self, atom1: Atom, atom2: Atom) -> Tuple[float, Sequence[float]]: """Get hydrogen bond parameters for two atoms.""" raise NotImplementedError("abstract method") @@ -136,7 +138,7 @@ def get_hydrogen_bond_parameters(self, atom1, atom2): dpka_max = self.parameters.sidechain_interaction cutoff = self.parameters.sidechain_cutoffs.get_value( atom1.group_type, atom2.group_type) - return [dpka_max, cutoff] + return dpka_max, cutoff def get_backbone_hydrogen_bond_parameters(self, backbone_atom, atom): """Get hydrogen bond parameters between backbone atom and other atom. @@ -311,4 +313,4 @@ def get_hydrogen_bond_parameters(self, atom1, atom2): atom1.group_type, atom2.group_type) cutoff = self.parameters.sidechain_cutoffs.get_value( atom1.group_type, atom2.group_type) - return [dpka_max, cutoff] + return dpka_max, cutoff