def from_snapshot(snapshot, scale=1.0): """Convert a Snapshot to a Compound. Snapshot can be a hoomd.data.Snapshot or a gsd.hoomd.Snapshot. Parameters ---------- snapshot : hoomd._hoomd.SnapshotSystemData_float or gsd.hoomd.Snapshot Snapshot from which to build the mbuild Compound. scale : float, optional, default 1.0 Value by which to scale the length values Returns ------- comp : Compound Note ---- GSD and HOOMD snapshots center their boxes on the origin (0,0,0), so the compound is shifted by half the box lengths """ comp = Compound() bond_array = snapshot.bonds.group n_atoms = snapshot.particles.N if "SnapshotSystemData_float" in dir(hoomd._hoomd) and isinstance( snapshot, hoomd._hoomd.SnapshotSystemData_float ): # hoomd v2 box = snapshot.box comp.box = Box.from_lengths_tilt_factors( lengths=np.array([box.Lx, box.Ly, box.Lz]) * scale, tilt_factors=np.array([box.xy, box.xz, box.yz]), ) else: # gsd / hoomd v3 box = snapshot.configuration.box comp.box = Box.from_lengths_tilt_factors( lengths=box[:3] * scale, tilt_factors=box[3:] ) # GSD and HOOMD snapshots center their boxes on the origin (0,0,0) shift = np.array(comp.box.lengths) / 2 # Add particles for i in range(n_atoms): name = snapshot.particles.types[snapshot.particles.typeid[i]] xyz = snapshot.particles.position[i] * scale + shift charge = snapshot.particles.charge[i] atom = Particle(name=name, pos=xyz, charge=charge) comp.add(atom, label=str(i)) # Add bonds particle_dict = {idx: p for idx, p in enumerate(comp.particles())} for i in range(bond_array.shape[0]): atom1 = int(bond_array[i][0]) atom2 = int(bond_array[i][1]) comp.add_bond([particle_dict[atom1], particle_dict[atom2]]) return comp
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 reverse_map_molecule(molecule, target, mapping_moieties): print('unique') cg_molecule = clone(molecule) # CG molecule aa_template = target[molecule.name] # full aa Compound for molecule aa_moieties = mapping_moieties[ molecule.name] # list of lists of indices for each bead aa_molecule = Compound() # this will have the final aa molecule cg_to_aa = [] # list of tuples containing (real index, aa atom) # now cycle through beads for index, bead in enumerate(cg_molecule.particles()): aa_atoms = Compound() # placeholder for molecule atoms [ aa_atoms.add(clone(aa_template.children[i])) for i in aa_moieties[index] ] aa_atoms.translate_to(bead.pos) # shift to cg_bead position cg_to_aa += list(zip(aa_moieties[index], aa_atoms.children)) # sort atoms in cg_to_aa and add them to the aa_molecule cg_to_aa = sorted(cg_to_aa) for atom in cg_to_aa: aa_molecule.add(clone(atom[1])) # add bonds from the template aa_template = aa_template.to_trajectory() for i, j in aa_template.top.bonds: aa_molecule.add_bond([aa_molecule[i.index], aa_molecule[j.index]]) # equilibrate molecule and shift back to center # if the atom names match OpenBabel forcefield naming convention: try: aa_molecule.energy_minimization(steps=2500) # otherwise rename with just element names: except: atomnames = [i.name for i in aa_molecule] # get the atomnames for atom in aa_molecule: # make the atomnames elements atom.name = atom.name[0] aa_molecule.energy_minimization(steps=2500) for i, atom in enumerate(atomnames): aa_molecule[i].name = atomnames[i] #aa_molecule.translate(center) aa_molecule.name = molecule.name return aa_molecule
def __init__(self, anchor=None, orientation=None, separation=0): super(Port, self).__init__(name='Port', port_particle=True) self.anchor = anchor up = Compound(name='subport', port_particle=True) up.add( Particle(name='G', pos=[0.005, 0.0025, -0.0025], port_particle=True), 'middle') up.add( Particle(name='G', pos=[0.005, 0.0225, -0.0025], port_particle=True), 'top') up.add( Particle(name='G', pos=[-0.015, -0.0075, -0.0025], port_particle=True), 'left') up.add( Particle(name='G', pos=[0.005, -0.0175, 0.0075], port_particle=True), 'right') down = clone(up) down.rotate(np.pi, [0, 0, 1]) self.add(up, 'up') self.add(down, 'down') self.used = False if orientation is None: orientation = [0, 1, 0] default_direction = [0, 1, 0] if np.array_equal(np.asarray(default_direction), unit_vector(-np.asarray(orientation))): self.rotate(np.pi, [1, 0, 0]) elif np.array_equal(np.asarray(default_direction), unit_vector(np.asarray(orientation))): pass else: normal = np.cross(default_direction, orientation) self.rotate(angle(default_direction, orientation), normal) if anchor: self.translate_to(anchor.pos) self.translate(separation * unit_vector(orientation))
def __init__(self, anchor=None, orientation=None, separation=0): super(Port, self).__init__(name='Port', port_particle=True) self.anchor = anchor up = Compound(name='subport', port_particle=True) up.add(Particle(name='G', pos=[0.005, 0.0025, -0.0025], port_particle=True), 'middle') up.add(Particle(name='G', pos=[0.005, 0.0225, -0.0025], port_particle=True), 'top') up.add(Particle(name='G', pos=[-0.015, -0.0075, -0.0025], port_particle=True), 'left') up.add(Particle(name='G', pos=[0.005, -0.0175, 0.0075], port_particle=True), 'right') down = clone(up) down.rotate(np.pi, [0, 0, 1]) self.add(up, 'up') self.add(down, 'down') self.used = False if orientation is None: orientation = [0, 1, 0] default_direction = [0, 1, 0] if np.array_equal( np.asarray(default_direction), unit_vector(-np.asarray(orientation))): self.rotate(np.pi, [1, 0, 0]) elif np.array_equal( np.asarray(default_direction), unit_vector(np.asarray(orientation))): pass else: normal = np.cross(default_direction, orientation) self.rotate(angle(default_direction, orientation), normal) if anchor: self.translate_to(anchor.pos) self.translate(separation*unit_vector(orientation))
def __init__(self, anchor=None, orientation=None, separation=0): super(Port, self).__init__(name="Port", port_particle=True) self.anchor = anchor default_direction = np.array([0, 1, 0]) if orientation is None: orientation = [0, 1, 0] orientation = np.asarray(orientation) up = Compound(name="subport", port_particle=True) pos = [0.005, 0.0025, -0.0025] up.add(Particle(name="G", pos=pos, port_particle=True), "middle") pos = [0.005, 0.0225, -0.0025] up.add(Particle(name="G", pos=pos, port_particle=True), "top") pos = [-0.015, -0.0075, -0.0025] up.add(Particle(name="G", pos=pos, port_particle=True), "left") pos = [0.005, -0.0175, 0.0075] up.add(Particle(name="G", pos=pos, port_particle=True), "right") down = clone(up) self.add(up, "up") self.add(down, "down") self.used = False if np.allclose(default_direction, unit_vector(-orientation)): down.rotate(np.pi, [0, 0, 1]) self.rotate(np.pi, [0, 0, 1]) elif np.allclose(default_direction, unit_vector(orientation)): down.rotate(np.pi, [0, 0, 1]) else: normal = np.cross(default_direction, orientation) self.rotate(angle(default_direction, orientation), normal) down.rotate(np.pi, normal) if anchor: self.translate_to(anchor.pos) self.translate(separation * unit_vector(orientation))
def __init__(self, anchor=None): super(Port, self).__init__(name='Port', port_particle=True) self.anchor = anchor up = Compound(name='subport', port_particle=True) up.add(Particle(name='G', pos=[0, 0, 0], port_particle=True), 'middle') up.add(Particle(name='G', pos=[0, 0.02, 0], port_particle=True), 'top') up.add(Particle(name='G', pos=[-0.02, -0.01, 0], port_particle=True), 'left') up.add(Particle(name='G', pos=[0.0, -0.02, 0.01], port_particle=True), 'right') down = clone(up) rotate_around_z(down, np.pi) self.add(up, 'up') self.add(down, 'down')
def __init__(self, anchor=None): super(Port, self).__init__(name='Port', port_particle=True) self.anchor = anchor up = Compound(name='subport', port_particle=True) up.add(Particle(name='G', pos=[0, 0, 0], port_particle=True), 'middle') up.add(Particle(name='G', pos=[0, 0.02, 0], port_particle=True), 'top') up.add(Particle(name='G', pos=[-0.02, -0.01, 0], port_particle=True), 'left') up.add(Particle(name='G', pos=[0.0, -0.02, 0.01], port_particle=True), 'right') down = clone(up) rotate_around_z(down, np.pi) self.add(up, 'up') self.add(down, 'down') self.used = False if anchor: translate_to(self, anchor.pos)
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_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_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_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
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 reverse_map_solvent(cg_molecule, target, sol_per_bead=4, cutoff=2, scaling_factor=5): """ molecules: list of water beads target: single atomistic solvent molecule sol_per_bead: number of atomistic solvent molecules per CG bead cutoff: max distance an atomistic molecule can be placed from the center of the CG bead """ solvent = Compound() # will contain all the solvent molecules in a bead solvent_molecule = Compound() # placeholder for each single molecule # for each atomistic molcule print("unique") for i in range(sol_per_bead): # get a random vector by which to shift the atomistic molecule """ randx = cutoff * (1 - 2 * np.random.rand()) randy = np.sqrt(cutoff**2 - randx**2) * (1 - 2 * np.random.rand()) # randy bobandy randz = np.sqrt(cutoff**2 - randx**2 - randy**2) * (1 - 2 * np.random.rand()) """ randx = 0.0 randy = 0.0 randz = 0.0 if i == 0: randx += 1.0 elif i == 1: randx -= 1.0 elif i == 2: randy += 1.0 elif i == 3: randy -= 1.0 shift_vec = np.array([randx, randy, randz]) shift_vec *= 0.2 #np.random.shuffle(shift_vec) # get random angles to spin the solvent molecule by theta = 2 * np.pi * np.random.rand() phi = np.pi * np.random.rand() # make a solvent molecule compound and shift it to the correct position solvent_molecule = clone(target) solvent_molecule.translate_to(cg_molecule.pos + shift_vec) solvent_molecule.spin(theta, [0, 0, 1]) solvent_molecule.spin(phi, [1, 0, 0]) # add the molecule to the bead compound solvent.add(solvent_molecule) # time to minimize the energy! # try with current atom names """ try: solvent.energy_minimization(steps=500) # otherwise rename with just element names: except: atomnames = [i.name for i in solvent] # get the atomnames for atom in solvent: # make the atomnames elements atom.name=atom.name[0] solvent.energy_minimization(steps=500) for i, atom in enumerate(atomnames): solvent[i].name = atomnames[i] """ # scale the solvent by 5 # get a list of individual molecules (so that it separates them into residues) solvent = [clone(child) for child in solvent.children] for i, solvent_compound in enumerate(solvent): solvent[i].name = cg_molecule.name solvent[i].translate_to(np.array(solvent[i].pos) * scaling_factor) return solvent
def reverse_map(coarse_grained, mapping_moieties, target=None, solvent_name=None, sol_per_bead=4, sol_cutoff=2, scaling_factor=5, parallel=True): """ Reverse map an mb.Compound Parameters --------- coarse_grained : mb.Compound original structure. Generated from a MDTraj trajectory mapping_moieties : dictionary Relate CG molecule names to a list of finer-detailed mbuild Compounds. Care must be taken that bead indices match with list indices. target_structure : dictionary mb.Compound, optional, default=False A target atomistic structure which can be used to reconstruct bonding. Bond network in the reverse-mapped structure will be completely overridden by the bond network from the target atomistic structure Care must be taken that atom indices match perfectly """ aa_system = Compound() not_solvent = [ mol for mol in coarse_grained.children if mol.name != solvent_name ] is_solvent = [ mol for mol in coarse_grained.children if mol.name == solvent_name ] print( "There are {} non-solvent molecules and {} solvent molecules.".format( len(not_solvent), len(is_solvent))) # For each bead, replace it with the appropriate mb compound # Iterate through each molecule (set of particles that are bonded together) if parallel: pool = mp.Pool(processes=mp.cpu_count()) # get the solvent molecules mapped in parallel inp = zip(is_solvent, [target[solvent_name]] * len(is_solvent), [sol_per_bead] * len(is_solvent), [sol_cutoff] * len(is_solvent)) chunksize = int(len(is_solvent) / mp.cpu_count()) + 1 solvent_list = pool.starmap(reverse_map_solvent, inp, chunksize) # name the solvents # get the non_solvent molecules mapped in parallel inp = zip(not_solvent, [target] * len(not_solvent), [mapping_moieties] * len(not_solvent)) chunksize = int(len(not_solvent) / mp.cpu_count()) + 1 molecule_list = pool.starmap(reverse_map_molecule, inp, chunksize) # put put solvents in one list solvent_molecule_list = [] for i in solvent_list: solvent_molecule_list += i # put lipids in a box and get the box size for molecule in molecule_list: aa_system.add(molecule) print(aa_system.boundingbox) # put everything in a box for molecule in solvent_molecule_list: aa_system.add(molecule) else: [ aa_system.add( reverse_map_molecule(molecule, target, mapping_moieties)) for molecule in not_solvent ] solvent_compound = reverse_map_solvent(is_solvent, target[solvent_name], sol_per_bead, sol_cutoff) [aa_system.add(molecule) for molecule in solvent_compound.children] return aa_system