Esempio n. 1
0
def get_center_of_mass(atoms, skip_H=False):
    '''
    Calculate the center of mass of the molecule.

    **Parameters**

        atoms: *list,* :class:`structures.atom.Atom`
            A list of atoms.
        skip_H: *bool, optional*
            Whether to include Hydrogens in the
            calculation (False), or not (True).

    **Returns**

        com: *np.array, float*
            A np.array of the x, y, and z coordinate of the center of mass.
    '''
    if len(atoms) == 0:
        return (0.0, 0.0, 0.0)

    if skip_H:
        local_atoms = np.array(
            [a.flatten() for a in atoms if a.element != "H"])
        masses = np.array([
            units.elem_weight(a.element) for a in atoms if a.element != "H"
        ]).reshape((-1, 1))
    else:
        local_atoms = np.array([a.flatten() for a in atoms])
        masses = np.array([units.elem_weight(a.element)
                           for a in atoms]).reshape((-1, 1))

    return sum(local_atoms * masses) / sum(masses)
Esempio n. 2
0
    def __init__(self, index=None, charge=None,
                 mass=None, element=None, line=None):
        # How many parameters exist in this potential
        self.N_params = 1

        if line is not None and all([x is None for x in [index, charge]]):
            self.assign_line(line)
        elif line is None and all([x is not None for x in [index, charge]]):
            assert not isinstance(index, list),\
                "In Coul, index is a list, not a string/int!"
            self.index, self.charge, self.mass, self.element =\
                index, charge, mass, element
        else:
            raise Exception("\
Either specify index and charge, or the line to be parsed, but not both.")

        # Assign default bounds
        self.charge_bounds = tuple(
            sorted(
                [np.sign(self.charge) * max(abs(self.charge) * 0.5,
                                            CHARGE_LOWER_LIMIT),
                 np.sign(self.charge) * min(abs(self.charge) * 1.5,
                                            CHARGE_UPPER_LIMIT)]))

        # Set a default mass if mass is None
        if self.mass is None and self.element is not None:
            self.mass = elem_weight(self.element)

        self.validate()
Esempio n. 3
0
    def generate(cls, atom_types, elems, signs):
        '''
        Randomly generate parameters for coulomb.

        **Parameters**

            atom_types: *list, str*
                A list of all the atom types to have parameters generated for.
            elems: *list, str*
                List of the elements (in the same order as atom_types).
            signs: *list, float*
                The list of the signs of the charges (in the same order as
                the atom_types).

        **Returns**

            coul_objs: *list,* :class:`squid.forcefields.coulomb.Coul`
                Returns a list of Coul objects.
        '''
        coul_objs = []

        for atype, elem, sign in zip(atom_types, elems, signs):
            assert sign in [-1.0, 1.0],\
                "Error - sign is not valid! Must be -1.0 or 1.0"
            charge_bounds = tuple(sorted([
                CHARGE_LOWER * float(sign), CHARGE_UPPER * float(sign)]))
            charge = random_in_range(charge_bounds)
            mass = elem_weight(elem)
            coul_objs.append(cls(atype, charge, mass, elem))
            coul_objs[-1].charge_bounds = charge_bounds

        return coul_objs
Esempio n. 4
0
def get_rg(mol, indices=None):

    (c_x, c_y, c_z) = mol.get_center_of_mass()
    rx_2 = 0
    ry_2 = 0
    rz_2 = 0
    mol_wt = 0
    if not indices:
        indices = list(range(1, len(mol.atoms) + 1))
    for a, atom in enumerate(mol.atoms):
        if a + 1 in indices:
            m_i = units.elem_weight(atom.element)
            mol_wt += m_i 
            (x_i, y_i, z_i) = atom.flatten()
            rx_2 += m_i*((x_i-c_x)**2)
            ry_2 += m_i*((y_i-c_y)**2)
            rz_2 += m_i*((z_i-c_z)**2)
        
    rg = math.sqrt((rx_2 + ry_2 + rz_2)/mol_wt)

    return rg
Esempio n. 5
0
    def get_atom_masses(self):
        '''
        This simplifies using data file writing by getting the masses of all
        the atoms in the correct order.

        **Returns**

            masses: *list, float*
                A list of the masses for each atom type.
        '''
        assert self.atom_labels is not None,\
            "Error - You must first run set_types before this works."

        masses = []
        for a in self.atom_labels:
            if a in self.parameters.coul_params:
                index = self.parameters.coul_params.index(a)
                mass = self.parameters.coul_params[index].mass
                masses.append(mass)
            else:
                masses.append(elem_weight(a))

        return masses
