Source code for pdb2pqr.inputgen
"""Create an APBS input file using :mod:`psize` data.
.. codeauthor:: Todd Dolinsky
.. codeauthor:: Nathan Baker
"""
import pickle
import logging
import argparse
from pathlib import Path
from . import psize
from .config import TITLE_STR
_LOGGER = logging.getLogger(__name__)
[docs]
class Elec:
"""An object for the ELEC section of an APBS input file."""
[docs]
def __init__(
self, pqrpath, size, method, asyncflag, istrng=0, potdx=False
):
"""Initialize object.
.. todo:: Remove hard-coded parameters.
:param pqrpath: path to PQR file
:type pqrpath: str
:param size: parameter sizing object
:type size: Psize
:param method: solution method (e.g., mg-para, mg-auto, etc.)
:type method: str
:param asyncflag: perform an asynchronous parallel focusing
calculation
:type asyncflag: bool
:param istrng: ionic strength/concentration (M)
:type istring: float
:param potdx: whether to write out potential information in DX format
:type potdx: bool
"""
# If this is an async or parallel calc, we want to use
# the per-grid dime rather than the global dime.
self.dime = size.ngrid
gmem = (
200.0
* self.dime[0]
* self.dime[1]
* self.dime[2]
/ 1024.0
/ 1024.0
)
if method == "": # method not named - use ceiling
if gmem > size.gmemceil:
method = "mg-para"
else:
method = "mg-auto"
if method == "mg-para":
self.dime = size.getSmallest()
self.method = method
self.istrng = istrng
self.glen = size.coarse_length
self.cglen = size.coarse_length
self.fglen = size.fine_length
self.pdime = size.proc_grid
self.label = ""
self.nlev = 4
self.ofrac = 0.1
self.async_ = 0
self.asyncflag = asyncflag
self.cgcent = "mol 1"
self.fgcent = "mol 1"
self.gcent = "mol 1"
self.mol = 1
self.lpbe = 1
self.npbe = 0
self.bcfl = "sdh"
# TODO - where did these very arbitrary numbers come from?
self.ion = [[-1, 1.815], [1, 1.875]] # Multiple ions possible
self.pdie = 2.0
self.sdie = 78.54
self.srfm = "smol"
self.chgm = "spl2"
self.sdens = 10.0
self.srad = 1.4
self.swin = 0.3
self.temp = 298.15
self.gamma = 0.105
self.calcenergy = "total"
self.calcforce = "no"
if potdx:
self.write = [["pot", "dx", pqrpath]]
else:
# Multiple write statements possible
self.write = [["pot", "dx", "pot"]]
def __str__(self):
text = f"elec {self.label}\n"
text += f" {self.method}\n"
text += f" dime {int(self.dime[0])} {int(self.dime[1])} "
text += f"{int(self.dime[2])}\n"
if self.method == "mg-auto":
text += f" cglen {self.cglen[0]:.4f} {self.cglen[1]:.4f} "
text += f"{self.cglen[2]:.4f}\n"
text += f" fglen {self.fglen[0]:.4f} {self.fglen[1]:.4f} "
text += f"{self.fglen[2]:.4f}\n"
text += f" cgcent {self.cgcent}\n"
text += f" fgcent {self.fgcent}\n"
elif self.method == "mg-manual":
text += f" glen {self.glen[0]:.3f} {self.glen[1]:.3f} "
text += f"{self.glen[2]:.3f}\n"
text += f" gcent {self.gcent}\n"
elif self.method == "mg-para":
text += f" pdime {int(self.pdime[0])} {int(self.pdime[1])} "
text += f"{int(self.pdime[2])}\n"
text += f" ofrac {self.ofrac:.1f}\n"
text += f" cglen {self.cglen[0]:.4f} {self.cglen[1]:.4f} "
text += f"{self.cglen[2]:.4f}\n"
text += f" fglen {self.fglen[0]:.4f} {self.fglen[1]:.4f} "
text += f"{self.fglen[2]:.4f}\n"
text += f" cgcent {self.cgcent}\n"
text += f" fgcent {self.fgcent}\n"
if self.asyncflag:
text += f" async {self.async_}\n"
text += f" mol {int(self.mol)}\n"
text += " lpbe\n" if self.lpbe else " npbe\n"
text += f" bcfl {self.bcfl}\n"
if self.istrng > 0:
for ion in self.ion:
text += f" ion charge {ion[0]:.2f} conc {self.istrng:.3f} "
text += f"radius {ion[1]:.4f}\n"
text += f" pdie {self.pdie:.4f}\n"
text += f" sdie {self.sdie:.4f}\n"
text += f" srfm {self.srfm}\n"
text += f" chgm {self.chgm}\n"
text += f" sdens {self.sdens:.2f}\n"
text += f" srad {self.srad:.2f}\n"
text += f" swin {self.swin:.2f}\n"
text += f" temp {self.temp:.2f}\n"
text += f" calcenergy {self.calcenergy}\n"
text += f" calcforce {self.calcforce}\n"
for write in self.write:
text += f" write {write[0]} {write[1]} {write[2]}\n"
text += "end\n"
return text
[docs]
class Input:
"""Each object of this class is one APBS input file."""
[docs]
def __init__(
self, pqrpath, size, method, asyncflag, istrng=0, potdx=False
):
"""Initialize the input file class.
Each input file contains a PQR name, a list of elec objects, and a
list of strings containing print statements.
For starters, assume two ELEC statements are needed, one for the
inhomgenous and the other for the homogenous dielectric calculations.
.. note::
This assumes you have already run psize, either by
:func:`size.run_psize(...)` or :func:`size.parse_string(...)`
followed by :func:`size.set_all()`.
:param pqrpath: path to PQR file
:type pqrpath: str
:param size: parameter sizing object
:type size: Psize
:param method: solution method (e.g., mg-para, mg-auto, etc.)
:type method: str
:param asyncflag: perform an asynchronous parallel focusing
calculation
:type asyncflag: bool
:param istrng: ionic strength/concentration (M)
:type istring: float
:param potdx: whether to write out potential information in DX format
:type potdx: bool
"""
self.pqrpath = Path(pqrpath)
self.pqrname = self.pqrpath.name
self.asyncflag = asyncflag
# Initialize variables to default elec values
elec1 = Elec(pqrpath, size, method, asyncflag, istrng, potdx)
if not potdx:
elec2 = Elec(pqrpath, size, method, asyncflag, istrng, potdx)
setattr(elec2, "sdie", 2.0)
setattr(elec2, "write", [])
else:
elec2 = ""
self.elecs = [elec1, elec2]
if not potdx:
self.prints = ["print elecEnergy 2 - 1 end"]
else:
self.prints = ["print elecEnergy 1 end"]
def __str__(self):
text = "read\n"
text += f" mol pqr {self.pqrname}\n"
text += "end\n"
for elec in self.elecs:
text += str(elec)
for prints in self.prints:
text += prints
text += "\nquit\n"
return text
[docs]
def print_input_files(self, output_path):
"""Generate the input file(s) associated with this object.
:param output_path: location for generated files
:type output_path: str
"""
path = Path(output_path)
base_name = path.stem
if self.asyncflag:
outname = base_name + "-para.in"
# Temporarily disable async flag
for elec in self.elecs:
elec.asyncflag = False
with open(outname, "wt") as out_file:
out_file.write(str(self))
# Now make the async files
elec = self.elecs[0]
nproc = elec.pdime[0] * elec.pdime[1] * elec.pdime[2]
for i in range(int(nproc)):
outname = base_name + f"-PE{i}.in"
for elec in self.elecs:
elec.asyncflag = True
elec.async_ = i
with open(outname, "wt") as out_file:
out_file.write(str(self))
else:
with open(path, "wt") as out_file:
out_file.write(str(self))
[docs]
def dump_pickle(self):
"""Make a Python pickle associated with the APBS input parameters.
.. todo:: is this function still useful?
"""
base_pqr_name = self.pqrpath.stem
outname = base_pqr_name + "-input.p"
with open(outname, "wb") as pfile:
pickle.dump(self, pfile)
[docs]
def split_input(filename):
"""Split the parallel input file into multiple async file names.
:param filename: the path to the original parallel input file
:type filename: str
"""
nproc = 0
with open(filename, "rt") as file_:
text = ""
while True:
line = file_.readline()
if not line:
break
text += line
line = line.strip()
if line.startswith("pdime"): # Get # Procs
words = line.split()
nproc = int(words[1]) * int(words[2]) * int(words[3])
if nproc == 0:
errstr = f"{filename} is not a valid APBS parallel input file!\n"
errstr = errstr + (
"The inputgen script was unable to asynchronize this file!"
)
raise RuntimeError(errstr)
base_pqr_name = Path(filename).stem
for iproc in int(range(nproc)):
outname = base_pqr_name + f"-PE{iproc}.in"
outtext = text.replace("mg-para\n", f"mg-para\n async {iproc}\n")
with open(outname, "w") as outfile:
outfile.write(outtext)
[docs]
def build_parser():
"""Build argument parser."""
desc = f"{TITLE_STR}\ninputgen: generating APBS input files since "
desc += "(at least) 2004"
parse = argparse.ArgumentParser(
description=desc,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parse.add_argument(
"--asynch",
action="store_true",
help="perform an asynchronous parallel calculation.",
)
parse.add_argument(
"--split",
action="store_true",
help=(
"split an existing parallel input file to multiple "
"async input files."
),
)
parse.add_argument(
"--potdx",
action="store_true",
help=("create an input to compute an electrostatic potential map."),
)
parse.add_argument(
"--method",
help=("force output file to write a specific APBS ELEC method."),
choices=["para", "auto", "manual", "async"],
)
parse.add_argument(
"--cfac",
type=float,
default=psize.CFAC,
help=(
"factor by which to expand molecular dimensions to "
"get coarse grid dimensions."
),
)
parse.add_argument(
"--fadd",
type=float,
default=psize.FADD,
help=(
"amount to add to molecular dimensions to get fine "
"grid dimensions."
),
)
parse.add_argument(
"--space",
type=float,
default=psize.SPACE,
help="desired fine mesh resolution",
)
parse.add_argument(
"--gmemfac",
type=int,
default=psize.GMEMFAC,
help=(
"number of bytes per grid point required for sequential "
"MG calculation"
),
)
parse.add_argument(
"--gmemceil",
type=int,
default=psize.GMEMCEIL,
help=(
"max MB allowed for sequential MG calculation; adjust "
"this to force the script to perform faster calculations "
"(which require more parallelism)"
),
)
parse.add_argument(
"--ofrac",
type=float,
default=psize.OFRAC,
help="overlap factor between mesh partitions (parallel)",
)
parse.add_argument(
"--redfac",
type=float,
default=psize.REDFAC,
help=(
"the maximum factor by which a domain dimension can "
"be reduced during focusing"
),
)
parse.add_argument(
"--istrng", help="Ionic strength (M); Na+ and Cl- ions will be used"
)
parse.add_argument("filename")
return parse
[docs]
def main():
"""Main driver"""
parser = build_parser()
args = parser.parse_args()
size = psize.Psize()
filename = args.filename
if args.split:
split_input(filename)
else:
size.run_psize(filename)
input_ = Input(
filename, size, args.method, args.asynch, args.istrng, args.potdx
)
path = Path(filename)
output_path = path.parent + path.stem + Path(".in")
input_.print_input_files(output_path)
if __name__ == "__main__":
main()