#! /usr/bin/env python
from pysic.core import *
from pysic.utility.error import InvalidPotentialError
import copy
[docs]class ProductPotential:
"""Class representing an interaction obtained by multiplying several :class:`~pysic.interactions.local.Potential` objects.
"""
def __init__(self,potentials):
self.potentials = []
self.n_targets = None
for pot in potentials:
self.add_potential(pot)
def __eq__(self,other):
try:
if self.n_targets != other.n_targets:
return False
if self.potentials != other.potentials:
return False
except:
return False
return True
def __ne__(self,other):
return not self.__eq__(other)
[docs] def set_potentials(self,potentials):
self.potentials = []
self.n_targets = None
for pot in potentials:
self.add_potential(pot)
[docs] def add_potential(self,potential):
if self.n_targets is None:
self.n_targets = potential.get_number_of_targets()
elif self.n_targets != potential.get_number_of_targets():
raise InvalidPotentialError("You may only multiply potentials with the same number of targets.")
self.potentials.append(copy.deepcopy(potential))
[docs] def get_symbols(self):
return self.potentials[0].get_symbols()
[docs] def get_indices(self):
return self.potentials[0].get_indices()
[docs] def get_different_symbols(self):
return self.potentials[0].get_different_symbols()
[docs] def get_different_indices(self):
return self.potentials[0].get_different_indices()
[docs] def get_cutoff(self):
return self.potentials[0].get_cutoff()
[docs] def get_cutoff_margin(self):
return self.potentials[0].get_cutoff_margin()
[docs] def get_soft_cutoff(self):
return self.potentials[0].get_soft_cutoff()
[docs] def get_number_of_targets(self):
return self.n_targets
[docs] def get_coordinator(self):
return self.potentials[0].get_coordinator()
[docs] def set_coordinator(self, coordinator):
self.potentials[0].set_coordinator(coordinator)
[docs] def set_symbols(self, symbols):
self.potentials[0].set_symbols(symbols)
[docs] def set_indices(self, indices):
self.potentials[0].set_indices(indices)
[docs] def add_symbols(self, symbols):
self.potentials[0].add_symbols(symbols)
[docs] def add_indices(self, indices):
self.potentials[0].add_indices(indices)
[docs] def set_cutoff(self, cutoff):
self.potentials[0].set_cutoff(cutoff)
[docs] def set_cutoff_margin(self, cutoff_margin):
self.potentials[0].set_cutoff_margin(cutoff_margin)
[docs] def set_soft_cutoff(self, cutoff):
self.potentials[0].set_soft_cutoff(cutoff)
[docs] def get_potentials(self):
return self.potentials
[docs] def is_multiplier(self):
return [False] + [True] * (len(self.potentials)-1)
[docs]class Potential:
"""Class for representing a potential.
Several types of potentials can be defined by specifying the type of the potential
as a keyword. The potentials contain a host of parameters and information on
what types of particles they act on. To view a list of available potentials, use the method
:meth:`~pysic.list_valid_potentials`.
A potential may be a pair or many-body potential: here, the bodies a potential
acts on are called targets. Thus specifying the number of targets of a potential also
determines if the potential is a many-body potential.
A potential may be defined for atom types or specifically for certain atoms. These are
specified by the symbols, tags, and indices. Each of these should be either 'None' or
a list of lists of n values where n is the number of targets. For example, if::
indices = [[0, 1], [1, 2], [2, 3]]
the potential will be applied between atoms 0 and 1, 1 and 2, and 2 and 3.
Parameters:
symbols: list of string
the chemical symbols (elements) on which the potential acts
tags: integer
atoms with specific tags on which the potential acts
indices: list of integers
atoms with specific indices on which the potential acts
potential_type: string
a keyword specifying the type of the potential
parameters: list of doubles
a list of parameters for characterizing the potential; their meaning depends on the type of potential
cutoff: double
the maximum atomic separation at which the potential is applied
"""
def __init__(self,potential_type,symbols=None,tags=None,indices=None,parameters=None,cutoff=0.0,cutoff_margin=0.0,coordinator=None):
if(is_valid_potential(potential_type)):
self.symbols = None
self.tags = None
self.indices = None
self.potential_type = potential_type
self.cutoff = cutoff
self.cutoff_margin = 0.0
self.coordinator = None
self.set_cutoff_margin(cutoff_margin)
self.n_targets = number_of_targets(potential_type)
self.names_of_params = names_of_parameters(potential_type)
self.set_symbols(symbols)
self.set_tags(tags)
self.set_indices(indices)
if(parameters == None):
self.parameters = len(self.names_of_params)*[0.0]
else:
self.parameters = parameters
if(len(self.names_of_params) != len(self.parameters)):
raise InvalidPotentialError(
'The potential "{pot}" requires {num} parameters: {par}'.format(
pot=potential_type,
num=str(number_of_parameters(potential_type)),
par=str(names_of_parameters(potential_type))
) )
self.set_coordinator(coordinator)
else:
raise InvalidPotentialError('There is no potential called "{pot}".'.format(pot=potential_type))
def __eq__(self,other):
try:
if self.symbols != other.symbols:
return False
if self.tags != other.tags:
return False
if self.indices != other.indices:
return False
if self.potential_type != other.potential_type:
return False
if self.parameters != other.parameters:
return False
if self.cutoff != other.cutoff:
return False
if self.coordinator != other.coordinator:
return False
except:
return False
return True
def __ne__(self,other):
return not self.__eq__(other)
def __repr__(self):
return ("Potential({name},symbols={symbs},"+ \
"tags={tags},indices={inds},parameters={params}"+ \
",cutoff={cut},cutoff_margin={marg},"+ \
"coordinator={coord})").format(name=self.potential_type,
symbs=str(self.symbols),
tags=str(self.tags),
inds=str(self.indices),
params=str(self.parameters),
cut=str(self.cutoff),
marg=str(self.cutoff_margin),
coord=str(self.coordinator))
[docs] def get_symbols(self):
"""Return a list of the chemical symbols (elements) on which the potential
acts on."""
return self.symbols
[docs] def get_indices(self):
"""Return a list of indices on which the potential acts on. """
return self.indices
[docs] def get_different_symbols(self):
"""Returns a list containing each symbol the potential affects once."""
all_symbols = []
if self.symbols == None:
return all_symbols
for symblist in self.symbols:
for symb in symblist:
if all_symbols.count(symb) == 0:
all_symbols.append(symb)
return all_symbols
[docs] def get_different_indices(self):
"""Returns a list containing each index the potential affects once."""
all_indices = []
if self.indices == None:
return all_indices
for indicelist in self.indices:
for index in indicelist:
if all_indices.count(index) == 0:
all_indices.append(index)
return all_indices
[docs] def get_parameter_values(self):
"""Returns a list containing the current parameter values of the potential."""
return self.parameters
[docs] def get_parameter_names(self):
"""Returns a list of the names of the parameters of the potential."""
return self.names_of_params
[docs] def get_parameter_value(self,param_name):
"""Returns the value of the given parameter.
Parameters:
param_name: string
name of the parameter
"""
return self.parameters[index_of_parameter(self.potential_type,param_name)]
[docs] def get_potential_type(self):
"""Returns the keyword specifying the type of the potential."""
return self.potential_type
[docs] def get_cutoff(self):
"""Returns the cutoff."""
return self.cutoff
[docs] def get_cutoff_margin(self):
"""Returns the margin for a smooth cutoff."""
return self.cutoff_margin
[docs] def get_soft_cutoff(self):
"""Returns the lower limit for a smooth cutoff."""
return self.cutoff-self.cutoff_margin
[docs] def get_number_of_targets(self):
"""Returns the number of targets."""
return self.n_targets
[docs] def accepts_target_list(self,targets):
"""Tests whether a list is suitable as a list of targets, i.e., symbols, tags, or indices
and returns True or False accordingly.
A list of targets should be of the format::
targets = [[a, b], [c, d]]
where the length of the sublists must equal the number of targets.
It is not tested that the values contained in the list are valid.
Parameters:
targets: list of strings or integers
a list whose format is checked
"""
n_targets = self.get_number_of_targets()
try:
for a_set in targets:
assert isinstance(a_set,list)
if len(a_set) != n_targets:
return False
return True
except:
return False
[docs] def get_coordinator(self):
"""Returns the Coordinator.
"""
return self.coordinator
[docs] def set_coordinator(self,coordinator):
"""Sets a new Coordinator.
"""
self.coordinator = coordinator
[docs] def set_symbols(self,symbols):
"""Sets the list of symbols to equal the given list.
Parameters:
symbols: list of strings
list of element symbols on which the potential acts
"""
if symbols == None:
return
if(self.accepts_target_list(symbols)):
self.symbols = symbols
elif(self.accepts_target_list([symbols])):
self.symbols = [symbols]
else:
raise InvalidPotentialError("Invalid number of targets.")
[docs] def set_indices(self,indices):
"""Sets the list of indices to equal the given list.
Parameters:
indices: list of integers
list of integers on which the potential acts
"""
if indices == None:
return
if(self.accepts_target_list(indices)):
self.indices = indices
elif(self.accepts_target_list([indices])):
self.indices = [indices]
else:
raise InvalidPotentialError("Invalid number of targets.")
[docs] def add_symbols(self,symbols):
"""Adds the given symbols to the list of symbols.
Parameters:
symbols: list of strings
list of additional symbols on which the potential acts
"""
if(self.accepts_target_list(symbols)):
if self.symbols == None:
self.symbols = []
for stuff in symbols:
self.symbols.append(stuff)
elif(self.accepts_target_list([symbols])):
if self.symbols == None:
self.symbols = []
self.symbols.append(symbols)
else:
raise InvalidPotentialError("Invalid number of targets.")
[docs] def add_indices(self,indices):
"""Adds the given indices to the list of indices.
Parameters:
indices: list of integers
list of additional indices on which the potential acts
"""
if(self.accepts_target_list(indices)):
if self.indices == None:
self.indices = []
for stuff in indices:
self.indices.append(stuff)
elif(self.accepts_target_list([indices])):
if self.indices == None:
self.indices = []
self.indices.append(indices)
else:
raise InvalidPotentialError("Invalid number of targets.")
[docs] def set_parameters(self, values):
"""Sets the numeric values of all parameters.
Equivalent to :meth:`~pysic.Potential.set_parameter_values`.
Parameters:
values: list of doubles
list of values to be assigned to parameters
"""
self.set_parameter_values(values)
[docs] def set_parameter_values(self,values):
"""Sets the numeric values of all parameters.
Parameters:
values: list of doubles
list of values to be assigned to parameters
"""
if len(values) == number_of_parameters(self.potential_type):
self.parameters = values
else:
raise InvalidPotentialError("The potential '{pot}' takes {n_par} parameters, not {n_in}.".
format(pot=self.potential_type,
n_par=number_of_parameters(self.potential_type),
n_in=len(values)))
[docs] def set_parameter_value(self,parameter_name,value):
"""Sets a given parameter to the desired value.
Parameters:
parameter_name: string
name of the parameter
value: double
the new value of the parameter
"""
index = index_of_parameter(self.potential_type,parameter_name)
if index > -1:
self.parameters[index] = value
else:
raise InvalidPotentialError("The potential '{pot}' does not have a parameter '{para}'".
format(pot=self.potential_type,para=parameter_name))
[docs] def set_cutoff(self,cutoff):
"""Sets the cutoff to a given value.
This method affects the hard cutoff.
For a detailed explanation on how to define a soft cutoff, see :meth:`~pysic.Potential.set_cutoff_margin`.
Parameters:
cutoff: double
new cutoff for the potential
"""
self.cutoff = cutoff
[docs] def set_cutoff_margin(self,margin):
"""Sets the margin for smooth cutoff to a given value.
Many potentials decay towards zero in infinity, but in a numeric simulation
they are cut at a finite range as specified by the cutoff radius. If the potential
is not exactly zero at this range, a discontinuity will be introduced.
It is possible to avoid this by including a smoothening factor in the potential
to force a decay to zero in a finite interval.
This method defines the decay interval :math:`r_\mathrm{hard}-r_\mathrm{soft}`.
Note that if the soft cutoff value is made smaller than 0 or larger than the
hard cutoff value an :class:`~pysic.InvalidPotentialError` is raised.
Parameters:
margin: double
The new cutoff margin
"""
if margin < 0.0:
raise InvalidPotentialError("A negative cutoff margin of {marg} was given.".format(marg=str(margin)))
if margin > self.cutoff:
raise InvalidPotentialError("A cutoff margin ({marg}) longer than the hard cutoff ({hard}) was given.".format(marg=str(margin), hard=str(self.cutoff)))
self.cutoff_margin = margin
[docs] def set_soft_cutoff(self,cutoff):
"""Sets the soft cutoff to a given value.
For a detailed explanation on the meaning of a soft cutoff, see
:meth:`~pysic.Potential.set_cutoff_margin`.
Note that actually the cutoff margin is recorded, so changing the
hard cutoff (see :meth:`~pysic.Potential.set_cutoff`) will also affect the
soft cutoff.
Parameters:
cutoff: double
The new soft cutoff
"""
self.set_cutoff_margin(self.cutoff-cutoff)
[docs] def get_potentials(self):
return [self]
[docs] def is_multiplier(self):
return [False]
[docs] def describe(self):
"""Prints a short description of the potential using the method :meth:`~pysic.describe_potential`."""
description_of_potential(self.potential_type,
self.parameters,
self.cutoff,
self.symbols,
self.tags,
self.indices)