Esempio n. 6
0
def quick_min(params,
              gradient,
              NEB_obj=None,
              new_opt_params={}):
    '''
    A quick min optimizer, overloaded for NEB use.
    Note, this will ONLY work for use within the NEB code.

    *Parameters*

        params: *list, float*
            A list of parameters to be optimized.
        gradient: *func*
            A function that, given params, returns the gradient.
        NEB_obj: :class:`neb.NEB`
            An NEB object to use.
        new_opt_params: *dict*
            A dictionary holding any changes to the optimization algorithm's
            parameters.  This includes the following -

                dt: *float*
                    Time step size to take.
                max_step: *float*
                    The maximum step size to take.
                viscosity: *float*
                    The viscosity within a verlet step (used if euler is
                    False).
                euler: *bool*
                    Whether to make an euler step or not.
                maxiter: *int*
                    Maximum number of iterations for the optimizer to run.
                    If None, then the code runs indefinitely.
                g_rms: *float*
                    The RMS value for which to optimize the gradient to.
                g_max: *float*
                    The maximum gradient value to be allowed.
                fit_rigid: *bool*
                    Remove erroneous rotation and translations during NEB.
                verbose: *bool*
                    Whether to have additional output.
                callback: *func, optional*
                    A function to be run after each optimization loop.

    *Returns*

        params: *list, float*
            A list of the optimized parameters.
        code: *int*
            An integer describing how the algorithm converged.  This can be
            identified in the constants file.
        iters: *int*
            The number of iterations the optimizer ran for.
    '''

    # Here we adjust parameters accordingly ----------------------------------
    dt = 0.001
    max_step = 0.2
    viscosity = 0.0
    euler = False
    maxiter = 1000
    g_rms = 1E-3
    g_max = 1E-3
    fit_rigid = True
    verbose = False
    callback = None

    loop_counter = 0

    opt_params = {"dt": dt,
                  "max_step": max_step,
                  "viscosity": viscosity,
                  "euler": euler,
                  "maxiter": maxiter,
                  "g_rms": g_rms,
                  "g_max": g_max,
                  "fit_rigid": fit_rigid,
                  "verbose": verbose,
                  "callback": callback}

    for name in new_opt_params:
        if name not in opt_params:
            raise Exception("Parameter %s not available in \
    quick min" % name)
        else:
            opt_params[name] = new_opt_params[name]

    dt = opt_params['dt']
    max_step = opt_params['max_step']
    viscosity = opt_params['viscosity']
    euler = opt_params['euler']
    maxiter = opt_params['maxiter']
    g_rms = opt_params['g_rms']
    g_max = opt_params['g_max']
    fit_rigid = opt_params['fit_rigid']
    verbose = opt_params['verbose']
    callback = opt_params['callback']

    def _vproj(v1, v2):
        '''
        Returns the projection of v1 onto v2
        Parameters:
            v1, v2: np vectors
        '''
        mag2 = np.linalg.norm(v2)**2
        if mag2 == 0:
            print("Can't project onto a zero vector")
            return v1
        return v2 * np.dot(v1, v2) / mag2

    v = np.array([0.0 for x in params])
    acc = np.array([0.0 for x in params])

    if not euler:
        masses = []
        for s in NEB_obj.states[1:-1]:
            for a in s:
                m = units.elem_weight(a.element)
                masses += [m, m, m]
        masses = np.array(masses) * 1E-3  # Convert to kg

    # Done adjusting parameters ----------------------------------------------

    # Now we do our header
    print("\nRunning neb with optimization method quick min")
    print("\tdt = %lg" % dt)
    print("\tmax_step = %lg" % max_step)
    if euler:
        print("\tAn euler step will be made")
    else:
        print("\tA verlet step of viscosity %lg will be made" % viscosity)
    print("\tWill%s use procrustes to remove rigid rotations and translations"
          % ("" if fit_rigid else " NOT"))
    print("Convergence Criteria:")

    def f(x):
        return units.convert("Ha/Ang", "eV/Ang", x)

    print("\tg_rms = %lg (Ha/Ang) = %lg (eV/Ang)" % (g_rms, f(g_rms)))
    print("\tg_max = %lg (Ha/Ang) = %lg (eV/Ang)" % (g_max, f(g_max)))
    print("\tmaxiter = %d" % maxiter)
    print("---------------------------------------------")
    # Done with header

    if maxiter is None:
        maxiter = float('inf')

    while loop_counter < maxiter:
        # Pre-check to see if we have already converged
        if (NEB_obj is not None and
            (NEB_obj.RMS_force < g_rms or
                NEB_obj.MAX_force < g_max)):
            break

        # If we are using NEB and want to align coordinates, do so.
        if fit_rigid and NEB_obj is not None:
            aligned = NEB_obj.align_coordinates(params, [v])
            params = aligned['r']
            v = aligned['B'][0]

        forces = np.array(-gradient(params))
        forces = np.array([units.convert_energy("Ha", "J", f2) * 1E20 for f2 in forces])

        # Deal with zeroing velocity or getting its direction here
        if np.dot(forces, v) > 0.0:
            v = _vproj(v, forces)
        else:
            v *= 0.0
            if verbose:
                print('Zeroed Velocities')

        if euler:
            v += dt * forces
            dx = v * dt
        else:
            # Else, make a Verlet step
            # Note, integration method used here takes average of
            # new and old accelerations during velocity update.
            # a_new = min((forces - v * viscosity) / masses, a_max)
            a_new = (forces - v * viscosity) / masses
            v = v + (acc + a_new) * 0.5 * dt
            acc = a_new
            dx = v * dt + 0.5 * acc * dt**2

        largest_step = max(np.linalg.norm(dx.reshape((-1, 3)), axis=1))
        if largest_step > max_step:
            dx *= max_step / largest_step

        params += dx

        loop_counter += 1

        if callback is not None:
            callback(params)

    # Deal with returns here
    to_return = [params]
    if NEB_obj is not None:
        if NEB_obj.RMS_force < g_rms:
            to_return.append(G_RMS_CONVERGENCE)
        elif NEB_obj.MAX_force < g_max:
            to_return.append(G_MAX_CONVERGENCE)
        elif loop_counter >= maxiter:
            to_return.append(MAXITER_CONVERGENCE)
        else:
            to_return.append(FAIL_CONVERGENCE)
    else:
        if loop_counter >= maxiter:
            to_return.append(MAXITER_CONVERGENCE)
        else:
            to_return.append(FAIL_CONVERGENCE)

    to_return.append(loop_counter)
    return tuple(to_return)
