"""
FEP+ solubility command line UI
Copyright Schrodinger, LLC. All rights reserved.
"""
import argparse
from typing import List
import sys
from schrodinger.application.desmond import stage
from schrodinger.application.desmond import util
from schrodinger.application.desmond.constants import CUSTOM_CHARGE_MODE
from schrodinger.infra import mm
from schrodinger.structure import StructureReader
from . import cmdline
from .cmdline import Option
_ERROR_TOO_SHORT = 'ERROR: Simulation time must be >= 500 ps.'
_ERROR_MISSING_INPUT = 'ERROR: Must provide <ligands.mae> for running a new job.'
_ERROR_DUPLICATE_TITLE = 'ERROR: Duplicate title for ligand (%s). Please ensure all titles are unique and try again.'
_ERROR_CHARGED_LIGAND = 'ERROR: Charged ligands are not supported in solubility calculations. Please check the %d charged ligand(s): %s.'
_ERROR_ZERO_EXTEND_TIME = "ERROR: -hydration_time or -sublimation_time must be non-zero."
_ERROR_NOT_ENOUGH_MOLECULES = ("ERROR: -dsb_n_molecules can not be less than "
f"{stage.DisorderedSystemBuilder.N_MOLECULES}.")
_ERROR_INCORRECT_COMPOSITION = "ERROR: -solvent-composition must be in the format X:Y:Z where X, Y, Z are integers specifying "
" the number of each solvent molecule matching the order in the input -solvent-template-structure file."
_MIN_ATOMS = 20
_WARNING_TOO_SMALL = 'WARNING: One or more of the input structures is smaller than 20 atoms.\n' + \
'If the simulation exits with an error "The simulation box is too small."\n' + \
'please run with -prepare and modify %s_md.msj to include\n' + \
'disordered_system_builder { \n molecules = 128 \n forcefield = %s \n } \n'
[docs]class FepSolubilityArgs(cmdline.BaseArgs):
[docs] def __init__(self, opt: argparse.Namespace):
"""
:param opt: Command line options with corresponding values.
"""
self.copy_parser_attributes(opt)
self.time = opt.time
self.hydration_fep_sim_time = opt.hydration_time
self.sublimation_fep_sim_time = opt.sublimation_time
self.solvation_fep_sim_time = opt.solvation_time
self.extend = opt.extend
self.h_mass = opt.h_mass
self.custom_charge_mode = opt.custom_charge_mode
self.dsb_n_molecules = opt.dsb_n_molecules
super(FepSolubilityArgs, self).__init__(opt)
[docs] def validate(self):
"""
Validate the parameters for missing files.
:raise SystemExit: For invalid parameters.
"""
from schrodinger.application.scisol.packages.fep import graph
from schrodinger.application.scisol.packages.fep import utils as \
fep_utils
for time in [
self.time, self.hydration_fep_sim_time,
self.sublimation_fep_sim_time
]:
if not (self.DEBUG or self.extend) and time < 500.0:
sys.exit(_ERROR_TOO_SHORT)
# Check the hydration/sublimation simulation times are specified
if self.extend and self.hydration_fep_sim_time == 0 and self.sublimation_fep_sim_time == 0:
sys.exit(_ERROR_ZERO_EXTEND_TIME)
if not (self.inp_file or self.checkpoint):
sys.exit(_ERROR_MISSING_INPUT)
util.ensure_file_exists(self.inp_file)
util.ensure_file_exists(self.extend)
self.check_ppj()
if self.inp_file:
if self.inp_file.endswith("fmp"):
g = graph.Graph.deserialize(self.inp_file)
cts = [node.struc for node in fep_utils.get_ligand_nodes(g)]
else:
cts = list(StructureReader(self.inp_file))
ct_titles = set()
charged_titles = set()
for ct in cts:
if ct.atom_total < _MIN_ATOMS:
jobname = self.JOBNAME if self.JOBNAME else '*'
print(_WARNING_TOO_SMALL % (jobname, self.forcefield))
if ct.formal_charge != 0:
charged_titles.add(ct.title)
if ct.title in ct_titles:
sys.exit(_ERROR_DUPLICATE_TITLE % (ct.title,))
ct_titles.add(ct.title)
if charged_titles:
sys.exit(_ERROR_CHARGED_LIGAND %
(len(charged_titles), ','.join(charged_titles)))
# check the number of molecules for disordered system builder
if self.dsb_n_molecules < stage.DisorderedSystemBuilder.N_MOLECULES:
sys.exit(_ERROR_NOT_ENOUGH_MOLECULES)
if self.solvation_only:
util.ensure_file_exists(self.solvent_template_structure)
if not self.solvent_composition:
sys.exit(_ERROR_INCORRECT_COMPOSITION)
try:
# 1 is for the solute
self.solvent_composition = [
int(v) for v in self.solvent_composition.split(':')
] + [1]
except ValueError:
sys.exit(_ERROR_INCORRECT_COMPOSITION)
super().validate()
[docs] def set_restart(self):
"""
Set the RESTART flag if only the checkpoint is given.
"""
if self.inp_file is None and self.checkpoint and not self.extend:
self.RESTART = True
[docs]def ui(argv: List[str]) -> FepSolubilityArgs:
"""
Parse the arguments and return an object containing the values.
:param argv: List of command line arguments
:return: Parsed command line options
"""
usage = """
* Run a new job:
$SCHRODINGER/fep_solubility -HOST <main-host> -SUBHOST <subhost> -JOBNAME <jobname> <ligands.mae>
* Restart a previously interrupted job:
$SCHRODINGER/fep_solubility -HOST <main-host> -SUBHOST <subhost> -JOBNAME <jobname> -RESTART -checkpoint <multisim-checkpoint-file>
* Prepare input files for multisim. Do NOT run job:
$SCHRODINGER/fep_solubility -HOST <main-host> -SUBHOST <subhost> -JOBNAME <jobname> <ligands.mae> -prepare
* Extend production simulations for certain ligands:
$SCHRODINGER/fep_solubility -HOST <main-host> -SUBHOST <subhost> -JOBNAME <jobname> -extend <ligand-file> -checkpoint <multisim-checkpoint-file> -hydration-time 5000.0 -sublimation-time 10000.0
An example for the format of an edge-file:
ligand1_title
ligand2_title
ligand3_title
"""
options = cmdline.get_common_options()
options.extend([
# name default help [dest]
Option("inp_file", None, "A Maestro structure file", {"nargs": "?"}),
Option(
"-time",
5000.0,
"Specify the production-simulation time (in ps) for the Molecular Dynamics stage."
" Default: 5000.0. "
"Min value: 500.0",
),
Option(
["-hydration-time", "-hydration_time"],
5000.0,
"Specify the production-simulation time (in ps) for the Hydration FEP stage."
" Default: 5000.0. "
"Min value: 500.0",
),
Option(
["-solvation-time", "-solvation_time"],
10000.0,
"Specify the production-simulation time (in ps) for the Solvation FEP stage."
" Default: 10000.0. "
"Min value: 500.0",
),
Option(
["-sublimation-time", "-sublimation_time"],
10000.0,
"Specify the production-simulation time (in ps) for the Sublimation FEP stage."
" Default: 10000.0. "
"Min value: 500.0",
),
Option(["-hydration-only", "-hydration_only"], False,
"Only run hydration fep."),
Option("-solvation-only", False, "Only run solvation fep."),
Option(
"-solvent-template-structure", None,
"Template structure for solvent(s). "
"Only used in the solvation only workflow."),
Option(
"-solvent-composition", "",
"Composition of the solvent in the format "
"X:Y:Z where X, Y, Z are integers specifying "
"the number of each solvent molecule matching the order in the "
"input -solvent-template-structure file. If there is only one "
"structure, specify a single integer X. "
"Only used in the solvation only workflow."),
Option("-crystal-structure", False,
"Treat input structure as crystal structure (off by default)."),
Option(["-h-mass", "-h_mass"], False,
"Turn on hydrogen mass repartitioning (off by default)."),
Option(
"-no_h_mass", True,
"Turn off hydrogen mass repartitioning (off by default). "
"NOTE: This option is deprecated and ignored."),
Option(
["-custom-charge-mode", "-custom_charge_mode"],
CUSTOM_CHARGE_MODE.ASSIGN,
"Set the custom charge calculation mode when using the "
f"{mm.OPLS_NAME_F16} forcefield."
"Default is to 'assign' custom charges based on the input geometries."
"Set to 'clear' to clear custom charges without assigning them."
"Set to 'keep' to keep existing custom charge parameters."),
Option(
"-extend",
"",
"Extend production simulations of specified ligands.",
{
"metavar": "<ligand-file>",
},
),
Option(
["-dsb-n-molecules", "-dsb_n_molecules"],
stage.DisorderedSystemBuilder.N_MOLECULES,
argparse.SUPPRESS,
),
])
args = cmdline.parse_options(usage, options, argv[1:])
return FepSolubilityArgs(args)