def calc(self, at, energy=None, force=None, virial=None, local_energy=None, local_virial=None, args_str=None, error=None, **kwargs): if not isinstance(args_str, basestring): args_str = dict_to_args_str(args_str) kw_args_str = dict_to_args_str(kwargs) args_str = ' '.join((self.get_calc_args_str(), kw_args_str, args_str)) if isinstance(energy, basestring): args_str = args_str + ' energy=%s' % energy energy = None if isinstance(energy, bool) and energy: args_str = args_str + ' energy' energy = None if isinstance(force, basestring): args_str = args_str + ' force=%s' % force force = None if isinstance(force, bool) and force: args_str = args_str + ' force' force = None if isinstance(virial, basestring): args_str = args_str + ' virial=%s' % virial virial = None if isinstance(virial, bool) and virial: args_str = args_str + ' virial' virial = None if isinstance(local_energy, basestring): args_str = args_str + ' local_energy=%s' % local_energy local_energy = None if isinstance(local_energy, bool) and local_energy: args_str = args_str + ' local_energy' local_energy = None if isinstance(local_virial, basestring): args_str = args_str + ' local_virial=%s' % local_virial local_virial = None if isinstance(local_virial, bool) and local_virial: args_str = args_str + ' local_virial' local_virial = None potlog.debug( 'Potential invoking calc() on n=%d atoms with args_str "%s"' % (len(at), args_str)) _potential.Potential.calc(self, at, energy, force, virial, local_energy, local_virial, args_str, error)
def __init__(self, args_str=None, **init_args): """ Initialises Descriptor object and calculate number of dimensions and permutations. """ if args_str is None: args_str = dict_to_args_str(init_args) RawDescriptor.__init__(self, args_str) self._n_dim = self.dimensions() self._n_perm = self.n_permutations()
def calc(self, at, grad=False, args_str=None, **calc_args): """ Calculates all descriptors of this type in the Atoms object, and gradients if grad=True. Results can be accessed dictionary- or attribute-style; 'descriptor' contains descriptor values, 'descriptor_index_0based' contains the 0-based indices of the central atom(s) in each descriptor, 'grad' contains gradients, 'grad_index_0based' contains indices to gradients (descriptor, atom). Cutoffs and gradients of cutoffs are also returned. """ if args_str is None: args_str = dict_to_args_str(calc_args) n_index = fzeros(1, 'i') n_desc, n_cross = self.descriptor_sizes(at, n_index=n_index) n_index = n_index[1] data = fzeros((self.n_dim, n_desc)) cutoff = fzeros(n_desc) data_index = fzeros((n_index, n_desc), 'i') if grad: # n_cross is number of cross-terms, proportional to n_desc data_grad = fzeros((self.n_dim, 3, n_cross)) data_grad_index = fzeros((2, n_cross), 'i') cutoff_grad = fzeros((3, n_cross)) if not grad: RawDescriptor.calc(self, at, descriptor_out=data, covariance_cutoff=cutoff, descriptor_index=data_index, args_str=args_str) else: RawDescriptor.calc(self, at, descriptor_out=data, covariance_cutoff=cutoff, descriptor_index=data_index, grad_descriptor_out=data_grad, grad_descriptor_index=data_grad_index, grad_covariance_cutoff=cutoff_grad, args_str=args_str) results = DescriptorCalcResult() convert = lambda data: np.array(data).T results.descriptor = convert(data) results.cutoff = convert(cutoff) results.descriptor_index_0based = convert(data_index - 1) if grad: results.grad = convert(data_grad) results.grad_index_0based = convert(data_grad_index - 1) results.cutoff_grad = convert(cutoff_grad) return results
def get_calc_args_str(self): """ Get the ``calc_args`` to be passed to :meth:`calc` as a string """ return dict_to_args_str(self._calc_args)
def calculate(self, atoms, properties, system_changes): Calculator.calculate(self, atoms, properties, system_changes) # we will do the calculation in place, to minimise number of copies, # unless atoms is not a quippy Atoms if isinstance(atoms, Atoms): self.quippy_atoms = weakref.proxy(atoms) else: potlog.debug( 'Potential atoms is not quippy.Atoms instance, copy forced!') self.quippy_atoms = Atoms(atoms) initial_arrays = self.quippy_atoms.arrays.keys() initial_info = self.quippy_atoms.info.keys() if properties is None: properties = ['energy', 'forces', 'stress'] # Add any default properties properties = set(self.get_default_properties() + properties) if len(properties) == 0: raise RuntimeError('Nothing to calculate') if not self.calculation_required(atoms, properties): return args_map = { 'energy': { 'energy': None }, 'energies': { 'local_energy': None }, 'forces': { 'force': None }, 'stress': { 'virial': None }, 'numeric_forces': { 'force': 'numeric_force', 'force_using_fd': True, 'force_fd_delta': 1.0e-5 }, 'stresses': { 'local_virial': None }, 'elastic_constants': {}, 'unrelaxed_elastic_constants': {} } # list of properties that require a call to Potential.calc() calc_properties = [ 'energy', 'energies', 'forces', 'numeric_forces', 'stress', 'stresses' ] # list of other properties we know how to calculate other_properties = ['elastic_constants', 'unrelaxed_elastic_constants'] calc_args = {} calc_required = False for property in properties: if property in calc_properties: calc_required = True calc_args.update(args_map[property]) elif property not in other_properties: raise RuntimeError( "Don't know how to calculate property '%s'" % property) if calc_required: self.calc(self.quippy_atoms, args_str=dict_to_args_str(calc_args)) if 'energy' in properties: self.results['energy'] = float(self.quippy_atoms.energy) if 'energies' in properties: self.results['energies'] = self.quippy_atoms.local_energy.copy( ).view(np.ndarray) if 'forces' in properties: self.results['forces'] = self.quippy_atoms.force.copy().view( np.ndarray).T if 'numeric_forces' in properties: self.results[ 'numeric_forces'] = self.quippy_atoms.numeric_force.copy( ).view(np.ndarray).T if 'stress' in properties: stress = -self.quippy_atoms.virial.copy().view( np.ndarray) / self.quippy_atoms.get_volume() # convert to 6-element array in Voigt order self.results['stress'] = np.array([ stress[0, 0], stress[1, 1], stress[2, 2], stress[1, 2], stress[0, 2], stress[0, 1] ]) if 'stresses' in properties: lv = np.array(self.quippy_atoms.local_virial) # make a copy vol_per_atom = self.get( 'vol_per_atom', self.quippy_atoms.get_volume() / len(atoms)) if isinstance(vol_per_atom, basestring): vol_per_atom = self.quippy_atoms.arrays[vol_per_atom] self.results['stresses'] = -lv.T.reshape( (len(atoms), 3, 3), order='F') / vol_per_atom if 'elastic_constants' in properties: cij_dx = self.get('cij_dx', 1e-2) cij = fzeros((6, 6)) self.calc_elastic_constants(self.quippy_atoms, fd=cij_dx, args_str=self.get_calc_args_str(), c=cij, relax_initial=False, return_relaxed=False) if not get_fortran_indexing(): cij = cij.view(np.ndarray) self.results['elastic_constants'] = cij if 'unrelaxed_elastic_constants' in properties: cij_dx = self.get('cij_dx', 1e-2) c0ij = fzeros((6, 6)) self.calc_elastic_constants(self.quippy_atoms, fd=cij_dx, args_str=self.get_calc_args_str(), c0=c0ij, relax_initial=False, return_relaxed=False) if not get_fortran_indexing(): c0ij = c0ij.view(np.ndarray) self.results['unrelaxed_elastic_constants'] = c0ij # copy back any additional output data to results dictionary skip_keys = ['energy', 'force', 'virial', 'numeric_force'] for key in self.quippy_atoms.arrays.keys(): if key not in initial_arrays and key not in skip_keys: self.results[key] = self.quippy_atoms.arrays[key].copy() for key in self.quippy_atoms.info.keys(): if key not in initial_info and key not in skip_keys: if isinstance(self.quippy_atoms.info[key], np.ndarray): self.results[key] = self.quippy_atoms.info[key].copy() else: self.results[key] = self.quippy_atoms.info[key]
def __init__(self, init_args=None, pot1=None, pot2=None, param_str=None, param_filename=None, bulk_scale=None, mpi_obj=None, callback=None, calculator=None, atoms=None, calculation_always_required=False, fpointer=None, finalise=True, error=None, **kwargs): self._calc_args = {} self._default_properties = [] self.calculation_always_required = calculation_always_required Calculator.__init__(self, atoms=atoms) if callback is not None or calculator is not None: if init_args is None: init_args = 'callbackpot' param_dirname = None if param_filename is not None: param_str = open(param_filename).read() param_dirname = path.dirname(param_filename) or None if init_args is None and param_str is None: raise ValueError('Need one of init_args,param_str,param_filename') if init_args is not None: if init_args.lower().startswith('callbackpot'): if not 'label' in init_args: init_args = init_args + ' label=%d' % id(self) else: # if param_str missing, try to find default set of QUIP params, # falling back on a do-nothing parameter string. if param_str is None and pot1 is None and pot2 is None: try: param_str = quip_xml_parameters(init_args) except IOError: param_str = r'<params></params>' if kwargs != {}: if init_args is not None: init_args = init_args + ' ' + dict_to_args_str(kwargs) else: init_args = dict_to_args_str(kwargs) # Change to the xml directory to initialise, so that extra files # like sparseX can be found. old_dir = os.getcwd() try: if param_dirname is not None: os.chdir(param_dirname) _potential.Potential.__init__(self, init_args, pot1=pot1, pot2=pot2, param_str=param_str, bulk_scale=bulk_scale, mpi_obj=mpi_obj, fpointer=fpointer, finalise=finalise, error=error) finally: os.chdir(old_dir) if init_args is not None and init_args.lower().startswith( 'callbackpot'): _potential.Potential.set_callback(self, Potential.callback) if callback is not None: self.set_callback(callback) if calculator is not None: self.set_callback(calculator_callback_factory(calculator)) if atoms is not None: atoms.set_calculator(self) self.name = init_args
def __init__(self, init_args=None, pot1=None, pot2=None, param_str=None, param_filename=None, bulk_scale=None, mpi_obj=None, callback=None, calculator=None, atoms=None, calculation_always_required=False, fpointer=None, finalise=True, error=None, **kwargs): self._calc_args = {} self._default_properties = [] self.calculation_always_required = calculation_always_required Calculator.__init__(self, atoms=atoms) if callback is not None or calculator is not None: if init_args is None: init_args = 'callbackpot' if param_filename is not None: param_str = open(param_filename).read() if init_args is None and param_str is None: raise ValueError('Need one of init_args,param_str,param_filename') if init_args is not None: if init_args.lower().startswith('callbackpot'): if not 'label' in init_args: init_args = init_args + ' label=%d' % id(self) else: # if param_str missing, try to find default set of QUIP params if param_str is None and pot1 is None and pot2 is None: param_str = quip_xml_parameters(init_args) if kwargs != {}: if init_args is not None: init_args = init_args + ' ' + dict_to_args_str(kwargs) else: init_args = dict_to_args_str(kwargs) _potential.Potential.__init__(self, init_args, pot1=pot1, pot2=pot2, param_str=param_str, bulk_scale=bulk_scale, mpi_obj=mpi_obj, fpointer=fpointer, finalise=finalise, error=error) if init_args is not None and init_args.lower().startswith( 'callbackpot'): _potential.Potential.set_callback(self, Potential.callback) if callback is not None: self.set_callback(callback) if calculator is not None: self.set_callback(calculator_callback_factory(calculator)) if atoms is not None: atoms.set_calculator(self) self.name = init_args
def calculate(self, atoms, quantities=None): """ Perform a calculation of `quantities` for `atoms` using this Potential. Automatically determines if a new calculation is required or if previous results are still appliciable (i.e. if the atoms haven't moved since last call) Called internally by :meth:`get_potential_energy`, :meth:`get_forces`, etc. """ if quantities is None: quantities = ['energy', 'forces', 'stress'] # Add any default quantities quantities = set(self.get_default_quantities() + quantities) if len(quantities) == 0: raise RuntimeError('Nothing to calculate') if not self.calculation_required(atoms, quantities): return args_map = { 'energy': { 'energy': None }, 'energies': { 'local_energy': None }, 'forces': { 'force': None }, 'stress': { 'virial': None }, 'numeric_forces': { 'force': 'numeric_force', 'force_using_fd': True, 'force_fd_delta': 1.0e-5 }, 'stresses': { 'local_virial': None }, 'elastic_constants': {}, 'unrelaxed_elastic_constants': {} } # list of quantities that require a call to Potential.calc() calc_quantities = [ 'energy', 'energies', 'forces', 'numeric_forces', 'stress', 'stresses' ] # list of other quantities we know how to calculate other_quantities = ['elastic_constants', 'unrelaxed_elastic_constants'] calc_args = {} calc_required = False for quantity in quantities: if quantity in calc_quantities: calc_required = True calc_args.update(args_map[quantity]) elif quantity not in other_quantities: raise RuntimeError( "Don't know how to calculate quantity '%s'" % quantity) if calc_required: self.calc(self.atoms, args_str=dict_to_args_str(calc_args)) if 'energy' in quantities: self.energy = float(self.atoms.energy) if 'energies' in quantities: self.energies = self.atoms.local_energy.view(np.ndarray) if 'forces' in quantities: self.forces = self.atoms.force.view(np.ndarray).T if 'numeric_forces' in quantities: self.numeric_forces = self.atoms.numeric_force.view(np.ndarray).T if 'stress' in quantities: stress = -self.atoms.virial.view( np.ndarray) / self.atoms.get_volume() # convert to 6-element array in Voigt order self.stress = np.array([ stress[0, 0], stress[1, 1], stress[2, 2], stress[1, 2], stress[0, 2], stress[0, 1] ]) if 'stresses' in quantities: lv = np.array(self.atoms.local_virial) # make a copy vol_per_atom = self.get('vol_per_atom', self.atoms.get_volume() / len(atoms)) if isinstance(vol_per_atom, basestring): vol_per_atom = self.atoms.arrays[vol_per_atom] self.stresses = -lv.T.reshape( (len(atoms), 3, 3), order='F') / vol_per_atom if 'elastic_constants' in quantities: cij_dx = self.get('cij_dx', 1e-2) cij = fzeros((6, 6)) self.calc_elastic_constants(self.atoms, fd=cij_dx, args_str=self.get_calc_args_str(), c=cij, relax_initial=False, return_relaxed=False) if not get_fortran_indexing(): cij = cij.view(np.ndarray) self.elastic_constants = cij if 'unrelaxed_elastic_constants' in quantities: cij_dx = self.get('cij_dx', 1e-2) c0ij = fzeros((6, 6)) self.calc_elastic_constants(self.atoms, fd=cij_dx, args_str=self.get_calc_args_str(), c0=c0ij, relax_initial=False, return_relaxed=False) if not get_fortran_indexing(): c0ij = c0ij.view(np.ndarray) self.unrelaxed_elastic_constants = c0ij
def __init__(self, init_args=None, pot1=None, pot2=None, param_str=None, param_filename=None, bulk_scale=None, mpi_obj=None, callback=None, calculator=None, cutoff_skin=1.0, atoms=None, fpointer=None, finalise=True, error=None, **kwargs): self.atoms = None self._prev_atoms = None self.energy = None self.energies = None self.forces = None self.stress = None self.stresses = None self.elastic_constants = None self.unrelaxed_elastic_constants = None self.numeric_forces = None self._calc_args = {} self._default_quantities = [] self.cutoff_skin = cutoff_skin if callback is not None or calculator is not None: if init_args is None: init_args = 'callbackpot' if param_filename is not None: param_str = open(param_filename).read() if init_args is None and param_str is None: raise ValueError('Need one of init_args,param_str,param_filename') if init_args is not None: if init_args.lower().startswith('callbackpot'): if not 'label' in init_args: init_args = init_args + ' label=%d' % id(self) else: # if param_str missing, try to find default set of QUIP params if param_str is None and pot1 is None and pot2 is None: param_str = quip_xml_parameters(init_args) if kwargs != {}: if init_args is not None: init_args = init_args + ' ' + dict_to_args_str(kwargs) else: init_args = dict_to_args_str(kwargs) _potential.Potential.__init__(self, init_args, pot1=pot1, pot2=pot2, param_str=param_str, bulk_scale=bulk_scale, mpi_obj=mpi_obj, fpointer=fpointer, finalise=finalise, error=error) if init_args is not None and init_args.lower().startswith( 'callbackpot'): _potential.Potential.set_callback(self, Potential.callback) if callback is not None: self.set_callback(callback) if calculator is not None: self.set_callback(calculator_callback_factory(calculator)) if atoms is not None: atoms.set_calculator(self)