Esempio n. 7
0
def fire(params, gradient, NEB_obj=None, new_opt_params={}):
    '''
    A FIRE optimizer, overloaded for NEB use.

    *Parameters*

        params: *list, float*
            A list of parameters to be optimized.
        gradient: *func*
            A function that, given params, returns the gradient.
        NEB_obj: :class:`neb.NEB`
            An NEB object to use.
        new_opt_params: *dict*
            A dictionary holding any changes to the optimization algorithm's
            parameters.  This includes the following -

                dt: *float*
                    Time step size to take.
                dtmax: *float, optional*
                    The maximum dt allowed.
                max_step: *float*
                    The maximum step size to take.
                Nmin: *int, optional*
                    The minimum number of steps before acceleration occurs.
                finc: *float, optional*
                    The factor by which dt increases.
                fdec: *float, optional*
                    The factor by which dt decreases.
                astart: *float, optional*
                    The starting acceleration.
                fa: *float, optional*
                    The factor by which the acceleration is scaled.
                viscosity: *float*
                    The viscosity within a verlet step (used if euler is
                    False).
                euler: *bool*
                    Whether to make an euler step or not.
                maxiter: *int*
                    Maximum number of iterations for the optimizer to run.
                    If None, then the code runs indefinitely.
                g_rms: *float*
                    The RMS value for which to optimize the gradient to.
                g_max: *float*
                    The maximum gradient value to be allowed.
                fit_rigid: *bool*
                    Remove erroneous rotation and translations during NEB.
                callback: *func, optional*
                    A function to be run after each optimization loop.

    *Returns*

        params: *list, float*
            A list of the optimized parameters.
        code: *int*
            An integer describing how the algorithm converged.  This can be
            identified in the constants file.
        iters: *int*
            The number of iterations the optimizer ran for.
    '''

    # Here we adjust parameters accordingly ----------------------------------
    dt = 0.1
    dtmax = 1.0
    max_step = 0.2
    Nmin = 5
    finc = 1.1
    fdec = 0.5
    astart = 0.1
    fa = 0.99
    viscosity = 0.0
    euler = False
    maxiter = 1000
    g_rms = 1E-3
    g_max = 1E-3
    fit_rigid = True
    callback = None
    unit = None

    loop_counter = 0

    opt_params = {
        "dt": dt,
        "dtmax": dtmax,
        "max_step": max_step,
        "Nmin": Nmin,
        "finc": finc,
        "fdec": fdec,
        "astart": astart,
        "fa": fa,
        "viscosity": viscosity,
        "euler": euler,
        "maxiter": maxiter,
        "g_rms": g_rms,
        "g_max": g_max,
        "fit_rigid": fit_rigid,
        "callback": callback,
        "unit": unit
    }

    for name in new_opt_params:
        if name not in opt_params:
            raise Exception("Parameter %s not available in \
    FIRE" % name)
        else:
            opt_params[name] = new_opt_params[name]

    dt = opt_params['dt']
    dtmax = opt_params['dtmax']
    max_step = opt_params['max_step']
    Nmin = opt_params['Nmin']
    finc = opt_params['finc']
    fdec = opt_params['fdec']
    astart = opt_params['astart']
    fa = opt_params['fa']
    viscosity = opt_params['viscosity']
    euler = opt_params['euler']
    maxiter = opt_params['maxiter']
    g_rms = opt_params['g_rms']
    g_max = opt_params['g_max']
    fit_rigid = opt_params['fit_rigid']
    callback = opt_params['callback']
    unit = opt_params['unit']

    v = np.array([0.0 for x in params])
    Nsteps = 0
    acc = astart
    accel = np.array([0.0 for x in params])

    if not euler:
        masses = []
        for s in NEB_obj.states[1:-1]:
            for a in s:
                m = units.elem_weight(a.element)
                masses += [m, m, m]
        masses = np.array(masses) * 1E-3  # Convert to kg

    # Done adjusting parameters ----------------------------------------------

    # Now we do our header
    print("\nRunning neb with optimization method FIRE")
    print("\tdt = %lg" % dt)
    print("\tdtmax = %lg" % dtmax)
    print("\tmax_step = %lg" % max_step)
    print("\tNmin = %d" % Nmin)
    print("\tfinc = %lg" % finc)
    print("\tfdec = %lg" % fdec)
    print("\tastart = %lg" % astart)
    print("\tfa = %lg" % fa)
    print(
        "\tWill%s use procrustes to remove rigid rotations and translations" %
        ("" if fit_rigid else " NOT"))
    if euler:
        print("\tAn euler step will be made")
    else:
        print("\tA verlet step of viscosity %lg will be made" % viscosity)
    print("Convergence Criteria:")

    def f(x):
        return units.convert("Ha/Ang", "eV/Ang", x)

    print("\tg_rms = %lg (Ha/Ang) = %lg (eV/Ang)" % (g_rms, f(g_rms)))
    print("\tg_max = %lg (Ha/Ang) = %lg (eV/Ang)" % (g_max, f(g_max)))
    print("\tmaxiter = %d" % maxiter)
    print("---------------------------------------------")
    # Done with header

    if maxiter is None:
        maxiter = float('inf')

    while loop_counter < maxiter:
        # Pre-check to see if we have already converged
        if (NEB_obj is not None
                and (NEB_obj.RMS_force < g_rms or NEB_obj.MAX_force < g_max)):
            break

        # If we are using NEB and want to align coordinates, do so.
        if fit_rigid and NEB_obj is not None:
            aligned = NEB_obj.align_coordinates(params, [v])
            params = aligned['r']
            v = aligned['B'][0]

        forces = np.array(-gradient(params))
        forces = np.array(
            [units.convert_energy("Ha", "J", f2) * 1E20 for f2 in forces])
        # if unit is not None:
        #     forces = np.array(
        #         [units.convert_energy("Ha", unit, f2) for f2 in forces])

        # On the first iteration, skip this as we don't know anything yet
        # so there's no need to start our slow down (fdec)
        if loop_counter > 0:
            if np.dot(v, forces) > 0.0:
                # If velocity in direction of forces, speed up
                v = ((1.0 - acc) * v + acc * np.linalg.norm(v) *
                     (forces / np.linalg.norm(forces)))
                if Nsteps > Nmin:
                    dt = min(dt * finc, dtmax)
                    acc *= fa
                Nsteps += 1
            else:
                # If not, slow down
                v *= 0.0
                acc = astart
                dt *= fdec
                Nsteps = 0

        if euler:
            v += dt * forces
            dx = v * dt
        else:
            # Else, make a Verlet step
            # Note, integration method used here takes average of
            # new and old accelerations during velocity update.
            a_new = (forces - v * viscosity) / masses
            v = v + (accel + a_new) * 0.5 * dt
            accel = a_new
            dx = v * dt + 0.5 * accel * dt**2

        # Limit large steps
        largest_step = max(np.linalg.norm(dx.reshape((-1, 3)), axis=1))
        if largest_step > max_step:
            dx *= max_step / largest_step

        # Method used by ASE to limit large steps. Instead of limiting by the
        # individual steps, it limits by the total step (total motion in
        # the system)
