Source code for susmost.sitestate

from math import exp
from .savexyz import shift_xyz_data
from .zmatrix import ZItem
from copy import deepcopy
import numpy as np


class StatePropMetaInfo:
	def __init__(self, pname, merge_func = None, join_func = None, 
					cmp_mode = 'indiff', cmp_func = None):
		self.pname = pname
		self.merge_func = merge_func
		self.join_func = join_func
		self.cmp_mode = cmp_mode
		self.cmp_func = cmp_func
		
	def is_mergable(self):
		return self.merge_func is not None

	def compare(self, v1, v2):
		if self.cmp_mode == '==':
			return v1 == v2
		elif self.cmp_mode == 'cmp_func':
			return self.cmp_func(v1, v2)
		elif self.cmp_mode == 'indiff': # indifferent
			return True
		else:
			assert False, self.cmp_mode

	def __repr__(self):
		return "SPMI " + self.pname + ' ' + str(self.__dict__)
		
	def __eq__(self, other):
		# print 'SPMI __cmp__ ', len(self.__dict__)  , len(other.__dict__)
		for k in self.__dict__:
			if self.__dict__[k] != other.__dict__[k]:
				print('SPMI __cmp__ ', k, self.__dict__[k], other.__dict__[k])
				return False
		return len(self.__dict__) == len(other.__dict__)

class StateProp:
	def __init__(self, name, value):
		self.value = value
		self.meta = SPMI_db[name]

	def equal(self, other):
		assert self.meta == other.meta, (self.meta, other.meta, default_props_meta)
		return self.meta.compare(self.value, other.value)

	def __getstate__(self):
		return (self.meta.pname, self.value)

	def __setstate__(self, state):
		pname,self.value = state
		self.meta = SPMI_db[pname]

	def __repr__(self):
		return str(self.meta.pname) + '=' + str(self.value)


def merge_weighted_avg(values, energies, beta):
	Z = sum([exp(-E*beta) for E in energies])
	return sum([exp(-E*beta)*v for v,E in zip(values, energies)])/Z
	
def merge_most_stable(values, energies, beta):
	i = np.argmin(energies)
	return values[i]

def join_sum(values, energies, shifts):
	return sum(values)

def join_avg(values, energies, shifts):
	return sum(values)/len(values)
	
def join_cover(values, energies, shifts):

	points = set() # shifts may repeat in case of co-existing adsorption complexes
	               # for example fcc/hcp hollow sites with zero shifts
	for x,y,z in shifts:
		p = round(x,3),round(y,3)
		points.add(p)

	return sum(values)/len(points)


def func_concat(values, sortkeys, separator):
	if len(values) == 1:
		return values[0]
	values_sorted = [str(v) for v,s in sorted(zip(values,sortkeys), key=lambda v_s:v_s[1] )]
	return "[" + separator.join(values_sorted) + "]"

def join_xyz_shifted(values, energies, shifts):
	return sum([shift_xyz_data(v,s) for v, s in zip(values, shifts)], [])


SPMI_db = {
	'name':	StatePropMetaInfo('name',
		merge_func = lambda values, energies, beta : func_concat(values, energies, "+"),
		join_func = lambda values, energies, shifts : func_concat(values,[tuple(s) for s in shifts], ":")
	),
	
	'xyz_mark':	StatePropMetaInfo('xyz_mark',
		merge_func = merge_most_stable,
		join_func = join_xyz_shifted
	),
	
	'ads_energy':	StatePropMetaInfo('ads_energy',
		merge_func = merge_weighted_avg,
		join_func = join_sum,
		cmp_mode = '==',
		cmp_func = lambda x,y : abs(x-y)/(abs(x) + abs(y)) < 1E-6
	),
	
	'coverage':	StatePropMetaInfo('coverage',
		merge_func = merge_weighted_avg,
		join_func = join_cover,
		cmp_mode = '==',
		cmp_func = lambda x,y : abs(x-y)/(abs(x) + abs(y)) < 1E-6
	),

	'idx':	StatePropMetaInfo('idx',
		merge_func = lambda values, energies, beta : func_concat(values, energies, "+"),
		join_func = lambda values, energies, shifts : func_concat(values,[tuple(s) for s in shifts], ":")
	)
}

def register_properties(*names):
	'''
		Register several state properties
		
		Parameters:
			:names: list of property names
	'''
	for n in names:
		register_property(n)

[docs] def register_property(name, cmp_func="==", merge_func=None, join_func=None): ''' Register new state property Parameters: :name: name of the property ''' assert name not in SPMI_db, name if cmp_func in ['==', 'indiff']: cmp_mode = cmp_func cmp_func = None elif cmp_func is not None: cmp_mode = 'cmp_func' else: assert False, cmp_func if merge_func is None: merge_func = merge_weighted_avg elif merge_func == 'no-merge': merge_func = None if join_func is None: join_func = join_sum spmi = StatePropMetaInfo(name, merge_func, join_func, cmp_mode, cmp_func) SPMI_db[name] = spmi
[docs] class SiteStateType: """ Descriptor of a lattice site state. """ def __init__(self, name, configuration=None, ads_energy=None, xyz_mark=None, **kwargs): """ Parameters: :name: name of the state :configuration: geometry of the state. `Internal coordinates <https://en.wikipedia.org/wiki/Z-matrix_(chemistry)>` of lattice sites spanned by the state. See functions ``make_dimer_zmatrix()``, make_trimer_zmatrix()`` etc :ads_energy: energy of the state, can be consdidered as chemical potential of the surface or adsorption energy. :xyz_mark: list of chemical element symbols, one element for each site of the ``configuration``. Used for visualization purposes only. For example for trajectory generation by ``mc.run()`` function. :**kwargs: other properties of the state. All states of the model must have the same set of properties. New properties must be registered with function ``register_property()``. Property ``coverage``is registered by default. """ self.props = dict() if configuration is not None and isinstance(configuration[0], ZItem): self.zmatrix = configuration self.xyz_mark = xyz_mark self.props['name'] = StateProp('name', name) #self.props['xyz_mark'] = None # will be filled in LatticeConfig self.props['ads_energy'] = StateProp('ads_energy', ads_energy) for pname in kwargs: self.props[pname] = StateProp(pname, kwargs[pname]) @property def name(self): return self.props['name'].value @property def ads_energy(self): return self.props['ads_energy'].value @ads_energy.setter def ads_energy(self, value): self.props['ads_energy'].value = value def __repr__(self): return "SST {}".format(self.props['name'])