From 76475e91a6ec3c3e748c0b6e16a0756a3548124c Mon Sep 17 00:00:00 2001 From: Miha Purg Date: Sat, 24 Jun 2017 14:30:09 +0200 Subject: [PATCH] Minor changes and refactoring. - Added helper function for initializing the logger in Qpyl.common - Added torsion calculations and fixed rmsd in Qpyl.core.qcalc - Added some newly implemented parameter keywords to Qpyl.core.qdyninp - Fatal errors due to 'bad' inputs/keywords in Qpyl.core.qdyninp / Qpyl.qgeninp / q_genrelax.py / q_genfeps.py can now be bypassed with ignore_errors / --ignore_errors --- packages/Qpyl/common.py | 36 +++- packages/Qpyl/core/qcalc.py | 31 ++- packages/Qpyl/core/qdyninp.py | 361 ++++++++++++++++++++-------------- packages/Qpyl/qgeninp.py | 37 ++-- qscripts-cli/q_amber2q.py | 13 +- qscripts-cli/q_analysedyns.py | 10 +- qscripts-cli/q_analysefeps.py | 10 +- qscripts-cli/q_automapper.py | 12 +- qscripts-cli/q_calc.py | 14 +- qscripts-cli/q_ffld2q.py | 12 +- qscripts-cli/q_genfeps.py | 18 +- qscripts-cli/q_genrelax.py | 20 +- qscripts-cli/q_makefep.py | 10 +- qscripts-cli/q_mapper.py | 13 +- qscripts-cli/q_pdbindex.py | 9 +- qscripts-cli/q_plot.py | 2 + qscripts-cli/q_rescale.py | 19 +- qscripts-cli/q_setprot.py | 4 + 18 files changed, 373 insertions(+), 258 deletions(-) diff --git a/packages/Qpyl/common.py b/packages/Qpyl/common.py index 448799f..251e091 100644 --- a/packages/Qpyl/common.py +++ b/packages/Qpyl/common.py @@ -28,6 +28,7 @@ # Some common classes and functions import math +import sys import os import shutil import logging @@ -50,7 +51,40 @@ def format(self, record): return logging.Formatter.format(self, record) -def raise_or_log(message, exception_class, logger, ignore_errors=False): +def init_logger(name, + level=None, + handler=None, + formatter=None): + """Helper function for initializing the logger. + + Args: + name (string): module name, usually root: 'Qpyl' + level (int, optional): logging level (DEBUG, INFO, WARNING...), + default is INFO + handler: (logging.Handler, optional): default is + StreamHandler(sys.stdout) + formatter: (logging.Formatter, optional): default is + SpecialFormatter + + Returns: + lg (logging.Logger) + + """ + lg = logging.getLogger(name) + if level == None: + level = logging.INFO + lg.setLevel(level) + + if handler == None: + handler = logging.StreamHandler(sys.stdout) + if formatter == None: + handler.setFormatter(SpecialFormatter()) + + lg.addHandler(handler) + return lg + + +def raise_or_log(message, exception_class, logger, ignore_errors): """Method used for raising exceptions or writting them to logger instead This way one can bypass certain exceptions like non-integer charge groups, diff --git a/packages/Qpyl/core/qcalc.py b/packages/Qpyl/core/qcalc.py index a3045fe..040253e 100644 --- a/packages/Qpyl/core/qcalc.py +++ b/packages/Qpyl/core/qcalc.py @@ -75,10 +75,7 @@ def run(self, qcalc_input_str, workdir=None): # "\n" is added to fix the blocking qcalc5 issue stdout, stderr = self.process.communicate(qcalc_input_str + "\n") - # not sure if this ever happens, but will keep it anyway - if stderr: - raise QCalcError("QCalc wrote to STDERR: {}".format(stderr)) - + # stderr is useless in qcalc5 return stdout @@ -128,7 +125,22 @@ def add_angle(self, atom1, atom2, atom3): """ self.actions.append((self.CALC_ANGLE, ["{} {} {}".format(atom1, - atom2)])) + atom2, + atom3)])) + def add_torsion(self, atom1, atom2, atom3, atom4): + """Add a torsion/torsion_energy calculation. + + Args: + atom1 (int): index of atom in topology + atom2 (int): index of atom in topology + atom3 (int): index of atom in topology + atom4 (int): index of atom in topology + + """ + self.actions.append((self.CALC_TORSION, + ["{} {} {} {}".format(atom1, atom2, + atom3, atom4)])) + def add_rmsd(self, masks): """Add a RMSD calculation. @@ -139,7 +151,8 @@ def add_rmsd(self, masks): """ if isinstance(masks, basestring): masks = [masks,] - self.actions.append((self.CALC_RMSD, "rmsd.out", masks + ["."])) + # rmsd.out is due to the unfortunate feature added in Q c79ef672400 + self.actions.append((self.CALC_RMSD, masks + [".",] + ["rmsd.out",])) def add_residue_nb_mon(self, resid_first, resid_last, masks): """Add a residue nonbond monitor calculation. @@ -254,6 +267,12 @@ def _parse(self): self.results[calc_i] = DataContainer(["Frame", "angle"]) elif "angle, qangle energy between" in line: self.results[calc_i] = DataContainer(["Frame", "angle"]) + elif "torsion between" in line: + self.results[calc_i] = DataContainer(["Frame", "torsion"]) + elif "torsion, torsion energy between" in line: + self.results[calc_i] = DataContainer(["Frame", "torsion"]) + elif "torsion, qtorsion energy between" in line: + self.results[calc_i] = DataContainer(["Frame", "torsion"]) elif "nonbond monitor for residues" in line: pass else: diff --git a/packages/Qpyl/core/qdyninp.py b/packages/Qpyl/core/qdyninp.py index a78b341..fb420cf 100644 --- a/packages/Qpyl/core/qdyninp.py +++ b/packages/Qpyl/core/qdyninp.py @@ -2,19 +2,19 @@ # -*- coding: utf-8 -*- # # MIT License -# +# # Copyright (c) 2017 Miha Purg -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -28,83 +28,105 @@ import copy +import logging from collections import OrderedDict as ODict -from Qpyl.common import __version__ - -def logical(x): - if str(x).lower() not in [ "on", "off", "1", "0" ]: raise ValueError - -Q_PARAMETERS = ODict( [ ("md", ODict( [ ("steps", int), - ("random_seed", int), - ("temperature", float), - ("stepsize", float), - ("bath_coupling", float), - ("initial_temperature", float), - ("separate_scaling", logical ), - ("lrf", logical ), - ("shake_solvent", logical ), - ("shake_solute", logical ), - ("shake_hydrogens", logical ), - ("shake_heavy", logical ), - ("shake_all_solvent", logical ), - ("shake_all_solute", logical ), - ("shake_all_hydrogens", logical ), - ("shake_all_heavy", logical ), - ("force_rms", logical ) - ])), - ("cut-offs", ODict( [ ("solute_solute", float), - ("solvent_solvent", float), - ("solute_solvent", float), - ("q_atom", float), - ("lrf", float) - ])), - ("sphere", ODict( [ ("centre", str), - ("radius", float), - ("shell_radius", float), - ("shell_force",float), - ("shell_force",float), - ("excluded_force",float), - ("excluded_freeze",logical), - ("exclude_bonded",logical) - ])), - ("pbc", ODict( [ ("pressure_seed", int), - ("rigid_box_centre", logical), - ("constant_pressure", logical), - ("max_volume_displ", float), - ("pressure", float), - ("atom_based_scaling", logical), - ("control_box", str), - ("put_solvent_back_in_box", logical), - ("put_solute_back_in_box", logical) - ])), - ("solvent", ODict( [ ("radius", float), - ("centre", str), - ("pack", float), - ("radial_force", float), - ("polarisation", logical), - ("charge_correction", logical), - ("polarisation_force", float), - ("morse_depth", float), - ("morse_width", float), - ("model", str) +from Qpyl.common import __version__, raise_or_log + +logger = logging.getLogger(__name__) + +def _str_in(x, list_of_values): + if str(x).lower() not in list_of_values: + raise ValueError + +_logical = lambda x: _str_in(x, ["on", "off", "1", "0"]) +_integrator = lambda x: _str_in(x, ["leap-frog", "velocity-verlet"]) +_thermostat = lambda x: _str_in(x, ["berendsen", "langevin", "nose-hoover"]) + +Q_PARAMETERS = ODict( [ ("md", ODict([("steps", int), + ("random_seed", int), + ("temperature", float), + ("stepsize", float), + ("bath_coupling", float), + ("initial_temperature", float), + ("separate_scaling", _logical), + ("lrf", _logical), + ("shake_solvent", _logical), + ("shake_solute", _logical), + ("shake_hydrogens", _logical), + ("shake_heavy", _logical), + ("shake_all_solvent", _logical), + ("shake_all_solute", _logical), + ("shake_all_hydrogens", _logical), + ("shake_all_heavy", _logical), + ("force_rms", _logical), + ("integrator", _integrator), + ("thermostat", _thermostat), + ("langevin_random", _logical), + ("langevin_friction", float), + ("nhchains", int), + ("nose-hoover_mass", int), + ])), + ("cut-offs", ODict([("solute_solute", float), + ("solvent_solvent", float), + ("solute_solvent", float), + ("q_atom", float), + ("lrf", float) ])), - ("intervals", ODict( [ ("non_bond", int), - ("output", int), - ("temperature", int), - ("energy", int), - ("trajectory", int), - ("volume_change", int) - ])), - ("files", ODict( [ ("topology", str), - ("restart", str), - ("final", str), - ("trajectory", str), - ("energy", str), - ("fep", str), - ("restraint", str), - ("water", str) + ("sphere", ODict([("centre", str), + ("radius", float), + ("shell_radius", float), + ("shell_force", float), + ("shell_force", float), + ("excluded_force", float), + ("excluded_freeze", _logical), + ("exclude_bonded", _logical) ])), + ("pbc", ODict([("pressure_seed", int), + ("rigid_box_centre", _logical), + ("constant_pressure", _logical), + ("max_volume_displ", float), + ("pressure", float), + ("atom_based_scaling", _logical), + ("control_box", str), + ("put_solvent_back_in_box", _logical), + ("put_solute_back_in_box", _logical) + ])), + ("solvent", ODict([("radius", float), + ("centre", str), + ("pack", float), + ("radial_force", float), + ("polarisation", _logical), + ("charge_correction", _logical), + ("polarisation_force", float), + ("morse_depth", float), + ("morse_width", float), + ("model", str) + ])), + ("qcp", ODict([("selection", str), + ("qcp_size", str), + ("qcp_kie", _logical), + ("qcp_pdb", str), + ("qcp_write", _logical), + ("qcp_seed", int) + ])), + ("intervals", ODict([("non_bond", int), + ("output", int), + ("temperature", int), + ("energy", int), + ("trajectory", int), + ("volume_change", int) + ])), + ("files", ODict([("topology", str), + ("restart", str), + ("final", str), + ("trajectory", str), + ("traj_input", str), + ("energy", str), + ("fep", str), + ("restraint", str), + ("water", str) + ])), ("lambdas", str), ("group_contribution", list), ("atom_restraints", list), @@ -112,145 +134,164 @@ def logical(x): ("distance_restraints", list), ("angle_restraints", list), ("wall_restraints", list) - ]) + ]) class QDynInputError(Exception): pass class QDynInput(object): - """ - Base class for parsing, modifying and generating QDyn inputs. - The api should look like this: + """Used for parsing, modifying and generating QDyn inputs. + + Args: + input_string (str): string of a qdyn input file + parameters (dict): { "MD": { "steps":10000, + "stepsize":1.00, ... }, + ... } + ignore_errors (boolean, optional): if set, write error messages to + logger.warning instead of raising + QDynInputError + + Usage: try: # load and parse inp = QDynInput( input_file_string ) - # or inp=QDynInput( parameters={ "md": ... } ) - + # or inp=QDynInput( parameters={ "md": ... } ) + # update with another input and save the overridden paramaters - overridden_parms = inp.update( input_file_2_string ) + overridden_parms = inp.update( input_file_2_string ) # update with a dictionary - new_parameters = { "md": { "steps" : 100000 }, + new_parameters = { "md": { "steps" : 100000 }, { "temperature" : 50 } } ) inp.update( new_parameters ) - + # check the input inp.check() - # get the input string + # get the input string new_inp_str = inp.get_string() except QDynInputError as e: print "Problem with input file: " + str(e) - - """ - def __init__(self, input_string="", parameters={}): - """ - Arguments: - input_string (str): string of the qdyn input file - parameters (dict): { "MD": { "steps":10000, "stepsize":1.00, ... }, ... } - """ + """ + def __init__(self, input_string="", parameters={}, ignore_errors=False): + self._ignore_errors = ignore_errors self.parameters = {} - self.update(input_string=input_string, parameters=parameters) def _parse_inp(self, input_string): - """ just extracts the keyword:value pairs into self.parameters, no checking is done at this point """ + # just extracts the keyword:value pairs into self.parameters, + # no checking is done at this point if not input_string: return {} parms = {} qsection = "" for line in input_string.split("\n"): - # remove comments and strip whitespaces. + # remove comments and strip whitespaces. line = line.split("#")[0].strip() line = line.split("!")[0].strip() # empty lines are useless if line == "": - continue + continue # found a qsection if line[0] == "[": qsection = line.strip("[").strip("]").lower() if qsection in parms: - raise QDynInputError("Section '%s' appears more than once" % qsection) + raise QDynInputError("Section '{}' appears more than once" + "".format(qsection)) if "group_contribution" in qsection or "restraints" in qsection: - parms[ qsection ] = [] # make sure the restraints are cleared if an empty section is defined + # make sure the restraints are cleared if + # an empty section is defined + parms[qsection] = [] continue - + if not qsection: raise QDynInputError("Line '%s' not in any qsection" % line) if "group_contribution" in qsection: - parms[ qsection ].append( line ) + parms[qsection].append(line) elif "restraints" in qsection: - rest = " ".join( [ "%-6s" % x for x in line.split() ] ) # prettify it - parms[ qsection ].append( rest ) + # prettify it + rest = " ".join(["%-6s" % x for x in line.split()]) + parms[qsection].append(rest) elif "lambdas" in qsection: - parms[ qsection ] = line + parms[qsection] = line else: c = line.strip().split() key = c[0] try: - value = " ".join( c[1:] ) + value = " ".join(c[1:]) except IndexError: value = None # checking is done later in _check_parms if qsection not in parms.keys(): - parms[ qsection ] = {} - parms[ qsection ][ key ] = value + parms[qsection] = {} + parms[qsection][key] = value return parms def _check_parms(self, parms): - """ - Checks if parameters are supported (typos and such) and if they are of correct type. - """ + # Checks if parameters are supported (typos and such) + # and if they are of correct type. + for qsection, qsec_parms in parms.iteritems(): if qsection not in Q_PARAMETERS: - raise QDynInputError("Unsupported section: '%s'" % qsection) + raise_or_log("Unsupported section: '{}'".format(qsection), + QDynInputError, logger, self._ignore_errors) try: if isinstance(qsec_parms, dict): - for key,value in qsec_parms.iteritems(): + for key, value in qsec_parms.iteritems(): exp_type = Q_PARAMETERS[qsection][key] exp_type(value) except KeyError: - raise QDynInputError("Keyword '%s' in section '%s' unsupported" % (key,qsection)) + raise_or_log("Unknown keyword '{}' in section '{}'" + "".format(key, qsection), + QDynInputError, logger, self._ignore_errors) except ValueError: - raise QDynInputError("Bad value '%s' for parameter '%s' in Q-section '%s'" % (value, key, qsection) ) - + raise_or_log("Bad value '{}' for parameter '{}' in section " + "'{}'".format(value, key, qsection), + QDynInputError, logger, self._ignore_errors) + def _update_dict(self, d1, d2): - """ - Updates values in dictionary d1 with values in dictionary d2 - """ - overridden = {} # contains parameters that were overwritten as tuples (old,new) + # Updates values in dictionary d1 with values in dictionary d2 + + # contains parameters that were overwritten as tuples (old,new) + overridden = {} for section, prms in d2.iteritems(): - if "group_contribution" in section or "restraints" in section or "lambdas" in section: + if "group_contribution" in section \ + or "restraints" in section \ + or "lambdas" in section: if section in d1: overridden[section] = (d1[section], prms) d1[section] = prms else: if section not in d1: d1[section] = {} - for keyword,prm in prms.iteritems(): + for keyword, prm in prms.iteritems(): if keyword in d1[section]: if d1[section][keyword] != prm: - overridden[section + "/" + keyword] = (d1[section][keyword], prm) + tmpk = section + "/" + keyword + overridden[tmpk] = (d1[section][keyword], prm) d1[section][keyword] = prm return overridden def update(self, input_string=None, parameters=None): - """ - Updates the parameters with either (or both) an input string or a parameter dictionary. - Either argument works, parameters overwrites, no argument fails + """Update/modify the parameters. + + Updates the parameters with either (or both) an input string or + a parameter dictionary. Either argument works, parameters + overwrites, no argument fails. """ + parms = self._parse_inp(input_string) if parameters: self._update_dict(parms, copy.deepcopy(parameters)) @@ -264,67 +305,91 @@ def update(self, input_string=None, parameters=None): def check(self): - """ + """Check for missing parameters. + Raises QDynInputError if required parameters are missing. (is not all knowing, please don't rely to much on it) + If 'ignore_errors' is set, it logs warnings instead. """ # check for nonsense or missing mandatory parameters - mdp = self.parameters.get( "md", [] ) - fp = self.parameters.get( "files", [] ) - ip = self.parameters.get( "intervals", [] ) + mdp = self.parameters.get("md", []) + fp = self.parameters.get("files", []) + ip = self.parameters.get("intervals", []) for keyword in ("temperature", "steps", "stepsize"): if keyword not in mdp: - raise QDynInputError("Missing parameter '%s'" % keyword) + raise_or_log("Missing parameter '{}'".format(keyword), + QDynInputError, logger, self._ignore_errors) # fep file and lambdas require each other if ("fep" in fp and "lambdas" not in self.parameters) or \ ("fep" not in fp and "lambdas" in self.parameters): - raise QDynInputError("Parameter 'fep' requires the 'lambdas' section and vice versa") + raise_or_log("Parameter 'fep' requires the 'lambdas' section " + "and vice versa", QDynInputError, + logger, self._ignore_errors) # when generating new velocities, both parms need to be present if ("initial_temperature" in mdp and "random_seed" not in mdp) or \ ("initial_temperature" not in mdp and "random_seed" in mdp): - raise QDynInputError("Parameter 'initial_temperature' requires 'random_seed' and vice versa") + raise_or_log("Parameter 'initial_temperature' requires " + "'random_seed' and vice versa", + QDynInputError, logger, self._ignore_errors) # if a restart file is not defined, we have to generate new velocities if "restart" not in fp and "initial_temperature" not in mdp: - raise QDynInputError("No restart file, please set 'initial_temperature' and 'random_seed' to generate velocities") + raise_or_log("No restart file, please set 'initial_temperature' " + "and 'random_seed' to generate velocities", + QDynInputError, logger, self._ignore_errors) # since energies are important let's not rely on default values in Q... # if an energy file is defined, energy interval must be defined - # (there is no room for libertarian politics in stupidville) if ("energy" not in fp and "energy" in ip) or \ ("energy" in fp and "energy" not in ip): - raise QDynInputError("'energy' must be defined in both 'intervals' and 'files' sections") + raise_or_log("'energy' must be defined in both 'intervals' " + "and 'files' sections", + QDynInputError, logger, self._ignore_errors) - def get_string(self, check=True): - """ - Returns the input as a string. + def get_string(self, check=True, sort=True): + """Returns the input as a string. + Arguments: - check (boolean): if True (default),call self.check() + check (boolean, optional): if True (default), call self.check() + sort (boolean, optional): if True (default), sort the sections and + keywords according to the order in which + they appear in Q_PARAMETERS """ if check: self.check() - # generate the string + qsections = self.parameters.keys() + if sort: + qsections = sorted(qsections, + key=lambda x: (Q_PARAMETERS.keys() + [x]).index(x)) + + # generate the string such that all the sections and keywords s = [] - for qsection, qsec_parms in Q_PARAMETERS.iteritems(): - if not qsection in self.parameters: - continue + for qsection in qsections: s.append("[%s]" % qsection) if "group_contribution" in qsection or "restraints" in qsection: s.extend(self.parameters[qsection]) elif "lambda" in qsection: s.append(self.parameters[qsection]) else: - for key,value in qsec_parms.iteritems(): + keywords = self.parameters[qsection].keys() + + if sort: + qkeys = Q_PARAMETERS[qsection].keys() + keywords = sorted(keywords, + key=lambda x: (qkeys + [x]).index(x)) + + for key in keywords: if key in self.parameters[qsection]: - s.append("%-20s %30s" % (key,self.parameters[qsection][key])) + val = self.parameters[qsection][key] + s.append("{:<20} {:>30}".format(key, val)) s.append("") - return "\n".join(s) + return "\n".join(s) diff --git a/packages/Qpyl/qgeninp.py b/packages/Qpyl/qgeninp.py index 7c2bd3c..1598f0f 100644 --- a/packages/Qpyl/qgeninp.py +++ b/packages/Qpyl/qgeninp.py @@ -44,7 +44,7 @@ from Qpyl.core.qdyninp import QDynInput, QDynInputError from Qpyl.core.qstructure import QStruct, QStructError, find_placeholders -from Qpyl.common import __version__ +from Qpyl.common import __version__, raise_or_log logger = logging.getLogger(__name__) @@ -60,7 +60,7 @@ class QGenfepsError(Exception): def genrelax(relax_proc_file, outdir, restraint, top_file=None, fep_file=None, runscript_file=None, - pdb_file=None, cont_file=None): + pdb_file=None, cont_file=None, ignore_errors=False): """Generates inputs for an MD simulation with Q (qdyn5). @@ -75,6 +75,9 @@ def genrelax(relax_proc_file, outdir, restraint, runscript_file (string): slurm/sge run script pdb_file (string): pdb pathname (used to convert placeholders) cont_file (string): pathname of previous qdyn5 input (continuation) + ignore_errors (boolean): passed to QStruct and QDynInp - write to + logger instead of raising exceptions on + non-critical things (a) Restraint coordinate can be set to: @@ -125,8 +128,7 @@ def genrelax(relax_proc_file, outdir, restraint, logger.info("These placeholders will be replaced with atom indices: {}" "".format(", ".join(c))) try: - # TODO: ignore_errors should not be hardcoded like this - qstruct = QStruct(pdb_file, "pdb", ignore_errors=False) + qstruct = QStruct(pdb_file, "pdb", ignore_errors=ignore_errors) relax_proc_str = qstruct.convert_placeholders(relax_proc_str) except QStructError as err_msg: raise QGenrelaxError("Failed to replace placeholders: " @@ -139,7 +141,8 @@ def genrelax(relax_proc_file, outdir, restraint, "other. Difficult to continue with a " "different topology...") try: - c = QDynInput(open(cont_file, 'r').read()) + c = QDynInput(open(cont_file, 'r').read(), + ignore_errors=ignore_errors) except QDynInputError as err_msg: raise QGenrelaxError("There is something wrong with the given " "input file ({}): {}".format(cont_file, @@ -255,8 +258,7 @@ def genrelax(relax_proc_file, outdir, restraint, elif c: logger.info("Replacing FEP file placeholders...") try: - # TODO: ignore_errors should not be fixed - qstruct = QStruct(pdb_file, "pdb", ignore_errors=False) + qstruct = QStruct(pdb_file, "pdb", ignore_errors=ignore_errors) fep_file_str = qstruct.convert_placeholders(fep_file_str) except QStructError as err_msg: raise QGenfepsError("Failed to replace placeholders: {}" @@ -310,7 +312,7 @@ def genrelax(relax_proc_file, outdir, restraint, try: # parse the general input - inp = QDynInput(gen_inp_s) + inp = QDynInput(gen_inp_s, ignore_errors=ignore_errors) # update the general parameters with step input, printout the # overriden parms, update the files section overridden_prms = inp.update(step_inp_s) @@ -508,7 +510,8 @@ def genrelax(relax_proc_file, outdir, restraint, def genfeps(fep_proc_file, relax_input_file, restraint, energy_list_fn, frames, repeats, fromlambda, prefix, first_frame_eq, - pdb_file=None, fep_file=None, runscript_file=None): + pdb_file=None, fep_file=None, runscript_file=None, + ignore_errors=False): """Generates inputs for a FEP/MD simulation with Q (qdyn5). @@ -527,6 +530,9 @@ def genfeps(fep_proc_file, relax_input_file, restraint, energy_list_fn, pdb_file (string): pdb pathname (used to convert placeholders) fep_file (string): alternate fep file pathname (ignoring input's fep) runscript_file (string): slurm/sge run script + ignore_errors (boolean): passed to QStruct and QDynInp - write to + logger instead of raising exceptions on + non-critical things Returns: rep_dirs (list): list of created replica folders @@ -571,8 +577,7 @@ def genfeps(fep_proc_file, relax_input_file, restraint, energy_list_fn, logger.info("These placeholders will be replaced with atom indices: " + ", ".join(c)) try: - # TODO: ignore_errors should not be fixed - qstruct = QStruct(pdb_file, "pdb", ignore_errors=False) + qstruct = QStruct(pdb_file, "pdb", ignore_errors=ignore_errors) fep_proc_str = qstruct.convert_placeholders(fep_proc_str) except QStructError as err_msg: raise QGenfepsError("Failed to replace placeholders: {}" @@ -592,7 +597,8 @@ def genfeps(fep_proc_file, relax_input_file, restraint, energy_list_fn, top_file_abs, fep_file_abs, re_file_abs, rest_file = None, None, None, None lambda_initial = None try: - c = QDynInput(open(relax_input_file, 'r').read()) + c = QDynInput(open(relax_input_file, 'r').read(), + ignore_errors=ignore_errors) except QDynInputError as err_msg: raise QGenfepsError("There is something wrong with the given input " "file ({}): {}".format(relax_input_file, err_msg)) @@ -638,8 +644,7 @@ def genfeps(fep_proc_file, relax_input_file, restraint, energy_list_fn, elif c: logger.info("Replacing FEP file placeholders...") try: - # TODO: ignore_errors should not be fixed - qstruct = QStruct(pdb_file, "pdb", ignore_errors=False) + qstruct = QStruct(pdb_file, "pdb", ignore_errors=ignore_errors) fep_file_str = qstruct.convert_placeholders(fep_file_str) except QStructError as err_msg: raise QGenfepsError("Failed to replace placeholders: {}" @@ -819,7 +824,7 @@ def genfeps(fep_proc_file, relax_input_file, restraint, energy_list_fn, # parse the general input and update with step input and files section try: - inp = QDynInput(gen_inp_s) + inp = QDynInput(gen_inp_s, ignore_errors=ignore_errors) inp.update(eq_step_inp_s) if "energy" in inp.parameters["intervals"]: files["energy"] = "{}{:03d}_{:4.3f}.en".format(PREFIX_EQ, @@ -913,7 +918,7 @@ def genfeps(fep_proc_file, relax_input_file, restraint, energy_list_fn, # update the parameters and check the input try: - inp = QDynInput(gen_inp_s) + inp = QDynInput(gen_inp_s, ignore_errors=ignore_errors) inp.update(fep_inp_s) if "energy" not in inp.parameters["intervals"]: raise QGenfepsError("FEP stage requires the energy printout " diff --git a/qscripts-cli/q_amber2q.py b/qscripts-cli/q_amber2q.py index 3ad8173..e8e6e2c 100755 --- a/qscripts-cli/q_amber2q.py +++ b/qscripts-cli/q_amber2q.py @@ -31,7 +31,6 @@ import sys import os import time -import logging import argparse from Qpyl.core.qlibrary import QLib, QLibError @@ -39,7 +38,9 @@ from Qpyl.core.qpotential import torsion_energy from Qpyl.core.qstructure import QStruct, QStructError from Qpyl.core.qtopology import QTopology, QTopologyError -from Qpyl.common import backup_file, SpecialFormatter +from Qpyl.common import backup_file, init_logger + +logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" This gnarly script converts Amber force-field parameters to Q format. @@ -75,6 +76,8 @@ "instance), or other weird stuff, but PLEASE don't " "ignore the output messages and PLEASE triple check " "your outputs.") +optarg.add_argument("-h", "--help", action="help", help="show this help " + " message and exit") if len(sys.argv) == 1: parser.print_help() @@ -99,12 +102,6 @@ sys.exit(1) -logger = logging.getLogger('Qpyl') -logger.setLevel(logging.INFO) -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(SpecialFormatter()) -logger.addHandler(handler) - # # create QLib, QPrm, QStruct and QTopology objects diff --git a/qscripts-cli/q_analysedyns.py b/qscripts-cli/q_analysedyns.py index f764213..d1d782f 100755 --- a/qscripts-cli/q_analysedyns.py +++ b/qscripts-cli/q_analysedyns.py @@ -37,14 +37,10 @@ import logging from Qpyl.qanalysis import QAnalyseDyns, QAnalyseDynError -from Qpyl.common import backup_file, SpecialFormatter from Qpyl import plotdata +from Qpyl.common import backup_file, init_logger -logger = logging.getLogger('Qpyl') -logger.setLevel(logging.INFO) -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(SpecialFormatter()) -logger.addHandler(handler) +logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Script for extracting temperatures and energies from QDyn outputs. @@ -72,6 +68,8 @@ optarg.add_argument("--skip", dest="skip", type=int, default=0, help="Skip percentage of data points in each log. " "Default=0") +optarg.add_argument("-h", "--help", action="help", help="show this help " + " message and exit") if len(sys.argv) == 1: parser.print_help() diff --git a/qscripts-cli/q_analysefeps.py b/qscripts-cli/q_analysefeps.py index c3863c1..31814a9 100755 --- a/qscripts-cli/q_analysefeps.py +++ b/qscripts-cli/q_analysefeps.py @@ -34,17 +34,13 @@ import argparse import logging -from Qpyl.common import backup_file, SpecialFormatter from Qpyl.qanalysis import QAnalyseFeps from Qpyl import plotdata +from Qpyl.common import backup_file, init_logger def main(): - logger = logging.getLogger('Qpyl') - logger.setLevel(logging.INFO) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(SpecialFormatter()) - logger.addHandler(handler) + logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Tool for analysing QFep outputs - extracting FEP results, activation and reaction free @@ -93,6 +89,8 @@ def main(): "Default={}".format(QScfg.get("files", \ "analysefeps_subcalc_dir")), default=QScfg.get("files", "analysefeps_subcalc_dir")) + optarg.add_argument("-h", "--help", action="help", help="show this help " + " message and exit") if len(sys.argv) == 1: diff --git a/qscripts-cli/q_automapper.py b/qscripts-cli/q_automapper.py index fa48261..0772228 100755 --- a/qscripts-cli/q_automapper.py +++ b/qscripts-cli/q_automapper.py @@ -51,17 +51,15 @@ import logging import inspect -from Qpyl.common import backup_file, SpecialFormatter from Qpyl.qanalysis import QAnalyseFeps from Qpyl.qmapping import QMapper, QMapperError +from Qpyl.common import backup_file, init_logger + +logger = init_logger('Qpyl') def main(): - logger = logging.getLogger('Qpyl') - logger.setLevel(logging.INFO) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(SpecialFormatter()) - logger.addHandler(handler) + logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Bored of changing your Hij and alpha manually when calibrating @@ -144,6 +142,8 @@ def main(): default=QScfg.get("qexec", "qfep"), help="qfep5 executable path (default={})." "".format(QScfg.get("qexec", "qfep"))) + optarg.add_argument("-h", "--help", action="help", help="show this help " + " message and exit") if len(sys.argv) == 1: parser.print_help() diff --git a/qscripts-cli/q_calc.py b/qscripts-cli/q_calc.py index 2cb97f9..41ab9ec 100755 --- a/qscripts-cli/q_calc.py +++ b/qscripts-cli/q_calc.py @@ -32,9 +32,11 @@ import logging import argparse -from Qpyl.common import backup_file, SpecialFormatter from Qpyl.qgroupcontrib import QGroupContrib, QGroupContribError from Qpyl import plotdata +from Qpyl.common import backup_file, init_logger + +logger = init_logger('Qpyl') def gc(args): @@ -159,11 +161,7 @@ def gc(args): def main(): - logger = logging.getLogger('Qpyl') - logger.setLevel(logging.INFO) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(SpecialFormatter()) - logger.addHandler(handler) + logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" A friendly command-line interface for calculating distances, angles, rmsds, @@ -244,11 +242,13 @@ def main(): help="qcalc5 executable path (default={})." "".format(QScfg.get("qexec", "qcalc"))) + gc_optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") if len(sys.argv) == 1: parser.print_help() sys.exit(1) - elif len(sys.argv) == 2: + elif len(sys.argv) == 2 and sys.argv[1] not in ['-h', '--help']: try: subps[sys.argv[1]].print_help() except KeyError: diff --git a/qscripts-cli/q_ffld2q.py b/qscripts-cli/q_ffld2q.py index 4323c9d..5313434 100755 --- a/qscripts-cli/q_ffld2q.py +++ b/qscripts-cli/q_ffld2q.py @@ -50,7 +50,9 @@ from Qpyl.core.qpotential import torsion_energy from Qpyl.core.qstructure import QStruct, QStructError from Qpyl.core.qtopology import QTopology, QTopologyError -from Qpyl.common import backup_file, SpecialFormatter +from Qpyl.common import backup_file, init_logger + +logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Command-line tool for converting OPLS-AA force-field parameters from FFLD @@ -77,6 +79,8 @@ "instance), or other weird stuff, but PLEASE don't " "ignore the output message and triple check your " "outputs.") +optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") if len(sys.argv) == 1: parser.print_help() @@ -89,12 +93,6 @@ print "FATAL! File '{}' doesn't exist.".format(v) sys.exit(1) -logger = logging.getLogger('Qpyl') -logger.setLevel(logging.INFO) -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(SpecialFormatter()) -logger.addHandler(handler) - # # create QLib, QPrm, QStruct and QTopology objects diff --git a/qscripts-cli/q_genfeps.py b/qscripts-cli/q_genfeps.py index 6623875..4a7f06a 100755 --- a/qscripts-cli/q_genfeps.py +++ b/qscripts-cli/q_genfeps.py @@ -34,9 +34,10 @@ import argparse import logging -from Qpyl.common import SpecialFormatter from Qpyl.qgeninp import genfeps, QGenfepsError +from Qpyl.common import init_logger +logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Script for generating FEP inputs from the 'procedure' file (example can be @@ -108,6 +109,12 @@ help="If set, the first FEP frame will be replaced " "by the last equilibration step (CADEE stuff).") +optarg.add_argument("--ignore_errors", action="store_true", default=False, + help="Keyword/parameter checks will no longer be fatal." + "Use with care.") + +optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") if len(sys.argv) == 1: parser.print_help() @@ -115,12 +122,6 @@ args = parser.parse_args() -logger = logging.getLogger('Qpyl') -logger.setLevel(logging.INFO) -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(SpecialFormatter()) -logger.addHandler(handler) - kwargs = {"fep_proc_file": args.fep_proc, "relax_input_file": args.relax_input, "restraint": args.restraint, @@ -132,7 +133,8 @@ "repeats": args.repeats, "fromlambda": args.fromlambda, "prefix": args.prefix, - "first_frame_eq": args.first_frame_eq} + "first_frame_eq": args.first_frame_eq, + "ignore_errors": args.ignore_errors} try: gen_dirs = genfeps(**kwargs) diff --git a/qscripts-cli/q_genrelax.py b/qscripts-cli/q_genrelax.py index 2ea3a6b..a997352 100755 --- a/qscripts-cli/q_genrelax.py +++ b/qscripts-cli/q_genrelax.py @@ -39,8 +39,10 @@ import argparse import logging -from Qpyl.common import SpecialFormatter from Qpyl.qgeninp import genrelax, QGenrelaxError +from Qpyl.common import init_logger + +logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Script for generating QDyn MD inputs from the 'procedure' file (example can be @@ -84,6 +86,13 @@ "".format(QScfg.get("inputs", "relax_dir")), default=QScfg.get("inputs", "relax_dir")) +optarg.add_argument("--ignore_errors", action="store_true", default=False, + help="Keyword/parameter checks will no longer be fatal." + "Use with care.") + +optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") + if len(sys.argv) == 1: parser.print_help() @@ -91,12 +100,6 @@ args = parser.parse_args() -logger = logging.getLogger('Qpyl') -logger.setLevel(logging.INFO) -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(SpecialFormatter()) -logger.addHandler(handler) - if args.cont == None and args.restraint == None: args.restraint = 'top' elif args.cont != None and args.restraint == None: @@ -110,7 +113,8 @@ "runscript_file": args.runscript, "restraint": args.restraint, "pdb_file": args.pdb, - "outdir": args.outdir} + "outdir": args.outdir, + "ignore_errors": args.ignore_errors} try: gen_inps = genrelax(**kwargs) diff --git a/qscripts-cli/q_makefep.py b/qscripts-cli/q_makefep.py index 10f9981..c2e3f6e 100755 --- a/qscripts-cli/q_makefep.py +++ b/qscripts-cli/q_makefep.py @@ -34,16 +34,12 @@ import argparse import logging -from Qpyl.common import backup_file, SpecialFormatter from Qpyl.qmakefep import make_fep, QMakeFepError +from Qpyl.common import backup_file, init_logger if __name__ == "__main__": - logger = logging.getLogger('Qpyl') - logger.setLevel(logging.INFO) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(SpecialFormatter()) - logger.addHandler(handler) + logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Generates a FEP file for EVB simulations in Q. The changes in atom types, @@ -82,6 +78,8 @@ "(from MCPB.py for instance), or other weird " "stuff, but PLEASE don't ignore the output " "messages and PLEASE triple check your outputs.") + optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") if len(sys.argv) == 1: parser.print_help() diff --git a/qscripts-cli/q_mapper.py b/qscripts-cli/q_mapper.py index acde5f6..f728e38 100755 --- a/qscripts-cli/q_mapper.py +++ b/qscripts-cli/q_mapper.py @@ -31,22 +31,17 @@ import argparse import logging -from Qpyl.common import backup_file, SpecialFormatter from Qpyl.qanalysis import QAnalyseFeps from Qpyl.qmapping import QMapper - +from Qpyl.common import backup_file, init_logger def main(): - logger = logging.getLogger('Qpyl') - logger.setLevel(logging.INFO) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(SpecialFormatter()) - logger.addHandler(handler) + logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Command-line interface for mapping EVB (or just plain old FEP) simulations with QFep. At the moment, supports only single Hij (constant) and alpha. - For FEP, just give it dummy values. + For FEP, use Hij=alpha=0. """, add_help=False) reqarg = parser.add_argument_group("Required") @@ -94,6 +89,8 @@ def main(): default=QScfg.get("qexec", "qfep"), help="qfep5 executable path (default={})." "".format(QScfg.get("qexec", "qfep"))) + optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") if len(sys.argv) == 1: parser.print_help() diff --git a/qscripts-cli/q_pdbindex.py b/qscripts-cli/q_pdbindex.py index 730adc1..c8ad726 100755 --- a/qscripts-cli/q_pdbindex.py +++ b/qscripts-cli/q_pdbindex.py @@ -38,14 +38,9 @@ import logging from Qpyl.core.qstructure import QStruct, QStructError -from Qpyl.common import backup_file, SpecialFormatter - -logger = logging.getLogger('Qpyl') -logger.setLevel(logging.INFO) -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(SpecialFormatter()) -logger.addHandler(handler) +from Qpyl.common import backup_file, init_logger +logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Command-line tool for converting atom placeholders to indexes. The diff --git a/qscripts-cli/q_plot.py b/qscripts-cli/q_plot.py index e2f654d..6b955f4 100755 --- a/qscripts-cli/q_plot.py +++ b/qscripts-cli/q_plot.py @@ -324,6 +324,8 @@ def on_resize(self,event): help="Export plots in Grace format to this directory: " "'{}'. Try without args, to see available plots." "".format(QScfg.get("files", "plot_export_dir"))) + optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") if len(sys.argv) == 1: parser.print_help() diff --git a/qscripts-cli/q_rescale.py b/qscripts-cli/q_rescale.py index 9f6ca51..d042987 100755 --- a/qscripts-cli/q_rescale.py +++ b/qscripts-cli/q_rescale.py @@ -34,7 +34,9 @@ import logging from Qpyl.core.qlibrary import QLib, QLibError -from Qpyl.common import backup_file, SpecialFormatter +from Qpyl.common import backup_file, init_logger + +logger = init_logger('Qpyl') parser = argparse.ArgumentParser(description=""" Script for rescaling charges in OPLS-AA. The given library must have correctly @@ -42,14 +44,17 @@ groups have integer charge. """, add_help=False) reqarg = parser.add_argument_group("Required") -parser.add_argument("lib_file", +reqarg.add_argument("lib_file", help="Q oplsaa library file (single residue only)") -parser.add_argument("-t", dest="threshold", type=float, default=0.3, +optarg = parser.add_argument_group("Optional") +optarg.add_argument("-t", dest="threshold", type=float, default=0.3, help="Threshold of difference between net charge and " "nearest integer charge, above which the script " "will fail. Default=0.3") -parser.add_argument("--ignore_errors", action="store_true", default=False, +optarg.add_argument("--ignore_errors", action="store_true", default=False, help="Use if nothing else works") +optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") if len(sys.argv) == 1: parser.print_help() @@ -61,12 +66,6 @@ print "FATAL! File %s doesn't exist." % args.lib_file sys.exit(1) -logger = logging.getLogger('Qpyl') -logger.setLevel(logging.INFO) -handler = logging.StreamHandler(sys.stdout) -handler.setFormatter(SpecialFormatter()) -logger.addHandler(handler) - # load the library try: diff --git a/qscripts-cli/q_setprot.py b/qscripts-cli/q_setprot.py index e1c87ee..45a099c 100755 --- a/qscripts-cli/q_setprot.py +++ b/qscripts-cli/q_setprot.py @@ -53,6 +53,10 @@ "their neutral form.") reqarg.add_argument("outfn", help="Output filename") +optarg = parser.add_argument_group("Optional") +optarg.add_argument("-h", "--help", action="help", help="show this " + "help message and exit") + if len(sys.argv) == 1: parser.print_help() sys.exit(1)