def solvate(solute, solvent, n_solvent, box, overlap=0.2, seed=12345): """Solvate a compound in a box of solvent using packmol. Parameters ---------- solute : mb.Compound solvent : mb.Compound n_solvent : int box : mb.Box overlap : float Returns ------- solvated : mb.Compound """ if not PACKMOL: raise IOError("Packmol not found") box = _validate_box(box) if not isinstance(solvent, (list, set)): solvent = [solvent] if not isinstance(n_solvent, (list, set)): n_solvent = [n_solvent] # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 center_solute = (box_maxs + box_mins) / 2 # Build the input file for each compound and call packmol. solvated_pdb = tempfile.mkstemp(suffix='.pdb')[1] solute_pdb = tempfile.mkstemp(suffix='.pdb')[1] solute.save(solute_pdb, overwrite=True) input_text = (PACKMOL_HEADER.format(overlap, solvated_pdb, seed) + PACKMOL_SOLUTE.format(solute_pdb, *center_solute)) for solv, m_solvent in zip(solvent, n_solvent): m_solvent = int(m_solvent) solvent_pdb = tempfile.mkstemp(suffix='.pdb')[1] solv.save(solvent_pdb, overwrite=True) input_text += PACKMOL_BOX.format(solvent_pdb, m_solvent, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2]) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. solvated = Compound() solvated.add(solute) for solv, m_solvent in zip(solvent, n_solvent): for _ in range(m_solvent): solvated.add(clone(solv)) solvated.update_coordinates(solvated_pdb) return solvated
def solvate(solute, solvent, n_solvent, box, overlap=0.2, seed=12345): """Solvate a compound in a box of solvent using packmol. Parameters ---------- solute : mb.Compound solvent : mb.Compound n_solvent : int box : mb.Box overlap : float Returns ------- solvated : mb.Compound """ if not PACKMOL: raise IOError("Packmol not found") box = _validate_box(box) n_solvent = int(n_solvent) solute_pdb = tempfile.mkstemp(suffix='.pdb')[1] solute.save(solute_pdb, overwrite=True) solvent_pdb = tempfile.mkstemp(suffix='.pdb')[1] solvent.save(solvent_pdb, overwrite=True) solvated_pdb = tempfile.mkstemp(suffix='.pdb')[1] # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 center_solute = (box_maxs + box_mins) / 2 # Build the input file and call packmol. input_text = ( PACKMOL_HEADER.format(overlap, solvated_pdb, seed) + PACKMOL_SOLUTE.format(solute_pdb, *center_solute) + PACKMOL_BOX.format(solvent_pdb, n_solvent, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2])) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. solvated = Compound() solvated.add(solute) for _ in range(n_solvent): solvated.add(clone(solvent)) solvated.update_coordinates(solvated_pdb) return solvated
def fill_box(compound, n_compounds, box, overlap=0.2, seed=12345): """Fill a box with a compound using packmol. Parameters ---------- compound : mb.Compound or list of mb.Compound n_compounds : int or list of int box : mb.Box overlap : float Returns ------- filled : mb.Compound """ if not PACKMOL: msg = "Packmol not found." if sys.platform.startswith("win"): msg = (msg + " If packmol is already installed, make sure that the " "packmol.exe is on the path.") raise IOError(msg) box = _validate_box(box) if not isinstance(compound, (list, set)): compound = [compound] if not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 # Build the input file for each compound and call packmol. filled_pdb = tempfile.mkstemp(suffix='.pdb')[1] input_text = PACKMOL_HEADER.format(overlap, filled_pdb, seed) for comp, m_compounds in zip(compound, n_compounds): m_compounds = int(m_compounds) compound_pdb = tempfile.mkstemp(suffix='.pdb')[1] comp.save(compound_pdb, overwrite=True) input_text += PACKMOL_BOX.format(compound_pdb, m_compounds, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2]) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. filled = Compound() for comp, m_compounds in zip(compound, n_compounds): for _ in range(m_compounds): filled.add(clone(comp)) filled.update_coordinates(filled_pdb) return filled
def fill_box(compound, n_compounds, box, overlap=0.2, seed=12345): """Fill a box with a compound using packmol. Parameters ---------- compound : mb.Compound n_compounds : int box : mb.Box overlap : float Returns ------- filled : mb.Compound """ if not PACKMOL: msg = "Packmol not found." if sys.platform.startswith("win"): msg = (msg + " If packmol is already installed, make sure that the " "packmol.exe is on the path.") raise IOError(msg) box = _validate_box(box) n_compounds = int(n_compounds) compound_pdb = tempfile.mkstemp(suffix='.pdb')[1] compound.save(compound_pdb, overwrite=True) filled_pdb = tempfile.mkstemp(suffix='.pdb')[1] # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 # Build the input file and call packmol. input_text = ( PACKMOL_HEADER.format(overlap, filled_pdb, seed) + PACKMOL_BOX.format(compound_pdb, n_compounds, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2])) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. filled = Compound() for _ in range(n_compounds): filled.add(clone(compound)) filled.update_coordinates(filled_pdb) return filled
def solvate(solute, solvent, n_solvent, box, overlap=0.2, seed=12345): """Solvate a compound in a box of solvent using packmol. Parameters ---------- solute : mb.Compound solvent : mb.Compound n_solvent : int box : mb.Box overlap : float Returns ------- solvated : mb.Compound """ if not PACKMOL: raise IOError("Packmol not found") if isinstance(box, (list, tuple)): box = Box(lengths=box) n_solvent = int(n_solvent) solute_pdb = tempfile.mkstemp(suffix='.pdb')[1] solute.save(solute_pdb, overwrite=True) solvent_pdb = tempfile.mkstemp(suffix='.pdb')[1] solvent.save(solvent_pdb, overwrite=True) solvated_pdb = tempfile.mkstemp(suffix='.pdb')[1] # In angstroms for packmol. box_lengths = box.lengths * 10 overlap *= 10 # center_solute = (-solute.center) * 10 center_solute = box_lengths/2 # Build the input file and call packmol. input_text = (PACKMOL_HEADER.format(overlap, solvated_pdb, seed) + PACKMOL_SOLUTE.format(solute_pdb, *center_solute) + PACKMOL_BOX.format(solvent_pdb, n_solvent, *box_lengths)) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. solvated = Compound() solvated.add(solute) for _ in range(n_solvent): solvated.add(clone(solvent)) solvated.update_coordinates(solvated_pdb) return solvated
def fill_box(compound, n_compounds, box, overlap=0.2, seed=12345): """Fill a box with a compound using packmol. Parameters ---------- compound : mb.Compound n_compounds : int box : mb.Box overlap : float Returns ------- filled : mb.Compound """ if not PACKMOL: msg = "Packmol not found." if sys.platform.startswith("win"): msg = (msg + " If packmol is already installed, make sure that the " "packmol.exe is on the path.") raise IOError(msg) if isinstance(box, (list, tuple)): box = Box(lengths=box) n_compounds = int(n_compounds) compound_pdb = tempfile.mkstemp(suffix='.pdb')[1] compound.save(compound_pdb, overwrite=True) filled_pdb = tempfile.mkstemp(suffix='.pdb')[1] # In angstroms for packmol. box_lengths = box.lengths * 10 overlap *= 10 # Build the input file and call packmol. input_text = (PACKMOL_HEADER.format(overlap, filled_pdb, seed) + PACKMOL_BOX.format(compound_pdb, n_compounds, *box_lengths)) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. filled = Compound() for _ in range(n_compounds): filled.add(clone(compound)) filled.update_coordinates(filled_pdb) return filled
def fill_sphere(compound, sphere, n_compounds=None, density=None, overlap=0.2, seed=12345, edge=0.2, compound_ratio=None, fix_orientation=False, temp_file=None): """Fill a sphere with a compound using packmol. One argument of `n_compounds and density` must be specified. If `n_compounds` is not None, the specified number of n_compounds will be inserted into a sphere of the specified size. If `density` is not None, the corresponding number of compounds will be calculated internally. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to be put in box. sphere : list, units nm Sphere coordinates in the form [x_center, y_center, z_center, radius] n_compounds : int or list of int Number of compounds to be put in box. density : float, units kg/m^3, default=None Target density for the sphere in macroscale units. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. edge : float, units nm, default=0.2 Buffer at the edge of the sphere to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. compound_ratio : list, default=None Ratio of number of each compound to be put in sphere. Only used in the case of `density` having been specified, `n_compounds` not specified, and more than one `compound`. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the sphere, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. Returns ------- filled : mb.Compound """ _check_packmol(PACKMOL) arg_count = 2 - [n_compounds, density].count(None) if arg_count != 1: msg = ("Exactly 1 of `n_compounds` and `density` " "must be specified. {} were given.".format(arg_count)) raise ValueError(msg) if isinstance(sphere, (list, set, tuple)): if len(sphere) != 4: msg = ("`sphere` must be a list of len 4") else: msg = ("`sphere` must be a list") raise ValueError(msg) if not isinstance(compound, (list, set)): compound = [compound] if n_compounds is not None and not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation]*len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = ("`compound` and `n_compounds` must be of equal length.") raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = ("`compound`, `n_compounds`, and `fix_orientation` must be of equal length.") raise ValueError(msg) for coord in sphere[:3]: if coord < sphere[3]: msg = ("`sphere` center coordinates must be greater than radius.") raise ValueError(msg) # Apply edge buffer radius = sphere[3] - edge if density is not None: if n_compounds is None: if len(compound) == 1: compound_mass = np.sum([a.mass for a in compound[0].to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_compounds = [int(density/compound_mass*(4/3*np.pi*radius**3)*.60224)] else: if compound_ratio is None: msg = ("Determing `n_compounds` from `density` " "for systems with more than one compound type requires" "`compound_ratio`") raise ValueError(msg) if len(compound) != len(compound_ratio): msg = ("Length of `compound_ratio` must equal length of " "`compound`") raise ValueError(msg) prototype_mass = 0 for c, r in zip(compound, compound_ratio): prototype_mass += r * np.sum([a.mass for a in c.to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_prototypes = int(density/prototype_mass*(4/3*np.pi*radius**3)*.60224) n_compounds = list() for c in compound_ratio: n_compounds.append(int(n_prototypes * c)) # In angstroms for packmol. sphere = np.multiply(sphere, 10) radius *= 10 overlap *= 10 # Build the input file for each compound and call packmol. filled_xyz = _new_xyz_file() # List to hold file handles for the temporary compounds compound_xyz_list = list() try: input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed) for comp, m_compounds, rotate in zip(compound, n_compounds, fix_orientation): m_compounds = int(m_compounds) compound_xyz = _new_xyz_file() compound_xyz_list.append(compound_xyz) comp.save(compound_xyz.name, overwrite=True) input_text += PACKMOL_SPHERE.format(compound_xyz.name, m_compounds, sphere[0], sphere[1], sphere[2], radius, PACKMOL_CONSTRAIN if rotate else "") print(input_text) _run_packmol(input_text, filled_xyz, temp_file) # Create the topology and update the coordinates. filled = Compound() filled = _create_topology(filled, compound, n_compounds) filled.update_coordinates(filled_xyz.name) finally: for file_handle in compound_xyz_list: file_handle.close() os.unlink(file_handle.name) filled_xyz.close() os.unlink(filled_xyz.name) return filled
def fill_region(compound, n_compounds, region, overlap=0.2, seed=12345, edge=0.2, fix_orientation=False, temp_file=None): """Fill a region of a box with a compound using packmol. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to be put in region. n_compounds : int or list of int Number of compounds to be put in region. region : mb.Box or list of mb.Box Region to be filled by compounds. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. edge : float, units nm, default=0.2 Buffer at the edge of the region to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. Returns ------- filled : mb.Compound If using mulitple regions and compounds, the nth value in each list are used in order. For example, if the third compound will be put in the third region using the third value in n_compounds. """ _check_packmol(PACKMOL) if not isinstance(compound, (list, set)): compound = [compound] if not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation]*len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = ("`compound` and `n_compounds` must be of equal length.") raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = ("`compound`, `n_compounds`, and `fix_orientation` must be of equal length.") raise ValueError(msg) # See if region is a single region or list if isinstance(region, Box): # Cannot iterate over boxes region = [region] elif not any(isinstance(reg, (list, set, Box)) for reg in region): region = [region] region = [_validate_box(reg) for reg in region] # In angstroms for packmol. overlap *= 10 # Build the input file and call packmol. filled_xyz = _new_xyz_file() # List to hold file handles for the temporary compounds compound_xyz_list = list() try: input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed) for comp, m_compounds, reg, rotate in zip(compound, n_compounds, region, fix_orientation): m_compounds = int(m_compounds) compound_xyz = _new_xyz_file() compound_xyz_list.append(compound_xyz) comp.save(compound_xyz.name, overwrite=True) reg_mins = reg.mins * 10 reg_maxs = reg.maxs * 10 reg_maxs -= edge * 10 # Apply edge buffer input_text += PACKMOL_BOX.format(compound_xyz.name, m_compounds, reg_mins[0], reg_mins[1], reg_mins[2], reg_maxs[0], reg_maxs[1], reg_maxs[2], PACKMOL_CONSTRAIN if rotate else "") _run_packmol(input_text, filled_xyz, temp_file) # Create the topology and update the coordinates. filled = Compound() filled = _create_topology(filled, compound, n_compounds) filled.update_coordinates(filled_xyz.name) finally: for file_handle in compound_xyz_list: file_handle.close() os.unlink(file_handle.name) filled_xyz.close() os.unlink(filled_xyz.name) return filled
def fill_box(compound, n_compounds=None, box=None, density=None, overlap=0.2, seed=12345, edge=0.2, compound_ratio=None, aspect_ratio=None, fix_orientation=False, temp_file=None): """Fill a box with a compound using packmol. Two arguments of `n_compounds, box, and density` must be specified. If `n_compounds` and `box` are not None, the specified number of n_compounds will be inserted into a box of the specified size. If `n_compounds` and `density` are not None, the corresponding box size will be calculated internally. In this case, `n_compounds` must be an int and not a list of int. If `box` and `density` are not None, the corresponding number of compounds will be calculated internally. For the cases in which `box` is not specified but generated internally, the default behavior is to calculate a cubic box. Optionally, `aspect_ratio` can be passed to generate a non-cubic box. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to be put in box. n_compounds : int or list of int Number of compounds to be put in box. box : mb.Box Box to be filled by compounds. density : float, units kg/m^3, default=None Target density for the system in macroscale units. If not None, one of `n_compounds` or `box`, but not both, must be specified. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. edge : float, units nm, default=0.2 Buffer at the edge of the box to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. compound_ratio : list, default=None Ratio of number of each compound to be put in box. Only used in the case of `density` and `box` having been specified, `n_compounds` not specified, and more than one `compound`. aspect_ratio : list of float If a non-cubic box is desired, the ratio of box lengths in the x, y, and z directions. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. Returns ------- filled : mb.Compound """ _check_packmol(PACKMOL) arg_count = 3 - [n_compounds, box, density].count(None) if arg_count != 2: msg = ("Exactly 2 of `n_compounds`, `box`, and `density` " "must be specified. {} were given.".format(arg_count)) raise ValueError(msg) if box is not None: box = _validate_box(box) if not isinstance(compound, (list, set)): compound = [compound] if n_compounds is not None and not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation]*len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = ("`compound` and `n_compounds` must be of equal length.") raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = ("`compound`, `n_compounds`, and `fix_orientation` must be of equal length.") raise ValueError(msg) if density is not None: if box is None and n_compounds is not None: total_mass = np.sum([n*np.sum([a.mass for a in c.to_parmed().atoms]) for c,n in zip(compound, n_compounds)]) # Conversion from (amu/(kg/m^3))**(1/3) to nm L = (total_mass/density)**(1/3)*1.1841763 if aspect_ratio is None: box = _validate_box(Box(3*[L])) else: L *= np.prod(aspect_ratio) ** (-1/3) box = _validate_box(Box([val*L for val in aspect_ratio])) if n_compounds is None and box is not None: if len(compound) == 1: compound_mass = np.sum([a.mass for a in compound[0].to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_compounds = [int(density/compound_mass*np.prod(box.lengths)*.60224)] else: if compound_ratio is None: msg = ("Determing `n_compounds` from `density` and `box` " "for systems with more than one compound type requires" "`compound_ratio`") raise ValueError(msg) if len(compound) != len(compound_ratio): msg = ("Length of `compound_ratio` must equal length of " "`compound`") raise ValueError(msg) prototype_mass = 0 for c, r in zip(compound, compound_ratio): prototype_mass += r * np.sum([a.mass for a in c.to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_prototypes = int(density/prototype_mass*np.prod(box.lengths)*.60224) n_compounds = list() for c in compound_ratio: n_compounds.append(int(n_prototypes * c)) # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 # Apply edge buffer box_maxs -= edge * 10 # Build the input file for each compound and call packmol. filled_pdb = tempfile.mkstemp(suffix='.pdb')[1] input_text = PACKMOL_HEADER.format(overlap, filled_pdb, seed) for comp, m_compounds, rotate in zip(compound, n_compounds, fix_orientation): m_compounds = int(m_compounds) compound_pdb = tempfile.mkstemp(suffix='.pdb')[1] comp.save(compound_pdb, overwrite=True) input_text += PACKMOL_BOX.format(compound_pdb, m_compounds, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2], PACKMOL_CONSTRAIN if rotate else "") _run_packmol(input_text, filled_pdb, temp_file) # Create the topology and update the coordinates. filled = Compound() for comp, m_compounds in zip(compound, n_compounds): for _ in range(m_compounds): filled.add(clone(comp)) filled.update_coordinates(filled_pdb) filled.periodicity = np.asarray(box.lengths, dtype=np.float32) return filled
def solvate(solute, solvent, n_solvent, box, overlap=0.2, seed=12345, edge=0.2, fix_orientation=False, temp_file=None): """Solvate a compound in a box of solvent using packmol. Parameters ---------- solute : mb.Compound Compound to be placed in a box and solvated. solvent : mb.Compound Compound to solvate the box. n_solvent : int Number of solvents to be put in box. box : mb.Box Box to be filled by compounds. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. edge : float, units nm, default=0.2 Buffer at the edge of the box to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. fix_orientation : bool Specify if solvent should not be rotated when filling box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. Returns ------- solvated : mb.Compound """ _check_packmol(PACKMOL) box = _validate_box(box) if not isinstance(solvent, (list, set)): solvent = [solvent] if not isinstance(n_solvent, (list, set)): n_solvent = [n_solvent] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation] * len(solvent) if len(solvent) != len(n_solvent): msg = ("`n_solvent` and `n_solvent` must be of equal length.") raise ValueError(msg) # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 center_solute = (box_maxs + box_mins) / 2 # Apply edge buffer box_maxs -= edge * 10 # Build the input file for each compound and call packmol. solvated_pdb = tempfile.mkstemp(suffix='.pdb')[1] solute_pdb = tempfile.mkstemp(suffix='.pdb')[1] solute.save(solute_pdb, overwrite=True) input_text = (PACKMOL_HEADER.format(overlap, solvated_pdb, seed) + PACKMOL_SOLUTE.format(solute_pdb, *center_solute)) for solv, m_solvent, rotate in zip(solvent, n_solvent, fix_orientation): m_solvent = int(m_solvent) solvent_pdb = tempfile.mkstemp(suffix='.pdb')[1] solv.save(solvent_pdb, overwrite=True) input_text += PACKMOL_BOX.format(solvent_pdb, m_solvent, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2], PACKMOL_CONSTRAIN if rotate else "") _run_packmol(input_text, solvated_pdb, temp_file) # Create the topology and update the coordinates. solvated = Compound() solvated.add(solute) for solv, m_solvent in zip(solvent, n_solvent): for _ in range(m_solvent): solvated.add(clone(solv)) solvated.update_coordinates(solvated_pdb) return solvated
def fill_box( compound, n_compounds=None, box=None, density=None, overlap=0.2, seed=12345, sidemax=100.0, edge=0.2, compound_ratio=None, aspect_ratio=None, fix_orientation=False, temp_file=None, update_port_locations=False, ): """Fill a box with a `mbuild.compound` or `Compound`s using PACKMOL. `fill_box` takes a single `mbuild.Compound` or a list of `mbuild.Compound`'s and return an `mbuild.Compound` that has been filled to the user's specifications to the best of PACKMOL's ability. When filling a system, two arguments of `n_compounds, box, and density` must be specified. If `n_compounds` and `box` are not None, the specified number of n_compounds will be inserted into a box of the specified size. If `n_compounds` and `density` are not None, the corresponding box size will be calculated internally. In this case, `n_compounds` must be an int and not a list of int. If `box` and `density` are not None, the corresponding number of compounds will be calculated internally. For the cases in which `box` is not specified but generated internally, the default behavior is to calculate a cubic box. Optionally, `aspect_ratio` can be passed to generate a non-cubic box. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to fill in box. n_compounds : int or list of int Number of compounds to be filled in box. box : mb.Box Box to be filled by compounds. density : float, units kg/m^3, default=None Target density for the system in macroscale units. If not None, one of `n_compounds` or `box`, but not both, must be specified. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. sidemax : float, optional, default=100.0 Needed to build an initial approximation of the molecule distribution in PACKMOL. All system coordinates must fit with in +/- sidemax, so increase sidemax accordingly to your final box size. edge : float, units nm, default=0.2 Buffer at the edge of the box to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. compound_ratio : list, default=None Ratio of number of each compound to be put in box. Only used in the case of `density` and `box` having been specified, `n_compounds` not specified, and more than one `compound`. aspect_ratio : list of float If a non-cubic box is desired, the ratio of box lengths in the x, y, and z directions. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. update_port_locations : bool, default=False After packing, port locations can be updated, but since compounds can be rotated, port orientation may be incorrect. Returns ------- filled : mb.Compound """ # check that the user has the PACKMOL binary on their PATH _check_packmol(PACKMOL) arg_count = 3 - [n_compounds, box, density].count(None) if arg_count != 2: msg = ("Exactly 2 of `n_compounds`, `box`, and `density` " "must be specified. {} were given.".format(arg_count)) raise ValueError(msg) if box is not None: box = _validate_box(box) if not isinstance(compound, (list, set)): compound = [compound] if n_compounds is not None and not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation] * len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = "`compound` and `n_compounds` must be of equal length." raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = ("`compound`, `n_compounds`, and `fix_orientation` " "must be of equal length.") raise ValueError(msg) if density is not None: if box is None and n_compounds is not None: total_mass = np.sum([ n * np.sum([a.mass for a in c.to_parmed().atoms]) for c, n in zip(compound, n_compounds) ]) # Conversion from (amu/(kg/m^3))**(1/3) to nm L = (total_mass / density)**(1 / 3) * 1.1841763 if aspect_ratio is None: box = _validate_box(Box(3 * [L])) else: L *= np.prod(aspect_ratio)**(-1 / 3) box = _validate_box(Box([val * L for val in aspect_ratio])) if n_compounds is None and box is not None: if len(compound) == 1: compound_mass = np.sum( [a.mass for a in compound[0].to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_compounds = [ int(density / compound_mass * np.prod(box.lengths) * 0.60224) ] else: if compound_ratio is None: msg = ("Determing `n_compounds` from `density` and `box` " "for systems with more than one compound type " "requires `compound_ratio`") raise ValueError(msg) if len(compound) != len(compound_ratio): msg = ("Length of `compound_ratio` must equal length of " "`compound`") raise ValueError(msg) prototype_mass = 0 for c, r in zip(compound, compound_ratio): prototype_mass += r * np.sum( [a.mass for a in c.to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_prototypes = int(density / prototype_mass * np.prod(box.lengths) * 0.60224) n_compounds = list() for c in compound_ratio: n_compounds.append(int(n_prototypes * c)) # Convert nm to angstroms for PACKMOL. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 # Apply 1nm edge buffer box_maxs -= edge * 10 # Build the input file for each compound and call packmol. filled_xyz = _new_xyz_file() # create a list to contain the file handles for the compound temp files compound_xyz_list = list() try: input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed, sidemax * 10) for comp, m_compounds, rotate in zip(compound, n_compounds, fix_orientation): m_compounds = int(m_compounds) compound_xyz = _new_xyz_file() compound_xyz_list.append(compound_xyz) comp.save(compound_xyz.name, overwrite=True) input_text += PACKMOL_BOX.format( compound_xyz.name, m_compounds, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2], PACKMOL_CONSTRAIN if rotate else "", ) _run_packmol(input_text, filled_xyz, temp_file) # Create the topology and update the coordinates. filled = Compound() filled = _create_topology(filled, compound, n_compounds) filled.update_coordinates(filled_xyz.name, update_port_locations=update_port_locations) filled.box = box filled.periodicity = np.asarray(box.lengths, dtype=np.float32) # ensure that the temporary files are removed from the machine after filling finally: for file_handle in compound_xyz_list: file_handle.close() os.unlink(file_handle.name) filled_xyz.close() os.unlink(filled_xyz.name) return filled
def fill_region( compound, n_compounds, region, overlap=0.2, seed=12345, sidemax=100.0, edge=0.2, fix_orientation=False, temp_file=None, update_port_locations=False, ): """Fill a region of a box with `mbuild.Compound`(s) using PACKMOL. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to fill in region. n_compounds : int or list of ints Number of compounds to be put in region. region : mb.Box or list of mb.Box Region to be filled by compounds. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. sidemax : float, optional, default=100.0 Needed to build an initial approximation of the molecule distribution in PACKMOL. All system coordinates must fit with in +/- sidemax, so increase sidemax accordingly to your final box size. edge : float, units nm, default=0.2 Buffer at the edge of the region to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. update_port_locations : bool, default=False After packing, port locations can be updated, but since compounds can be rotated, port orientation may be incorrect. Returns ------- filled : mb.Compound If using mulitple regions and compounds, the nth value in each list are used in order. For example, if the third compound will be put in the third region using the third value in n_compounds. """ # check that the user has the PACKMOL binary on their PATH _check_packmol(PACKMOL) if not isinstance(compound, (list, set)): compound = [compound] if not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation] * len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = "`compound` and `n_compounds` must be of equal length." raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = "`compound`, `n_compounds`, and `fix_orientation` must be of equal length." raise ValueError(msg) # See if region is a single region or list if isinstance(region, Box): # Cannot iterate over boxes region = [region] elif not any(isinstance(reg, (list, set, Box)) for reg in region): region = [region] region = [_validate_box(reg) for reg in region] # In angstroms for packmol. overlap *= 10 # Build the input file and call packmol. filled_xyz = _new_xyz_file() # List to hold file handles for the temporary compounds compound_xyz_list = list() try: input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed, sidemax * 10) for comp, m_compounds, reg, rotate in zip(compound, n_compounds, region, fix_orientation): m_compounds = int(m_compounds) compound_xyz = _new_xyz_file() compound_xyz_list.append(compound_xyz) comp.save(compound_xyz.name, overwrite=True) reg_mins = reg.mins * 10 reg_maxs = reg.maxs * 10 reg_maxs -= edge * 10 # Apply edge buffer input_text += PACKMOL_BOX.format( compound_xyz.name, m_compounds, reg_mins[0], reg_mins[1], reg_mins[2], reg_maxs[0], reg_maxs[1], reg_maxs[2], PACKMOL_CONSTRAIN if rotate else "", ) _run_packmol(input_text, filled_xyz, temp_file) # Create the topology and update the coordinates. filled = Compound() filled = _create_topology(filled, compound, n_compounds) filled.update_coordinates(filled_xyz.name, update_port_locations=update_port_locations) finally: for file_handle in compound_xyz_list: file_handle.close() os.unlink(file_handle.name) filled_xyz.close() os.unlink(filled_xyz.name) return filled
def fill_region(compound, n_compounds, region, overlap=0.2, seed=12345): """Fill a region of a box with a compound using packmol. Parameters ---------- compound : mb.Compound or list of mb.Compound n_compounds : int or list of int region : mb.Box or list of mb.Box overlap : float Returns ------- filled : mb.Compound If using mulitple regions and compounds, the nth value in each list are used in order. For example, if the third compound will be put in the third region using the third value in n_compounds. """ if not PACKMOL: msg = "Packmol not found." if sys.platform.startswith("win"): msg = (msg + " If packmol is already installed, make sure that the " "packmol.exe is on the path.") raise IOError(msg) if not isinstance(compound, (list, set)): compound = [compound] if not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] # See if region is a single region or list if isinstance(region, Box): # Cannot iterate over boxes region = [region] elif not any(isinstance(reg, (list, set, Box)) for reg in region): region = [region] region = [_validate_box(reg) for reg in region] # In angstroms for packmol. overlap *= 10 # Build the input file and call packmol. filled_pdb = tempfile.mkstemp(suffix='.pdb')[1] input_text = PACKMOL_HEADER.format(overlap, filled_pdb, seed) for comp, m_compounds, reg in zip(compound, n_compounds, region): m_compounds = int(m_compounds) compound_pdb = tempfile.mkstemp(suffix='.pdb')[1] comp.save(compound_pdb, overwrite=True) reg_mins = reg.mins * 10 reg_maxs = reg.maxs * 10 input_text += PACKMOL_BOX.format(compound_pdb, m_compounds, reg_mins[0], reg_mins[1], reg_mins[2], reg_maxs[0], reg_maxs[1], reg_maxs[2]) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. filled = Compound() for comp, m_compounds in zip(compound, n_compounds): for _ in range(m_compounds): filled.add(clone(comp)) filled.update_coordinates(filled_pdb) return filled
def fill_box(compound, n_compounds=None, box=None, density=None, overlap=0.2, seed=12345): """Fill a box with a compound using packmol. Two arguments of `n_compounds, box, and density` must be specified. If `n_compounds` and `box` are not None, the specified number of n_compounds will be inserted into a box of the specified size. If `n_compounds` and `density` are not None, the corresponding box size will be calculated internally. In this case, `n_compounds` must be an int and not a list of int. If `box` and `density` are not None, the corresponding number of compounds will be calculated internally. Parameters ---------- compound : mb.Compound or list of mb.Compound n_compounds : int or list of int box : mb.Box overlap : float, units nm density : float, units kg/m^3 Returns ------- filled : mb.Compound TODO : Support aspect ratios in generated boxes TODO : Support ratios of n_compounds """ if not PACKMOL: msg = "Packmol not found." if sys.platform.startswith("win"): msg = (msg + " If packmol is already installed, make sure that the " "packmol.exe is on the path.") raise IOError(msg) arg_count = 3 - [n_compounds, box, density].count(None) if arg_count != 2: msg = ("Exactly 2 of `n_compounds`, `box`, and `density` " "must be specified. {} were given.".format(arg_count)) raise ValueError(msg) if box is not None: box = _validate_box(box) if not isinstance(compound, (list, set)): compound = [compound] if n_compounds is not None and not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if density is not None: if box is None and n_compounds is not None: total_mass = np.sum([ n * np.sum([a.mass for a in c.to_parmed().atoms]) for c, n in zip(compound, n_compounds) ]) # Conversion from (amu/(kg/m^3))**(1/3) to nm L = (total_mass / density)**(1 / 3) * 1.1841763 box = _validate_box(Box(3 * [L])) if n_compounds is None and box is not None: if len(compound) > 1: msg = ("Determing `n_compounds` from `density` and `box` " "currently only supported for systems with one " "compound type.") raise ValueError(msg) else: compound_mass = np.sum( [a.mass for a in compound[0].to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_compounds = [ int(density / compound_mass * np.prod(box.lengths) * .60224) ] # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 # Build the input file for each compound and call packmol. filled_pdb = tempfile.mkstemp(suffix='.pdb')[1] input_text = PACKMOL_HEADER.format(overlap, filled_pdb, seed) for comp, m_compounds in zip(compound, n_compounds): m_compounds = int(m_compounds) compound_pdb = tempfile.mkstemp(suffix='.pdb')[1] comp.save(compound_pdb, overwrite=True) input_text += PACKMOL_BOX.format(compound_pdb, m_compounds, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2]) proc = Popen(PACKMOL, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate(input=input_text) if err: _packmol_error(out, err) # Create the topology and update the coordinates. filled = Compound() for comp, m_compounds in zip(compound, n_compounds): for _ in range(m_compounds): filled.add(clone(comp)) filled.update_coordinates(filled_pdb) filled.periodicity = np.asarray(box.lengths, dtype=np.float32) return filled
def solvate(solute, solvent, n_solvent, box, overlap=0.2, seed=12345, edge=0.2, fix_orientation=False, temp_file=None): """Solvate a compound in a box of solvent using packmol. Parameters ---------- solute : mb.Compound Compound to be placed in a box and solvated. solvent : mb.Compound Compound to solvate the box. n_solvent : int Number of solvents to be put in box. box : mb.Box Box to be filled by compounds. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. edge : float, units nm, default=0.2 Buffer at the edge of the box to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. fix_orientation : bool Specify if solvent should not be rotated when filling box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. Returns ------- solvated : mb.Compound """ _check_packmol(PACKMOL) box = _validate_box(box) if not isinstance(solvent, (list, set)): solvent = [solvent] if not isinstance(n_solvent, (list, set)): n_solvent = [n_solvent] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation] * len(solvent) if len(solvent) != len(n_solvent): msg = ("`n_solvent` and `n_solvent` must be of equal length.") raise ValueError(msg) # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 center_solute = (box_maxs + box_mins) / 2 # Apply edge buffer box_maxs -= edge * 10 # Build the input file for each compound and call packmol. solvated_xyz = _new_xyz_file() solute_xyz = _new_xyz_file() # generate list of temp files for the solvents solvent_xyz_list = list() try: solute.save(solute_xyz.name, overwrite=True) input_text = (PACKMOL_HEADER.format(overlap, solvated_xyz.name, seed) + PACKMOL_SOLUTE.format(solute_xyz.name, *center_solute)) for solv, m_solvent, rotate in zip(solvent, n_solvent, fix_orientation): m_solvent = int(m_solvent) solvent_xyz = _new_xyz_file() solvent_xyz_list.append(solvent_xyz) solv.save(solvent_xyz.name, overwrite=True) input_text += PACKMOL_BOX.format(solvent_xyz.name, m_solvent, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2], PACKMOL_CONSTRAIN if rotate else "") _run_packmol(input_text, solvated_xyz, temp_file) # Create the topology and update the coordinates. solvated = Compound() solvated.add(solute) solvated = _create_topology(solvated, solvent, n_solvent) solvated.update_coordinates(solvated_xyz.name) finally: for file_handle in solvent_xyz_list: file_handle.close() os.unlink(file_handle.name) solvated_xyz.close() solute_xyz.close() os.unlink(solvated_xyz.name) os.unlink(solute_xyz.name) return solvated
def fill_box(compound, n_compounds=None, box=None, density=None, overlap=0.2, seed=12345, edge=0.2, compound_ratio=None, aspect_ratio=None, fix_orientation=False, temp_file=None): """Fill a box with a compound using packmol. Two arguments of `n_compounds, box, and density` must be specified. If `n_compounds` and `box` are not None, the specified number of n_compounds will be inserted into a box of the specified size. If `n_compounds` and `density` are not None, the corresponding box size will be calculated internally. In this case, `n_compounds` must be an int and not a list of int. If `box` and `density` are not None, the corresponding number of compounds will be calculated internally. For the cases in which `box` is not specified but generated internally, the default behavior is to calculate a cubic box. Optionally, `aspect_ratio` can be passed to generate a non-cubic box. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to be put in box. n_compounds : int or list of int Number of compounds to be put in box. box : mb.Box Box to be filled by compounds. density : float, units kg/m^3, default=None Target density for the system in macroscale units. If not None, one of `n_compounds` or `box`, but not both, must be specified. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. edge : float, units nm, default=0.2 Buffer at the edge of the box to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. compound_ratio : list, default=None Ratio of number of each compound to be put in box. Only used in the case of `density` and `box` having been specified, `n_compounds` not specified, and more than one `compound`. aspect_ratio : list of float If a non-cubic box is desired, the ratio of box lengths in the x, y, and z directions. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. Returns ------- filled : mb.Compound """ _check_packmol(PACKMOL) arg_count = 3 - [n_compounds, box, density].count(None) if arg_count != 2: msg = ("Exactly 2 of `n_compounds`, `box`, and `density` " "must be specified. {} were given.".format(arg_count)) raise ValueError(msg) if box is not None: box = _validate_box(box) if not isinstance(compound, (list, set)): compound = [compound] if n_compounds is not None and not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation]*len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = ("`compound` and `n_compounds` must be of equal length.") raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = ("`compound`, `n_compounds`, and `fix_orientation` must be of equal length.") raise ValueError(msg) if density is not None: if box is None and n_compounds is not None: total_mass = np.sum([n*np.sum([a.mass for a in c.to_parmed().atoms]) for c, n in zip(compound, n_compounds)]) # Conversion from (amu/(kg/m^3))**(1/3) to nm L = (total_mass/density)**(1/3)*1.1841763 if aspect_ratio is None: box = _validate_box(Box(3*[L])) else: L *= np.prod(aspect_ratio) ** (-1/3) box = _validate_box(Box([val*L for val in aspect_ratio])) if n_compounds is None and box is not None: if len(compound) == 1: compound_mass = np.sum([a.mass for a in compound[0].to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_compounds = [int(density/compound_mass*np.prod(box.lengths)*.60224)] else: if compound_ratio is None: msg = ("Determing `n_compounds` from `density` and `box` " "for systems with more than one compound type requires" "`compound_ratio`") raise ValueError(msg) if len(compound) != len(compound_ratio): msg = ("Length of `compound_ratio` must equal length of " "`compound`") raise ValueError(msg) prototype_mass = 0 for c, r in zip(compound, compound_ratio): prototype_mass += r * np.sum([a.mass for a in c.to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_prototypes = int(density/prototype_mass*np.prod(box.lengths)*.60224) n_compounds = list() for c in compound_ratio: n_compounds.append(int(n_prototypes * c)) # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 # Apply edge buffer box_maxs -= edge * 10 # Build the input file for each compound and call packmol. filled_xyz = _new_xyz_file() # create a list to contain the file handles for the compound temp files compound_xyz_list = list() try: input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed) for comp, m_compounds, rotate in zip(compound, n_compounds, fix_orientation): m_compounds = int(m_compounds) compound_xyz = _new_xyz_file() compound_xyz_list.append(compound_xyz) comp.save(compound_xyz.name, overwrite=True) input_text += PACKMOL_BOX.format(compound_xyz.name, m_compounds, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2], PACKMOL_CONSTRAIN if rotate else "") _run_packmol(input_text, filled_xyz, temp_file) # Create the topology and update the coordinates. filled = Compound() filled = _create_topology(filled, compound, n_compounds) filled.update_coordinates(filled_xyz.name) filled.periodicity = np.asarray(box.lengths, dtype=np.float32) finally: for file_handle in compound_xyz_list: file_handle.close() os.unlink(file_handle.name) filled_xyz.close() os.unlink(filled_xyz.name) return filled
def fill_sphere( compound, sphere, n_compounds=None, density=None, overlap=0.2, seed=12345, sidemax=100.0, edge=0.2, compound_ratio=None, fix_orientation=False, temp_file=None, update_port_locations=False, ): """Fill a sphere with a compound using packmol. One argument of `n_compounds and density` must be specified. If `n_compounds` is not None, the specified number of n_compounds will be inserted into a sphere of the specified size. If `density` is not None, the corresponding number of compounds will be calculated internally. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to be put in box. sphere : list, units nm Sphere coordinates in the form [x_center, y_center, z_center, radius] n_compounds : int or list of int Number of compounds to be put in box. density : float, units kg/m^3, default=None Target density for the sphere in macroscale units. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. sidemax : float, optional, default=100.0 Needed to build an initial approximation of the molecule distribution in PACKMOL. All system coordinates must fit with in +/- sidemax, so increase sidemax accordingly to your final box size edge : float, units nm, default=0.2 Buffer at the edge of the sphere to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. compound_ratio : list, default=None Ratio of number of each compound to be put in sphere. Only used in the case of `density` having been specified, `n_compounds` not specified, and more than one `compound`. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the sphere, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. update_port_locations : bool, default=False After packing, port locations can be updated, but since compounds can be rotated, port orientation may be incorrect. Returns ------- filled : mb.Compound """ _check_packmol(PACKMOL) arg_count = 2 - [n_compounds, density].count(None) if arg_count != 1: msg = ("Exactly 1 of `n_compounds` and `density` " "must be specified. {} were given.".format(arg_count)) raise ValueError(msg) if isinstance(sphere, (list, set, tuple)): if len(sphere) != 4: msg = "`sphere` must be a list of len 4" else: msg = "`sphere` must be a list" raise ValueError(msg) if not isinstance(compound, (list, set)): compound = [compound] if n_compounds is not None and not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation] * len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = "`compound` and `n_compounds` must be of equal length." raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = "`compound`, `n_compounds`, and `fix_orientation` must be of equal length." raise ValueError(msg) for coord in sphere[:3]: if coord < sphere[3]: msg = "`sphere` center coordinates must be greater than radius." raise ValueError(msg) # Apply edge buffer radius = sphere[3] - edge if density is not None: if n_compounds is None: if len(compound) == 1: compound_mass = np.sum( [a.mass for a in compound[0].to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_compounds = [ int(density / compound_mass * (4 / 3 * np.pi * radius**3) * 0.60224) ] else: if compound_ratio is None: msg = ( "Determing `n_compounds` from `density` " "for systems with more than one compound type requires" "`compound_ratio`") raise ValueError(msg) if len(compound) != len(compound_ratio): msg = ("Length of `compound_ratio` must equal length of " "`compound`") raise ValueError(msg) prototype_mass = 0 for c, r in zip(compound, compound_ratio): prototype_mass += r * np.sum( [a.mass for a in c.to_parmed().atoms]) # Conversion from kg/m^3 / amu * nm^3 to dimensionless units n_prototypes = int(density / prototype_mass * (4 / 3 * np.pi * radius**3) * 0.60224) n_compounds = list() for c in compound_ratio: n_compounds.append(int(n_prototypes * c)) # In angstroms for packmol. sphere = np.multiply(sphere, 10) radius *= 10 overlap *= 10 # Build the input file for each compound and call packmol. filled_xyz = _new_xyz_file() # List to hold file handles for the temporary compounds compound_xyz_list = list() try: input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed, sidemax * 10) for comp, m_compounds, rotate in zip(compound, n_compounds, fix_orientation): m_compounds = int(m_compounds) compound_xyz = _new_xyz_file() compound_xyz_list.append(compound_xyz) comp.save(compound_xyz.name, overwrite=True) input_text += PACKMOL_SPHERE.format( compound_xyz.name, m_compounds, sphere[0], sphere[1], sphere[2], radius, PACKMOL_CONSTRAIN if rotate else "", ) _run_packmol(input_text, filled_xyz, temp_file) # Create the topology and update the coordinates. filled = Compound() filled = _create_topology(filled, compound, n_compounds) filled.update_coordinates(filled_xyz.name, update_port_locations=update_port_locations) finally: for file_handle in compound_xyz_list: file_handle.close() os.unlink(file_handle.name) filled_xyz.close() os.unlink(filled_xyz.name) return filled
def fill_region( compound, n_compounds, region, overlap=0.2, bounds=None, seed=12345, sidemax=100.0, edge=0.2, fix_orientation=False, temp_file=None, update_port_locations=False, ): """Fill a region of a box with `mbuild.Compound` (s) using PACKMOL. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to fill in region. n_compounds : int or list of ints Number of compounds to be put in region. region : mb.Box or list of mb.Box Region to be filled by compounds. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. sidemax : float, optional, default=100.0 Needed to build an initial approximation of the molecule distribution in PACKMOL. All system coordinates must fit with in +/- sidemax, so increase sidemax accordingly to your final box size. edge : float, units nm, default=0.2 Buffer at the edge of the region to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the box, default=False. bounds : list-like of floats [minx, miny, minz, maxx, maxy, maxz], units nm, default=None Bounding within box to pack compounds, if you want to pack within a bounding area that is not the full extent of the region, bounds are required. temp_file : str, default=None File name to write PACKMOL raw output to. update_port_locations : bool, default=False After packing, port locations can be updated, but since compounds can be rotated, port orientation may be incorrect. Returns ------- filled : mb.Compound If using mulitple regions and compounds, the nth value in each list are used in order. For example, the third compound will be put in the third region using the third value in n_compounds. """ # check that the user has the PACKMOL binary on their PATH _check_packmol(PACKMOL) if not isinstance(compound, (list, set)): compound = [compound] if not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation] * len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): raise ValueError( "`compound` and `n_compounds` must be of equal length.") if compound is not None: if len(compound) != len(fix_orientation): raise ValueError( "`compound`, `n_compounds`, and `fix_orientation` must be of " "equal length.") # See if region is a single region or list my_regions = [] if isinstance(region, Box): # Cannot iterate over boxes my_regions.append(region) # if region is a list of boxes or a list of lists of floats append to my_regions, otherwise the list is expected to be a list of floats elif isinstance(region, list): for reg in region: if isinstance(reg, (list, Box)): my_regions.append(reg) else: raise ValueError( f"list contents expected to be mbuild.Box or list of floats, provided: {type(reg)}" ) else: raise ValueError( f"expected a list of type: list or mbuild.Box, was provided {region} of type: {type(region)}" ) container = [] if not bounds: bounds = [] for bound, reg in zip_longest(bounds, my_regions, fillvalue=None): if bound is None: container.append(reg) else: container.append(bound) container = [_validate_box(bounding) for bounding in container] # In angstroms for packmol. overlap *= 10 # Build the input file and call packmol. filled_xyz = _new_xyz_file() # List to hold file handles for the temporary compounds compound_xyz_list = list() try: input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed, sidemax * 10) for comp, m_compounds, rotate, items_n in zip(compound, n_compounds, fix_orientation, container): m_compounds = int(m_compounds) compound_xyz = _new_xyz_file() compound_xyz_list.append(compound_xyz) comp.save(compound_xyz.name, overwrite=True) # TODO how to handle these mins and maxs of this system # box should not have any idea of mins and maxs my_min = items_n[1] my_max = items_n[2] reg_mins = np.asarray(my_min) * 10.0 reg_maxs = np.asarray(my_max) * 10.0 reg_maxs -= edge * 10 # Apply edge buffer input_text += PACKMOL_BOX.format( compound_xyz.name, m_compounds, reg_mins[0], reg_mins[1], reg_mins[2], reg_maxs[0], reg_maxs[1], reg_maxs[2], PACKMOL_CONSTRAIN if rotate else "", ) _run_packmol(input_text, filled_xyz, temp_file) # Create the topology and update the coordinates. filled = Compound() filled = _create_topology(filled, compound, n_compounds) filled.update_coordinates(filled_xyz.name, update_port_locations=update_port_locations) finally: for file_handle in compound_xyz_list: file_handle.close() os.unlink(file_handle.name) filled_xyz.close() os.unlink(filled_xyz.name) return filled
def solvate( solute, solvent, n_solvent, box, overlap=0.2, seed=12345, sidemax=100.0, edge=0.2, fix_orientation=False, temp_file=None, update_port_locations=False, ): """Solvate a compound in a box of solvent using packmol. Parameters ---------- solute : mb.Compound Compound to be placed in a box and solvated. solvent : mb.Compound Compound to solvate the box. n_solvent : int Number of solvents to be put in box. box : mb.Box Box to be filled by compounds. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. sidemax : float, optional, default=100.0 Needed to build an initial approximation of the molecule distribution in PACKMOL. All system coordinates must fit with in +/- sidemax, so increase sidemax accordingly to your final box size edge : float, units nm, default=0.2 Buffer at the edge of the box to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. fix_orientation : bool Specify if solvent should not be rotated when filling box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. update_port_locations : bool, default=False After packing, port locations can be updated, but since compounds can be rotated, port orientation may be incorrect. Returns ------- solvated : mb.Compound """ # check that the user has the PACKMOL binary on their PATH _check_packmol(PACKMOL) box = _validate_box(box) if not isinstance(solvent, (list, set)): solvent = [solvent] if not isinstance(n_solvent, (list, set)): n_solvent = [n_solvent] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation] * len(solvent) if len(solvent) != len(n_solvent): msg = "`n_solvent` and `n_solvent` must be of equal length." raise ValueError(msg) # In angstroms for packmol. box_mins = box.mins * 10 box_maxs = box.maxs * 10 overlap *= 10 center_solute = (box_maxs + box_mins) / 2 # Apply edge buffer box_maxs -= edge * 10 # Build the input file for each compound and call packmol. solvated_xyz = _new_xyz_file() solute_xyz = _new_xyz_file() # generate list of temp files for the solvents solvent_xyz_list = list() try: solute.save(solute_xyz.name, overwrite=True) input_text = PACKMOL_HEADER.format( overlap, solvated_xyz.name, seed, sidemax * 10) + PACKMOL_SOLUTE.format(solute_xyz.name, *center_solute) for solv, m_solvent, rotate in zip(solvent, n_solvent, fix_orientation): m_solvent = int(m_solvent) solvent_xyz = _new_xyz_file() solvent_xyz_list.append(solvent_xyz) solv.save(solvent_xyz.name, overwrite=True) input_text += PACKMOL_BOX.format( solvent_xyz.name, m_solvent, box_mins[0], box_mins[1], box_mins[2], box_maxs[0], box_maxs[1], box_maxs[2], PACKMOL_CONSTRAIN if rotate else "", ) _run_packmol(input_text, solvated_xyz, temp_file) # Create the topology and update the coordinates. solvated = Compound() solvated.add(solute) solvated = _create_topology(solvated, solvent, n_solvent) solvated.update_coordinates( solvated_xyz.name, update_port_locations=update_port_locations) finally: for file_handle in solvent_xyz_list: file_handle.close() os.unlink(file_handle.name) solvated_xyz.close() solute_xyz.close() os.unlink(solvated_xyz.name) os.unlink(solute_xyz.name) return solvated
def fill_region(compound, n_compounds, region, overlap=0.2, seed=12345, edge=0.2, fix_orientation=False, temp_file=None): """Fill a region of a box with a compound using packmol. Parameters ---------- compound : mb.Compound or list of mb.Compound Compound or list of compounds to be put in region. n_compounds : int or list of int Number of compounds to be put in region. region : mb.Box or list of mb.Box Region to be filled by compounds. overlap : float, units nm, default=0.2 Minimum separation between atoms of different molecules. seed : int, default=12345 Random seed to be passed to PACKMOL. edge : float, units nm, default=0.2 Buffer at the edge of the region to not place molecules. This is necessary in some systems because PACKMOL does not account for periodic boundary conditions in its optimization. fix_orientation : bool or list of bools Specify that compounds should not be rotated when filling the box, default=False. temp_file : str, default=None File name to write PACKMOL's raw output to. Returns ------- filled : mb.Compound If using mulitple regions and compounds, the nth value in each list are used in order. For example, if the third compound will be put in the third region using the third value in n_compounds. """ _check_packmol(PACKMOL) if not isinstance(compound, (list, set)): compound = [compound] if not isinstance(n_compounds, (list, set)): n_compounds = [n_compounds] if not isinstance(fix_orientation, (list, set)): fix_orientation = [fix_orientation]*len(compound) if compound is not None and n_compounds is not None: if len(compound) != len(n_compounds): msg = ("`compound` and `n_compounds` must be of equal length.") raise ValueError(msg) if compound is not None: if len(compound) != len(fix_orientation): msg = ("`compound`, `n_compounds`, and `fix_orientation` must be of equal length.") raise ValueError(msg) # See if region is a single region or list if isinstance(region, Box): # Cannot iterate over boxes region = [region] elif not any(isinstance(reg, (list, set, Box)) for reg in region): region = [region] region = [_validate_box(reg) for reg in region] # In angstroms for packmol. overlap *= 10 # Build the input file and call packmol. filled_pdb = tempfile.mkstemp(suffix='.pdb')[1] input_text = PACKMOL_HEADER.format(overlap, filled_pdb, seed) for comp, m_compounds, reg, rotate in zip(compound, n_compounds, region, fix_orientation): m_compounds = int(m_compounds) compound_pdb = tempfile.mkstemp(suffix='.pdb')[1] comp.save(compound_pdb, overwrite=True) reg_mins = reg.mins * 10 reg_maxs = reg.maxs * 10 reg_maxs -= edge * 10 # Apply edge buffer input_text += PACKMOL_BOX.format(compound_pdb, m_compounds, reg_mins[0], reg_mins[1], reg_mins[2], reg_maxs[0], reg_maxs[1], reg_maxs[2], PACKMOL_CONSTRAIN if rotate else "") _run_packmol(input_text, filled_pdb, temp_file) # Create the topology and update the coordinates. filled = Compound() for comp, m_compounds in zip(compound, n_compounds): for _ in range(m_compounds): filled.add(clone(comp)) filled.update_coordinates(filled_pdb) return filled