""" OrderedSelectors contains GroupSelector classes of defined order of selection. .. inheritance-diagram:: fullrmc.Selectors.OrderedSelectors :parts: 1 """ # standard libraries imports from __future__ import print_function import re # external libraries imports import numpy as np # fullrmc imports= from ..Globals import INT_TYPE, FLOAT_TYPE, LOGGER from ..Globals import str, long, unicode, bytes, basestring, range, xrange, maxint from ..Core.Collection import is_integer, is_number from ..Core.GroupSelector import GroupSelector class DefinedOrderSelector(GroupSelector): """ DefinedOrderSelector is a group selector with a defined order of selection. :Parameters: #. engine (None, fullrmc.Engine): The selector stochastic engine. #. order (None, list, set, tuple, numpy.ndarray): The selector order of groups. If None, order is set automatically to all groups indexes list. .. code-block:: python # import external libraries import numpy as np # import fullrmc modules from fullrmc.Engine import Engine from fullrmc.Selectors.OrderedSelectors import DefinedOrderSelector # create engine ENGINE = Engine(path='my_engine.rmc') # set pdb file ENGINE.set_pdb('system.pdb') # Add constraints ... # Re-define groups if needed ... # Re-define groups generators as needed ... ##### set the order of selection from closest to the origin to the further. ##### # compute groups centers centers = [np.sum(ENGINE.realCoordinates[g.indexes], axis=0)/len(g) for g in ENGINE.groups] # compute distances to origin distances = [np.sqrt(np.add.reduce(c**2)) for c in centers] # compute increasing order order = np.argsort(distances) # set group selector ENGINE.set_group_selector( DefinedOrderSelector(engine=ENGINE, order=order) ) """ def __init__(self, engine, order=None): # initialize GroupSelector super(DefinedOrderSelector, self).__init__(engine=engine) # set order self.set_order(order) # initialize selector self.__initialize_selector__() def _codify__(self, name='selector', engine=None, addDependencies=True): assert isinstance(name, basestring), LOGGER.error("name must be a string") assert re.match('[a-zA-Z_][a-zA-Z0-9_]*$', name) is not None, LOGGER.error("given name '%s' can't be used as a variable name"%name) assert engine is not None, LOGGER.error("codifying '%s' requires engine variable name"%self.__class__.__name__) assert isinstance(engine, basestring), LOGGER.error("engine must be a string") assert re.match('[a-zA-Z_][a-zA-Z0-9_]*$', engine) is not None, LOGGER.error("given engine '%s' can't be used as a variable name"%name) dependencies = 'from fullrmc.Selectors import OrderedSelectors' code = [] if addDependencies: code.append(dependencies) code.append("{name} = OrderedSelectors.DefinedOrderSelector(engine={engine}, order={order})" .format(name=name, engine=engine, order=self.order)) # return return [dependencies], '\n'.join(code) def __initialize_selector__(self): if self.__order is None: self.__index = None else: self.__index = 0 def _runtime_initialize(self): """ Automatically sets the selector order at the engine runtime. """ assert self.engine is not None, LOGGER.error("engine must be set prior to calling _runtime_initialize") if self.__order is None: self.__order = np.array(range(len(self.engine.groups)), dtype=INT_TYPE) self.__initialize_selector__() else: assert max(self.__order) < len(self.engine.groups), LOGGER.error("Groups in engine are modified, must re-set GroupSelector order using set_order method") @property def order(self): """ List copy of the order of selection.""" if self.__order is None: order = None else: order = list(self.__order) return order @property def index(self): """The current selection index.""" return self.__index def set_order(self, order): """ Set selector groups order. :Parameters: #. order (None, list, set, tuple, numpy.ndarray): The selector order of groups. """ if order is None: newOrder = None else: assert isinstance(order, (list, set, tuple, np.ndarray)), LOGGER.error("order must a instance among list, set, tuple or numpy.ndarray") if isinstance(order, np.ndarray): assert len(order.shape)==1, LOGGER.error("order numpy.ndarray must have one dimension") order = list(order) assert len(order)>0, LOGGER.error("order can't be empty") newOrder = [] for idx in order: assert is_integer(idx), LOGGER.error("order indexes must be integers") idx = int(idx) assert idx>=0, LOGGER.error("order indexes must be positive") assert idx<len(self.engine.groups), LOGGER.error("order indexes must be smaller than engine's number of groups") newOrder.append(idx) newOrder = np.array(newOrder, dtype=INT_TYPE) # set order self.__order = newOrder # re-initialize selector self.__initialize_selector__() def select_index(self): """ Select index. :Returns: #. index (integer): The selected group index in engine groups list. """ # get group index groupIndex = self.__order[self.__index%len(self.__order)] # update order index self.__index += 1 # return group index return groupIndex class DirectionalOrderSelector(DefinedOrderSelector): """ DirectionalOrderSelector is a group selector with a defined order of selection. The order of selection is computed automatically at engine runtime by computing Groups distance to center, and setting the order from the further to the closest if expand argument is True or from the closest to the further if expand is False. :Parameters: #. engine (None, fullrmc.Engine): The selector stochastic engine. #. center (None, list, set, tuple, numpy.ndarray): The center of expansion. If None, the center is automatically set to the origin (0,0,0). #. expand (bool): Whether to set the order from the the further to the closest or from the closest to the further if it is set to False. #. adjustMoveGenerators (bool): If set to True, all groups move generator instances will be changed automatically at engine runtime to a MoveGeneratorCollector of TranslationTowardsCenterGenerator and a randomRotation (for only more than 2 atoms groups). Generators parameters can be given by generatorsParams. It is advisable to set this flag to True in order to take advantage of an automatic and intelligent directional moves. #. generatorsParams (None, dict): The automatically created moves generators parameters. If None is given, default parameters are used. If a dictionary is given, only two keys are allowed. 'TG' key is for TranslationTowardsCenterGenerator parameters and 'RG' key is for RotationGenerator parameters. TranslationTowardsCenterGenerator amplitude parameter is not the same for all groups but intelligently allowing certain groups to move more than others according to damping parameter. **Parameters are the following:**\n * TG_amp = generatorsParams['TG']['amplitude']: Used for TranslationTowardsCenterGenerator amplitude parameters. * TG_ang = generatorsParams['TG']['angle']: Used as TranslationTowardsCenterGenerator angle parameters. * TG_dam = generatorsParams['TG']['damping']: Also used for TranslationTowardsCenterGenerator amplitude parameters. * RG_ang = generatorsParams['RG']['amplitude']: Used as RotationGenerator angle parameters. **Parameters are used as the following:**\n * TG = TranslationTowardsCenterGenerator(center={"fixed":center}, amplitude=AMPLITUDE, angle=TG_ang)\n Where TG_amp < AMPLITUDE < TG_amp.TG_dam * RG = RotationGenerator(amplitude=RG_ang) * MoveGeneratorCollector(collection=[TG,RG], randomize=True) **NB: The parameters are not checked for errors until engine runtime.** .. raw:: html <iframe width="560" height="315" src="https://www.youtube.com/embed/6nsNJrOhLu4?rel=0" frameborder="0" allowfullscreen> </iframe> .. code-block:: python # import fullrmc modules from fullrmc.Engine import Engine from fullrmc.Selectors.OrderedSelectors import DirectionalOrderSelector # create engine ENGINE = Engine(path='my_engine.rmc') # set pdb file ENGINE.set_pdb('system.pdb') # Add constraints ... # Re-define groups if needed ... # Re-define groups generators as needed ... # Set the order of selection from further to the closest to a (1,1,1). # Automatically adjust the groups move generators allowing modulation of amplitudes. ENGINE.set_group_selector( DirectionalOrderSelector(engine = ENGINE, center = (1,1,1), adjustMoveGenerators = True) ) """ def __init__(self, engine, center=None, expand=True, adjustMoveGenerators=False, generatorsParams={"TG":{"amplitude":0.1, "damping":0.1, "angle":90}, "RG":{"amplitude":10}}): # initialize GroupSelector super(DirectionalOrderSelector, self).__init__(engine=engine, order=None) # set center self.set_center(center) # set expand self.set_expand(expand) # set expand self.set_adjust_move_generators(adjustMoveGenerators) # set expand self.set_generators_parameters(generatorsParams) def _codify__(self, name='selector', engine=None, addDependencies=True): assert isinstance(name, basestring), LOGGER.error("name must be a string") assert re.match('[a-zA-Z_][a-zA-Z0-9_]*$', name) is not None, LOGGER.error("given name '%s' can't be used as a variable name"%name) assert engine is not None, LOGGER.error("codifying '%s' requires engine variable name"%self.__class__.__name__) assert isinstance(engine, basestring), LOGGER.error("engine must be a string") assert re.match('[a-zA-Z_][a-zA-Z0-9_]*$', engine) is not None, LOGGER.error("given engine '%s' can't be used as a variable name"%engine) dependencies = 'from fullrmc.Selectors import OrderedSelectors' code = [] if addDependencies: code.append(dependencies) code.append("{name} = OrderedSelectors.DirectionalOrderSelector(\ engine={engine}, center={center}, expand={expand}, \ adjustMoveGenerators={adjustMoveGenerators}, \ generatorsParams={generatorsParams})" .format(name=name, engine=engine, center=list(self.__center), expand=self.__expand, adjustMoveGenerators=self.__adjustMoveGenerators, generatorsParams=self.__generatorsParams)) # return return [dependencies], '\n'.join(code) def _runtime_initialize(self): """ Automatically sets the selector order at the engine runtime. """ #diffs = np.array([(np.sum(self.engine.realCoordinates[g.indexes], axis=0)/len(g))-self.__center for g in self.engine.groups], dtype=FLOAT_TYPE) diffs = np.array([(np.sum(self.engine.realCoordinates[self.engine._atomsCollector.get_relative_indexes(g.indexes)], axis=0)/len(g))-self.__center for g in self.engine.groups], dtype=FLOAT_TYPE) # FIXED 2019-03-18 dists = np.array([np.sqrt(np.add.reduce(diff**2)) for diff in diffs]) order = np.argsort(dists).astype(INT_TYPE) if self.__expand: order = [o for o in reversed(order)] # set order self.set_order(order) # set groups move generators if self.__adjustMoveGenerators: from fullrmc.Core.MoveGenerator import MoveGeneratorCollector from fullrmc.Generators.Rotations import RotationGenerator from fullrmc.Generators.Translations import TranslationTowardsCenterGenerator TG_amp = self.__generatorsParams['TG']['amplitude'] TG_ang = self.__generatorsParams['TG']['angle'] TG_dam = self.__generatorsParams['TG']['damping'] RG_ang = self.__generatorsParams['RG']['amplitude'] maxDist = FLOAT_TYPE(np.max(dists)) TG_ampInterval = TG_amp-TG_amp*TG_dam for idx in range(len(self.engine.groups)): g = self.engine.groups[idx] damping = ((maxDist-dists[idx])/maxDist)*TG_ampInterval coll = [TranslationTowardsCenterGenerator(center={"fixed":self.__center}, amplitude=TG_amp-damping, angle=TG_ang, direction=not self.__expand)] if len(g) > 1: coll.append(RotationGenerator(amplitude=RG_ang)) mg = MoveGeneratorCollector(collection=coll, randomize=True) g.set_move_generator( mg ) @property def expand(self): """ expand flag.""" return self.__expand @property def center(self): """ center (X,Y,Z) coordinates.""" return self.__center @property def adjustMoveGenerators(self): """ adjustMoveGenerators flag.""" return self.__adjustMoveGenerators @property def generatorsParams(self): """ Automatic generators parameters.""" return self.__generatorsParams def set_generators_parameters(self, generatorsParams): """ Set move generators parameters. #. generatorsParams (None, dict): The automatically created moves generators parameters. If None is given, default parameters are used. If a dictionary is given, only two keys are allowed. 'TG' key is for TranslationTowardsCenterGenerator parameters and 'RG' key is for RotationGenerator parameters. TranslationTowardsCenterGenerator amplitude parameter is not the same for all groups but intelligently allowing certain groups to move more than others according to damping parameter. **Parameters are the following:**\n * TG_amp = generatorsParams['TG']['amplitude']: Used for TranslationTowardsCenterGenerator amplitude parameters. * TG_ang = generatorsParams['TG']['angle']: Used as TranslationTowardsCenterGenerator angle parameters. * TG_dam = generatorsParams['TG']['damping']: Also used for TranslationTowardsCenterGenerator amplitude parameters. * RG_ang = generatorsParams['RG']['amplitude']: Used as RotationGenerator angle parameters. **Parameters are used as the following:**\n * TG = TranslationTowardsCenterGenerator(center={"fixed":center}, amplitude=AMPLITUDE, angle=TG_ang)\n Where TG_amp < AMPLITUDE < TG_amp.TG_dam * RG = RotationGenerator(amplitude=RG_ang) * MoveGeneratorCollector(collection=[TG,RG], randomize=True) **NB: The parameters are not checked for errors until engine runtime.** """ if generatorsParams is None: generatorsParams = {} assert isinstance(generatorsParams, dict), LOGGER.error("generatorsParams must be a python dictionary") newGenParams = {"TG":{"amplitude":0.1, "damping":0.1, "angle":90}, "RG":{"amplitude":10}} # update TranslationTowardsCenterGenerator values for gkey in newGenParams: generatorsParams.setdefault(gkey, newGenParams[gkey]) #if not gkey in generatorsParams: # continue assert isinstance(generatorsParams[gkey], dict), LOGGER.error("generatorsParams value must be a python dictionary") for key in newGenParams[gkey]: newGenParams[gkey][key] = generatorsParams[gkey].get(key, newGenParams[gkey][key]) # check generatorsParams damping parameters assert is_number(generatorsParams["TG"]["damping"]), LOGGER.error("generatorsParams['TG']['damping'] must be a number") generatorsParams["TG"]["damping"] = FLOAT_TYPE(generatorsParams["TG"]["damping"]) assert generatorsParams["TG"]["damping"]>=0, LOGGER.error("generatorsParams['TG']['damping'] must be bigger than 0") assert generatorsParams["TG"]["damping"]<=1, LOGGER.error("generatorsParams['TG']['damping'] must be smaller than 1") # set generatorsParams self.__generatorsParams = newGenParams def set_center(self, center): """ Set the center. :Parameters: #. center (None, list, tuple, numpy.ndarray): The center of expansion. If None, the center is automatically set to the origin (0,0,0). """ if center is None: center = np.array((0,0,0), dtype=FLOAT_TYPE) else: assert isinstance(center, (list, tuple, np.ndarray)), LOGGER.error("center must a instance among list, tuple or numpy.ndarray") if isinstance(center, np.ndarray): assert len(center.shape)==1,LOGGER.error("center numpy.ndarray must have one dimension") center = list(center) assert len(center) == 3, LOGGER.error("center must have exactly three items") assert is_number(center[0]), LOGGER.error("center items must be numbers") assert is_number(center[1]), LOGGER.error("center items must be numbers") assert is_number(center[2]), LOGGER.error("center items must be numbers") center = np.array(([float(c) for c in center]), dtype=FLOAT_TYPE) # set center self.__center = center def set_expand(self, expand): """ Set expand. :Parameters: #. expand (bool): Whether to set the order from the the further to the closest or from the closest to the further if it is set to False. """ assert isinstance(expand, bool), LOGGER.error("expand must be boolean") self.__expand = expand def set_adjust_move_generators(self, adjustMoveGenerators): """ Set expand. :Parameters: #. adjustMoveGenerators (bool): If set to True, all groups move generator instances will be changed automatically at engine runtime to a MoveGeneratorCollector of TranslationTowardsCenterGenerator and a randomRotation (for only more than 2 atoms groups). Generators parameters can be given by generatorsParams. It is advisable to set this flag to True in order to take advantage of an automatic and intelligent directional moves. """ assert isinstance(adjustMoveGenerators, bool), LOGGER.error("adjustMoveGenerators must be boolean") self.__adjustMoveGenerators = adjustMoveGenerators