diff --git a/autosolvate/molecule/molecule.py b/autosolvate/molecule/molecule.py index b1af2b7..dd12c95 100644 --- a/autosolvate/molecule/molecule.py +++ b/autosolvate/molecule/molecule.py @@ -215,7 +215,7 @@ def reference_name(self) -> str: # @dataclass class Molecule(System): #constants - _SUPPORT_INPUT_FORMATS = ['pdb', 'xyz'] + _SUPPORT_INPUT_FORMATS = ['pdb', 'xyz', 'mol2', 'prep'] # other def __init__( self, @@ -223,7 +223,7 @@ def __init__( charge: int, multiplicity: int, name = "", - residue_name = "MOL", + residue_name = "", folder = WORKING_DIR, amber_solvent = False, ) -> None: @@ -233,7 +233,7 @@ def __init__( Parameters ---------- xyzfile : str - The file path of the xyz file of the molecule, can also be a pdb file. + The file path of the structure file of the molecule, can be in xyz, pdb, mol2 or prep format. charge : int The charge of the molecule. multiplicity : int @@ -241,7 +241,7 @@ def __init__( name : str, optional The name of the molecule, by default "" will be the basename of the xyzfile. residue_name : str, optional - The residue name of the molecule, by default "MOL". + The residue name of the molecule, ignored if the xyzfile is provided as mol2 or prep format, by default will be assigned according to the filename. folder : str, optional The folder to store all files of the molecule, by default is the current working directory. amber_solvent : bool, optional @@ -260,29 +260,83 @@ def __init__( self.logger.name = self.__class__.__name__ if not self.amber_solvent: - self.name = process_system_name(name, xyzfile, support_input_format=Molecule._SUPPORT_INPUT_FORMATS) - self.read_coordinate(xyzfile) + if not self.name: + self.name = process_system_name(name, xyzfile, support_input_format=Molecule._SUPPORT_INPUT_FORMATS, check_exist=False) + self.process_input_xyz(xyzfile) + self.generate_pdb() - def read_coordinate(self, fname:str): - ext = os.path.splitext(fname)[-1][1:] - setattr(self, ext, fname) + def process_input_xyz(self, xyzfile:str) -> None: for e in Molecule._SUPPORT_INPUT_FORMATS: - if e == ext: - continue - newpath = self.reference_name + "." + e - subprocess.run(f"obabel -i {ext} {fname} -o {e} -O {newpath} ---errorlevel 0", shell = True) - setattr(self, e, newpath) + ext = os.path.splitext(xyzfile)[-1] + if e in ext: + self.__setattr__(e, xyzfile) + break + else: + raise ValueError(f"Unsupported input format for the molecule: {xyzfile}") def generate_pdb(self): - if not self.check_exist("pdb") and self.name != "water": - prep2pdb4amber_solvent(self) - self.logger.info(f"AMBER predefined solvent {self.name} is used.") - self.logger.info(f"Converted the predefined prep file {self.prep} to pdb file {self.pdb}") - elif self.name == "water": + """ + Convert the input structure file to pdb file if the provided coordinate file is not in pdb format. + If the pdb file for the same molecule already exists, it will be ignored. + This behavior is to keep the residue name of the molecule consistent. + """ + if self.name == "water" and self.amber_solvent: assign_water_pdb(self) self.logger.info(f"AMBER predefined water is used.") self.logger.info(f"Write the reference pdb file for {self.name} to {self.pdb}") self.logger.info(f"Water model used: TIP3P") + elif self.amber_solvent: + prep2pdb(self) + self.logger.info(f"Predefined solvent {self.name} is used.") + self.logger.info(f"Converted the predefined prep file {self.prep} to pdb file {self.pdb}") + elif self.check_exist("prep"): + newpath = self.reference_name + ".pdb" + if self.check_exist("pdb"): + self.logger.warning(f"The existing pdb file {self.pdb} will be ignored as amber prep file is provided.") + shutil.move(self.pdb, self.reference_name + "-bak.pdb") + self.logger.info(f"Converted the prep file {self.prep} to pdb file {self.pdb}") + prep2pdb(self) + elif self.check_exist("mol2"): + newpath = self.reference_name + ".pdb" + if self.check_exist("pdb"): + self.logger.warning(f"The existing pdb file {self.pdb} will be ignored as mol2 file is provided.") + shutil.move(self.pdb, self.reference_name + "-bak.pdb") + self.logger.info(f"Converted the mol2 file {self.mol2} to pdb file {self.pdb}") + subprocess.run(f"obabel -i mol2 {self.mol2} -o pdb -O {newpath} ---errorlevel 0", shell = True) + self.pdb = newpath + elif self.check_exist("pdb"): + pass + elif self.check_exist("xyz"): + newpath = self.reference_name + ".pdb" + subprocess.run(f"obabel -i xyz {self.xyz} -o pdb -O {newpath} ---errorlevel 0", shell = True) + self.pdb = newpath + + def get_residue_name(self): + """ + Get the residue name from the provided pdb, mol2, or prep file. + Note that the residue name of the molecule will be automatically assigned if the residue name is not found. + Note if the mol2, or prep file is provided, the 'residue_name' attribute will be updated according to these files instead of the argument 'residue_name'. + """ + new_residue_name = "" + if self.check_exist("prep"): # 这里必须要改。都已经知道residue name了就没有必要从prep文件里获取了。必须找到从prep文件中直接读取residue name的方法,或者找到把prep文件转换成pdb文件并且不借助residue name的方法。 + prep2pdb_withexactpath(self.prep, self.reference_name + "-fromprep.pdb", self.residue_name) + new_residue_name = get_residue_name_from_pdb(self.reference_name + "-fromprep.pdb") + elif self.check_exist("mol2"): + newpath = self.reference_name + "-frommol2.pdb" + subprocess.run(f"obabel -i mol2 {self.mol2} -o pdb -O {newpath} ---errorlevel 0", shell = True) + new_residue_name = get_residue_name_from_pdb(newpath) + elif self.residue_name: + new_residue_name = self.residue_name + else: + self.logger.warning(f"Cannot find the residue name of molecule {self.name}, trying to automatically assign one.") + new_residue_name = try_ones_best_to_get_residue_name(self, self.pdb, self.name) + if not new_residue_name: + new_residue_name = "MOL" + if self.residue_name != new_residue_name: + self.logger.warning(f"Residue name of {self.name} changed: {self.residue_name} -> {new_residue_name}") + self.logger.warning(f"This behavior is to keep the residue name of the molecule consistent.") + self.residue_name = new_residue_name + self.logger.info(f"Residue name of {self.name} is set to {self.residue_name}") def update(self): if not self.amber_solvent: diff --git a/autosolvate/molecule/molecule_complex.py b/autosolvate/molecule/molecule_complex.py index 2f67a94..b313752 100644 --- a/autosolvate/molecule/molecule_complex.py +++ b/autosolvate/molecule/molecule_complex.py @@ -89,12 +89,11 @@ def __init__(self, def read_coordinate(self, fname:str): ext = os.path.splitext(fname)[-1][1:] setattr(self, ext, fname) - for e in Molecule._SUPPORT_INPUT_FORMATS: - if e == ext: - continue - nname = os.path.splitext(fname)[0] + "." + e - subprocess.run(f"obabel -i {ext} {fname} -o {e} > {nname}", shell = True) - setattr(self, e, nname) + if ext != "pdb": + subprocess.run(f"obabel -i {ext} {fname} -o pdb -O {self.reference_name}.pdb ---errorlevel 0", shell = True) + # subprocess.run(f"obabel -i {ext} {fname} -o pdb -O {self.reference_name}.pdb", shell = True) + # os.system(f"obabel -i {ext} {fname} -o pdb -O {self.reference_name}.pdb") + self.pdb = f"{self.reference_name}.pdb" def check_protein_fragment(self, fragment_obmol:ob.OBMol, fragment_index = 0): for j in range(fragment_obmol.NumResidues()): @@ -145,7 +144,7 @@ def getFragments(self): fragres = fragment_obmol.GetResidue(0) fragresname = fragres.GetName() # This is a water molecule or an amino acid moleucle. - if fragresname == "HOH" or fragresname in AMINO_ACID_RESIDUES: + if fragresname == "HOH" or fragresname == "WAT" or fragresname in AMINO_ACID_RESIDUES: self.fragmols.append(ob.OBMol(fragment_obmol)) self.fragresiduenames.append(fragresname) logger.info(f"Fragment {i} is a known molecule with res name {fragresname}. ") diff --git a/autosolvate/molecule/transition_metal_complex.py b/autosolvate/molecule/transition_metal_complex.py index d54ef48..46ea05b 100644 --- a/autosolvate/molecule/transition_metal_complex.py +++ b/autosolvate/molecule/transition_metal_complex.py @@ -111,13 +111,13 @@ def check_system(self): self.metal_legand_bonds = self.get_bond_connect_with_metal() for metal_residue_name in self.metal_residue_names: if not os.path.exists(f"{self.origin_folder}/{metal_residue_name}.mol2"): - logger.error(f"Metal residue {metal_residue_name}.mol2 does not exist.") + self.logger.error(f"Metal residue {metal_residue_name}.mol2 does not exist.") raise FileNotFoundError self.logger.info(f"Find mol2 for {metal_residue_name}: {self.origin_folder}/{metal_residue_name}.mol2") self.metal_mol2_files.append(f"{self.origin_folder}/{metal_residue_name}.mol2") for legand_residue_name in self.legand_residue_names: if not os.path.exists(f"{self.origin_folder}/{legand_residue_name}.mol2"): - logger.error(f"Legand residue {legand_residue_name}.mol2 does not exist.") + self.logger.error(f"Legand residue {legand_residue_name}.mol2 does not exist.") raise FileNotFoundError self.logger.info(f"Find mol2 for {legand_residue_name}: {self.origin_folder}/{legand_residue_name}.mol2") self.legand_mol2_files.append(f"{self.origin_folder}/{legand_residue_name}.mol2") @@ -168,8 +168,8 @@ def find_out_real_residue_index(self, solvated_pdb:str): for key in tmpdict.keys(): self.logger.info(f"The system contains {len(tmpdict[key])} {key}" + f" with indices {tmpdict[key]}") if len(tmpdict[key]) != len(tmpdict[list(tmpdict.keys())[0]]): - logger.error(f"The number of {key} is different from others!") - logger.error(f"This may indicate an incomplete transition metal complex structure") + self.logger.error(f"The number of {key} is different from others!") + self.logger.error(f"This may indicate an incomplete transition metal complex structure") raise ValueError for i in range(len(tmpdict[list(tmpdict.keys())[0]])): tmc_res_index = {} diff --git a/autosolvate/multicomponent.py b/autosolvate/multicomponent.py index ff61c9a..d0d4cc5 100644 --- a/autosolvate/multicomponent.py +++ b/autosolvate/multicomponent.py @@ -14,6 +14,9 @@ import getopt, sys, os import subprocess from typing import List, Tuple, Iterable +import json +import logging +import inspect from .molecule import * from .dockers import * @@ -22,39 +25,40 @@ from autosolvate.autosolvate import * class MulticomponentParamsBuilder(): + """ + Create amber parameter files for a single xyz or pdb file with multiple separate fragments + + Parameters + ---------- + xyzfile : str + structure file name, can be any structural files that openbabel recognizes. + name : array_like, Optional. default: the base name of the provided structure file. + Not used + residue_name : array_like, Optional. default: Residue name provided in pdb file or assigned as UAA, UAB, UAC, etc. + Residue names for each fragments. A list of strings of three capital letters. Its length should equal to the number of fragments in xyzfile. If this parameter is not given, the residues will be assigned by "U" plus "AB","AC",..."AZ", "BA"... + charge : dict | array_like, Optional. default: 0 + Charge for each fragment. A list of integer with its length equal to the number of fragments, or a dictionary with the three-letter name of the residue as the key and the corresponding charge as the value. If not given, all fragment will be considered as neutral. + spinmult : dict | array_like, Optional. default: 0 + Multiplicity for each fragment. A list of integer with its length equal to the number of fragments, or a dictionary with the three-letter name of the residue as the key and the corresponding charge as the value. If not given, all fragment will be considered as singlet. + outputFile : str, Optional, default='water_solvated' + Filename-prefix for outputfiles + pre_optimize_fragments : bool, Optional, default: False + do geometry optimization with MMFF94 forcefield in OpebBabel before running antechamber + srun_use : bool, Optional, default='False + Run all commands with a srun prefix + gaussianexe : str, Optional, default: g16 + name of the Gaussian executeble + gaussiandir : str, Optional, default: $GAUSSIANDIR + path of Gaussian + amberhome : str, Optional, default: $AMBERHOME + path of amber + deletefiles : bool, Optional, default: False + Delete all temporary files except the .prmtop and .inpcrd file of the pdb file provided. + """ def __init__(self, xyzfile: str, name="", residue_name="SLU", charge=0, spinmult=1, charge_method="resp", folder = WORKING_DIR, **kwargs): - """ - Create amber parameter files for a single xyz or pdb file with multiple separate fragments - Parameters - ---------- - xyzfile : str - structure file name, can be any structural files that openbabel recognizes. - name : array_like, Optional. default: the base name of the provided structure file. - Not used - residue_name : array_like, Optional. default: Residue name provided in pdb file or assigned as UAA, UAB, UAC, etc. - Residue names for each fragments. A list of strings of three capital letters. Its length should equal to the number of fragments in xyzfile. If this parameter is not given, the residues will be assigned by "U" plus "AB","AC",..."AZ", "BA"... - charge : dict | array_like, Optional. default: 0 - Charge for each fragment. A list of integer with its length equal to the number of fragments, or a dictionary with the three-letter name of the residue as the key and the corresponding charge as the value. If not given, all fragment will be considered as neutral. - spinmult : dict | array_like, Optional. default: 0 - Multiplicity for each fragment. A list of integer with its length equal to the number of fragments, or a dictionary with the three-letter name of the residue as the key and the corresponding charge as the value. If not given, all fragment will be considered as singlet. - outputFile : str, Optional, default='water_solvated' - Filename-prefix for outputfiles - pre_optimize_fragments : bool, Optional, default: False - do geometry optimization with MMFF94 forcefield in OpebBabel before running antechamber - srun_use : bool, Optional, default='False - Run all commands with a srun prefix - gaussianexe : str, Optional, default: g16 - name of the Gaussian executeble - gaussiandir : str, Optional, default: $GAUSSIANDIR - path of Gaussian - amberhome : str, Optional, default: $AMBERHOME - path of amber - deletefiles : bool, Optional, default: False - Delete all temporary files except the .prmtop and .inpcrd file of the pdb file provided. - """ self.folder = folder self.mol = MoleculeComplex(xyzfile, name=name, residue_name=residue_name, charges=charge, multiplicities=spinmult, folder = self.folder) @@ -166,6 +170,22 @@ def build(self): class MixtureBuilder(): + """ + Create amber parameter files for a single solute with mixed solvents + + Use 'add_solute' to add solute and 'add_solvent' to add solvent. + + Parameters + ---------- + folder : str, Optional, default: current working directory + working directory + cube_size : int, Optional, default: 54 + size of solvent cube in angstroms + closeness : float, Optional, default: 2.0 + Solute-solvent closeness setting, corresponding to the tolerance parameter in packmol in Å, + charge_method : str, Optional, default: "bcc" + name of charge fitting method (bcc, resp) + """ def __init__(self, folder = WORKING_DIR, cube_size = 54, closeness = 2.0, charge_method = "bcc"): self.solutes = [] self.solvents = [] @@ -182,35 +202,142 @@ def __init__(self, folder = WORKING_DIR, cube_size = 54, closeness = 2.0, charge PackmolDocker(workfolder = self.folder), TleapDocker(workfolder = self.folder) ] + self.logger = logging.getLogger(name = self.__class__.__name__) + self.output_handler = logging.FileHandler(filename = "autosolvate.log", mode = "a", encoding="utf-8") + self.output_formater = logging.Formatter(fmt = '%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt="%H:%M:%S") + self.output_handler.setFormatter(self.output_formater) + if len(self.logger.handlers) == 0: + self.logger.addHandler(self.output_handler) - def add_solute(self, xyzfile:str, name="", residue_name="", charge=0, spinmult=1, number = 1, **kwargs): - # use the first three letters of the xyzfile name as the residue name if not provided - if not residue_name: - residue_name = try_ones_best_to_get_residue_name(xyzfile, name) - molecule = Molecule(xyzfile, charge=charge, multiplicity=spinmult, folder = self.folder, name = name, residue_name=residue_name) + def add_complex_solute(self, xyzfile:str, fragment_charge = 0, fragment_spinmult = 1, number = 1, **kwargs): + """ + add a molecular complex as the solute - if "mol2" in kwargs and os.path.isfile(kwargs["mol2"]): + Parameters + ---------- + xyzfile : str + structure file name, can be any type within ["xyz", "pdb", "mol2"]. frcmod are not supported for molecular complex. + fragment_charge : dict | array_like, Optional, default: 0 + Charge for each fragment. A dictionary with the three-letter name of the residue as the key and the corresponding charge as the value. If not given, all fragment will be considered as neutral. + fragment_spinmult : dict | array_like, Optional, default: 1 + Multiplicity for each fragment. A dictionary with the three-letter name of the residue as the key and the corresponding charge as the value. If not given, all fragment will be considered as singlet. + number : int, Optional, default: 1 + number of the solute in the system. Be cautious about this as the solute may have more than 1 fragments. + **kwargs : dict + Other arguments that need to be included in the solute. Remained for future development. + """ + molecule = MoleculeComplex(xyzfile, charges=fragment_charge, multiplicities=fragment_spinmult, folder = self.folder) + for fragment in molecule.newmolecules: + for docker in self.single_molecule_pipeline: + docker.run(fragment) + TleapDocker(workfolder = self.folder).run(molecule) + molecule.number = number + self.solutes.append(molecule) + + def add_solute(self, xyzfile:str, name="", residue_name="SLU", charge=0, spinmult=1, number = 1, **kwargs): + """ + add a solute molecule + + Parameters + ---------- + xyzfile : str + structure file name, can be any type within ["xyz", "pdb", "mol2"] + name : str, Optional, default: the base name of the provided structure file + name of the solute + residue_name : str, Optional, default: "SLU" + residue name of the solute. Note if an mol2 or prep file is provided, the residue name will be read from the file. + charge : int, Optional, default: 0 + charge of the solute + spinmult : int, Optional, default: 1 + spin multiplicity of the solute + number : int, Optional, default: 1 + number of the solute in the system. + **kwargs : dict + additional files needed for the solute, including "mol2", "frcmod", "lib", "prep". + If the user want to skip the antechamber and leap steps, the user need to provide the "mol2" and "frcmod" files by adding the following arguments: + mol2 : str + the path of the mol2 file of the solute + frcmod : str + the path of the frcmod file of the solute + """ + + if ("mol2" in kwargs and os.path.isfile(kwargs["mol2"])) and \ + ("frcmod" in kwargs and os.path.isfile(kwargs["frcmod"])): + molecule = Molecule(xyzfile, charge=charge, multiplicity=spinmult, folder = self.folder, name = name, residue_name=residue_name) molecule.mol2 = kwargs["mol2"] - if "frcmod" in kwargs and os.path.isfile(kwargs["frcmod"]): molecule.frcmod = kwargs["frcmod"] - if "lib" in kwargs and os.path.isfile(kwargs["lib"]): - molecule.lib = kwargs["lib"] - molecule.update() - - if not molecule.check_exist("frcmod") or not (molecule.check_exist("mol2") or molecule.check_exist("lib")): + molecule.get_residue_name() + molecule.update() + else: + molecule = Molecule(xyzfile, charge=charge, multiplicity=spinmult, folder = self.folder, name = name, residue_name=residue_name) for docker in self.single_molecule_pipeline: docker.run(molecule) molecule.number = number self.solutes.append(molecule) - def add_solvent(self, xyzfile:str = "", name="", residue_name="", charge=0, spinmult=1, number = 210*8, slv_generate = False, **kwargs): - if name in AMBER_SOLVENT_DICT and not slv_generate: # predefined amber solvent + def get_solvent_type(self, xyzfile = "", name = "", **kwargs): + solvent_type = "generate" + if name in AMBER_SOLVENT_DICT: + if (("prep" in kwargs and os.path.isfile(kwargs["prep"])) or \ + ("mol2" in kwargs and os.path.isfile(kwargs["mol2"]))) and \ + ("frcmod" in kwargs and os.path.isfile(kwargs["frcmod"])): + solvent_type = "custom" + else: + solvent_type = "amber" + elif name in custom_solv_dict: + if (("prep" in kwargs and os.path.isfile(kwargs["prep"])) or \ + ("mol2" in kwargs and os.path.isfile(kwargs["mol2"]))) and \ + ("frcmod" in kwargs and os.path.isfile(kwargs["frcmod"])): + solvent_type = "custom" + else: + solvent_type = "autosolvate_custom" + elif (("prep" in kwargs and os.path.isfile(kwargs["prep"])) or \ + ("mol2" in kwargs and os.path.isfile(kwargs["mol2"]))) and \ + ("frcmod" in kwargs and os.path.isfile(kwargs["frcmod"])): + solvent_type = "custom" + elif os.path.exists(xyzfile): + solvent_type = "generate" + else: + raise ValueError("Solvent not found") + return solvent_type + + def add_solvent(self, xyzfile:str = "", name="", residue_name="SLV", charge=0, spinmult=1, number = 210*8, **kwargs): + + """ + add a type of solvent + + Parameters + ---------- + xyzfile : str + structure file name, can be any type within ["xyz", "pdb", "mol2", "prep"]. Can be ignored if the solvent is predefined in AMBER. + name : str + name of the solvent. Predefined solvents include ["water", "methanol", "chloroform", "nma"]. + residue_name : str, Optional, default: "SLV" + residue name of the solvent. Will be ignored if the solvent is predefined in AMBER. This argument will be ignored if a mol2 or prep file is provided together with a frcmod. + charge : int, Optional, default: 0 + charge of the solvent + spinmult : int, Optional, default: 1 + spin multiplicity of the solvent + number : int, Optional, default: 210*8 + number of the solvent in the system. + **kwargs : dict + additional files needed for the solvent, including "mol2", "frcmod", "lib", "prep", will support "itp", "top" in the future. + If the user want to skip the antechamber and leap steps, the user need to provide the ["mol2" or "prep"] and "frcmod" files by adding the following arguments: + mol2 : str + the path of the mol2 file of the solvent + frcmod : str + the path of the frcmod file of the solvent + """ + + solvent_type = self.get_solvent_type(xyzfile, name, **kwargs) + if solvent_type == "amber": + self.logger.info(f"Adding predefined solvent {name}") self_solvent = AMBER_SOLVENT_DICT[name] self_solvent.folder = self.folder self_solvent.generate_pdb() - elif name in custom_solv_dict: # custom solvent in autosolvate - # solvent data prepared by autosolvate + elif solvent_type == "autosolvate_custom": + self.logger.info(f"Adding autosolvate custom solvent {name}") solvPrefix = custom_solv_dict[name] solvent_frcmod_path = pkg_resources.resource_filename('autosolvate', os.path.join('data',solvPrefix,solvPrefix+".frcmod")) @@ -221,9 +348,8 @@ def add_solvent(self, xyzfile:str = "", name="", residue_name="", charge=0, spin self_solvent = Molecule(solvent_pdb_path, 0, 1, name, residue_name = custom_solv_residue_name[name], folder = self.folder) self_solvent.frcmod = solvent_frcmod_path self_solvent.prep = solvent_prep_path - elif os.path.exists(xyzfile) and slv_generate: # user defined solvent - # generate solvent box - residue_name = try_ones_best_to_get_residue_name(xyzfile, name) + elif solvent_type == "custom": + self.logger.info(f"Adding user provided custom solvent {name}") self_solvent = Molecule(xyzfile, charge=charge, multiplicity=spinmult, folder = self.folder, name = name, residue_name=residue_name) if "mol2" in kwargs and os.path.isfile(kwargs["mol2"]): self_solvent.mol2 = kwargs["mol2"] @@ -231,11 +357,15 @@ def add_solvent(self, xyzfile:str = "", name="", residue_name="", charge=0, spin self_solvent.frcmod = kwargs["frcmod"] if "lib" in kwargs and os.path.isfile(kwargs["lib"]): self_solvent.lib = kwargs["lib"] + if "prep" in kwargs and os.path.isfile(kwargs["prep"]): + self_solvent.prep = kwargs["prep"] + self_solvent.get_residue_name() self_solvent.update() - - if not self_solvent.check_exist("frcmod") or not (self_solvent.check_exist("mol2") or self_solvent.check_exist("lib")): - for docker in self.single_molecule_pipeline: - docker.run(self_solvent) + elif solvent_type == "generate": + self.logger.info(f"Adding solvent {name} whose forcefield parameters will be generated with GAFF") + self_solvent = Molecule(xyzfile, charge=charge, multiplicity=spinmult, folder = self.folder, name = name, residue_name=residue_name) + for docker in self.single_molecule_pipeline: + docker.run(self_solvent) else: raise ValueError("Solvent not found") self_solvent.number = number @@ -263,7 +393,40 @@ def build(self): folder = self.folder) for docker in self.custom_solvation: docker.run(system) - + +def startmulticomponent_fromfile(jsonpath:str): + with open(jsonpath, "r") as f: + data = json.load(f) + data["folder"] = os.getcwd() + signature = inspect.signature(MixtureBuilder.__init__) + function_params = signature.parameters + filtered_data = {k: v for k, v in data.items() if k in function_params} + builder = MixtureBuilder(**filtered_data) + + if "solute" in data: + data["solute"] = add_missing_xyzfile_keyword(data["solute"]) + if check_multicomponent(data["solute"]["xyzfile"]): + builder.add_complex_solute(**data["solute"]) + else: + builder.add_solute(**data["solute"]) + if "solvent" in data: + data["solvent"] = add_missing_xyzfile_keyword(data["solvent"]) + builder.add_solvent(**data["solvent"]) + if "solutes" in data and type(data["solutes"]) == list: + for i in range(len(data["solutes"])): + data["solutes"][i] = add_missing_xyzfile_keyword(data["solutes"][i]) + if check_multicomponent(data["solutes"][i]["xyzfile"]): + builder.add_complex_solute(**data["solutes"][i]) + else: + builder.add_solute(**data["solutes"][i]) + if "solvents" in data and type(data["solvents"]) == list: + for i in range(len(data["solvents"])): + data["solvents"][i] = add_missing_xyzfile_keyword(data["solvents"][i]) + builder.add_solvent(**data["solvents"][i]) + builder.build() + + + def startmulticomponent(argumentList): r""" Wrap function that parses command line options for autosolvate multicomponent module, @@ -405,5 +568,4 @@ def startmulticomponent(argumentList): if __name__ == "__main__": - inst = MulticomponentParamsBuilder("PAHs.pdb", deletefiles=True) - inst.build() + startmulticomponent(sys.argv[1:]) diff --git a/autosolvate/tests/helper_functions.py b/autosolvate/tests/helper_functions.py index 4320fa4..8ad02d3 100644 --- a/autosolvate/tests/helper_functions.py +++ b/autosolvate/tests/helper_functions.py @@ -188,9 +188,9 @@ def compare_inpcrd_prmtop(out, ref, threshold = 1.0e-6): def compare_boxgen(out, ref): compareExist = True for suffix in [".pdb", ".prmtop", ".inpcrd"]: - compare_exist *= os.path.exists(os.path.splitext(out)[0] + suffix) + compareExist *= os.path.exists(os.path.splitext(out)[0] + suffix) comparePdb = compare_pdb(out, ref) - compareInpcrd, comparePrmtop = compare_pdb(out, ref) + compareInpcrd, comparePrmtop = compare_inpcrd_prmtop(out, ref) return comparePdb, compareInpcrd, comparePrmtop diff --git a/autosolvate/tests/inputs/ionpair.xyz b/autosolvate/tests/inputs/ionpair.xyz index 24bd1b8..d290f52 100644 --- a/autosolvate/tests/inputs/ionpair.xyz +++ b/autosolvate/tests/inputs/ionpair.xyz @@ -1,5 +1,5 @@ 46 -/home/fren5/AutoSolvae-update/AutoSolvate/autosolvate/tests/inputs/ionpair.pdb +/home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs/ionpair.pdb S 3.19500 0.41600 1.49200 O 4.02100 0.24700 0.15100 O 1.79100 0.90800 0.94300 diff --git a/autosolvate/tests/inputs/mixturebuilder_input1.json b/autosolvate/tests/inputs/mixturebuilder_input1.json new file mode 100644 index 0000000..4e3ec6c --- /dev/null +++ b/autosolvate/tests/inputs/mixturebuilder_input1.json @@ -0,0 +1,22 @@ +{ + "cube_size": 54, + "charge_method": "bcc", + "solute": { + "xyzfile": "inputs/naphthalene_neutral.xyz", + "number": 1, + "charge": 0, + "spinmult": 1, + "name": "naphthalene", + "residue_name": "NAP" + }, + "solvents":[ + { + "name": "acetonitrile", + "number": 200 + }, + { + "name": "water", + "number": 600 + } + ] +} \ No newline at end of file diff --git a/autosolvate/tests/inputs/mixturebuilder_input2.json b/autosolvate/tests/inputs/mixturebuilder_input2.json new file mode 100644 index 0000000..d8d4d64 --- /dev/null +++ b/autosolvate/tests/inputs/mixturebuilder_input2.json @@ -0,0 +1,26 @@ +{ + "cube_size": 30, + "charge_method": "bcc", + "solute": { + "xyzfile": "inputs/ionpair.pdb", + "fragment_charge": { + "SUF": -2, + "TPA": 1 + } + }, + "solvents":[ + { + "name": "acetonitrile", + "number": 150, + "frcmod": "inputs/acetonitrile.frcmod", + "prep": "inputs/acetonitrile.prep", + "residue_name": "C3N" + }, + { + "name": "toluene", + "number": 150, + "xyzfile": "inputs/toluene.pdb", + "residue_name": "TOL" + } + ] +} \ No newline at end of file diff --git a/autosolvate/tests/inputs/toluene.pdb b/autosolvate/tests/inputs/toluene.pdb new file mode 100644 index 0000000..28fc209 --- /dev/null +++ b/autosolvate/tests/inputs/toluene.pdb @@ -0,0 +1,16 @@ +HETATM 1 C UNK 0 -0.983 0.000 0.000 0.00 0.00 C +HETATM 2 C UNK 0 -0.285 1.208 0.000 0.00 0.00 C +HETATM 3 C UNK 0 -0.285 -1.208 0.000 0.00 0.00 C +HETATM 4 C UNK 0 -2.474 0.000 -0.000 0.00 0.00 C +HETATM 5 C UNK 0 1.110 1.208 -0.000 0.00 0.00 C +HETATM 6 C UNK 0 1.110 -1.208 0.000 0.00 0.00 C +HETATM 7 C UNK 0 1.807 0.000 -0.000 0.00 0.00 C +HETATM 8 H UNK 0 -0.817 2.156 -0.002 0.00 0.00 H +HETATM 9 H UNK 0 -0.817 -2.156 -0.002 0.00 0.00 H +HETATM 10 H UNK 0 -2.869 -0.880 0.518 0.00 0.00 H +HETATM 11 H UNK 0 -2.869 0.881 0.517 0.00 0.00 H +HETATM 12 H UNK 0 -2.849 -0.000 -1.028 0.00 0.00 H +HETATM 13 H UNK 0 1.653 2.148 -0.001 0.00 0.00 H +HETATM 14 H UNK 0 1.653 -2.148 -0.001 0.00 0.00 H +HETATM 15 H UNK 0 2.893 -0.000 -0.000 0.00 0.00 H +END diff --git a/autosolvate/tests/refs/multicomponent/napthalene-acetonitrile-water.inpcrd b/autosolvate/tests/refs/multicomponent/naphthalene-acetonitrile-water.inpcrd similarity index 100% rename from autosolvate/tests/refs/multicomponent/napthalene-acetonitrile-water.inpcrd rename to autosolvate/tests/refs/multicomponent/naphthalene-acetonitrile-water.inpcrd diff --git a/autosolvate/tests/refs/multicomponent/napthalene-acetonitrile-water.pdb b/autosolvate/tests/refs/multicomponent/naphthalene-acetonitrile-water.pdb similarity index 100% rename from autosolvate/tests/refs/multicomponent/napthalene-acetonitrile-water.pdb rename to autosolvate/tests/refs/multicomponent/naphthalene-acetonitrile-water.pdb diff --git a/autosolvate/tests/refs/multicomponent/napthalene-acetonitrile-water.prmtop b/autosolvate/tests/refs/multicomponent/naphthalene-acetonitrile-water.prmtop similarity index 100% rename from autosolvate/tests/refs/multicomponent/napthalene-acetonitrile-water.prmtop rename to autosolvate/tests/refs/multicomponent/naphthalene-acetonitrile-water.prmtop diff --git a/autosolvate/tests/test_multicomponent.py b/autosolvate/tests/test_multicomponent.py index 4ba4cbf..07f5f49 100644 --- a/autosolvate/tests/test_multicomponent.py +++ b/autosolvate/tests/test_multicomponent.py @@ -93,12 +93,10 @@ def test_multicomponent(tmpdir): assert path_exist assert hp.compare_pdb(f"{inpfname}.pdb", hp.get_reference_dir(f"multicomponent/{inpfname}-processed.pdb")) - - def test_mixture_builder(): test_name = "test_mixture_builder" builder = MixtureBuilder(folder=os.getcwd()) - builder.add_solute(hp.get_input_dir("naphthalene_neutral.xyz"), name="napthalene", residue_name="NAP", charge=0, spinmult=1, number=1) + builder.add_solute(hp.get_input_dir("naphthalene_neutral.xyz"), name="naphthalene", residue_name="NAP", charge=0, spinmult=1, number=1) # predefined solvents do not need to specify the xyz file. builder.add_solvent(name="acetonitrile", residue_name="C3N", charge=0, spinmult=1, number=200) @@ -106,24 +104,51 @@ def test_mixture_builder(): builder.build() solute_path_exist = True - solute_path_exist *= os.path.exists("napthalene.mol2") - solute_path_exist *= os.path.exists("napthalene.frcmod") + solute_path_exist *= os.path.exists("naphthalene.mol2") + solute_path_exist *= os.path.exists("naphthalene.frcmod") + assert solute_path_exist + + # predefined solvent will not generate the mol2 and frcmod files. + path_exist = True + path_exist *= os.path.exists("naphthalene-acetonitrile-water.pdb") + path_exist *= os.path.exists("naphthalene-acetonitrile-water.prmtop") + path_exist *= os.path.exists("naphthalene-acetonitrile-water.inpcrd") + + assert path_exist + assert hp.compare_pdb( + "naphthalene-acetonitrile-water.pdb", + hp.get_reference_dir(f"multicomponent/naphthalene-acetonitrile-water.pdb"), + threshold = np.inf, # I set it to inf because packmol has some randomness in the output. This function will check the number of atoms and residues. + ) + assert hp.compare_inpcrd_prmtop( + "naphthalene-acetonitrile-water.prmtop", + 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")) + + solute_path_exist = True + solute_path_exist *= os.path.exists("naphthalene.mol2") + solute_path_exist *= os.path.exists("naphthalene.frcmod") assert solute_path_exist # predefined solvent will not generate the mol2 and frcmod files. path_exist = True - path_exist *= os.path.exists("napthalene-acetonitrile-water.pdb") - path_exist *= os.path.exists("napthalene-acetonitrile-water.prmtop") - path_exist *= os.path.exists("napthalene-acetonitrile-water.inpcrd") + path_exist *= os.path.exists("naphthalene-acetonitrile-water.pdb") + path_exist *= os.path.exists("naphthalene-acetonitrile-water.prmtop") + path_exist *= os.path.exists("naphthalene-acetonitrile-water.inpcrd") assert path_exist assert hp.compare_pdb( - "napthalene-acetonitrile-water.pdb", - hp.get_reference_dir(f"multicomponent/napthalene-acetonitrile-water.pdb"), + "naphthalene-acetonitrile-water.pdb", + hp.get_reference_dir(f"multicomponent/naphthalene-acetonitrile-water.pdb"), threshold = np.inf, # I set it to inf because packmol has some randomness in the output. This function will check the number of atoms and residues. ) assert hp.compare_inpcrd_prmtop( - "napthalene-acetonitrile-water.prmtop", - hp.get_reference_dir(f"multicomponent/napthalene-acetonitrile-water.prmtop"), + "naphthalene-acetonitrile-water.prmtop", + 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. - ) \ No newline at end of file + ) diff --git a/autosolvate/utils/inputparser.py b/autosolvate/utils/inputparser.py index 78373bd..2acf421 100644 --- a/autosolvate/utils/inputparser.py +++ b/autosolvate/utils/inputparser.py @@ -1,29 +1,11 @@ import os import re +import json import shutil -def parse_multisolvent_input(filepath:str): - """ - Parse the input file for defining the multisolvent system - - Parameters - ---------- - filepath : str - The path to the input file. - - Returns - ------- - dict - The dictionary containing the parsed input. - """ - solvent_input = {} - with open(filepath, "r") as f: - for line in f: - args = line.strip().split() - solvent_dict = {} - solvent_dict["xyzpath"] = args[0] - solvent_dict["number"] = int(args[1]) - solvent_dict[""] - solvent_dict["charge"] = int(args[2]) - solvent_dict["spinmult"] = int(args[3]) \ No newline at end of file + +def parse_json_input(jsonpath:str): + with open(jsonpath, "r") as f: + data = json.load(f) + \ No newline at end of file diff --git a/autosolvate/utils/tools.py b/autosolvate/utils/tools.py index 1a88e80..7fb131e 100644 --- a/autosolvate/utils/tools.py +++ b/autosolvate/utils/tools.py @@ -347,24 +347,34 @@ def edit_system_pdb(box: object) -> None: f.write(line) f.close() -def prep2pdb4amber_solvent(mol: object) -> None: +def prep2pdb(mol: object) -> None: r''' - Output a pdb file for a amber predefined solvent (methanol, chloroform, nma) + Output a pdb file from a prep file using tleap ''' outputfolder = mol.folder - resname = mol.residue_name - pdbname = mol.name + '.pdb' pdbpath = mol.reference_name + '.pdb' if not os.path.exists(outputfolder): os.makedirs(outputfolder) if os.path.exists(pdbpath) and os.path.isfile(pdbpath): os.remove(pdbpath) + residue_name = extract_residue_name_from_prep(mol.prep) with open(os.path.join(mol.folder, "leap_convert.cmd"), "w") as f: f.write(f"loadAmberPrep {mol.prep}\n") - f.write(f"singlemol = combine { {mol.residue_name} }\n") - f.write(f"savepdb singlemol {pdbpath}\n") + f.write(f"savepdb {residue_name} {pdbpath}\n") os.system(f"tleap -f {os.path.join(mol.folder, 'leap_convert.cmd')}") mol.pdb = pdbpath + +def prep2pdb_withexactpath(preppath:str, pdbpath:str, resname:str="MOL") -> None: + r''' + Output a pdb file from a prep file using tleap + ''' + if os.path.exists(pdbpath) and os.path.isfile(pdbpath): + os.remove(pdbpath) + residue_name = extract_residue_name_from_prep(preppath) + with open("leap_convert.cmd", "w") as f: + f.write(f"loadAmberPrep {preppath}\n") + f.write(f"savepdb {residue_name} {pdbpath}\n") + os.system("tleap -f leap_convert.cmd") def assign_water_pdb(mol: object) -> None: '''assign a reference pdb file for water''' @@ -386,7 +396,7 @@ def assign_water_pdb(mol: object) -> None: mol.pdb = pdbpath def get_residue_name_from_pdb(file_path:str) -> str: - residue_name = None + residue_name = "" with open(file_path, 'r') as file: for line in file: if line.startswith('ATOM') or line.startswith('HETATM'): @@ -395,7 +405,31 @@ 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 + + + + # other functions +def extract_residue_name_from_prep(prep_file:str) -> str: + """ChatGPT按照ch3cn.prep文件的格式写的,不保证通用性,可能有问题""" + with open(prep_file, 'r') as file: + lines = file.readlines() + + for line in lines: + if "INT" in line: + parts = line.split() + return parts[0] + return "MOL" + def process_system_name(name:str, xyzfile:str, support_input_format:Iterable[str] = ("xyz", "pdb", "mol2", "prep", "off", "lib"), check_exist = True): if not os.path.isfile(xyzfile) and check_exist: raise ValueError("The input file {:s} does not exist".format(xyzfile)) @@ -473,7 +507,7 @@ def try_ones_best_to_get_residue_name(self, xyzfile:str, name:str): if len(xyzbasename) >= 3: residue_name = xyzbasename[:3].upper() elif len(xyzbasename) < 3: - residue_name = "SLU" + residue_name = "MOL" return residue_name diff --git a/inputtemplate.json b/inputtemplate.json new file mode 100644 index 0000000..151611d --- /dev/null +++ b/inputtemplate.json @@ -0,0 +1,72 @@ +{ + "cubesize": 20, + "charge_method": "bcc", + "solute": { + "xyzfile": "naphtalene.xyz", + "charge": 0, + "spinmult": 1 + }, + "solute_multicomponent": { + "xyz": "PAHs.xyz" + }, + "solute_ionpair": { + "pdb": "ionpair.pdb", + "fragment_charge":{ + "SUF": -2, + "TPA": 1 + }, + "fragment_spinmult":{ + "SUF": 1, + "TPA": 1 + } + }, + "solvent_amber_water": { + "name": "water" + }, + "solvent_amber_methanol": { + "name": "methanol" + }, + "solvent_autosolvate_prepared": { + "name": "acetonitrile" + }, + "solvents": [ + { + "name": "water", + "number": 100 + }, + { + "name": "methanol", + "number": 100 + }, + { + "name": "acetonitrile", + "number": 100 + }, + { + "mol2": "toluene.mol2", + "frcmod": "toluene.frcmod", + "number": 100 + }, + { + "prep": "chcl3.prep", + "frcmod": "chcl3.frcmod", + "number": 100 + }, + { + "xyz": "toluene.xyz", + "charge": 0, + "spinmult": 1, + "number": 100 + }, + { + "xyz": "BMIM.pdb", + "charge": 1, + "spinmult": 1, + "number": 100 + }, + { + "itp": "BMIM.itp", + "number": 100 + } + ] +} \ No newline at end of file diff --git a/mixturebuilder_input2.json b/mixturebuilder_input2.json new file mode 100644 index 0000000..fa9390f --- /dev/null +++ b/mixturebuilder_input2.json @@ -0,0 +1,26 @@ +{ + "cube_size": 30, + "charge_method": "bcc", + "solute": { + "xyzfile": "/home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs/ionpair.pdb", + "fragment_charge": { + "SUF": -2, + "TPA": 1 + } + }, + "solvents":[ + { + "name": "acetonitrile", + "number": 150, + "frcmod": "/home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs/acetonitrile.frcmod", + "prep": "/home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs/acetonitrile.prep", + "residue_name": "C3N" + }, + { + "name": "toluene", + "number": 150, + "xyzfile": "/home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs/toluene.pdb", + "residue_name": "TOL" + } + ] +} \ No newline at end of file diff --git a/test_water_acn.ipynb b/test_water_acn.ipynb index c09de38..7af1668 100644 --- a/test_water_acn.ipynb +++ b/test_water_acn.ipynb @@ -24,7 +24,6 @@ "name": "stderr", "output_type": "stream", "text": [ - "1 molecule converted\n", "1 molecule converted\n" ] } @@ -59,6 +58,106 @@ ")\n", "builder.build()" ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "from autosolvate.autosolvate import *\n", + "from autosolvate.multicomponent import *\n", + "import os \n", + "ppath = \"/home/fren5/AutoSolvate-update/AutoSolvate\" \n", + "os.chdir(ppath)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "23:13:54 CheckExecutable INFO: Fragment 0 is a new molecule with res name SUF. Update the term list.\n", + "23:13:54 CheckExecutable INFO: Fragment 1 is a new molecule with res name TPA. Update the term list.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "23:13:56 CheckExecutable INFO: All fragments: SUF TPA\n", + "23:13:56 CheckExecutable INFO: New fragments: SUF TPA\n", + "23:13:56 CheckExecutable WARNING: All multiplicities are set to 1\n", + "23:13:56 CheckExecutable INFO: SUF corresponds to a new fragment.\n", + "23:13:56 CheckExecutable INFO: Set charge for SUF to -2\n", + "23:13:56 CheckExecutable INFO: Set multiplicity for SUF to 1\n", + "23:13:56 CheckExecutable INFO: TPA corresponds to a new fragment.\n", + "23:13:56 CheckExecutable INFO: Set charge for TPA to 1\n", + "23:13:56 CheckExecutable INFO: Set multiplicity for TPA to 1\n", + "23:13:56 CheckExecutable INFO: Net charge of the molecule is -1\n", + "23:13:56 CheckExecutable INFO: Total multiplicity of the molecule is 1\n", + "23:13:56 CheckExecutable INFO: Create Molecule object for fragment SUF\n", + "23:13:56 CheckExecutable INFO: Create Molecule object for fragment TPA\n", + "23:14:07 CheckExecutable INFO: Before being passed to tleap, the atom label in the original pdb should be updated.\n", + "23:14:07 CheckExecutable INFO: original pdb: /home/fren5/AutoSolvate-update/AutoSolvate/mixture-test-2/ionpair.pdb\n", + "23:14:07 CheckExecutable INFO: Start to update the atom label to standard amber format\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/prep to search path.\n", + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/lib to search path.\n", + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/parm to search path.\n", + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/cmd to search path.\n", + "-f: Source /home/fren5/AutoSolvate-update/AutoSolvate/mixture-test-2/leap_convert.cmd.\n", + "\n", + "Welcome to LEaP!\n", + "(no leaprc in search path)\n", + "Sourcing: /home/fren5/AutoSolvate-update/AutoSolvate/mixture-test-2/leap_convert.cmd\n", + "Loading Prep file: /home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs/acetonitrile.prep\n", + "Writing pdb file: /home/fren5/AutoSolvate-update/AutoSolvate/mixture-test-2/acetonitrile.pdb\n", + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/prep to search path.\n", + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/lib to search path.\n", + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/parm to search path.\n", + "-I: Adding /home/fren5/psi4conda/envs/autosolvate/dat/leap/cmd to search path.\n", + "-f: Source leap_convert.cmd.\n", + "\n", + "Welcome to LEaP!\n", + "(no leaprc in search path)\n", + "Sourcing: ./leap_convert.cmd\n", + "Loading Prep file: /home/fren5/AutoSolvate-update/AutoSolvate/autosolvate/tests/inputs/acetonitrile.prep\n", + "Writing pdb file: /home/fren5/AutoSolvate-update/AutoSolvate/mixture-test-2/acetonitrile-fromprep.pdb\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "*** Error: tl_getline(): not interactive, use stdio.\n", + "\n", + "*** Error: tl_getline(): not interactive, use stdio.\n" + ] + } + ], + "source": [ + "nebpath = \"/home/fren5/AutoSolvate-update/AutoSolvate/mixture-test-2\" \n", + "if not os.path.exists(nebpath):\n", + " os.makedirs(nebpath)\n", + "os.chdir(nebpath)\n", + "from autosolvate.multicomponent import startmulticomponent_fromfile\n", + "startmulticomponent_fromfile(\n", + " \"/home/fren5/AutoSolvate-update/AutoSolvate/mixturebuilder_input2.json\",\n", + ")\n" + ] } ], "metadata": {