#        largest_step = np.sqrt(np.vdot(dx.flatten(), dx.flatten()))
#        if largest_step > max_step:
#            dx *= max_step / largest_step

# Move atoms
        params += dx
        loop_counter += 1

        if callback is not None:
            callback(params)

    # Deal with returns here
    to_return = [params]
    if NEB_obj is not None:
        if NEB_obj.RMS_force < g_rms:
            to_return.append(G_RMS_CONVERGENCE)
        elif NEB_obj.MAX_force < g_max:
            to_return.append(G_MAX_CONVERGENCE)
        elif loop_counter >= maxiter:
            to_return.append(MAXITER_CONVERGENCE)
        else:
            to_return.append(FAIL_CONVERGENCE)
    else:
        if loop_counter >= maxiter:
            to_return.append(MAXITER_CONVERGENCE)
        else:
            to_return.append(FAIL_CONVERGENCE)

    to_return.append(loop_counter)
    return tuple(to_return)
Esempio n. 8
0
def packmol(system_obj,
            molecules,
            molecule_ratio=(1, ),
            density=1.0,
            seed=1,
            persist=True,
            number=None,
            additional="",
            custom=None,
            extra_block_at_beginning='',
            extra_block_at_end='',
            tolerance=2.0):
    '''
    Given a list of molecules, pack this system appropriately.  Note,
    we now will pack around what is already within the system!  This is
    done by first generating a packmol block for the system at hand,
    followed by a block for the solvent.

    A custom script is also allowed; however, if this path is chosen, then
    ensure all file paths for packmol exist.  We change directories within
    this function to a sys_packmol folder, where all files are expected to
    reside.  When a custom script is specified, the packmol script file is
    verbatim whatever the custom script is defined as.  Otherwise, the
    script will take on the following form (All capitals are filled in by
    the function or as input variables).  In the case of NUMBER, if it is
    set as None, then an internal value is calculated based on density and
    system_obj size.

    .. code-block:: bash

        tolerance XXX
        filetype xyz
        output XXXXXX.packed.xyz
        seed XXX

        # If atoms already exist in the system object
        structure XXXXXX_fixed.xyz
        number 1
        fixed CENTER_OF_MASS 0. 0. 0.
        centerofmass
        end structure

        EXTRA_BLOCK_AT_BEGINNING

        # For each molecule, the following is done
        structure MOL_i.xyz
        number NUMBER
        inside box A B C D E F
        ADDITIONAL
        end structure

        EXTRA_BLOCK_AT_END


    **Parameters**

        system_obj: :class:`squid.structures.system.System`
            The system object to pack the molecules into.
        molecules: *list,* :class:`squid.structures.molecule.Molecule`
            Molecules to be added to this system.
        molecule_ratio: *tuple, float, optional*
            The ration that each molecule in *molecules* will be added to
            the system.
        density: *float, optional*
            The density of the system in g/mL
        seed: *float, optional*
            Seed for random generator.
        persist: *bool, optional*
            Whether to maintain the generated sys_packmol directory or
            not.
        number: *int or list, int, optional*
            Overide density and specify the exact number of molecules to
            pack. When using a list of molecules, you must specify each
            in order within a list.
        additional: *str, optional*
            Whether to add additional constraints to the standard packmol
            setup.
        custom: *str, optional*
            A custom packmol script to run for the given input molecules.
            Note, you should ensure all necessary files are within the
            sys_packmol folder if using this option.
        extra_block_at_beginning: *str, optional*
            An additional block to put prior to the standard block.
        extra_block_at_end: *str, optional*
            An additional block to put after the standard block.
        tolerance: *float, optional*
            The tolerance around which we allow atomic overlap/proximity.

    **Returns**

        None

    **References**

        * Packmol - http://www.ime.unicamp.br/~martinez/packmol/home.shtml

    '''
    if not os.path.exists('sys_packmol'):
        os.mkdir('sys_packmol')
    os.chdir('sys_packmol')

    assert is_array(molecules),\
        "Error - Molecules must be an array!"

    packmol_path = get_packmol_obj()

    f = open(system_obj.name + '.packmol', 'w')

    if custom is not None:
        f.write(custom)
        f.close()
    else:
        f.write('''
tolerance ''' + str(tolerance) + '''
filetype xyz
output ''' + system_obj.name + '''.packed.xyz
seed ''' + str(seed) + '''
''')

        # If the system already has atoms, then set them
        if system_obj.atoms is not None and len(system_obj.atoms) > 0:
            write_xyz(system_obj.atoms, "%s_fixed.xyz" % system_obj.name)
            center_of_mass = get_center_of_mass(system_obj.atoms)
            com = " ".join([str(com) for com in center_of_mass])
            f.write('''
structure ''' + system_obj.name + '''_fixed.xyz
number 1
fixed ''' + com + ''' 0. 0. 0.
centerofmass
end structure
''')

        # convert density to amu/angstrom^3. 1 g/mL = 0.6022 amu/angstrom^3
        density *= 0.6022
        average_molecular_weight = sum([
            (units.elem_weight(a.element)) * molecule_ratio[i]
            for i in range(len(molecules)) for a in molecules[i].atoms
        ]) / sum(molecule_ratio)
        count = (density * system_obj.box_size[0] * system_obj.box_size[1] *
                 system_obj.box_size[2] / average_molecular_weight)
        molecule_counts = [
            int(round(count * x / sum(molecule_ratio))) for x in molecule_ratio
        ]
        if number is not None:
            molecule_counts = number
            if type(number) is not list:
                molecule_counts = [molecule_counts]

        f.write(extra_block_at_beginning)
        lower = tuple([-x / 2.0 for x in system_obj.box_size])
        upper = tuple([x / 2.0 for x in system_obj.box_size])
        for i, m in enumerate(molecules):
            xyz_file = open('%s_%d.xyz' % (system_obj.name, i), 'w')
            xyz_file.write(str(len(m.atoms)) + '\nAtoms\n')
            for a in m.atoms:
                xyz_file.write('%s%d %f %f %f\n' %
                               (a.element, i, a.x, a.y, a.z))
            xyz_file.close()

            f.write('''
structure %s_%d.xyz
number %d
inside box %f %f %f %f %f %f''' % (
                (system_obj.name, i, molecule_counts[i]) + lower + upper) + '''
''' + additional + '''
end structure
''')
        f.write(extra_block_at_end)
        f.close()

    # Run packmol
    os.system(packmol_path + ' < ' + system_obj.name +
              '.packmol > packmol.log')
    atoms = read_xyz(system_obj.name + '.packed.xyz')
    os.chdir('..')

    # Now have a list of atoms with element = H0 for molecule 0,
    # H1 for molecule 1, etc
    i = 0
    offset = len(system_obj.atoms)
    while i < len(atoms):
        ints_in_element = [
            j for j, h in enumerate(atoms[i].element) if h.isdigit()
        ]
        if len(ints_in_element) == 0:
            # This is the fixed molecule that already exists in the system
            i += offset
            continue
        # More robust, now we handle > 10 molecules!
        molecule_number = int(atoms[i].element[min(ints_in_element):])
        molecule = molecules[molecule_number]
        system_obj.add(molecule)
        # Update positions of latest molecule
        for a in system_obj.atoms[-len(molecule.atoms):]:
            a.x, a.y, a.z = atoms[i].x, atoms[i].y, atoms[i].z
            i += 1

    if not persist:
        os.system("rm -rf sys_packmol")