Skip to content

Commit

Permalink
enabled cmd input and json input
Browse files Browse the repository at this point in the history
  • Loading branch information
fangning-ren committed Jul 11, 2024
1 parent de7d7e6 commit 4e383f3
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 175 deletions.
7 changes: 4 additions & 3 deletions autosolvate/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from autosolvate.autosolvate import *
from autosolvate.generatetrajs import *
from autosolvate.clustergen import *
from autosolvate.FFmetalcomplex import *
# from autosolvate.FFmetalcomplex import *
from autosolvate.multicomponent import *

## Main function
Expand All @@ -27,8 +27,9 @@ def main(args=None):
print('Usage: autosolvate [OPTIONS]')
print(' [NO OPTION] launches GUI')
print(' boxgen [OPTIONS] generate initial structure')
print(' boxgen_metal[OPTIONS] generate initial structure for organometallic compounds')
print(' mdrun [OPTIONS] automated QM/MM trajectory generatio')
print(' boxgen_metal[OPTIONS] generate initial structure and forcefield for organometallic compounds')
print(' boxgen_multicomponent [OPTIONS] generate initial structure for multicomponent systems')
print(' mdrun [OPTIONS] automated QM/MM trajectory generation')
print(' clustergen [OPTIONS] extract microsolvated clusters ')
print(' -h, --help short usage description')
print()
Expand Down
1 change: 1 addition & 0 deletions autosolvate/dockers/_packmol_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def load_solute(self, doc: TextIO, box: SolvatedSystem) -> None:
# doc.write('{:<15} {} \n'.format('structure', solute.pdb))
if not os.path.exists(os.path.join(self.workfolder, os.path.basename(solute.pdb))):
shutil.copy(solute.pdb, os.path.join(self.workfolder, os.path.basename(solute.pdb)))
# this is actually a bug in packmol. The path to the pdb file cannot be too long. One have to copy the file to the working directory
doc.write('{:<15} {} \n'.format('structure', os.path.basename(solute.pdb)))
doc.write('{:<15} {:<5} \n'.format('number', 1))
doc.write('{:<10} {posx} {posy} {posz} {com} {com} {com}\n'.format('fixed', posx=solute_pos[0], posy=solute_pos[1], posz=solute_pos[2], com=0.0))
Expand Down
235 changes: 95 additions & 140 deletions autosolvate/multicomponent.py

Large diffs are not rendered by default.

27 changes: 18 additions & 9 deletions autosolvate/tests/test_multicomponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
from . import helper_functions as hp




