def __call__(self, iterative): r'''When this point is reached, a complete time integration step was finished and PLUMED should be notified about this. ''' if not self.hooked: if log.do_high: log.hline() log("Reinitializing PLUMED") log.hline() if log.do_warning: log.warn("You are using PLUMED as a hook for your integrator. " "If PLUMED adds time-dependent forces (for instance " "when performing metadynamics) there is no energy " "conservation. The conserved quantity reported by " "YAFF is irrelevant in this case.") self.setup_plumed(timestep=iterative.timestep, restart=iterative.counter > 0) self.hooked = True # PLUMED provides a setEnergy command, which should pass the # current potential energy. It seems that this is never used, so we # don't pass anything for the moment. # current_energy = sum([part.energy for part in iterative.ff.parts[:-1] if not isinstance(part, ForcePartPlumed)]) # self.plumed.cmd("setEnergy", current_energy) self.plumedstep = iterative.counter self._internal_compute(None, None) self.plumed.cmd("update")
def apply_generators(system, parameters, ff_args): '''Populate the attributes of ff_args, prepares arguments for ForceField **Arguments:** system A System instance for which the force field object is being made ff_args An instance of the FFArgs class. parameters An instance of the Parameters, typically made by ``Parmaeters.from_file('parameters.txt')``. ''' # Collect all the generators that have a prefix. generators = {} for x in globals().values(): if isinstance(x, type) and issubclass(x, Generator) and x.prefix is not None: generators[x.prefix] = x() # Go through all the sections of the parameter file and apply the # corresponding generator. for prefix, section in parameters.sections.iteritems(): generator = generators.get(prefix) if generator is None: if log.do_warning: log.warn('There is no generator named %s.' % prefix) else: generator(system, section, ff_args)
def apply(self, par_table, system, ff_args): '''Generate terms for the system based on the par_table **Arguments:** par_table A dictionary with tuples of ffatypes is keys and lists of parameters as values. system The system for which the force field is generated. ff_args An instance of the FFArgs class. ''' if system.bonds is None: raise ValueError('The system must have bonds in order to define valence cross terms.') part_valence = ff_args.get_part_valence(system) for indexes in self.iter_indexes(system): key = tuple(system.get_ffatype(i) for i in indexes) par_list = par_table.get(key, []) if len(par_list) == 0 is None and log.do_warning: log.warn('No valence %s parameters found for atoms %s with key %s' % (self.prefix, indexes, key)) continue for pars in par_list: indexes0 = self.get_indexes0(indexes) indexes1 = self.get_indexes1(indexes) indexes2 = self.get_indexes2(indexes) args_01 = (pars[0], pars[3], pars[4]) + (self.ICClass0(*indexes0), self.ICClass1(*indexes1)) args_02 = (pars[1], pars[3], pars[5]) + (self.ICClass0(*indexes0), self.ICClass2(*indexes2)) args_12 = (pars[2], pars[4], pars[5]) + (self.ICClass1(*indexes1), self.ICClass2(*indexes2)) part_valence.add_term(self.VClass01(*args_01)) part_valence.add_term(self.VClass02(*args_02)) part_valence.add_term(self.VClass12(*args_12))
def apply(self, par_table, system, ff_args): '''Generate terms for the system based on the par_table **Arguments:** par_table A dictionary with tuples of ffatypes is keys and lists of parameters as values. system The system for which the force field is generated. ff_args An instance of the FFArgs class. ''' if system.bonds is None: raise ValueError('The system must have bonds in order to define valence terms.') part_valence = ff_args.get_part_valence(system) for indexes in self.iter_indexes(system): key = tuple(system.get_ffatype(i) for i in indexes) par_list = par_table.get(key, []) if len(par_list) == 0 and log.do_warning: log.warn('No valence %s parameters found for atoms %s with key %s' % (self.prefix, indexes, key)) continue for pars in par_list: vterm = self.get_vterm(pars, indexes) part_valence.add_term(vterm)
def apply(self, par_table, scale_table, system, ff_args): # Prepare the atomic parameters sigmas = np.zeros(system.natom) epsilons = np.zeros(system.natom) onlypaulis = np.zeros(system.natom, np.int32) for i in xrange(system.natom): key = (system.get_ffatype(i),) par_list = par_table.get(key, []) if len(par_list) == 0: if log.do_warning: log.warn('No MM3 parameters found for atom %i with fftype %s.' % (i, system.get_ffatype(i))) else: sigmas[i], epsilons[i], onlypaulis[i] = par_list[0] # Prepare the global parameters scalings = Scalings(system, scale_table[1], scale_table[2], scale_table[3]) # Get the part. It should not exist yet. part_pair = ff_args.get_part_pair(PairPotMM3) if part_pair is not None: raise RuntimeError('Internal inconsistency: the MM3 part should not be present yet.') pair_pot = PairPotMM3(sigmas, epsilons, onlypaulis, ff_args.rcut, ff_args.tr) nlist = ff_args.get_nlist(system) part_pair = ForcePartPair(system, nlist, scalings, pair_pot) ff_args.parts.append(part_pair)
def apply_lammps_generators(system, parameters): '''Populate the attributes of ff_args, prepares arguments for ForceField **Arguments:** system A System instance for which the force field object is being made parameters An instance of the Parameters, typically made by ``Parmaeters.from_file('parameters.txt')``. ''' # Collect all the generators that have a prefix. generators = {} for x in globals().values(): if isinstance(x, type) and issubclass( x, Generator) and x.prefix is not None: generators[x.prefix] = x() output = {} # Go through all the sections of the parameter file and apply the # corresponding generator. for prefix, section in parameters.sections.items(): generator = generators.get(prefix) if generator is None: if log.do_warning: log.warn( 'There is no generator named %s. It will be ignored.' % prefix) else: output[prefix] = generator(system, section) return output
def propagate(self): success = self.minimizer.propagate() self.x = self.minimizer.x if success == False: if log.do_warning: log.warn('Line search failed in optimizer. Aborting optimization. This is probably due to a dicontinuity in the energy or the forces. Check the truncation of the non-bonding interactions and the Ewald summation parameters.') return True return BaseOptimizer.propagate(self)
def set_standard_masses(self): """Initialize the ``masses`` attribute based on the atomic numbers.""" with log.section('SYS'): from molmod.periodic import periodic if self.masses is not None: if log.do_warning: log.warn('Overwriting existing masses with default masses.') self.masses = np.array([periodic[n].mass for n in self.numbers])
def g09log_to_hdf5(f, fn_log): """Convert Gaussian09 BOMD log file to Yaff HDF5 format. **Arguments:** f An open and writable HDF5 file. fn_log The name of the Gaussian log file. """ with log.section('G09H5'): if log.do_medium: log('Loading Gaussian 09 file \'%s\' into \'trajectory\' of HDF5 file \'%s\'' % ( fn_log, f.filename )) # First make sure the HDF5 file has a system description that is consistent # with the XYZ file. if 'system' not in f: raise ValueError('The HDF5 file must contain a system group.') if 'numbers' not in f['system']: raise ValueError('The HDF5 file must have a system group with atomic numbers.') natom = f['system/numbers'].shape[0] # Take care of the trajectory group tgrp = get_trajectory_group(f) # Take care of the pos and vel datasets dss = get_trajectory_datasets(tgrp, ('pos', (natom, 3)), ('vel', (natom, 3)), ('frc', (natom, 3)), ('time', (1,)), ('step', (1,)), ('epot', (1,)), ('ekin', (1,)), ('etot', (1,)), ) ds_pos, ds_vel, ds_frc, ds_time, ds_step, ds_epot, ds_ekin, ds_etot = dss # Load frame by frame row = get_last_trajectory_row(dss) for numbers, pos, vel, frc, time, step, epot, ekin, etot in _iter_frames_g09(fn_log): if (numbers != f['system/numbers']).any(): log.warn('The element numbers of the HDF5 and LOG file do not match.') write_to_dataset(ds_pos, pos, row) write_to_dataset(ds_vel, vel, row) write_to_dataset(ds_frc, frc, row) write_to_dataset(ds_time, time, row) write_to_dataset(ds_step, step, row) write_to_dataset(ds_epot, epot, row) write_to_dataset(ds_ekin, ekin, row) write_to_dataset(ds_etot, etot, row) row += 1 # Check number of rows check_trajectory_rows(tgrp, dss, row)
def apply(self, par_table, cpar_table, scale_table, mixing_rules, system, ff_args): # Prepare the atomic parameters amps = np.zeros(system.nffatype, float) bs = np.zeros(system.nffatype, float) for i in xrange(system.nffatype): key = (system.ffatypes[i],) par_list = par_table.get(key, []) if len(par_list) == 0: if log.do_warning: log.warn('No EXPREP parameters found for ffatype %s.' % system.ffatypes[i]) else: amps[i], bs[i] = par_list[0] # Prepare the cross parameters amp_cross = np.zeros((system.nffatype, system.nffatype), float) b_cross = np.zeros((system.nffatype, system.nffatype), float) for i0 in xrange(system.nffatype): for i1 in xrange(i0+1): cpar_list = cpar_table.get((system.ffatypes[i0], system.ffatypes[i1]), []) if len(cpar_list) == 0: if log.do_high: log('No EXPREP cross parameters found for ffatypes %s,%s. Mixing rule will be used' % (system.ffatypes[i0], system.ffatypes[i1])) else: amp_cross[i0,i1], b_cross[i0,i1] = cpar_list[0] if i0 != i1: amp_cross[i1,i0], b_cross[i1,i0] = cpar_list[0] # Prepare the global parameters scalings = Scalings(system, scale_table[1], scale_table[2], scale_table[3]) amp_mix, amp_mix_coeff = mixing_rules['A'] if amp_mix == 0: amp_mix_coeff = 0.0 elif amp_mix == 1: amp_mix_coeff = amp_mix_coeff[0] b_mix, b_mix_coeff = mixing_rules['B'] if b_mix == 0: b_mix_coeff = 0.0 elif b_mix == 1: b_mix_coeff = b_mix_coeff[0] # Get the part. It should not exist yet. part_pair = ff_args.get_part_pair(PairPotExpRep) if part_pair is not None: raise RuntimeError('Internal inconsistency: the EXPREP part should not be present yet.') pair_pot = PairPotExpRep( system.ffatype_ids, amp_cross, b_cross, ff_args.rcut, ff_args.tr, amps, amp_mix, amp_mix_coeff, bs, b_mix, b_mix_coeff, ) nlist = ff_args.get_nlist(system) part_pair = ForcePartPair(system, nlist, scalings, pair_pot) ff_args.parts.append(part_pair)
def _verify_hooks(self): with log.section('ENSEM'): thermo = None index_thermo = 0 baro = None index_baro = 0 # Look for the presence of a thermostat and/or barostat if hasattr(self.hooks, '__len__'): for index, hook in enumerate(self.hooks): if hook.method == 'thermostat': thermo = hook index_thermo = index elif hook.method == 'barostat': baro = hook index_baro = index elif self.hooks is not None: if self.hooks.method == 'thermostat': thermo = self.hooks elif self.hooks.method == 'barostat': baro = self.hooks # If both are present, delete them and generate TBCombination element if thermo is not None and baro is not None: from yaff.sampling.npt import TBCombination if log.do_warning: log.warn( 'Both thermostat and barostat are present separately and will be merged' ) del self.hooks[max(index_thermo, index_thermo)] del self.hooks[min(index_thermo, index_baro)] self.hooks.append(TBCombination(thermo, baro)) if hasattr(self.hooks, '__len__'): for hook in self.hooks: if hook.name == 'TBCombination': thermo = hook.thermostat baro = hook.barostat elif self.hooks is not None: if self.hooks.name == 'TBCombination': thermo = self.hooks.thermostat baro = self.hooks.barostat if log.do_warning: if thermo is not None: log('Temperature coupling achieved through ' + str(thermo.name) + ' thermostat') if baro is not None: log('Pressure coupling achieved through ' + str(baro.name) + ' barostat')
def _verify_hooks(self): with log.section('ENSEM'): thermo = None index_thermo = 0 baro = None index_baro = 0 # Look for the presence of a thermostat and/or barostat if hasattr(self.hooks, '__len__'): for index, hook in enumerate(self.hooks): if hook.method == 'thermostat': thermo = hook index_thermo = index elif hook.method == 'barostat': baro = hook index_baro = index elif self.hooks is not None: if self.hooks.method == 'thermostat': thermo = self.hooks elif self.hooks.method == 'barostat': baro = self.hooks # If both are present, delete them and generate TBCombination element if thermo is not None and baro is not None: from yaff.sampling.npt import TBCombination if log.do_warning: log.warn('Both thermostat and barostat are present separately and will be merged') del self.hooks[max(index_thermo, index_thermo)] del self.hooks[min(index_thermo, index_baro)] self.hooks.append(TBCombination(thermo, baro)) if hasattr(self.hooks, '__len__'): for hook in self.hooks: if hook.name == 'TBCombination': thermo = hook.thermostat baro = hook.barostat elif self.hooks is not None: if self.hooks.name == 'TBCombination': thermo = self.hooks.thermostat baro = self.hooks.barostat if log.do_warning: if thermo is not None: log('Temperature coupling achieved through ' + str(thermo.name) + ' thermostat') if baro is not None: log('Pressure coupling achieved through ' + str(baro.name) + ' barostat')
def detect_ffatypes(self, rules): """Initialize the ``ffatypes`` attribute based on ATSELECT rules. **Argument:** rules A list of (ffatype, rule) pairs that will be used to initialize the attributes ``self.ffatypes`` and ``self.ffatype_ids``. If the system already has FF atom types, they will be overwritten. """ with log.section('SYS'): # Give warning if needed if self.ffatypes is not None: if log.do_warning: log.warn('Overwriting existing FF atom types.') # Compile all the rules my_rules = [] for ffatype, rule in rules: check_name(ffatype) if isinstance(rule, str): rule = atsel_compile(rule) my_rules.append((ffatype, rule)) # Use the rules to detect the atom types lookup = {} self.ffatypes = [] self.ffatype_ids = np.zeros(self.natom, int) for i in range(self.natom): my_ffatype = None for ffatype, rule in my_rules: if rule(self, i): my_ffatype = ffatype break if my_ffatype is None: raise ValueError( 'Could not detect FF atom type of atom %i.' % i) ffatype_id = lookup.get(my_ffatype) if ffatype_id is None: ffatype_id = len(lookup) self.ffatypes.append(my_ffatype) lookup[my_ffatype] = ffatype_id self.ffatype_ids[i] = ffatype_id # Make sure all is done well ... self._init_derived_ffatypes()
def detect_ffatypes(self, rules): """Initialize the ``ffatypes`` attribute based on ATSELECT rules. **Argument:** rules A list of (ffatype, rule) pairs that will be used to initialize the attributes ``self.ffatypes`` and ``self.ffatype_ids``. If the system already has FF atom types, they will be overwritten. """ with log.section('SYS'): # Give warning if needed if self.ffatypes is not None: if log.do_warning: log.warn('Overwriting existing FF atom types.') # Compile all the rules my_rules = [] for ffatype, rule in rules: check_name(ffatype) if isinstance(rule, basestring): rule = atsel_compile(rule) my_rules.append((ffatype, rule)) # Use the rules to detect the atom types lookup = {} self.ffatypes = [] self.ffatype_ids = np.zeros(self.natom, int) for i in xrange(self.natom): my_ffatype = None for ffatype, rule in my_rules: if rule(self, i): my_ffatype = ffatype break if my_ffatype is None: raise ValueError('Could not detect FF atom type of atom %i.' % i) ffatype_id = lookup.get(my_ffatype) if ffatype_id is None: ffatype_id = len(lookup) self.ffatypes.append(my_ffatype) lookup[my_ffatype] = ffatype_id self.ffatype_ids[i] = ffatype_id # Make sure all is done well ... self._init_derived_ffatypes()
def apply(self, atom_table, bond_table, scale_table, dielectric, system, ff_args): if system.charges is None: system.charges = np.zeros(system.natom) elif log.do_warning and abs(system.charges).max() != 0: log.warn('Overwriting charges in system.') system.charges[:] = 0.0 system.radii = np.zeros(system.natom) # compute the charges for i in xrange(system.natom): pars = atom_table.get(system.get_ffatype(i)) if pars is not None: charge, radius = pars system.charges[i] += charge system.radii[i] = radius elif log.do_warning: log.warn('No charge defined for atom %i with fftype %s.' % (i, system.get_ffatype(i))) for i0, i1 in system.iter_bonds(): ffatype0 = system.get_ffatype(i0) ffatype1 = system.get_ffatype(i1) if ffatype0 == ffatype1: continue charge_transfer = bond_table.get((ffatype0, ffatype1)) if charge_transfer is None: if log.do_warning: log.warn('No charge transfer parameter for atom pair (%i,%i) with fftype (%s,%s).' % (i0, i1, system.get_ffatype(i0), system.get_ffatype(i1))) else: system.charges[i0] += charge_transfer system.charges[i1] -= charge_transfer # prepare other parameters scalings = Scalings(system, scale_table[1], scale_table[2], scale_table[3]) # Setup the electrostatic pars ff_args.add_electrostatic_parts(system, scalings, dielectric)
def apply(self, par_table, cpar_table, scale_table, system, ff_args): # Prepare the atomic parameters c6s = np.zeros(system.nffatype, float) bs = np.zeros(system.nffatype, float) vols = np.zeros(system.nffatype, float) for i in xrange(system.nffatype): key = (system.ffatypes[i],) par_list = par_table.get(key, []) if len(par_list) == 0: if log.do_warning: log.warn('No DAMPDISP parameters found for atom %i with fftype %s.' % (i, system.get_ffatype(i))) else: c6s[i], bs[i], vols[i] = par_list[0] # Prepare the cross parameters c6_cross = np.zeros((system.nffatype, system.nffatype), float) b_cross = np.zeros((system.nffatype, system.nffatype), float) for i0 in xrange(system.nffatype): for i1 in xrange(i0+1): cpar_list = cpar_table.get((system.ffatypes[i0], system.ffatypes[i1]), []) if len(cpar_list) == 0: if log.do_high: log('No DAMPDISP cross parameters found for ffatypes %s,%s. Mixing rule will be used' % (system.ffatypes[i0], system.ffatypes[i1])) else: c6_cross[i0,i1], b_cross[i0,i1] = cpar_list[0] if i0 != i1: c6_cross[i1,i0], b_cross[i1,i0] = cpar_list[0] # Prepare the global parameters scalings = Scalings(system, scale_table[1], scale_table[2], scale_table[3]) # Get the part. It should not exist yet. part_pair = ff_args.get_part_pair(PairPotDampDisp) if part_pair is not None: raise RuntimeError('Internal inconsistency: the DAMPDISP part should not be present yet.') pair_pot = PairPotDampDisp(system.ffatype_ids, c6_cross, b_cross, ff_args.rcut, ff_args.tr, c6s, bs, vols) nlist = ff_args.get_nlist(system) part_pair = ForcePartPair(system, nlist, scalings, pair_pot) ff_args.parts.append(part_pair)
def apply(self, atom_table, bond_table, scale_table, dielectric, system): if system.charges is None: system.charges = np.zeros(system.natom) elif log.do_warning and abs(system.charges).max() != 0: log.warn('Overwriting charges in system.') system.charges[:] = 0.0 system.radii = np.zeros(system.natom) # compute the charges for i in range(system.natom): pars = atom_table.get(system.get_ffatype(i)) if pars is not None: charge, radius = pars system.charges[i] += charge system.radii[i] = radius elif log.do_warning: log.warn('No charge defined for atom %i with fftype %s.' % (i, system.get_ffatype(i))) for i0, i1 in system.iter_bonds(): ffatype0 = system.get_ffatype(i0) ffatype1 = system.get_ffatype(i1) if ffatype0 == ffatype1: continue charge_transfer = bond_table.get((ffatype0, ffatype1)) if charge_transfer is None: if log.do_warning: log.warn( 'No charge transfer parameter for atom pair (%i,%i) with fftype (%s,%s).' % (i0, i1, system.get_ffatype(i0), system.get_ffatype(i1))) else: system.charges[i0] += charge_transfer system.charges[i1] -= charge_transfer
def detect_bonds(self, exceptions=None): """Initialize the ``bonds`` attribute based on inter-atomic distances **Optional argument:** exceptions: Specify custom threshold for certain pairs of elements. This must be a dictionary with ((num0, num1), threshold) as items. For each pair of elements, a distance threshold is used to detect bonded atoms. The distance threshold is based on a database of known bond lengths. If the database does not contain a record for the given element pair, the threshold is based on the sum of covalent radii. """ with log.section('SYS'): from molmod.bonds import bonds if self.bonds is not None: if log.do_warning: log.warn('Overwriting existing bonds.') work = np.zeros((self.natom*(self.natom-1))/2, float) self.cell.compute_distances(work, self.pos) ishort = (work < bonds.max_length*1.01).nonzero()[0] new_bonds = [] for i in ishort: i0, i1 = _unravel_triangular(i) n0 = self.numbers[i0] n1 = self.numbers[i1] if exceptions is not None: threshold = exceptions.get((n0, n1)) if threshold is None and n0!=n1: threshold = exceptions.get((n1, n0)) if threshold is not None: if work[i] < threshold: new_bonds.append([i0, i1]) continue if bonds.bonded(n0, n1, work[i]): new_bonds.append([i0, i1]) self.bonds = np.array(new_bonds) self._init_derived_bonds()
def detect_bonds(self, exceptions=None): """Initialize the ``bonds`` attribute based on inter-atomic distances **Optional argument:** exceptions: Specify custom threshold for certain pairs of elements. This must be a dictionary with ((num0, num1), threshold) as items. For each pair of elements, a distance threshold is used to detect bonded atoms. The distance threshold is based on a database of known bond lengths. If the database does not contain a record for the given element pair, the threshold is based on the sum of covalent radii. """ with log.section('SYS'): from molmod.bonds import bonds if self.bonds is not None: if log.do_warning: log.warn('Overwriting existing bonds.') work = np.zeros((self.natom * (self.natom - 1)) // 2, float) self.cell.compute_distances(work, self.pos) ishort = (work < bonds.max_length * 1.01).nonzero()[0] new_bonds = [] for i in ishort: i0, i1 = _unravel_triangular(i) n0 = self.numbers[i0] n1 = self.numbers[i1] if exceptions is not None: threshold = exceptions.get((n0, n1)) if threshold is None and n0 != n1: threshold = exceptions.get((n1, n0)) if threshold is not None: if work[i] < threshold: new_bonds.append([i0, i1]) continue if bonds.bonded(n0, n1, work[i]): new_bonds.append([i0, i1]) self.bonds = np.array(new_bonds) self._init_derived_bonds()
def write_pseudo_atoms(ff, workdir): """ Write pseudo_atoms.def file """ system = ff.system if system.masses is None: system.set_standard_masses() # If no charges are present, we write q=0 if system.charges is None: charges = np.zeros(system.natom) else: charges = system.charges # RASPA cannot handle Gaussian charges if system.radii is not None and np.any(system.radii != 0.0): if log.do_warning: log.warn("Atomic radii were specified, but RASPA will not take " "this into account for electrostatic interactions") raise ValueError("Gaussian electrostatics not supported by RASPA") # We need to write an entry for each atomtype, specifying the charge. If # atoms of the same atomtype show different charges, this means the # atomtypes need to be fine grained ffatypes, ffatype_ids = get_lammps_ffatypes(ff) # Write the file with open(os.path.join(workdir, 'pseudo_atoms.def'), 'w') as f: f.write("#number of pseudo atoms\n%d\n" % (len(ffatypes))) f.write("#type print as chem oxidation mass " "charge polarization B-factor radii connectivity " "anisotropic anisotropic-type tinker-type\n") for iffa, ffa in enumerate(ffatypes): iatom = np.where(ffatype_ids == iffa)[0][0] symbol = periodic[system.numbers[iatom]].symbol f.write( "%-12s yes %6s %6s %10d %12.8f %+32.20f %6.3f " "%6.3f %6.3f %5d %5d %10s %5d\n" % (ffa, symbol, symbol, 0, system.masses[iatom] / amu, charges[iatom], 0.0, 1.0, 1.0, 0, 0, "absolute", 0)) return ffatypes, ffatype_ids
def __init__(self, ff, fn_system, fn_log="none", suffix='', do_table=True, fn_table='lammps.table', scalings_table=[0.0, 0.0, 1.0], do_ei=True, kspace='ewald', kspace_accuracy=1e-7, scalings_ei=[0.0, 0.0, 1.0], triclinic=True, comm=None, move_central_cell=False): r'''Initalize LAMMPS ForcePart **Arguments:** system An instance of the ``System`` class. fn_system The file containing the system data in LAMMPS format, can be generated using external.lammpsio.write_lammps_system_data **Optional Arguments:** fn_log Filename where LAMMPS output is stored. This is probably only necessary for debugging. Default: None, which means no output is stored suffix The suffix of the liblammps_*.so library file do_table Boolean, compute a potentual using tabulated values fn_table Filename of file containing tabulated non-bonded potential without point-charge electrostatics. Can be written using the ```write_lammps_table``` method. Default: lammps.table scalings_vdw List containing vdW scaling factors for 1-2, 1-3 and 1-4 neighbors do_ei Boolean, compute a point-charge electrostatic contribution kspace Method to treat long-range electrostatics, should be one of ewald or pppm kspace_accuracy Desired relative error in electrostatic forces Default: 1e-6 scalings_ei List containing electrostatic scaling factors for 1-2, 1-3 and 1-4 neighbors triclinic Boolean, specify whether a triclinic cell will be used during the simulation. If the cell is orthogonal, set it to False as LAMMPS should run slightly faster. Default: True comm MPI communicator, required if LAMMPS should run in parallel move_central_cell Boolean, if True, every atom is moved to the central cell (centered at the origin) before passing positions to LAMMPS. Change this if LAMMPS gives a Atoms Lost ERROR. ''' self.system = ff.system # Try to load the lammps package, quit if not possible try: from lammps import lammps except: log("Could not import the lammps python package which is required to use LAMMPS as a library" ) raise ImportError # Some safety checks... if self.system.cell.nvec != 3: raise ValueError( 'The system must be 3D periodic for LAMMPS calculations.') if not os.path.isfile(fn_system): raise ValueError('Could not read file %s' % fn_system) if do_table: if not os.path.isfile(fn_table): raise ValueError('Could not read file %s' % fn_table) tables = read_lammps_table(fn_table) table_names = [table[0] for table in tables] npoints, _, rcut = tables[0][1] elif do_ei: rcut = 0 for part in ff.parts: if part.name == 'pair_ei': rcut = part.pair_pot.rcut if rcut == 0: log("ERROR, do_ei set to True, but pair_ei was not found in the ff" ) else: raise NotImplementedError if not kspace in ['ewald', 'pppm']: raise ValueError('kspace should be one of ewald or pppm') if self.system.natom > 2000 and kspace == 'ewald' and log.do_warning: log.warn("You are simulating a more or less large system." "It might be more efficient to use kspace=pppm") if self.system.natom < 1000 and kspace == 'pppm' and log.do_warning: log.warn("You are simulating a more or less small system." "It might be better to use kspace=ewald") # Initialize a class instance and some attributes ForcePart.__init__(self, 'lammps', self.system) self.comm = comm self.triclinic = triclinic self.move_central_cell = move_central_cell ffatypes, ffatype_ids = get_lammps_ffatypes(ff) nffa = len(ffatypes) # Pass all commands that would normally appear in the LAMMPS input file # to our instance of LAMMPS. self.lammps = lammps(name=suffix, comm=self.comm, cmdargs=["-screen", fn_log, "-log", "none"]) self.lammps.command("units electron") self.lammps.command("atom_style full") self.lammps.command("atom_modify map array") self.lammps.command("box tilt large") self.lammps.command("read_data %s" % fn_system) self.lammps.command("mass * 1.0") self.lammps.command("bond_style none") # Hybrid style combining electrostatics and table if do_ei and do_table: self.lammps.command( "pair_style hybrid/overlay coul/long %13.8f table spline %d" % (rcut, npoints)) self.lammps.command("pair_coeff * * coul/long") self.lammps.command("kspace_style %s %10.5e" % (kspace, kspace_accuracy)) # Only electrostatics elif do_ei: self.lammps.command("pair_style coul/long %13.8f" % rcut) self.lammps.command("pair_coeff * *") self.lammps.command("kspace_style %s %10.5e" % (kspace, kspace_accuracy)) # Only table elif do_table: self.lammps.command("pair_style table spline %d" % (npoints)) else: raise NotImplementedError for i in range(nffa): ffai = ffatypes[i] for j in range(i, nffa): ffaj = ffatypes[j] if ffai > ffaj: name = '%s---%s' % (str(ffai), str(ffaj)) else: name = '%s---%s' % (str(ffaj), str(ffai)) if do_ei and do_table: self.lammps.command("pair_coeff %d %d table %s %s" % (i + 1, j + 1, fn_table, name)) elif do_table: self.lammps.command("pair_coeff %d %d %s %s" % (i + 1, j + 1, fn_table, name)) if do_ei is not None: self.lammps.command( "special_bonds lj %f %f %f coul %f %f %f" % (scalings_table[0], scalings_table[1], scalings_table[2], scalings_ei[0], scalings_ei[1], scalings_ei[2])) else: self.lammps.command( "special_bonds lj %f %f %f" % (scalings_table[0], scalings_table[1], scalings_table[2])) self.lammps.command("neighbor 0.0 bin") self.lammps.command("neigh_modify delay 0 every 1 check no") self.lammps.command("compute virial all pressure NULL virial") self.lammps.command("variable eng equal pe") self.lammps.command("variable Wxx equal c_virial[1]") self.lammps.command("variable Wyy equal c_virial[2]") self.lammps.command("variable Wzz equal c_virial[3]") self.lammps.command("variable Wxy equal c_virial[4]") self.lammps.command("variable Wxz equal c_virial[5]") self.lammps.command("variable Wyz equal c_virial[6]") thermo_style = "step time etotal evdwl ecoul elong etail " thermo_style += " ".join(["c_virial[%d]" % i for i in range(1, 7)]) self.lammps.command("thermo_style custom %s" % thermo_style) self.lammps.command("fix 1 all nve") # LAMMPS needs cell vectors (ax,0,0), (bx,by,0) and (cx,cy,cz) # This means we need to perform a rotation to switch between Yaff and # LAMMPS coordinates. All information about this rotation is stored # in the variables defined below self.rvecs = np.eye(3) self.cell = Cell(self.rvecs) self.rot = np.zeros((3, 3)) self.vtens_lammps = np.zeros((6, ))
def __init__(self, system, timestep=0.0, restart=0, fn='plumed.dat', kernel=None, fn_log='plumed.log'): r'''Initialize a PLUMED ForcePart. More information on the interface between PLUMED and MD codes can be found on http://tcb.ucas.ac.cn/plumed2/developer-doc/html/_how_to_plumed_your_m_d.html Unfortunately, PLUMED partially breaks the orthogonality of the pes and the sampling modules in Yaff: PLUMED sometimes requires information from the integrator. For example, in metadynamics PLUMED needs to know when a time integration step has been completed (which is not necessarily after each force calculation). ForcePartPlumed therefore also inherits from Hook and by attaching this hook to the integrator, it is possible to obtain the necessary information from the integrator. Problems within PLUMED for this approach to work, particularly in the VES module, have been resolved; see the discussion at https://groups.google.com/forum/?fromgroups=#!topic/plumed-users/kPZu_tNZtgk **Arguments:** system An instance of the System class **Optional Arguments:** timestep The timestep (in au) of the integrator restart Set to a value different from 0 to let PLUMED know that this is a restarted run fn A filename from which the PLUMED instructions are read, default is plumed.dat kernel Path to the PLUMED library (something like /path/to/libplumedKernel.so). If None is provided, the environment variable $PLUMED_KERNEL should point to the library file. fn_log Path to the file where PLUMED logs output, default is plumed.log ''' self.system = system self.fn = fn self.kernel = kernel self.fn_log = fn_log self.plumedstep = 0 # TODO In the LAMMPS-PLUMED interface (src/USER-PLUMED/fix_plumed.cpp) # it is mentioned that biasing is not possible when tailcorrections are # included. Maybe this should be checked... # Check cell dimensions, only 0D and 3D systems supported if not self.system.cell.nvec in [0, 3]: raise NotImplementedError # Setup PLUMED by sending commands to the PLUMED API self.setup_plumed(timestep, restart) # PLUMED requires masses to be set... if self.system.masses is None: self.system.set_standard_masses() # Initialize the ForcePart ForcePart.__init__(self, 'plumed', self.system) # Initialize the Hook, can't see a reason why start and step could # differ from default values Hook.__init__(self, start=0, step=1) self.hooked = False if log.do_warning: log.warn("When using PLUMED as a hook for your integrator " "and PLUMED adds time-dependent forces (for instance " "when performing metadynamics), there is no energy " "conservation. The conserved quantity reported by " "YAFF is irrelevant in this case.")
def _init_derived_ffatypes(self): if self.ffatype_ids is None: if len(self.ffatypes) != self.natom: raise TypeError('When the ffatype_ids are derived automatically, the length of the ffatypes list must match the number of atoms.') lookup = {} ffatypes = [] self.ffatype_ids = np.zeros(self.natom, int) for i in xrange(self.natom): if self.scope_ids is None: ffatype = self.ffatypes[i] key = ffatype, None else: scope_id = self.scope_ids[i] ffatype = self.ffatypes[i] key = ffatype, scope_id ffatype_id = lookup.get(key) if ffatype_id is None: ffatype_id = len(ffatypes) ffatypes.append(ffatype) lookup[key] = ffatype_id self.ffatype_ids[i] = ffatype_id self.ffatypes = ffatypes for ffatype in self.ffatypes: check_name(ffatype) # check the range of the ids if self.ffatype_ids.min() != 0 or self.ffatype_ids.max() != len(self.ffatypes)-1: raise ValueError('The ffatype_ids have incorrect bounds.') # differentiate ffatype_ids if the same ffatype_id is used in different # scopes if self.scopes is not None: self.ffatype_id_to_scope_id = {} fixed_fids = {} for i in xrange(self.natom): fid = self.ffatype_ids[i] sid = self.ffatype_id_to_scope_id.get(fid) if sid is None: self.ffatype_id_to_scope_id[fid] = self.scope_ids[i] elif sid != self.scope_ids[i]: # We found the same ffatype_id in a different scope_id. This # must be fixed. First check if we have already a new # scope_id ready sid = self.scope_ids[i] new_fid = fixed_fids.get((sid, fid)) if new_fid is None: # No previous new fid create, do it now. new_fid = len(self.ffatypes) # Copy the ffatype label self.ffatypes.append(self.ffatypes[fid]) # Keep track of the new fid fixed_fids[(sid, fid)] = new_fid if log.do_warning: log.warn('Atoms with type ID %i in scope %s were changed to type ID %i.' % (fid, self.scopes[sid], new_fid)) # Apply the new fid self.ffatype_ids[i] = new_fid self.ffatype_id_to_scope_id[new_fid] = sid # Turn the ffatypes in the scopes into array if self.ffatypes is not None: self.ffatypes = np.array(self.ffatypes, copy=False) if self.scopes is not None: self.scopes = np.array(self.scopes, copy=False) # check the range of the ids if self.ffatype_ids.min() != 0 or self.ffatype_ids.max() != len(self.ffatypes)-1: raise ValueError('The ffatype_ids have incorrect bounds.') if log.do_medium: log('The following atom types are present in the system:') log.hline() if self.scopes is None: log(' Atom type ID Number of atoms') log.hline() for ffatype_id, ffatype in enumerate(self.ffatypes): log('%22s %3i %3i' % (ffatype, ffatype_id, (self.ffatype_ids==ffatype_id).sum())) else: log(' Scope Atom type ID Number of atoms') log.hline() for ffatype_id, ffatype in enumerate(self.ffatypes): scope = self.scopes[self.ffatype_id_to_scope_id[ffatype_id]] log('%22s %22s %3i %3i' % (scope, ffatype, ffatype_id, (self.ffatype_ids==ffatype_id).sum())) log.hline() log.blank()
def run(self, nsteps, mc_moves=None, initial=None, einit=0, translation_stepsize=1.0*angstrom, volumechange_stepsize=10.0*angstrom**3, close_contact=0.4*angstrom): """ Perform Monte-Carlo steps **Arguments:** nsteps Number of Monte-Carlo steps **Optional Arguments:** mc_moves Dictionary containing relative probabilities of the different Monte-Carlo moves. It is not required that the probabilities sum to 1, they are normalized automatically. Example initial System instance describing the initial configuration of guest molecules einit The energy of the initial state translation_stepsize The maximal magnitude of a TrialTranslation volumechange_stepsize The maximal magnitude of a TrialVolumechange close_contact Automatically reject TrialMove if atoms are placed shorter than this distance apart """ if log.do_warning: log.warn("Currently, Yaff does not consider interactions of a guest molecule " "with its periodic images in MC simulations. Make sure that you choose a system size " "that is large compared to the guest dimensions, so it is indeed " "acceptable to neglect these interactions.") with log.section(self.log_name), timer.section(self.log_name): # Initialization self.translation_stepsize = translation_stepsize self.volumechange_stepsize = volumechange_stepsize self.close_contact = close_contact if initial is not None: self.N = initial.natom//self.guest.natom assert self.guest.natom*self.N==initial.natom, ("Initial configuration does not contain correct number of atoms") self.current_configuration = initial self.get_ff(self.N).system.pos[:] = initial.pos if self.ewald_reci is not None: self.ewald_reci.compute_structurefactors( initial.pos, initial.charges, self.ewald_reci.cosfacs, self.ewald_reci.sinfacs) else: self.current_configuration = self.get_ff(self.N).system self.energy = einit if not self.conditions_set: raise ValueError("External conditions have not been set!") # Normalized probabilities and accompanying methods specifying the trial MC moves # Trial moves are sorted alphabetically if mc_moves is None: mc_moves = self.default_trials trials, probabilities = [], [] for t in sorted(mc_moves.keys()): if not t in self.allowed_trials: raise ValueError("Trial move %s not allowed!"%t) trial = getattr(mctrials,"Trial"+t.capitalize(),None) if trial is None: raise NotImplementedError("The requested trial move %s is not implemented"%(t)) # Trials is a list containing instances of Trial classes from the mctrials module trials.append(trial(self)) probabilities.append(mc_moves[t]) probabilities = np.asarray(probabilities) probabilities /= np.sum(probabilities) assert np.all(probabilities>=0.0), "Negative probabilities are not allowed!" # Take the cumulative sum, makes it a bit easier to determine which MC move is selected probabilities = np.cumsum(probabilities) # Array to keep track of accepted (1st column) and tried (2nd column) # moves, with rows corresponding to different possible moves acceptance = np.zeros((len(trials),2), dtype=int) # Start performing MC moves self.Nmean = self.N self.emean = self.energy self.Vmean = self.current_configuration.cell.volume self.counter = 0 self.call_hooks() for istep in range(nsteps): switch = np.random.rand() # Select one of the possible MC moves imove = np.where(switch<probabilities)[0][0] # Call the corresponding method accepted = trials[imove]() # Update records with accepted and tried MC moves acceptance[imove,1] += 1 if accepted: acceptance[imove,0] += 1 self.counter += 1 self.Nmean += (self.N-self.Nmean)/self.counter self.emean += (self.energy-self.emean)/self.counter self.Vmean += (self.current_configuration.cell.volume-self.Vmean)/self.counter self.call_hooks() return acceptance
def write_lammps_system_data(system, ff=None, fn='lammps.data', triclinic=True): ''' Write information about a Yaff system to a LAMMPS data file Following information is written: cell vectors, atom type ids and topology (as defined by the bonds) **Arguments** system Yaff system fn Filename to write the LAMMPS data to triclinic Boolean, specify whether a triclinic cell will be used during the simulation. If the cell is orthogonal, set it to False as LAMMPS should run slightly faster. Default: True ''' if system.cell.nvec != 3: raise ValueError( 'The system must be 3D periodic for Lammps calculations.') if system.ffatypes is None: raise ValueError('Atom types need to be defined.') if system.bonds is None: raise ValueError('Bonds need to be defined') if system.charges is None: if log.do_warning: log.warn( "System has no charges, writing zero charges to LAMMPS file") charges = np.zeros((system.natom, )) else: charges = system.charges if ff is None: ffatypes, ffatype_ids = system.ffatypes, system.ffatype_ids else: ffatypes, ffatype_ids = get_lammps_ffatypes(ff) fdat = open(fn, 'w') fdat.write( "Generated by Yaff\n\n%20d atoms\n%20d bonds\n%20d angles \n%20d dihedrals\n%20d impropers\n\n" % (system.natom, system.nbond, 0, 0, 0)) fdat.write( "%20d atom types\n%20d bond types\n%20d angle types\n%20d dihedral types\n%20d improper types\n\n" % (np.amax(ffatype_ids) + 1, 1, 0, 0, 0)) rvecs, R = cell_lower(system.cell.rvecs) pos = np.einsum('ij,kj', system.pos, R) fdat.write( "%30.24f %30.24f xlo xhi\n%30.24f %30.24f ylo yhi\n%30.24f %30.24f zlo zhi\n" % (0.0, rvecs[0, 0], 0.0, rvecs[1, 1], 0.0, rvecs[2, 2])) if triclinic: fdat.write("%30.24f %30.24f %30.24f xy xz yz\n" % (rvecs[1, 0], rvecs[2, 0], rvecs[2, 1])) fdat.write("Atoms\n\n") for i in range(system.natom): fdat.write("%5d %3d %3d %30.24f %30.24f %30.24f %30.24f\n" % (i + 1, 1, ffatype_ids[i] + 1, charges[i], pos[i, 0], pos[i, 1], pos[i, 2])) fdat.write("\nBonds\n\n") for i in range(system.nbond): fdat.write("%5d %3d %5d %5d\n" % (i + 1, 1, system.bonds[i, 0] + 1, system.bonds[i, 1] + 1)) fdat.close()
def write_lammps_table(ff, fn='lammps.table', rmin=0.50 * angstrom, nrows=2500, unit_style='electron'): ''' Write tables containing noncovalent interactions for LAMMPS. For every pair of ffatypes, a separate table is generated. Because electrostatic interactions require a specific treatment, point- charge electrostatics are NOT included in the tables. When distributed charge distributions (e.g. Gaussian) are used, this complicates matters. LAMMPS will still only treat point-charge electrostatics using a dedicated method (e.g. Ewald or PPPM), so the table has to contain the difference between the distributed charges and the point charge electrostatic interactions. This also means that every ffatype need a unique charge distribution, i.e. all atoms of the same atom type need to have the same charge and Gaussian radius. All pair potentials contributing to the table need to have the same scalings for near-neighbor interactions; this is however independent of the generation of the table and is dealt with elsewhere **Arguments:** ff Yaff ForceField instance **Optional arguments:** fn Filename where tables will be stored ''' # Find out if we are dealing with electrostatics from distributed charges corrections = [] for part in ff.parts: if part.name == 'pair_ei': if np.any(part.pair_pot.radii != 0.0): # Create a ForcePart with electrostatics from distributed # charges, completely in real space. pair_pot_dist = PairPotEI(part.pair_pot.charges, 0.0, part.pair_pot.rcut, tr=part.pair_pot.get_truncation(), dielectric=part.pair_pot.dielectric, radii=part.pair_pot.radii) fp_dist = ForcePartPair(ff.system, ff.nlist, part.scalings, pair_pot_dist) corrections.append((fp_dist, 1.0)) # Create a ForcePart with electrostatics from point # charges, completely in real space. pair_pot_point = PairPotEI(part.pair_pot.charges, 0.0, part.pair_pot.rcut, tr=part.pair_pot.get_truncation(), dielectric=part.pair_pot.dielectric) fp_point = ForcePartPair(ff.system, ff.nlist, part.scalings, pair_pot_point) corrections.append((fp_point, -1.0)) # Find the largest cut-off rmax = 0.0 for part in ff.parts: if part.name.startswith('pair_'): if part.name == 'pair_ei' and len(corrections) == 0: continue rmax = np.amax([rmax, part.pair_pot.rcut]) # Get LAMMPS ffatypes ffatypes, ffatype_ids = get_lammps_ffatypes(ff) # Select atom pairs for each pair of atom types ffa_pairs = [] for i in range(len(ffatypes)): index0 = np.where(ffatype_ids == i)[0][0] for j in range(i, len(ffatypes)): index1 = -1 candidates = np.where(ffatype_ids == j)[0] for cand in candidates: if cand==index0 or cand in ff.system.neighs1[index0] or\ cand in ff.system.neighs2[index0] or cand in ff.system.neighs3[index0] or\ cand in ff.system.neighs4[index0]: continue else: index1 = cand break if index1 == -1: log("ERROR constructing LAMMPS tables: there is no pair of atom types %s-%s which are not near neighbors" % (ffatypes[i], ffatypes[j])) log("Consider using a supercell to fix this problem") raise ValueError ffa_pairs.append([index0, index1]) if log.do_medium: with log.section('LAMMPS'): log("Generating LAMMPS table with covalent interactions") log.hline() log("rmin = %s | rmax = %s" % (log.length(rmin), log.length(rmax))) # We only consider one neighbor interaction ff.compute() ff.nlist.nneigh = 1 # Construct array of evenly spaced values distances = np.linspace(rmin, rmax, nrows) ftab = open(fn, 'w') ftab.write("# LAMMPS tabulated potential generated by Yaff\n") ftab.write("# All quantities in atomic units\n") ftab.write( "# The names of the tables refer to the ffatype_ids that have to be used in the Yaff system\n" ) ftab.write("#%4s %13s %21s %21s\n" % ("i", "d", "V", "F")) # Loop over all atom pairs for index0, index1 in ffa_pairs: energies = [] for d in distances: gposnn = np.zeros(ff.system.pos.shape, float) ff.nlist.neighs[0] = (index0, index1, d, 0.0, 0.0, d, 0, 0, 0) energy = 0.0 for part in ff.parts: if not part.name.startswith('pair'): continue if part.name == 'pair_ei': continue energy += part.compute(gpos=gposnn) for part, sign in corrections: gposcorr = np.zeros(ff.system.pos.shape, float) energy += sign * part.compute(gpos=gposcorr) gposnn[:] += sign * gposcorr row = [d, energy, gposnn[index0, 2]] energies.append(row) energies = np.asarray(energies) ffai = ffatypes[ffatype_ids[index0]] ffaj = ffatypes[ffatype_ids[index1]] if np.all(energies[:, 1] == 0.0): log.warn("Noncovalent energies between atoms %d (%s) and %d (%s) are zero"\ % (index0,ffai,index1,ffaj)) if np.all(energies[:, 2] == 0.0): log.warn("Noncovalent forces between atoms %d (%s) and %d (%s) are zero"\ % (index0,ffai,index1,ffaj)) if ffai > ffaj: name = '%s---%s' % (str(ffai), str(ffaj)) else: name = '%s---%s' % (str(ffaj), str(ffai)) ftab.write("%s\nN %d R %13.8f %13.8f\n\n" % (name, nrows, rmin / lammps_units[unit_style]['distance'], rmax / lammps_units[unit_style]['distance'])) for irow, row in enumerate(energies): ftab.write( "%05d %+13.8f %+21.12f %+21.12f\n" % (irow + 1, row[0] / lammps_units[unit_style]['distance'], row[1] / lammps_units[unit_style]['energy'], row[2] / lammps_units[unit_style]['energy'] * lammps_units[unit_style]['distance'])) if log.do_medium: log("%s done" % name)
def _init_derived_ffatypes(self): if self.ffatype_ids is None: if len(self.ffatypes) != self.natom: raise TypeError( 'When the ffatype_ids are derived automatically, the length of the ffatypes list must match the number of atoms.' ) lookup = {} ffatypes = [] self.ffatype_ids = np.zeros(self.natom, int) for i in range(self.natom): if self.scope_ids is None: ffatype = self.ffatypes[i] key = ffatype, None else: scope_id = self.scope_ids[i] ffatype = self.ffatypes[i] key = ffatype, scope_id ffatype_id = lookup.get(key) if ffatype_id is None: ffatype_id = len(ffatypes) ffatypes.append(ffatype) lookup[key] = ffatype_id self.ffatype_ids[i] = ffatype_id self.ffatypes = ffatypes for ffatype in self.ffatypes: check_name(ffatype) # check the range of the ids if self.ffatype_ids.min() != 0 or self.ffatype_ids.max() != len( self.ffatypes) - 1: raise ValueError('The ffatype_ids have incorrect bounds.') # differentiate ffatype_ids if the same ffatype_id is used in different # scopes if self.scopes is not None: self.ffatype_id_to_scope_id = {} fixed_fids = {} for i in range(self.natom): fid = self.ffatype_ids[i] sid = self.ffatype_id_to_scope_id.get(fid) if sid is None: self.ffatype_id_to_scope_id[fid] = self.scope_ids[i] elif sid != self.scope_ids[i]: # We found the same ffatype_id in a different scope_id. This # must be fixed. First check if we have already a new # scope_id ready sid = self.scope_ids[i] new_fid = fixed_fids.get((sid, fid)) if new_fid is None: # No previous new fid create, do it now. new_fid = len(self.ffatypes) # Copy the ffatype label self.ffatypes.append(self.ffatypes[fid]) # Keep track of the new fid fixed_fids[(sid, fid)] = new_fid if log.do_warning: log.warn( 'Atoms with type ID %i in scope %s were changed to type ID %i.' % (fid, self.scopes[sid], new_fid)) # Apply the new fid self.ffatype_ids[i] = new_fid self.ffatype_id_to_scope_id[new_fid] = sid # Turn the ffatypes in the scopes into array if self.ffatypes is not None: self.ffatypes = np.array(self.ffatypes, copy=False) if self.scopes is not None: self.scopes = np.array(self.scopes, copy=False) # check the range of the ids if self.ffatype_ids.min() != 0 or self.ffatype_ids.max() != len( self.ffatypes) - 1: raise ValueError('The ffatype_ids have incorrect bounds.') if log.do_medium: log('The following atom types are present in the system:') log.hline() if self.scopes is None: log(' Atom type ID Number of atoms') log.hline() for ffatype_id, ffatype in enumerate(self.ffatypes): log('%22s %3i %3i' % (ffatype, ffatype_id, (self.ffatype_ids == ffatype_id).sum())) else: log(' Scope Atom type ID Number of atoms' ) log.hline() for ffatype_id, ffatype in enumerate(self.ffatypes): scope = self.scopes[ self.ffatype_id_to_scope_id[ffatype_id]] log('%22s %22s %3i %3i' % (scope, ffatype, ffatype_id, (self.ffatype_ids == ffatype_id).sum())) log.hline() log.blank()
def check_mic(self, system): '''Check if each scale2 and scale3 are uniquely defined. **Arguments:** system An instance of the system class, i.e. the one that is used to create this scaling object. This check is done by constructing for each scaled pair, all possible bond paths between the two atoms. For each path, the bond vectors (after applying the minimum image convention) are added. If for a given pair, these sums of bond vectors differ between all possible paths, the differences are expanded in cell vectors which can be used to construct a proper supercell in which scale2 and scale3 pairs are all uniquely defined. ''' if system.cell.nvec == 0: return troubles = False with log.section('SCALING'): for i0, i1, scale, nbond in self.stab: if nbond == 1: continue all_deltas = [] paths = [] for path in iter_paths(system, i0, i1, nbond): delta_total = 0 for j0 in xrange(nbond): j1 = j0 + 1 delta = system.pos[path[j0]] - system.pos[path[j1]] system.cell.mic(delta) delta_total += delta all_deltas.append(delta_total) paths.append(path) all_deltas = np.array(all_deltas) if abs(all_deltas.mean(axis=0) - all_deltas).max() > 1e-10: troubles = True if log.do_warning: log.warn('Troublesome pair scaling detected.') log('The following bond paths connect the same pair of ' 'atoms, yet the relative vectors are different.') for ipath in xrange(len(paths)): log('%2i %27s %10s %10s %10s' % ( ipath, ','.join(str(index) for index in paths[ipath]), log.length(all_deltas[ipath,0]), log.length(all_deltas[ipath,1]), log.length(all_deltas[ipath,2]), )) log('Differences between relative vectors in fractional ' 'coordinates:') for ipath0 in xrange(1, len(paths)): for ipath1 in xrange(ipath0): diff = all_deltas[ipath0] - all_deltas[ipath1] diff_frac = np.dot(system.cell.gvecs, diff) log('%2i %2i %10.4f %10.4f %10.4f' % ( ipath0, ipath1, diff_frac[0], diff_frac[1], diff_frac[2] )) log.blank() if troubles: raise AssertionError('Due to the small spacing between some crystal planes, the scaling of non-bonding interactions will not work properly. Use a supercell to avoid this problem.')
def xyz_to_hdf5(f, fn_xyz, sub=slice(None), file_unit=angstrom, name='pos'): """Convert XYZ trajectory file to Yaff HDF5 format. **Arguments:** f An open and writable HDF5 file. fn_xyz The filename of the XYZ trajectory file. **Optional arguments:** sub The sub argument for the XYZReader. This must be a slice object that defines the subsampling of the XYZ file reader. By default all frames are read. file_unit The unit of the data in the XYZ file. [default=angstrom] name The name of the HDF5 dataset where the trajectory is stored. This array is stored in the 'trajectory' group. This routine will also test the consistency of the row attribute of the trajectory group. If some trajectory data is already present, it will be replaced by the new data. It is highly recommended to first initialize the HDF5 file with the ``to_hdf5`` method of the System class. """ with log.section('XYZH5'): if log.do_medium: log('Loading XYZ file \'%s\' into \'trajectory/%s\' of HDF5 file \'%s\'' % (fn_xyz, name, f.filename)) # First make sure the HDF5 file has a system description that is consistent # with the XYZ file. if 'system' not in f: raise ValueError('The HDF5 file must contain a system group.') if 'numbers' not in f['system']: raise ValueError( 'The HDF5 file must have a system group with atomic numbers.') xyz_reader = XYZReader(fn_xyz, sub=sub, file_unit=file_unit) if len(xyz_reader.numbers) != len(f['system/numbers']): raise ValueError( 'The number of atoms in the HDF5 and the XYZ files does not match.' ) if (xyz_reader.numbers != f['system/numbers']).any(): log.warn( 'The atomic numbers of the HDF5 and XYZ file do not match.') # Take care of the trajectory group tgrp = get_trajectory_group(f) # Take care of the dataset ds, = get_trajectory_datasets(tgrp, (name, (len(xyz_reader.numbers), 3))) # Fill the dataset with data. row = get_last_trajectory_row([ds]) for title, coordinates in xyz_reader: write_to_dataset(ds, coordinates, row) row += 1 # Check number of rows check_trajectory_rows(tgrp, [ds], row)
def xyz_to_hdf5(f, fn_xyz, sub=slice(None), file_unit=angstrom, name='pos'): """Convert XYZ trajectory file to Yaff HDF5 format. **Arguments:** f An open and writable HDF5 file. fn_xyz The filename of the XYZ trajectory file. **Optional arguments:** sub The sub argument for the XYZReader. This must be a slice object that defines the subsampling of the XYZ file reader. By default all frames are read. file_unit The unit of the data in the XYZ file. [default=angstrom] name The name of the HDF5 dataset where the trajectory is stored. This array is stored in the 'trajectory' group. This routine will also test the consistency of the row attribute of the trajectory group. If some trajectory data is already present, it will be replaced by the new data. It is highly recommended to first initialize the HDF5 file with the ``to_hdf5`` method of the System class. """ with log.section('XYZH5'): if log.do_medium: log('Loading XYZ file \'%s\' into \'trajectory/%s\' of HDF5 file \'%s\'' % ( fn_xyz, name, f.filename )) # First make sure the HDF5 file has a system description that is consistent # with the XYZ file. if 'system' not in f: raise ValueError('The HDF5 file must contain a system group.') if 'numbers' not in f['system']: raise ValueError('The HDF5 file must have a system group with atomic numbers.') xyz_reader = XYZReader(fn_xyz, sub=sub, file_unit=file_unit) if len(xyz_reader.numbers) != len(f['system/numbers']): raise ValueError('The number of atoms in the HDF5 and the XYZ files does not match.') if (xyz_reader.numbers != f['system/numbers']).any(): log.warn('The atomic numbers of the HDF5 and XYZ file do not match.') # Take care of the trajectory group tgrp = get_trajectory_group(f) # Take care of the dataset ds, = get_trajectory_datasets(tgrp, (name, (len(xyz_reader.numbers), 3))) # Fill the dataset with data. row = get_last_trajectory_row([ds]) for title, coordinates in xyz_reader: write_to_dataset(ds, coordinates, row) row += 1 # Check number of rows check_trajectory_rows(tgrp, [ds], row)
def g09log_to_hdf5(f, fn_log): """Convert Gaussian09 BOMD log file to Yaff HDF5 format. **Arguments:** f An open and writable HDF5 file. fn_log The name of the Gaussian log file. """ with log.section('G09H5'): if log.do_medium: log('Loading Gaussian 09 file \'%s\' into \'trajectory\' of HDF5 file \'%s\'' % (fn_log, f.filename)) # First make sure the HDF5 file has a system description that is consistent # with the XYZ file. if 'system' not in f: raise ValueError('The HDF5 file must contain a system group.') if 'numbers' not in f['system']: raise ValueError( 'The HDF5 file must have a system group with atomic numbers.') natom = f['system/numbers'].shape[0] # Take care of the trajectory group tgrp = get_trajectory_group(f) # Take care of the pos and vel datasets dss = get_trajectory_datasets( tgrp, ('pos', (natom, 3)), ('vel', (natom, 3)), ('frc', (natom, 3)), ('time', (1, )), ('step', (1, )), ('epot', (1, )), ('ekin', (1, )), ('etot', (1, )), ) ds_pos, ds_vel, ds_frc, ds_time, ds_step, ds_epot, ds_ekin, ds_etot = dss # Load frame by frame row = get_last_trajectory_row(dss) for numbers, pos, vel, frc, time, step, epot, ekin, etot in _iter_frames_g09( fn_log): if (numbers != f['system/numbers']).any(): log.warn( 'The element numbers of the HDF5 and LOG file do not match.' ) write_to_dataset(ds_pos, pos, row) write_to_dataset(ds_vel, vel, row) write_to_dataset(ds_frc, frc, row) write_to_dataset(ds_time, time, row) write_to_dataset(ds_step, step, row) write_to_dataset(ds_epot, epot, row) write_to_dataset(ds_ekin, ekin, row) write_to_dataset(ds_etot, etot, row) row += 1 # Check number of rows check_trajectory_rows(tgrp, dss, row)
def check_mic(self, system): '''Check if each scale2 and scale3 are uniquely defined. **Arguments:** system An instance of the system class, i.e. the one that is used to create this scaling object. This check is done by constructing for each scaled pair, all possible bond paths between the two atoms. For each path, the bond vectors (after applying the minimum image convention) are added. If for a given pair, these sums of bond vectors differ between all possible paths, the differences are expanded in cell vectors which can be used to construct a proper supercell in which scale2 and scale3 pairs are all uniquely defined. ''' if system.cell.nvec == 0: return troubles = False with log.section('SCALING'): for i0, i1, scale, nbond in self.stab: if nbond == 1: continue all_deltas = [] paths = [] for path in iter_paths(system, i0, i1, nbond): delta_total = 0 for j0 in range(nbond): j1 = j0 + 1 delta = system.pos[path[j0]] - system.pos[path[j1]] system.cell.mic(delta) delta_total += delta all_deltas.append(delta_total) paths.append(path) all_deltas = np.array(all_deltas) if abs(all_deltas.mean(axis=0) - all_deltas).max() > 1e-10: troubles = True if log.do_warning: log.warn('Troublesome pair scaling detected.') log('The following bond paths connect the same pair of ' 'atoms, yet the relative vectors are different.') for ipath in range(len(paths)): log('%2i %27s %10s %10s %10s' % ( ipath, ','.join(str(index) for index in paths[ipath]), log.length(all_deltas[ipath, 0]), log.length(all_deltas[ipath, 1]), log.length(all_deltas[ipath, 2]), )) log('Differences between relative vectors in fractional ' 'coordinates:') for ipath0 in range(1, len(paths)): for ipath1 in range(ipath0): diff = all_deltas[ipath0] - all_deltas[ipath1] diff_frac = np.dot(system.cell.gvecs, diff) log('%2i %2i %10.4f %10.4f %10.4f' % (ipath0, ipath1, diff_frac[0], diff_frac[1], diff_frac[2])) log.blank() if troubles: raise AssertionError( 'Due to the small spacing between some crystal planes, the scaling of non-bonding interactions will not work properly. Use a supercell to avoid this problem.' )