def formalCharge(molecule): """Compute the formal charge on a molecule. This function requires that the molecule has explicit hydrogen atoms. Parameters ---------- molecule : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>` A molecule object. Returns ------- formal_charge : :class:`Charge <BioSimSpace.Types.Charge>` The total formal charge on the molecule. """ if type(molecule) is not _Molecule: raise TypeError( "'molecule' must be of type 'BioSimSpace._SireWrappers.Molecule'") from rdkit import Chem as _Chem # Create a temporary working directory. tmp_dir = _tempfile.TemporaryDirectory() work_dir = tmp_dir.name # Zero the total formal charge. formal_charge = 0 # Stdout/stderr redirection doesn't work from within Jupyter. if _is_notebook: # Run in the working directory. with _Utils.cd(work_dir): # Save the molecule to a PDB file. _IO.saveMolecules("tmp", molecule, "PDB") # Read the ligand PDB into an RDKit molecule. mol = _Chem.MolFromPDBFile("tmp.pdb") # Compute the formal charge. formal_charge = _Chem.rdmolops.GetFormalCharge(mol) else: # Run in the working directory and redirect stderr from RDKit. with _Utils.cd(work_dir), _Utils.stderr_redirected(): # Save the molecule to a PDB file. _IO.saveMolecules("tmp", molecule, "PDB") # Read the ligand PDB into an RDKit molecule. mol = _Chem.MolFromPDBFile("tmp.pdb") # Compute the formal charge. formal_charge = _Chem.rdmolops.GetFormalCharge(mol) return formal_charge * _electron_charge
def formalCharge(molecule): """Compute the formal charge on a molecule. This function requires that the molecule has explicit hydrogen atoms. Parameters ---------- molecule : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>` A molecule object. Returns ------- formal_charge : :class:`Charge <BioSimSpace.Types.Charge>` The total formal charge on the molecule. """ if type(molecule) is not _Molecule: raise TypeError("'molecule' must be of type 'BioSimSpace._SireWrappers.Molecule'") from rdkit import Chem as _Chem from rdkit import RDLogger as _RDLogger # Disable RDKit warnings. _RDLogger.DisableLog('rdApp.*') # Create a temporary working directory. tmp_dir = _tempfile.TemporaryDirectory() work_dir = tmp_dir.name # Zero the total formal charge. formal_charge = 0 # Run in the working directory. with _Utils.cd(work_dir): # Save the molecule to a PDB file. _IO.saveMolecules("tmp", molecule, "PDB") # Read the ligand PDB into an RDKit molecule. mol = _Chem.MolFromPDBFile("tmp.pdb") # Compute the formal charge. formal_charge = _Chem.rdmolops.GetFormalCharge(mol) return formal_charge * _electron_charge
def matchAtoms(molecule0, molecule1, scoring_function="rmsd_align", matches=1, return_scores=False, prematch={}, timeout=5 * _Units.Time.second, property_map0={}, property_map1={}): """Find mappings between atom indices in molecule0 to those in molecule1. Molecules are aligned using a Maximum Common Substructure (MCS) search. When requesting more than one match, the mappings will be sorted using a scoring function and returned in order of best to worst score. (Note that, depending on the scoring function the "best" score may have the lowest value.) Parameters ---------- molecule0 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>` The molecule of interest. molecule1 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>` The reference molecule. scoring_function : str The scoring function used to match atoms. Available options are: - "rmsd" Calculate the root mean squared distance between the coordinates of atoms in molecule0 to those that they map to in molecule1. - "rmsd_align" Align molecule0 to molecule1 based on the mapping before computing the above RMSD score. - "rmsd_flex_align" Flexibly align molecule0 to molecule1 based on the mapping before computing the above RMSD score. (Requires the 'fkcombu'. package: http://strcomp.protein.osaka-u.ac.jp/kcombu) matches : int The maximum number of matches to return. (Sorted in order of score). return_scores : bool Whether to return a list containing the scores for each mapping. prematch : dict A dictionary of atom mappings that must be included in the match. timeout : BioSimSpace.Types.Time The timeout for the maximum common substructure search. property_map0 : dict A dictionary that maps "properties" in molecule0 to their user defined values. This allows the user to refer to properties with their own naming scheme, e.g. { "charge" : "my-charge" } property_map1 : dict A dictionary that maps "properties" in molecule1 to their user defined values. Returns ------- matches : dict, [dict], ([dict], list) The best atom mapping, a list containing a user specified number of the best mappings ranked by their score, or a tuple containing the list of best mappings and a list of the corresponding scores. Examples -------- Find the best maximum common substructure mapping between two molecules. >>> import BioSimSpace as BSS >>> mapping = BSS.Align.matchAtoms(molecule0, molecule1) Find the 5 best mappings. >>> import BioSimSpace as BSS >>> mappings = BSS.Align.matchAtoms(molecule0, molecule1, matches=5) Find the 5 best mappings along with their ranking scores. >>> import BioSimSpace as BSS >>> mappings, scores = BSS.Align.matchAtoms(molecule0, molecule1, matches=5, return_scores=True) Find the 5 best mappings along with their ranking scores. Score by flexibly aligning molecule0 to molecule1 based on each mapping and computing the root mean squared displacement of the matched atoms. >>> import BioSimSpace as BSS >>> mappings, scores = BSS.Align.matchAtoms(molecule0, molecule1, matches=5, return_scores=True, scoring_function="rmsd_flex_align") Find the best mapping that contains a prematch (this is a dictionary mapping atom indices in molecule0 to those in molecule1). >>> import BioSimSpace as BSS >>> mapping = BSS.Align.matchAtoms(molecule0, molecule1, prematch={0 : 10, 3 : 7}) """ # A list of supported scoring functions. scoring_functions = ["RMSD", "RMSDALIGN", "RMSDFLEXALIGN"] # Validate input. if type(molecule0) is not _Molecule: raise TypeError( "'molecule0' must be of type 'BioSimSpace._SireWrappers.Molecule'") if type(molecule1) is not _Molecule: raise TypeError( "'molecule1' must be of type 'BioSimSpace._SireWrappers.Molecule'") if type(scoring_function) is not str: raise TypeError("'scoring_function' must be of type 'str'") else: # Strip underscores and whitespace, then convert to upper case. _scoring_function = scoring_function.replace("_", "").upper() _scoring_function = _scoring_function.replace(" ", "").upper() if not _scoring_function in scoring_functions: raise ValueError( "Unsupported scoring function '%s'. Options are: %s" % (scoring_function, scoring_functions)) if _scoring_function == "RMSDFLEXALIGN" and _fkcombu_exe is None: raise _MissingSoftwareError( "'rmsd_flex_align' option requires the 'fkcombu' program: " "http://strcomp.protein.osaka-u.ac.jp/kcombu") if type(matches) is not int: raise TypeError("'matches' must be of type 'int'") else: if matches < 0: raise ValueError("'matches' must be positive!") if type(return_scores) is not bool: raise TypeError("'return_matches' must be of type 'bool'") if type(prematch) is not dict: raise TypeError("'prematch' must be of type 'dict'") else: _validate_mapping(molecule0, molecule1, prematch, "prematch") if type(timeout) is not _Units.Time._Time: raise TypeError("'timeout' must be of type 'BioSimSpace.Types.Time'") if type(property_map0) is not dict: raise TypeError("'property_map0' must be of type 'dict'") if type(property_map1) is not dict: raise TypeError("'property_map1' must be of type 'dict'") # Extract the Sire molecule from each BioSimSpace molecule. mol0 = molecule0._getSireObject() mol1 = molecule1._getSireObject() # Convert the timeout to seconds and take the magnitude as an integer. timeout = int(timeout.seconds().magnitude()) # Create a temporary working directory. tmp_dir = _tempfile.TemporaryDirectory() work_dir = tmp_dir.name # Use RDKkit to find the maximum common substructure. try: # Run inside a temporary directory. with _Utils.cd(work_dir): # Write both molecules to PDB files. _IO.saveMolecules("tmp0", molecule0, "PDB", property_map=property_map0) _IO.saveMolecules("tmp1", molecule1, "PDB", property_map=property_map1) # Load the molecules with RDKit. # Note that the C++ function overloading seems to be broken, so we # need to pass all arguments by position, rather than keyword. # The arguments are: "filename", "sanitize", "removeHs", "flavor" mols = [ _Chem.MolFromPDBFile("tmp0.pdb", False, False, 0), _Chem.MolFromPDBFile("tmp1.pdb", False, False, 0) ] # Generate the MCS match. mcs = _rdFMCS.FindMCS(mols, atomCompare=_rdFMCS.AtomCompare.CompareAny, bondCompare=_rdFMCS.BondCompare.CompareAny, completeRingsOnly=True, ringMatchesRingOnly=True, matchChiralTag=False, matchValences=False, maximizeBonds=False, timeout=timeout) # Get the common substructure as a SMARTS string. mcs_smarts = _Chem.MolFromSmarts(mcs.smartsString) except: raise RuntimeError("RDKIT MCS mapping failed!") # Score the mappings and return them in sorted order (best to worst). mappings, scores = _score_rdkit_mappings(mol0, mol1, mols[0], mols[1], mcs_smarts, prematch, _scoring_function, property_map0, property_map1) # Sometimes RDKit fails to generate a mapping that includes the prematch. # If so, then try generating a mapping using the MCS routine from Sire. if len(mappings) == 1 and mappings[0] == prematch: # Convert timeout to a Sire Unit. timeout = timeout * _SireUnits.second # Regular match. Include light atoms, but don't allow matches between heavy # and light atoms. m0 = mol0.evaluate().findMCSmatches( mol1, _SireMol.AtomResultMatcher(_to_sire_mapping(prematch)), timeout, True, property_map0, property_map1, 6, False) # Include light atoms, and allow matches between heavy and light atoms. # This captures mappings such as O --> H in methane to methanol. m1 = mol0.evaluate().findMCSmatches( mol1, _SireMol.AtomResultMatcher(_to_sire_mapping(prematch)), timeout, True, property_map0, property_map1, 0, False) # Take the mapping with the larger number of matches. if len(m1) > 0: if len(m0) > 0: if len(m1[0]) > len(m0[0]): mappings = m1 else: mappings = m0 else: mappings = m1 else: mappings = m0 # Score the mappings and return them in sorted order (best to worst). mappings, scores = _score_sire_mappings(mol0, mol1, mappings, prematch, _scoring_function, property_map0, property_map1) if matches == 1: if return_scores: return (mappings[0], scores[0]) else: return mappings[0] else: # Return a list of matches from best to worst. if return_scores: return (mappings[0:matches], scores[0:matches]) # Return a tuple containing the list of matches from best to # worst along with the list of scores. else: return mappings[0:matches]
def flexAlign(molecule0, molecule1, mapping=None, fkcombu_exe=None, property_map0={}, property_map1={}): """Flexibly align atoms in molecule0 to those in molecule1 using the mapping between matched atom indices. Parameters ---------- molecule0 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>` The molecule to align. molecule1 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>` The reference molecule. mapping : dict A dictionary mapping atoms in molecule0 to those in molecule1. fkcombu_exe : str Path to the fkcombu executable. If None is passed, then BioSimSpace will attempt to find fkcombu by searching your PATH. property_map0 : dict A dictionary that maps "properties" in molecule0 to their user defined values. This allows the user to refer to properties with their own naming scheme, e.g. { "charge" : "my-charge" } property_map1 : dict A dictionary that maps "properties" in molecule1 to their user defined values. Returns ------- molecule : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>` The aligned molecule. Examples -------- Align molecule0 to molecule1 based on a precomputed mapping. >>> import BioSimSpace as BSS >>> molecule0 = BSS.Align.flexAlign(molecule0, molecule1, mapping) Align molecule0 to molecule1. Since no mapping is passed one will be autogenerated using :class:`matchAtoms <BioSimSpace.Align.matchAtoms>` with default options. >>> import BioSimSpace as BSS >>> molecule0 = BSS.Align.flexAlign(molecule0, molecule1) """ # Check that we found fkcombu in the PATH. if fkcombu_exe is None: if _fkcombu_exe is None: raise _MissingSoftwareError( "'BioSimSpace.Align.flexAlign' requires the 'fkcombu' program: " "http://strcomp.protein.osaka-u.ac.jp/kcombu") else: fkcombu_exe = _fkcombu_exe # Check that the user supplied executable exists. else: if not _os.path.isfile(fkcombu_exe): raise IOError("'fkcombu' executable doesn't exist: '%s'" % fkcombu_exe) if type(molecule0) is not _Molecule: raise TypeError( "'molecule0' must be of type 'BioSimSpace._SireWrappers.Molecule'") if type(molecule1) is not _Molecule: raise TypeError( "'molecule1' must be of type 'BioSimSpace._SireWrappers.Molecule'") if type(property_map0) is not dict: raise TypeError("'property_map0' must be of type 'dict'") if type(property_map1) is not dict: raise TypeError("'property_map1' must be of type 'dict'") # The user has passed an atom mapping. if mapping is not None: if type(mapping) is not dict: raise TypeError("'mapping' must be of type 'dict'.") else: _validate_mapping(molecule0, molecule1, mapping, "mapping") # Get the best match atom mapping. else: mapping = matchAtoms(molecule0, molecule1, property_map0=property_map0, property_map1=property_map1) # Convert the mapping to AtomIdx key:value pairs. sire_mapping = _to_sire_mapping(mapping) # Create a temporary working directory. tmp_dir = _tempfile.TemporaryDirectory() work_dir = tmp_dir.name # Execute in the working directory. with _Utils.cd(work_dir): # Write the two molecules to PDB files. _IO.saveMolecules("molecule0", molecule0, "PDB", property_map=property_map0) _IO.saveMolecules("molecule1", molecule1, "PDB", property_map=property_map1) # Write the mapping to text. (Increment indices by one). with open("mapping.txt", "w") as file: for idx0, idx1 in sire_mapping.items(): file.write("%d %d\n" % (idx0.value() + 1, idx1.value() + 1)) # Create the fkcombu command string. command = "%s -T molecule0.pdb -R molecule1.pdb -alg F -iam mapping.txt -opdbT aligned.pdb" % fkcombu_exe # Run the command as a subprocess. proc = _subprocess.run(command, shell=True, stdout=_subprocess.PIPE, stderr=_subprocess.PIPE) # Check that the output file exists. if not _os.path.isfile("aligned.pdb"): raise _AlignmentError( "Failed to align molecules based on mapping: %r" % mapping) from None # Load the aligned molecule. aligned = _IO.readMolecules("aligned.pdb")[0] # Get the "coordinates" property for molecule0. prop = property_map0.get("coordinates", "coordinates") # Copy the coordinates back into the original molecule. molecule0._sire_object = molecule0._sire_object.edit() \ .setProperty(prop, aligned._sire_object.property("coordinates")).commit() # Return the aligned molecule. return _Molecule(molecule0)
def _solvate(molecule, box, shell, model, num_point, ion_conc, is_neutral, work_dir=None, property_map={}): """Internal function to add solvent using 'gmx solvate'. Parameters ---------- molecule : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`, \ :class:`System <BioSimSpace._SireWrappers.System>` A molecule, or system of molecules. box : [:class:`Length <BioSimSpace.Types.Length>`] A list containing the box size in each dimension. shell : :class:`Length` <BioSimSpace.Types.Length>` Thickness of the water shell around the solute. model : str The name of the water model. num_point : int The number of atoms in the water model. ion_conc : float The ion concentration in (mol per litre). is_neutral : bool Whether to neutralise the system. work_dir : str The working directory for the process. property_map : dict A dictionary that maps system "properties" to their user defined values. This allows the user to refer to properties with their own naming scheme, e.g. { "charge" : "my-charge" } Returns ------- system : :class:`System <BioSimSpace._SireWrappers.System>` The solvated system. """ if molecule is not None: # Store the centre of the molecule. center = molecule._getAABox(property_map).center() # Work out the vector from the centre of the molecule to the centre of the # water box, converting the distance in each direction to Angstroms. vec = [] for x, y in zip(box, center): vec.append(0.5 * x.angstroms().magnitude() - y) # Translate the molecule. This allows us to create a water box # around the molecule. molecule.translate(vec, property_map) if type(molecule) is _System: # Reformat all of the water molecules so that they match the # expected GROMACS topology template. waters = _SireIO.setGromacsWater( molecule._sire_object.search("water"), model) # Convert to a BioSimSpace molecules container. waters = _Molecules(waters.toMolecules()) # Remove the old water molecules then add those with the updated topology. molecule.removeWaterMolecules() molecule.addMolecules(waters) # Create a temporary working directory and store the directory name. if work_dir is None: tmp_dir = _tempfile.TemporaryDirectory() work_dir = tmp_dir.name # Run the solvation in the working directory. with _Utils.cd(work_dir): # Create the gmx command. if num_point == 3: mod = "spc216" else: mod = model command = "%s solvate -cs %s" % (_gmx_exe, mod) if molecule is not None: # Write the molecule/system to a GRO files. _IO.saveMolecules("input", molecule, "gro87") _os.rename("input.gro87", "input.gro") # Update the command. command += " -cp input.gro" # Add the box information. if box is not None: command += " -box %f %f %f" % (box[0].nanometers().magnitude(), box[1].nanometers().magnitude(), box[2].nanometers().magnitude()) # Add the shell information. if shell is not None: command += " -shell %f" % shell.nanometers().magnitude() # Just add box information. else: command += " -box %f %f %f" % (box[0].nanometers().magnitude(), box[1].nanometers().magnitude(), box[2].nanometers().magnitude()) # Add the output file. command += " -o output.gro" with open("README.txt", "w") as file: # Write the command to file. file.write("# gmx solvate was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open("solvate.out", "w") stderr = open("solvate.err", "w") # Run gmx solvate as a subprocess. proc = _subprocess.run(command, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # gmx doesn't return sensible error codes, so we need to check that # the expected output was generated. if not _os.path.isfile("output.gro"): raise RuntimeError("'gmx solvate failed to generate output!") # Extract the water lines from the GRO file. water_lines = [] with open("output.gro", "r") as file: for line in file: if _re.search("SOL", line): # Store the SOL atom record. water_lines.append(line) # Add any box information. This is the last line in the GRO file. water_lines.append(line) # Write a GRO file that contains only the water atoms. if len(water_lines) - 1 > 0: with open("water.gro", "w") as file: file.write("BioSimSpace %s water box\n" % model.upper()) file.write("%d\n" % (len(water_lines) - 1)) for line in water_lines: file.write("%s" % line) else: raise ValueError( "No water molecules were generated. Try increasing " "the 'box' size or 'shell' thickness.") # Create a TOP file for the water model. By default we use the Amber03 # force field to generate a dummy topology for the water model. with open("water_ions.top", "w") as file: file.write("#define FLEXIBLE 1\n\n") file.write("; Include AmberO3 force field\n") file.write('#include "amber03.ff/forcefield.itp"\n\n') file.write("; Include %s water topology\n" % model.upper()) file.write('#include "amber03.ff/%s.itp"\n\n' % model) file.write("; Include ions\n") file.write('#include "amber03.ff/ions.itp"\n\n') file.write("[ system ] \n") file.write("BioSimSpace %s water box\n\n" % model.upper()) file.write("[ molecules ] \n") file.write(";molecule name nr.\n") file.write("SOL %d\n" % ((len(water_lines) - 1) / num_point)) # Load the water box. water = _IO.readMolecules(["water.gro", "water_ions.top"]) # Create a new system by adding the water to the original molecule. if molecule is not None: # Translate the molecule and water back to the original position. vec = [-x for x in vec] molecule.translate(vec, property_map) water.translate(vec) if type(molecule) is _System: # Extract the non-water molecules from the original system. non_waters = _Molecules( molecule.search("not water")._sire_object.toMolecules()) # Create a system by adding these to the water molecules from # gmx solvate, which will include the original waters. system = non_waters.toSystem() + water else: system = molecule.toSystem() + water # Add all of the water box properties to the new system. for prop in water._sire_object.propertyKeys(): prop = property_map.get(prop, prop) # Add the space property from the water system. system._sire_object.setProperty( prop, water._sire_object.property(prop)) else: system = water # Now we add ions to the system and neutralise the charge. if ion_conc > 0 or is_neutral: # Write the molecule + water system to file. _IO.saveMolecules("solvated", system, "gro87") _IO.saveMolecules("solvated", system, "grotop") _os.rename("solvated.gro87", "solvated.gro") _os.rename("solvated.grotop", "solvated.top") # First write an mdp file. with open("ions.mdp", "w") as file: file.write("; Neighbour searching\n") file.write("cutoff-scheme = Verlet\n") file.write("rlist = 1.1\n") file.write("pbc = xyz\n") file.write("verlet-buffer-tolerance = -1\n") file.write("\n; Electrostatics\n") file.write("coulombtype = cut-off\n") file.write("\n; VdW\n") file.write("rvdw = 1.0\n") # Create the grompp command. command = "%s grompp -f ions.mdp -po ions.out.mdp -c solvated.gro -p solvated.top -o ions.tpr" % _gmx_exe with open("README.txt", "a") as file: # Write the command to file. file.write( "\n# gmx grompp was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open("grommp.out", "w") stderr = open("grommp.err", "w") # Run grompp as a subprocess. proc = _subprocess.run(command, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # Flag whether to break out of the ion adding stage. is_break = False # Check for the tpr output file. if not _os.path.isfile("ions.tpr"): if shell is None: raise RuntimeError( "'gmx grommp' failed to generate output! " "Perhaps your box is too small?") else: is_break = True _warnings.warn( "Unable to achieve target ion concentration, try using " "'box' option instead of 'shell'.") # Only continue if grommp was successful. This allows us to skip the remainder # of the code if the ion addition failed when the 'shell' option was chosen, i.e. # because the estimated simulation box was too small. if not is_break: is_break = False # The ion concentration is unset. if ion_conc == 0: # Get the current molecular charge. charge = system.charge() # Round to the nearest integer value. charge = round(charge.magnitude()) # Create the genion command. command = "echo SOL | %s genion -s ions.tpr -o solvated_ions.gro -p solvated.top -neutral" % _gmx_exe # Add enough counter ions to neutralise the charge. if charge > 0: command += " -nn %d" % abs(charge) else: command += " -np %d" % abs(charge) else: # Create the genion command. command = "echo SOL | %s genion -s ions.tpr -o solvated_ions.gro -p solvated.top -%s -conc %f" \ % (_gmx_exe, "neutral" if is_neutral else "noneutral", ion_conc) with open("README.txt", "a") as file: # Write the command to file. file.write( "\n# gmx genion was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open("genion.out", "w") stderr = open("genion.err", "w") # Run genion as a subprocess. proc = _subprocess.run(command, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # Check for the output GRO file. if not _os.path.isfile("solvated_ions.gro"): if shell is None: raise RuntimeError( "'gmx genion' failed to add ions! Perhaps your box is too small?" ) else: is_break = True _warnings.warn( "Unable to achieve target ion concentration, try using " "'box' option instead of 'shell'.") if not is_break: # Counters for the number of SOL, NA, and CL atoms. num_sol = 0 num_na = 0 num_cl = 0 # We now need to loop through the GRO file to extract the lines # corresponding to water or ion atoms. water_ion_lines = [] with open("solvated_ions.gro", "r") as file: for line in file: # This is a Sodium atom. if _re.search("NA", line): water_ion_lines.append(line) num_na += 1 # This is a Chlorine atom. if _re.search("CL", line): water_ion_lines.append(line) num_cl += 1 # This is a water atom. elif _re.search("SOL", line): water_ion_lines.append(line) num_sol += 1 # Add any box information. This is the last line in the GRO file. water_ion_lines.append(line) # Write a GRO file that contains only the water and ion atoms. if len(water_ion_lines) - 1 > 0: with open("water_ions.gro", "w") as file: file.write("BioSimSpace %s water box\n" % model.upper()) file.write("%d\n" % (len(water_ion_lines) - 1)) for line in water_ion_lines: file.write("%s" % line) # Ions have been added. Update the TOP file fo the water model # with the new atom counts. if num_na > 0 or num_cl > 0: with open("water_ions.top", "w") as file: file.write("#define FLEXIBLE 1\n\n") file.write("; Include AmberO3 force field\n") file.write( '#include "amber03.ff/forcefield.itp"\n\n') file.write("; Include %s water topology\n" % model.upper()) file.write('#include "amber03.ff/%s.itp"\n\n' % model) file.write("; Include ions\n") file.write('#include "amber03.ff/ions.itp"\n\n') file.write("[ system ] \n") file.write("BioSimSpace %s water box\n\n" % model.upper()) file.write("[ molecules ] \n") file.write(";molecule name nr.\n") file.write("SOL %d\n" % (num_sol / num_point)) if num_na > 0: file.write("NA %d\n" % num_na) if num_cl > 0: file.write("CL %d\n" % num_cl) # Load the water/ion box. water_ions = _IO.readMolecules( ["water_ions.gro", "water_ions.top"]) # Create a new system by adding the water to the original molecule. if molecule is not None: if type(molecule) is _System: # Extract the non-water molecules from the original system. non_waters = _Molecules( molecule.search( "not water")._sire_object.toMolecules()) # Create a system by adding these to the water and ion # molecules from gmx solvate, which will include the # original waters. system = non_waters.toSystem() + water_ions else: system = molecule.toSystem() + water_ions # Add all of the water molecules' properties to the new system. for prop in water_ions._sire_object.propertyKeys(): prop = property_map.get(prop, prop) # Add the space property from the water system. system._sire_object.setProperty( prop, water_ions._sire_object.property(prop)) else: system = water_ions # Store the name of the water model as a system property. system._sire_object.setProperty("water_model", _SireBase.wrap(model)) return system