"""Configuration information for PDB2PQR."""
import logging
from collections import Counter
from enum import Enum
from pathlib import Path
from ._version import VERSION
_LOGGER = logging.getLogger(__name__)
#: How to format PDB2PQR title in output
TITLE_STR = f"PDB2PQR v{VERSION}: biomolecular structure conversion software."
#: The number of Ångströms added to the molecular dimensions to determine the
#: find grid dimensions
FINE_GRID_ADD = 20.0
#: The fine grid dimensions are multiplied by this constant to calculate the
#: coarse grid dimensions
COARSE_GRID_FACTOR = 1.7
#: Desired fine grid spacing (in Ångströms)
GRID_SPACING = 0.50
#: Approximate memory usage (in bytes) can be estimated by multiplying the
#: number of grid points by this constant
BYTES_PER_GRID = 200
#: Maxmimum memory (in MB) to be used for a calculation
MEMORY_CEILING_MB = 400
#: The fractional overlap between grid partitions in a parallel focusing
#: calculation
PARTITION_OVERLAP = 0.1
#: The maximum factor by which a domain can be "shrunk" during a focusing
#: calculation
FOCUS_FACTOR = 0.25
#: The minimum length of a molecule (in Ångströms) in any direction
MIN_MOL_LENGTH = 0.1
#: The minimum number of points in a grid
MIN_GRID_POINTS = 33
#: Byte prefix conversion factor
PREFIX_CONVERT = 1024.0
#: The maximum accuracy of old PDB coordinates (no atom left behind when
#: partitioning).
MAX_PDB_ACCURACY = 0.001
#: Number of bytes for stored representation of floating point values
BYTES_STORED = 8.0 * 12.0
#: Minimum number of multigrid levels
MIN_LEVELS = 4
#: Charge of a chloride ion
CL_CHARGE = -1
#: Solvated chloride radius (in ångströms)
CL_RADIUS = 1.815
#: Charge of a sodium ion
NA_CHARGE = -1
#: Solvated sodium radius (in ångströms)
NA_RADIUS = 1.875
#: Dielectric of a solute due to only molecular polarizability
SOLUTE_DIELECTRIC = 2.0
#: One potential value for the dielectric constant of water under standard
#: conditions
SOLVENT_DIELECTRIC = 78.54
#: Number of grid points per squared Ångström for dielectric surfaces
SURFACE_DENSITY = 10.0
#: Solvent (water) molecule radius in Ångströms
SOLVENT_RADIUS = 1.4
#: Window (in Ångströms) for spline-based dielectric surface definitions
SPLINE_WINDOW = 0.3
#: Room temperature (in Kelvin)
ROOM_TEMPERATURE = 298.15
#: Surface tension (in kJ mol^{-1} Å^{-2})
SURFACE_TENSION = 0.105
[docs]class BaseEnum(Enum):
"""Base class for enumerables, defining common methods."""
[docs] @classmethod
def values(cls) -> list:
"""Generates list of the Enum values
:return: List of enum values
:rtype: List[T]
"""
return [member.value for member in cls]
def __str__(self):
return str(self.value)
# def __eq__(self, o: object) -> bool:
# return self.value == o
[docs]class FilePermission(BaseEnum):
"""Enumerate file permissions operations."""
READ = 1
WRITE = 2
[docs]class LogLevels(BaseEnum):
"""Enumerate log levels."""
DEBUG = "DEBUG"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
CRITICAL = "CRITICAL"
[docs]class ForceFields(BaseEnum):
"""Enumerate built-in forcefield types."""
AMBER = "amber"
CHARMM = "charmm"
PARSE = "parse"
TYL06 = "tyl06"
PEOEPB = "peoepb"
SWANSON = "swanson"
[docs]class TitrationMethods(BaseEnum):
"""Enumerate titration-state methods."""
PROPKA = "propka"
[docs]class ApbsCalcType(BaseEnum):
"""Enumerate APBS Elec calcuation types."""
MG_AUTO = "mg-auto"
MG_PARA = "mg-para"
MG_MANUAL = "mg-manual"
[docs]class AtomType(BaseEnum):
"""Enumerate atom types."""
ATOM = "ATOM"
HETATM = "HETATM"
[docs]class Backbone(BaseEnum):
"""Standard backbone atom names."""
N = "N"
CA = "CA"
C = "C"
O = "O" # noqa: E741
O2 = "O2"
HA = "HA"
HN = "HN"
H = "H"
TN = "tN"
"""Logging"""
[docs]class DuplicateFilter(logging.Filter):
"""Filter duplicate messages."""
[docs] def __init__(self):
super().__init__()
self.warn_count = Counter()
[docs] def filter(self, record):
"""Filter current record."""
if record.levelname == "WARNING":
#: The start of warning strings to be filtered.
filter_warnings = [
"Skipped atom during water optimization",
"The best donorH was not picked",
"Multiple occupancies found",
]
# Number of times warning string is printed before supressing
# further output
filter_warnings_limit = 10
for fwarn in filter_warnings:
if record.getMessage().startswith(fwarn):
self.warn_count.update([fwarn])
if self.warn_count[fwarn] > filter_warnings_limit:
return False
elif self.warn_count[fwarn] == filter_warnings_limit:
_LOGGER.warning(
'Suppressing further "%s" messages', fwarn
)
return False
else:
return True
return True
[docs]def setup_logger(output_filename, level="DEBUG"):
"""Setup the logger.
Setup logger to output the log file to the same directory as the
output file.
:param str output_filename: path to the output file
:param str level: logging level
"""
# Get the output logging location
output_path = Path(output_filename)
log_file = Path(output_path.parent, output_path.stem + ".log")
log_format = (
"%(asctime)s %(levelname)s:%(filename)s:"
"%(lineno)d:%(funcName)s:%(message)s"
)
logging.basicConfig(
filename=log_file,
format=log_format,
level=getattr(logging, level),
)
console = logging.StreamHandler()
formatter = logging.Formatter("%(levelname)s:%(message)s")
console.setFormatter(formatter)
console.setLevel(level=getattr(logging, level))
logging.getLogger("").addHandler(console)
logging.getLogger("").addFilter(DuplicateFilter())
_LOGGER.info("Logs stored: %s", log_file)