def test_ionpair_solvation(tmpdir):
"""
@TODO:
Expand Down Expand Up @@ -46,9 +44,6 @@ def test_ionpair_solvation(tmpdir):
path_main_exist *= os.path.exists(f"{name}.{suffix}")
assert path_main_exist

# assert hp.compare_pdb(f"water_solvated.pdb", hp.get_reference_dir(f"multicomponent/water_solvated.pdb"))
# assert hp.compare_inpcrd_prmtop(f"water_solvated.prmtop", hp.get_reference_dir(f"multicomponent/water_solvated.prmtop"))

def test_ionpair_solvation_custom_solvent(tmpdir):
testName = "test_custom_ionpair_solvation"
solutexyz = hp.get_input_dir("ionpair.pdb")
Expand All @@ -73,8 +68,6 @@ def test_ionpair_solvation_custom_solvent(tmpdir):
path_main_exist *= os.path.exists(f"{name}.{suffix}")
assert path_main_exist



def test_multicomponent(tmpdir):
testName = "test_multicomponent"
inpfname = "PAHs"
Expand Down Expand Up @@ -125,10 +118,11 @@ def test_mixture_builder():
hp.get_reference_dir(f"multicomponent/naphthalene-acetonitrile-water.prmtop"),
threshold = np.inf, # I set it to inf because packmol has some randomness in the output. This function will check the topology and force field parameters.
)

def test_mixture_builder_file_input():
test_name = "test_mixture_builder_file_input"
startmulticomponent_fromfile(hp.get_input_dir("mixturebuilder_input1.json"))
inputfilepath = hp.get_input_dir("mixturebuilder_input1.json")
startmulticomponent(["-f", inputfilepath])

solute_path_exist = True
solute_path_exist *= os.path.exists("naphthalene.mol2")
Expand All @@ -152,3 +146,18 @@ def test_mixture_builder_file_input():
hp.get_reference_dir(f"multicomponent/naphthalene-acetonitrile-water.prmtop"),
threshold = np.inf, # I set it to inf because packmol has some randomness in the output. This function will check the topology and force field parameters.
)

def test_mixture_builder_cmd_input():
"""This is a legacy feature, designed solely to respect the habits of users of the older version. It is not recommended for use."""
test_name = "test_mixture_builder_cmd_input"
solute_xyz = hp.get_input_dir("naphthalene_neutral.xyz")
startmulticomponent([
"-m", solute_xyz,
"-o", "mybox",
"-c", "0",
"-u", "1",
"-s", "water",
"-b", "20",
"-t", "0.8",
])
assert os.path.exists("mybox.prmtop")
1 change: 1 addition & 0 deletions autosolvate/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .tools import *
from .inputparser import *
from .tools_fffit import *
from .frcmod import *
from .check_executables import *
Expand Down
112 changes: 108 additions & 4 deletions autosolvate/utils/inputparser.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,115 @@
# This module is here to process the json input of multicomponent.py

import os
import re
import copy
import json
import shutil

from typing import Iterable, List, Dict, Any, Tuple


def convert_cmd_to_dict(argument_dict:Dict[str, Any]) -> Dict[str, Any]:
special_arguments = ["solute", "solvent"]
newdata = {}
for key, value in argument_dict.items():
if key not in special_arguments:
newdata[key] = value
data = argument_dict
if "main" in data and data["main"]:
newdata["solute"] = dict()
newdata['solute']['xyzfile'] = data["main"]
if "charge" in data:
newdata["solute"]["charge"] = data["charge"]
if "spinmult" in data:
newdata["solute"]["spinmult"] = data["spinmult"]
if "solvent" in data and data["solvent"]:
newdata["solvent"] = dict()
if "solventoff" in data:
newdata["solvent"]["off"] = data["solventoff"]
if "solventfrcmod" in data:
newdata["solvent"]["frcmod"] = data["solventfrcmod"]
if "solvent" in data:
newdata["solvent"]["name"] = data["solvent"]
return newdata


def correct_keyword(data:dict) -> dict:
"""
Correct the key in the dictionary to the acceptable keys
"""
keyword_dict = {
"chargemethod": "charge_method",
"cubesize": "cube_size",
"fragmentcharge": "fragment_charge",
"fragmentspinmultiplicity": "fragment_spinmult",
"fragmentmultiplicity": "fragment_spinmult",
"spinmultiplicity": "spinmult",
"multiplicity": "spinmult",
"output": "prefix",
}
newdata = {}
for key, value in data.items():
if key in keyword_dict:
newdata[keyword_dict[key]] = value
else:
newdata[key] = value
return newdata

def add_missing_xyzfile_keyword(data:dict, support_input_format:Iterable[str] = ("xyz", "pdb", "mol2", "prep", "off", "lib")) -> dict:
if "xyzfile" in data:
return data
for key, value in data.items():
if key in support_input_format and not "xyzfile" in data and isinstance(value, str) and os.path.isfile(value):
data["xyzfile"] = value
break
return data

def overwrite_keyword(data:dict, arguments:List[Tuple[str, Any]]) -> dict:
"""
Overwrite the keyword in the dictionary with the arguments
"""
newdata = copy.deepcopy(data)
ignoredargs = []
arguments
for key, value in arguments:
if isinstance(value, str) and value == "":
ignoredargs.append(key)
for key in ignoredargs:
arguments.remove((key, ""))
for key, value in arguments:
newdata[key] = value
if "main" in data and data["main"]:
newdata["solute"] = dict()
newdata['solute']['xyzfile'] = data["main"]
if "charge" in data:
newdata["solute"]["charge"] = data["charge"]
if "spinmult" in data:
newdata["solute"]["spinmult"] = data["spinmult"]
if "solvent" in data and data["solvent"]:
newdata["solvent"] = dict()
if "solventoff" in data:
newdata["solvent"]["off"] = data["solventoff"]
if "solventfrcmod" in data:
newdata["solvent"]["frcmod"] = data["solventfrcmod"]
if "solvent" in data:
newdata["solvent"]["name"] = data["solvent"]

return newdata

def parse_json_input(jsonpath:str):
with open(jsonpath, "r") as f:
data = json.load(f)

def check_inputs(data:dict) -> dict:
"""
Check if the input is valid
"""
if "solute" not in data and "solutes" not in data:
raise ValueError("No solute is provided")
if "solvent" not in data and "solvents" not in data:
raise ValueError("No solvent is provided")
if "solute" in data and "solutes" in data:
raise ValueError("You cannot provide both solute and solutes, only provide the solute argument if you want to use single solute")
if "solvent" in data and "solvents" in data:
raise ValueError("You cannot provide both solvent and solvents, only provide the solvent argument if you want to use single solvent")
if "solutes" in data and (not isinstance(data["solutes"], list) or len(data["solutes"]) == 0):
raise ValueError("solutes must be a list")
if "solvents" in data and (not isinstance(data["solvents"], list) or len(data["solvents"]) == 0):
raise ValueError("solvents must be a list")
10 changes: 1 addition & 9 deletions autosolvate/utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,15 +405,7 @@ def get_residue_name_from_pdb(file_path:str) -> str:
return residue_name


# processing input dictionary
def add_missing_xyzfile_keyword(data:dict, support_input_format:Iterable[str] = ("xyz", "pdb", "mol2", "prep")) -> dict:
if "xyzfile" in data:
return data
for key, value in data.items():
if key in support_input_format and not "xyzfile" in data and isinstance(value, str) and os.path.isfile(value):
data["xyzfile"] = value
break
return data




Expand Down
10 changes: 0 additions & 10 deletions docs/tutorial_multicomponent.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ The following tutorial illustrates the basic usage of **Autosolvate Multicompone

There will be one example systems: naphthalene in mixed water and acetonitrile solvent. The tutorial will be broken down into three steps:

.. note::

Note::

Note to Autosolvate Developers

I have not tested if the download links work properly in this document because I can not add this .rst file to https://autosolvate.readthedocs.io/en/latest/.

Please test and make sure all the download link work properly.



Prerequisites
Expand Down
112 changes: 112 additions & 0 deletions test_water_acn.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,123 @@
"%autoreload 2\n",
"from autosolvate.autosolvate import *\n",
"from autosolvate.multicomponent import *\n",
"\n",
"import os \n",
"ppath = \"/home/fren5/AutoSolvate-update/AutoSolvate\" \n",
"os.chdir(ppath)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"usage: ipykernel_launcher.py [-h] [-f FILE] [-m MAIN] [-o OUTPUT] [-c CHARGE]\n",
" [-u SPINMULTIPLICITY] [-s SOLVENT]\n",
" [-g CHARGEMETHOD] [-b CUBESIZE] [-t CLOSENESS]\n",
" [-r] [-e GAUSSIANEXE] [-d GAUSSIANDIR]\n",
" [-a AMBERHOME]\n",
"\n",
"Add solvent box to a given solute and generate related force field parameters.\n",
"\n",
"optional arguments:\n",
" -h, --help show this help message and exit\n",
" -f FILE, --file FILE json file containing the input parameters, Required\n",
" when using multiple solvents\n",
" -m MAIN, --main MAIN solute xyz file\n",
" -o OUTPUT, --output OUTPUT\n",
" prefix of the output file names\n",
" -c CHARGE, --charge CHARGE\n",
" formal charge of solute\n",
" -u SPINMULTIPLICITY, --spinmultiplicity SPINMULTIPLICITY\n",
" spin multiplicity of solute\n",
" -s SOLVENT, --solvent SOLVENT\n",
" solvent xyz files, Will use single solvent if\n",
" provided.\n",
" -g CHARGEMETHOD, --chargemethod CHARGEMETHOD\n",
" name of charge fitting method (bcc, resp)\n",
" -b CUBESIZE, --cubesize CUBESIZE\n",
" size of solvent cube in angstroms\n",
" -t CLOSENESS, --closeness CLOSENESS\n",
" solute-solvent closeness setting. Automation is not\n",
" possible for mixed solvent\n",
" -r, --srunuse option to run inside a slurm job\n",
" -e GAUSSIANEXE, --gaussianexe GAUSSIANEXE\n",
" name of the Gaussian quantum chemistry package\n",
" executable\n",
" -d GAUSSIANDIR, --gaussiandir GAUSSIANDIR\n",
" path to the Gaussian package\n",
" -a AMBERHOME, --amberhome AMBERHOME\n",
" path to the AMBER molecular dynamics package root\n",
" directory\n",
"\n",
"suggest usage: autosolvate multicomponent -f input.json if an input file is\n",
"provided, all command line options will be ignored. If using command line as\n",
"the traditional way, it will only generate a single solute with single\n",
"solvent. This is a legacy feature, designed solely for the compatibility with\n",
"the older version. It is not recommended for further use.\n"
]
},
{
"ename": "SystemExit",
"evalue": "0",
"output_type": "error",
"traceback": [
"An exception has occurred, use %tb to see the full traceback.\n",
"\u001b[0;31mSystemExit\u001b[0m\u001b[0;31m:\u001b[0m 0\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/fren5/psi4conda/envs/autosolvate/lib/python3.8/site-packages/IPython/core/interactiveshell.py:3516: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.\n",
" warn(\"To exit: use 'exit', 'quit', or Ctrl-D.\", stacklevel=1)\n"
]
}
],
"source": [
"startmulticomponent([\n",
" \"-h\"\n",
"])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"1 molecule converted\n"
]
}
],
"source": [
"newpath = os.path.join(ppath, \"test_cmd_input\")\n",
"\n",
"if not os.path.exists(newpath):\n",
" os.makedirs(newpath)\n",
"\n",
"os.chdir(newpath)\n",
"inputdir = \"/home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs\"\n",
"startmulticomponent([\n",
" \"-m\", os.path.join(inputdir, \"naphthalene_neutral.xyz\"),\n",
" \"-o\", \"mybox\",\n",
" \"-c\", \"0\",\n",
" \"-u\", \"1\",\n",
" \"-s\", \"water\",\n",
" \"-b\", \"20\",\n",
" \"-t\", \"0.8\",\n",
"])\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
Expand Down

0 comments on commit 4e383f3

Please sign in to comment.